2023寒假一起练 - 基于 STM32+iCE40 的电赛训练平台移植 Reindeer 软核并实现流水灯功能
一、项目描述
本次活动,硬禾提供的板卡上有 Lattice 的 ICE40UP5K FPGA 和 STM32G031 MCU,板载LPC11U35 下载器,可以通过 USB-C 接口进行 FPGA 的配置,并通过虚拟串口通信配置 STM32G031,支持在 ICE40UP5K 上对 RISC-V 软核的移植以及使用开源的 FPGA 开发工具链。另外硬禾还搭配了电赛扩展板,用于实现信号源、仪器仪表、控制以及信号处理等功能。
在本项目中,我选择移植 PulseRain Reindeer 这个软核,使用到了板卡上的这些硬件 :
- LPC11U35,虚拟出一个 U 盘,可以用于下载 FPGA rbt 文件
- ICE40UP5K,运行 PulseRain Reindeer 软核
- 板载 LED1~LED4 用于实现流水灯演示
所以:
- 电赛扩展板上的高速 ADC/DAC、LCD、编码器和按键都没有使用到,未来有时间会尝试把这些外设都用起来。
- 板卡上的 STM32G031 也没有利用上。
二、设计思路
具体实现的思路如下:
- 安装配置 ICE40UP5K FPGA 的设计工具,在本项目项目我使用的是 Lattice 官方提供的 Radiant 设计软件,官方提供免费的 LICENSE 供工程师使用;
- 从 github 上克隆 PulseRain Reindeer 项目;
- 使用 Radiant 打开 Indeer 的工程文件,修改串口 Tx/Rx 的管脚映射;
- 在工程中的 Reindeer 模块中增加 LED1-LED4 等 4 个输出,并增加一个 gpio_out 寄存器,低四位分别对应 LED1-LED4 这四个管脚,并修改约束文件中的管脚对应核心板上的四个 LED 灯;
- 修改工程的 Bitstream 导出格式为 Raw Bit File,然后执行综合、布线,将导出的 rbt 文件直接拷贝到名为 step 的 U 盘,实现下载 bitfile 到 核心板上的 spi flash;
- 下载 RSICV 工具链,编译一个实现依次点亮 LED 灯的程序;
- 连接一个串口到核心板上 FPGA 映射的 UART Tx/Rx 管脚,使用 Reindeer 工程内提供的 python script,通过 Reindeer onchip-debugger 下载程序 elf 文件到软核上执行;
三、FPGA 管脚连接
-
UART Tx/Rx:
备注:
因为核心板上这两个管脚连接到了扩展板上的ADC,会影响串口工作,所以可以将ADC从扩展板上移除或者将核心板从扩展版上移除,然后再进行串口连接。
核心板接口 | ICE40UP5K FPGA |
Pin 9 (丝印F0) | 26 (TXD) |
Pin 10 (丝印F1) | 27 (RXD) |
- 核心板上的 LED1-LED4:
核心板 LED | ICE40UP5K FPGA |
LED1 | 48 (LED1) |
LED2 | 47 (LED2) |
LED3 | 46 (LED3) |
LED4 | 45 (LED4) |
四、开发环境的搭建和移植过程
- 安装配置 ICE40UP5K FPGA 的设计工具:
从 https://www.latticesemi.com/zh-CN/Products/DesignSoftwareAndIP/FPGAandLDS/Radiant 下载 Windows 版本进行安装,然后申请 License;
- 克隆 PulseRain Reindeer 项目:
git clone https://github.com/PulseRain/Reindeer.git
备注:
我在移植过程中发现使用 master 最新版本代码时,生成的 bitfile 有些问题,在执行脚本下载仓库里的 hello_world.bin 程序之后无法输出打印串口信息,脚本执行结果如下:
===============================================================================
# Copyright (c) 2018, PulseRain Technology LLC
# Reindeer Configuration Utility, Version 1.0
===============================================================================
baud_rate = 115200
com_port = COM17
toolchain = riscv-none-embed-
===============================================================================
Reseting CPU ...
Loading E:\lattice\Reindeer\bitstream_and_binary\zephyr\hello_world.elf
__start 80000000
//================================================================
//== Section vector
//================================================================
addr = 0x80000000, length = 1044 (0x414)
//================================================================
//== Section reset
//================================================================
addr = 0x80004000, length = 4 (0x4)
//================================================================
//== Section exceptions
//================================================================
addr = 0x80004004, length = 620 (0x26c)
//================================================================
//== Section text
//================================================================
addr = 0x80004270, length = 7172 (0x1c04)
//================================================================
//== Section devconfig
//================================================================
addr = 0x80005e74, length = 36 (0x24)
//================================================================
//== Section rodata
//================================================================
addr = 0x80005e98, length = 1216 (0x4c0)
//================================================================
//== Section datas
//================================================================
addr = 0x80006358, length = 28 (0x1c)
//================================================================
//== Section initlevel
//================================================================
addr = 0x80006374, length = 36 (0x24)
===================> start the CPU, entry point = 0x80000000
# 然后就停在这里,没有串口信息输出...
通过与群里的同学进行交流之后,Reindeer 可能在某一个版本更新之后出了问题,于是我找到了最初提交 hello_world.bin 时的commit id,将仓库 checkout 到这个commit id的版本,经测试该版本可以使用,下载完 hello_world.bin 之后也可以输出信息到串口。
- checkout 仓库到 commit-id c23e62b47d104334d3176716ebadf4af878e8ef3
git checkout c23e62b47d104334d3176716ebadf4af878e8ef3
- 使用 Radiant 打开 Reindeer 的工程文件:
打开 Reindeer/build/par/Lattice/UPDuinoV2/UPDuinoV2.rdf
- 在工程中的 Reindeer 模块中增加 LED1-LED4 等 4 个输出,并增加一个 gpio_out 寄存器,低四位分别对应 LED1-LED4 这四个管脚,改动 diff 如下:
diff --git a/source/Lattice/UPDuinoV2/Reindeer.v b/source/Lattice/UPDuinoV2/Reindeer.v
index 3adc164..cfc34a7 100644
--- a/source/Lattice/UPDuinoV2/Reindeer.v
+++ b/source/Lattice/UPDuinoV2/Reindeer.v
@@ -38,6 +38,11 @@ module Reindeer (
//=====================================================================
// status
//=====================================================================
+ output wire LED1,
+ output wire LED2,
+ output wire LED3,
+ output wire LED4,
+
output wire REDn, // Red
output wire BLUn, // Blue
output wire GRNn // Green
@@ -71,6 +76,7 @@ module Reindeer (
wire uart_tx_cpu;
wire uart_tx_ocd;
+ wire [7 : 0] gpio_out;
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// CLOCK
@@ -138,6 +144,7 @@ module Reindeer (
.start_address (cpu_start_addr),
.processor_paused (processor_paused),
+ .gpio_out(gpio_out),
.peek_pc (),
.peek_ir (),
@@ -148,6 +155,10 @@ module Reindeer (
);
assign processor_active = ~processor_paused;
+ assign LED1 = gpio_out[0];
+ assign LED2 = gpio_out[1];
+ assign LED3 = gpio_out[2];
+ assign LED4 = gpio_out[3];
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// OCD
diff --git a/submodules/PulseRain_MCU/PulseRain_processor_core/source/PulseRain_RV2T_core.v b/submodules/PulseRain_MCU/PulseRain_processor_core/source/PulseRain_RV2T_core.v
index 6a2e4e6..903fd56 100644
--- a/submodules/PulseRain_MCU/PulseRain_processor_core/source/PulseRain_RV2T_core.v
+++ b/submodules/PulseRain_MCU/PulseRain_processor_core/source/PulseRain_RV2T_core.v
@@ -65,6 +65,7 @@ module PulseRain_RV2T_core (
output wire [31 : 0] peek_pc,
output wire [31 : 0] peek_ir,
+ output wire [31 : 0] gpio_out,
output wire [`MEM_ADDR_BITS - 1 : 0] mem_addr,
@@ -238,6 +239,7 @@ module PulseRain_RV2T_core (
.clk (clk),
.reset_n (reset_n),
.sync_reset (sync_reset),
+ .gpio_out(gpio_out),
.data_read_enable (mm_reg_re),
.data_write_enable (mm_reg_we),
diff --git a/submodules/PulseRain_MCU/PulseRain_processor_core/source/RV2T_mm_reg.v b/submodules/PulseRain_MCU/PulseRain_processor_core/source/RV2T_mm_reg.v
index 1393989..2202fe5 100644
--- a/submodules/PulseRain_MCU/PulseRain_processor_core/source/RV2T_mm_reg.v
+++ b/submodules/PulseRain_MCU/PulseRain_processor_core/source/RV2T_mm_reg.v
@@ -58,6 +58,7 @@ module RV2T_mm_reg (
//=======================================================================
output reg enable_out,
output wire [`XLEN - 1 : 0] word_out,
+ output reg [31 : 0] gpio_out,
output wire timer_triggered
);
@@ -118,8 +119,20 @@ module RV2T_mm_reg (
assign tx_data = data_write_word [7 : 0];
- assign word_out = (data_rw_addr_d1 == `UART_TX_ADDR) ? {tx_active, 31'd0} : machine_timer_data_out;
+ assign word_out = (data_rw_addr_d1 == `UART_TX_ADDR) ? {tx_active, 31'd0} :
+ ((data_rw_addr_d1 == 3'b101) ? {gpio_out} : machine_timer_data_out);
+ //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ // GIIO
+ //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ // assign gpio_write = ((data_rw_addr == 3'b101) & data_write_enable) ? 1'b1 : 1'b0;
+ always @(posedge clk or negedge reset_n)begin
+ if(~reset_n)
+ gpio_out <= 8'h0;
+ // else if(gpio_write)
+ else if(((data_rw_addr == 3'b101) & data_write_enable) ? 1'b1 : 1'b0)
+ gpio_out <= data_write_word[7 : 0];
+ end
endmodule
diff --git a/submodules/PulseRain_MCU/source/PulseRain_RV2T_MCU.v b/submodules/PulseRain_MCU/source/PulseRain_RV2T_MCU.v
index 5883cbf..ae0a23c 100644
--- a/submodules/PulseRain_MCU/source/PulseRain_RV2T_MCU.v
+++ b/submodules/PulseRain_MCU/source/PulseRain_RV2T_MCU.v
@@ -60,6 +60,7 @@ module PulseRain_RV2T_MCU (
//=====================================================================
input wire start,
input wire [`PC_BITWIDTH - 1 : 0] start_address,
+ output wire [31 : 0] gpio_out,
output wire processor_paused,
@@ -113,6 +114,7 @@ module PulseRain_RV2T_MCU (
.ocd_reg_we (ocd_reg_we),
.ocd_reg_write_addr (ocd_reg_write_addr),
.ocd_reg_write_data (ocd_reg_write_data),
+ .gpio_out(gpio_out),
.start_TX (start_TX),
.tx_data (tx_data),
- 修改约束文件 build/par/constraints/Lattice/UPDuinoV2/Reindeer.ldc 中的管脚对应核心板上的四个 LED 灯并修改串口 Tx/Rx 的管脚映射:
#ldc_set_location -site {35} [get_ports osc_in]
#ldc_set_location -site {43} [get_ports reset_button]
ldc_set_location -site {41} [get_ports REDn]
ldc_set_location -site {40} [get_ports BLUn]
ldc_set_location -site {39} [get_ports GRNn]
ldc_set_location -site {26} [get_ports TXD]
ldc_set_location -site {27} [get_ports RXD]
ldc_set_location -site {16} [get_ports spi_ss]
ldc_set_location -site {48} [get_ports LED1]
ldc_set_location -site {47} [get_ports LED2]
ldc_set_location -site {46} [get_ports LED3]
ldc_set_location -site {45} [get_ports LED4]
create_clock -name {osc_i/CLKHF} -period 41.6667 [get_pins {osc_i/CLKHF }]
- 修改工程的 Bitstream 导出格式为 Raw Bit File:
- 然后执行综合、布线,生成 rbt 文件,FPGA 资源占用报告见下图:
- 将导出的 rbt 文件直接拷贝到名为 step 的 U 盘,实现下载 bitfile 到 核心板上的 spi flash;
- 下载 RSICV 工具链 gnu-mcu-eclipse-riscv-none-gcc-7.2.0-1-20171109-1926-win64-setup.exe,下载地址 https://gnu-mcu-eclipse.github.io/toolchain/riscv/ ,安装后在系统环境变量PATH 中增加相应的工具链的路径:
- 同时,为了实现通过 makefile 编译程序,可以通过安装 mingw 工具,然后调用 mingw-make 程序,相应地,也需要将 mingw 程序执行路径添加到 PATH 环境变量,如上图所示;
- 修改 Reindeer 仓库中提供的 software/makefile 例程,在例程中添加流水灯控制代码并执行 make 进行编译,生成一个 step.elf 文件:
xxx@xxx MINGW64 /e/lattice/Reindeer-master/software/makefile
$ mingw32-make.exe
===> Building main.o
============> Building Dependency
============> Generating OBJ
----------------------------------------------------------------------------
====> Linking step.elf
===> Dumping sections for all
- 连接一个串口到核心板上 FPGA 映射的 UART Tx/Rx 管脚(连接见下图),使用 Reindeer 工程内提供的 python script,通过 Reindeer onchip-debugger 下载程序 elf 文件到软核上执行:
备注:
由于未知原因,我发现需要先下载 Reindeer 提供elf文件,然后再下载我编译的step.elf文件,程序才能正常运行,可能是有vector相关的问题导致:
E:\lattice\Reindeer-master\scripts>python reindeer_config.py --port=COM17 --reset --image=E:\lattice\Reindeer-master\software\makefile\step.elf --console_enable --run
===============================================================================
# Copyright (c) 2018, PulseRain Technology LLC
# Reindeer Configuration Utility, Version 1.0
===============================================================================
baud_rate = 115200
com_port = COM17
toolchain = riscv-none-embed-
===============================================================================
Reseting CPU ...
Loading E:\lattice\Reindeer-master\software\makefile\step.elf
_start 80000000
//================================================================
//== Section .text
//================================================================
addr = 0x80000000, length = 2936 (0xb78)
//================================================================
//== Section .rodata
//================================================================
addr = 0x80000b78, length = 157 (0x9d)
//================================================================
//== Section .eh_frame
//================================================================
addr = 0x80000c18, length = 4 (0x4)
//================================================================
//== Section .init_array
//================================================================
addr = 0x80001000, length = 12 (0xc)
//================================================================
//== Section .fini_array
//================================================================
addr = 0x8000100c, length = 4 (0x4)
//================================================================
//== Section .data
//================================================================
addr = 0x80001010, length = 1064 (0x428)
//================================================================
//== Section .sdata
//================================================================
addr = 0x80001438, length = 4 (0x4)
===================> start the CPU, entry point = 0x80000000
=== PulseRain Reindeer running on ICE40UP5K ===
...
ALL LEDs -> OFF
...
- LED1 -> ON
- LED2 -> ON
- LED3 -> ON
- LED4 -> ON
- LED4 -> OFF
- LED3 -> OFF
- LED2 -> OFF
- LED1 -> OFF
五、代码
- RTL的修改见上一章节;
- 流水灯程序代码如下:
volatile uint32_t* const REG_GPIO_OUT = (uint32_t*) 0x20000014;
void delayMs(unsigned int ms)
{
unsigned int _startMillis;
_startMillis = millis();
while(millis() - _startMillis < ms);
}
int main()
{
// int t = 0;
Serial.println("=== PulseRain Reindeer running on ICE40UP5K ===\n");
Serial.println("...\n");
(*REG_GPIO_OUT) &= ~0xF;
Serial.println("ALL LEDs -> OFF\n");
Serial.println("...\n");
while(1) {
// t = t - cnt;
// t = t + foo();
// Serial.println("xxxxxx ddddd");
// myprint ("ssss %d y iiiiiuk\n", t);
// myprint("REG_MTIME_LOW: %d\n", (*REG_MTIME_LOW));
for (int i = 0; i < 4; i++) {
(*REG_GPIO_OUT) |= (1 << i);
myprint ("- LED%d -> ON\n", i + 1);
delayMs(500);
}
for (int i = 3; i >= 0; i--) {
(*REG_GPIO_OUT) &= ~(1 << i);
myprint ("- LED%d -> OFF\n", i + 1);
delayMs(500);
}
}
return 0;
}
六、功能展示
- 串口输出信息
=== PulseRain Reindeer running on ICE40UP5K ===
...
ALL LEDs -> OFF
...
- LED1 -> ON
- LED2 -> ON
- LED3 -> ON
- LED4 -> ON
- LED4 -> OFF
- LED3 -> OFF
- LED2 -> OFF
- LED1 -> OFF
- LED1 -> ON
- LED2 -> ON
- LED3 -> ON
- LED4 -> ON
- LED4 -> OFF
- LED3 -> OFF
- LED2 -> OFF
- LED1 -> OFF
- LED1 -> ON
- LED2 -> ON
- LED3 -> ON
- LED4 -> ON
- LED4 -> OFF
- LED3 -> OFF
- LED2 -> OFF
- LED1 -> OFF
- LED1 -> ON
- LED2 -> ON
- LED3 -> ON
- LED4 -> ON
- LED4 -> OFF
- LED3 -> OFF
- LED2 -> OFF
- LED1 -> OFF
- LED1 -> ON
- LED2 -> ON
- LED3 -> ON
- LED4 -> ON
- LED4 -> OFF
- LED3 -> OFF
- LED2 -> OFF
- LED1 -> OFF
- LED1 -> ON
- LED2 -> ON
- LED3 -> ON
- 流水灯效果见视频
八、未来计划
未来计划能够把扩展板上的ADC/DAC用起来,尝试示波器等功能。