一、Scala配置项修改和RTL代码生成
可以通过对scala中的配置项修改,来达到定制化配置RISC-V的目的,这里总结几个比较常用的配置项、配置项含义和所在的scala中的位置:
1.$rocket-chip/src/main/scala/system/Config.scala
1)new WithNExtTopInterrupts(128) ++,修改中断输入数量,这里是128个外部中断;
2)new WithDefaultMemPort ++、new WithDefaultMMIOPort ++、new WithDefaultSlavePort ++,添加、删除和修改AXI4 MEM、IOMaster和L2Slave接口;(支持AXI/AHB/TileLink)
3)new WithNSmallCores(2),修改内核数量,这里配置的是2;
4)new WithNMemoryChannels(2) ++,修改AXI4 MEM接口数量,这里会产生2组AXI4 MEM接口;
5)class Default2RV64SmallConfig extends Config(new WithNSmallCores(2) ++ new WithCoherentBusTopology ++ new BaseConfig),按照自己的配置定义RISC-V核,其中Default2RV64SmallConfig名称会被RTL生成器调用。
2.$rocket-chip/src/main/scala/subsystem/Config.scala
1)beatBytes = site(XLen)/8,修改AXI4数据位宽,如果是RV32,这里数据位宽是32bits;
2)case ExtMem => Some(MemoryPortParams(MasterPortParams(
base = x"8000_0000",
size = x"1000_0000",
beatBytes = site(MemoryBusKey).beatBytes,
idBits = 4), 1))
修改MEM/IO空间地址段位置和大小,这里MEM空间地址为0x8000_0000到0x1000_0000;
3)dcache = Some(DCacheParams(
nSets = 64,
nWays = 1,
nTLBSets = 1,
nTLBWays = 4,
tagECC = Some("secded"),
dataECC = Some("secded"),
dataECCBytes = 4,
修改Cache大小、组相连数量、添加Cache ECC支持等。
4)case BootROMLocated(InSubsystem) => Some(BootROMParams(contentFileName = "./bootrom/bootrom.img")),修改bootrom.img所在位置,这里是默认所在位置;
3.$rocket-chip/src/main/scala/rocket/RocketCore.scala
1) useVM: Boolean = false,修改虚拟内存使能,这里是关闭了虚拟内存;
2) haveCFlush: Boolean = true,修改Flush Cache使能,是否能通过指令Flush Cache数据;
3) clockGate: Boolean = true,时钟Gate是否使能,打开可降低功耗;
4) nPMPs: Int = 8,设置PMP内存保护地址段数量,RV32最大是8,RV64最大是16;
除此以外,还可配置:原子操作支持、压缩指令支持、断点数、不可屏蔽中断、特权模式使能、乘法除法等功能,这里不一一介绍了。
4.$rocket-chip/src/main/scala/devices/debug/Periphery.scala
1)protocols: Set[DebugExportProtocol] = Set(JTAG),修改Debug接口,可以选DMI/JTAG/APB等,这里选JTAG。
5.$rocket-chip/src/main/scala/devices/debug/Periphery.scala
require(reset_vector_source.getWidth >= params.address.bitLength, s"BootROM defined with a reset vector (${params.address})too large for physical address space ${reset_vector_source.getWidth})") bootROMResetVectorSourceNode.bundle := params.address.U,修改复位向量地址,修改后系统复位从该地址开始读取数据,这里改成了0x1000。
除此以外,还可以修改使能如TLB、分支预测、配置时钟异步、本地中断等功能,需要研究对应的scala代码并进行修改。
Scala代码修改完成后,在$rocket-chip/vsim或者$rocket-chip/vsim目录下运行:
1make verilog CONFIG=freechips.rocketchip.system.Default2RV32SmallConfig
其中CONFIG的名称为$rocket-chip/src/main/scala/system/Config.scala中修改的名称。RocketChip生成器会将scala语言转换成RTL代码,并生成其他相关文件,最终得到如下图所示的verilog代码和相关仿真测试文件。

二、工具链和编译库生成
工具链和编译库可以在网上下载,或者通过$rocket-tools目录下的build.sh/build-rv32ima.sh对当前配置进行一次交叉编译。在进行交叉编译前,指定编译出来的工具链所在位置,可在bashrc中指定如下位置:
1
2
3#RISC-V gnu toolchain export RISCV=/home/alanwu/Documents/RISCV/rocket-tools export PATH=/opt/riscv/bin:$PATH
如果指定的是/opt/文件夹下,需要注意权限的设置,完成后使用如下命令进行交叉编译:
1
2./build.sh #RV64使用该命令 ./build-rv32ima.sh #RV32使用该命令
交叉编译的时间有点长,最终会在指定目录下产生如下图所示的工具链文件。

如果是网上下载的,可以直接把文件放到对应目录,然后在bashrc中指定路径即可。
三、VCS硬件仿真环境搭建
建立一个RISCV-SIM文件夹,VCS硬件仿真都在该文件夹中完成,如下图所示,其中case_log文件夹用来放跑vcs的log信息,hardware_code文件夹用来放RTL代码,program_code文件夹用来放需要跑的case,wave_dump_log文件夹用来放跑出来的case波形。

1.程序编译
这里从仿真需要的顺序依次介绍下脚本的使用,首先是make_debug.sh脚本,该脚本是用来编译case,把基于C的case编译成镜像文件。首先会调用program_code文件夹下的riscv2bin.sh文件,该文件将c编译出来的.riscv文件转换成.bin/.elf文件。
1
2
3
4
5
6
7
8
9
10
11#!/bin/bash #make_debug.sh GCC=riscv32-unknown-elf-gcc OBJCOPY=riscv32-unknown-elf-objcopy OBJDUMP=riscv32-unknown-elf-objdump ELF_NAME=$1 BIN_NAME=`basename $ELF_NAME .bin` HEX_NAME=`basename $ELF_NAME .hex` #convert elf to bin $OBJCOPY -O binary ${ELF_NAME}.riscv ${BIN_NAME}.bin
然后把bin/elf文件转换成纯二进制的hex文件,该文件实际没有文件格式,就是CPU可执行的二进制机器码,理论上应该叫.img文件。这里需要注意.hex的数据格式的转换,由于该二进制文件最终要放入仿真的mem中,因此需要根据mem位宽进行数据转换,使用program_code文件夹下的bin2hex_128.sh或bin2hex_256.sh脚本自动转换。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21#!/bin/bash BIN_NAME=$1 BASE_NAME=`basename $BIN_NAME .bin` HEX_NAME=`basename $BIN_NAME .hex` #dump hex for 32 bits width hexdump -v -e/'4 "%08xn"' ${BIN_NAME}.bin > temp.hex #swap even and add rows sed '$!N;s/([^n]*)n([^n]*)/2n1/' temp.hex | tee > temp1.hex #merge 2 lines into 1 line, 32bits -> 64bits awk '{if(NR%2==0) {printf $0 "n"} else{printf $0}}' temp1.hex | tee > temp2.hex #swap even and add rows again sed '$!N;s/([^n]*)n([^n]*)/2n1/' temp2.hex | tee > temp1.hex #merge 2 lines into 1 line again, 64bits -> 128bits awk '{if(NR%2==0) {printf $0 "n"} else{printf $0}}' temp1.hex | tee > temp2.hex #swap even and add rows again sed '$!N;s/([^n]*)n([^n]*)/2n1/' temp2.hex | tee > temp1.hex #merge 2 lines into 1 line again, 128bits -> 256bits awk '{if(NR%2==0) {printf $0 "n"} else{printf $0}}' temp1.hex | tee > ${HEX_NAME}.hex rm temp.hex temp1.hex temp2.hex chmod 755 ${HEX_NAME}.hex
完成后,将hex文件复制到RSICV-SIM文件夹下,该文件最终会在top.v中的verilog仿真代码中调用,并被初始化到MEM中。
1
2
3
4initial begin #1ns; $readmemh("./program.hex", top.TestHarness.mem.srams.mem.mem_ext.ram); end
2.VCS硬件仿真
在跑VCS之前,需要指定RTL代码路径和include文件,编写filelist文件,把需要的内容添加到仿真中。除此之外,还需要完成testbech的编写和波形dump的参数,也就是top.v的代码编写。波形dump可以参考如下代码实现,这里是全脚本自动化的过程,也就是说每个case自动跑并自动判断结束条件,然后进行一个case,方便全case的自动化回归测试。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53initial begin // #490us; $fsdbDumpfile("./wave_dump_log/helloworld.fsdb"); // $fsdbAutoSwitchDumpfile(10000, "WAVEFORM_DEMO/helloworld.fsdb", 10); // $fsdbDumpvars(3, top); // $fsdbDumpvars(0, "top.TestHarness.mem", "+mda"); $fsdbDumpvars(0, top); #1200us; $system("date +%y%m%d%H%M%S > timelog"); $fscanf(TIME,"%t",data); $fwrite(PAT_RESULT, "%tn", data ); $fwrite(PAT_RESULT, "********************************************n" ); $fwrite(PAT_RESULT, "********************************************n" ); $fwrite(PAT_RESULT, "PAT NAME : helloworld, PASS FAILEDn"); $fwrite(PAT_RESULT, "********************************************n" ); $fwrite(PAT_RESULT, "********************************************nnn" ); $fclose(TIME); $fclose(PAT_RESULT); $display("n"); $display("**************************************************"); $display("**************************************************"); $display("****** PAT NAME : helloworld, PASS FAILED***********"); $display("**************************************************"); $display("**************************************************"); $finish; end initial begin // delay period //#100ns; wait(top.TestHarness.ldut.mmio_axi4_0_aw_ready & top.TestHarness.ldut.mmio_axi4_0_aw_valid); wait(top.TestHarness.ldut.mmio_axi4_0_aw_bits_addr[30:0] == 31'h7070_7070); wait(top.TestHarness.ldut.mmio_axi4_0_w_ready & top.TestHarness.ldut.mmio_axi4_0_w_valid); wait(top.TestHarness.ldut.mmio_axi4_0_w_bits_data[31:0] == 32'hdead_beef); #10us; $system("date +%y%m%d%H%M%S > timelog"); $fscanf(TIME,"%t",data); $fwrite(PAT_RESULT, "%tn", data ); $fwrite(PAT_RESULT, "*************************************************n" ); $fwrite(PAT_RESULT, "*************************************************n" ); $fwrite(PAT_RESULT, "PAT NAME : helloworld, PASS SUCCESSEDn"); $fwrite(PAT_RESULT, "*************************************************n" ); $fwrite(PAT_RESULT, "*************************************************nnn" ); $fclose(TIME); $fclose(PAT_RESULT); $display("n"); $display("**************************************************"); $display("**************************************************"); $display("****** PAT NAME : helloworld, PASS SUCCESSED********"); $display("**************************************************"); $display("**************************************************"); $finish; end
硬件仿真使用make run命令执行,其在Makefile中的命令可以参考如下设置,注意需要添加一些SIMV_DEF的仿真define定义。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17#Makefile SIMV_DEF = +define+RANDOMIZE_MEM_INIT +define+RANDOMIZE_REG_INIT +define+VIRAGE_FAST_VERILOG +define+MEM_CHECK_OFF +define+RANDOMIZE_DELAY=0 +define+PRINTF_COND=0 run:simv ./simv -l run.log simv:filelist.f vcs -full64 -cpp g++-4.8 -cc gcc-4.8 -LDFLAGS -Wl,--no-as-needed -f filelist.f -sverilog +v2k $(SIMV_DEF) -debug_access+all -debug_access+designer -fsdb-max_var_elem=8388608 -kdb $(COV) -timescale=1ns/1ps -deraceclockdata -l com.log
仿真跑完后,可以在regression_result.dat文件中查看每次跑case的结果,是FAILED还是SUCCESSED,并且可以查看这个Case跑的时间。仿真FAILED的条件是超时1200us,这个时间可以根据实际情况进行修改,如果系统超时时间更长,可以修改对应参数;仿真SUCCESSED的条件在1200us内CPU向0x7070_7070地址写数据0xdeadbeef,该地址是保留地址。该动作由程序发出,也就是说在测试程序的结尾,需要添加一段代码以保证CPU发出0x7070_7070地址的magic code。
3.查看波形
默认生成的波形为fsdb格式,也可以手动修改成vcd等其他波形格式,通过verid将波形加载进来,这里编写了段自动加载波形的verdi脚本,可以根据不同的case加载不同的波形,代码如下:
1
2
3#Makefile verdi: verdi -sv -f filelist.f $(SIMV_DEF) +systemverilogext+.sv+.v -ssv -ssy -ssz -nologo -top top -ssf ./wave_dump_log/${CASE}.fsdb &
这里同样需要注意仿真和查看波形的define一致性,define不一致可能会导致波形对应的代码段不生效,导致trace信号失效,因此保持跑仿真和看仿真结果的参数一致非常重要。跑完的波形都放在了wave_dump_log文件夹中。
四、功能测试和回归测试
在进行功能测试前,需要对测试功能进行程序编写,按照如下的目录结构将.c、.s、.h等程序文件放在program_code文件夹下,具体放置位置如下图所示:

按照图中所示方法将编写好的所有case放在对应路径下,在跑功能测试时,使用如下命令完成一次功能测试和仿真:
1
2
3
4
5
6
7
8#如果case名是helloworld make debug make run make verdi #如果case名不是helloworld,使用[case_name]指定需要跑的case名 make debug [case_name] make run CASE=[case_name] make verdi CASE=[case_name]
在跑回归测试时,可以将所有的case名称放在case_list中,然后通过脚本依次将case_name替换成需要跑的case名。
仿真环境也支持官方的benchmark或google随机测试pattern生成的测试集,但需要注意的是部分测试benchmark无法在平台上跑通,例如配置的硬件不支持浮点运算,而生成的测试集中存在浮点运算指令,那么仿真结果会FAILED。除此以外,还需要注意仿真能用到的硬件只有CPU、MEM和BootROM。如果想要测试更多的硬件,如总线网络NoC、外设、系统中断等,需要自行添加额外的IP核和RTL电路。
有需要该平台的可以留言邮箱或私信我获取,有相关问题也可以留言私信沟通。
最后
以上就是温暖镜子最近收集整理的关于RocketChip RISC-V生成RTL到仿真全流程一、Scala配置项修改和RTL代码生成二、工具链和编译库生成三、VCS硬件仿真环境搭建四、功能测试和回归测试的全部内容,更多相关RocketChip内容请搜索靠谱客的其他文章。
发表评论 取消回复