[分享] 【Perf-V评测】蜂鸟E203开源SOC的学习及RTL仿真实验

cruelfox   2021-6-12 19:45 楼主

  Perf-V FPGA板的资源可以实现RISC-V CPU核,即用FPGA中的硬逻辑搭出来一个CPU. 这个“搭积木”的过程就是FPGA综合工具干的活了——是将CPU逻辑的描述(源代码)翻译成硬件(逻辑门、寄存器等)的过程。用硬件描述语言(HDL)实现的CPU并且其源代码是开放的,就可称作开源CPU. 开源的RISC-V CPU已经有不少了,可以作为学习和实验RISC-V的基础。

  E203是蜂鸟E200系列RISC-V IP核中的一款开源软核,用Verilog语言编写。E203作者写了两本关于RISC-V的书对E203的设计和应用进行了较为详细的介绍,一本偏重硬件,一本侧重软件。想深入了解RISC-V的朋友可以找来读。

book.PNG

  Perf-V官方提供的技术资料里面包含了蜂鸟E203开源SOC的FPGA demo工程,是把Xilinx Vivado的工程目录整个打包了。

baidu.PNG   不过几十MB的压缩包中,几乎都是Vivado生成的文件,源代码只占一点点。要学习E203源代码,可以从里面找,也可以从github单独下载。


  E203 SOC不仅仅是一个CPU,它包含了E203 RISC-V核之外的总线、内存块、外设功能模块等,相当于一个完整的MCU. 

soc.PNG   在正式到板子上玩 RISC-V 之前,有必要对 E203 SOC 的结构进行了解。

 

  下面是我从 github 上抓下来的 E203 RTL代码树,一共有几个子目录,源码总共不到3MB.

rtl
├─core
│      config.v
│      e203_biu.v
│      e203_clkgate.v
│      e203_clk_ctrl.v
│      e203_core.v
│      e203_cpu.v
│      e203_cpu_top.v
│      e203_defines.v
│      e203_dtcm_ctrl.v
│      e203_dtcm_ram.v
│      e203_extend_csr.v
│      e203_exu.v
│      e203_exu_alu.v
│      e203_exu_alu_bjp.v
│      e203_exu_alu_csrctrl.v
│      e203_exu_alu_dpath.v
│      e203_exu_alu_lsuagu.v
│      e203_exu_alu_muldiv.v
│      e203_exu_alu_rglr.v
│      e203_exu_branchslv.v
│      e203_exu_commit.v
│      e203_exu_csr.v
│      e203_exu_decode.v
│      e203_exu_disp.v
│      e203_exu_excp.v
│      e203_exu_longpwbck.v
│      e203_exu_oitf.v
│      e203_exu_regfile.v
│      e203_exu_wbck.v
│      e203_ifu.v
│      e203_ifu_ifetch.v
│      e203_ifu_ift2icb.v
│      e203_ifu_litebpu.v
│      e203_ifu_minidec.v
│      e203_irq_sync.v
│      e203_itcm_ctrl.v
│      e203_itcm_ram.v
│      e203_lsu.v
│      e203_lsu_ctrl.v
│      e203_reset_ctrl.v
│      e203_srams.v
│      
├─debug
│      sirv_debug_csr.v
│      sirv_debug_module.v
│      sirv_debug_ram.v
│      sirv_debug_rom.v
│      sirv_jtag_dtm.v
│      
├─fab
│      sirv_icb1to16_bus.v
│      sirv_icb1to2_bus.v
│      sirv_icb1to8_bus.v
│      
├─general
│      sirv_1cyc_sram_ctrl.v
│      sirv_gnrl_bufs.v
│      sirv_gnrl_dffs.v
│      sirv_gnrl_icbs.v
│      sirv_gnrl_ram.v
│      sirv_gnrl_xchecker.v
│      sirv_sim_ram.v
│      sirv_sram_icb_ctrl.v
│      
├─mems
│      sirv_mrom.v
│      sirv_mrom_top.v
│      
├─perips
│      i2c_master_bit_ctrl.v
│      i2c_master_byte_ctrl.v
│      i2c_master_defines.v
│      i2c_master_top.v
│      sirv_aon.v
│      sirv_aon_lclkgen_regs.v
│      sirv_aon_porrst.v
│      sirv_aon_top.v
│      sirv_aon_wrapper.v
│      sirv_AsyncResetReg.v
│      sirv_AsyncResetRegVec.v
│      sirv_AsyncResetRegVec_1.v
│      sirv_AsyncResetRegVec_129.v
│      sirv_AsyncResetRegVec_36.v
│      sirv_AsyncResetRegVec_67.v
│      sirv_clint.v
│      sirv_clint_top.v
│      sirv_DeglitchShiftRegister.v
│      sirv_expl_apb_slv.v
│      sirv_expl_axi_slv.v
│      sirv_flash_qspi.v
│      sirv_flash_qspi_top.v
│      sirv_gpio.v
│      sirv_gpio_top.v
│      sirv_hclkgen_regs.v
│      sirv_jtaggpioport.v
│      sirv_LevelGateway.v
│      sirv_otp_top.v
│      sirv_plic_man.v
│      sirv_plic_top.v
│      sirv_pmu.v
│      sirv_pmu_core.v
│      sirv_pwm16.v
│      sirv_pwm16_core.v
│      sirv_pwm16_top.v
│      sirv_pwm8.v
│      sirv_pwm8_core.v
│      sirv_pwm8_top.v
│      sirv_pwmgpioport.v
│      sirv_qspi_1cs.v
│      sirv_qspi_1cs_top.v
│      sirv_qspi_4cs.v
│      sirv_qspi_4cs_top.v
│      sirv_qspi_arbiter.v
│      sirv_qspi_fifo.v
│      sirv_qspi_media.v
│      sirv_qspi_media_1.v
│      sirv_qspi_media_2.v
│      sirv_qspi_physical.v
│      sirv_qspi_physical_1.v
│      sirv_qspi_physical_2.v
│      sirv_queue.v
│      sirv_queue_1.v
│      sirv_repeater_6.v
│      sirv_ResetCatchAndSync.v
│      sirv_ResetCatchAndSync_2.v
│      sirv_rtc.v
│      sirv_spigpioport.v
│      sirv_spigpioport_1.v
│      sirv_spigpioport_2.v
│      sirv_spi_flashmap.v
│      sirv_tlfragmenter_qspi_1.v
│      sirv_tlwidthwidget_qspi.v
│      sirv_tl_repeater_5.v
│      sirv_uart.v
│      sirv_uartgpioport.v
│      sirv_uartrx.v
│      sirv_uarttx.v
│      sirv_uart_top.v
│      sirv_wdog.v
│      
├─soc
│      e203_soc_top.v
│      
└─subsys
        e203_subsys_clint.v
        e203_subsys_gfcm.v
        e203_subsys_hclkgen.v
        e203_subsys_hclkgen_rstsync.v
        e203_subsys_main.v
        e203_subsys_mems.v
        e203_subsys_perips.v
        e203_subsys_plic.v
        e203_subsys_pll.v
        e203_subsys_pllclkdiv.v
        e203_subsys_top.v

  文档中给出的层次结构描述是这样的:

subsys.PNG

  和这套RTL代码一起提供的还有很多测试例,可以对E203 CPU进行仿真测试。

  在tb目录下有一个 tb_top.v 的文件,就是测试用的顶层模块。在PC机上,用Verilog语言的仿真工具,差不多就可以做仿真实验了。在文档中,作者提到使用的是iverilog这个免费的仿真软件(全称 Icarus Verilog) ,正好我也用这个,就直接调用 iverilog 处理源代码。

  不过一开始就遇到错误了:包含文件 e200_defines.v 找不到。原来 rtl/core 里面文件名是 e203_defines.v 不一样。改了下,然后居然宏定义缺失。仔细一看,是 tb_top.v 里面到处使用的是 e200 和 E200 字样,而 rtl 代码树中都没有出现,遂都分别改为 e203 和 E203 才可以。

  因为我不想逐个添加 verilog 文件,就用了 iverilog 的 +libdir 命令指定子目录。这样还有一些模块找不到的错误,是因为引用的 verilog 模块名没有对应到 verilog 文件名(一个文件可以包含多个模块定义),故还需要单独指定一些文件。调整好以后,写成的文件如下:

+incdir+rtl/core
+incdir+rtl/perips
+define+DISABLE_SV_ASSERTION=1
rtl/general/sirv_1cyc_sram_ctrl.v
rtl/general/sirv_gnrl_icbs.v
rtl/general/sirv_sim_ram.v
rtl/general/sirv_gnrl_bufs.v
rtl/general/sirv_gnrl_ram.v
rtl/general/sirv_sram_icb_ctrl.v
rtl/general/sirv_gnrl_dffs.v
rtl/general/sirv_gnrl_xchecker.v
rtl/perips/sirv_spi_flashmap.v
+libdir+rtl/core
+libdir+rtl/mems
+libdir+rtl/debug
+libdir+rtl/fab
+libdir+rtl/perips
+libdir+rtl/soc
+libdir+rtl/subsys
tb/tb_top.v

将这个文件命名为 sim_e203.f 再用 iverilog -f sim_e203.f 即可编译。

  如果成功了,执行 vvp a.out 进行仿真。

  这时候 vvp 报错了,是 tb_top.v 中这里有问题

  initial begin
    $value$plusargs("DUMPWAVE=%d",dumpwave);
    if(dumpwave != 0)begin
         // To add your waveform generation function
    end
  end

  我暂时也不用 dump ,先给把这段注释屏蔽掉不管。然后再来一次,出现仿真运行的信息了:

E:\Perf-V\e203>vvp a.out
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
ERROR: tb/tb_top.v:257: $readmemh: Unable to open .verilog for reading.
ITCM 0x00: xxxxxxxxxxxxxxxx
ITCM 0x01: xxxxxxxxxxxxxxxx
ITCM 0x02: xxxxxxxxxxxxxxxx
ITCM 0x03: xxxxxxxxxxxxxxxx
ITCM 0x04: xxxxxxxxxxxxxxxx
ITCM 0x05: xxxxxxxxxxxxxxxx
ITCM 0x06: xxxxxxxxxxxxxxxx
ITCM 0x07: xxxxxxxxxxxxxxxx
ITCM 0x16: xxxxxxxxxxxxxxxx
ITCM 0x20: xxxxxxxxxxxxxxxx

  看来是需要加载的内存数据文件缺少了,不能正确初始化。根据提示找到代码中读取文件的位置,便知道了需要提供 TESTCASE 宏指定一个数据文件。

  在 github 上 e200_opensource/riscv-tools/riscv-tests/isa/generated/ 下有许多编译过的文件,以 .verilog 结尾,可以直接拿来测试。这个目录下的测试例是测试指令集的。比如我使用 rv32ui-p-add.verilog 这个文件,就执行:

E:\Perf-V\e203>vvp a.out +TESTCASE=rv32ui-p-add
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
TESTCASE=                    rv32ui-p-add
ITCM 0x00: 340510730001aa0d
ITCM 0x01: ff85051300002517
ITCM 0x02: 01f5222301e52023
ITCM 0x03: 040f416334202f73
ITCM 0x04: 4fa507ff02634fa1
ITCM 0x05: 0c634fad05ff0f63
ITCM 0x06: 0bff05634f8505ff
ITCM 0x07: 4f9d0dff00634f95
ITCM 0x16: 2f03f52505130000
ITCM 0x20: 2f8300052f03f065

然后等待仿真结果。

 

  但是等了很久都没有出来结果。电脑的CPU也一直占用着…… Ctrl-C中止了它,仿真时间还是在0,有奇怪的问题了。

  不解,我就到网上搜搜看有没有人遇到同样问题的。还真有人提到E203仿真的坑,就点开看看。据说是 iverilog 版本10不行,需要版本12. 而我用的 win32 port 版本正好是v10.0 ,而且更高版本的已经没有现成的win32 port了。

  那么远程登陆到单位的Linux工作站上去试试吧。iverilog 肯定是要自己编译的,好在源代码并不大,从 github 上很快拖下来了。准备编译,需要 gperf 但该机上没有,现装,可以用。make 过程中, g++ 报错了,估计是GCC版本不够高的原因。我看了下,出错的代码是

      buf << (file_? file_ : "") << ":" << lineno_;

  那么就修改成 if-else 语句来用吧。net_scope.cc 文件改了两处,编译通过。最后 iverilog 编译完成了。

  再执行 iverilog 和 vvp ,不到一分钟,仿真运行结果出来了:

sim.PNG 看来自家机器仿真不了是iverilog软件的问题。

回复评论 (5)

  用仿真耗时和"Total cycle_count"表达的仿真过程主时钟周期数进行折算,相当于 E203 SOC 的仿真运行频率只有 800Hz 左右.

  这样的仿真虽然耗时,但因为可以查看每个信号的状态,适合查错用。上面我试的仿真内容是验证 add 指令,其测试代码(RISC-V指令)是用汇编语言写的,而且用了宏定义,故不熟悉指令架构就难以明白其意图。例如,这是汇编文件片段:

  #-------------------------------------------------------------
  # Arithmetic tests
  #-------------------------------------------------------------

  TEST_RR_OP( 2,  add, 0x00000000, 0x00000000, 0x00000000 );
  TEST_RR_OP( 3,  add, 0x00000002, 0x00000001, 0x00000001 );
  TEST_RR_OP( 4,  add, 0x0000000a, 0x00000003, 0x00000007 );

  TEST_RR_OP( 5,  add, 0xffffffffffff8000, 0x0000000000000000, 0xffffffffffff8000 );
  TEST_RR_OP( 6,  add, 0xffffffff80000000, 0xffffffff80000000, 0x00000000 );
  TEST_RR_OP( 7,  add, 0xffffffff7fff8000, 0xffffffff80000000, 0xffffffffffff8000 );

  TEST_RR_OP( 8,  add, 0x0000000000007fff, 0x0000000000000000, 0x0000000000007fff );
  TEST_RR_OP( 9,  add, 0x000000007fffffff, 0x000000007fffffff, 0x0000000000000000 );
  TEST_RR_OP( 10, add, 0x0000000080007ffe, 0x000000007fffffff, 0x0000000000007fff );

  TEST_RR_OP( 11, add, 0xffffffff80007fff, 0xffffffff80000000, 0x0000000000007fff );
  TEST_RR_OP( 12, add, 0x000000007fff7fff, 0x000000007fffffff, 0xffffffffffff8000 );

  TEST_RR_OP( 13, add, 0xffffffffffffffff, 0x0000000000000000, 0xffffffffffffffff );
  TEST_RR_OP( 14, add, 0x0000000000000000, 0xffffffffffffffff, 0x0000000000000001 );
  TEST_RR_OP( 15, add, 0xfffffffffffffffe, 0xffffffffffffffff, 0xffffffffffffffff );

  generated 目录下生成的机器码包括仿真用的 verilog 文件(相当于单片机烧写用的 .hex 文件的作用),编译得到的 .elf 文件,以及反汇编 ELF 文件的结果 *.dump.

 

  看一下 .dump 文件的内容:很长,不能贴全在这里了

Disassembly of section .text.init:

80000000 <_start>:
80000000:	aa0d                	j	80000132 <reset_vector>
80000002:	0001                	nop

80000004 <trap_vector>:
80000004:	34051073          	csrw	mscratch,a0
80000008:	00002517          	auipc	a0,0x2
8000000c:	ff850513          	addi	a0,a0,-8 # 80002000 <test_trap_data>
80000010:	01e52023          	sw	t5,0(a0)
80000014:	01f52223          	sw	t6,4(a0)
80000018:	34202f73          	csrr	t5,mcause
8000001c:	040f4163          	bltz	t5,8000005e <other_interrupts>
80000020:	4fa1                	li	t6,8
80000022:	07ff0263          	beq	t5,t6,80000086 <write_tohost>
80000026:	4fa5                	li	t6,9
80000028:	05ff0f63          	beq	t5,t6,80000086 <write_tohost>
8000002c:	4fad                	li	t6,11
8000002e:	05ff0c63          	beq	t5,t6,80000086 <write_tohost>
80000032:	4f85                	li	t6,1
80000034:	0bff0563          	beq	t5,t6,800000de <ifetch_error_handler>
80000038:	4f95                	li	t6,5
8000003a:	0dff0063          	beq	t5,t6,800000fa <load_error_handler>
8000003e:	4f9d                	li	t6,7
80000040:	0dff0b63          	beq	t5,t6,80000116 <store_error_handler>
80000044:	80000f17          	auipc	t5,0x80000
80000048:	fbcf0f13          	addi	t5,t5,-68 # 0 <_start-0x80000000>
8000004c:	000f0363          	beqz	t5,80000052 <trap_vector+0x4e>
80000050:	8f02                	jr	t5
80000052:	34202f73          	csrr	t5,mcause
80000056:	000f5363          	bgez	t5,8000005c <handle_exception>
8000005a:	a009                	j	8000005c <handle_exception>

8000005c <handle_exception>:
8000005c:	a01d                	j	80000082 <other_interrupts+0x24>

8000005e <other_interrupts>:
8000005e:	80000fb7          	lui	t6,0x80000
80000062:	003f8f93          	addi	t6,t6,3 # 80000003 <_end+0xffffdff3>
80000066:	05ff0463          	beq	t5,t6,800000ae <sft_irq_handler>
8000006a:	80000fb7          	lui	t6,0x80000
8000006e:	007f8f93          	addi	t6,t6,7 # 80000007 <_end+0xffffdff7>
80000072:	05ff0a63          	beq	t5,t6,800000c6 <tmr_irq_handler>
80000076:	80000fb7          	lui	t6,0x80000
8000007a:	00bf8f93          	addi	t6,t6,11 # 8000000b <_end+0xffffdffb>
8000007e:	01ff0c63          	beq	t5,t6,80000096 <ext_irq_handler>
80000082:	5391e193          	ori	gp,gp,1337

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>

80000096 <ext_irq_handler>:
80000096:	00002517          	auipc	a0,0x2
8000009a:	f6a50513          	addi	a0,a0,-150 # 80002000 <test_trap_data>
8000009e:	00052f03          	lw	t5,0(a0)
800000a2:	00452f83          	lw	t6,4(a0)
800000a6:	34002573          	csrr	a0,mscratch
800000aa:	30200073          	mret

800000ae <sft_irq_handler>:
800000ae:	00002517          	auipc	a0,0x2
800000b2:	f5250513          	addi	a0,a0,-174 # 80002000 <test_trap_data>
800000b6:	00052f03          	lw	t5,0(a0)
800000ba:	00452f83          	lw	t6,4(a0)
800000be:	34002573          	csrr	a0,mscratch
800000c2:	30200073          	mret

......

80000132 <reset_vector>:
80000132:	00000f13          	li	t5,0
80000136:	00000f93          	li	t6,0
8000013a:	f1402573          	csrr	a0,mhartid
8000013e:	e101                	bnez	a0,8000013e <reset_vector+0xc>
80000140:	4181                	li	gp,0
80000142:	00000297          	auipc	t0,0x0
80000146:	ec228293          	addi	t0,t0,-318 # 80000004 <trap_vector>
8000014a:	4521                	li	a0,8
8000014c:	30052073          	csrs	mstatus,a0
80000150:	fff00513          	li	a0,-1
80000154:	30452073          	csrs	mie,a0
80000158:	30529073          	csrw	mtvec,t0

8000015c <post_mtvec>:
8000015c:	80000297          	auipc	t0,0x80000
80000160:	ea428293          	addi	t0,t0,-348 # 0 <_start-0x80000000>
80000164:	00028e63          	beqz	t0,80000180 <post_mtvec+0x24>
80000168:	10529073          	csrw	stvec,t0
8000016c:	0000b2b7          	lui	t0,0xb
80000170:	10928293          	addi	t0,t0,265 # b109 <_start-0x7fff4ef7>
80000174:	30229073          	csrw	medeleg,t0
80000178:	30202373          	csrr	t1,medeleg
8000017c:	ee6290e3          	bne	t0,t1,8000005c <handle_exception>
80000180:	30005073          	csrwi	mstatus,0
80000184:	08000513          	li	a0,128
80000188:	30052073          	csrs	mstatus,a0
8000018c:	4501                	li	a0,0
8000018e:	bfc51073          	csrw	0xbfc,a0
80000192:	0000100f          	fence.i
80000196:	000012b7          	lui	t0,0x1
8000019a:	a0028293          	addi	t0,t0,-1536 # a00 <_start-0x7ffff600>

8000019e <waitloop1>:
8000019e:	12fd                	addi	t0,t0,-1
800001a0:	fe029fe3          	bnez	t0,8000019e <waitloop1>
800001a4:	100083b7          	lui	t2,0x10008
800001a8:	00838393          	addi	t2,t2,8 # 10008008 <_start-0x6fff7ff8>
800001ac:	0003a283          	lw	t0,0(t2)
800001b0:	00040337          	lui	t1,0x40
800001b4:	fff34313          	not	t1,t1
800001b8:	0062f2b3          	and	t0,t0,t1
800001bc:	0053a023          	sw	t0,0(t2)
800001c0:	40000293          	li	t0,1024

800001c4 <waitloop2>:
800001c4:	12fd                	addi	t0,t0,-1
800001c6:	0003ae03          	lw	t3,0(t2)
800001ca:	fe029de3          	bnez	t0,800001c4 <waitloop2>
800001ce:	0003a283          	lw	t0,0(t2)
800001d2:	00040337          	lui	t1,0x40
800001d6:	0062e2b3          	or	t0,t0,t1
800001da:	0053a023          	sw	t0,0(t2)
......

80000262 <test_2>:
80000262:	4081                	li	ra,0
80000264:	4101                	li	sp,0
80000266:	00208f33          	add	t5,ra,sp
8000026a:	4e81                	li	t4,0
8000026c:	4189                	li	gp,2
8000026e:	37df1d63          	bne	t5,t4,800005e8 <fail>

80000272 <test_3>:
80000272:	4085                	li	ra,1
80000274:	4105                	li	sp,1
80000276:	00208f33          	add	t5,ra,sp
8000027a:	4e89                	li	t4,2
8000027c:	418d                	li	gp,3
8000027e:	37df1563          	bne	t5,t4,800005e8 <fail>

80000282 <test_4>:
80000282:	408d                	li	ra,3
80000284:	411d                	li	sp,7
80000286:	00208f33          	add	t5,ra,sp
8000028a:	4ea9                	li	t4,10
8000028c:	4191                	li	gp,4
8000028e:	35df1d63          	bne	t5,t4,800005e8 <fail>

......

  我尚不熟悉RISC-V的指令集,因此大部分都看不懂。

  这段代码是直接放到 E203 SOC 的 ITCM 中的,因此一启动就执行了。它包含了中断向量的处理,是一个完整的程序,只是并不像我们做MCU程序要操作片上设备以实现功能。

 

  想查看仿真的细节,用verilog系统的dump功能就可以。在 tb_top.v 中加这两行,将 e_203_cpu 这个模块以及其下级的信号导出成vcd文件:

        $dumpfile("e203test.vcd");
        $dumpvars(0, u_e203_soc_top.u_e203_subsys_top.u_e203_subsys_main.u_e203_cpu_top.u_e203_cpu);
  再重新做仿真,就会每次都生成 e203test.vcd 这个文件(几百MB,是纯文本文件)。然后可以用 gtkwave 这个软件来看波形了:

vcd.PNG   这样可以观看 CPU 是如何取指令,如何执行指令等等的每一步状态变化,可以结合源代码阅读,帮助理解E203 RISC-V CPU的设计。

 

  既然 E203 SOC 整体都被仿真了,要写程序控制 GPIO、UART 也是可以做到,并且从仿真可以观察到的。只是,那样就适合用 FPGA 在电路中验证。

本帖最后由 cruelfox 于 2021-6-12 22:36 编辑
点赞 (1) 2021-6-12 21:50

测试代码(RISC-V指令)是用汇编语言写

楼主很强大

点赞  2021-6-13 10:55

用汇编语言写楼主很强大

点赞  2021-6-16 10:38

看不懂,但感觉很厉害,希望能早入达到楼主的水平。

点赞  2021-12-22 10:36

请问楼主有没有试过从flash启动的仿真

点赞  2024-4-26 15:57
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复