有人预言,RISC-V或将是继Intel和Arm之后的第三大主流处理器体系。欢迎访问全球首家只专注于RISC-V单片机行业应用的中文网站
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
本帖最后由 皋陶 于 2020-8-26 19:05 编辑
从零开始学RISC-V之初探IFU
文章目录
背景介绍
CPU内核设计几大块,主要包含取指单元(IFU),执行单元(EXU),访存单元(LSU),异常与中断(INT)以及调试单元(DBG)。
一条指令的从取指单元开始,经过执行单元完成具体运算,按指令类型确定是否会访问内部或外部存储器,异常和中断负责处理特殊的情况,调试则是软件与硬件的窗口。
每个模块的设计原则和侧重点都不一样。
本节将从取指单元开始,介绍一条最简单的指令的运行过程。
由于项目定位为一个入门级学习型项目,因此可能不会涉及到同步异常,不会涉及到中断以及调试单元等复杂模块。
让IFU开始读数据吧
终于开始IFU的设计了,这也就意味着你正式踏入CPU Core设计的领域了。不得不说,这是一个很大的进步。
IFU全称叫Instruction Fetch Unit,即指令获取单元。
顾名思义,该单元的主要功能是从IRAM中按需获取指令,并将该指令发送给后续单元,比如说执行单元,进行处理。
按照不同的应用场景介绍其工作原理,列述如下:
- if (a == b)
- branch_a
- else
- branch_b
复制代码
上述实例中,对于不同的a和b的值,会执行不同的语句体。
一般来讲,当IFU取到if语句对应的指令(这是一条跳转指令)时,它会根据当前的信息预测到底是取branch_a代表的指令,还是branch_b代表的指令。
不论怎样,它都会有一个预测的结果P。
但是if语句的执行结果是依赖a和b的值的,IFU并不知道这两个值的比较结果(因为指令的具体执行有专门的执行单元负责),因此有可能IFU预测的是错的。
比如IFU预测的是后面应该执行branch_a,但是实际执行结果却是下一条指令应该执行branch_b,这样,IFU提前取到的branch_a就是一个错误的不该在此时被取到的指令。
在错误的时间遇到的人,注定是要放弃的,希望大家明白。
这个过程,就是大名鼎鼎的分支预测机制。分支预测算法对处理器性能有很大的影响,毕竟频繁的预测错误-丢弃的结果就是极大浪费时间,因此属于CPU设计的重难点之一。
总的来说,IFU的功能,就是保证在正确的时间取到正确的数据。
作为刚刚一脚踏入IFU设计的人来讲,我们暂时将这些复杂的分支预测机制放一放,先把IFU最最最基本的功能实现吧。
那就是:你是一个成熟的IFU了,应该要知道从指定的地址去获取数据了。
一个“成熟”的IFU的实现代码如下:
- // 成熟的IFU
- module xf100_ifu (
- // 与下一单元(EXU)的接口,本节暂时先不管
- output o_ifu_exu_valid,
- input o_ifu_exu_ready,
- output [31:0] o_ifu_exu_pc,
- output [31:0] o_ifu_exu_instr,
- // 与IRAM单元的接口
- output ifu2ram_cs ,
- output ifu2ram_wen ,
- output [14:0] ifu2ram_addr,
- input [31:0] ifu2ram_din ,
- input clk ,
- input rst_n
- );
- wire ifu_exu_valid;
- xf100_gnrl_dfflr #(1) ifu_valid_dfflr (clk, rst_n, 1'b1, 1'b1, ifu_exu_valid);
- // set pc
- `define RST_PC 32'h80000000 // 复位PC,当CPU上电时,IFU会从此地址获取到第一条指令。
- reg [31:0] ifu_pc;
- wire ifu_upena = o_ifu_exu_valid & o_ifu_exu_ready;
- // 就目前来讲,IFU会无条件自动获取相邻的下一条指令,所以IFU地址会一直+4
- wire [31:0] ifu_pc_nxt = ifu_pc + 32'h4;
- always @ (posedge clk or negedge rst_n) begin
- if (rst_n == 1'b0) begin
- ifu_pc <= `RST_PC;
- end else if(ifu_upena) begin
- ifu_pc <= ifu_pc_nxt;
- end
- end
- assign o_ifu_exu_valid = ifu_exu_valid; // for now we just fix the ifu2exu valid to 1, assuming that the instr data from sram is always valid.
- assign o_ifu_exu_pc = ifu_pc;
- assign o_ifu_exu_instr = ifu2ram_din;
- // set the ifu req to instr sram.
- assign ifu2ram_cs = 1'b1 & o_ifu_exu_ready;
- assign ifu2ram_wen = 1'b0; //IFU总是读IRAM,因此写使能一直拉低。
- assign ifu2ram_addr = ifu_pc; //每次IFU取指令的地址
- endmodule
复制代码
以上就是IFU初探的所有设计。代码中涉及到了一种非常有效的控制机制,valid-ready握手机制。
这一机制严格把控每一条指令的执行,每一个状态的翻转,从逻辑思路上保证CPU运行的严谨性,需要好好体会。
后续文章会逐步深入解析该握手机制的运行原理。
将上述IFU和IRAM,按照预先设计好的逻辑层次例化,就完成了本节内容所预定的任务。
在我们的xf100中,它是这样的:
- tb_top是最顶层文件,它会例化xf100_soc
- xf100_soc包含xf100_core和xf100_inst_ram两个模块,分别对于cpu core和IRAM。
- xf100_core包含xf100_ifu和其他模块(暂时用不到)。
例化完成之后,就可以执行加载与仿真了。
加载与仿真
- 首先在tb/tb_top.v文件里添加对IRAM的上电初始化操作。
- // initial instruction ram.
- `define INSTR_RAM u_xf100_soc.inst_ram.u_gnrl_inst_ram
- integer i;
- reg [7:0] instr_mem [0:16384*4-1];
- initial begin
- $readmemh("../../riscv-tools/riscv-tests/isa/generated/rv32ui-p-add.verilog", instr_mem);
- for (i=0;i<16384*4;i=i+1) begin
- `INSTR_RAM.ram_r[i] = instr_mem[i];
- end
- end
复制代码 修改vsim/Makefile中的install部分,让新建的.v代码文件能被编译工具识别。
需要注意的是此处的路径千万不能出错,否则会在编译过程中出现模块未定义的错误。
- install:
- mkdir -p ${SIM_DIR}/install/tb
- mkdir -p ${SIM_DIR}/install/rtl/${CORE}
- cp ${SIM_DIR}/../tb/tb_top.v ${SIM_DIR}/install/tb/ -rf
- cp ${SIM_DIR}/../../design ${SIM_DIR}/install/rtl/${CORE}/ -rf
复制代码
- 其余改动,你可以根据自身使用工具的情况自行修改。再次提醒注意各个文件的路径,包括存储路径,或者编译过程中使用的其他路径。一般而言,出现模块未定基本上都是这类原因。
上述改动完成之后,就可以运行仿真并查看波形了。实例波形图如下:
从图中可以看出,Addr(注,此处只使用了15bit),按照步长4依次递增。
同时从IRAM中返回于地址对应的指令数据。
该数据可以与dump文件,也就是程序的反汇编文件对比,用于后续代码的调试。
下一步,我们将进入指令的执行模块,看看第一条加法指令到底是如何被执行的。
总结一下
- IFU主要功能是从指定地址获取指令数据,并自行预测下一次读取指令的地址。有可能会预测错误,所以会产生一定的代价,IFU的设计目标之一是减少预测的准确率,并同时降低错误预测代价。
AM就是一个存储指令的只读存储器,该存储器数据在读请求信号有效的当拍就可以输出。大小可配置,符合要求即可。 - IFU主要功能是从指定地址获取指令数据,并自行预测下一次读取指令的地址。有可能会预测错误,所以会产生一定的代价,IFU的设计目标之一是减少预测的准确率,并同时降低错误预测代价。
- valid-ready握手机制在此处刚刚露了脸,但是由于仅仅只有IFU,因此其作用并未显现,但该机制是保证CPU功能的核心,需要注意。
本篇完,感谢关注:RISC-V单片机中文网
|