FII RISC-V3.01 CPU FII-PRA040 FII-PRX100-S FII-PRX100D PRX100 Risc-V Risc-V Core Risc-V Tutorial

C Programming (2) on RISCV FII-PRX100 (ARTIX-7, XC7A100T) XILINX FPGA Board with our FII-Risc-V CPU (RV32G2.0)

V1.0

Fraser Innovation inc

RISCV FII-PRX100 (ARTIX-7, XC7A100T) XILINX FPGA Board C Programming 2

Version Control

Version Date Description
1.0 10/17/2020 Initial Release

Copyright Notice:

© 2020 Fraser Innovation Inc ALL RIGHTS RESERVED

Without written permission of Fraser Innovation Inc, no unit or individual may extract or modify part of or all the contents of this manual. Offenders will be held liable for their legal responsibility.

Thank you for purchasing the FPGA development board. Please read the manual carefully before using the product and make sure that you know how to use the product correctly. Improper operation may damage the development board. This manual is constantly updated, and it is recommended that you download the latest version when using.

Official Shopping Website:

https://fpgamarketing.com/FII-PRX100-D-ARTIX-100T-XC7A100T-RISC-V-FPGA-Board-PRX100-D-1.htm

Contents

1. RISCV FII-PRX100 (ARTIX-7, XC7A100T) Xilinx FPGA Board 4

2. Schematics Analysis 6

3. Address Map 9

4. RISCV_seg_cnt Project 10

5. RISCV_button Project 23

6. RISCV_timer_irq Project 26

7. RISCV_seg_irq Project 39

8. Exercise 46

9. References 47

RISCV FII-PRX100 (ARTIX-7, XC7A100T) Xilinx FPGA Board

RISCV FII-PRX100 Xilinx FPGA board has two versions, one has SRAM, denotes as PRX100-S, the other has DDR instead of SRAM, denotes as PRX100-D (https://fpgamarketing.com/FII-PRX100-D-ARTIX-100T-XC7A100T-RISC-V-FPGA-Board-PRX100-D-1.htm). See Figure 1 and 2 for different versions of PRX100. The difference could be easily found by observing the highlighted area on board. DDR version has an obvious IC on the front marked as “DDR”.

 

Figure 1 PRX100-D, DDR version

Take PRX100-S as an example, the two interfaces JTAG and UART have been highlighted in Figure 2. Note to connect jumper J6 1-2 to use J4 or J5 as external download interface as shown in Figure 3. JTAG interface is used to program

FPGA. UART interface is used to output to the computer. Before using Freedom Studio to run the C program, the FPGA board needs to be programmed with RISCV verilog project.

Figure 2 PRX100-S, SRAM version

Figure 3 JTAG interface

Schematics Analysis

LEDs, buttons, and segment display will be used in the following projects, so an analysis of schematics is helpful to understand the variable assignments when writing the C programs.

  • LED

It can be seen that to lit the LEDs, the current must flow from the left side to the right side. Since the left side is a constant high voltage, the right side is FPGA wiring. Only if the FPGA wiring is in low voltage, the left side voltage is higher than the right side voltage, thus the current would flow through LEDs, and the LEDs will be lit. On the opposite, when FPGA wiring is in high voltage, both the two sides of LEDs are in high voltages, thus no current flowing through, and LEDs will remain off.

 

LED wiring of schematics
LED wiring of schematics

Figure 4 LED wiring of schematics

  • Button

In this case, buttons are considered as inputs. When pressing the push buttons, the PB will connect the left and right side wiring, otherwise, it can be considered as an open circuit. When the PB is pressed, and the connection is on, the VCC voltage will drop through a resistor, and then connects to ground. Thus FPGA wiring will connect to ground directly, thus in low voltage. If the PB is open circuit, there is no way VCC could flow, thus no current flow through the resistors, and the FPGA wiring will have the same voltage as VCC.

Figure 5 Button wiring of schematics

  • Segment Display

Segment display have two types of FPGA wiring, location selection and segment selection. Since location selection is connected to P-channel field-effect transistor (FET), the gate must be more negative than the source for a typically 0.78V [1], so that VCC can flow through the segment display. Thus to lit the segment display, location selection must be in low voltage. Similarly, if the segment selection is also in low voltage (assume the corresponding location selection is in low voltage, so that the VCC could flow through the segment display), so there is a voltage difference between segment selection and VCC, VCC could flow from the right side of the segment display to the left side. On the opposite, if segment selection is in high voltage, there is no voltage difference between the left and right side of the segment display, and thus the segment display will be off.

Figure 6 Segment display wiring of schematics

Address Map

Here is a screenshot of the address allocation, which is helpful when writing the C project, as shown in Figure 7.

Figure 7 Address allocation

RISCV_seg_cnt Project

As shown in Figure 8, firstly, select the tab of the Freedom Studio under File > Import, the import window will pop up. Under the General tab, choose Existing Projects into Workspace, and click Next. In Figure 9, browse for the designed projects, click Copy projects into workspace box, and click Finish.

Then RISCV_seg_cnt project will appear under Project Explorer tab as shown in Figure 10.

Figure 8 Import window (1)

Figure 9 Import window (2)

Figure 10 RISCV_seg_cnt in Project Explorer

After adding the project into the workspace, right click the project folder under Project Explorer, click Clean Project (do it every time before actually building the projects). Then click Build Project. After the console displays “ Build Finished”, click Refresh to refresh the project as shown in Figure 11 and Figure 12. Then to start debugging, right click the project, and select Debug As > Debug Configurations.

Figure 11 Clean, build and refresh the project

Figure 12 Debug

Figure 13 Debug Configurations

In the pop up windows as shown in Figure 13, set up the debug configuration as stated in C programming 1. Under GDB OpenOCD Debugging, add a new launch configuration by clicking the icon on the top left corner. Usually after building projects, with refresh, the C/C++ Application row will fill up the corresponding *.elf file automatically. If not, click Search Project under the blank, to search for the *.elf file. Or else, use Browse button to find the *.elf file under debug folder (this will be an absolute path). After all done of those, click Debug to load the program. Assuming the RISCV FII-PRX100-S XILINX FPGA Board is programmed with FII-RISCV3.01, the experiment phenomena can be observed that the segment display will continue counting.

Next, the detailed explanation of the software code to lit the segment display will be commented in each code block. They are as follows:

  • Platform.h

#ifndef __PLATFORM_H

#define __PLATFORM_H

#ifdef __cplusplus

extern “C” {

#endif

#include “fii_types.h”//data type definition

#include “fii_gpio.h”//gpio definition

#include “fii_uart.h”//uart definition

#include “fii_irq.h”//interrupt definition

#include “encoding.h”//parameter definition

 

#define RAM_ADDR   0x90000000    //define DTCM address

 

//define a convenient way to access the address

#define  GPIO_REG(offset)   (*(volatile unsigned int *)(GPIO_ADDR + offset))

#define  RAM_REG(offset)    (*(volatile unsigned int *)(RAM_ADDR + offset))

#define  UART_REG(offset)   (*(volatile unsigned int *)(UART_BASE + offset))

#define  TIME_REG(offset)   (*(volatile unsigned int *)(TIME_ADDR + offset))

 

#ifdef __cplusplus

}

#endif

#endif  // __PLATFORM_H

 

 

  • Fii_types.h

/*

C type      | Bytes in RV32  |  Bytes in RV64

char        |       1        |        1

short       |       2        |        2

int         |       4        |        4

long        |       4        |        8

long long   |       8        |        8

void*       |       4        |        8

float       |       4        |        4

double      |       8        |        8

long double |       16       |        16

*/

//declare FII data types, aim to eliminate the conflict when cooperating with others

#ifndef __FII_TYPES_H

#define __FII_TYPES_H

#ifdef __cplusplus

extern “C” {

#endif

 

#define  RV_TYPE  RV32 //define FII-RISCV CPU to be 32 bits

typedef unsigned int        uintptr_t;

typedef unsigned long long  u64_t;

typedef unsigned int        u32_t;

typedef unsigned short      u16_t;

typedef unsigned char       u8_t;

typedef long long           s64_t;

typedef int                 s32_t;

typedef short               s16_t;

typedef char                s8_t;

#if 0

 

#if (RV_TYPE == RV32)

typedef unsigned long       u32_t;

typedef long                s32_t;

#elif (RV_TYPE == RV64)

typedef unsigned long       u64_t;

typedef long                s64_t;

#endif

 

#endif

#ifdef __cplusplus

}

#endif

#endif  // __FII_TYPES_H

 

  • Fii_gpio.h

#ifndef __FII_GPIO_H

#define __FII_GPIO_H

#ifdef __cplusplus

extern “C” {

#endif

//declare the segment display fonts

//the actual value is related to FII-RISCV gpio definition

//EXAMPLE: 8-seg display

//SEG_C:0X39

//DP G F E D C B A

//0  0 1 1 1 0 0 1

#define  SEG_S   0x6D

#define  SEG_A   0x77

#define  SEG_B   0x7C

#define  SEG_C   0x39

#define  SEG_D   0x5E

#define  SEG_E   0x79

#define  SEG_F   0x71

#define  SEG_0   0x3F

#define  SEG_1   0x06

#define  SEG_2   0x5B

#define  SEG_3   0x4F

#define  SEG_4   0x66

#define  SEG_5   0x6D

#define  SEG_6   0x7D

#define  SEG_7   0x07

#define  SEG_8   0x7F

#define  SEG_9   0x67

#define  SEG_j   0x40

#define  SEG_p   0x80

#define  SEG__   0x00

#define BYTE_DELAY 0x00200000// macro definition

#define GPIO_ADDR  0xf0000000//define GPIO base address

//define offset address related to the GPIO base address

#define LED_VAL  0x00//LED value

#define LED_DIR  0x04//LED direction (I/O)

#define SEAT_VAL 0x08//segment location (one of six segment display)

#define SEAT_DIR 0x0C//segment location direction (I/O)

#define SEG_VAL  0x10//segment selection (A,B,C,D,E,F,G,DP)

#define SEG_DIR  0x14//segment selection direction (I/O)

#define BUT_VAL  0x18//button value

#define BUT_DIR  0x1C//button direction (I/O)

//===============================================

//===============================================

#define SEG_POS   0x07

#ifdef __cplusplus

}

#endif

#endif /* end __FII_GPIO_H */

  • Fii_uart.h
  • Main.c

#ifndef __FII_UART_H

#define __FII_UART_H

#ifdef __cplusplus

extern “C” {

#endif

//===============================================

#define UART_BASE  0xe0000000//define UART base address

//define offset address related to the UART base address

#define UART_VER  0x00//define UART version

#define UART_RUN  0x04//define UART enable

#define UART_DATA 0x08//define UART data

#define UART_RDY  0x0C//define UART ready (read only)

//===============================================

int send_to_uart(const void* ptr, int len);//declaration of UART send function

//===============================================

#ifdef __cplusplus

}

#endif

#endif /* __FII_UART_H */

#include <stdio.h> //include the standard I/O library, mainly used to declare printf function

#include “platform.h”//user defined function in C library, including GPIO configuration, definition of the memory,

//and related subfunctions and parameters

#define  NOP_DELAY  0x100//define a macro

const unsigned char font[] = //declaration of an array of indexing the segment display font, here hexadecimal (0~f) is used

{

SEG_0, SEG_1, SEG_2, SEG_3,

SEG_4, SEG_5, SEG_6, SEG_7,

SEG_8, SEG_9, SEG_A, SEG_B,

SEG_C, SEG_D, SEG_E, SEG_F

};

//function used to delay

void delay_cnt (int cnt)

{

u32_t  i;

for(i = 0; i < cnt ; i ++ )

asm(“nop”);

return;

};

int main(void)//define main function

{

//initialization segment display

unsigned int  curr_seat = 0x01;//segment display initial location (the right most segment display)

unsigned int  char_num = 0;//segment display initial value

unsigned int  curr_num = 0;//lit time for segment display

unsigned int  char_pos = 0;//array index for segment display font

//UART output

printf(“\r\nRiscV Program : Display segment number and print it. \r\n”);

//initialization GPIO

(*(volatile unsigned int *)(GPIO_ADDR + LED_VAL)) = ~0L;//led_value is 0, negation is applied for the LED wiring on schematics

(*(volatile unsigned int *)(GPIO_ADDR + LED_DIR)) = 0;//LED’s direction is output

(*(volatile unsigned int *)(GPIO_ADDR + SEAT_DIR)) = 0;//segment location value’s direction is output

(*(volatile unsigned int *)(GPIO_ADDR + SEG_DIR)) = 0;//segment selection value’s direction is output

while ( 1 )//main loop

{

(*(volatile unsigned int *)(GPIO_ADDR + SEG_VAL)) = ~font[char_pos & 0xf];//set the segment display index

//negation is applied for the wiring on schematics

(*(volatile unsigned int *)(GPIO_ADDR + SEAT_VAL)) = ~curr_seat;//set the segment display location

//negation is applied for the wiring on schematics

delay_cnt (NOP_DELAY);//set time delay

curr_seat = curr_seat << 1;//shift the lit segment display to the left 1 bit

if(curr_seat == 0x40) //decision making, if the segment display location is the left most

{

curr_seat = 0x01;//reset it to the right most

curr_num ++ ;//counter +1

 

if(curr_num % 1000 == 9)//prevent multiple print, print once every 1000 iterations

{

char_num ++ ;//segment display value +1

printf(“seg cnt num = 0x%06x \r\n”, char_num);

}

 

char_pos = char_num;//pass the segment display value to the index font

}

else char_pos = char_pos >> 4;//right shift the current segment display value by 4 (hexadecimal by 1 bit)

}

}

RISCV_button Project

Same as the previous steps, import the project into the Freedom Studio, as shown in Figure 14. The experiment phenomenon is that when pressing the buttons (only three buttons are implemented here: menu, up, and return), the corresponding segment display will change from ‘1’ to ‘0’.

Figure 14 RISCV_button project

Except for the main function of RISCV_button is different than that of RISCV_seg_cnt, the other header files are all the same. Therefore, only main.c file will be listed here. The detailed explanation will be commented in the code block.

  • Main.c

#include <stdio.h>//include the standard I/O library, mainly used to declare printf function

#include “platform.h”//user defined function in C library, including GPIO configuration, definition of the memory,

//and related subfunctions and parameters

#define NOP_DELAY 0x00000200//define a macro, the value is different than that in RISCV_seg_cnt. It can be modified according to

//the needs

int main(void)//main function for RISCV_button function

{

unsigned int  curr_seat = 0x01;//segment display initial location (the right most segment display)

volatile unsigned int  i;//used in the delay loop

unsigned int  curr_gpio_val;//read the button input

(*(volatile unsigned int *)(GPIO_ADDR + SEAT_DIR)) = 0;//segment location value’s direction is output

(*(volatile unsigned int *)(GPIO_ADDR + SEG_DIR)) = 0;//segment selection value’s direction is output

(*(volatile unsigned int *)(GPIO_ADDR + BUT_DIR)) = ~0L;//button direction is input (note there is a negation)

(*(volatile unsigned int *)(GPIO_ADDR + SEG_VAL)) = ~SEG__;//initialize the current segment display to be empty (no display)

//negation is applied for the segment display wiring on schematics

printf(“\r\nThis is a button test program. \r\n”);

while (1)//main loop

{

curr_gpio_val = (*(volatile unsigned int *)(GPIO_ADDR + BUT_VAL));//read the button input

(*(volatile unsigned int *)(GPIO_ADDR + SEAT_VAL)) = ~curr_seat;//set the segment display location

//negation is applied for the wiring on schematics

if(curr_gpio_val & curr_seat)//decision making, if it is not the corresponding location

//button and the corresponding segment display location are bitwise equal

//button is negation for the wiring on schematics

(*(volatile unsigned int *)(GPIO_ADDR + SEG_VAL)) = ~SEG_1;//display 1

else//if it is the corresponding location

(*(volatile unsigned int *)(GPIO_ADDR + SEG_VAL)) = ~SEG_0;//display 0

for(i = 0; i < NOP_DELAY; i ++ )//time delay for the segment display

asm(“nop”);

curr_seat = curr_seat << 1;//shift the lit segment display to the left 1 bit

if(curr_seat == 0x08) curr_seat = 0x01;//decision making, if the segment display location is exceeds the 3rd

//3 segment display corresponding 3 push buttons

}

}

 

RISCV_timer_irq Project

Before diving into the project, a simple introduction of interrupt should be mentioned. In digital computers, interrupts are the processor’s response to events that require software attention. The interrupt condition will warn the processor and use it as a request to the processor to interrupt the currently executing code when allowed so that the event can be processed in time. If the request is accepted, the processor will respond by suspending its current activity, saving its state, and executing a function called an interrupt handler (or interrupt service routine, ISR) to handle the event. The interrupt is temporary, and unless the interrupt indicates a fatal error, the processor will resume normal activity after the interrupt handler completes [2].

More complicatedly, processors may face the situation to handle multiple interrupts. In that case, interrupt priority level (IPL) should be considered. Usually, only one interrupt is allowed at one time, other interrupts shall wait until it completes. Sometimes, interrupts may occur during the interrupt handling, it is called nested interrupt.

RISCV_timer_irq avoids complicated interrupt occurrence. Machine timer interrupt is implemented here.

Import the project as the same steps with the previous projects. The corresponding Freedom Studio project explorer is shown in Figure 15.

Figure 15 RISCV_timer_irq

A new header file fii_irq.h is used here. Except for that, entry.S and main.c will be introduced as well. The rest of the header files are the same as the previous projects.

  • Fii_irq.h

#ifndef __FII_IRQ_H

#define __FII_IRQ_H

#ifdef __cplusplus

extern “C” {

#endif

#include “fii_types.h”

#include “encoding.h”

 

#define CLINT_CTRL_ADDR (0x02000000)//define kernel interrupt base address

#define PLIC_CTRL_ADDR  (0x0C000000)//define external interrupt base address

#define TIME_ADDR       CLINT_CTRL_ADDR  //timer interrupt address

 

#define RTC_FREQ        32768//RTC timer frequency

 

#define MCAUSE_INT      0x80000000//mcause bit 31 mask, decision making, ‘1’ is interrupt, ‘0’ is exception

#define MCAUSE_CAUSE    0x7FFFFFFF//mcause bit 30-0 mask, decision making, exception code

//here, machine timer interrupt exception code is ‘7’

 

//offset with respect to the kernel interrupt base address

#define CLINT_CTRL_REG  (0x0000 << 2)//0x0000, timer register

#define TM_CTRL_REG     (0x0001 << 2)//0x0004, timer control register

#define TM_L_REG        (0x0002 << 2)//0x0008, timer_val register lower 32 bits

#define TM_H_REG        (0x0003 << 2)//0x000c, timer_val register higher 32 bits

#define TMCMP_L_REG     (0x0004 << 2)//0x0010, timer cmp register lower 32 bits

#define TMCMP_H_REG     (0x0005 << 2)//0x0014, timer cmp register higher 32 bits

 

unsigned int handle_trap(unsigned int mcause, unsigned int epc);//define interrupt entrance function

 

#ifdef __cplusplus

}

#endif

#endif  // __FII_IRQ_H

It should be mentioned that the implementation of mcause register is following the definition in version 1.9 “RISC-V instruction set manual privileged architecture”. As shown in Figure 16.

Figure 16 mcause register (bit 31= interrupt, bit 30-0 =exception code)

The interrupt project will use entry.S file, which is used for invoking the interrupt entrance function. In the initialization function of the main program, the address of trap_entry will be assigned to the RISCV CPU control and status register (CSR) mtvec. When the interrupt actually happens, the RISCV CPU program counter (PC) will suspend from the main execution, and points to the address of trap_entry function instead. After the interrupt handler completes, PC resumes the main execution.

  • Entry.S

.equ REGBYTES,  (1 << 2)#define REGBYTES to be 4

#.file “encoding.h”

.section      .text.entry #define it to be the code

.align 2#2^2 = 4 bytes aligned

.global trap_entry#declaration of global function

trap_entry:#function actual starting point

ADDI sp, sp, -32*REGBYTES   # REGBYTES = 4 #free stack depth to store

#store x1-x31 register value to the stack

SW x1, 1*REGBYTES(sp)

SW x2, 2*REGBYTES(sp)

SW x3, 3*REGBYTES(sp)

SW x4, 4*REGBYTES(sp)

SW x5, 5*REGBYTES(sp)

SW x6, 6*REGBYTES(sp)

SW x7, 7*REGBYTES(sp)

SW x8, 8*REGBYTES(sp)

SW x9, 9*REGBYTES(sp)

SW x10, 10*REGBYTES(sp)

SW x11, 11*REGBYTES(sp)

SW x12, 12*REGBYTES(sp)

SW x13, 13*REGBYTES(sp)

SW x14, 14*REGBYTES(sp)

SW x15, 15*REGBYTES(sp)

SW x16, 16*REGBYTES(sp)

SW x17, 17*REGBYTES(sp)

SW x18, 18*REGBYTES(sp)

SW x19, 19*REGBYTES(sp)

SW x20, 20*REGBYTES(sp)

SW x21, 21*REGBYTES(sp)

SW x22, 22*REGBYTES(sp)

SW x23, 23*REGBYTES(sp)

SW x24, 24*REGBYTES(sp)

SW x25, 25*REGBYTES(sp)

SW x26, 26*REGBYTES(sp)

SW x27, 27*REGBYTES(sp)

SW x28, 28*REGBYTES(sp)

SW x29, 29*REGBYTES(sp)

SW x30, 30*REGBYTES(sp)

SW x31, 31*REGBYTES(sp)

#pass the arguments before (input a0, a1, a2) and after (return a0) calling function handle_trap

csrr a0, mcause#x[a0] = CSRs[mcause]

csrr a1, mepc#x[a1] = CSRs[mepc]

mv a2, sp#x[a2] = x[sp]

call handle_trap#invoke handle_trap function

csrw mepc, a0#CSRs[mepc] = x[a0]

#load x1-x31 register value from stack

LW x1, 1*REGBYTES(sp)

LW x2, 2*REGBYTES(sp)

LW x3, 3*REGBYTES(sp)

LW x4, 4*REGBYTES(sp)

LW x5, 5*REGBYTES(sp)

LW x6, 6*REGBYTES(sp)

LW x7, 7*REGBYTES(sp)

LW x8, 8*REGBYTES(sp)

LW x9, 9*REGBYTES(sp)

LW x10, 10*REGBYTES(sp)

LW x11, 11*REGBYTES(sp)

LW x12, 12*REGBYTES(sp)

LW x13, 13*REGBYTES(sp)

LW x14, 14*REGBYTES(sp)

LW x15, 15*REGBYTES(sp)

LW x16, 16*REGBYTES(sp)

LW x17, 17*REGBYTES(sp)

LW x18, 18*REGBYTES(sp)

LW x19, 19*REGBYTES(sp)

LW x20, 20*REGBYTES(sp)

LW x21, 21*REGBYTES(sp)

LW x22, 22*REGBYTES(sp)

LW x23, 23*REGBYTES(sp)

LW x24, 24*REGBYTES(sp)

LW x25, 25*REGBYTES(sp)

LW x26, 26*REGBYTES(sp)

LW x27, 27*REGBYTES(sp)

LW x28, 28*REGBYTES(sp)

LW x29, 29*REGBYTES(sp)

LW x30, 30*REGBYTES(sp)

LW x31, 31*REGBYTES(sp)

addi sp, sp, 32*REGBYTES#return the stack pointer to the original location

mret#resume the pc

.weak handle_trap

handle_trap:

1:

j 1b

 

As shown in Figure 17, the principle of stack used above is as follows:

1: the location of the stack pointer before the interrupt

2: just before entering the interrupt, the stack pointer is moved to -32 x 4 bytes location

3: store 31 registers value between 1-2, and the stack pointer stays at location 2

4: before the interrupt finishes, load the data between 3 and 4 back to the 31 registers, and move the stack pointer back to 4.

Note that after the whole procedure, the stack pointer returns to its origin location. Although the stack leaves space for all 32 registers, x0 is hardwired to 0, the value is not changing, so only 31 registers will be operated.

pop

32 x 4 bytes

push

Figure 17 Stack principle

  • Main.c

#include <stdio.h> //include the standard I/O library, mainly used to declare printf function

#include “platform.h”//user defined function in C library, including GPIO configuration, definition of the memory,

//and related subfunctions and parameters

extern void trap_entry();//declaration that trap_entry function is invoked from outside (entry.S)

void set_timer(u64_t msec)//define a function to set timer

{

//define 64 bits timers using 2*32 bits registers

u64_t now;//define 64 bits timer value ‘now’

now   = TIME_REG(TM_H_REG);//fill the high 32-bits

now   = now << 32;//move the high 32-bits into the right place

now   |= TIME_REG(TM_L_REG);//bitwise or with the lower 32 bits

//add time on the current timer, in milisec (msec), to get the 64 bits new timer ‘then’

u64_t then = now + msec*(RTC_FREQ/1000);//conversion from RTC_FREQ (32768) to msec (1000)

//write ‘then’ timer low 32 bits into timer compare register low 32 bits

TIME_REG( TMCMP_L_REG ) = then & 0xffffffff;

TIME_REG( TMCMP_H_REG ) = then >> 32;//write ‘then’ timer high 32 bits into timer compare register high 32 bits

 

return;

}

//timer counter

unsigned int time_cnt = 0;

/*Entry Point for Machine Timer Interrupt Handler*/

void handle_m_time_interrupt(){

clear_csr(mie, MIP_MTIP);//mie bit 7 is machine mode timer interrupt enable

// clear MIP_MTIP bit right after entering the interrupt handler

//prevent the nested interrupt

set_timer(250); // reset the timer for 250ms

// read the current value of the LEDs and invert them.

if((time_cnt % 4 ) == 0) // 1s, 250ms*4

{

GPIO_REG(LED_VAL) = ~((time_cnt >> 2) & 0xff);//display LEDs every 1s, only reserve the significant 8 bits

//only 8 bits LED (0xff), negation is from the LED wiring of schematics

printf(“time_irq = 0x%08x \r\n”, time_cnt); //print timer counter every 4 times entering the interrupt

}

time_cnt ++;//timer counter increment

// Re-enable the timer interrupt.

set_csr(mie, MIP_MTIP);//mie bit 7 is machine mode timer interrupt enable

// set MIP_MTIP bit before finishing the interrupt

}

//define a general interrupt entrance function

unsigned int handle_trap(unsigned int mcause, unsigned int epc)

{

//decision making

//whether it’s a interrupt or exception (mcause bit 31)

//whether the exception code is 7 (mcause bit 30-0)

//both satisfied, timer interrupt confirmed

if ((mcause & MCAUSE_INT) && ((mcause & MCAUSE_CAUSE) == IRQ_M_TIMER))

{

handle_m_time_interrupt();//invoke the interrupt handler

}

else

{

//print debug information

printf(“There is not any handle_trap available \n”);

printf(“mstatus    = 0x%08x\n” , read_csr(mstatus) );//mstatus: machine status register

printf(“mie        = 0x%08x\n” , read_csr(mie) );//mie: machine interrupt-enable register

printf(“mcause     = 0x%08x\n” , read_csr(mcause) );//mcause: machine trap cause

printf(“mtvec      = 0x%08x\n” , read_csr(mtvec)); //mtvec: machine trap-handler base address

 

}

return epc;

}

//initialization function

void _init(void)

{

time_cnt = 0;//initialize the timer counter

printf(“Program Initial… \n”);

 

write_csr(mtvec, &trap_entry);//assign the address of trap_entry to mtvec

//when interrupt begins, PC moves to trap_entry

 

return;

}

//initialization of LED

static void init_led(void)

{

GPIO_REG(LED_VAL) = ~0L;  //init LED output value is ‘0’

//negation is from the LED wiring of schematics

GPIO_REG(LED_DIR) = 0;    //init LED gpio direction is output

return;

}

int main(void)//main function

{

int  i = 0;//used for delay loop

 

_init();//initialization

 

printf(“\r\nRiscV Program : Invoke timer IRQ. \r\n”);

init_led();//LED initialization

set_timer(250);//first time set timer

set_csr(mie, MIP_MTIP);//enable timer interrupt

write_csr(mstatus, MSTATUS_MIE); // enable all machine mode interrupt

TIME_REG( TM_CTRL_REG ) = 0x80000001;//timer enable, and start to count using timer

 

while ( 1 )//loop

{

for(i = 0; i < 0x100000; i ++ )

asm(“nop”);//assembly operation ‘nop’ every 0x100000

}

}

RISCV_seg_irq Project

Next, timer interrupt project and segment display project are combined together, to be the RISCV_seg_irq project. Import the project following the same procedure as before. Only main.c file is slightly different than that in RISCV_timer_irq.

  • Main.c

#include <stdio.h> //include the standard I/O library, mainly used to declare printf function

#include “platform.h”//user defined function in C library, including GPIO configuration, definition of the memory,

//and related subfunctions and parameters

#define  NOP_DELAY  0x100//define a macro

extern void trap_entry();//declaration that trap_entry function is invoked from outside (entry.S)

void set_timer(u64_t msec)//define a function to set timer

{

//define 64 bits timers using 2*32 bits registers

u64_t now;//define 64 bits timer value ‘now’

now   = TIME_REG(TM_H_REG);//fill the high 32-bits

now   = now << 32;//move the high 32-bits into the right place

now   |= TIME_REG(TM_L_REG);//bitwise or with the lower 32 bits

//add time on the current timer, in milisec (msec), to get the 64 bits new timer ‘then’

u64_t then = now + msec*(RTC_FREQ/1000);//conversion from RTC_FREQ (32768) to msec (1000)

//write ‘then’ timer low 32 bits into timer compare register low 32 bits

TIME_REG( TMCMP_L_REG ) = then & 0xffffffff;

TIME_REG( TMCMP_H_REG ) = then >> 32;//write ‘then’ timer high 32 bits into timer compare register high 32 bits

 

return;

}

//timer counter

unsigned int time_cnt = 0;

/*Entry Point for Machine Timer Interrupt Handler*/

void handle_m_time_interrupt(){

clear_csr(mie, MIP_MTIP);//mie bit 7 is machine mode timer interrupt enable

// clear MIP_MTIP bit right after entering the interrupt handler

//prevent the nested interrupt

// Reset the timer for 3s in the future.

// This also clears the existing timer interrupt.

set_timer(500); // reset the timer for 500ms

// read the current value of the LEDs and invert them.

if((time_cnt % 4 ) == 0) // 2s, 500ms*4

{

GPIO_REG(LED_VAL) = ~((time_cnt >> 2) & 0xff);//display LEDs every 2s, only reserve the significant 8 bits

//only 8 bits LED (0xff), negation is from the LED wiring of schematics

printf(“time_irq = 0x%08x \r\n”, time_cnt); //print timer counter every 4 times entering the interrupt

}

time_cnt ++;//timer counter increment

// Re-enable the timer interrupt.

set_csr(mie, MIP_MTIP);//mie bit 7 is machine mode timer interrupt enable

// set MIP_MTIP bit before finishing the interrupt

}

//define a general interrupt entrance function

unsigned int handle_trap(unsigned int mcause, unsigned int epc)

{

//decision making

//whether it’s a interrupt or exception (mcause bit 31)

//whether the exception code is 7 (mcause bit 30-0)

//both satisfied, timer interrupt confirmed

if ((mcause & MCAUSE_INT) && ((mcause & MCAUSE_CAUSE) == IRQ_M_TIMER))

{

handle_m_time_interrupt();//invoke the interrupt handler

}

else

{

//print debug information

printf(“There is not any handle_trap available \n”);

printf(“mstatus    = 0x%08x\n” , read_csr(mstatus) );//mstatus: machine status register

printf(“mie        = 0x%08x\n” , read_csr(mie) );//mie: machine interrupt-enable register

printf(“mcause     = 0x%08x\n” , read_csr(mcause) );//mcause: machine trap cause

printf(“mtvec      = 0x%08x\n” , read_csr(mtvec)); //mtvec: machine trap-handler base address

 

}

return epc;

}

//initialization function

void _init(void)

{

time_cnt = 0;//initialize the timer counter

printf(“RiscV Program : Display segment number and invoke timer IRQ \r\n”);

write_csr(mtvec, &trap_entry);//assign the address of trap_entry to mtvec

//when interrupt begins, PC moves to trap_entry

 

//added GPIO address initialization

(*(volatile unsigned int *)(GPIO_ADDR + SEAT_DIR)) = 0;//location selection

(*(volatile unsigned int *)(GPIO_ADDR + SEG_DIR)) = 0;//segment selection

return;

}

//declaration of an array of indexing the segment display font

//here hexadecimal (0~f) is used

const unsigned char font[] =

{

SEG_0, SEG_1, SEG_2, SEG_3,

SEG_4, SEG_5, SEG_6, SEG_7,

SEG_8, SEG_9, SEG_A, SEG_B,

SEG_C, SEG_D, SEG_E, SEG_F

};

//initialization of LED

static void init_led(void)

{

GPIO_REG(LED_VAL) = ~0L;  //init LED output value is ‘0’

//negation is from the LED wiring of schematics

GPIO_REG(LED_DIR) = 0;    //init LED gpio direction is output

return;

}

//added segment display function

//takes arguments of segment display font and location selection

void display_seg(unsigned char font_idx, unsigned int font_seat)

{

(*(volatile unsigned int *)(GPIO_ADDR + SEG_VAL)) = ~(font[font_idx]);//display font, negation is from the segment wiring of schematics

(*(volatile unsigned int *)(GPIO_ADDR + SEAT_VAL)) = ~font_seat;//location selection, negation is from the segment wiring of schematics

return;

}

int main(void)//main function

{

int  i = 0;//used for delay loop

unsigned int  curr_seat = 0x01;

 

unsigned int  curr_seg = 0;

unsigned char out_char = 0;

_init();//initialization

 

printf(“\r\nRun Segment Timer IRQ Program \r\n”);

 

init_led();//LED initialization

set_timer(500);//first time set timer

set_csr(mie, MIP_MTIP);//enable timer interrupt

write_csr(mstatus, MSTATUS_MIE);// enable all machine mode interrupt

TIME_REG( TM_CTRL_REG ) = 0x80000001;//timer enable, and start to count using timer

 

while ( 1 )//main loop

{

display_seg(out_char, curr_seat);//invoke segment display function

for(i = 0; i < NOP_DELAY ; i ++ )

asm(“nop”);//assembly operation ‘nop’

curr_seat = curr_seat << 1;//shift the lit segment display to the left 1 bit

if(curr_seat == 0x40)//decision making, if the segment display location is the left most

{

curr_seg = time_cnt;//assign the timer counter to the segment display

curr_seat = 0x01;//reset it to the right most

}

else

curr_seg = curr_seg >> 4;//right shift the current segment display value by 4 (hexadecimal by 1 bit)

out_char = curr_seg & 0xf;//only display one hexadecimal number at one time

}

}

Exercise

  • Create a new C project which inputs menu, up, and return 3 buttons, and the corresponding segment display will show 1, 2 and 3 respectively.
  • Create a new C project to lit the LEDs from the right to the left (LED0 to LED7). When it reaches the left most LED (LED7), returns to the right (LED0). The lit time and the shift interval is approximately 1s. Use timer interrupt to accomplish it.
  • Modify the RISCV verilog project and program the FPGA board, to implement the 8 slide switches in the GPIO group. Create a new project to lit the LEDs when sliding up the corresponding switches.

References

[1]2020. [Online]. Available: https://www.mouser.ca/ProductDetail/ON-Semiconductor-Fairchild/NDS336P?qs=QwEULm8S1DpwQ6VqpkXnVA==. [Accessed: 21- Oct- 2020].

[2]”Interrupt | Wikiwand”, Wikiwand, 2020. [Online]. Available: https://www.wikiwand.com/en/Interrupt. [Accessed: 20- Oct- 2020].

 

Related posts