Menu Close

RISC-V EXU模块和CPU运行(2)CPU运行过程(基于流水线)

当完成整个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 取指模块流水线:

 

端口说明:

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 运作。

Posted in FPGA, FPGA 教材教案, RISC-V IPcore设计, RISC-V 教材教案, RISC-V架构, 教材与教案, 文章, 集成电路