Menu Close

RISC-V C语言编程1

1. FII-RISCV CPU简介

 

相关参考文章:

RISC-V教学教案

 

首先,简单了解一下FII-RISCV CPU。RISCV是一种基于精简指令集计算机(RISC,reduced instruction set computer)原理的开源标准指令集体系结构(ISA,instruction set architecture) [1]。FII-RISCV 是按照RISCV标准进行研发的。以下是有关FII-RISCV的一些基本特点:

  • 支持32个寄存器(RV32I,支持整型运算)
  • 不支持乘除法汇编指令(最新版本支持)
  • 不支持原子指令
  • 不支持压缩指令
  • 支持软件中断
  • 支持计时器中断
  • 支持UART输出(外设)
  • 支持GPIO 4组(每组32个)
  • 4K bytes DTCM
  • 16K bytes ITCM

图一截取了部分RISCV地址分配,主要用于编写C语言工程时的地址赋值查询。

 

%title插图%num

图一 地址分配

GPIO配置如下所示。 相关代码模块位于顶层fii_cpu_sys.v中。

wire [ 31: 0 ] gpio_ia;

wire [ 31: 0 ] gpio_oa;

wire [ 31: 0 ] gpio_ta;

wire [ 31: 0 ] gpio_a;

 

//LED I/O buffer(缓冲器)

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;

//数码管位选I/O buffer(缓冲器)

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;

//数码管段选I/O buffer(缓冲器)

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;

 

//按键I/O buffer (缓冲器)

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];

需要注意的是,只有将assign与I/O buffer(缓冲器)一起使用时,assign才能接inout类型,否则,assign只能连接输出。

 

 

2. RISCV FII-PRX100 (ARTIX-7, XC7A100T) Xilinx FPGA 板简介

RISCV FII-PRX100 Xilinx FPGA板有两个版本,一个配有SRAM,表示为PRX100-S,另一个配有DDR而不是SRAM,表示为PRX100-D(https://item.taobao.com/item.htm?spm=a1z10.1-c-s.w5003-22702892501.2.7f0056012gj77C&id=615296285488&scene=taobao_shop)。 有关PRX100的不同版本,参见图二和图三。 通过FPGA板上红色显示的区域,可以发现两个版本的差异。 DDR版本的正面有一个明显的IC,标记为“ DDR”。

%title插图%num

图二PRX100-D,DDR版本

以PRX100-S为例,图三中突出显示了两个JTAG (内部和外部)和UART接口。注意,连接跳线J6 1-2以使用J4或J5作为外部下载接口,如图四所示。 JTAG接口主要用于FPGA编程。 UART接口用于输出到计算机。 在使用Freedom Studio运行C程序之前,需要使用RISCV verilog工程对FPGA板进行编程。

%title插图%num

图三 PRX100-S,SRAM版本

 

%title插图%num

图四 外部JTAG接口

 

 

3.C语言工程编译流程

如图五所示,C 语言的编译流程如下:

  • 编写C语言代码(g. Unix和MS-DOS中的foo.c)
  • Compiler将C语言转成汇编语言(e.g. Unix中的s,MS-DOS中的 foo.asm)
  • Assembler将汇编语言转换为机器码模块(g.Unix中的foo.o, MS-DOS中的foo.obj)
  • Linker将汇编语言模块组装,生产最终输出的执行文件(e.g. Unix中的out, MS-DOS中的a.exe)
  • 运行时,loader把这个程序加载到内存中,并跳转到它开始的地址

%title插图%num

图五 从C源代码翻译为可运行程序的步骤 [2]

下图六至八列出了不同的编译情形。可以看出,compiler和assembler将C源文件转换为目标文件(机器码模块),linker在转换多个文件时将每个目标文件链接在一起。如果仅给出汇编文件或目标文件,则可以略过编译器或汇编器的翻译。

%title插图%num

图六 单一文件编译

%title插图%num

图七 两个文件同时编译

%title插图%num

图八 多种文件(*.c, *.s, *.o) 同时编译

4. 用Freedom Studio创建一个C 语言工程

Freedom Studio被用作开发平台。 它由SiFive公司研发,基于行业标准Eclipse IDE,并且RISCV硬件板兼容[3]。 此外,它还预先构建了RISCV GCC工具链(toolchain)和仿真器(emulator)。

按照下面显示的步骤在Freedom Studio中创建一个新的C项目。

首先,点击File > New > C Project,然后会弹出一个窗口,如图十所示。将项目名称设置为test。 对于Project type,选择Empty Project,然后在Toolchains中选择RISC-V Cross GCC。 其他设置保持默认选项。 点击两次Next,然后点击Finish完成设置。

%title插图%num

图九 创建C语言工程

%title插图%num

图十 C工程设置

%title插图%num

图十一 结束C工程设置

如图十二所示,右键单击新创建的C工程,选择Properties,图十三中的窗口将弹出。

%title插图%num

图十二 工程Properties

图十三中有很多需要注意的项。 要进入常规设置界面,在左边的C/C++ Build选项下点击Settings。相应的设置会出现在右侧。

首先,点击Target Processor. 由于FII RISCV是基于整数的32位指令集,因此为Architecture选择RV32I(-march = rv32i *)注意不要勾选下面的Multiple extensionAtomic extension。 对于Integer ABI,选择ILP32(-mabi = ilp32 *)。 ABI(application binary interface,应用程序二进制接口)是一种定义数据结构和计算例程的格式。 ILP32表示C项目中的int类型,long类型和指针类型均为32位。 small data limit一栏,填4。

%title插图%num

图十三 Target Processor设置

在图十四中,对于Optimization设置,选择Optimization LevelOptimize for debug (-Og)。勾选 Disable builtin (-fno-builtin) 以禁用所有标准C库函数的替换和内联操作。 其他复选框保持默认选项。

%title插图%num

图十四 Optimization设置

在图十五中,单击Warnings,然后勾选 Enable all common warnings (-Wall)。 此选项将提供常见的警告信息。 这非常实用,因为修正常见的警告会防止严重错误的发生。

单击Debugging,进入调试设置,如图十六所示。将Debug level设置为Default (-g)

%title插图%num

图十五 Warnings 设置

 

%title插图%num

图十六 Debugging设置

GNU RISC-V Cross Linker下点击General,并选中Do not use standard start files (-nostartfiles),如图十七所示。自定义的启动文件将被使用,稍后将对其进行介绍。

 

%title插图%num

图十七 Linker General设置

GNU RISC-V Cross C Linker下的 Miscellaneous 设置中,如图十八所示,选中Use newlib-nano(–specs = nano.specs),并将Other linker flags设置为-t -nostdinc –entry _start -Wl, -m,elf32lriscv -Wl,-EL,-b,elf32-littleriscv -Wl,–check-sections -Wl,–wrap = printf。 有关选项的详细说明,请参见表一。 其他选项可以在gcc.gnu.org上查询。

%title插图%num

图十八 Miscellaneous设置

 

选项

描述

–specs=nano.specs 添加newlib-nano,nano是专为小型嵌入式应用程序而设计的库
-t 打开trace文件
-nostdinc 不在标准系统目录中搜索头文件
–entry _start 将起始地址设置为startup.s
-Wl 将选项传递给链接器
-m 设置仿真
Elf32lriscv 以RISCV32I格式输出文件
-El 链接小端(little-endian)对象
-b 指定后续输入文件的目标
Elf32-littleriscv 输出文件为小端(little-endian)格式
–check-sections 检查section地址是否重叠
–wrap=printf 对printf使用包装函数

表一 linker选项描述

在图十九中,在GNU RISC-V Cross Create Flash Image下的General设置中选择Raw binary。 此选项输出原始二进制文件,可用于FLASH编程。

%title插图%num

图十九 Flash Image设置

接下来,开始使用来自RISCV_Hello(该工程可在文章末尾下载)的一些现有文件配置新的C项目。RISCV_Hello主要做了一个打印输出,代码如下:

#include <stdio.h>

#include "fii_types.h"

#include "platform.h"

#define  NOP_DELAY  0x100

void delay_cnt (int cnt)

{

    unsigned int  i;

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

        asm("nop");

    return;

};

int main(void)

{

    while ( 1 )

    {

        printf("Hello FII Risc-V !\r\n");

        delay_cnt(NOP_DELAY);

    }

}

 

RISCV_Hello工程下选择bsp,inc,entry.S,main.S,startup.S,sys.ldstest_dbg.cfg文件,在弹出菜单上右键单击 Copy,然后右键单击 test 工程,选择Paste以粘贴所有复制的文件。 该过程如图二十,图二十一所示。

%title插图%num

图二十 从RISCV_Hello中复制文件

%title插图%num

图二十一 复制文件到test工程

将文件添加到test工程后,右键单击,然后像以前一样选择Properties,如图十二所示。 在图二十二中,单击GNU RISC-V Cross C Compiler下的Includes设置。 在Include paths中,单击Add图标,然后会弹出一个窗口。 在Directory column中,填$ {workspace_loc:/ $ {ProjName} / inc}或点击选择Workspace,然后选择test工程下的inc,如图二十三所示。单击OK完成。

%title插图%num

图二十二 添加头文件

%title插图%num

图二十三 选择增加文件路径

在图二十四中,单击GNU RISC-V Cross C LinkerGeneral设置,在右侧的Script files中,单击Add图标将路径添加到链接器脚本。 将$ {workspace_loc:/ $ {ProjName} /sys.lds}填到File,或单击Workspace,然后找到test工程下的sys.lds,如图二十五所示。然后单击Apply and Close完成设置。

%title插图%num

图二十四 添加linker script路径

 

%title插图%num

图二十五 选择增加文件路径

完成上述所有步骤之后,进行Clean ProjectBuild ProjectRefresh,如图二十六所示。

%title插图%num

图二十六 Clean Project,Build Project和Refresh

 

%title插图%num

图二十七 Debug

 

右键单击test工程,然后选择Debug As > Debug Configurations,如图二十七所示。图二十八显示了Debug Configuration弹出窗口。 在GDB OpenOCD Debugging下,单击左上角图标New launch configuration以添加test Debug。 通常在构建项目,Refresh后,Main选项卡下的C/C++ Application 行将自动填充相应的* .elf文件。 如果不是,单击空白下的Search Project以搜索* .elf文件。 否则,使用 Browse按钮在调试文件夹下找到* .elf文件(绝对路径)。

%title插图%num

图二十八 Debug configurations(调试配置)

图二十九显示了Debugger设置。 在 Config options选项中填-f test_dbg.cfg,并在Commands中填写set mem inaccessible-by-default off。 然后单击Debug开始调试。 “set mem inaccessible-by-default off”使GDB将未由内存范围明确描述的内存视为RAM [4]。

%title插图%num

图二十九 Debugger设置

5. C语言工程文件组成

  • Test_dbg.cfg

主要用于配置OpenOCD,对于常规工程不是必需的,仅用于调试。 通常,用户不需要修改它。 它主要包括jtag适配器接口时钟配置,jtag适配器接口芯片配置,CPU类型,jtag模块信息等。

  • Entry.S

在此不使用它,但之后将用于中断工程。 将在C语言编程2中详细介绍。

  • Startup.S

项目中第一个被执行的文件。 它由sys.lds文件分配给初始地址空间。

主要目的是初始化寄存器,包括31个寄存器(X0硬连线为0)和CSR寄存器。 它还设置堆栈的初始位置并调用C源文件的main函数。

  • Sys.lds

用于将不同的文件, 变量在统一地址编码下进行分类存贮。 有关链接器功能示意图,参见图三十。具体细节将在代码模块中注释详细说明。代码模块如下:

%title插图%num

图三十 Linker示意图

OUTPUT_FORMAT( “elf32-littleriscv” )/*将输出指定为可执行文件和链接格式-> *.elf,并以32位little endian格式*/

OUTPUT_ARCH( “riscv” )/*指定输出架构为RISCV */

OUTPUT( “asm_temp.bin” )/* 指定输出文件为asm_temp.bin,功能与命令行中的-o filename相同

                           如果同时设置两者,则命令行具有优先级*/

 

ENTRY(_start)/*将起始地址设置为startup.s,在命令行中与–entry _filename相同

               如果同时设置两者,则命令行具有优先级 */

_STACK_SIZE = 0x100;/*定义宏*/

_HEAP_SIZE = 0x400;/* 定义宏*/

/*

MEMORY: 描述内存块的地址和大小

句法:

MEMORY

  {

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

    …

  }

attr 必须仅包含以下字符:

R: 只读段

W: 读写段 

X: 可执行段

A: 可分配段

I: 已经初始化段

L: 和’I’相同

!: 反转之后的所有属性

origin是内存区域的起始地址,它必须计算为常数,并且不能包含任何符号

len是存储区域的大小(以字节为单位)的表达式

*/

MEMORY

{

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

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

}

SECTIONS/*section命令 */

{

    . = 0x80000000; /*.地址设置为0x80000000 */

    .init  :/* 初始化段*/

    {

        KEEP (*(SORT_NONE(.init)))/*将 init 里的内容放入初始化段 */

    }

    .text  :/*程式码 */

    { 

        *(.text)/*将所有程序代码放到这个地址段上,具体顺序,由软件确定 */

    }

    .rodata : /*只读数据 */

    {

        *(.rodata) /*将所有只读数据放到这个地址段上,具体顺序,由软件确定 */

    }

    . = 0x90000000;/*.地址设置为0x90000000 */

    .data : /*可读可写已经初始化的数据*/

    { 

        *(.data) /*将所有可读可写已经初始化的数据放到这个地址段上,具体顺序,由软件确定 */

    }

    .bss : /*可读可写未初始化的数据 */

    { 

        . = ALIGN(4);/*4字节对齐 */

        *(.bss)/*将所有可读可写未初始化的数据放到这个地址段上,具体顺序,由软件确定 */

        *(COMMON)/*将所有常见符号放在这里*/

        . = ALIGN(4);/*4字节对齐 */

        __bss_end = .;/*将当前地址赋值给__bss_end */

    }

    . = ALIGN(16);/*16字节对齐 */

    

    .heap : /*定义堆 */

    { 

        _heap_begin = .;/*定义_heap_begin为当前地址*/

        _heap_start = .;/*定义_heap_start为当前地址*/

        . = . + _HEAP_SIZE;/*定义堆大小 */

        . = ALIGN(16);/*16字节对齐 */

        _heap_end = .;/*定义_heap_end为当前地址 */

    }

    

    . = ALIGN(8);/*8字节对齐*/

    /*provide仅在符号被引用且未被任何包含的对象定义的情况下定义符号 */

    PROVIDE( _end = . );/*定义_end为当前地址*/

    PROVIDE( end = . );/*定义end为当前地址*/

    

    .stack ORIGIN(ram) + LENGTH(ram) – _STACK_SIZE :/*定义.stack地址 */

    {

        . = _STACK_SIZE;/*_STACK_SIZE 分配地址 */

        PROVIDE( _sp = . );/*定义_sp为当前地址*/

    } 

}

  • 额外注意

右键单击test工程,选择Properties > Settings > Build Steps,如图三十一所示。填riscv64-unknown-elf-objdump.exe ${ProjName}.elf  -d  –full-contents -M no-aliases > fii.txtPost-build steps Command行里,并与填riscv64-unknown-elf-objdump.exe ${ProjName}.elf  -d  –full-contents -M no-aliases,numeric > fii.txt.时获得的结果进行比较。 有关选项的详细信息,请参见表二。

%title插图%num

图三十一 Post-build steps设置

 

选项

描述

-d 显示可执行节的汇编器内容
–full-contents 显示所有要求的section的全部内容
-M 将选项传递给反汇编器
No-aliases 仅反汇编成规范的指令,而不反汇编成伪指令
numeric 打印数字寄存器名称,而不是ABI名称

表二 riscv64-unknown-elf-objdump.exe选项描述

6. 习题

  • 创建一个新的C语言工程,通过UART输出 “This is my first program!”
  • 创建一个新的C语言工程 让LED 灯 从右到左 依次点亮,之前的LED 熄灭, 到第八个灯后(LED 7) 返回到LED 0, 如此循环往复 每次灯亮 和移位的时间大约为1 秒钟左右

 

 

7. 参考文献

  • “RISC-V | Wikiwand”, Wikiwand, 2020. [Online]. Available: https://www.wikiwand.com/en/RISC-V. [Accessed: 27- Oct- 2020].
  • “RISC-V Books – RISC-V International”, RISC-V International, 2020. [Online]. Available: https://riscv.org/community/learn/risc-v-books/. [Accessed: 27- Oct- 2020].
  • “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].
  • “Memory Region Attributes (Debugging with GDB)”, org, 2020. [Online]. Available: https://sourceware.org/gdb/current/onlinedocs/gdb/Memory-Region-Attributes.html. [Accessed: 27- Oct- 2020].

 

附件下载

Posted in RISC-V, 应用开发

发表评论

相关链接