Code Monkey home page Code Monkey logo

ysyx's People

Contributors

5265325 avatar myyerrol avatar puckbee avatar sashimi-yzh avatar zhangyx1998 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

ysyx's Issues

【提问】RT-Thread编译后出现supervisor特权级的CSR

按照教程编译OSCPU/rt-thread之后,把elf文件dump了一下,发现有如下指令。

    80008f74:	9e850513          	addi	a0,a0,-1560 # 80016958 <rt_data_queue_deinit+0x12e8>
    80008f78:	e3dfd0ef          	jal	ra,80006db4 <rt_kprintf>
    80008f7c:	180027f3          	csrr	a5,satp
    80008f80:	fef43023          	sd	a5,-32(s0)
    80008f84:	fe043783          	ld	a5,-32(s0)

查阅指令集文档得知satp是supervisor特权级的CSR,是否意味着需要对rt-thread代码做进一步修改?

欢迎使用讨论区

“一生一芯” 讨论区注意事项

讨论区目前还在试验阶段,后续会对提问 --> 解答的流程进行优化。

提问流程

  • 每个问题请开一个新的issue,issue 的标题和内容可以使用中文。

  • 提交 issue 的界面右侧可以为 issue 添加标签(Label),请从预先定义好的标签中选择匹配的项目对您的 issue 进行标记。例如,本篇 issue 的类型为 知识分享,话题是 Policy&Schedule

  • 您还可以为您的问题指定(Assign)助教以获得快速解答,我们稍后会列出助教团队成员的 GitHub ID 以及擅长方向以供参考。

  • 无论是交流类 issue 还是求助类 issue,所有人均可参与讨论和解答。我们的助教团队也会尽可能对您的 issue 作出回应。

提问规范

  • 讨论区的目的是最大化知识分享的效率,减少重复问题。因此,在提问前请先搜索已有的 issue ,确定该问题没有被提出过。

  • 对于 需要帮助 类型的问题,请尽可能包含详细的代码信息、运行环境和报错信息,以便试图提供帮助的同学复现你的问题。

请勿在本仓库提交与 “一生一芯” 项目无关的 issue !

[分享]Verilog代码接入到Difftest

前言

昨天看到有老哥写了Chisel的difftest接入,心血来潮想试试Verilog怎么接入。
一定要参考Chisel的difftest接入DiffTest Usge这两篇,我算是一点点补充
水平有限,请各位多多指教。

TOP文件

首先我们要在difftest的上一层建立一个build文件夹,并新建一个 SimTop.v 并将下列内容写入,这个文件就是整个RTL仿真设计的顶层。
还要将CPU的所有RTL文件放入。(也可以考虑用 make install 自动copy)

module SimTop(
    input         clock,
    input         reset,

    input  [63:0] io_logCtrl_log_begin,
    input  [63:0] io_logCtrl_log_end,
    input  [63:0] io_logCtrl_log_level,
    input         io_perfInfo_clean,
    input         io_perfInfo_dump,

    output        io_uart_out_valid,
    output [7:0]  io_uart_out_ch,
    output        io_uart_in_valid,
    input  [7:0]  io_uart_in_ch
  // ......
);

endmodule

RAM的接入

对于DiffTest框架,提供了一块申请的8GB内存在路径,在difftest/src/test/csrc/common/ram.h中定义了仿真内存的大小,也提供了对应的读写函数,Verilog通过DPI-C的方式访问读写函数完成对仿真内存的读写,代码在difftest/src/test/vsrc/common/ram.v
下面提供的RAMHelper好像只有一读一写,而在没有Cache的情况下,没有办法让取指和访存同时访问这块内存(我在想可不可以上升取指下降访存来过渡),于是我重写了一个有一写两读的RAM模块。
其中指令端口通过一个MUX将64位数据对齐到了32位。inst_addr右移3位是因为CPU内部是以字节编址,而仿真内存是以8字节编址,MUX的选择信号是右移出去三位数据的最高位,来选择64位中的高32位/低32位。
数据的读写也是按照4字节对齐,为了适应仿真内存也要做相应的映射,详情见下述代码

module RAM_1W2R(
    input clk,
    
    input [`BUS_WIDTH]inst_addr,
    input inst_ena,
    output [31:0]inst,

    // DATA PORT
    input ram_wr_en,
    input ram_rd_en,
    input [`BUS_WIDTH]ram_wmask,
    input [`BUS_WIDTH]ram_addr,
    input [`BUS_WIDTH]ram_wr_data,
    output reg [`BUS_WIDTH]ram_rd_data
);

    // INST PORT

    wire[`BUS_WIDTH] inst_2 = ram_read_helper(inst_ena,{3'b000,(inst_addr-64'h0000_0000_8000_0000)>>3});

    assign inst = inst_addr[2] ? inst_2[63:32] : inst_2[31:0];

    // DATA PORT 
    assign ram_rd_data = ram_read_helper(ram_rd_en, {3'b000,(ram_addr-64'h0000_0000_8000_0000)>>3});

    always @(posedge clk) begin
        ram_write_helper((ram_addr-64'h0000_0000_8000_0000)>>3, ram_wr_data, ram_wmask, ram_wr_en);
    end

endmodule

将此模块实例化到 SimTop.v 中并且将端口接入到自己设计的CPU中,这样就完成了,RAM的接入

指令的提交

Difftest通过,DifftestInstrCommit这个模块来将信号提交到DiffTest中。

module DifftestInstrCommit(InstrCommit)(
  input        clock,  // 时钟
  input [ 7:0] coreid, // 处理器核ID,多处理器有用,默认为0即可
  input [ 7:0] index,  
  input        valid,  // 提交有效信号,只有为高电平时提交才有效
  input [63:0] pc,     // PC指针的值
  input [31:0] instr,  // 对应的指令
  input        skip,   // 是否跳过此条对比
  input        isRVC,  // 是否为压缩指令
  input        scFailed,
  input        wen,    // 写回寄存器使能
  input [ 7:0] wdest,  // 写回寄存器的地址
  input [63:0] wdata   // 写回的数据
);

将需要提交的信号链接,记住手册里的一句话,在指令提交的时刻其产生的影响恰好生效

  reg r_wen;
  reg [7:0]r_wdest;
  reg [`BUS_WIDTH]r_wdata,r_pc;
  reg [31:0]r_inst;
  reg vaild;

  always @(posedge clock) begin
    if(reset)begin
      r_wen     <= 'd0;
      r_wdest   <= 'd0;
      r_wdata   <= 'd0;
      r_pc      <= 'd0;
      r_inst    <= 'd0;
      vaild     <= 'd0;
    end else begin
      r_wen     <= u_riscvcpu.regs_wr_en   ; // 这里面的u_riscvcpu 是cpu核的例化名
      r_wdest   <= {3'd0,u_riscvcpu.regs_wr_addr};
      r_wdata   <= u_riscvcpu.regs_wr_data;
      r_pc      <= {inst_addr[63:32],1'b1,inst_addr[30:0]};
      r_inst    <= inst;
      vaild     <= 1'b1;
    end
  end

DifftestInstrCommit U_inst_commit(
  .clock    ( clock ),
  .coreid   ( 8'd0 ),//8bit
  .index    ( 8'd0 ),//8bit
  .valid    ( vaild ),
  .pc       ( r_pc ),//64bit
  .instr    ( r_inst ),//32bit
  .skip     ( 1'b0 ),
  .isRVC    ( 1'b0 ),
  .scFailed ( 1'b0 ),
  .wen      ( r_wen    ),
  .wdest    ( r_wdest ),//8bit
  .wdata    ( r_wdata ) //64bit
);

REGFILE的提交

对应模块名为DifftestArchIntRegState,类似的方法接入

module DifftestArchIntRegState (
  input         clock,
  input [ 7:0]  coreid,
  input [63:0]  gpr_0,
  input [63:0]  gpr_1,
  input [63:0]  gpr_2,
  input [63:0]  gpr_3,
  input [63:0]  gpr_4,
  input [63:0]  gpr_5,
  input [63:0]  gpr_6,
  input [63:0]  gpr_7,
  input [63:0]  gpr_8,
  input [63:0]  gpr_9,
  input [63:0]  gpr_10,
  input [63:0]  gpr_11,
  input [63:0]  gpr_12,
  input [63:0]  gpr_13,
  input [63:0]  gpr_14,
  input [63:0]  gpr_15,
  input [63:0]  gpr_16,
  input [63:0]  gpr_17,
  input [63:0]  gpr_18,
  input [63:0]  gpr_19,
  input [63:0]  gpr_20,
  input [63:0]  gpr_21,
  input [63:0]  gpr_22,
  input [63:0]  gpr_23,
  input [63:0]  gpr_24,
  input [63:0]  gpr_25,
  input [63:0]  gpr_26,
  input [63:0]  gpr_27,
  input [63:0]  gpr_28,
  input [63:0]  gpr_29,
  input [63:0]  gpr_30,
  input [63:0]  gpr_31
);

编译运行

然后就可以编译emu

make -C difftest emu

运行difftest

./build/emu -i ../am-kernels/tests/cpu-tests/build/add-riscv64-mycpu.bin

根据日志可以看到已经捕获到错误的信息

Emu compiled at Jul 23 2021, 22:43:12
The image is /opt/RISCV/am-kernels/tests/cpu-tests/build/add-riscv64-mycpu.bin
Using simulated 8192MB RAM
[warning] sdcard img not found
--diff is not given, try to use $(NEMU_HOME)/build/riscv64-nemu-interpreter-so by default
Using /opt/RISCV/NEMU/build/riscv64-nemu-interpreter-so for difftest
[src/device/io/mmio.c,13,add_mmio_map] Add mmio map 'clint' at [0xa2000000, 0xa200ffff]
[src/device/io/mmio.c,13,add_mmio_map] Add mmio map 'sdhci' at [0xa3000000, 0xa300007f]
[src/device/sdcard.c,118,init_sdcard] Can not find sdcard image: /home/yzh/projectn/debian.img
pc:0000000080000000 instr:00000413 wen:1 wdest:  8 wdata:0000000000000000
The first instruction of core 0 has commited. Difftest enabled.
pc:0000000080000004 instr:00009117 wen:1 wdest:  2 wdata:0000000000009004

============== Commit Group Trace (Core 0) ==============
commit group [0]: pc 0080000000 cmtcnt 1
commit group [1]: pc 0080000004 cmtcnt 1 <--
commit group [2]: pc 0000000000 cmtcnt 0
commit group [3]: pc 0000000000 cmtcnt 0
commit group [4]: pc 0000000000 cmtcnt 0
commit group [5]: pc 0000000000 cmtcnt 0
commit group [6]: pc 0000000000 cmtcnt 0
commit group [7]: pc 0000000000 cmtcnt 0
commit group [8]: pc 0000000000 cmtcnt 0
commit group [9]: pc 0000000000 cmtcnt 0
commit group [a]: pc 0000000000 cmtcnt 0
commit group [b]: pc 0000000000 cmtcnt 0
commit group [c]: pc 0000000000 cmtcnt 0
commit group [d]: pc 0000000000 cmtcnt 0
commit group [e]: pc 0000000000 cmtcnt 0
commit group [f]: pc 0000000000 cmtcnt 0

============== Commit Instr Trace ==============
commit inst [0]: pc 0080000000 inst 00000413 wen 1 dst 00000008 data 0000000000000000
commit inst [1]: pc 0080000004 inst 00009117 wen 1 dst 00000002 data 0000000000009004 <--
commit inst [2]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000
commit inst [3]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000
commit inst [4]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000
commit inst [5]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000
commit inst [6]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000
commit inst [7]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000
commit inst [8]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000
commit inst [9]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000
commit inst [a]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000
commit inst [b]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000
commit inst [c]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000
commit inst [d]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000
commit inst [e]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000
commit inst [f]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000

==============  REF Regs  ==============
  $0: 0x0000000000000000   ra: 0x0000000000000000   sp: 0x0000000080009004   gp: 0x0000000000000000
  tp: 0x0000000000000000   t0: 0x0000000000000000   t1: 0x0000000000000000   t2: 0x0000000000000000
  s0: 0x0000000000000000   s1: 0x0000000000000000   a0: 0x0000000000000000   a1: 0x0000000000000000
  a2: 0x0000000000000000   a3: 0x0000000000000000   a4: 0x0000000000000000   a5: 0x0000000000000000
  a6: 0x0000000000000000   a7: 0x0000000000000000   s2: 0x0000000000000000   s3: 0x0000000000000000
  s4: 0x0000000000000000   s5: 0x0000000000000000   s6: 0x0000000000000000   s7: 0x0000000000000000
  s8: 0x0000000000000000   s9: 0x0000000000000000  s10: 0x0000000000000000  s11: 0x0000000000000000
  t3: 0x0000000000000000   t4: 0x0000000000000000   t5: 0x0000000000000000   t6: 0x0000000000000000
 ft0: 0x0000000000000000  ft1: 0x0000000000000000  ft2: 0x0000000000000000  ft3: 0x0000000000000000
 ft4: 0x0000000000000000  ft5: 0x0000000000000000  ft6: 0x0000000000000000  ft7: 0x0000000000000000
 fs0: 0x0000000000000000  fs1: 0x0000000000000000  fa0: 0x0000000000000000  fa1: 0x0000000000000000
 fa2: 0x0000000000000000  fa3: 0x0000000000000000  fa4: 0x0000000000000000  fa5: 0x0000000000000000
 fa6: 0x0000000000000000  fa7: 0x0000000000000000  fs2: 0x0000000000000000  fs3: 0x0000000000000000
 fs4: 0x0000000000000000  fs5: 0x0000000000000000  fs6: 0x0000000000000000  fs7: 0x0000000000000000
 fs8: 0x0000000000000000  fs9: 0x0000000000000000 fs10: 0x0000000000000000 fs11: 0x0000000000000000
 ft8: 0x0000000000000000  ft9: 0x0000000000000000 ft10: 0x0000000000000000 ft11: 0x0000000000000000
pc: 0x0000000080000008 mstatus: 0x0000000000000000 mcause: 0x0000000000000000 mepc: 0x0000000000000000
                       sstatus: 0x0000000000000000 scause: 0x0000000000000000 sepc: 0x0000000000000000
satp: 0x0000000000000000
mip: 0x0000000000000000 mie: 0x0000000000000000 mscratch: 0x0000000000000000 sscratch: 0x0000000000000000
mideleg: 0x0000000000000000 medeleg: 0x0000000000000000
mtval: 0x0000000000000000 stval: 0x0000000000000000mtvec: 0x0000000000000000 stvec: 0x0000000000000000
priviledgeMode: 0
     sp different at pc = 0x0080000004, right= 0x0000000080009004, wrong = 0x0000000000009004
Core 0: ABORT at pc = 0x2913c2304813023
total guest instructions = 0
instrCnt = 0, cycleCnt = 0, IPC = -nan
Seed=0 Guest cycle spent: 3 (this will be different from cycleCnt if emu loads a snapshot)
Host time spent: 10ms

Tips:
将Difftest的Makefile中clean:vcs-clean下面的一行改成

rm -rf $(filter-out $(wildcard $(BUILD_DIR)/*.v), $(wildcard $(BUILD_DIR)/*))

可以在执行make clean时不删除RTL代码

Chisel生成的verilog没有数组的解决方法

问题描述

由于chisel导出的verilog没有数组,所以chisel导出的verilog代码可能无法直接用于difftest仿真。
例如,直接用verilog描述的信号为:

Output [7:0] test [3:0];

使用chisel的描述语句为

val test = Vec(4, UInt(8.W))

将该代码生成verilog,声明的端口自动会转换为

...
output [7:0] test_0,
output [7:0] test_1,
output [7:0] test_2,
output [7:0] test_3,
...

解决方法

最简单的解决方法是直接在生成的Verilog中对不符合接口规范的语句进行替换。
这是我用Python创建的修改脚本replace.py
其中的几个变量str_tobe_replace_1 str_tobe_replace_2 str_tobe_replace_3需要根据你的实际代码进行一定的修改。

# 源文件路径(默认导出的文件覆盖原文件)
file_path   =   "./generated/oscpu2/SimTop.v"


str_tobe_replace_1  =   '''  output [63:0] io_memAXI_0_w_bits_data_0,
  output [63:0] io_memAXI_0_w_bits_data_1,
  output [63:0] io_memAXI_0_w_bits_data_2,
  output [63:0] io_memAXI_0_w_bits_data_3,'''

str_new_1   =   '''  output [63:0] io_memAXI_0_w_bits_data [3:0], // !!! generated by python'''

str_tobe_replace_2  =   '''  input  [63:0] io_memAXI_0_r_bits_data_0,
  input  [63:0] io_memAXI_0_r_bits_data_1,
  input  [63:0] io_memAXI_0_r_bits_data_2,
  input  [63:0] io_memAXI_0_r_bits_data_3,'''
str_new_2   =   '''  input [63:0] io_memAXI_0_r_bits_data [3:0], // !!! generated by python'''

str_tobe_replace_3  =   '''  wire [63:0] axi_mem_io_mem_data_read; '''

str_new_3   =   '''  wire [63:0] axi_mem_io_mem_data_read; 

  wire [63:0] io_memAXI_0_w_bits_data_0;
  wire [63:0] io_memAXI_0_w_bits_data_1;
  wire [63:0] io_memAXI_0_w_bits_data_2;
  wire [63:0] io_memAXI_0_w_bits_data_3;
  assign io_memAXI_0_w_bits_data[0] = io_memAXI_0_w_bits_data_0 ;
  assign io_memAXI_0_w_bits_data[1] = io_memAXI_0_w_bits_data_1 ;
  assign io_memAXI_0_w_bits_data[2] = io_memAXI_0_w_bits_data_2 ;
  assign io_memAXI_0_w_bits_data[3] = io_memAXI_0_w_bits_data_3 ;

  wire  [63:0] io_memAXI_0_r_bits_data_0;
  wire  [63:0] io_memAXI_0_r_bits_data_1;
  wire  [63:0] io_memAXI_0_r_bits_data_2;
  wire  [63:0] io_memAXI_0_r_bits_data_3;
  assign io_memAXI_0_r_bits_data_0  = io_memAXI_0_r_bits_data[0];
  assign io_memAXI_0_r_bits_data_1  = io_memAXI_0_r_bits_data[1];
  assign io_memAXI_0_r_bits_data_2  = io_memAXI_0_r_bits_data[2];
  assign io_memAXI_0_r_bits_data_3  = io_memAXI_0_r_bits_data[3];
  '''


file_content = ""
with open(file_path, "r") as f:
    file_content = f.read()

file_content = file_content.replace(str_tobe_replace_1, str_new_1)
file_content = file_content.replace(str_tobe_replace_2, str_new_2)
file_content = file_content.replace(str_tobe_replace_3, str_new_3)

with open(file_path, "w+") as f:
    f.write(file_content)

同时为了方便将处理好的verilog源文件复制到进行difftest的仿真路径,可以使用下面的shell脚本,一键编译verilog和修改源码(需要根据实际目录修改一些路径)

sbt "test:runMain oscpu2.verilog_TopMain_oscpu2"
python replace.py
sed -i '1i\`timescale 1ns / 10ps' ./generated/oscpu2/SimTop.v
cp ./generated/oscpu2/SimTop.v ../projects/czcpu_axi_diff/vsrc/SimTop.v

【求助】cpu_test中与字符处理相关的程序运行不通

目前可以通过其它cpu_test程序,但是含有字符操作的程序通不过
image
image
通过看波形可以观察是运行sprintf函数的时候直接就运行halt了。
联系之前AM教程中讲到的完善putch函数,这个问题是否是未修改putch导致的?请问大家有遇到这个问题吗?是怎么解决的?多谢!!!

在difftest中查看仿真波形

在Difftest中查看仿真波形

描述:在调试过程中,遇到信号值不正确,单从emu程序中看不出问题的时候,可以考虑启用波形仿真。

1.修改源文件

1.1 make 相关

  • 文件:verilator.mk:定位到以下代码段(关键字trace
  • 文件路径:"你的项目名字"/difftest/verilator.mk
# Verilator trace support

EMU_TRACE ?= 1		#==============>>这一行<<==============#

ifeq ($(EMU_TRACE),1)

VEXTRA_FLAGS += --trace

endif
  • EMU_TRACE ?= 修改为 EMU_TRACE ?= 1 ,并保存
    • 修改后将启用 verilator 的波形生成功能
    • 有关trace选项的功能描述详见 verilator 官方文档 Verilator (veripool.org)
    • 章节 "8.1.8 How do I generate waveforms (traces) in C++?"

1.2 difftest 源码

  • 文件:emu.h
  • 文件路径:"你的项目名字"/difftest/src/test/csrc/verilator/emu.h
#define VM_TRACE 1
  • 在 "文件" 中加入如上的宏定义,并保存

2.编译和运行

2.1 编译新的emu程序

  • 进入 "你的项目名字"/difftest 目录执行以下命令,生成新的emu程序
make emu

2.2 运行emu程序

  • 进入"你的项目名字"/build目录,执行以下命令(将emu*.bin文件拷贝到其他地方执行也可以,只要你知道这两个文件的路径)
./emu -i add-riscv64-mycpu.bin
  • 程序会统计仿真时钟周期个数Guest cycle spent: 22("22"是例子具体要看你自己的程序)
    • 注意:仿真周期个数统计需要在SimTop.v中接入 “DifftestTrapEvent ”模块
    • 可参考:oscpu-framework,接入DifftestTrapEvent

  • 执行以下命令,获取帮助提示
./emu -h

  • 注意红框部分的描述,执行以下命令
    • -b:开始记录的第 n 个周期
    • -e:结束记录的第 N 个周期
    • 由上面得到的Guest cycle填入0和22,表示全程记录
./emu -b 0 -e 22 --dump-wave -i add-riscv64-mycpu.bin
  • 预期得到以下输出

  • "你的项目名字"/build目录下会生成以时间命名的*.vcd文件

3.使用gtkwave观察仿真波形

  • 打开*.vcd文件,添加一些信号,即可查看仿真波形

4.思路过程

  • 这里是我启用difftest框架中功能的思考

【提问(已解决)】关于执行mret指令时difftest报错的问题

mstatus different at pc = 0x00800005f4, right= 0x0000000000000088, wrong = 0x0000000000001888

执行mret指令时,在我们的实现里始终保持在machine mode,但是difftest给出的默认实现是返回到user mode(见mstatus中的mpp field),需要在difftest中修改源码吗?具体是在哪里修改?

(QQ群中有人提出类似问题但是聊天记录中没有具体回答)

Difftest 和 NEMU 的版本对应关系

在 difftest 讲座中提供的 difftest 和配套的 NEMU 来自于香山处理器配套的开发环境. 近期, 香山主线所使用的 difftest 和 NEMU 经历了一次更新, NEMU 提供的 difftest API 发生了变化. 需要同时更新 NEMU 和 difftest 的版本才能让 difftest 正确运行.

请注意: NEMU 和 difftest 的版本需要匹配, 否则 difftest 无法正确运行. 我们提供以下两个可用的版本:

稳定版本

这些分支上的代码会比较稳定. 这也是 difftest 讲座 时给大家提供的版本.

https://github.com/OpenXiangShan/difftest/tree/ysyx
https://github.com/OpenXiangShan/NEMU/tree/ysyx

香山 master 分支版本

想尝鲜的同学可以更新到 master 分支的 difftest / NEMU. 这一分支上的代码是目前香山处理器开发所使用的版本.

https://github.com/OpenXiangShan/NEMU
https://github.com/OpenXiangShan/difftest

这一版本的 NEMU 编译命令相比之前的版本发生了变化: 参见 新NEMU临时使用指南

这一仓库下的 readme 介绍了如何使用香山的开发环境:
https://github.com/OpenXiangShan/xs-env

按照课件仿真rvcpu.v失败: buffer overflow detected

git pull拉取最新代码后,按照《Verilator+gtkwave快速上手教程.pdf》课件给出的3.2.1内容运行仿真命令,输出仿真失败。

VMware虚拟机,8核4GB,能跑通counter example
Ubuntu版本:20.04.1
Verilator版本:4.204
Gtkwave版本:3.3.103

image

【分享】接入difftest的几个主要步骤

写在前面

我个人使用 chisel 编写项目,并且使用 mill 作为构建工具。

使用 verilog 编写或者使用 sbt 构建工具大同小异。

如果在操作过程有出现本文章未提及的问题,欢迎在讨论区提出。你提出的问题很可能能帮助到别人。

下载 difftest 项目和所需依赖

主要为 difftest 项目NEMU 模拟器

其中,difftest 项目必须在 chisel 项目根目录下,NEMU 项目不限制位置,但是必须设置 NEMU_HOME 环境变量为 NEMU 所在目录。

此外,还需要设置 NOOP_HOME 为项目根目录(参考 verilog.mk,虽然不会使用这个功能,但是这个变量必须要有)。

我个人的项目树大致如下,不要求完全一致,符合上述要求即可。

/home/xxx/
  chisel-template/
    difftest/
    src/
    build.sc
  NEMU/

因此 NEMU_HOME 环境变量应该设置为 /home/xxx/NEMUNOOP_HOME 环境变量应该设置为 /home/xxx/chisel-template

(正式开始前建议执行 git commit -am --allow-empty "before difftest" 保证接入失败后可以随时回退回原来的状态)

构建 SimTop.v

由于 difftest 框架本身由 verilog 写成,仅使用 chisel 编写的项目需要此步骤。使用 verilog 编写的项目可以直接跳过此步骤,但是必须保证 build/SimTop.v 是 CPU 的顶级文件且具有 规定的端口

我的 SimTop.v 实现如下:

package sim

import chisel3._
import rvcore._
import difftest._

class SimTop extends Module {
  // 规定的端口格式,不多不少,但可以暂时不使用。
  val io = IO(new Bundle {
    val logCtrl = new LogCtrlIO
    val perfInfo = new PerfInfoIO
    val uart = new UARTIO
  })

  val rvcore = Module(new RvCore)

  // rvcore 访问内存的端口,下个步骤会说明,此处暂不连接
  rvcore.io.ram.rdata := 0.U

  // 暂不使用 difftest 给出的端口,但是还是需要初始化合法值
  io.uart.in.valid := false.B
  io.uart.out.valid := false.B
  io.uart.out.ch := 0.U
}

由于需要使用 difftest 模块下的文件,需要指示 mill difftest 模块的位置。修改 build.sc 如下(sbt 需修改 build.sbt,两个文件不通用)

// import Mill dependency
import mill._
import mill.scalalib._
import mill.scalalib.scalafmt.ScalafmtModule
import mill.scalalib.TestModule.ScalaTest
// support BSP
import mill.bsp._

object ysyx extends SbtModule { m =>
  override def millSourcePath = os.pwd
  override def scalaVersion = "2.12.13"
  override def scalacOptions = Seq(
    "-Xsource:2.11",
    "-language:reflectiveCalls",
    "-deprecation",
    "-feature",
    "-Xcheckinit",
    // Enables autoclonetype2 in 3.4.x (on by default in 3.5)
    "-P:chiselplugin:useBundlePlugin"
  )
  override def ivyDeps = Agg(
    ivy"edu.berkeley.cs::chisel3:3.4.3"
  )
  override def scalacPluginIvyDeps = Agg(
    ivy"edu.berkeley.cs:::chisel3-plugin:3.4.3",
    ivy"org.scalamacros:::paradise:2.1.1"
  )
  object test extends Tests with ScalaTest {
    override def ivyDeps = m.ivyDeps() ++ Agg(
      ivy"edu.berkeley.cs::chiseltest:0.3.3"
    )
  }
  override def moduleDeps = super.moduleDeps ++ Seq(difftest)
}

object difftest extends ScalaModule {
  override def scalaVersion = "2.12.13"
  override def millSourcePath = os.pwd / "difftest"
  override def ivyDeps = Agg(
    ivy"edu.berkeley.cs::chisel3:3.4.3"
  )
}

此时,mill 可以执行以下命令生成 build/SimTop.v

mill -i __.test.runMain top.TopMain -td ./build

其中,top.TopMain 是本 chisel 项目的顶层文件,形如

package top

import chisel3.stage._
import sim._

object TopMain extends App {
  (new ChiselStage).execute(
    args,
    Seq(
      ChiselGeneratorAnnotation(() => new SimTop())
    )
  )
}

SimTop 是编写的 CPU 模块。

运行上述命令后,期望能找到 build/SimTop.v

构建 difftest 程序 build/emu

在项目根目录中执行以下命令即可构建 build/emu

make -C difftest emu

在准备好上述文件后,理论上就算不做任何修改,在没有接入 difftest 的情况下期望能生成 build/emu 文件(可能会有大量输出,成功与否以 build/emu 文件是否存在为准)。如果无法生成,请检查是否做好上述准备,如是否已配置正确的 NEMU_HOME 环境变量、build/SimTop.v 是否是 CPU 模块的顶层文件等。

此时,运行 build/emu --help 应该能够看到帮助文档。

$ build/emu --help
Emu compiled at Jul 21 2021, 22:18:14
Usage: build/emu [OPTION...]

  -s, --seed=NUM             use this seed
  -C, --max-cycles=NUM       execute at most NUM cycles
  -I, --max-instr=NUM        execute at most NUM instructions
  -W, --warmup-instr=NUM     the number of warmup instructions
  -D, --stat-cycles=NUM      the interval cycles of dumping statistics
  -i, --image=FILE           run with this image file
  -b, --log-begin=NUM        display log from NUM th cycle
  -e, --log-end=NUM          stop display log at NUM th cycle
      --force-dump-result    force dump performance counter result in the end
      --load-snapshot=PATH   load snapshot from PATH
      --no-snapshot          disable saving snapshots
      --dump-wave            dump waveform when log is enabled
      --no-diff              disable differential testing
      --diff=PATH            set the path of REF for differential testing
  -h, --help                 print program help info

准备镜像文件

任意镜像文件均可,可以使用 AbstractMachine 编译 cpu-test 作为镜像(需要实现几乎全部 rv64i 指令才能正常运行),也可以自己编写并生成 stripped 的纯二进制文件。该文件会作为 difftest 过程中执行的程序。

也可以使用框架里的 inst.bin 替代。

以下使用 $IMG 代指该镜像文件。

此时执行 build/emu -i $IMG,期望看到以下输出。

$ build/emu -i ../oscpu-framework/cpu/inst.bin 
Emu compiled at Jul 21 2021, 22:18:14
The image is ../oscpu-framework/cpu/inst.bin
Using simulated 8192MB RAM
[warning] sdcard img not found
--diff is not given, try to use $(NEMU_HOME)/build/riscv64-nemu-interpreter-so by default
Using /home/lh/NEMU/build/riscv64-nemu-interpreter-so for difftest
[src/device/io/mmio.c,13,add_mmio_map] Add mmio map 'clint' at [0xa2000000, 0xa200ffff]
[src/device/io/mmio.c,13,add_mmio_map] Add mmio map 'sdhci' at [0xa3000000, 0xa300007f]
[src/device/sdcard.c,118,init_sdcard] Can not find sdcard image: /home/yzh/projectn/debian.img
No instruction commits for 5000 cycles of core 0. Please check the first instruction.
Note: The first instruction may lie in 0x10000000 which may executes and commits after 500 cycles.
   Or the first instruction may lie in 0x80000000 which may executes and commits after 2000 cycles.

============== Commit Group Trace (Core 0) ==============
commit group [0]: pc 0000000000 cmtcnt 0 
commit group [1]: pc 0000000000 cmtcnt 0 
commit group [2]: pc 0000000000 cmtcnt 0 
commit group [3]: pc 0000000000 cmtcnt 0 
commit group [4]: pc 0000000000 cmtcnt 0 
commit group [5]: pc 0000000000 cmtcnt 0 
commit group [6]: pc 0000000000 cmtcnt 0 
commit group [7]: pc 0000000000 cmtcnt 0 
commit group [8]: pc 0000000000 cmtcnt 0 
commit group [9]: pc 0000000000 cmtcnt 0 
commit group [a]: pc 0000000000 cmtcnt 0 
commit group [b]: pc 0000000000 cmtcnt 0 
commit group [c]: pc 0000000000 cmtcnt 0 
commit group [d]: pc 0000000000 cmtcnt 0 
commit group [e]: pc 0000000000 cmtcnt 0 
commit group [f]: pc 0000000000 cmtcnt 0 <--

============== Commit Instr Trace ==============
commit inst [0]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [1]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [2]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [3]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [4]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [5]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [6]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [7]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [8]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [9]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [a]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [b]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [c]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [d]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [e]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [f]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 <--

==============  REF Regs  ==============
  $0: 0x0000000000000000   ra: 0x0000000000000000   sp: 0x0000000000000000   gp: 0x0000000000000000 
  tp: 0x0000000000000000   t0: 0x0000000000000000   t1: 0x0000000000000000   t2: 0x0000000000000000 
  s0: 0x0000000000000000   s1: 0x0000000000000000   a0: 0x0000000000000000   a1: 0x0000000000000000 
  a2: 0x0000000000000000   a3: 0x0000000000000000   a4: 0x0000000000000000   a5: 0x0000000000000000 
  a6: 0x0000000000000000   a7: 0x0000000000000000   s2: 0x0000000000000000   s3: 0x0000000000000000 
  s4: 0x0000000000000000   s5: 0x0000000000000000   s6: 0x0000000000000000   s7: 0x0000000000000000 
  s8: 0x0000000000000000   s9: 0x0000000000000000  s10: 0x0000000000000000  s11: 0x0000000000000000 
  t3: 0x0000000000000000   t4: 0x0000000000000000   t5: 0x0000000000000000   t6: 0x0000000000000000 
 ft0: 0x0000000000000000  ft1: 0x0000000000000000  ft2: 0x0000000000000000  ft3: 0x0000000000000000 
 ft4: 0x0000000000000000  ft5: 0x0000000000000000  ft6: 0x0000000000000000  ft7: 0x0000000000000000 
 fs0: 0x0000000000000000  fs1: 0x0000000000000000  fa0: 0x0000000000000000  fa1: 0x0000000000000000 
 fa2: 0x0000000000000000  fa3: 0x0000000000000000  fa4: 0x0000000000000000  fa5: 0x0000000000000000 
 fa6: 0x0000000000000000  fa7: 0x0000000000000000  fs2: 0x0000000000000000  fs3: 0x0000000000000000 
 fs4: 0x0000000000000000  fs5: 0x0000000000000000  fs6: 0x0000000000000000  fs7: 0x0000000000000000 
 fs8: 0x0000000000000000  fs9: 0x0000000000000000 fs10: 0x0000000000000000 fs11: 0x0000000000000000 
 ft8: 0x0000000000000000  ft9: 0x0000000000000000 ft10: 0x0000000000000000 ft11: 0x0000000000000000 
pc: 0x0000000080000000 mstatus: 0x0000000000000000 mcause: 0x0000000000000000 mepc: 0x0000000000000000
                       sstatus: 0x0000000000000000 scause: 0x0000000000000000 sepc: 0x0000000000000000
satp: 0x0000000000000000
mip: 0x0000000000000000 mie: 0x0000000000000000 mscratch: 0x0000000000000000 sscratch: 0x0000000000000000
mideleg: 0x0000000000000000 medeleg: 0x0000000000000000
mtval: 0x0000000000000000 stval: 0x0000000000000000mtvec: 0x0000000000000000 stvec: 0x0000000000000000
priviledgeMode: 0
Core 0: ABORT at pc = 0x0
total guest instructions = 0
instrCnt = 0, cycleCnt = 0, IPC = -nan
Seed=0 Guest cycle spent: 5001 (this will be different from cycleCnt if emu loads a snapshot)
Host time spent: 6ms

在前几行输出可以看出,由于我们还没有接入 difftest,框架无法获取我们 CPU 的执行状态。因此接下来需要修改 CPU 模块来接入 difftest 框架。

接入RAM

我们的 CPU 需要获取执行的指令以及运行 load/store 指令。原先这些数据verilator 中的测试框架给出,现在需要将其转接到 difftest 框架中。

difftest 框架通过 RAMHelper 向 CPU 提供读写信息。其定义在 ram.v 中。我们需要做的就是在 CPU 中实例化这个 RAMHelper 模块并且按照其定义连接对应信号。

需要注意的是,RAMHelper 读写都仅以 8字节 为一个单位,也即,当rIdx1 时,实际读取了 0x80000008 开始的八个字节。写入同理。

正确接入后,期望在 CPU 运行过程中可以正确读取对应地址的信息。正确性可以通过在代码中打印日志人工判断。

我个人在 SimTop.v 中的实现如下:

  class RAMHelper extends BlackBox {
    val io = IO(new Bundle {
      val clk = Input(Clock())
      val en = Input(Bool())
      val rIdx = Input(UInt(64.W))
      val rdata = Output(UInt(64.W))
      val wIdx = Input(UInt(64.W))
      val wdata = Input(UInt(64.W))
      val wmask = Input(UInt(64.W))
      val wen = Input(Bool())
    })
  }
  val ram = Module(new RAMHelper)
  val rvcore = Module(new RvCore)

  ram.io.clk := clock
  ram.io.en := rvcore.io.ram.en
  ram.io.rIdx := rvcore.io.ram.raddr - (BigInt("80000000", 16) >> 3).U
  ram.io.wIdx := rvcore.io.ram.waddr - (BigInt("80000000", 16) >> 3).U
  ram.io.wdata := rvcore.io.ram.wdata
  ram.io.wmask := rvcore.io.ram.wmask
  ram.io.wen := rvcore.io.ram.wen
  rvcore.io.ram.rdata := ram.io.rdata

接入“指令提交”

我们需要通过一个方式告诉 difftest 框架已经完成了某条指令的执行。该功能通过 DifftestInstrCommit 实现。verilog中对应的模块是 difftest.v

具体而言,需要在写回阶段,实例化 DifftestInstrCommit 并且连接对应的信号。

我个人的实现大概如下:

val commit = Module(new difftest.DifftestInstrCommit)
commit.io.clock := clock
commit.io.coreid := 0.U
commit.io.index := 0.U

commit.io.valid := RegNext(io.in_valid && !io.stall)
commit.io.pc := RegNext(io.in.pc)
commit.io.instr := DontCare
commit.io.skip := false.B
commit.io.isRVC := false.B
commit.io.scFailed := false.B
commit.io.wen :=
  RegNext(io.in_valid && io.in.wb.rd =/= 0.U)
commit.io.wdata := RegNext(io.in.wb.data)
commit.io.wdest := RegNext(io.in.wb.rd)

其中,我没有使用 RegNext 的信号是暂时不需要管的,和我一致即可。其余使用了 RegNext 的信号需要替换成你们对应的实现。(端口含义通过命名可以猜出来很多了)

此时,执行 mill -i __.test.runMain top.TopMain -td ./buildmake -C difftest emubuild/emu -i $IMG,期望 difftest 能够正确识别出第一条指令的执行。但是由于其余数据我们还未提供给 difftest 框架,所以极可能第一条指令就会被判错,这是正常现象。

该阶段正确性的判定可以通过是否输出了 The first instruction of core 0 has commited. Difftest enabled. 判断。

我个人在该阶段的输出如下:

$ build/emu -b 0 -i ../oscpu-framework/cpu/inst.bin
Emu compiled at Jul 21 2021, 23:00:47
The image is ../oscpu-framework/cpu/inst.bin
Using simulated 8192MB RAM
[warning] sdcard img not found
--diff is not given, try to use $(NEMU_HOME)/build/riscv64-nemu-interpreter-so by default
Using /home/lh/NEMU/build/riscv64-nemu-interpreter-so for difftest
[src/device/io/mmio.c,13,add_mmio_map] Add mmio map 'clint' at [0xa2000000, 0xa200ffff]
[src/device/io/mmio.c,13,add_mmio_map] Add mmio map 'sdhci' at [0xa3000000, 0xa300007f]
[src/device/sdcard.c,118,init_sdcard] Can not find sdcard image: /home/yzh/projectn/debian.img
Received commit pc=80000000
The first instruction of core 0 has commited. Difftest enabled. 

============== Commit Group Trace (Core 0) ==============
commit group [0]: pc 0080000000 cmtcnt 1 <--
commit group [1]: pc 0000000000 cmtcnt 0 
commit group [2]: pc 0000000000 cmtcnt 0 
commit group [3]: pc 0000000000 cmtcnt 0 
commit group [4]: pc 0000000000 cmtcnt 0 
commit group [5]: pc 0000000000 cmtcnt 0 
commit group [6]: pc 0000000000 cmtcnt 0 
commit group [7]: pc 0000000000 cmtcnt 0 
commit group [8]: pc 0000000000 cmtcnt 0 
commit group [9]: pc 0000000000 cmtcnt 0 
commit group [a]: pc 0000000000 cmtcnt 0 
commit group [b]: pc 0000000000 cmtcnt 0 
commit group [c]: pc 0000000000 cmtcnt 0 
commit group [d]: pc 0000000000 cmtcnt 0 
commit group [e]: pc 0000000000 cmtcnt 0 
commit group [f]: pc 0000000000 cmtcnt 0 

============== Commit Instr Trace ==============
commit inst [0]: pc 0080000000 inst 00000000 wen 1 dst 00000001 data 0000000000000001 <--
commit inst [1]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [2]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [3]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [4]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [5]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [6]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [7]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [8]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [9]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [a]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [b]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [c]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [d]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [e]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 
commit inst [f]: pc 0000000000 inst 00000000 wen 0 dst 00000000 data 0000000000000000 

==============  REF Regs  ==============
  $0: 0x0000000000000000   ra: 0x0000000000000001   sp: 0x0000000000000000   gp: 0x0000000000000000 
  tp: 0x0000000000000000   t0: 0x0000000000000000   t1: 0x0000000000000000   t2: 0x0000000000000000 
  s0: 0x0000000000000000   s1: 0x0000000000000000   a0: 0x0000000000000000   a1: 0x0000000000000000 
  a2: 0x0000000000000000   a3: 0x0000000000000000   a4: 0x0000000000000000   a5: 0x0000000000000000 
  a6: 0x0000000000000000   a7: 0x0000000000000000   s2: 0x0000000000000000   s3: 0x0000000000000000 
  s4: 0x0000000000000000   s5: 0x0000000000000000   s6: 0x0000000000000000   s7: 0x0000000000000000 
  s8: 0x0000000000000000   s9: 0x0000000000000000  s10: 0x0000000000000000  s11: 0x0000000000000000 
  t3: 0x0000000000000000   t4: 0x0000000000000000   t5: 0x0000000000000000   t6: 0x0000000000000000 
 ft0: 0x0000000000000000  ft1: 0x0000000000000000  ft2: 0x0000000000000000  ft3: 0x0000000000000000 
 ft4: 0x0000000000000000  ft5: 0x0000000000000000  ft6: 0x0000000000000000  ft7: 0x0000000000000000 
 fs0: 0x0000000000000000  fs1: 0x0000000000000000  fa0: 0x0000000000000000  fa1: 0x0000000000000000 
 fa2: 0x0000000000000000  fa3: 0x0000000000000000  fa4: 0x0000000000000000  fa5: 0x0000000000000000 
 fa6: 0x0000000000000000  fa7: 0x0000000000000000  fs2: 0x0000000000000000  fs3: 0x0000000000000000 
 fs4: 0x0000000000000000  fs5: 0x0000000000000000  fs6: 0x0000000000000000  fs7: 0x0000000000000000 
 fs8: 0x0000000000000000  fs9: 0x0000000000000000 fs10: 0x0000000000000000 fs11: 0x0000000000000000 
 ft8: 0x0000000000000000  ft9: 0x0000000000000000 ft10: 0x0000000000000000 ft11: 0x0000000000000000 
pc: 0x0000000080000004 mstatus: 0x0000000000000000 mcause: 0x0000000000000000 mepc: 0x0000000000000000
                       sstatus: 0x0000000000000000 scause: 0x0000000000000000 sepc: 0x0000000000000000
satp: 0x0000000000000000
mip: 0x0000000000000000 mie: 0x0000000000000000 mscratch: 0x0000000000000000 sscratch: 0x0000000000000000
mideleg: 0x0000000000000000 medeleg: 0x0000000000000000
mtval: 0x0000000000000000 stval: 0x0000000000000000mtvec: 0x0000000000000000 stvec: 0x0000000000000000
priviledgeMode: 0
     ra different at pc = 0x0080000000, right= 0x0000000000000001, wrong = 0x0000000000000000
Core 0: ABORT at pc = 0x0
total guest instructions = 0
instrCnt = 0, cycleCnt = 0, IPC = -nan
Seed=0 Guest cycle spent: 5 (this will be different from cycleCnt if emu loads a snapshot)
Host time spent: 4ms

从错误信息中,可以看到期望的 Difftest enabled,说明 InstrCommit 正确接入了。并且可以看到在 ==Commit Instr Trace== 中我的CPU正确提交了第一条指令,但是由于 ra 寄存器的错误终止了 difftest。这个问题在下个阶段会修复。

接入寄存器组

difftest 框架主要比对我们 CPU 的寄存器值和 NEMU 模拟器的寄存器值来判定实现的正确性。该功能通过 DifftestArchIntRegState 实现。verilog 也有对应的模块

具体而言,需要在 寄存器组 模块中,实例化 DifftestArchIntRegState 并连接对应的信号。

我个人的实现大概如下:

// 这是我原先的寄存器实现,不需要和我一致
val reg = RegInit(VecInit(Seq.fill(32)(0.U(c.XLEN.W))))

// 以下为接入 difftest 新增内容
val mod = Module(new difftest.DifftestArchIntRegState)
mod.io.clock := clock
mod.io.coreid := 0.U
mod.io.gpr := reg

此外,difftest 在运行时还会对比 CSR 寄存器的值。由于我们此时并未实现,直接硬编码即可。(但是不能留空,否则会出现非合法值导致 NEMU 异常)

    val csr = Module(new difftest.DifftestCSRState)
    csr.io.clock := clock
    csr.io.coreid := 0.U
    csr.io.mstatus := 0.U
    csr.io.mcause := 0.U
    csr.io.mepc := 0.U
    csr.io.sstatus := 0.U
    csr.io.scause := 0.U
    csr.io.sepc := 0.U
    csr.io.satp := 0.U
    csr.io.mip := 0.U
    csr.io.mie := 0.U
    csr.io.mscratch := 0.U
    csr.io.sscratch := 0.U
    csr.io.mideleg := 0.U
    csr.io.medeleg := 0.U
    csr.io.mtval:= 0.U
    csr.io.stval:= 0.U
    csr.io.mtvec := 0.U
    csr.io.stvec := 0.U
    csr.io.priviledgeMode := 0.U

此时,再次执行 mill -i __.test.runMain top.TopMain -td ./buildmake -C difftest emubuild/emu -i $IMG,此时期望能够正确执行 difftest 了。(此时的表现为 difftest 没有任何输出)
但是由于我们还未实现 Trap 通知 difftest 框架我们的程序已经终止,difftest将会一直执行下去。(因为在 AMhalt 的实现暂时是一个死循环)

关于 Trap

NJU-ProjectN 中告知框架 “程序已运行结束” 的方式是发送一个 Trap 事件。在 difftest 框架中也是如此。在没有修改的情况下,如果使用 NEMU 运行 AM 编译的程序,会出现程序运行结束之后仍然无法退出的情况。该行为在 trap.c 中定义。

$NEMU_HOME/build/riscv64-nemu-interpreter -b am-kernels/tests/cpu-tests/build/add-riscv64-mycpu.bin

可以看到,无论等待多久,NEMU 的运行都不会停止。

要修改这个行为,让程序能够自动退出,只需要在 trap.chalt 前加入一条特殊的指令即可。

void halt(int code) {
  asm volatile("mv a0, %0; .word 0x0000006b" : :"r"(code));
  while (1);
}

asm 语句在 halt 函数中添加了一条特殊指令 0x0000006b。该指令在 NEMU 中的行为是终止执行,并将 a0 寄存器的值作为程序是否正常退出的标志。 a0 为 0 时表示时正常终止,否则代表异常终止。该行为定义在 exec/special.c 中。

此时重新编译 cpu-test (可以使用 make cleanmake ARCH=riscv64-mycpu),并且再次使用 NEMU 运行该测试,可以发现 NEMU 可以正常终止,并输出 HIT GOOD TRAP

$ $NEMU_HOME/build/riscv64-nemu-interpreter -b ../am-kernels/tests/cpu-tests/build/recursion-riscv64-mycpu.bin
[src/device/io/mmio.c,13,add_mmio_map] Add mmio map 'clint' at [0xa2000000, 0xa200ffff]
[src/device/io/mmio.c,13,add_mmio_map] Add mmio map 'sdhci' at [0xa3000000, 0xa300007f]
[src/device/sdcard.c,118,init_sdcard] Can not find sdcard image: /home/yzh/projectn/debian.img
[src/monitor/monitor.c,46,load_img] The image is ../am-kernels/tests/cpu-tests/build/recursion-riscv64-mycpu.bin
[src/monitor/monitor.c,29,welcome] Debug: OFF
[src/monitor/monitor.c,32,welcome] Build time: 15:45:21, Jul 23 2021
Welcome to riscv64-NEMU!
For help, type "help"
[src/device/io/mmio.c,13,add_mmio_map] Add mmio map 'screen' at [0xa1000100, 0xa1000107]
[src/device/io/mmio.c,13,add_mmio_map] Add mmio map 'vmem' at [0xa0000000, 0xa00752ff]
[src/device/io/mmio.c,13,add_mmio_map] Add mmio map 'serial' at [0xa10003f8, 0xa10003ff]
[src/device/io/mmio.c,13,add_mmio_map] Add mmio map 'rtc' at [0xa1000048, 0xa1000057]
[src/device/io/mmio.c,13,add_mmio_map] Add mmio map 'keyboard' at [0xa1000060, 0xa1000063]
[src/device/io/mmio.c,13,add_mmio_map] Add mmio map 'audio' at [0xa1000200, 0xa1000217]
[src/device/io/mmio.c,13,add_mmio_map] Add mmio map 'audio-sbuf' at [0xa0800000, 0xa080ffff]
[src/monitor/cpu-exec.c,97,cpu_exec] nemu: HIT GOOD TRAP at pc = 0x0000000080000290


[src/monitor/cpu-exec.c,35,monitor_statistic] total guest instructions = 6552

接入 Trap

接入 difftestTrap 的流程与之前类似,只需要实例化对应的接口并连接信号即可。对应本次是 DifftestTrapEvent 模块。

暂时不需要区分 GoodTrapBadTrap,因此大部分内容都可以硬编码完成。此外,只需要保证你的 CPU 在识别到 0x0000006b 这个非标准的指令的时候不会出现异常行为即可。

我的实现如下:

val trap = Module(new DifftestTrapEvent)
trap.io.clock    := clock
trap.io.coreid   := c.CoreId.U
trap.io.valid    := io.in.commit.instr === BigInt("0000006b", 16).U
trap.io.code     := 0.U // GoodTrap
trap.io.pc       := io.in.commit.pc
trap.io.cycleCnt := 0.U
trap.io.instrCnt := 0.U

此时再次执行 difftest 相关流程,可以看到 difftest 也最终输出了 Hit Good Trap 指示程序正确结束了。

Emu compiled at Jul 23 2021, 16:49:20
The image is ../am-kernels/tests/cpu-tests/build/fact-riscv64-mycpu.bin
Using simulated 8192MB RAM
[warning] sdcard img not found
--diff is not given, try to use $(NEMU_HOME)/build/riscv64-nemu-interpreter-so by default
Using /home/lh/NEMU/build/riscv64-nemu-interpreter-so for difftest
[src/device/io/mmio.c,13,add_mmio_map] Add mmio map 'clint' at [0xa2000000, 0xa200ffff]
[src/device/io/mmio.c,13,add_mmio_map] Add mmio map 'sdhci' at [0xa3000000, 0xa300007f]
[src/device/sdcard.c,118,init_sdcard] Can not find sdcard image: /home/yzh/projectn/debian.img
The first instruction of core 0 has commited. Difftest enabled. 
Core 0: HIT GOOD TRAP at pc = 0x8000011c
total guest instructions = 0
instrCnt = 0, cycleCnt = 0, IPC = -nan
Seed=0 Guest cycle spent: 5529 (this will be different from cycleCnt if emu loads a snapshot)
Host time spent: 9ms

最后,附带一个能够一件测试 cpu-test 中所有指令的命令:

find ../am-kernels/tests/cpu-tests/build/ -name *.bin | xargs -i build/emu -i {}

如果一次性输出太多,可以考虑重定向到某个文件中然后查找 ABORT 关键词查看错误的样例。

【求助】oscpu-axi版本的difftest出现问题

直接按照readme 克隆下来的代码,只修改了myinfo.txt,但是编译却出现了如下错误
image

已经尝试过再次clone(检查了submodule都存在),也尝试过自己下载zip包解压工程,检查并修改了menuconfig,ram.h的内存大小。还是报上图的错误。
也尝试过把之前成功接入的difftest工程与新的工程相互移植,发现在新的oscpu-axi工程上difftest均不可用。
已经黔驴技穷了,望各位小伙伴指点迷津啊!!!

分支预测错误情况下的difftest调用

遇到分支预测错误的情况,需要flush掉流水线,如何暂停difftest的指令提交,等到新的流水指令到来的时候再继续提交指令,以及difftest调用NEMU继续执行

difftest访存踩坑分享

错误描述

我在对lw和sw指令进行测试,按照学长提供的测试代码自己写了一段汇编并翻译成2进制进行测试。
$MP0RK)HPD 56TZZM27G$G
LYQPQO8B 4GOE6JXA UKUZY
我在ram.cpp里面加入了printf信号打印出存储器,看到写入和读出的数据是正确的。
{P%)B{59% HCKU8@U)C$ARV
但是difftest报错显示,这个读出的数据和理想的寄存器数据不同?
3WYNG1(KF5P5JI5_VB(6)1N
通过分析可以发现理想的寄存器状态是0x0000000400000003,而读出的数据是0x0000000000000003。该pc对应的指令ld,即读取64位数据。那么基本可以判断是在对存储器进行读写时出现的问题。

访存分析

大小端模式

RISCV的数据均为小端模式,即高位数据存储在存储器的大地址,低位数据存储在存储器的小地址。以下图为例,最高位字节是0x0A,最低位字节是0x0D。
1628043702(1)

Difftest的读出写入函数

打开difftest/src/test/csrc/common/ram.cpp文件,找到ram_write_helper和ram_inst_helper函数。在这里,我添加了两行printf(已注释)来打印写入或者读出数据,来辅助调试。

extern "C" uint64_t ram_inst_helper(uint8_t en, uint64_t rIdx) {
  if (!ram)
    return 0;
  if (en && rIdx >= EMU_RAM_SIZE / sizeof(uint64_t)) {
    rIdx %= EMU_RAM_SIZE / sizeof(uint64_t);
  }
  pthread_mutex_lock(&ram_mutex);
  uint64_t rdata = (en) ? ram[rIdx] : 0;
  pthread_mutex_unlock(&ram_mutex);
  // printf("Read\t rIdx: 0x%lx \t rdata: 0x%lx \n", rIdx, rdata );
  return rdata;
}

extern "C" void ram_write_helper(uint64_t wIdx, uint64_t wdata, uint64_t wmask, uint8_t wen) {
  if (wen && ram) {
    if (wIdx >= EMU_RAM_SIZE / sizeof(uint64_t)) {
      printf("ERROR: ram wIdx = 0x%lx out of bound!\n", wIdx);
      assert(wIdx < EMU_RAM_SIZE / sizeof(uint64_t));
    }
    pthread_mutex_lock(&ram_mutex);
    ram[wIdx] = (ram[wIdx] & ~wmask) | (wdata & wmask);
    pthread_mutex_unlock(&ram_mutex);
    // printf("Write\t wIdx: 0x%lx \t wdata: 0x%lx \t wmask: 0x%lx \n", wIdx, wdata, wmask);
  }
}

分析代码可以发现,代码的读写都是以uint64_t进行的!!!所以在写入过程加入了一个64位的写入掩码wmask!!!
即,该存储器是按照64位的存储空间设置的,所以对应的rtl代码要做出相同的适配。
由于项目给出的例子中没有涉及到回写部分的操作,所以在这里我也是纠结了很久才修正了bug。

RTL访存代码(chisel)

我使用的是chisel,给出我的访存部分的代码;对于verilog选手,我也会给出chisel生成的verilog代码。

chisel代码——difftest接口

大体上和chisel接入difftest经验贴的接入部分一致。需要特别注意的是( rvcore.io.mem_r_addr - "h80000000".U(64.W) ) >> 3.U这里计算出来的地址要右移3位。主要原因在于存储器的每个存储地址对应的存储空间是64位的!!!最低的3位的地址是该存储地址内存储空间的索引。
假设低4位的地址为the_addr[3:0],存储器存储的64位数据为the_data[63:0]
那么对于lw来说,the_addr[2]就决定读取the_data的高32位the_data[63:32]还是低32位the_data[31:0]
同样对于lh来说,the_addr[2:1]就决定读取the_data的高16位the_data[63:48]或者是中间的the_data[47:32]the_data[31:16]还是低16位the_data[15:0]
同样对于lb来说,the_addr[2:0]就决定读取the_data的哪8位数据。
对于写入,也是相同的情况。
1628044439(1)

chisel代码——访存阶段(Mem_Stage)

代码写的比较烂,没有进行优化。核心的**就是,写入或者读出时,按照读写指令的类型和具体的地址,对数据进行移位,配合读写掩码得到正确的结果。

package oscpu

import chisel3._
import chisel3.util._
import chisel3.experimental._

class Mem_Stage extends Module {
    val io = IO(new Bundle{
        val inst_type       = Input(UInt( 5.W))
        val rd_data         = Input(UInt(64.W))
        val save_data       = Input(UInt(64.W))
        val mem_r_data      = Input(UInt(64.W))

        val mem_ext         = Input(UInt(1.W))
        val mem_sel         = Input(UInt(2.W))

        val mem_r_ena       = Output(UInt( 1.W))
        val mem_r_addr      = Output(UInt(64.W))

        val mem_w_ena       = Output(UInt( 1.W))
        val mem_w_addr      = Output(UInt(64.W))
        val mem_w_data      = Output(UInt(64.W))
        val mem_w_mask      = Output(UInt(64.W))

        val rd_w_data       = Output(UInt(64.W))
    })


    val the_mask            = Wire(UInt(64.W))
    val the_data_mask       = Wire(UInt(64.W))  // should be removed
    val the_data_ext        = Wire(UInt(64.W))

    when(reset.asBool) {
        io.mem_r_ena    := 0.U
        io.mem_r_addr   := 0.U
        io.mem_w_ena    := 0.U
        io.mem_w_addr   := 0.U
        io.mem_w_data   := 0.U
        io.mem_w_mask   := 0.U
        io.rd_w_data    := 0.U
        the_mask        := 0.U
        the_data_mask   := 0.U 
        the_data_ext    := 0.U 
    }.otherwise{

        // THE MASK
        // 64-bits
        when     ( io.mem_sel === 3.U ) { 
            the_mask := Cat( VecInit(Seq.fill(64)( 1.U )).asUInt )
        }
        // 32-bits
        .elsewhen( io.mem_sel === 2.U ) { 
            when(io.rd_data(2).asBool){
                the_mask := Cat( VecInit(Seq.fill(32)( 1.U )).asUInt, VecInit(Seq.fill(32)( 0.U )).asUInt ) 
            }.otherwise{
                the_mask := Cat( VecInit(Seq.fill(32)( 0.U )).asUInt, VecInit(Seq.fill(32)( 1.U )).asUInt ) 
            }
        }
        // 16-bits
        .elsewhen( io.mem_sel === 1.U ) { 

            when(io.rd_data(2,1) === 1.U){
                the_mask := Cat( VecInit(Seq.fill(32)( 0.U )).asUInt, VecInit(Seq.fill(16)( 1.U )).asUInt, VecInit(Seq.fill(16)( 0.U )).asUInt ) 
            }  
            .elsewhen(io.rd_data(2,1) === 2.U){
                the_mask := Cat( VecInit(Seq.fill(16)( 0.U )).asUInt, VecInit(Seq.fill(16)( 1.U )).asUInt, VecInit(Seq.fill(32)( 0.U )).asUInt ) 
            }  
            .elsewhen(io.rd_data(2,1) === 3.U){
                the_mask := Cat( VecInit(Seq.fill(16)( 1.U )).asUInt, VecInit(Seq.fill(48)( 0.U )).asUInt ) 
            }    
            .otherwise{
                the_mask := Cat( VecInit(Seq.fill(48)( 0.U )).asUInt, VecInit(Seq.fill(16)( 1.U )).asUInt ) 
            }
        }
        // 8-bits
        .otherwise {
            when(io.rd_data(2,0) === 1.U){
                the_mask := Cat( VecInit(Seq.fill(48)( 0.U )).asUInt, VecInit(Seq.fill( 8)( 1.U )).asUInt, VecInit(Seq.fill( 8)( 0.U )).asUInt  ) 
            }  
            .elsewhen(io.rd_data(2,0) === 2.U){
                the_mask := Cat( VecInit(Seq.fill(40)( 0.U )).asUInt, VecInit(Seq.fill( 8)( 1.U )).asUInt, VecInit(Seq.fill( 16)( 0.U )).asUInt  ) 
            }  
            .elsewhen(io.rd_data(2,0) === 3.U){
                the_mask := Cat( VecInit(Seq.fill(32)( 0.U )).asUInt, VecInit(Seq.fill( 8)( 1.U )).asUInt, VecInit(Seq.fill(24)( 0.U )).asUInt  ) 
            }  
            .elsewhen(io.rd_data(2,0) === 4.U){
                the_mask := Cat( VecInit(Seq.fill(24)( 0.U )).asUInt, VecInit(Seq.fill( 8)( 1.U )).asUInt, VecInit(Seq.fill(32)( 0.U )).asUInt  ) 
            }  
            .elsewhen(io.rd_data(2,0) === 5.U){
                the_mask := Cat( VecInit(Seq.fill(16)( 0.U )).asUInt, VecInit(Seq.fill( 8)( 1.U )).asUInt, VecInit(Seq.fill(40)( 0.U )).asUInt  ) 
            }  
            .elsewhen(io.rd_data(2,0) === 6.U){
                the_mask := Cat( VecInit(Seq.fill( 8)( 0.U )).asUInt, VecInit(Seq.fill( 8)( 1.U )).asUInt, VecInit(Seq.fill(48)( 0.U )).asUInt  ) 
            }  
            .elsewhen(io.rd_data(2,0) === 7.U){
                the_mask := Cat( VecInit(Seq.fill( 8)( 1.U )).asUInt, VecInit(Seq.fill(56)( 0.U )).asUInt ) 
            }  
            .otherwise{
                 the_mask := Cat( VecInit(Seq.fill(56)( 0.U )).asUInt, VecInit(Seq.fill( 8)( 1.U )).asUInt  ) 
            }

        }    



        // load
        when( io.inst_type(1) === 1.U ){
            io.mem_r_ena    := 1.U
            io.mem_r_addr   := io.rd_data
            the_data_mask   := io.mem_r_data & the_mask

            // 64-bits
            when     ( io.mem_sel === 3.U ) { 
                the_data_ext    := the_data_mask
                io.rd_w_data    := the_data_ext
            }
            // 32-bits
            .elsewhen( io.mem_sel === 2.U ) {
                when(io.rd_data(2).asBool){
                    the_data_ext    := the_data_mask >> 32.U
                }.otherwise{
                    the_data_ext    := the_data_mask
                }

                when( io.mem_ext.asBool )   { io.rd_w_data := Cat( VecInit(Seq.fill(32)( the_data_ext(31) )).asUInt,   the_data_ext(31,0) ) }
                .otherwise                  { io.rd_w_data := Cat( VecInit(Seq.fill(32)( 0.U )).asUInt,            the_data_ext(31,0) ) }
            }
            // 16-bits
            .elsewhen( io.mem_sel === 1.U ) {

                when(io.rd_data(2,1) === 1.U){
                    the_data_ext    := the_data_mask >> 16.U
                }  
                .elsewhen(io.rd_data(2,1) === 2.U){
                    the_data_ext    := the_data_mask >> 32.U
                }  
                .elsewhen(io.rd_data(2,1) === 3.U){
                    the_data_ext    := the_data_mask >> 48.U
                }  
                .otherwise{
                    the_data_ext    := the_data_mask
                }

                when( io.mem_ext.asBool )   { io.rd_w_data := Cat( VecInit(Seq.fill(48)( the_data_ext(15) )).asUInt,   the_data_ext(15,0) ) }
                .otherwise                  { io.rd_w_data := Cat( VecInit(Seq.fill(48)( 0.U )).asUInt,            the_data_ext(15,0) ) }
            }
            // 8-bits
            .otherwise                      {
                when(io.rd_data(2,0) === 1.U){
                    the_data_ext    := the_data_mask >> 8.U
                }  
                .elsewhen(io.rd_data(2,0) === 2.U){
                    the_data_ext    := the_data_mask >> 16.U
                }  
                .elsewhen(io.rd_data(2,0) === 3.U){
                    the_data_ext    := the_data_mask >> 24.U
                }  
                .elsewhen(io.rd_data(2,0) === 4.U){
                    the_data_ext    := the_data_mask >> 32.U
                }  
                .elsewhen(io.rd_data(2,0) === 5.U){
                    the_data_ext    := the_data_mask >> 40.U
                }  
                .elsewhen(io.rd_data(2,0) === 6.U){
                    the_data_ext    := the_data_mask >> 48.U
                }  
                .elsewhen(io.rd_data(2,0) === 7.U){
                    the_data_ext    := the_data_mask >> 56.U
                }  
                .otherwise{
                    the_data_ext    := the_data_mask
                }

                when( io.mem_ext.asBool )   { io.rd_w_data := Cat( VecInit(Seq.fill(56)( the_data_ext(7) )).asUInt,    the_data_ext(7,0) ) }
                .otherwise                  { io.rd_w_data := Cat( VecInit(Seq.fill(56)( 0.U )).asUInt,            the_data_ext(7,0) ) }
            }

        }.otherwise{
            io.mem_r_ena    := 0.U
            io.mem_r_addr   := 0.U
            io.rd_w_data    := io.rd_data
            the_data_mask   := 0.U 
            the_data_ext    := 0.U 
        }
        


        // save
        when( io.inst_type(0) === 1.U ){
            io.mem_w_ena    := 1.U
            io.mem_w_addr   := io.rd_data
            io.mem_w_mask   := the_mask

            // 64-bits
            when     ( io.mem_sel === 3.U ) { 
                io.mem_w_data   := io.save_data
            }
            // 32-bits
            .elsewhen( io.mem_sel === 2.U ) {
                when(io.rd_data(2).asBool){
                    io.mem_w_data   := io.save_data << 32.U
                }.otherwise{
                    io.mem_w_data   := io.save_data
                }

            }
            // 16-bits
            .elsewhen( io.mem_sel === 1.U ) {

                when(io.rd_data(2,1) === 1.U){
                    io.mem_w_data   := io.save_data << 16.U
                }  
                .elsewhen(io.rd_data(2,1) === 2.U){
                    io.mem_w_data   := io.save_data << 32.U
                }  
                .elsewhen(io.rd_data(2,1) === 3.U){
                    io.mem_w_data   := io.save_data << 48.U
                }  
                .otherwise{
                    io.mem_w_data   := io.save_data
                }
            }
            // 8-bits
            .otherwise {
                when(io.rd_data(2,0) === 1.U){
                    io.mem_w_data   := io.save_data << 8.U
                }  
                .elsewhen(io.rd_data(2,0) === 2.U){
                    io.mem_w_data   := io.save_data << 16.U
                }  
                .elsewhen(io.rd_data(2,0) === 3.U){
                    io.mem_w_data   := io.save_data << 24.U
                }  
                .elsewhen(io.rd_data(2,0) === 4.U){
                    io.mem_w_data   := io.save_data << 32.U
                }  
                .elsewhen(io.rd_data(2,0) === 5.U){
                    io.mem_w_data   := io.save_data << 40.U
                }  
                .elsewhen(io.rd_data(2,0) === 6.U){
                    io.mem_w_data   := io.save_data << 48.U
                }  
                .elsewhen(io.rd_data(2,0) === 7.U){
                    io.mem_w_data   := io.save_data << 56.U
                }  
                .otherwise{
                    io.mem_w_data   := io.save_data
                }
            }
        }.otherwise{
            io.mem_w_ena    := 0.U
            io.mem_w_addr   := 0.U
            io.mem_w_data   := 0.U
            io.mem_w_mask   := 0.U
        }
    }
}

verilog代码——访存

chisel自动生成的代码,可读性比较差

module Mem_Stage(
  input         clock,
  input         reset,
  input  [4:0]  io_inst_type,
  input  [63:0] io_rd_data,
  input  [63:0] io_save_data,
  input  [63:0] io_mem_r_data,
  input         io_mem_ext,
  input  [1:0]  io_mem_sel,
  output        io_mem_r_ena,
  output [63:0] io_mem_r_addr,
  output        io_mem_w_ena,
  output [63:0] io_mem_w_addr,
  output [63:0] io_mem_w_data,
  output [63:0] io_mem_w_mask,
  output [63:0] io_rd_w_data
);
  wire  _T_1 = io_mem_sel == 2'h3; // @[Mem_Stage.scala 48:31]
  wire  _T_2 = io_mem_sel == 2'h2; // @[Mem_Stage.scala 52:31]
  wire [63:0] _GEN_0 = io_rd_data[2] ? 64'hffffffff00000000 : 64'hffffffff; // @[Mem_Stage.scala 53:39 Mem_Stage.scala 54:26 Mem_Stage.scala 56:26]
  wire  _T_5 = io_mem_sel == 2'h1; // @[Mem_Stage.scala 60:31]
  wire  _T_7 = io_rd_data[2:1] == 2'h1; // @[Mem_Stage.scala 62:34]
  wire  _T_9 = io_rd_data[2:1] == 2'h2; // @[Mem_Stage.scala 65:39]
  wire  _T_11 = io_rd_data[2:1] == 2'h3; // @[Mem_Stage.scala 68:39]
  wire [63:0] _GEN_1 = io_rd_data[2:1] == 2'h3 ? 64'hffff000000000000 : 64'hffff; // @[Mem_Stage.scala 68:47 Mem_Stage.scala 69:26 Mem_Stage.scala 72:26]
  wire [63:0] _GEN_2 = io_rd_data[2:1] == 2'h2 ? 64'hffff00000000 : _GEN_1; // @[Mem_Stage.scala 65:47 Mem_Stage.scala 66:26]
  wire [63:0] _GEN_3 = io_rd_data[2:1] == 2'h1 ? 64'hffff0000 : _GEN_2; // @[Mem_Stage.scala 62:42 Mem_Stage.scala 63:26]
  wire  _T_13 = io_rd_data[2:0] == 3'h1; // @[Mem_Stage.scala 77:34]
  wire  _T_15 = io_rd_data[2:0] == 3'h2; // @[Mem_Stage.scala 80:39]
  wire  _T_17 = io_rd_data[2:0] == 3'h3; // @[Mem_Stage.scala 83:39]
  wire  _T_19 = io_rd_data[2:0] == 3'h4; // @[Mem_Stage.scala 86:39]
  wire  _T_21 = io_rd_data[2:0] == 3'h5; // @[Mem_Stage.scala 89:39]
  wire  _T_23 = io_rd_data[2:0] == 3'h6; // @[Mem_Stage.scala 92:39]
  wire  _T_25 = io_rd_data[2:0] == 3'h7; // @[Mem_Stage.scala 95:39]
  wire [63:0] _GEN_4 = io_rd_data[2:0] == 3'h7 ? 64'hff00000000000000 : 64'hff; // @[Mem_Stage.scala 95:47 Mem_Stage.scala 96:26 Mem_Stage.scala 99:27]
  wire [63:0] _GEN_5 = io_rd_data[2:0] == 3'h6 ? 64'hff000000000000 : _GEN_4; // @[Mem_Stage.scala 92:47 Mem_Stage.scala 93:26]
  wire [63:0] _GEN_6 = io_rd_data[2:0] == 3'h5 ? 64'hff0000000000 : _GEN_5; // @[Mem_Stage.scala 89:47 Mem_Stage.scala 90:26]
  wire [63:0] _GEN_7 = io_rd_data[2:0] == 3'h4 ? 64'hff00000000 : _GEN_6; // @[Mem_Stage.scala 86:47 Mem_Stage.scala 87:26]
  wire [63:0] _GEN_8 = io_rd_data[2:0] == 3'h3 ? 64'hff000000 : _GEN_7; // @[Mem_Stage.scala 83:47 Mem_Stage.scala 84:26]
  wire [63:0] _GEN_9 = io_rd_data[2:0] == 3'h2 ? 64'hff0000 : _GEN_8; // @[Mem_Stage.scala 80:47 Mem_Stage.scala 81:26]
  wire [63:0] _GEN_10 = io_rd_data[2:0] == 3'h1 ? 64'hff00 : _GEN_9; // @[Mem_Stage.scala 77:42 Mem_Stage.scala 78:26]
  wire [63:0] _GEN_11 = io_mem_sel == 2'h1 ? _GEN_3 : _GEN_10; // @[Mem_Stage.scala 60:41]
  wire [63:0] _GEN_12 = io_mem_sel == 2'h2 ? _GEN_0 : _GEN_11; // @[Mem_Stage.scala 52:41]
  wire [63:0] _GEN_13 = io_mem_sel == 2'h3 ? 64'hffffffffffffffff : _GEN_12; // @[Mem_Stage.scala 48:41 Mem_Stage.scala 49:22]
  wire [63:0] the_mask = reset ? 64'h0 : _GEN_13; // @[Mem_Stage.scala 33:24 Mem_Stage.scala 41:25]
  wire [63:0] _the_data_mask_T = io_mem_r_data & the_mask; // @[Mem_Stage.scala 110:46]
  wire [63:0] _GEN_36 = io_inst_type[1] ? _the_data_mask_T : 64'h0; // @[Mem_Stage.scala 107:40 Mem_Stage.scala 110:29 Mem_Stage.scala 182:29]
  wire [63:0] the_data_mask = reset ? 64'h0 : _GEN_36; // @[Mem_Stage.scala 33:24 Mem_Stage.scala 42:25]
  wire [63:0] _the_data_ext_T = {{32'd0}, the_data_mask[63:32]}; // @[Mem_Stage.scala 120:54]
  wire [63:0] _GEN_14 = io_rd_data[2] ? _the_data_ext_T : the_data_mask; // @[Mem_Stage.scala 119:43 Mem_Stage.scala 120:37 Mem_Stage.scala 122:37]
  wire [63:0] _the_data_ext_T_1 = {{16'd0}, the_data_mask[63:16]}; // @[Mem_Stage.scala 132:54]
  wire [63:0] _the_data_ext_T_3 = {{48'd0}, the_data_mask[63:48]}; // @[Mem_Stage.scala 138:54]
  wire [63:0] _GEN_16 = _T_11 ? _the_data_ext_T_3 : the_data_mask; // @[Mem_Stage.scala 137:51 Mem_Stage.scala 138:37 Mem_Stage.scala 141:37]
  wire [63:0] _GEN_17 = _T_9 ? _the_data_ext_T : _GEN_16; // @[Mem_Stage.scala 134:51 Mem_Stage.scala 135:37]
  wire [63:0] _GEN_18 = _T_7 ? _the_data_ext_T_1 : _GEN_17; // @[Mem_Stage.scala 131:46 Mem_Stage.scala 132:37]
  wire [63:0] _the_data_ext_T_4 = {{8'd0}, the_data_mask[63:8]}; // @[Mem_Stage.scala 150:54]
  wire [63:0] _the_data_ext_T_6 = {{24'd0}, the_data_mask[63:24]}; // @[Mem_Stage.scala 156:54]
  wire [63:0] _the_data_ext_T_8 = {{40'd0}, the_data_mask[63:40]}; // @[Mem_Stage.scala 162:54]
  wire [63:0] _the_data_ext_T_10 = {{56'd0}, the_data_mask[63:56]}; // @[Mem_Stage.scala 168:54]
  wire [63:0] _GEN_20 = _T_25 ? _the_data_ext_T_10 : the_data_mask; // @[Mem_Stage.scala 167:51 Mem_Stage.scala 168:37 Mem_Stage.scala 171:37]
  wire [63:0] _GEN_21 = _T_23 ? _the_data_ext_T_3 : _GEN_20; // @[Mem_Stage.scala 164:51 Mem_Stage.scala 165:37]
  wire [63:0] _GEN_22 = _T_21 ? _the_data_ext_T_8 : _GEN_21; // @[Mem_Stage.scala 161:51 Mem_Stage.scala 162:37]
  wire [63:0] _GEN_23 = _T_19 ? _the_data_ext_T : _GEN_22; // @[Mem_Stage.scala 158:51 Mem_Stage.scala 159:37]
  wire [63:0] _GEN_24 = _T_17 ? _the_data_ext_T_6 : _GEN_23; // @[Mem_Stage.scala 155:51 Mem_Stage.scala 156:37]
  wire [63:0] _GEN_25 = _T_15 ? _the_data_ext_T_1 : _GEN_24; // @[Mem_Stage.scala 152:51 Mem_Stage.scala 153:37]
  wire [63:0] _GEN_26 = _T_13 ? _the_data_ext_T_4 : _GEN_25; // @[Mem_Stage.scala 149:46 Mem_Stage.scala 150:37]
  wire [63:0] _GEN_28 = _T_5 ? _GEN_18 : _GEN_26; // @[Mem_Stage.scala 129:45]
  wire [63:0] _GEN_30 = _T_2 ? _GEN_14 : _GEN_28; // @[Mem_Stage.scala 118:45]
  wire [63:0] _GEN_32 = _T_1 ? the_data_mask : _GEN_30; // @[Mem_Stage.scala 113:45 Mem_Stage.scala 114:33]
  wire [63:0] _GEN_37 = io_inst_type[1] ? _GEN_32 : 64'h0; // @[Mem_Stage.scala 107:40 Mem_Stage.scala 183:29]
  wire [63:0] the_data_ext = reset ? 64'h0 : _GEN_37; // @[Mem_Stage.scala 33:24 Mem_Stage.scala 43:25]
  wire [7:0] io_rd_w_data_lo_lo = {the_data_ext[31],the_data_ext[31],the_data_ext[31],the_data_ext[31],the_data_ext[31],
    the_data_ext[31],the_data_ext[31],the_data_ext[31]}; // @[Mem_Stage.scala 125:110]
  wire [15:0] io_rd_w_data_lo = {the_data_ext[31],the_data_ext[31],the_data_ext[31],the_data_ext[31],the_data_ext[31],
    the_data_ext[31],the_data_ext[31],the_data_ext[31],io_rd_w_data_lo_lo}; // @[Mem_Stage.scala 125:110]
  wire [31:0] io_rd_w_data_hi_1 = {the_data_ext[31],the_data_ext[31],the_data_ext[31],the_data_ext[31],the_data_ext[31],
    the_data_ext[31],the_data_ext[31],the_data_ext[31],io_rd_w_data_lo_lo,io_rd_w_data_lo}; // @[Mem_Stage.scala 125:110]
  wire [31:0] io_rd_w_data_lo_1 = the_data_ext[31:0]; // @[Mem_Stage.scala 125:132]
  wire [63:0] _io_rd_w_data_T_32 = {io_rd_w_data_hi_1,io_rd_w_data_lo_1}; // @[Cat.scala 30:58]
  wire [63:0] _io_rd_w_data_T_33 = {32'h0,io_rd_w_data_lo_1}; // @[Cat.scala 30:58]
  wire [63:0] _GEN_15 = io_mem_ext ? _io_rd_w_data_T_32 : _io_rd_w_data_T_33; // @[Mem_Stage.scala 125:45 Mem_Stage.scala 125:60 Mem_Stage.scala 126:60]
  wire [5:0] io_rd_w_data_lo_lo_lo_2 = {the_data_ext[15],the_data_ext[15],the_data_ext[15],the_data_ext[15],the_data_ext
    [15],the_data_ext[15]}; // @[Mem_Stage.scala 144:110]
  wire [11:0] io_rd_w_data_lo_lo_2 = {the_data_ext[15],the_data_ext[15],the_data_ext[15],the_data_ext[15],the_data_ext[
    15],the_data_ext[15],io_rd_w_data_lo_lo_lo_2}; // @[Mem_Stage.scala 144:110]
  wire [23:0] io_rd_w_data_lo_4 = {the_data_ext[15],the_data_ext[15],the_data_ext[15],the_data_ext[15],the_data_ext[15],
    the_data_ext[15],io_rd_w_data_lo_lo_lo_2,io_rd_w_data_lo_lo_2}; // @[Mem_Stage.scala 144:110]
  wire [15:0] io_rd_w_data_lo_5 = the_data_ext[15:0]; // @[Mem_Stage.scala 144:132]
  wire [63:0] _io_rd_w_data_T_82 = {the_data_ext[15],the_data_ext[15],the_data_ext[15],the_data_ext[15],the_data_ext[15]
    ,the_data_ext[15],io_rd_w_data_lo_lo_lo_2,io_rd_w_data_lo_lo_2,io_rd_w_data_lo_4,io_rd_w_data_lo_5}; // @[Cat.scala 30:58]
  wire [63:0] _io_rd_w_data_T_83 = {48'h0,io_rd_w_data_lo_5}; // @[Cat.scala 30:58]
  wire [63:0] _GEN_19 = io_mem_ext ? _io_rd_w_data_T_82 : _io_rd_w_data_T_83; // @[Mem_Stage.scala 144:45 Mem_Stage.scala 144:60 Mem_Stage.scala 145:60]
  wire [6:0] io_rd_w_data_lo_lo_lo_4 = {the_data_ext[7],the_data_ext[7],the_data_ext[7],the_data_ext[7],the_data_ext[7],
    the_data_ext[7],the_data_ext[7]}; // @[Mem_Stage.scala 174:109]
  wire [13:0] io_rd_w_data_lo_lo_4 = {the_data_ext[7],the_data_ext[7],the_data_ext[7],the_data_ext[7],the_data_ext[7],
    the_data_ext[7],the_data_ext[7],io_rd_w_data_lo_lo_lo_4}; // @[Mem_Stage.scala 174:109]
  wire [27:0] io_rd_w_data_lo_8 = {the_data_ext[7],the_data_ext[7],the_data_ext[7],the_data_ext[7],the_data_ext[7],
    the_data_ext[7],the_data_ext[7],io_rd_w_data_lo_lo_lo_4,io_rd_w_data_lo_lo_4}; // @[Mem_Stage.scala 174:109]
  wire [55:0] io_rd_w_data_hi_9 = {the_data_ext[7],the_data_ext[7],the_data_ext[7],the_data_ext[7],the_data_ext[7],
    the_data_ext[7],the_data_ext[7],io_rd_w_data_lo_lo_lo_4,io_rd_w_data_lo_lo_4,io_rd_w_data_lo_8}; // @[Mem_Stage.scala 174:109]
  wire [7:0] io_rd_w_data_lo_9 = the_data_ext[7:0]; // @[Mem_Stage.scala 174:132]
  wire [63:0] _io_rd_w_data_T_140 = {io_rd_w_data_hi_9,io_rd_w_data_lo_9}; // @[Cat.scala 30:58]
  wire [63:0] _io_rd_w_data_T_141 = {56'h0,io_rd_w_data_lo_9}; // @[Cat.scala 30:58]
  wire [63:0] _GEN_27 = io_mem_ext ? _io_rd_w_data_T_140 : _io_rd_w_data_T_141; // @[Mem_Stage.scala 174:45 Mem_Stage.scala 174:60 Mem_Stage.scala 175:60]
  wire [63:0] _GEN_29 = _T_5 ? _GEN_19 : _GEN_27; // @[Mem_Stage.scala 129:45]
  wire [63:0] _GEN_31 = _T_2 ? _GEN_15 : _GEN_29; // @[Mem_Stage.scala 118:45]
  wire [63:0] _GEN_33 = _T_1 ? the_data_ext : _GEN_31; // @[Mem_Stage.scala 113:45 Mem_Stage.scala 115:33]
  wire [63:0] _GEN_35 = io_inst_type[1] ? io_rd_data : 64'h0; // @[Mem_Stage.scala 107:40 Mem_Stage.scala 109:29 Mem_Stage.scala 180:29]
  wire [63:0] _GEN_38 = io_inst_type[1] ? _GEN_33 : io_rd_data; // @[Mem_Stage.scala 107:40 Mem_Stage.scala 181:29]
  wire [95:0] _GEN_78 = {io_save_data, 32'h0}; // @[Mem_Stage.scala 201:53]
  wire [126:0] _io_mem_w_data_T = {{31'd0}, _GEN_78}; // @[Mem_Stage.scala 201:53]
  wire [126:0] _GEN_39 = io_rd_data[2] ? _io_mem_w_data_T : {{63'd0}, io_save_data}; // @[Mem_Stage.scala 200:43 Mem_Stage.scala 201:37 Mem_Stage.scala 203:37]
  wire [79:0] _GEN_79 = {io_save_data, 16'h0}; // @[Mem_Stage.scala 211:53]
  wire [94:0] _io_mem_w_data_T_1 = {{15'd0}, _GEN_79}; // @[Mem_Stage.scala 211:53]
  wire [111:0] _GEN_81 = {io_save_data, 48'h0}; // @[Mem_Stage.scala 217:53]
  wire [126:0] _io_mem_w_data_T_3 = {{15'd0}, _GEN_81}; // @[Mem_Stage.scala 217:53]
  wire [126:0] _GEN_40 = _T_11 ? _io_mem_w_data_T_3 : {{63'd0}, io_save_data}; // @[Mem_Stage.scala 216:51 Mem_Stage.scala 217:37 Mem_Stage.scala 220:37]
  wire [126:0] _GEN_41 = _T_9 ? _io_mem_w_data_T : _GEN_40; // @[Mem_Stage.scala 213:51 Mem_Stage.scala 214:37]
  wire [126:0] _GEN_42 = _T_7 ? {{32'd0}, _io_mem_w_data_T_1} : _GEN_41; // @[Mem_Stage.scala 210:46 Mem_Stage.scala 211:37]
  wire [71:0] _GEN_82 = {io_save_data, 8'h0}; // @[Mem_Stage.scala 226:53]
  wire [78:0] _io_mem_w_data_T_4 = {{7'd0}, _GEN_82}; // @[Mem_Stage.scala 226:53]
  wire [87:0] _GEN_84 = {io_save_data, 24'h0}; // @[Mem_Stage.scala 232:53]
  wire [94:0] _io_mem_w_data_T_6 = {{7'd0}, _GEN_84}; // @[Mem_Stage.scala 232:53]
  wire [103:0] _GEN_86 = {io_save_data, 40'h0}; // @[Mem_Stage.scala 238:53]
  wire [126:0] _io_mem_w_data_T_8 = {{23'd0}, _GEN_86}; // @[Mem_Stage.scala 238:53]
  wire [119:0] _GEN_88 = {io_save_data, 56'h0}; // @[Mem_Stage.scala 244:53]
  wire [126:0] _io_mem_w_data_T_10 = {{7'd0}, _GEN_88}; // @[Mem_Stage.scala 244:53]
  wire [126:0] _GEN_43 = _T_25 ? _io_mem_w_data_T_10 : {{63'd0}, io_save_data}; // @[Mem_Stage.scala 243:51 Mem_Stage.scala 244:37 Mem_Stage.scala 247:37]
  wire [126:0] _GEN_44 = _T_23 ? _io_mem_w_data_T_3 : _GEN_43; // @[Mem_Stage.scala 240:51 Mem_Stage.scala 241:37]
  wire [126:0] _GEN_45 = _T_21 ? _io_mem_w_data_T_8 : _GEN_44; // @[Mem_Stage.scala 237:51 Mem_Stage.scala 238:37]
  wire [126:0] _GEN_46 = _T_19 ? _io_mem_w_data_T : _GEN_45; // @[Mem_Stage.scala 234:51 Mem_Stage.scala 235:37]
  wire [126:0] _GEN_47 = _T_17 ? {{32'd0}, _io_mem_w_data_T_6} : _GEN_46; // @[Mem_Stage.scala 231:51 Mem_Stage.scala 232:37]
  wire [126:0] _GEN_48 = _T_15 ? {{32'd0}, _io_mem_w_data_T_1} : _GEN_47; // @[Mem_Stage.scala 228:51 Mem_Stage.scala 229:37]
  wire [126:0] _GEN_49 = _T_13 ? {{48'd0}, _io_mem_w_data_T_4} : _GEN_48; // @[Mem_Stage.scala 225:46 Mem_Stage.scala 226:37]
  wire [126:0] _GEN_50 = _T_5 ? _GEN_42 : _GEN_49; // @[Mem_Stage.scala 208:45]
  wire [126:0] _GEN_51 = _T_2 ? _GEN_39 : _GEN_50; // @[Mem_Stage.scala 199:45]
  wire [126:0] _GEN_52 = _T_1 ? {{63'd0}, io_save_data} : _GEN_51; // @[Mem_Stage.scala 195:45 Mem_Stage.scala 196:33]
  wire [63:0] _GEN_54 = io_inst_type[0] ? io_rd_data : 64'h0; // @[Mem_Stage.scala 189:40 Mem_Stage.scala 191:29 Mem_Stage.scala 252:29]
  wire [63:0] _GEN_55 = io_inst_type[0] ? the_mask : 64'h0; // @[Mem_Stage.scala 189:40 Mem_Stage.scala 192:29 Mem_Stage.scala 254:29]
  wire [126:0] _GEN_56 = io_inst_type[0] ? _GEN_52 : 127'h0; // @[Mem_Stage.scala 189:40 Mem_Stage.scala 253:29]
  wire [126:0] _GEN_61 = reset ? 127'h0 : _GEN_56; // @[Mem_Stage.scala 33:24 Mem_Stage.scala 38:25]
  assign io_mem_r_ena = reset ? 1'h0 : io_inst_type[1]; // @[Mem_Stage.scala 33:24 Mem_Stage.scala 34:25]
  assign io_mem_r_addr = reset ? 64'h0 : _GEN_35; // @[Mem_Stage.scala 33:24 Mem_Stage.scala 35:25]
  assign io_mem_w_ena = reset ? 1'h0 : io_inst_type[0]; // @[Mem_Stage.scala 33:24 Mem_Stage.scala 36:25]
  assign io_mem_w_addr = reset ? 64'h0 : _GEN_54; // @[Mem_Stage.scala 33:24 Mem_Stage.scala 37:25]
  assign io_mem_w_data = _GEN_61[63:0];
  assign io_mem_w_mask = reset ? 64'h0 : _GEN_55; // @[Mem_Stage.scala 33:24 Mem_Stage.scala 39:25]
  assign io_rd_w_data = reset ? 64'h0 : _GEN_38; // @[Mem_Stage.scala 33:24 Mem_Stage.scala 40:25]
endmodule

所以您写的这个读没有用到掩码,是因为在总线中利用掩码产生了正确的读数据,这样理解对吗?

我想请问
1.为什么ram读数据时地址要减去64'h0000_0000_8000_0000
2.ram的代码中读数据时不需要考虑是lb,lh,lw等等的这些吗,我看是直接读64位的

  1. 因为RAM中的数据是加载的objdump从elf可执行文件中生成的bin文件,可执行文件中从虚拟地址64'h0000_0000_8000_0000开始重定位,分配地址,而bin文件中将指令和数据从0开始放置。举个例子,int a = b[0]假设需要lw指令将b[0]的数据从内存中加载到寄存器里面,当我们生成可执行文件后,在链接的时候会进行重定位,根据规范,编译器会假设这个程序放置在从虚拟地址64'h0000_0000_8000_0000开始的一段空间,然后我们根据b[0]在数据段中的偏移和64‘h8000_0000这两个数可以计算b[0]的虚拟地址,最后填写到lw指令中,当你自己写的CPU运行到该指令时,会很自然地想要读取RAM这个地址(大于0x8000_0000)的数据,本来按照正常情况,这个地址确实应该有你想要的数据,但是我们bin文件是假设数据从0开始放置的,所以就需要你转换一下,减去0x8000_0000。
  2. 所以需要根据lb、lh和lw生成对应的控制信号——mask掩码来选取64位数据中自己想要的数据,当你编写Axi总线的时候,也要提供这样一个信号的。

Originally posted by @codefuturedalao in #9 (comment)

【提问】 如何高效的识别指令?

我看了下 《riscv-spec.pdf》 RV64I 的指令,里面的 opcode 有 15 种,但是每种 opcode 下,又有可能同时存在 I 指令 或者 R 指令。

RISCV 指令为什么不是一种 opcode 对应一个指令呢?

MacOS 经验分享

Fix Issues on MacOS

Verilator

Fail to compile from source code in GitHub Repositary. Use brew install verilator to install 4.200 version, slightly behind the latest 4.204 version, but also works well.

GTKWave

Avoid Security Check

Add --no-quarantine to avoid security protection on MacOS. Use brew install gtkwave --no-quarantine to install GTKWave.

Strange Error

/usr/local/bin/gtkwave: line 122: test: too many arguments
/usr/local/bin/gtkwave: line 149: test: ==: unary operator expected
find: /share/locale: No such file or directory
/usr/local/bin/gtkwave: line 215: /usr/local/bin/../../../Contents/Resources/bin/: No such file or directory
/usr/local/bin/gtkwave: line 215: exec: /usr/local/bin/../../../Contents/Resources/bin/: cannot execute: No such file or directory

GTKWave on macOS gives a good solution.

TL;DR

Use Perl’s package manager to install Switch and run binary with full path.

cpan install Switch
perl -V:'installsitelib'
/Applications/gtkwave.app/Contents/Resources/bin/gtkwave

Adjust Build Script

In build.sh, some commands only works in Linux.

  1. There is no -f option with readlink in MacOS. Use SHELL_PATH=$(pwd) to show current directory.

  2. There is no hostnamectl command in MacOX. Use hostname instead.

  3. Substitute gtkwave with full path /Applications/gtkwave.app/Contents/Resources/bin/gtkwave

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.