Lab8 V5.0
版本控制
版本 | 描述 |
---|---|
V0 | Lab3 |
V1.0 | Lab3 相对V0变化: 修改了文件名,各阶段以_stage结尾(因为if是关键词,所以module名不能叫if,遂改为if_stage,为了统一命名,将所有module后缀加上_stage) 删除了imm_sign信号(默认对立即数进行有符号数扩展) 由于对sw指令进行了重新理解:无论如何都是需要将rt_data传递给EXE阶段,故将部分译码逻辑进行后移至EXE阶段,避免id_to_exe_data总线过于庞大 将ins_shmat剔除出id_to_exe_data,因为imm包括ins_shamt 对信号进行重命名(例如在ID阶段有个信号叫rf_we,最终要传递给WB阶段,那么在EXE阶段,该信号叫作exe_rf_we,同理mem_rf_we,wb_rf_we),不然都叫rf_we,Debug的时候太痛苦了。 |
V2.0 | Lab4 相对V1.0的变化 引入`ifdef-`else-`endif来实现相对V1.0的代码增量 增加了旁路控制,减少流水线阻塞(因为增加了旁路,所以修改了ID、EXE、MEM的接口) 修改了ready_go命令,用于控制流水线的阻塞 |
V3.0 | Lab6(加载的是func_lab6的.coe文件,而不是func_lab5的,因为没有func_lab5这个东西) 相对V2.0的变化:电路细节变化详见:“CPU设计实战电路图”,图中Lab6的变化均以红色背景的方框框起来,并以黄色背景的“Lab6复用/新增信号”标注。本lab6.docx中,新增及修改的信号以红色标注。 删掉了ALU.v的接口,将alu_shamt合并至data_1,方便对新加指令sllv、srlv、srav的数据通路的复用 增加了ID.v中的alu_add的“或门”的输入 扩充了id_to_exe_data总线数据, 因为alu_op扩充了新的指令 添加了imm_zero_ext信号 修改了ins_R、ins_J、ins_I,因为增加了新的指令 乘法器和除法器调用vivado的IP,其周期参数化可配置设计 乘法和除法结果存放的寄存器HI/LO放在WB.v中 增加了sfr.v,用于特殊寄存器的存储 增加了mul_div.v,用于处理乘法和除法。增加了mul.v用于处理乘法,增加了divu.v用于处理无符号除法,增加了divs.v用于处理有符号除法 修改了id_to_exe_data,添加了新信号:imm_zero_ext_en 修改了id_to_exe_data,扩充了alu_op的位宽,并将alu_op调整至id_to_exe_data的最高位,方便未来扩充指令 |
V4.0 | Lab7 修改了alu_op的位宽 新增了两个bus:lw_bus、sw_bus,同时将mem_rd整合至lw_bus,将mem_we整合至sw_bus 扩大了id_to_exe_data lw_bus传递至MEM阶段结束,sw_bus传递至EXE阶段结束 修改了旁路,之前都是lw的旁路,这里将其扩充了lb、lbu、lh、lhu、lwl、lwr |
V5.0 | Lab8 (对应第7章任务一):添加syscall例外支持 增加MTC0、MFC0、ERET指令 增加CP0寄存器Status、Cause、EPC 增加SYSCALL指令 新建SSR.v(即system registers),是ID.v的子模块 为ID、EXE、MEM、WB级增加了flush信号,用于将数据清零 |
Top顶层
接口信号
MYCPU_TOP.v(TOP)
名称 | 宽度 | 方向 | 描述 |
---|---|---|---|
时钟与复位 | |||
clk | 1 | I | 时钟信号,来自clk_pll的输出时钟 |
resetn | 1 | I | 复位信号,低电平同步复位 |
取指端访存接口 | |||
inst_sram_en | 1 | O | 指令RAM使能信号,高电平有效 |
inst_sram_wen | 4 | O | 指令RAM字节写使能信号,高电平有效 |
inst_sram_addr | 32 | O | 指令RMA读写地址,字节寻址 |
inst_sram_wdata | 32 | O | 指令RAM写数据 |
inst_sram_rdata | 32 | I | 指令RAM读数据 |
数据端访存接口 | |||
data_sram_en | 1 | O | 数据RAM使能信号,高电平有效 |
data_sram_wen | 4 | O | 数据RAM字节写使能信号,高电平有效 |
data_sram_addr | 32 | O | 数据RAM读写地址,字节寻址 |
data_sram_wdata | 32 | O | 数据RAM写数据 |
data_sram_rdata | 32 | I | 数据RAM读数据 |
debug信号,供验证平台使用 | |||
debug_wb_pc | 32 | O | 写回级(多周期最后一级)的PC,需要myCPU里将PC一路传递到写回级 |
debug_wb_rf_wen | 4 | O | 写回级写寄存器堆(regfiles)的写使能,为字节使能,如果myCPU写regfiles为单字节写使能,则将写使能扩展成4位即可 |
debug_wb_rf_wnum | 5 | O | 写回级写regfiles的目的寄存器号 |
debug_wb_rf_wdata | 32 | O | 写回级写regfiles的写数据 |
接口时序
略(MIPS经典五级流水线)
代码结构
MYCPU_TOP.v
|____IF.v
|____ID.v
|____RF.v(2个读端口,1个写端口)
|____SFR.v(1个读端口,2个写端口)
|____SSR.v
|____EXE.v
|____ALU.v
|____MUL_DIV.v(专门处理乘法和除法)
|____MUL.v
|____DIVU.v
|____DIVS.v
|____MEM.v
|____WB.v
|____MYCPU.h
DATA_RAM.v
IF.v(修改为IF_STAGE,因为会与关键词if冲突)
接口信号
名称 | 宽度 | 方向 | 描述 |
---|---|---|---|
时钟与复位 | |||
clk | 1 | I | 时钟信号,来自clk_pll的输出时钟 |
resetn | 1 | I | 复位信号,低电平同步复位 |
与TOP | |||
inst_sram_en | 1 | O | RAM使能信号,高电平有效 |
inst_sram_wen | 4 | O | RAM字节写使能信号,高电平有效 |
inst_sram_addr | 32 | O | RMA读写地址,字节寻址 |
inst_sram_wdata | 32 | O | RAM写数据 |
inst_sram_rdata | 32 | I | RAM读数据 |
与ID | |||
id_to_if_allowin | 1 | I | pipe allowin |
if_to_id_vld | 1 | O | pipe valid |
if_to_id_data | 64 | O | pipe data(instruction 32-bits, pc 32-bits) |
jump_bus | 33 | I | branch instructions(enable 1bit,address 32-bits) |
接口时序
ID.v
接口信号
名称 | 宽度 | 方向 | 描述 |
---|---|---|---|
时钟与复位 | |||
clk | 1 | I | 时钟信号,来自clk_pll的输出时钟 |
resetn | 1 | I | 复位信号,低电平同步复位 |
与IF | |||
id_to_if_allowin | 1 | O | pipe allowin |
if_to_id_vld | 1 | I | pipe valid |
if_to_id_data | 64 | I | pipe data(instruction 32-bits, pc 32-bits) |
jump_bus | 33 | O | branch instructions(enable 1bit,address 32-bits) |
与EXE | |||
id_flush | 1 | I | 将id_data给清零 |
exe_front_flush | 1 | O | 将exe_data给清零 |
id_flush | 1 | I | 将id_data给清零 |
exe_to_id_allowin | 1 | I | pipe allowin |
id_to_exe_vld | 1 | O | pipe valid |
id_to_exe_data | 164 | O | {alu_op:21, shamt_is_shamt:1, imm_zero_ext_en:1, ins_R:1, ins_I:1, imm:16, mem_rd:1, mem_we:1, rf_we:1, rf_dst_addr:5, rt_data:32, rs_sfr_data_2:32, pc:32} {exe_ctr_op:1, alu_op:22, shamt_is_shamt:1, imm_zero_ext_en:1, ins_R:1, ins_I:1, imm:16, lwx_bus_less2:8, swx_bus:8, rf_we:4, rf_dst_addr:5, rt_data:32, rs_sfr_data_2:32, pc:32} |
exe_bypass_bus | 38 | I | {exe_rf_we:1, exe_rf_dst_addr:5, exe_rf_data:32} |
exe_to_id_sfr_bus | 76 | I | [75] exe_sfr_we1 [74:70] exe_sfr_w_addr1 [69:38] exe_sfr_data1 [37] exe_sfr_we2 [36:32] exe_sfr_w_addr2 [31:0] exe_sfr_data2 |
lwx_bus_less2 将其合并至id_to_exe_data中 | 10 | O | lwx类总线: ins_lb: 1, ins_lbu: 1, ins_lh: 1, ins_lhu: 1, ins_lwl: 1, ins_lwr: 1, ins_lw: 1, rs_low2: 2 mem_rd: 1 |
swx_bus 将其合并至id_to_exe_data中 | 8 | O | swx类总线: ins_sb: 1, ins_sh: 1, ins_swl: 1, ins_swr: 1, mem_we[3:0]: 4 |
mul_div_busy | 1 | I | 正在进行乘法或除法运算 |
与MEM | |||
mem_bypass_bus | 38 | I | {mem_rf_we:1, mem_rf_dst_addr:5, mem_rf_data:32} |
与WB | |||
wb_to_rf_bus | 41 | I | {rf_we:4, rf_addr:5, rf_data:32} |
wb_exc_bus | 48 | I | 详见:3.4节 pc:32 c0_op:4 c0_int:6 c0_excode:5 exc:1 |
接口信号(RF.v)
名称 | 宽度 | 方向 | 描述 |
---|---|---|---|
时钟与复位 | |||
clk | 1 | I | 时钟信号,来自clk_pll的输出时钟 |
与ID内部信号 | |||
rf_r_addr1 | 5 | I | RF读地址1 |
rf_r_data1 | 32 | O | RF读数据1 |
rf_r_addr2 | 5 | I | RF读地址2 |
rf_r_data2 | 32 | O | RF读数据2 |
rf_wen1 | 4 | I | RF写使能1 |
rf_w_addr1 | 5 | I | RF写地址1 |
rf_w_data1 | 32 | O | RF写数据1 |
接口信号(SFR)
当写入同一地址时,以sfr_we_1优先
名称 | 宽度 | 方向 | 描述 |
---|---|---|---|
时钟与复位 | |||
clk | 1 | I | 时钟信号,来自clk_pll的输出时钟 |
resetn | 1 | I | 复位信号,低电平同步复位 |
与ID内部信号 | |||
sfr_r_addr1 | 5 | I | SFR读地址1 |
sfr_r_data1 | 32 | O | SFR读数据1 |
sfr_we_1 | 1 | I | SFR写使能1 |
sfr_w_addr1 | 5 | I | SFR写地址1 |
sfr_w_data1 | 32 | I | SFR写数据1 |
sfr_we_2 | 1 | I | SFR写使能2 |
sfr_w_addr2 | 5 | I | SFR写地址2 |
sfr_w_data2 | 32 | I | SFR写数据2 |
接口信号(SSR)
名称 | 宽度 | 方向 | 描述 |
---|---|---|---|
时钟与复位 | |||
clk | 1 | I | 时钟信号,来自clk_pll的输出时钟 |
resetn | 1 | I | 复位信号,低电平同步复位 |
与ID内部信号 | |||
c0_op | 4 | I | 3:保留着,先置为0 2:wb_bd:branch delay slot(本Lab8版本可以恒置为0) 1:wb_exc:由WB传回来的例外 0:syscall |
c0_eret | 1 | I | eret |
c0_int | 6 | I | 中断 |
c0_excode | 5 | I | 由WB传回来的excode |
c0_wb_pc | 32 | I | 由WB传回来的PC值 |
c0_we | 1 | I | SSR写使能(1:写,0:读) |
c0_addr | 8 | I | SSR写/读地址 [7:5]:c0_sel [5:0]:c0_id |
c0_wdata | 32 | I | SSR写数据 |
c0_rdata | 32 | O | SSR读数据 |
接口时序
电路设计
图3-4-1 译码电路分组(注:黄线少画了两条)
根据附录——MIPS指令。由于跳转指令不传递给EXE阶段,直接传递给IF阶段,且为纯组合逻辑输出,有可能成为关键路径,故对跳转指令单独处理。除了跳转指令外,涉及加法(减法归为加法)的指令如图3-4-1所示,即ins_addu、ins_addiu、ins_subu、ins_lw、ins_sw。
对于图3-4-1的拼接运算,可以当作移位运算执行。
EXE.v
接口信号
名称 | 宽度 | 方向 | 描述 |
---|---|---|---|
时钟与复位 | |||
clk | 1 | I | 时钟信号,来自clk_pll的输出时钟 |
resetn | 1 | I | 复位信号,低电平同步复位 |
与TOP(外接的DATA_RAM) | |||
data_sram_en | 1 | O | 数据RAM使能信号,高电平有效 |
data_sram_wen | 4 | O | 数据RAM字节写使能信号,高电平有效(4个比特,应该代表32 = 4 bytes) |
data_sram_addr | 32 | O | 数据RAM读写地址,字节寻址 |
data_sram_wdata | 32 | O | 数据RAM写数据 |
与ID | |||
id_flush | 1 | O | 将id_data给清零 |
exe_front_flush | 1 | I | 将exe_data给清零 |
exe_to_id_allowin | 1 | O | pipe allowin |
id_to_exe_vld | 1 | I | pipe valid |
id_to_exe_data | 164 | I | {alu_op:21, shamt_is_shamt:1, imm_zero_ext_en:1, ins_R:1, ins_I:1, imm:16, mem_rd:1, mem_we:1, rf_we:1, rf_dst_addr:5, rt_data:32, rs_sfr_data_2:32, pc:32} {exe_ctr_op:1, alu_op:22, shamt_is_shamt:1, imm_zero_ext_en:1, ins_R:1, ins_I:1, imm:16, lwx_bus_less2:8, swx_bus:8, rf_we:4, rf_dst_addr:5, rt_data:32, rs_sfr_data_2:32, pc:32} |
exe_bypass_bus | 41 | O | {exe_rf_we:4, exe_rf_dst_addr:5, exe_rf_data:32} |
exe_to_id_sfr_bus | 76 | O | [75] exe_sfr_we1 [74:70] exe_sfr_w_addr1 [69:38] exe_sfr_data1 [37] exe_sfr_we2 [36:32] exe_sfr_w_addr2 [31:0] exe_sfr_data2 |
mul_div_busy | 1 | O | 正在进行乘法或除法运算 |
lwx_bus 将其合并至id_to_exe_data中 | 10 | I | lwx类总线: ins_lb: 1, ins_lbu: 1, ins_lh: 1, ins_lhu: 1, ins_lwl: 1, ins_lwr: 1, ins_lw: 1, rs_low2: 2 mem_rd: 1 |
swx_bus 将其合并至id_to_exe_data中 | 8 | I | swx类总线: ins_sb: 1, ins_sh: 1, ins_swl: 1, ins_swr: 1, mem_we[3:0]: 4 |
与MEM | |||
mem_front_flush | 1 | O | 将mem_data给清零 |
exe_flush | 1 | I | 将exe_data给清零 |
id_flush | 1 | O | 将id_data给清零 |
mem_to_id_allowin | 1 | I | pipe allowin |
exe_to_mem_vld | 1 | O | pipe valid |
exe_to_mem_data | 99 | O | {mem_rd:1, rf_we:1, rf_dst_addr:5, pc:32(其实可以删掉pc,这里是debug显示用的,可以叫debug_pc), exe_result:32} {exe_exc_bus:16, lwx_bus: 10, rf_we:4, rf_dst_addr:5, pc:32(其实可以删掉pc,这里是debug显示用的,可以叫debug_pc), exe_result:32} |
lwx_bus 将其合并至id_to_exe_data中 | 10 | O | lwx类总线: ins_lb: 1, ins_lbu: 1, ins_lh: 1, ins_lhu: 1, ins_lwl: 1, ins_lwr: 1, ins_lw: 1, rs_low2: 2 mem_rd: 1 |
exe_exc_bus (归于exe_to_mem_data) | 16 | O | 详见:3.4节 c0_op:4 c0_int:6 c0_excode:5 exc:1 |
接口信号(ALU.v)
暂时不需要时钟和复位,纯组合逻辑
名称 | 宽度 | 方向 | 描述 |
---|---|---|---|
时钟与复位 | |||
clk | 1 | I | 时钟信号,来自clk_pll的输出时钟 |
resetn | 1 | I | 复位信号,低电平同步复位 |
与ID内部信号 | |||
alu_shamt | 6 | I | ALU移位(R-指令的shamt部分) |
alu_op | 19 | I | ALU操作(加、减、乘除、位运算) |
alu_din1 | 32 | I | ALU输入1 |
alu_din2 | 32 | I | ALU输入2 |
alu_out | 32 | O | ALU输出 |
mul_div_busy | 1 | O | 正在进行乘法或除法运算 |
接口信号(MUL_DIV.v)
名称 | 宽度 | 方向 | 描述 |
---|---|---|---|
时钟与复位 | |||
clk | 1 | I | 时钟信号,来自clk_pll的输出时钟 |
resetn | 1 | I | 复位信号,低电平同步复位 |
与ALU内部信号 | |||
alu_mult | 1 | I | 有符号乘 |
alu_multu | 1 | I | 无符号乘 |
alu_div | 1 | I | 有符号除 |
alu_divu | 1 | I | 无符号除 |
alu_mthi | 1 | I | 将寄存器 rs 的值写入到 HI 寄存器中 |
alu_mtlo | 1 | I | 将寄存器 rs 的值写入到LO寄存器中 |
alu_din1 | 32 | I | ALU输入1 |
alu_din2 | 32 | I | ALU输入2 |
exe_to_id_sfr_bus | 76 | O | [75] exe_sfr_we1 [74:70] exe_sfr_w_addr1 [69:38] exe_sfr_data1 [37] exe_sfr_we2 [36:32] exe_sfr_w_addr2 [31:0] exe_sfr_data2 |
mul_div_busy | 1 | O | 正在进行乘法或除法运算 |
接口信号MUL.v
名称 | 宽度 | 方向 | 描述 |
---|---|---|---|
时钟与复位 | |||
clk | 1 | I | 时钟信号,来自clk_pll的输出时钟 |
resetn | 1 | I | 复位信号,低电平同步复位 |
与ALU内部信号 | |||
mul_din_vld | 1 | I | 数据有效信号 |
mul_din1 | 33 | I | 乘数1 |
mul_din2 | 33 | I | 乘数2 |
mul_result_hi | 32 | O | 乘法高32位 |
mul_result_lo | 32 | O | 乘法低32位 |
接口信号DIVU.v
名称 | 宽度 | 方向 | 描述 |
---|---|---|---|
时钟与复位 | |||
clk | 1 | I | 时钟信号,来自clk_pll的输出时钟 |
resetn | 1 | I | 复位信号,低电平同步复位 |
与ALU内部信号 | |||
divu_din_vld | 1 | I | 数据有效信号 |
divu_din1 | 33 | I | 被除数 |
divu_din2 | 33 | I | 除数 |
divu_hi | 32 | O | 结果高32位 |
divu_lo | 32 | O | 结果低32位 |
接口信号DIVS.v
名称 | 宽度 | 方向 | 描述 |
---|---|---|---|
时钟与复位 | |||
clk | 1 | I | 时钟信号,来自clk_pll的输出时钟 |
resetn | 1 | I | 复位信号,低电平同步复位 |
与ALU内部信号 | |||
divs_din_vld | 1 | I | 数据有效信号 |
divs_din1 | 33 | I | 被除数 |
divs_din2 | 33 | I | 除数 |
divs_hi | 32 | O | 结果高32位 |
divs_lo | 32 | O | 结果低32位 |
接口时序
MEM.v
接口信号
名称 | 宽度 | 方向 | 描述 |
---|---|---|---|
时钟与复位 | |||
clk | 1 | I | 时钟信号,来自clk_pll的输出时钟 |
resetn | 1 | I | 复位信号,低电平同步复位 |
与TOP(外接的DATA_RAM) | |||
data_sram_rdata | 32 | I | 数据RAM读数据 |
与EXE | |||
mem_front_flush | 1 | I | 将mem_data给清零 |
exe_flush | 1 | O | 将exe_data给清零 |
mem_to_exe_allowin | 1 | O | pipe allowin |
exe_to_mem_vld | 1 | I | pipe valid |
exe_to_mem_data | 99 | I | {mem_rd:1, rf_we:1, rf_dst_addr:5, pc:32(其实可以删掉pc,这里是debug显示用的,可以叫debug_pc), exe_result:32} {exe_exc_bus:16, lwx_bus: 10, rf_we:4, rf_dst_addr:5, pc:32(其实可以删掉pc,这里是debug显示用的,可以叫debug_pc), exe_result:32} |
lwx_bus 将其合并至id_to_exe_data中 | 10 | I | lwx类总线: ins_lb: 1, ins_lbu: 1, ins_lh: 1, ins_lhu: 1, ins_lwl: 1, ins_lwr: 1, ins_lw: 1, rs_low2: 2 mem_rd: 1 |
exe_exc_bus (归为exe_to_mem_data) | 16 | I | 详见:3.4节 c0_op:4 c0_int:6 c0_excode:5 exc:1 |
与WB | |||
mem_flush | 1 | I | 将mem_data给清零 |
wb_front_flush | 1 | O | 将wb_data给清零 |
wb_to_mem_allowin | 1 | I | pipe allowin |
mem_to_wb_vld | 1 | O | pipe valid |
mem_to_wb_data | 89 | O | {mem_exc_bus:16, rf_we:4, rf_dst_addr:5, mem_result:32, pc:32(其实可以删掉pc,这里是debug显示用的,可以叫debug_pc)} |
mem_exc_bus (归为mem_to_wb_data) | 16 | O | 详见:3.4节 c0_op:4 c0_int:6 c0_excode:5 exc:1 |
与ID | |||
mem_bypass_bus | 38 | O | {mem_rf_we:4, mem_rf_dst_addr:5, mem_rf_data:32} |
wb_exc_bus | 16 | O | 详见:3.4节 c0_op:4 c0_int:6 c0_excode:5 exc:1 |
WB.v
接口信号
名称 | 宽度 | 方向 | 描述 |
---|---|---|---|
时钟与复位 | |||
clk | 1 | I | 时钟信号,来自clk_pll的输出时钟 |
resetn | 1 | I | 复位信号,低电平同步复位 |
与TOP | |||
debug_wb_pc | 32 | O | 写回级(多周期最后一级)的PC,需要myCPU里将PC一路传递到写回级(与原书保持一致) |
debug_wb_rf_wen | 4 | O | 写回级写寄存器堆(regfiles)的写使能,为字节使能,如果myCPU写regfiles为单字节写使能,则将写使能扩展成4位即可(与原书保持一致) |
debug_wb_rf_wnum | 5 | O | 写回级写regfiles的目的寄存器号(与原书保持一致) |
debug_wb_rf_wdata | 32 | O | 写回级写regfiles的写数据(与原书保持一致) |
与MEM | |||
mem_flush | 1 | O | 将mem_data给清零 |
wb_front_flush | 1 | I | 将wb_data给清零 |
wb_to_mem_allowin | 1 | O | pipe allowin |
mem_to_wb_vld | 1 | I | pipe valid |
mem_to_wb_data | 89 | I | { mem_exc_bus:16, rf_we:4, rf_dst_addr:5, mem_result:32, pc:32(其实可以删掉pc,这里是debug显示用的,可以叫debug_pc)} |
mem_exc_bus (归于mem_to_wb_data) | 16 | I | 详见:3.4节 c0_op:4 c0_int:6 c0_excode:5 exc:1 |
与ID | |||
wb_to_rf_bus | 41 | O | {rf_we:4, rf_addr:5, rf_data:32} |
wb_exc_bus | 48 | O | 详见:3.4节 pc:32 c0_op:4 c0_int:6 c0_excode:5 exc:1 |
LAB8——完结
LAB8——后记
指令分析
mtc0/mfc0
-
mtc0/mfc0(摘自:第10章 协处理器访问指令的实现_mfc0和mtc0-优快云博客)指令作用为:
而《CPU设计实战》中这样写:
CP0[rd,sel]GPR[rt],所以这里rd应当是寄存器编号,sel是选择用的:
eret指令
- ERET指令:需要完成两个动作:
- 将CP0的EPC寄存器中存放的例外指令PC作为目标地址跳转过去
- 将Status.EXL清0(这是为了将处理器的特权等级恢复到出现例外的时刻)
寄存器EPC
-
寄存器EPC(R/W):存放上一次发生例外指令的PC,没有复位值。
寄存器Cause
-
寄存器Cause,内容如下:(是有复位值的)
寄存器Status
-
寄存器Status,注意:部分有复位值,部分没有复位值:
Syscall指令
-
SYSCALL指令:《CPU设计实战》的指令中只有一句:“触发系统调用例外”。但是具体怎么执行呢?
《CPU设计实战》中还有一句:“所有类型的例外在发生后都跳转到地址0xBFC00380处取指执行”
流水线
原书P172页,针对“精确例外”的流水线分析
-
发生例外前已结束的指令、发生在例外后的指令都不用考虑
-
发生例外时,仍在流水线中的指令怎么处理呢:这里介绍一种常用的设计思路:例外发生的判断逻辑分布在各流水线,靠近与之相关的数据通路;发现例外信息附着在指令上沿流水线一路携带下去,直到写回级才真正报出例外,此时才会根据所携带的例外信息更新PC置为例外入口地址。当然,报出例外的流水级不一定要设定为写回级,设定在执行级可访存级也可以,这样还会减少设计负担。但是要注意:选择报出例外的流水级的原则是:在该级之后的流水级不能产生新的例外(比如报出例外的流水级设定为执行级,则要求访存级和写回级不能产生或标记上新的例外),否则就违反了精确例外的要求。
原书在访回级判断例外的原因是,原书认为在访回级时,其前的指令都已经执行完了,可以“精确”地判断出例外。但是本人所设计乘、除法器是多周期的,如果在访回级判断出了例外,但是执行级还在执行之前的乘/除法指令,怎么办呢,这不符合“精确例外”的要求啊。
首先例外最早可以ID阶段检测出来(如syscall指令),最晚可以在EXE阶段检测出来(如加/减溢出等指令)。因此最晚在EXE阶段检测出来后,下一条指令直接清空,禁止后续指令的执行,同时然后向后传递例外:EXE到MEM到WB,当传递到WB阶段时才向外报出例外。但是在EXE阶段需要增加一判断:判断上一条指令(这里专指多周期的乘、除指令)是否马上执行完毕(即在例外传递至WB前执行完毕,即乘除法指令在3个周期以内可以执行完毕),若可在3个周期内执行完毕,才允许EXE到MEM传递例外。
- 简单地讲,在EXE阶段检测出来例外后,先不外向发出例外,而是逐级传递例外:EXE到MEM到WB,在逐级传递的同时通过这3个阶段的缓存的例外信息,去阻止下一条指令的执行。最终在WB阶段对外发出例外。
-
例外的检测
-
我是打算在EXE阶段首次检测出例外,然后在WB阶段对外发出例外。但是像bne、jal等指令,在ID阶段就执行完毕了,所以考虑下面这样一种情况:
第一条指令:addu(有溢出)
第二条指令:jal
那么在addu执行到EXE阶段时,首次检测出例外,而jal此时处于ID阶段,并且将要跳转。此时不受影响,而是将exe_pc保存至EPC,并将allowin置为0
例外的设计
- 如果出现例外(比如整数溢出),则当前级禁止allowin,同时将该禁止随流水线逐级向后传,传至WB_ff1后,对外发出。
旁路回顾
-
在旁路设计之前,我是想尽可能复用之前设计的旁路的。然后我发现一个问题:
只要检测到lw要写的rt地址,与当前的ins_rt相同,则执行lw_stall_en。即检测到在EXE阶段lw要写ins_rt这个地址,同时有指令要读ins_rt这个地址(R型指令要读ins_rt这个地址),则执行lw_stall_en。
但是这忽略了一种情况:即检测到在EXE阶段lw要写ins_rt这个地址,同时有指令不写ins_rt这个地址(J型指令、I型指令不写ins_rt地址)。这时是不需要进行stall的
但是本人设计的是只要检测到在EXE阶段lw要写ins_rt这个地址,同时与当前指令的ins_rt地址,则使能lw_stall_en,因此这是不合理的,没必要的,平白增加了许多stall.
- 因此,需要对lw_stall_en的设计加上新的限定,即
- 修改exe/mem/wb_rt_bypass:加上限定条件:R型指令或I型指令(因为像bne/beq这样的I型指令,虽说是I型,但是需要同时读出rs_data和rt_data)
- 修改exe/mem/wb_rs_bypass:加上限定条件: R型指令或I型指令
- 但是本次修改不再修改Lab6,而是归于Lab8,为此本人创建了一个分支:“Lab7_bypass”这个bypass,针对该情况进行了探讨,但是发现一个问题:就是J型指令只有ins_jal和ins_j,其他指令都是R型指令和I型指令。没必要为这两条指令专门添加MUX,没必要,完全没必要。
旁路设计
- mtc0/mfc0与Lab6的指令:mflo/mfhi/mtlo/mthi挺像的,在Lab6中是有关于LO/HI这两个寄存器的旁路的。但是不需要设置与mtc0/mfc0有关的旁路,因为LO/HI之所以会有旁路,是因为乘、除法指令需要执行,而mtc0/mfc0是不需要执行的,简单的讲,最紧张的情况如下:
-
第一条指令:mtc0
-
第二条指令:mfc0
然而等mfc0执行到ID阶段的时候,mtc0已经将数据写到寄存器里了,故不会影响mfc0的读取,因此这里的“写后读”不需要旁路。
- 考虑下面这种情况:
-
第一条指令:lw
-
第二条指令:mtc0
lw取出的数据恰恰是mtc0所需要,因此这里是需要旁路的,同理其他类似指令也是需要旁路的。
c0_op
- 原本我是将ins_eret这条指令先添加至c0_op中进行传递的,但是我发现ins_eret直接在ID阶段使用就可以了,没必要传过去。遂将c0_eret从c0_op中剥离。
1.
flush操作
因为Lab8是需要将整个流水线给flush掉的,但是我意识到一个问题,就是如果我像Lab4一样,直接通过allowin为0时,将数据置0:
感觉是有问题的,因为如果我来个多发射,一条指令在等另一条指令执行完,那么这个时候我是需要exe_data能保持的,而不是清零,因此Lab4的设计对多发射是不友好的。故本次Lab8,我加了一个flush信号,通过各级的flush来将流水线中的数据清零。
LAB8——Debug
PC卡死
因为我不确定ins_syscall的跳转地址是不是21.1.6节中的0xBFC00380,故跑出了bug,然后去看了下ref的代码,确定就是跳转至0xBFC00380,于是修改如下:
mfc0报错
原来是忘了将mfc0添加进ins_I里面了,已添加如下:
然而还有错误:
原因是忘了处理“例外时将地址存入EPC”:
改为:
mfc0报错(2)
然而执行到mfc0时还是报错:
通过波形发现,ins_rd=0x0e,但是目前我只准备了3个寄存器:Status、Cause、EPC,没有一个地址是0x0e,也就是说这个读地址是有问题的,然后发现地址的进制写错了:
改为:
然而,我又想到一个问题,之前的旁路是有rs和rt寄存器的,因为之前的指令只会读rs和rt寄存器,因此发生“写后读”只会在这两个寄存器上产生。
但是这里的SSR.v的读地址和rd有关,应该也需要“写后读”的旁路,于是本人开始增加rd的旁路。(脑子抽风了,rd是读地址,不是读数据)
mfc0报错(3)
经排查,原来是21.1.5节中的图版本落后,没有bev:
遂修改如下:
eret报错
经排查,猜测是eret跳转地址的问题,发现ins_eret的时候,c0中的寄存器的id竟然不是EPC:
遂增加了ins_eret控制选项:
mfc0报错
mfc0指令运行错误,该条指令是读取EPC寄存器,错误的原因是先前向EPC写的数据有错。经排查,前面若干个周期之前经过了一条这样的指令:
divsyscall
-
具体的,当mul/div这种多周期指令后面遇到例外时,进行stall_en操作,即流水线阻塞。
-
由于这条指令,导致本人debug了一下午加一个晚上,最后被迫通过“凑时序”的方式干掉。
-
因为看不惯Lab4的流水线清零操作,本人设计添加了flush,通过flush进行各级的清零,但是发现清零后各级的PC最终全清零的,导致最终写到EPC的寄存器全零,于是本人决定使用对PC进行保存:
EXE阶段:
MEM阶段:
但是只将EXE、MEM阶段给保存了PC值(即数据的低32 bits),但是WB阶段则不需要保存,因为wb_exc传至WB阶段后,下一周期IF就会跳转至中断入口地址0xBFC00380,因此不需要对WB阶段的PC进行保留。另一方面,因为在进行多周期操作的时候,在EXE阶段通过else if(exe_exc || exe_flush || exe_exc_lat || exe_front_flush)来对数据进行flush操作时,已经将pc锁住(这里的锁住是指在mul/div进行多周期操作的时候锁住PC),因此ID的PC无用,不需要将ID的PC锁住,于是ID阶段的flush是直接全部清零:
ID阶段:
WB阶段:
-
另一方面,在执行mul/div操作期间已经检测到ins_syscall,但是不可将例外报出,需要等mul/div执行完毕后才报出,故在EXE阶段,在mul_div_busy的时候,不可将EXE阶段的例外信号(exe_exc)传递给MEM阶段。本人在EXE阶段通过exe_exc_lat_tmp、exe_exc_lat这两个信号用来控制这种情况,当mul_div_busy为低时,再将exe_exc拉高,另外,因为本人是在EXE阶段检测判断出所有例外,故有一信号exe_exc_dec,该信号在Lab8只有exe_syscall一个输入,后面可以“或”上其他例外,即:
-
总之,可以看到各个阶段的处理都是不同的,其中EXE阶段的处理最复杂,所以本阶段是“凑了时序”的。分析凑时序的原因:一方面是因为对Lab4的清零进行修改,改为flush操作,而我又不想新增一个专为例外而设置的“新flush”信号,所以控制信号变了复杂。另一方面,在设计之处就没考虑到“mul/div到syscall”这种操作,对“Lab8”的例外不是很熟悉,想着先搓出来一个可以用的Lab8,看看“例外”是不是我理解的“例外”,搓好后,又不想推翻之前所设计的然后重新设计,遂“凑了时序”。
-
-
-