用STM32+iCE40电赛训练平台完成一款开源RISCV软核——NEORV32的移植
将一款热门的开源RISCV软核NEORV32移植至STM32+iCE40电赛训练平台,并且使用软核运行编译好的C代码以流水灯的形式点亮核心板上的LED。
标签
FPGA
数字逻辑
2023寒假在家练
dangel
更新2023-03-28
北京工业大学
858

一、项目需求


  • 选择并移植一款开源的RISCV软核,使其在在核心板的FPGA芯片lattice ice40up5k上能够正常运行。
  • 以流水灯方式将开发板上LED点亮。

 

二、实现思路


电赛平台上所使用的FPGA芯片为Lattice的ICE40UP5K,片上有5280个LUT资源,足以让我们跑一些中小型的RISCV软核,如果充分利用芯片内置的IP核的话可以节省下更多的资源以支持更丰富的指令集。对于RISCV的移植与应用,电赛训练平台上的STM32可以闲置着。

首先我们需要先选择移植的RISCV软核,由于片上资源的限制最好选择一些较为精简的开源软核。此处给出几款易于使用和学习的开源RISCV软核:

1.Reindeer

Reindeer项目地址

此款软核在硬禾学堂的另一款ICE40UP5K的开发板上有使用过,可以找到相关的课程。而且使用的是Verilog语言比较容易阅读。采用了RISCV内核+on chip debug(OCD)的设计,可以通过串口很方便地调试程序并且查看内核运行的情况。但是由于比较比较小众网络上关于此软核的讨论较少,遇到问题需要找到相关资料比较困难。

2.TinyRISCV

TinyRISCV项目地址

这一款软核是由国人实现的,所以提供了详细的中文设计文档,从内核的Verilog代码到配套用于编译的makefile文件都有着详尽的说明,Verilog中的编写逻辑也有着十分细致的说明,可以说是最方便、最适合国人学习的开源软核之一。对于英文文档阅读功底不是很好,或者不是很了解RISCV的新手来说十分的推荐,即使不想使用这一款软核,也可以阅读下设计文档可以帮助你更好地理解RISCV架构。但是其缺点也十分明显,正如其名,TinyRISCV十分地精简,只支持了基础的RV32IM指令集,如果想要用更高级的指令集需要自己实现填充空白,这就要求具备有一定的开发以及阅读能力。当然对于本项目来说,TinyRISCV的指令集也是可以满足需求的。所以如果你是刚接触FPGA和RISCV对其相关的英文信息不太了解,TinyRISCV是个很不错的选择。

3.NeoRV32

NeoRV32

这一款软核是国外开源社区上十分热门的软核,该项目最突出的有点就是其设计文档,尽管TinyRISCV的设计文档以及十分详细了,但相比起NeoRV32还是稍有逊色,该项目提供了一份data sheet以及user guide 。不仅同TinyRISCV一样提供了设计文档和开发思路,并且在user guide中给出了非常友好的使用教程,从使用到自定义配置都有着详细的说明资料,称得上是手把手带你入门,并且其相关资料也比较丰富,有很多的开源贡献者参与其中,给出了一些开箱即用的项目文件可供参考。而且NeoRV32的指令集支持非常丰富,并且可以随我们所需来自由配置。NeoRV32还提供了不同程度的硬件设计,从核心到处理器再到整合的系统。我们可以只采用核心的RISCV设计,然后编写自己的外设和ocd来实现一个SOC,也可以直接采用NeoRV32提供的SOC样例。按照user guide我们可以一步步地按照我们的要求来对核心进行自定义。NeoRV32无论是对于初学者还是尚有基础想要更深一步地开发者都是一个非常好的选择。不过其缺点也十分明显,由于实现的功能比较丰富,想要系统地将其实现学习一遍会耗费不少时间。并且由于是国外开源项目其文档以及提供的相关资料都是英文,需要有一定的英文阅读能力以及RISCV和linux等基础。

我所选择的是NeoRV32因为完成项目之后想要更深一步地学习,NeoRV32比较符合我的需求。大家可以根据自己的需求来自行选择。

对于本项目而言,点亮流水灯其实有两种解决方式。

一种是将LED灯作为外设,连接到一个外设寄存器中,通过访问更改寄存器数据来更改显示。这一种方式实现起来也不难,在Reindeer的内核中由于分配给外设的地址空间比较多,实现起来也很容易。

另一种是实现GPIO设计,通过控制GPIO间接控制LED灯的显示,这种方法需要我们实现GPIO并且在程序上对其进行包装。不过TinyRISCV以及NeoRV32都有GPIO实现,所以可以直接使用。

本项目使用了第二种,即GPIO的设计。

确定了大致方向之后就可以开始我们的移植以及流水灯实现了。

三、开发环境


在本项目中,FPGA开发使用了Lattice官方的Radiant设计软件,该软件可以免费申请许可证。

Radiant下载

在下载安装完成之后,我们可以免费获取有效期为一年的许可证。

点进获取许可证界面在Radiant一栏的最下方可以找到免费证书的申请地址。

Fn12oeViLMINMJuPnLPRUxQ_-nWp

FrsPlI4sXty0pGQVnEEuAUS3pufm

选择Node-locked证书即可进入申请界面。

FgmNFxJLWJ54nYDqVIEH1EGxocsE

我们注册账号之后需要填入本机的MAC地址以生成适用的证书。可以打开Windows的command命令行输入指令"ipconfig/all"查看。

Fv1tl2OnOmTx1l6pcd0_7Xf7MXwx

FmrXGU1auJKuWCp6rgXiYyBtikgn

之后我们就会在邮箱中收到可用的证书了。将其下载保存至本地便可以用来激活软件。

FkF3udSfPysaNW_N80_2ZtDkt8xa

除了FPGA的综合布局布线使用了Windows上的radiant之外其他的工作都可以使用Linux完成。并且推荐使用Linux。我使用的是WSL的Ubuntu 22.04.1,可以在微软的应用商店里直接搜索下载。

以下操作均在Linux下进行

根据NeoRV32作者的User Guide我直接使用了他所打包好的工具链(下载地址

wget https://github.com/stnolting/riscv-gcc-prebuilt/releases/download/rv64imc-3.0.0/riscv64-unknown-elf.gcc-12.1.0.tar.gz

然后在/var/下新建了一个文件夹"neorv32"并且工具链解压至该文件夹,然后将工具链的"bin"目录加入到系统环境变量之中。

$ cd /var/
$ mkdir /var/neorv32
$ tar -xvf riscv64-unknown-elf.gcc-12.1.0.tar.gz
$ export PATH=$PATH:/var/neorv32/bin

 

需要注意的是,使用export方法修改环境变量是非永久的,如果修改的用户退出Linux再次登录要重新将其加入环境变量中。如果需要永久修改,其方法可以在自行搜索。

最后我们需要注意一下工具链的前缀,如果安装的工具链前缀是”riscv32-unknown-elf-“可以不做修改,如果不是就要在“common.mk"文件中修改一下工具链的前缀。

我们进入工具链的"bin"文件夹,可以看到所安装工具链前缀,显然我们安装的是”riscv64-unknown-elf-“,

FqLrUdVnDD3dWLb3nxBc7UT4X3pp

在neorv32_main/sw/common/common.mk中第五十一行便是工具链前缀设置。

FtHjaQgHEon8W0zV23wIQMhCyDmw

修改完成之后,将工具链"bin"目录加入环境变量中便完成了开发环境的配置。

如果是新装的Linux系统需要安装make还有gcc

可以直接使用系统的包管理工具自行安装,如果遇到网络错误,请使用清华源镜像(地址)其中有使用教程。

四、内核移植+点灯手把手教学

我们同样在windows下载一个项目文件,然后使用Radiant新建一个项目(注意选择对应的芯片UP5K的sg48封装)。先右键imp_1(默认名称),在Project Properties中设置项目的vhdl库为”neorv32“,然后将neorv32-main/rtl/core中所有的vhd文件都加入到项目之中。注意core/mem文件夹中的两个vhdl也需放入。

Fjd4iI4aVPrtIEkrGc1EjmPAoFspFhDMwFMYtFw9vD5qXpyjC4P3W4F_

FvdeY8r_3ExP8Gq1hI7jVXJZTk2Q

以上便是neorv32的核心vhdl。由于Radiant中文件的综合顺序与文件排序有关,所以需要将文件按如下次序排列

“neorv32_package.vhd”

“neorv32_bootloader_image.vhd”

“neorv32_application_image.vhd”

“neorv32_imem.entity.vhd”

“neorv32_dmem.entity.vhd”

FtmlMNmG66auRWN5wPaDp8VWlUCC

 

接下来我们要选择程序的Boot方式,Neorv32提供的可以大致分为两种,一个是直接在综合时将程序写到ram之中,另一个就是通过bootloader在写入FPGA运行内核时上传。

两种方式各有优劣,对于我们的开发板来说,fpga没有连接串口,而bootloader是需要串口进行通讯的,所以如果要选择bootloader这一方式的话就需要我们自己准备一个usb转串口的模块接入fpga引出的引脚。

并且我们的fpga连接了一个上电自动烧录的flash,所以如果采用第一种方式下载程序在掉电重启后仍可运行,而bootloader则需要连接串口重新上传。

再者bootloader模块也需要占用到片上的资源,如果我们所要运行的程序比较大,要求的指令集比较多,那么很可能资源就不够了,这是我们就不得不选择第一种,尽量削减外设数量空出资源给程序让步。

但是bootloader的一个好处就是可以在内核运行时通过reset进入到控制台模式,这是我们就能直接将编译好的程序上传而不用重新综合布线FPGA的电路,可以极大的节省我们的调试时间。

选择好了boot方式之后可以直接导入对应方式的顶层文件模板,然后在模板之中对其进行自定义设置。

直接综合

在"neorv32-main/rtl/test_setups"目录下我们可以看到三个预设vhdl文件。其中“neorv32_test_setup_approm.vhd”便是我们使用直接综合方式的模板文件。我们将其加入到项目之中并且设置为顶层文件。

我们根据自身的实际情况以及需求来更改模板。

首先是时钟频率我们需要更改为12000000HZ,FtPtYEPvd2l2Fn0MzB-LRjxmYWoH

之后我们便可直接综合项目。

该配置下的资源占用,我们可以通过精简指令集与不必须的外设来减少占用。FoEkH1HFSmdAhOLy94ge3TsQrpOU

使用bootloader

(顶层文件更改内容图文)

"neorv32-main/rtl/test_setups"目录下的neorv32_test_setup_bootloader.vhd即为bootloader的模板文件。

我们同样将其导入项目设为顶层文件。并且进行一些修改。

首先是同直接综合一样需要修改时钟频率为12000000HZ。

但是由于加入了bootloader占用了部分资源,我们可以在直接综合的资源占用中看到slice的资源已经十分紧张。直接综合布局布线的话片上资源是不够的,所以我们需要核心做出精简。首先我更改了指令内存与数据内存的大小,均改为了4kB。

Fr2DbC2Kb0zriWo6k6Rh-1LRJCM3

随后我们为对指令集做出精简,对于我们的项目,不会用到扩展指令,所以我们将其注释掉。

FpiOWaV7xci7-YZSIuLpHGI3rvJ3

可以看到使用的资源少了很多很多,即使是加上了bootloader。

FmTIIgkkBBuMF3rWv4qUap2P-X3c

 

并且在radiant中有一个十分方便的功能,可以查看各个文件所用的资源数量,这样可以帮助我们在超出资源时对代码做出优化。

FtTTI3Hoo6purOLEZy8557n2apS0

根据自己的需求完成顶层文件的设计之后,我们便可以进行管脚约束,即为顶层文件的输入输出定义对应管脚。两种方案的管脚约束基本类似,唯一的差别就是bootloader需要增加uart串口的rx与tx。

下图为bootloader的管脚约束,直接综合方案中我们可以忽略rx与tx其余管脚相同。

FqFXXecSf1JkaxRL4VWB4pUl_ZUt

对于tx与rx管脚我选择了FPGA的IO1与IO18,即核心板上的9号与32号引脚。

完成了管脚约束之后我们便可以开始布局布线与导出rbt文件。

注意电赛平台中FPGA使用的下载方式是直接传入rbt文件,而Radiant默认生成的并不是rbt文件,需要在项目策略中设置导出格式。

FpAm6boaD3t8Oq9LhK987SyyiULR

我们完成了硬件的基本配置之后便可以进行软件部分的编写。

以下部分除特别注释外均为Linux环境

首先我们检查一下工具链是否安装正确

进入到“neorv32-main/sw/example/demo_blink_led”目录下执行

make check

Ftsw0DBqibjZKHQbYfpIOJ2s_ulA

如果工具链配置正确无误会显示“Toolchain check OK”,如果出现错误请到开发环境一节中查找错误。

在“neorv32-main/sw/example”目录下可以看到作者所给出的不同外设的示例,如果想使用更多的外设请参考此目录下示例。demo_blink_led是作者默认的测试程序,我们可以通过下载此程序观察led是否显示一个不断累加的二进制数。同时此程序也是作者在程序内存vhdl中的默认程序。

我们可以在example中直接新建一个目录用于存放我们的代码文件,将任意示例中的makefile直接拷贝到新建的目录中即可。在本项目中,新建一“test”目录。如上所述拷贝makefile,新建main.c文件包含neorv32.h这一头文件即可开始程序编写。

要实现流水灯代码十分简单。在neorv32中gpio输出使用"neorv32_gpio_port_set()",延时函数用"neorv32_cpu_delay_ms()"。

FvahdgTue6ChZSiKtyxXUvTf_yRJ

代码内容十分简单不做赘述。

我们在该目录下,

执行"make clean_all install",便可在"/neorv32-main/rtl/"目录下生成程序对应的DMEM寄存器vhdl文件,文件名为“neorv32_application_image.vhd”(用于直接综合boot方式)。

执行"make clean_all exe",便可在目录下生成执行文件,文件名为“neorv32_exe.bin”(用于串口上传boot方式)。

若需要直接综合,则将vhdl文件覆盖Windows系统下的"neorv32-main/trl/core"目录的同名文件。

然后使用Radiant再走一遍综合布局布线流程,下载到FPGA即可看到开发板的LED呈现流水灯效果。

在我们的fpga开发板上采用该boot方式,掉电重启后程序仍会保留。

若采用串口下载,需要准备一个可以传输二进制文件的串口调试程序。(串口设置波特率19200,8个数据位,1个停止位,无奇偶校验位,无控制协议,接收换行为"\r\n")

将含有bootloader的rbt文件下载至fpga中,串口调试程序连接到对应的串口,按下重置按键状态指示灯(GPIO0)会开始闪烁进入重置状态,需要八秒内在电脑上按下按键以进入bootloader。

进入bootloader后,会出现如下界面。

FlLPBz89bOQcTgfpYqwEpn8ZtCZx

可以按照提示输入字母进入不同模式,我们要上传程序,所以直接输入u进入等待上传状态。

Fl42w5Ucpr_i8XMyNQIc3lj6JWCR

 

需要注意,在本项目中bootloader顶层文件中我们修改了指令内存与数据内存的大小需要同样修改neorv32-main/sw/common/neorv32.ld链接文件。

FpDmuLWo3QiCnfeYzuFQFMQufS8a

然后重新编译我们的程序,在串口调试程序中上传我们编译出来的neorv32_exe.bin文件,注意需要选择二进制上传。

FoK0rLXPsfMrh2W88UlQ5pawAPxs

上传完成之后,输入e即可进入执行状态。然后我们就可以在核心板的LED之上观察到流水灯。

FlrlxbwOwjWU8w0iAjm2ty76ECp1

五、后记

在开始本项目以前也有我也有进行过FPGA的简单学习,但也仅限于点点灯、做做多路选择器等,熟悉下语言。并且之前所用的都是verilog,在这一次的项目中大部分使用了VHDL,两者在语言上还是有着众多差异的。经过这一次的项目我得以系统地学习了VHDL,以及硬件描述语言的大型项目结构。在这之前我也没有深入了解过RISCV内核,通过这一次项目我深入学习了RISCV寄存器结构,以及多重流水线的设计与实现。由于之前没有系统学习过RISCV,所以一开始我花费了许多的时间搜寻资料并且阅读学习,经过了一个多月的学习终有所成。

在项目的完成过程之中碰到了许多的问题,首先是之前使用的FPGA开发板是Intel的并没有使用过Lattice的Radiant开发软件,虽有部分相似但是很多内容还是不一样的。并且在国内没有很多资料只能在国际社区进行搜寻。在学习RISCV时也碰到了类似的问题,许多资料,无论是官方的还是非官方的大多数都是英文资料,又由于没有深入了解过RISCV在弄清一些专有名词上花费了很多时间。这一次的“寒假在家练”活动让我有了一个深入学习FPGA与RISCV的机会,同时也增强了我的英文资料阅读能力。

在这之后我想更深一步,自己设计实现一个简单的RISCV软核,加深自身对于RISCV架构的了解以及硬件描述代码的能力。

最后附上项目的代码下载地址:

https://pan.baidu.com/s/1rVzDq-JZGn1VlGW-5mUWrQ?pwd=1111

附参考资料:

一、NeoRV32Github地址

二、ReindeerGithub地址

三、tinyriscvGithub地址

四、RISCV汇编资料

团队介绍
团队成员
dangel
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号