IBEX启动simple system仿真全流程分析

本文部分内容参考了这篇文章,感谢前人栽树!
https://zhuanlan.zhihu.com/p/108624018

IBEX Simple System介绍

IBEX的Simple System系统除了挂载ram之外,还挂载有simulator_ctrl和timer两个外设,并且有一个tracing模块用于记录ibex core读取总线数据的记录。
在这里插入图片描述
由于verilator只能编译可综合的设计,所以对于RAM我们必须提供一个可综合的model,RAM的可综合model非常简单,用二维数组就可以实现。但是我们必须解决如何把一个初始的RAM镜像load进去,如果不考虑灵活性的话,直接指定一个镜像文件也是可行的。但如果要让testbench指定的话,就必须提供一个接口,可供C++调用(这与Verilator中的实现机制有密切的关系,在testbench的cpp文件中也要关于ram进行配置)。

simulator ctrl从bus上读取写地址和写数据。如果写地址为0x20000,则向file中写数据。如果向0x20008中写1,sim_finish便置为1,经过若干时钟周期后,调用$finish,终止仿真。

always_ff @(posedge clk_i or negedge rst_ni) begin
	    if (~rst_ni) begin
	      rvalid_o <= 0;
	      sim_finish <= 'b0;
	    end else begin
	      // Immeditely respond to any request
	      rvalid_o <= req_i;
	
	      if (req_i & we_i) begin
	        case (ctrl_addr)
	          CHAR_OUT_ADDR: begin
	            if (be_i[0]) begin
	              $fwrite(log_fd, "%c", wdata_i[7:0]);
	
	              if(FlushOnChar) begin
	                $fflush(log_fd);
	              end
	            end
	          end
	          SIM_CTRL_ADDR: begin
	            if ((be_i[0] & wdata_i[0]) && (sim_finish == 'b0)) begin
	              $display("Terminating simulation by software request.");
	              sim_finish <= 3'b001;
	            end
	          end
	          default: ;
	        endcase
	      end
	    end
	
	    if (sim_finish != 'b0) begin
	      sim_finish <= sim_finish + 1;
	    end
	    if (sim_finish >= 3'b010) begin
	      $finish;
	    end
  end

timer根据Machine Timer Registers,为core产生中断。中断的输出信号直接被接往CPU core。timer从bus上接受地址和数据,若读地址为0x30000-0x3000c,则返回对应timer register的值。

Bus上负责cpu core与ram,simulator ctrl,timer之间的交流。在担任总线功能的同时,也必须要完成仲裁器的任务。根据地址,选择要启动的外设。

AddressDescription
0x20000ASCII Out, write ASCII characters here that will get output to the log file
0x20008Simulator Halt, write 1 here to halt the simulation
0x30000RISC-V timer mtime register
0x30004RISC-V timer mtimeh register
0x30008RISC-V timer mtimecmp register
0x3000CRISC-V timer mtimecmph register
0x100000 – 0x1FFFFF1 MB memory for instruction and data. Execution starts at 0x100080, exception handler base is 0x100000

配置环境

略,IBEX文档中有详细介绍 。

FuseSoC建立仿真

有关于FuseSoC的介绍可以查看手册https://fusesoc.readthedocs.io/en/stable/user/cli.html

总的来说,FuseSoC是一个用于HDL代码的 包管理+建立系统 的工具,他的基本单元有各种.core文件构成:A FuseSoC core is a reasonably self-contained, reusable piece of IP, such as a FIFO implementation. core类似于package的概念,具有层次化结构。里面说明了这个core包含的HDL文件,包含的其他的低层次core,以及许多配置信息。这些配置信息例如:选择之后需要使用的EDA工具(Tool Flows),选择需要仿真、综合、静态分析(Targets)。FuseSoC希望能隐藏这些工具使用细节上的差异。

运行如下命令:

fusesoc --cores-root=. run --target=sim --setup --build lowrisc:ibex:ibex_simple_system --RV32E=0 --RV32M=ibex_pkg::RV32MFast

FuseSoC建立了lowrisc:ibex:ibex_simple_system命名的core,它的位置在examples/simple_system。它所包含的core的依赖关系图如下所示。
ibex_top继续包含ibex中的各个组件。

ibex_simple_system
ibex_simple_system_core
ibex_top_tracing
sim_shared
ibex-top
ibex_tracer

命令行中指定target为sim,即是选择了如下的目标tool flow的配置。
在这里插入图片描述命令还对RV32E以及RV32M这两个参数进行显式的配置。还有许多其他的参数,都使用默认default值。
在这里插入图片描述
执行完该命令之后,会出现build文件夹。rtl目录下存放了core包含的全部文件,sim-verilator目录下存放使用verilator编译好的cpp文件以及其对应的.o, .d文件。Verilator把一个仿真,包括DUT和testbench编译成了Linux下的一个可执行文件。

生成待测程序

make -C examples/sw/simple_system/hello_test

通过之前配置完成的RISC-V工具链,将程序编译为elf文件。同时会生成.o .d .bin .vmem等文件。
hello_test.vmem是由srecord工具生成,相当于把代码空间objcopy到一个文件中。

在这里插入图片描述
在hello_test.c中,有一系列以putchar为基础的输出函数。putchar是通过对特定地址0x2000写一个字符来实现。simulator ctrl外设会将这一特定地址的写操作转化为对log file的写。

//simple_system_regs.h
#define SIM_CTRL_BASE 0x20000
#define SIM_CTRL_OUT 0x0
#define SIM_CTRL_CTRL 0x8

//simple_system_common.h
#define DEV_WRITE(addr, val) (*((volatile uint32_t *)(addr)) = val)

simple_system_common.c
int putchar(int c) {
  DEV_WRITE(SIM_CTRL_BASE + SIM_CTRL_OUT, (unsigned char)c);
  return c;
}

之后hello_test.c中pcount_enable,timer_enable等操作,涉及riscV特权级以及中断等指令的实现,这里暂时不作讨论。

CPU上电后初始PC地址0x80,在simple_system/sw/simple_system/common/crt0.s中定义了如下的vectors段。从0x00到0x7c是向量表,0x80放置了CPU执行的第一条指令,跳转到reset_handler中。

  .section .vectors, "ax"
  .option norvc;

  // All unimplemented interrupts/exceptions go to the default_exc_handler.
  .org 0x00
  .rept 7
  jal x0, default_exc_handler
  .endr
  jal x0, timer_handler
  .rept 23
  jal x0, default_exc_handler
  .endr

  // reset vector
  .org 0x80
  jal x0, reset_handler

程序跳转到reset_handler执行,重置所有寄存器为0。指令运行到main_entry,跳转到main(也就是hello_test.cpp中的main),执行用户程序。

main_entry:
  /* jump to main program entry point (argc = argv = 0) */
  addi x10, x0, 0
  addi x11, x0, 0
  jal x1, main

运行仿真器

./build/lowrisc_ibex_ibex_simple_system_0/sim-verilator/Vibex_simple_system [-t] --meminit=ram,<sw_elf_file>

这个可执行文件的入口地址在examples/simple_system/ibex_simple_system_main.cc

#include "ibex_simple_system.h"

int main(int argc, char **argv) {
  SimpleSystem simple_system(
      "TOP.ibex_simple_system.u_ram.u_ram.gen_generic.u_impl_generic",
      1024 * 1024);

  return simple_system.Main(argc, argv);
}

有关SimpleSystem的声明如下。其中ibex_simple_system这个类就是由Verilator编译好的DUT model。

class SimpleSystem {
 public:
  SimpleSystem(const char *ram_hier_path, int ram_size_words);
  virtual ~SimpleSystem() {}
  virtual int Main(int argc, char **argv);

  // Return an ISA string, as understood by Spike, for the system being
  // simulated.
  std::string GetIsaString() const;

 protected:
  ibex_simple_system _top;
  VerilatorMemUtil _memutil;
  MemArea _ram;

  virtual int Setup(int argc, char **argv, bool &exit_app);
  virtual void Run();
  virtual bool Finish();
};

调用的SimpleSystem的Main函数如下所示。分别是对于仿真系统进行SetUp和Run。

SimpleSystem::SimpleSystem(const char *ram_hier_path, int ram_size_words)
    : _ram(ram_hier_path, ram_size_words, 4) {}

int SimpleSystem::Main(int argc, char **argv) {
  bool exit_app;
  int ret_code = Setup(argc, argv, exit_app);

  if (exit_app) {
    return ret_code;
  }

  Run();

  if (!Finish()) {
    return 1;
  }

  return 0;
}

在SetUp中,首先会将待测DUT的model同testbench相连。
之后指定MemArea,其基本原理是把某个memory的对象的hierarchy(这里是TOP.ibex_simple_system.u_ram.u_ram.gen_generic.u_impl_generic)绑定到一个名称上,如"ram",之后便可以调用RAM model里的函数把数据写入到对应"ram"的软件模型中。
在SetUp函数中,最终会调用Verilator的Verilated::commandArgs(argc, argv), 根据命令中提供的参数,对仿真进行配置。这里我们传入了–meminit=ram,<sw_elf_file>,verilator会将elf文件load进"ram"中,作为初始内存的状态。

int SimpleSystem::Setup(int argc, char **argv, bool &exit_app) {
  VerilatorSimCtrl &simctrl = VerilatorSimCtrl::GetInstance();

  simctrl.SetTop(&_top, &_top.IO_CLK, &_top.IO_RST_N,
                 VerilatorSimCtrlFlags::ResetPolarityNegative);

  _memutil.RegisterMemoryArea("ram", 0x0, &_ram);
  simctrl.RegisterExtension(&_memutil);

  exit_app = false;
  return simctrl.ParseCommandArgs(argc, argv, exit_app);
}

在Run()函数里面,最终会完成对DUT的激励。在while循环中,反转clk,对模型eval,Trace。

//vendor\lowrisc_ip\dv\verilator\simutil_verilator\cpp\verilator_sim_ctrl.cc
 while (1) {
	...
    *sig_clk_ = !*sig_clk_;
    ...
    top_->eval();
    time_++;
    Trace();
	...
  }
}

最终结果

trace_core_000000.log中记录了CPU执行的全部指令。可以与crt0.s以及hello_test.cpp等程序对照进行查看。
在这里插入图片描述
Verilator显示出仿真运行的整体状况。
在这里插入图片描述

结语

经过几天的配环境和学习,终于大致上摸清了仿真simple system的过程,也恶补了许多之前忽略的计组的知识。simple system虽然小巧,但是涉及了CPU仿真的大部分环节,也加深了我对于RISC-V核的认识。
后续我会进一步了解Verilator更多的内容,学习IBEX提供的另一套co-simulation的UVM testbench。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值