一、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目录下运行:
make 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中指定如下位置:
#RISC-V gnu toolchain
export RISCV=/home/alanwu/Documents/RISCV/rocket-tools
export PATH=/opt/riscv/bin:$PATH
如果指定的是/opt/文件夹下,需要注意权限的设置,完成后使用如下命令进行交叉编译:
./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文件。
#!/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脚本自动转换。
#!/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 "%08x\n"' ${BIN_NAME}.bin > temp.hex
#swap even and add rows
sed '$!N;s/\([^\n]*\)\n\([^\n]*\)/\2\n\1/' 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]*\)/\2\n\1/' 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]*\)/\2\n\1/' 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中。
initial 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的自动化回归测试。
initial 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, "%t\n", data );
$fwrite(PAT_RESULT, "********************************************\n" );
$fwrite(PAT_RESULT, "********************************************\n" );
$fwrite(PAT_RESULT, "PAT NAME : helloworld, PASS FAILED\n");
$fwrite(PAT_RESULT, "********************************************\n" );
$fwrite(PAT_RESULT, "********************************************\n\n\n" );
$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, "%t\n", data );
$fwrite(PAT_RESULT, "*************************************************\n" );
$fwrite(PAT_RESULT, "*************************************************\n" );
$fwrite(PAT_RESULT, "PAT NAME : helloworld, PASS SUCCESSED\n");
$fwrite(PAT_RESULT, "*************************************************\n" );
$fwrite(PAT_RESULT, "*************************************************\n\n\n" );
$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定义。
#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加载不同的波形,代码如下:
#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放在对应路径下,在跑功能测试时,使用如下命令完成一次功能测试和仿真:
#如果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电路。
有需要该平台的可以留言邮箱或私信我获取,有相关问题也可以留言私信沟通。