当完成整个RISC-V 内核模块后, 需要ITCM 模块 读取软件编写的代码的机器码, 按照取指, 译码,执行,访存,写回等逻辑来执行当前的机器码, 这5个部分并不是每次都会发送,有时也会提前返回,比如 addi, 没有访存外部存储器。在版本是RISC-V V3.00版和之后的版本中, 使用的是流水线方式来处理cpu 指令。我们采用的是3级流水线方式。
相关参考文章:
RISC-V教学教案
流水线框图:

图中蓝色方块都是D触发器锁存。 代表流水线的级别。
第一级流水线:
由于我们使用dual port memory 作为ITCM (指令存储器),所以每次给出地址之后, 需要等待一个时钟周期, 然后才能得到相应的指令数据。所以取指的过程需要2个时钟周期。 但这个部分依然属于第一级流水线范畴(取指)。如果使用reg 堆积出来一个ram, 将会节省一个时钟周期, 缺点是占用大量的fpga 系统资源。
第二级流水线:
处理整个的译码,执行阶段。包括decoder (译码), ALU(执行),branch (分支指令执行),CSR (寄存器相关的执行)
第三级流水线:
写回 32个通用寄存器(regfile)。如果是LOAD/STORE 相关的指令,处理外设相关的操作。这个部分会被连接到总线上,包括gpio ,timer, Uart, 以及之后risc-v cpu 扩展所需要的外设。
cpu 取指模块流水线:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
`timescale 1ns / 1ps module fii_instr_rib # ( parameter [31:0] DBG_BASEADDR = 32'h0000_0800, parameter [31:0] TMR_BASEADDR = 32'h0200_0000, parameter [31:0] PLIC_BASEADDR = 32'h0c00_0000, parameter [31:0] CPU_BASEADDR = 32'h8000_0000, parameter [31:0] MEM_BASEADDR = 32'h9000_0000, parameter [31:0] UART_BASEADDR = 32'he000_0000, parameter [31:0] PWM_BASEADDR = 32'hf000_2000, parameter [31:0] GPIO_BASEADDR = 32'hf000_0000 ) ( input sys_clk, output reg [31:0] o_curr_pc, output [31:0] o_curr_instr, //=============================================================================== input i_pc_chg_flag, input i_ir_hold_flag, input [31:0] i_exe_next_PC, output o_CPU_load_vld, //=============================================================================== input i_ext_irq, input i_sft_irq, input i_tmr_irq, input o_glb_irq, // mstatus[3]; enable all Machine Mode interrupt input [31:0] i_dbg_entry_adr, // jtag debug entry address input i_dbg_entry_set, // jtag debug entry address enable ( one clock ) input [31:0] i_dpc_r, input [31:0] i_dbg_instr, input dret, input [31:0] i_mepc, input [31:0] i_vect_pc, // mtvec regitesr content output o_irq_src, output o_exp_src, input mret, input dbg_start, //=============================================================================== input itcm_wea, input [31:0] itcm_addra, input [31:0] itcm_dina, //=============================================================================== input [31:0] i_rib_saddr, input [31:0] i_rib_sdin, output [31:0] o_rib_sdout, input i_rib_svalid, output [1:0] o_rib_sready, input [3:0] i_rib_swe, input i_rib_srd, input i_rib_sop, //=============================================================================== // master bus signal input i_rib_mvalid, input [1:0] i_rib_mready, //=============================================================================== input i_cpu_reset, input rst_n ); //=============================================================================== // cpu master bus operation // long instruction : dec_instr should be set as 'INST_NOP, during long instruction. reg rib_delay_op = 0; always @ ( posedge sys_clk or negedge rst_n) if(!rst_n) rib_delay_op <= 0; //else if ( o_rib_mvalid & ~i_rib_mready[0] ) rib_delay_op <= 1; else if ( i_rib_mvalid ^ i_rib_mready[0] ) rib_delay_op <= 1; else rib_delay_op <= 0; wire bus_hsk_ok = i_rib_mvalid & i_rib_mready[0]; // long period load & store operation; normal operation long_ir_flag is 0 wire long_ir_flag = ( i_rib_mvalid & (~i_rib_mready[0]) ) | rib_delay_op; // long period instruction pos edge; normal operation long_ir_cs is 0 wire long_ir_cs = ( i_rib_mvalid & (~i_rib_mready[0]) ) & i_rib_sop; //=============================================================================== wire o_CPU_load_cs = ( i_rib_saddr[ 31: 16 ] == CPU_BASEADDR[ 31: 16 ] ) ? i_rib_sop : 1'b0; /* ls_r is used for 0x8000_0000 DATA load instruction, becasue 0x8000_0000 is dual port ram , it will be delay 2 clock (output data form dual port ram), the ls_r[1] is signal that o_cpu_load_cs delay 2 clock */ reg [1:0] ls_r = 0; always @( posedge sys_clk or negedge rst_n) if(!rst_n) ls_r <= 0; else ls_r <= {ls_r[0], o_CPU_load_cs}; assign o_CPU_load_vld = ls_r[1]; assign o_rib_sready = {1'b0, ls_r[1]}; //=============================================================================== wire [31:0] pc_instr; wire [31:0] i_CPU_load_data = pc_instr; wire [ 4: 0 ] data_sft = {i_rib_saddr[ 1: 0 ], 3'b000}; //i_D_PC[ 1: 0 ] * 8; wire [ 31: 0 ] o_CPU_dout = i_CPU_load_data >> data_sft; assign o_rib_sdout = o_CPU_dout; //=============================================================================== reg [ 31: 0 ] instr_fch_PC = 0; //=============================================================================== /* next_addr is relevent o_cpu_load_cs, for example : current program pc is 0x8000_0004, (load instruction ,data at 0x8000_01000), next_addr will be next program pc 0x8000_0004 + 4, after load instruction finish. */ reg [31:0] next_addr = 0; always @( posedge sys_clk ) if(!rst_n | i_cpu_reset) next_addr <= CPU_BASEADDR; else if(i_rib_sop) next_addr <= instr_fch_PC; //=============================================================================== assign o_exp_src = 0; wire nop_flag; // don't generate interrupt when cpu load & flush instruction assign o_irq_src = (long_ir_flag | nop_flag) ? 1'b0 : (i_ext_irq | i_sft_irq | i_tmr_irq); wire irq_exp_flag = (o_irq_src & o_glb_irq) | o_exp_src ; // edge o_irq_src high, then o_glb_irq low //=============================================================================== reg [31:0] jump_addr = 0; always @( * ) begin if(i_dbg_entry_set) jump_addr = i_dbg_entry_adr; else if(dret) jump_addr = i_dpc_r; else if (long_ir_cs) // long address & (addr == 0x8000_xxxx) jump_addr = {i_rib_saddr[31:2], 2'b00}; else if( irq_exp_flag ) jump_addr = i_vect_pc; // mtvec regitesr content else if( mret ) jump_addr = i_mepc; // The PC before enter interrupt else if( i_ir_hold_flag ) jump_addr = o_curr_pc; // long term instruction ,pc hold at current position else jump_addr = i_exe_next_PC; end wire w_jump_flag = i_dbg_entry_set | dret | irq_exp_flag | i_pc_chg_flag | mret | long_ir_cs | i_ir_hold_flag; //=============================================================================== //stage 1 always @( posedge sys_clk ) if (( !rst_n ) | i_cpu_reset ) instr_fch_PC <= CPU_BASEADDR; else begin if (w_jump_flag) instr_fch_PC <= jump_addr; else if( ~bus_hsk_ok & rib_delay_op ) // long period load & store instr_fch_PC <= next_addr; else instr_fch_PC <= instr_fch_PC + 4; end //=============================================================================== //stage 2 always @( posedge sys_clk or negedge rst_n ) if ( !rst_n ) o_curr_pc <= CPU_BASEADDR; else o_curr_pc <= i_ir_hold_flag ? o_curr_pc : (bus_hsk_ok & rib_delay_op) ? next_addr : instr_fch_PC; //else o_curr_pc <= instr_fch_PC; //=============================================================================== /* next clock should be output a inst_nop , because dual port ram , address jump, the pipeline address (current address + 4) will be ignore, */ reg waiting_r = 0; always @ (posedge sys_clk or negedge rst_n) if(!rst_n) waiting_r <= 0; else waiting_r <= w_jump_flag; //=============================================================================== // w_dbg_pc point to 0x8000_xxxxx address , we need get data from dual port ram , // so, w_dbg_pc need 2 clock to get the data(0x8000_xxxx) wire [31:0] w_dbg_pc = instr_fch_PC; localparam [ 31: 0 ] DBG_RAM_ADDR = 32'h0000_0400; // 0x400 - 0x800 wire dbg_addr_sel = (w_dbg_pc[31:12] == DBG_BASEADDR[31:12]) & (w_dbg_pc[11:10] != 2'b00) ? 1'b1 : 1'b0; reg dbg_addr_cs = 0; always @ (posedge sys_clk or negedge rst_n) if(!rst_n) dbg_addr_cs <= 0; else dbg_addr_cs <= dbg_addr_sel; //=============================================================================== wire [31:0] instr_data_sel = dbg_addr_cs ? i_dbg_instr : pc_instr ; assign o_curr_instr = (waiting_r) ? `INST_NOP : instr_data_sel; assign nop_flag = (o_curr_instr == `INST_NOP) ? 1'b1 : 1'b0; //=============================================================================== wire instr_ena = ( instr_fch_PC[ 31: 16 ] == CPU_BASEADDR[31:16] ) ? 1'b1 : 1'b0; TDP_RAM_INSTR program_inst ( .clka ( sys_clk ), .ena ( 1'b1 ), .wea ( itcm_wea ), .addra ( itcm_addra[31:2] ), .dina ( itcm_dina ), .douta ( ), .clkb ( sys_clk ), .enb ( instr_ena ), .web ( 1'b0 ), .addrb ( instr_fch_PC[ 31: 2 ] ), //8K 32bits, 32K byte .dinb ( 32'b0 ), .doutb ( pc_instr ) ); //=============================================================================== endmodule |
端口说明:
input sys_clk, // 系统时钟
output reg [31:0] o_curr_pc, // 输出 当前的PC 值
output [31:0] o_curr_instr, // 输出 当前的 指令值
//=======================================================================
input i_pc_chg_flag, // 译码,执行模块通知 pc 值需要更改,不再是正常的 + 4
input i_ir_hold_flag, // 处理乘除法指令
input [31:0] i_exe_next_PC, // 译码,执行模块通知 下一个pc 值
output o_CPU_load_vld, // 普林斯顿架构 itcm 提供的数据有效
//=======================================================================
input i_ext_irq, // 外部中断
input i_sft_irq, // 软件中断
input i_tmr_irq, // timer中断
input o_glb_irq, // CSR 全局中断位,mstatus[3]; enable all Machine Mode interrupt
input [31:0] i_dbg_entry_adr, // jtag 调试入口地址,jtag debug entry address
input i_dbg_entry_set, // jtag 调试入口 enable, jtag debug entry address enable ( one clock )
input [31:0] w_dpc_r, // jtag 调试 pc
input [31:0] w_dbg_instr, // jtag 调试 指令
input dret, // jtag 调试中断 返回
input [31:0] i_mepc, // 中断时,存储的当前pc 值
input [31:0] i_vect_pc, // 中断时, 跳入中断的pc 值,mtvec regitesr content
output o_irq_src, // 通知 CSR 模块 中断信号线
output o_exp_src, // 通知 CSR 模块 异常信号线
input mret, // 中断返回 信号
input dbg_start, // 进入jtag 调试 信号
//=======================================================================
input itcm_wea, // ITCM 更新 写 enable
input [31:0] itcm_addra, // ITCM 更新 地址
input [31:0] itcm_dina, // ITCM 更新 数据
//=======================================================================
// riscv 内部总线 slave 总线
input [31:0] i_rib_saddr, // riscv 内部总线 地址
input [31:0] i_rib_sdin, // riscv 内部总线 写数据
output [31:0] o_rib_sdout,// riscv 内部总线 读数据
input i_rib_svalid, // riscv 内部总线 指令有效
output [1:0] o_rib_sready, // riscv 内部总线 ,外设读取的数据有效
input [3:0] i_rib_swe, // riscv 内部总线 写 enable
input i_rib_srd, // riscv 内部总线 读 信号
input i_rib_sop, // riscv 内部总线 指令操作 enable
//=======================================================================
// riscv 内部总线 master 总线,master bus signal
input i_rib_mvalid, // riscv 内部总线指令有效
input [1:0] i_rib_mready,// riscv 内部总线,外部读取的数据有效
//=======================================================================
长周期指令操作:
在长周期的操作时,需要插入空指令,保持流水线连续。
// cpu master bus operation
// long instruction : dec_instr should be set as ‘INST_NOP, during long instruction.
reg rib_delay_op = 0;
always @ ( posedge sys_clk or negedge rst_n)
if(!rst_n) rib_delay_op <= 0;
else if ( i_rib_mvalid ^ i_rib_mready[0] ) rib_delay_op <= 1;
else rib_delay_op <= 0;
wire bus_hsk_ok = i_rib_mvalid & i_rib_mready[0];
// long period load & store operation; normal operation long_ir_flag is 0
wire long_ir_flag = ( i_rib_mvalid & (~i_rib_mready[0]) ) | rib_delay_op;
// long period instruction pos edge; normal operation long_ir_cs is 0
wire long_ir_cs = ( i_rib_mvalid & (~i_rib_mready[0]) ) & i_rib_sop;
长周期指令,包括普林斯顿结构数据读取, jtag 相关指令。
普林斯顿架构下, 读取的数据延迟:
reg [1:0] ls_r = 0;
always @( posedge sys_clk or negedge rst_n)
if(!rst_n) ls_r <= 0;
else
ls_r <= {ls_r[0], o_CPU_load_cs};
assign o_CPU_load_vld = ls_r[1];
assign o_rib_sready = {1’b0, ls_r[1]};
由于使用的时双端口 memory, 需要延迟一个时钟周期,配合总线一起使用。
lb,lw,lh等load 指令的相关处理:
wire [ 4: 0 ] data_sft = {i_rib_saddr[ 1: 0 ], 3’b000}; //i_D_PC[ 1: 0 ] * 8;
wire [ 31: 0 ] o_CPU_dout = i_CPU_load_data >> data_sft;
assign o_rib_sdout = o_CPU_dout;
普林斯顿架构下,保存当前pc操作:
reg [31:0] next_addr = 0;
always @( posedge sys_clk )
if(!rst_n | i_cpu_reset) next_addr <= CPU_BASEADDR;
else if(i_rib_sop) next_addr <= instr_fch_PC;
中断源处理:
assign o_exp_src = 0;
wire nop_flag;
// don’t generate interrupt when cpu load & flush instruction
assign o_irq_src = (long_ir_flag | nop_flag) ? 1’b0 : (i_ext_irq | i_sft_irq | i_tmr_irq);
wire irq_exp_flag = (o_irq_src & o_glb_irq) | o_exp_src ; // edge w_irq_src high, then o_glb_irq low
长周期指令, 空指令等需要等待, 其他情况时,触发中断。
各种情况下的的pc 跳转:
reg [31:0] jump_addr = 0;
always @( * )
begin
if(i_dbg_entry_set)
jump_addr = i_dbg_entry_adr;
else if(dret)
jump_addr = i_dpc_r;
else if (long_ir_cs) // long address & (addr == 0x8000_xxxx)
jump_addr = {i_rib_saddr[31:2], 2’b00};
else if( irq_exp_flag )
jump_addr = i_vect_pc; // mtvec regitesr content
else if( mret )
jump_addr = i_mepc; // The PC before enter interrupt
else if( i_ir_hold_flag )
jump_addr = o_curr_pc; // long term instruction ,pc hold at current position
else jump_addr = i_exe_next_PC;
end
准备跳转的pc:
wire w_jump_flag = i_dbg_entry_set | dret | irq_exp_flag | i_pc_chg_flag | mret | long_ir_cs | i_ir_hold_flag;
always @( posedge sys_clk )
if (( !rst_n ) | i_cpu_reset ) instr_fch_PC <= CPU_BASEADDR;
else
begin
if (w_jump_flag)
instr_fch_PC <= jump_addr;
else if( ~bus_hsk_ok & rib_delay_op ) // long period load & store
instr_fch_PC <= next_addr;
else
instr_fch_PC <= instr_fch_PC + 4;
end
实际要跳转的pc:
always @( posedge sys_clk or negedge rst_n )
if ( !rst_n ) o_curr_pc <= CPU_BASEADDR;
else o_curr_pc <= i_ir_hold_flag ? o_curr_pc : (bus_hsk_ok & rib_delay_op) ? next_addr : instr_fch_PC;
空指令enable 信号:
当发生跳转时(不是正常的pc + 4),需要插入空指令,保持流水线连续,
reg waiting_r = 0;
always @ (posedge sys_clk or negedge rst_n)
if(!rst_n) waiting_r <= 0;
else waiting_r <= w_jump_flag;
assign o_curr_instr = (waiting_r) ? `INST_NOP : instr_data_sel;
流水线操作要比状态机操作复杂一些, 尤其时增加了jtag 的调式功能之后。这个部分可以参考以上的代码, 同时仿真整个risc-v 的工程,帮助了解流水线操作的相关cpu 运作。


