tiny riscv - 自动化测试+ 总线-多路选择器+优先级仲裁

367 阅读5分钟

安装pthon环境

anaconda

pycharm

安装iverilog

v11 - x64

BIN文件是一种二进制文件。

bin文件转化为 txt文件。

测试

python: compile_and_sim.py 编译.v和顶层,编译tb文件。 tb文件里加载指令。

1 python compile_and_sim.py 指令rv32ui - p -addi.bin

测试指令的bin文件。 4个字节存一条指令

测试一条指令

编译rtl 运行仿真。

2 python test_all.py

测试所有指令

获取路径下所由bin文件

调用compile_and_sim.py编译每一个bin文件。

跳转和流水线暂停

跳转就是改变PC寄存器的值。又因为跳转与否需要在执行阶段才知道,所以当需要跳转时,则需要暂停流水线(正确来说是冲刷流水线。流水线是不可以暂停的,除非时钟不跑了)。

在执行阶段,当判断需要发生跳转时,发出跳转信号和跳转地址给ctrl(ctrl.v)模块。ctrl模块判断跳转信号有效后会给pc_reg、if_id和id_ex模块发出流水线暂停信号,并且还会给pc_reg模块发出跳转地址。在时钟上升沿到来时,if_id和id_ex模块如果检测到流水线暂停信号有效则送出NOP指令,从而使得整条流水线(译码阶段、执行阶段)流淌的都是NOP指令,已经取出的指令就会无效,这就是流水线冲刷机制。

可知,暂停信号来自多个模块。对于跳转(跳转包含暂停流水线操作),是要冲刷整条流水线的,因为跳转后流水线上其他阶段的其他操作是无效的。对于其他模块的暂停信号,一种最简单的设计就是也冲刷整条流水线,但是这样的话MCU的效率就会低一些。

总线

设想一下一个没有总线的SOC,处理器核与外设之间的连接是怎样的。可能会如下图所示:

image.png

处理器核core直接与每个外设进行交互。假设一个外设有一条地址总线和一条数据总线,总共有N个外设,那么处理器核就有N条地址总线和N条数据总线,而且每增加一个外设就要修改(改动还不小)core的代码。有了总线之后(见本章开头的图2_1),处理器核只需要一条地址总线和一条数据总线,大大简化了处理器核与外设之间的连接。

目前已经有不少成熟、标准的总线,比如AMBA、wishbone、AXI等。设计CPU时大可以直接使用其中某一种,以节省开发时间。但是为了追求简单,tinyriscv并没有使用这些总线,而是自主设计了一种名为RIB(RISC-V Internal Bus)的总线。RIB总线支持多主多从连接,但是同一时刻只支持一主一从通信。

RIB总线上的各个主设备之间采用固定优先级仲裁机制。

RIB总线本质上是一个多路选择器,从多个主设备中选择其中一个来访问对应的从设备。

RIB总线地址的最高4位决定要访问的是哪一个从设备,因此最多支持16个从设备。

仲裁方式采用的类似状态机的方式来实现,

  // 主设备请求信号
    assign req = {m2_req_i, m1_req_i, m0_req_i};


    // 授权主设备切换
    always @ (posedge clk) begin
        if (rst == `RstEnable) begin
            grant <= grant1;
        end else begin
            grant <= next_grant;
        end
    end

    // 仲裁逻辑
    // 固定优先级仲裁机制
    // 优先级由高到低:主设备0,主设备2,主设备1
    always @ (*) begin
        if (rst == `RstEnable) begin
            next_grant = grant1;
            hold_flag_o = `HoldDisable;
        end else begin
            case (grant)
                grant0: begin
                    if (req[0]) begin
                        next_grant = grant0;
                        hold_flag_o = `HoldEnable;
                    end else if (req[2]) begin
                        next_grant = grant2;
                        hold_flag_o = `HoldEnable;
                    end else begin
                        next_grant = grant1;
                        hold_flag_o = `HoldDisable;
                    end
                end
                grant1: begin
                    if (req[0]) begin
                        next_grant = grant0;
                        hold_flag_o = `HoldEnable;
                    end else if (req[2]) begin
                        next_grant = grant2;
                        hold_flag_o = `HoldEnable;
                    end else begin
                        next_grant = grant1;
                        hold_flag_o = `HoldDisable;
                    end
                end
                grant2: begin
                    if (req[0]) begin
                        next_grant = grant0;
                        hold_flag_o = `HoldEnable;
                    end else if (req[2]) begin
                        next_grant = grant2;
                        hold_flag_o = `HoldEnable;
                    end else begin
                        next_grant = grant1;
                        hold_flag_o = `HoldDisable;
                    end
                end
                default: begin
                    next_grant = grant1;
                    hold_flag_o = `HoldDisable;
                end
            endcase
        end
    end

中断

中断(中断返回)本质上也是一种跳转,只不过还需要附加一些读写CSR寄存器的操作。

RISC-V中断分为两种类型,一种是同步中断,即ECALL、EBREAK等指令所产生的中断,另一种是异步中断,即GPIO、UART等外设产生的中断。

对于中断模块设计,一种简单的方法就是当检测到中断(中断返回)信号时,先暂停整条流水线,设置跳转地址为中断入口地址,

然后读、写必要的CSR寄存器(mstatus、mepc、mcause等),等读写完这些CSR寄存器后取消流水线暂停,这样处理器就可以从中断入口地址开始取指,进入中断服务程序。

RTL仿真验证

写完处理器代码后,怎么证明所写的处理器是能正确执行指令的呢?这时就需要写testbench来测试了。其实在写代码的时候就应该在头脑里进行仿真。这里并没有使用ModelSim这些软件进行仿真,而是使用了一个轻量级的iverilog和vvp工具。

1 在写testbench文件时,有两点需要注意的,第一点就是在testbench文件里加上读指令文件的操作:

initial begin
    $readmemh ("inst.data", tinyriscv_soc_top_0.u_rom._rom);
end

第2行代码的作用就是将inst.data文件读入到rom模块里,inst.data里面的内容就是一条条指令,这样处理器开始执行时就可以从rom里取到指令。

2 第二点就是,在仿真期间将仿真波形dump出到某一个文件里:

initial begin
    $dumpfile("tinyriscv_soc_tb.vcd");
    $dumpvars(0, tinyriscv_soc_tb);
end

这样仿真波形就会被dump出到tinyriscv_soc_tb.vcd文件,使用gtkwave工具就可以查看波形了。