前提
RISC-V实践部分我们使用的是三级流水,包含了取指、译码和执行三个步骤。
tinyriscv的流水线结构图:
1.取指
tinyriscv整体框架图:
对比tinyriscv的流水线结构图和tinyriscv整体框架图,取指实际涉及到的的只有pc_reg.v(PC寄存器)模块、rom.v(ROM)模块、if_id.v(流水线寄存器)模块,rib.v(RIB)模块是实行模块连接功能的,ctrl.v(控制单元)模块是实行产生暂停流水线、跳转等控制信号。
tinyriscv并没有具体的取指模块和代码。PC寄存器模块的输出pc_o会连接到外设rom模块的地址输入,又由于rom的读取是组合逻辑,因此每一个时钟上升沿到来之前(时序是满足要求的),从rom输出的指令已经稳定在if_id模块的输入,当时钟上升沿到来时指令就会输出到id模块。
取到的指令和指令地址会输入到if_id模块(if_id.v),if_id模块是一个时序电路,作用是将输入的信号打一拍后再输出到译码(id.v)模块。
1.pc_reg.v(时序逻辑电路)
1.1功能
实现复位、跳转、暂停以及寻指
1.2输入输出信号
序号 信号名 输入/输出 位宽(bits) 说明 1 clk 输入 1 时钟输入信号 2 rst
输入 1 复位输入信号 3 jump_flag_i
输入 1 跳转标志,来源于ctrl.v的jump_flag 4 jump_addr_i
输入 32 跳转地址,跳转到该地址,来源于ctrl.v的jump_addr_o 5 hold_flag_i
输入 3 暂停标志,PC寄存器的值保持不变,来源于ctrl.v的hold_flag_o 6 jtag_reset_flag_i
输入 1 复位标志,设置为复位后的值,用在使用jtag下载时的复位reset_req_o 7 pc_o
输出 32
PC寄存器值,从该处取,输出到if_id.v的inst_addr_i
1.3复位
if (rst == `RstEnable || jtag_reset_flag_i == 1'b1) begin
pc_o <= `CpuResetAddr;
注释:这个好像没啥讲的,就是复位信号来了就复位。
1.4跳转
end else if (jump_flag_i == `JumpEnable) begin
pc_o <= jump_addr_i;
注释:跳转信号来了就跳转
1.5暂停
end else if (hold_flag_i >= `Hold_Pc) begin
pc_o <= pc_o;
注释:暂停信号来了保持不变
1.6寻址(地址加4)
end else begin
pc_o <= pc_o + 4'h4;
注释:使用jal指令将下一条指令PC + 4 的地址保存到目标寄存器中,但这里我倾向于是使用了加法器,涉及到RISC-V寻址模式,一个字节是8bit数据,RV32为32bit数据也就是4个字节,所以读下一个32bit的指令就是 + 4字节。
2. if_id.v(时序逻辑电路)
2.1功能
指令向译码模块传递
2.2输入输出
序号 信号名 输入/输出 位宽(bits) 说明 1 clk
输入 1 时钟输入信号 2 rst
输入 1 复位输入信号 3 inst_i 输入 32 输入指令内容,来源于rib.v的m0_data_o
4 inst_addr_i
输入 32 输入指令地址,来源于pc_reg.v的pc_o
5 hold_flag_i
输入 3 流水线暂停标志,来源 ctrl.v的hold_flag_o
6 int_flag_i
输入 8 外设中断输入信号,来源连续赋值语句assign int_flag = {7'h0, timer0_int},timer0_int来源于timer.v的int_sig_o
7 int_flag_o
输出 8 外设中断输出信号,输出到clint.v的int_flag_i
8 inst_o
输出 32 输出指令内容,输出到id.v的inst_i 9 inst_addr_o
输出 32 输出指令地址,输出到id.v的inst_addr_i
2.3 代码注释
gen_pipe_dff #(32) inst_ff(clk, rst, hold_en, `INST_NOP, inst_i, inst);
gen_pipe_dff #(32) inst_addr_ff(clk, rst, hold_en, `ZeroWord, inst_addr_i, inst_addr);
gen_pipe_dff #(8) inst_ff(clk, rst, hold_en, `INT_NONE, int_flag_i, int_flag);
注释:这三段话都是D触发器的例化,实现将输入的信号打一拍后再输出到译码模块(id.v)的功能。
3.rom.v(组合逻辑电路)
3.1功能
存储指令码,格局PC寄存器的pc_o的值输出指令码
3.2输入输出
序号 | 信号名 | 输入/输出 | 位宽(bits) | 说明 |
1 | clk | 输入 | 1 | 时钟输入 |
2 | rst | 输入 | 1 | 复位输入 |
3 | we_i | 输入 | 1 | 写使能,来自rib.v的s0_we_o |
4 | addr_i | 输入 | 32 | 地址,来自rib.v的s0_addr_o |
5 | data_i | 输入 | 32 | 输入的指令码,来自rib.v的s0_data_o |
6 | data_o | 输出 | 32 | 输出的指令码,输出到rib.v的s0_data_i |
3.3定义存储空间
reg[`MemBus] _rom[0:`RomNum - 1];
注释:定义一个32*4096的二维数组(我是理解为4096个32位宽的一个rom),用于做存储数据空间,一条指令32bit,也就是说这个rom可以存4096条指令。
3.4补充知识
RISC-V指令中的算术运算只作用在寄存器中,而数据传输指令就是为了在RISC-V的内存和寄存器间进行数据传输存在的。想访问内存中的字指令就需要提供内存地址。
注释:rom可以存4096个指令那它就有4096个地址
3.5写数据
if (we_i == `WriteEnable) begin
_rom[addr_i[31:2]] <= data_i;
end
注释:写使能就将指令码写入rom
3.6读数据
if (rst == `RstEnable) begin
data_o = `ZeroWord;
end else begin
data_o = _rom[addr_i[31:2]];
end
注释:两种读方式,一种是当复位信号来时写出32'h0,另一种就是正常的读出指令码
3.7问题解答
_rom[addr_i[31:2]]
addr_i为什么是[31:2]呢?
我的理解是因为它在pc_reg中pc_o的值 + 4后传入到rib.v进行处理后得到一个pc指针值是从addr_i[2]开始的