FII RISC-V3.01 CPU FII-PRX100-S FII-PRX100D FPGA Board Based FPGA Tutor PRX100 Risc-V Risc-V Core Risc-V Tutorial

C Programming (1) 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 1

Version Control

Version Date Description
1.0 10/24/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

Content

1. Introduction to FII-RISCV CPU 4

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

3. C Project Compilation Process 11

4. Using Freedom Studio to Create a New C Project 14

5. C Project Files Composition 30

6. Exercise 37

7. References 38

Introduction to FII-RISCV CPU

First of all, there is an overview of the CPU, FII-RISCV. RISCV is an open standard instruction set architecture (ISA) based on reduced instruction set computer (RISC) principles [1]. FII-RISCV is researched and developed following the RISCV standard. Here are some basic features about FII-RISCV:

  1. RV32I (32 registers that supports integer operations)
  2. Does not support multiplication instructions (the newest version do support)
  3. Does not support Atomic operations
  4. Does not support compressed instruction
  5. Supports software interrupt
  6. Supports timer interrupt
  7. Supports UART output (Peripherals)
  8. Supports 4 groups of GPIO interfaces (32 pins per group)
  9. 4K bytes DTCM
  10. 16K bytes ITCM

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

Figure 1 Address allocation

The GPIO configuration is shown below. The relevant code block is located in the top level entity, fii_cpu_sys.v.

wire [ 31: 0 ] gpio_ia;

wire [ 31: 0 ] gpio_oa;

wire [ 31: 0 ] gpio_ta;

wire [ 31: 0 ] gpio_a;

//IO buffer for LED

fii_iobuf #( .IO_WIDTH( 32 ) )

fii_iobuf_insta

(

.i_dio_t    ( gpio_ta ),

.i_dio      ( gpio_oa ),

.o_dio      ( gpio_ia ),

.io_dio_p   ( gpio_a )

);

assign LED = gpio_a[ IO_WIDTHa – 1: 0 ];

wire [ 31: 0 ] gpio_ib;

wire [ 31: 0 ] gpio_ob;

wire [ 31: 0 ] gpio_tb;

wire [ 31: 0 ] gpio_b;

//IO buffer for segment display location selection

fii_iobuf #( .IO_WIDTH( 32 ) )

fii_iobuf_instb

(

.i_dio_t    ( gpio_tb ),

.i_dio      ( gpio_ob ),

.o_dio      ( gpio_ib ),

.io_dio_p   ( gpio_b )

);

assign SEAT = gpio_b[ IO_WIDTHb – 1: 0 ];

wire [ 31: 0 ] gpio_ic;

wire [ 31: 0 ] gpio_oc;

wire [ 31: 0 ] gpio_tc;

wire [ 31: 0 ] gpio_c;

//IO buffer for segment selection

fii_iobuf #( .IO_WIDTH( 32 ) )

fii_iobuf_instc

(

.i_dio_t    ( gpio_tc ),

.i_dio      ( gpio_oc ),

.o_dio      ( gpio_ic ),

.io_dio_p   ( gpio_c )

);

assign SEG = gpio_c[ IO_WIDTHc – 1: 0 ];

wire [ 31: 0 ] gpio_id;

wire [ 31: 0 ] gpio_od;

wire [ 31: 0 ] gpio_td;

wire [ 31: 0 ] gpio_d;

//IO buffer fro push buttons

fii_iobuf #( .IO_WIDTH( 32 ) )

fii_iobuf_instd

(

.i_dio_t    ( gpio_td ),

.i_dio      ( gpio_od ),

.o_dio      ( gpio_id ),

.io_dio_p   ( gpio_d )

);

assign PB  = gpio_d[2:0];

It’s important to note that only when using “assign” with I/O buffer, “assign” can connect an inout type, otherwise, “assign” always connects an output.

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 2 and 3 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 2 PRX100-D, DDR version

Take PRX100-S as an example, the two interfaces, JTAG and UART have been highlighted in Figure 3. Note to connect jumper J6 1-2 to use J4 or J5 as external download interface as shown in Figure 4. 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 3 PRX100-S, SRAM version

 

Figure 4 JTAG interface

C Project Compilation Process

As shown in Figure 5, the translation procedure for C program is as follows:

  • Write C source code (e.g. foo.c in Unix and MS-DOS)
  • Compiler converts C program to assembly program (e.g. foo.s in Unix, foo.asm in MS-DOS )
  • Assembler converts assembly program to the machine language module (object, e.g. foo.o in Unix, foo.obj in MS-DOS)
  • Linker assembles machine language module, including libraries to the executable file (e.g. a.out in Unix, a.exe in MS-DOS)
  • At run time, loader would load the program into memory and jump to the address where it started.

Figure 5 Translate the C source code to runnable program [2]

The following figures 6-8 illustrate different translation situations. It can be seen that the compiler and assembler translate the C source files to the object files, where the linker links every object files together when translating multiple files. If only the assembly files or object files are given, then the compiler or assembler translation could be neglected.

Single C Source File Translation
Single C Source File Translation

Two C source files translation

Figure 8 Multiple types of files (*.c, *.s, *.o) translation

Using Freedom Studio to Create a New C Project

Here, Freedom Studio is used as the development platform. It is based on the industry standard Eclipse IDE, and is compatible with RISCV hardware boards by SiFive company[3]. Besides, it has prebuilt RISCV GCC toolchain and emulator.

Follow the steps shown below to create a new C project in Freedom Studio.

Firstly, go to File > New > C Project, then a window will pop up as shown in Figure 10. Set the Project name to be test. For the Project type, choose Empty Project, and the Toolchains choose RISC-V Cross GCC. The other settings will remain default. Click two time Next and then Finish the setting.

Figure 9 Create a C project

Figure 10 C project setup

Figure 11 Finish the new C project setting

Right click the new created C project, and select Properties as shown in Figure 12, a window in Figure 13 will pop up.

Figure 12 Project properties

There are lots of items in Figure 13 needed to be notified. To enter the general setting interface, click Settings under C/C++ Build drop-down menu in the left column, then the corresponding setting will occur in the right part.

Firstly, click the Target Processor. Since FII RISCV is 32bit, integer-based instruction set, choose RV32I (-march=rv32i*) for Architecture. Note do not check the Multiple extension or Atomic extension below. For Integer ABI, choose ILP32 (-mabi=ilp32*). ABI (application binary interface) is a format defines the data structure and computational routines. ILP32 means int type, long type, and the pointer type in C project are all 32 bits. For small data limit, fill 4.

Figure 13 Target Processor setting

In Figure 14, for Optimization setting, choose Optimize for debug (-Og) for the Optimization Level. Check Disable builtin (-fno-builtin) to disable all replacement and inlining of standard C library functions with equivalents. The other checkboxes will remain default.

Figure 14 Optimization setting

In Figure 15, click Warnings, and click Enable all common warnings (-Wall). This option will provide common warning information. It is useful since fixing common warning will prevent the occurrence of critical errors.

Click Debugging to enter the debugging setting as shown in Figure 16. Set the Debug level to be Default (-g).

Figure 15 Warning setting

Figure 16 Debugging setting

Find General under GNU RISC-V Cross Linker, and check Do not use standard start files (-nostartfiles) as shown in Figure 17. A customized start file will be used, and will be introduced later.

Figure 17 Linker general setting

In Miscellaneous setting under GNU RISC-V Cross C Linker as shown in Figure 18, check Use newlib-nano (–specs=nano.specs), and set Other linker flags to be -t -nostdinc –entry _start -Wl, -m, elf32lriscv -Wl, -EL, -b, elf32-littleriscv -Wl, –check-sections -Wl, –wrap=printf. See Table 1 for the detailed option explanations. Other options could be found available on GNC official website.

Figure 18 Miscellaneous setting

Option Description
–specs=nano.specs Add newlib-nano, nano is designed for small embedded applications
-t Trace file opens
-nostdinc Do not search the standard system directories for header files
–entry _start Set start address as startup.s
-Wl Pass the option behind to the linker
-m Set emulation
Elf32lriscv Output file in RISCV32I format
-El Link little-endian objects
-b Specify target for following input files
Elf32-littleriscv Output file in little-endian format
–check-sections Check section addresses for overlaps
–wrap=printf Use wrapper function for printf

Table 1 Linker option explanation

In Figure 19, choose Raw binary for General setting under GNU RISC-V Cross Create Flash Image. This option outputs raw binary file, which can be used to program the FLASH.

Figure 19 Flash Image setting

Next, start to configure the new C project with some existing files from RISCV_Hello. Select bsp, inc, entry.S, main.S, startup.S, sys.lds, and test_dbg.cfg files, right click to select Copy on the popup menu, and then right click test project, select Paste to paste all copied files. The procedure is shown in Figure 20 -21.

Figure 20 Copy files from RISCV_Hello

Figure 21 Paste files to test

After adding the files to the test project, right click it, and select Properties as shown in Figure 12 as before. In Figure 22, click Includes setting under GNU RISC-V Cross C Compiler. In the Include paths, click the Add icon, then a popup window will show up. In the Directory column, fill ${workspace_loc:/${ProjName}/inc} or select Workspace, and choose inc under test project as shown in Figure 23. Click OK to finish.

Figure 22 Include the header files

Figure 23 Add directory path

In Figure 24, click General setting of GNU RISC-V Cross C Linker, in the right Script files, click Add icon to add path to the linker script. Fill ${workspace_loc:/${ProjName}/sys.lds} to the File , or click Workspace, and find the sys.lds under test project shown in Figure 25. Then click Apply and Close to finish the setting.

Figure 24 Include the linker script

Figure 25 Add file path

After all of the the previous steps have been done, Clean Project, Build Project, and Refresh as shown in Figure 26.

Figure 26 Clean, build, and refresh the project

Figure 27 Debug

Right click test project and select Debug As > Debug Configurations as shown in Figure 27. Figure 28 shows the debug configuration popup window. Under GDB OpenOCD Debugging, click the upper left corner icon, New launch configuration to add test Debug. Usually after building projects, with refresh, the C/C++ Application row under the Main tab 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).

Figure 28 Debug configurations

Figure 29 shows the Debugger setting. Fill -f test_dbg.cfg in Config options, and fill set mem inaccessible-by-default off in Commands. Then click Debug to start debugging. “set mem inaccessible-by-default off” makes GDB treat the memory not explicitly described by the memory range as RAM [4].

Figure 29 Debugger setting

C Project Files Composition

  • Test_dbg.cfg

It is mainly used to configure OpenOCD, and is not required for the normal project, only used for debugging. Generally, users do not need to modify it. It mainly includes jtag adapter interface clock configuration, jtag adapter interface chip configuration, CPU type, information of jtag module, and etc..

  • Entry.S

It is not used here, but will be used for interrupt project later. Detailed introduction will be present in C programming 2.

  • Startup.S

The first ever file will be executed in the project. It is assigned to the initial address space by the sys.lds file.

The main purpose is to initialize the registers, including the 31 registers ( X0 is hardwired to 0) and CSR registers. It also sets the stack initial position and call main function of the C source file.

  • Sys.lds

It is used to sort and classified store different file and variables under uniform address coding. See Figure 30 for a general idea of linker functionality. The code block is attached as well, and the detailed explanation is commended inside the code block.

Figure 30 Linker functionality [5]

OUTPUT_FORMAT( “elf32-littleriscv” )/* specify the output as executable and linking format -> *.elf , and in 32 bits little endian*/

OUTPUT_ARCH( “riscv” )/*specify the output architecture is RISCV */

OUTPUT( “asm_temp.bin” )/* specify the output file is asm_temp.bin, function the same as -o filename in the command line, if setting both at the same time, the command line has priority */

ENTRY(_start)/*set the start address as startup.s, function the same as –entry _filename in the command line

if setting both at the same time, the command line has priority */

_STACK_SIZE = 0x100;/*define a macro */

_HEAP_SIZE = 0x400;/*define a macro */

/*MEMORY: describe a memory block location and size

syntax:

MEMORY

{

name [(attr)] : ORIGIN = origin, LENGTH = len

}

attr must consist only of the following characters:

R: read-only section

W: read/write section

X: executable section

A: allocatable section

I: initialized section

L: same as ‘I’

!: Invert the sense of any of the attributes that follow

origin is the start address of the memory region, it must evaluate to a constant and it cannot involve any symbols

len is an expression for the size in bytes of the memory region

*/

MEMORY

{

rom (rx) :  ORIGIN = 0x80000000, LENGTH = 32K

ram (wx!r) : ORIGIN = 0x90000000, LENGTH = 4K

}

SECTIONS/*section command */

{

. = 0x80000000; /*.address is set as 0x80000000 */

.init  :/*initialization section */

{

KEEP (*(SORT_NONE(.init)))/* put .init content into initialization, SORT_NONE means keeping the order*/

}

.text  :/* program code*/

{

*(.text)/*put all the program code here */

}

.rodata : /*read-only data*/

{

*(.rodata) /* put all the read-only data here*/

}

. = 0x90000000;/*.address is set as 0x90000000 */

.data :  /* read-write initialized data*/

{

*(.data) /* put all the read-write initialized data here*/

}

.bss : /* read-write zero initialized data*/

{

. = ALIGN(4);/* align with 4 bytes */

*(.bss)/* put all the read-write zero initialized data here*/

*(COMMON)/* put all the common symbols here*/

. = ALIGN(4);/*align with 4 bytes  */

__bss_end = .;/*assign __bss_end as the current address */

}

. = ALIGN(16);/* align with 16 bytes*/

 

.heap : /*define heap section */

{

_heap_begin = .;/*define _heap_begin address*/

_heap_start = .;/*define _heap_start address*/

. = . + _HEAP_SIZE;/*define heap size*/

. = ALIGN(16);/*align with 16 bytes*/

_heap_end = .;/*define _heap_end address*/

}

 

. = ALIGN(8);/*align with 8 bytes */

/*provide is to define a symbol only if it is referenced and is not defined by any object included*/

PROVIDE( _end = . );/*define _end address */

PROVIDE( end = . );/*define end address */

 

.stack ORIGIN(ram) + LENGTH(ram) – _STACK_SIZE :/*define .stack address */

{

. = _STACK_SIZE;/*_STACK_SIZE assign the address*/

PROVIDE( _sp = . );/*define _sp address */

}

}

  • Extra note

Right click test project, and go to Properties > Settings > Build Steps, as shown in Figure 31. Fill riscv64-unknown-elf-objdump.exe ${ProjName}.elf -d –full-contents -M no-aliases > fii.txt in the Post-build steps Command column, and compare with the result got when filling riscv64-unknown-elf-objdump.exe ${ProjName}.elf -d –full-contents -M no-aliases,numeric > fii.txt. See Table 2 for flag details.

Figure 31 Post-build steps setting

Option Description
-d Display assembler contents of executable sections
–full-contents Display the full contents of all sections requested
-M Pass text option on to the disassembler
No-aliases Disassemble only into canonical instructions, rather than into pseudo-instructions
numeric Print numeric register names, rather than ABI names

Table 2 Option description in riscv64-unknown-elf-objdump.exe

Exercise

  • Create a new C project to print “This is my first program!” using UART interface
  • 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.

 

References

  1. “RISC-V | Wikiwand”, Wikiwand, 2020. [Online]. Available: https://www.wikiwand.com/en/RISC-V. [Accessed: 27- Oct- 2020].
  2. “RISC-V Books – RISC-V International”, RISC-V International, 2020. [Online]. Available: https://riscv.org/community/learn/risc-v-books/. [Accessed: 27- Oct- 2020].
  3. “Freedom Studio Version 2019.03 – SiFive”, https://www.sifive.com/share.png, 2020. [Online]. Available: https://www.sifive.com/blog/freedom-studio-version-2019.03. [Accessed: 27- Oct- 2020].
  4. “Memory Region Attributes (Debugging with GDB)”, Sourceware.org, 2020. [Online]. Available: https://sourceware.org/gdb/current/onlinedocs/gdb/Memory-Region-Attributes.html. [Accessed: 27- Oct- 2020].
  5. “Linker Scripts for MSP430G2553 · MSP430-GCC”, Nhivp.github.io, 2020. [Online]. Available: https://nhivp.github.io/msp430-gcc/2018-07-19/linker-scripts. [Accessed: 27- Oct- 2020].

 

Related posts

Leave a Comment