【Perf-V评测】移植蜂鸟E203开源SOC到FPGA板
[复制链接]
本帖最后由 cruelfox 于 2021-6-17 23:10 编辑
Perf-V开发板资料里面有E203 SOC移植好的工程,但是因为用的Vivado版本比我装的高,软件不能全部导进来,主要是因为其中两个Xilinx IP核的问题。
我用Vivado中的IP Catalog重新定制了这两个模块的IP,但不知什么原因,最终生成的系统没有能够正常工作。原始工程(软件版本更高)中的 .bit 文件直接下载到 FPGA 中是可以工作的(JTAG连接可以发现RISC-V CPU),说明代码结构是没有问题的。于是我用它作参考,自己移植一下这个SOC.
上一篇帖子中我分享了E203 SOC RTL代码的仿真,将这套代码移植到FPGA的操作需要另外编写一个顶层模块,不用仿真测试的顶层模块(文件 tb_top.v)。在Perf-V自带FPGA工程中,顶层模块代码是 system.v, 相当于在 e203_soc_top 这个模块外面再套一层“壳",将FPGA管脚信号连上去。
看看 e203_soc_top 模块的输入输出端口:
时钟信号及使能:有高频(HF)和低频(LF)两个时钟输入,分别是16MHz和32.768kHz. 使能信号是给出去的,可以不连。
input hfextclk,
output hfxoscen,
input lfextclk,
output lfxoscen,
JTAG口的信号:除了TDO都是输入,TDO还有一个输出使能信号。
input io_pads_jtag_TCK_i_ival,
input io_pads_jtag_TMS_i_ival,
input io_pads_jtag_TDI_i_ival,
output io_pads_jtag_TDO_o_oval,
output io_pads_jtag_TDO_o_oe,
32个GPIO口的信号,每个口都有6个信号。
input io_pads_gpio_0_i_ival,
output io_pads_gpio_0_o_oval,
output io_pads_gpio_0_o_oe,
output io_pads_gpio_0_o_ie,
output io_pads_gpio_0_o_pue,
output io_pads_gpio_0_o_ds,
......
QSPI flash连接的信号,其中 DQ[n] 是双向端口,因此每个口也有6个信号。
output io_pads_qspi_sck_o_oval,
output io_pads_qspi_cs_0_o_oval,
input io_pads_qspi_dq_0_i_ival,
output io_pads_qspi_dq_0_o_oval,
output io_pads_qspi_dq_0_o_oe,
output io_pads_qspi_dq_0_o_ie,
output io_pads_qspi_dq_0_o_pue,
output io_pads_qspi_dq_0_o_ds,
......
额外的控制信号:
// Erst is input need to be pull-up by default
input io_pads_aon_erst_n_i_ival,
// dbgmode are inputs need to be pull-up by default
input io_pads_dbgmode0_n_i_ival,
input io_pads_dbgmode1_n_i_ival,
input io_pads_dbgmode2_n_i_ival,
// BootRom is input need to be pull-up by default
input io_pads_bootrom_n_i_ival,
// dwakeup is input need to be pull-up by default
input io_pads_aon_pmu_dwakeup_n_i_ival,
// PMU output is just output without enable
output io_pads_aon_pmu_padrst_o_oval,
output io_pads_aon_pmu_vddpaden_o_oval
在Perf-V提供demo的 system.v 文件中,定义了一些 FPGA 的输入输出端口。比如 Perf-V 板子上的 LED、RGB LED、按键、拨动开关、插座引出的I/O口、JTAG口等。特殊管脚比如时钟、JTAG,需要和板子适配;至于 E203 的 32 个GPIO口怎么对应到这些LED、开关、插座上去,可以随自己便。
E203的一个GPIO口有6个信号,只须对应到FPGA的一个引脚。在demo的 system.v 中是这样写的:
wire iobuf_gpio_7_o;
IOBUF
#(
.DRIVE(12),
.IBUF_LOW_PWR("TRUE"),
.IOSTANDARD("DEFAULT"),
.SLEW("SLOW")
)
IOBUF_gpio_7
(
.O(iobuf_gpio_7_o),
.IO(gpio_7),
.I(dut_io_pads_gpio_7_o_oval),
.T(~dut_io_pads_gpio_7_o_oe)
);
assign dut_io_pads_gpio_7_i_ival = iobuf_gpio_7_o & dut_io_pads_gpio_7_o_ie;
这里 IOBUF 的写法是 Xilinx 软件支持的,含义可以猜出来。输出使能信号 dut_io_pads_gpio_7_o_oe 决定了实际 FPGA 引脚是否驱动(输出),输入使能信号 dut_io_pads_gpio_7_o_ie 是决定引脚上的电平状态是否反映到内部信号,还有 dut_io_pads_gpio_7_o_pue (上拉允许) 和 dut_io_pads_gpio_7_o_ds (用途不明确?) 两个信号就没有使用。好象 FPGA 不支持动态改变引脚的上拉功能,所以用不上。
GPIO在板子上的分配,demo里面写得略复杂了一点。我就先给简化了:GPIO0~8 直接连到三个 RGB LED,其余连到 Arduino 接口,再剩下的连到 PMOD 口上去。
assign led0_r = gpio_0;
assign led0_g = gpio_1;
assign led0_b = gpio_2;
assign led1_r = gpio_3;
assign led1_g = gpio_4;
assign led1_b = gpio_5;
assign led2_r = gpio_6;
assign led2_g = gpio_7;
assign led2_b = gpio_8;
assign arduino_a[0] = gpio_9;
assign arduino_a[1] = gpio_10;
assign arduino_a[2] = gpio_11;
assign arduino_a[3] = gpio_12;
assign arduino_a[4] = gpio_13;
assign arduino_a[5] = gpio_14;
assign arduino_d[0] = gpio_15;
assign arduino_d[1] = gpio_16;
assign arduino_d[2] = gpio_17;
assign arduino_d[3] = gpio_18;
assign arduino_d[4] = gpio_19;
assign arduino_d[5] = gpio_20;
assign arduino_d[6] = gpio_21;
assign arduino_d[7] = gpio_22;
assign arduino_d[8] = gpio_23;
assign arduino_d[9] = gpio_24;
assign arduino_d[10] = gpio_25;
assign arduino_d[11] = gpio_26;
assign arduino_d[12] = gpio_27;
assign arduino_d[13] = gpio_28;
assign pmod_io[0] = gpio_29;
assign pmod_io[1] = gpio_30;
assign pmod_io[2] = gpio_31;
E203 SOC 的主时钟是 16MHz, 但开发板的晶振是 50MHz. 因此这里需要一个 PLL 把时钟转换一下。这就要用到 FPGA 提供的 IP 了。参考 demo 的实现,用一个 MMCM IP模块,得到 16MHz 和 8.388MHz 两个时钟,再将 8.388MHz 1/256分频到32768kHz左右作为 LF 时钟。暂时也不需要很准。其实 16MHz 也不是必须的,比如直接将 50MHz 分频到 25MHz 或 12.5MHz 给 RISC-V 用也问题不大,除了UART时钟受影响要重新设以外。
E203 SOC 的复位信号(输入)是 io_pads_aon_erst_n_i_ival, 在 demo 里面用了一个复位模块IP来提供,但是我之前遇到的失败就从这里引起。于是我不用它,简单点就用 PLL 的锁定信号和外部复位按键组合代替了:
assign reset_periph = ~ck_rst | ~mmcm_locked;
assign dut_io_pads_aon_erst_n_i_ival = ~reset_periph;
JTAG的4个信号照着 demo 里面那样处理,这是必不可缺少的。QSPI 的信号是连到板子上 flash 的,用来存放RISC-V程序。在调试阶段可以先不管 QSPI.
连接好 FPGA 接口以后就可以开始软件综合了,发现错误再回去改。
还有一个工作是将 FPGA 的接口信号映射到 FPGA 管脚。demo工程里面已经有约束文件了,可以提取出来修改了用,不然全照着电路图一个一个设管脚就很繁琐了。
完成综合实现之后,Vivado显示的 FPGA 资源使用情况:
将生成的 .bit 文件下载到 FPGA 中之后,需要检查SOC的工作情况。这一步需要借助调试器,从SOC的JTAG口访问内部资源。这个JTAG不是FPGA的JTAG, 而是FPGA用户逻辑实现的和RISC-V相连的JTAG. 在开发板上两个JTAG口的针脚定义也一样,PerfXLab的下载线也应该是支持两种JTAG用法的。但是我不想拔来插去的,就用另外的工具来处理RISC-V这边的JTAG了。
我用了一片 FT232R 当作调试硬件,用 openocd 连接开发板上的 E203 SOC:
有这样的信息提示就表示识别到 RISC-V CPU 了。
因为现在系统里面没有程序,CPU当处于异常状态。
Open On-Chip Debugger
> halt
> reg pc
pc (/32): 0x00000000
> step
halted at 0x0 due to step
> mdw 0 4
0x00000000: 00000000 00000000 00000000 00000000
>
要执行程序,一种办法是用 openocd 直接将代码写到内存里面去,然后修改 PC 寄存器,执行。
> load_image f:/rv32.hex
1602 bytes written at address 0x80000000
72 bytes written at address 0x80001000
16 bytes written at address 0x80002fb8
downloaded 1690 bytes in 1.156250s (1.427 KiB/s)
> resume 0x80000000
> halt
halted at 0x80000090 due to debug interrupt
>
GDB也可以拿来调试程序。
GNU gdb (GDB) 8.3.0.20190516-git
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "--host=i686-w64-mingw32 --target=riscv-nuclei-elf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb) set arch riscv:rv32
The target architecture is assumed to be riscv:rv32
(gdb) target remote localhost:3333
Remote debugging using localhost:3333
warning: No executable has been specified and target does not support
determining executable automatically. Try using the "file" command.
0x80000090 in ?? ()
(gdb) x /3i $pc
=> 0x80000090: sw gp,-140(t5)
0x80000094: j 0x80000086
0x80000096: auipc a0,0x2
(gdb) si
halted at 0x80000094 due to step
0x80000094 in ?? ()
(gdb) si
halted at 0x80000086 due to step
0x80000086 in ?? ()
(gdb) si
halted at 0x80000088 due to step
0x80000088 in ?? ()
(gdb) si
halted at 0x8000008c due to step
0x8000008c in ?? ()
(gdb) si
halted at 0x80000090 due to step
0x80000090 in ?? ()
上面载入内存的代码是E203代码树中测试指令集的一个程序,看起来,测试内容完成之后就进入一个循环了。单步跟踪的结果是 0x80000086 到 0x80000094 之间循环。
看一下程序的 dump 内容对比:
80000086 <write_tohost>:
80000086: 4521 li a0,8
80000088: 30052073 csrs mstatus,a0
8000008c: 00001f17 auipc t5,0x1
80000090: f63f2a23 sw gp,-140(t5) # 80001000 <tohost>
80000094: bfcd j 80000086 <write_tohost>
这样的程序没有进行 I/O 操作,不能从硬件上观察到变化。下面需要用程序测试下 GPIO.
|