RocketChip RISC-V生成RTL到仿真全流程

一、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代码和相关仿真测试文件。

RocketChip RISC-V生成RTL到仿真全流程_第1张图片

二、工具链和编译库生成

工具链和编译库可以在网上下载,或者通过$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使用该命令

交叉编译的时间有点长,最终会在指定目录下产生如下图所示的工具链文件。

RocketChip RISC-V生成RTL到仿真全流程_第2张图片

如果是网上下载的,可以直接把文件放到对应目录,然后在bashrc中指定路径即可。

三、VCS硬件仿真环境搭建

建立一个RISCV-SIM文件夹,VCS硬件仿真都在该文件夹中完成,如下图所示,其中case_log文件夹用来放跑vcs的log信息,hardware_code文件夹用来放RTL代码,program_code文件夹用来放需要跑的case,wave_dump_log文件夹用来放跑出来的case波形。

RocketChip RISC-V生成RTL到仿真全流程_第3张图片

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文件夹下,具体放置位置如下图所示:

RocketChip RISC-V生成RTL到仿真全流程_第4张图片

按照图中所示方法将编写好的所有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电路。

有需要该平台的可以留言邮箱或私信我获取,有相关问题也可以留言私信沟通。

你可能感兴趣的:(RISC-V,SOC设计,risc-v,soc,前端,集成测试,scala)