流水账(CPU设计实战)——lab10

Lab10 V7.0

版本控制

版本描述
V0Lab3
V1.0Lab3 相对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.0Lab4 相对V1.0的变化 引入`ifdef-`else-`endif来实现相对V1.0的代码增量 增加了旁路控制,减少流水线阻塞(因为增加了旁路,所以修改了ID、EXE、MEM的接口) 修改了ready_go命令,用于控制流水线的阻塞
V3.0Lab6(加载的是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.0Lab7 修改了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.0Lab8 (对应第7章任务一):添加syscall例外支持 增加MTC0、MFC0、ERET指令 增加CP0寄存器Status、Cause、EPC 增加SYSCALL指令 新建SSR.v(即system registers),是ID.v的子模块 为ID、EXE、MEM、WB级增加了flush信号,用于将数据清零
V6.0添加break指令断点例外 添加地址错、整数溢出、保留指令例外 增加CP0寄存器Count、Compare、BadVAddr 增加时钟中断 增加6个硬中断 增加2个软件中断
V6.8此版本感觉叫5.8更好,但是本人就顺着叫6.8了,此版本实际上是V5.0的重写,重新设计了数据通路: 将SSR.v放在WB阶段(依据原书P173) MFC0/MTC0也放在WB阶段,并且不引入旁路,而是直接插入NOP(依据原书P180)
V7.0完成了原书Lab10需要的类SRAM总线的测试 该版本存在软件中断这一遗留问题(详见31.15小节)

Top顶层

接口信号

MYCPU_TOP.v(TOP)

名称宽度方向描述
时钟与复位
clk1I时钟信号,来自clk_pll的输出时钟
resetn1I复位信号,低电平同步复位
取指端访存接口
inst_sram_en1O指令RAM使能信号,高电平有效
inst_sram_wen4O指令RAM字节写使能信号,高电平有效
inst_sram_addr32O指令RMA读写地址,字节寻址
inst_sram_wdata32O指令RAM写数据
inst_sram_rdata32I指令RAM读数据
inst_sram_req1O请求信号,为1时有读写请求,为1时无读写请求
inst_sram_wr1O为1表示该次是写请求,为0表示该次是读请求
inst_sram_size2O该次请求传输的字节数,0:1byte,1:2bytes,2:4bytes
inst_sram_wstrb4O该次写请求的字节写使能
inst_sram_addr32O该次请求的地址
inst_sram_wdata32O该次写请求的写数据
inst_sram_addr_ok1I该次写请求的地址传输OK,读:地址被接收;写:地址和数据被接收
inst_sram_data_ok1I该次请求的数据传输OK,读:数据返回;写:数据写入完成
inst_sram_rdata32I该次写请求返回的读数据
数据端访存接口
data_sram_en1O数据RAM使能信号,高电平有效
data_sram_wen4O数据RAM字节写使能信号,高电平有效
data_sram_addr32O数据RAM读写地址,字节寻址
data_sram_wdata32O数据RAM写数据
data_sram_rdata32I数据RAM读数据
data_sram_req1O请求信号,为1时有读写请求,为1时无读写请求
data_sram_wr1O为1表示该次是写请求,为0表示该次是读请求
data_sram_size2O该次请求传输的字节数,0:1byte,1:2bytes,2:4bytes
data_sram_wstrb4O该次写请求的字节写使能
data_sram_addr32O该次请求的地址
data_sram_wdata32O该次写请求的写数据
data_sram_addr_ok1I该次写请求的地址传输OK,读:地址被接收;写:地址和数据被接收
data_sram_data_ok1I该次请求的数据传输OK,读:数据返回;写:数据写入完成
data_sram_rdata32I该次写请求返回的读数据
debug信号,供验证平台使用
debug_wb_pc32O写回级(多周期最后一级)的PC,需要myCPU里将PC一路传递到写回级
debug_wb_rf_wen4O写回级写寄存器堆(regfiles)的写使能,为字节使能,如果myCPU写regfiles为单字节写使能,则将写使能扩展成4位即可
debug_wb_rf_wnum5O写回级写regfiles的目的寄存器号
debug_wb_rf_wdata32O写回级写regfiles的写数据

接口时序

略(MIPS经典五级流水线)

代码结构

MYCPU_TOP.v

|____IF.v

|____ID.v

|____RF.v(2个读端口,1个写端口)

|____SFR.v(1个读端口,2个写端口)

|____EXE.v

|____ALU.v

|____MUL_DIV.v(专门处理乘法和除法)

|____MUL.v

|____DIVU.v

|____DIVS.v

|____MEM.v

|____WB.v

|____SSR.v

|____MYCPU.h

DATA_RAM.v

IF.v(修改为IF_STAGE,因为会与关键词if冲突)

接口信号

名称宽度方向描述
时钟与复位
clk1I时钟信号,来自clk_pll的输出时钟
resetn1I复位信号,低电平同步复位
与TOP
inst_sram_en1ORAM使能信号,高电平有效
inst_sram_wen4ORAM字节写使能信号,高电平有效
inst_sram_addr32ORMA读写地址,字节寻址
inst_sram_wdata32ORAM写数据
inst_sram_rdata32IRAM读数据
inst_sram_req1O请求信号,为1时有读写请求,为1时无读写请求
inst_sram_wr1O为1表示该次是写请求,为0表示该次是读请求
inst_sram_size2O该次请求传输的字节数,0:1byte,1:2bytes,2:4bytes
inst_sram_wstrb4O该次写请求的字节写使能
inst_sram_addr32O该次请求的地址
inst_sram_wdata32O该次写请求的写数据
inst_sram_addr_ok1I该次写请求的地址传输OK,读:地址被接收;写:地址和数据被接收
inst_sram_data_ok1I该次请求的数据传输OK,读:数据返回;写:数据写入完成
inst_sram_rdata32I该次写请求返回的读数据
与ID
id_to_if_allowin1Ipipe allowin
if_to_id_vld1Opipe valid
if_to_id_data64Opipe data(instruction 32-bits, pc 32-bits)
jump_bus33Ibranch instructions(enable 1bit,address 32-bits)
if_to_id_flush1OIF传给ID的数据部分清零(PC不清零)
与WB
wb_jump_en1I检测到EXC,然后决定是否jump
wb_jump_pc32I检测到EXC,jump的PC值
wb_exc_flush1IWB阶段检测到EXC,对流水线清空

接口时序

ID.v

接口信号

名称宽度方向描述
时钟与复位
clk1I时钟信号,来自clk_pll的输出时钟
resetn1I复位信号,低电平同步复位
与TOP
inst_sram_en1ORAM使能信号,高电平有效
inst_sram_wen4ORAM字节写使能信号,高电平有效
inst_sram_addr32ORMA读写地址,字节寻址
inst_sram_wdata32ORAM写数据
inst_sram_rdata32IRAM读数据
与IF
id_to_if_allowin1Ipipe allowin
if_to_id_vld1Opipe valid
if_to_id_data64Opipe data(instruction 32-bits, pc 32-bits)
jump_bus33Ibranch instructions(enable 1bit,address 32-bits)
if_to_id_flush1IIF传给ID的数据部分清零(PC不清零)
与EXE
exe_to_id_allowin1Ipipe allowin
id_to_exe_vld1Opipe valid
id_to_exe_data179O{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} {id_exc_bus:16, alu_op:21, shamt_is_shamt:1, imm_zero_ext_en:1, ins_R:1, ins_I:1, imm:16, lwx_bus_less2:10, swx_bus:8, rf_we:4, rf_dst_addr:5, rt_data:32, rs_sfr_data_2:32, pc:32}
exe_bypass_bus38I{exe_rf_we:1, exe_rf_dst_addr:5, exe_rf_data:32}
exe_to_id_sfr_bus76I[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中10Olwx类总线: 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中8Oswx类总线: ins_sb: 1, ins_sh: 1, ins_swl: 1, ins_swr: 1, mem_we[3:0]: 4
mul_div_busy1I正在进行乘法或除法运算
exe_ins_mfc01I指令mfc0传递至EXE阶段
exe_ins_lwx1Ilwx类指令传递至EXE阶段
与MEM
mem_bypass_bus38I{mem_rf_we:1, mem_rf_dst_addr:5, mem_rf_data:32}
mem_ins_mfc01I指令mfc0传递至MEM阶段
与WB
wb_to_rf_bus41I{rf_we:4, rf_addr:5, rf_data:32}
wb_exc_flush1IWB阶段检测到EXC,对流水线清空
wb_c0_has_int1IWB阶段的SSR检测到中断
wb_ins_mfc01I指令mfc0传递至WB阶段

接口信号(RF.v)

名称宽度方向描述
时钟与复位
clk1I时钟信号,来自clk_pll的输出时钟
与ID内部信号
rf_r_addr15IRF读地址1
rf_r_data132ORF读数据1
rf_r_addr25IRF读地址2
rf_r_data232ORF读数据2
rf_wen14IRF写使能1
rf_w_addr15IRF写地址1
rf_w_data132ORF写数据1

接口信号(SFR)

当写入同一地址时,以sfr_we_1优先

名称宽度方向描述
时钟与复位
clk1I时钟信号,来自clk_pll的输出时钟
resetn1I复位信号,低电平同步复位
与ID内部信号
sfr_r_addr15ISFR读地址1
sfr_r_data132OSFR读数据1
sfr_we_11ISFR写使能1
sfr_w_addr15ISFR写地址1
sfr_w_data132ISFR写数据1
sfr_we_21ISFR写使能2
sfr_w_addr25ISFR写地址2
sfr_w_data232ISFR写数据2

接口时序

电路设计

在这里插入图片描述

图3-4-1 译码电路分组(注:黄线少画了两条)

根据附录——MIPS指令。由于跳转指令不传递给EXE阶段,直接传递给IF阶段,且为纯组合逻辑输出,有可能成为关键路径,故对跳转指令单独处理。除了跳转指令外,涉及加法(减法归为加法)的指令如图3-4-1所示,即ins_addu、ins_addiu、ins_subu、ins_lw、ins_sw。

对于图3-4-1的拼接运算,可以当作移位运算执行。

EXE.v

接口信号

名称宽度方向描述
时钟与复位
clk1I时钟信号,来自clk_pll的输出时钟
resetn1I复位信号,低电平同步复位
与TOP(外接的DATA_RAM)
data_sram_en1O数据RAM使能信号,高电平有效
data_sram_wen4O数据RAM字节写使能信号,高电平有效(4个比特,应该代表32 = 4 bytes)
data_sram_addr32O数据RAM读写地址,字节寻址
data_sram_wdata32O数据RAM写数据
data_sram_req1O请求信号,为1时有读写请求,为1时无读写请求
data_sram_wr1O为1表示该次是写请求,为0表示该次是读请求
data_sram_size2O该次请求传输的字节数,0:1byte,1:2bytes,2:4bytes
data_sram_wstrb4O该次写请求的字节写使能
data_sram_addr32O该次请求的地址
data_sram_wdata32O该次写请求的写数据
data_sram_addr_ok1I该次写请求的地址传输OK,读:地址被接收;写:地址和数据被接收
data_sram_data_ok1I该次请求的数据传输OK,读:数据返回;写:数据写入完成
data_sram_rdata32I该次写请求返回的读数据
与ID
exe_to_id_allowin1Opipe allowin
id_to_exe_vld1Ipipe valid
id_to_exe_data179I{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} {id_exc_bus:16, alu_op:21, shamt_is_shamt:1, imm_zero_ext_en:1, ins_R:1, ins_I:1, imm:16, lwx_bus_less2:10, swx_bus:8, rf_we:4, rf_dst_addr:5, rt_data:32, rs_sfr_data_2:32, pc:32}
exe_bypass_bus41O{exe_rf_we:4, exe_rf_dst_addr:5, exe_rf_data:32}
exe_ins_mfc01O指令mfc0传递至EXE阶段
exe_ins_lwx1Olwx类指令传递至EXE阶段
exe_to_id_sfr_bus76O[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_busy1O正在进行乘法或除法运算
lwx_bus 将其合并至id_to_exe_data中10Ilwx类总线: 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中8Iswx类总线: ins_sb: 1, ins_sh: 1, ins_swl: 1, ins_swr: 1, mem_we[3:0]: 4
与MEM
mem_to_id_allowin1Ipipe allowin
exe_to_mem_vld1Opipe valid
exe_to_mem_data138O{mem_rd:1, rf_we:1, rf_dst_addr:5, pc:32(其实可以删掉pc,这里是debug显示用的,可以叫debug_pc), exe_result:32 {exe_to_mem_exc_bus:56, 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中10Olwx类总线: 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
与WB
wb_exc_flush1IWB阶段检测到EXC,对流水线清空

接口信号(ALU.v)

暂时不需要时钟和复位,纯组合逻辑

名称宽度方向描述
时钟与复位
clk1I时钟信号,来自clk_pll的输出时钟
resetn1I复位信号,低电平同步复位
与ID内部信号
alu_shamt6IALU移位(R-指令的shamt部分)
alu_op19IALU操作(加、减、乘除、位运算)
alu_add_ov1O加法/减法溢出
alu_din132IALU输入1
alu_din232IALU输入2
alu_out32OALU输出
mul_div_busy1O正在进行乘法或除法运算

接口信号(MUL_DIV.v)

名称宽度方向描述
时钟与复位
clk1I时钟信号,来自clk_pll的输出时钟
resetn1I复位信号,低电平同步复位
与ALU内部信号
alu_mult1I有符号乘
alu_multu1I无符号乘
alu_div1I有符号除
alu_divu1I无符号除
alu_mthi1I将寄存器 rs 的值写入到 HI 寄存器中
alu_mtlo1I将寄存器 rs 的值写入到LO寄存器中
alu_din132IALU输入1
alu_din232IALU输入2
exe_to_id_sfr_bus76O[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_busy1O正在进行乘法或除法运算

接口信号MUL.v

名称宽度方向描述
时钟与复位
clk1I时钟信号,来自clk_pll的输出时钟
resetn1I复位信号,低电平同步复位
与ALU内部信号
mul_din_vld1I数据有效信号
mul_din133I乘数1
mul_din233I乘数2
mul_result_hi32O乘法高32位
mul_result_lo32O乘法低32位

接口信号DIVU.v

名称宽度方向描述
时钟与复位
clk1I时钟信号,来自clk_pll的输出时钟
resetn1I复位信号,低电平同步复位
与ALU内部信号
divu_din_vld1I数据有效信号
divu_din133I被除数
divu_din233I除数
divu_hi32O结果高32位
divu_lo32O结果低32位

接口信号DIVS.v

名称宽度方向描述
时钟与复位
clk1I时钟信号,来自clk_pll的输出时钟
resetn1I复位信号,低电平同步复位
与ALU内部信号
divs_din_vld1I数据有效信号
divs_din133I被除数
divs_din233I除数
divs_hi32O结果高32位
divs_lo32O结果低32位

接口时序

MEM.v

接口信号

名称宽度方向描述
时钟与复位
clk1I时钟信号,来自clk_pll的输出时钟
resetn1I复位信号,低电平同步复位
与TOP(外接的DATA_RAM)
data_sram_rdata32I数据RAM读数据
data_sram_data_ok1I该次请求的数据传输OK,读:数据返回;写:数据写入完成
与ID
mem_bypass_bus38O{mem_rf_we:4, mem_rf_dst_addr:5, mem_rf_data:32}
mem_ins_mfc01O指令mfc0传递至MEM阶段
与EXE
mem_to_exe_allowin1Opipe allowin
exe_to_mem_vld1Ipipe valid
exe_to_mem_data138I{mem_rd:1, rf_we:1, rf_dst_addr:5, pc:32(其实可以删掉pc,这里是debug显示用的,可以叫debug_pc), exe_result:32 {exe_to_mem_exc_bus:56, 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中10Ilwx类总线: 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
与WB
wb_to_mem_allowin1Ipipe allowin
mem_to_wb_vld1Opipe valid
mem_to_wb_data128O{ mem_to_wb_exc_bus:56, rf_we:4, rf_dst_addr:5, mem_result:32, pc:32(其实可以删掉pc,这里是debug显示用的,可以叫debug_pc)}
wb_exc_flush1IWB阶段检测至EXC,对流水线清空

WB.v

接口信号

名称宽度方向描述
时钟与复位
clk1I时钟信号,来自clk_pll的输出时钟
resetn1I复位信号,低电平同步复位
与TOP
debug_wb_pc32O写回级(多周期最后一级)的PC,需要myCPU里将PC一路传递到写回级(与原书保持一致)
debug_wb_rf_wen4O写回级写寄存器堆(regfiles)的写使能,为字节使能,如果myCPU写regfiles为单字节写使能,则将写使能扩展成4位即可(与原书保持一致)
debug_wb_rf_wnum5O写回级写regfiles的目的寄存器号(与原书保持一致)
debug_wb_rf_wdata32O写回级写regfiles的写数据(与原书保持一致)
与IF
wb_jump_en1O检测到EXC,然后决定是否jump
wb_jump_pc32O检测到EXC,jump的PC值
wb_exc_flush1OWB阶段检测到EXC,对流水线清空
与MEM
wb_to_mem_allowin1Opipe allowin
mem_to_wb_vld1Ipipe valid
mem_to_wb_data128I{ mem_to_wb_exc_bus:56, rf_we:4, rf_dst_addr:5, mem_result:32, pc:32(其实可以删掉pc,这里是debug显示用的,可以叫debug_pc)}
与ID
wb_to_rf_bus41O{rf_we:4, rf_addr:5, rf_data:32}
wb_exc_flush1OWB阶段检测到EXC,对流水线清空
wb_c0_has_int1OWB阶段的SSR检测到中断
wb_ins_mfc01O指令mfc0传递至WB阶段

接口信号(SSR)

名称宽度方向描述
时钟与复位
clk1I时钟信号,来自clk_pll的输出时钟
resetn1I复位信号,低电平同步复位
与ID内部信号
c0_op4I3:保留着,先置为0 2:wb_bd:branch delay slot(本Lab8版本可以恒置为0) 1:wb_exc:由WB传回来的例外 0:syscall
c0_eret1Ieret
c0_int6I中断
c0_excode5I由WB传回来的excode
c0_wb_pc32I由WB传回来的PC值
c0_we1ISSR写使能(1:写,0:读)
c0_addr8ISSR写/读地址 [7:5]:c0_sel [5:0]:c0_id
c0_wdata32ISSR写数据
c0_rdata32OSSR读数据
c0_has_int1O发生中断
c0_badvaddr_i32Ibadvaddr的数据输入

LAB10——后记

sram_wrap.v

  1. Lab10增加了对SRAM的控制,给SRAM套了层壳:sram_wrap.v

    在这里插入图片描述

    里面有个同步FIFO,是用来模拟读出来的数据的,就是当ram_random_mask(这个是随机数生成的)为全0时,数据是无效的,但是vivado实例化的SRAM肯定是有效的,可以正常读出来数据的,这个buf(也就是同步FIFO)就是用来存储正常读出来的数据,然后对外说:没有读出来。

    1. 看了一遍sram_wrap.v,感觉不需要我出手,也没啥鸟用。

IF → \rightarrow req

IF阶段的req分析:

  1. 根据原书P191:“上述两个问题的方案效率较高,但是复杂度也高。如果我们为流水线增加一个规则:仅当IF级allowin为1时pre-IF级才对外发出地址请求,那么当pre-IF级ready_go为1时,IF级的allowin一定为1,就不会出现“pre_IF_ready_go=1, IF_allowin=0”这种情况,也就不会出现上述两个复杂的问题。增加该规则后,取指效率降低了,但是大大简化了状态机的设置。”
    1.

状态机设计

  1. 刚开始没用状态机,写的代码是这样的:

    在这里插入图片描述

    在这里插入图片描述

    就是通过添加一个辅助的锁存信号,对数据进行控制,但是费了半天劲觉得设计的逻辑没有欠缺后,意识到2个问题:一个是若干个月之后再来看这些代码,我还看得懂吗?另一个是后面还要引入真正的AXI总线,是不是又要重新设计一遍,然后考虑各种情况(当然现在我还没看下一节的AXI部分)。故决定删掉这些控制代码,改用状态机控制

    1. 针对状态机,从4个状态(IDLE/ADDR/DATA/LAT)删减为3个状态(ADDR/DATA/LAT)。

      1. 根据时序图,得到如下状态转移图V1.0:

        在这里插入图片描述

        1. 对于上述状态转移图V1.0,作以下几点说明:
序号条件说明
1c_state = S_ADDR且allowin=1 且 addr_ok=1向外发出请求(req=1),n_state = S_DATA
2c_state = S_ADDR且allowin=0 且 addr_ok=1 (allowin=0,所以不向外发出req。但是这个时候addr_ok为1,说明后面是要接收数据的,此时虽说allowin=0,但是也要跳到S_LAT以便等待接收数据)不向外发出请求(req=0), n_state = S_LAT(不会出现这种情况,因为allowin=0时,不会发出req,故该情况不可能发生。上图的状态转移图是旧版)
3c_state = S_ADDR 且 allowin=0 且 addr_ok=0 (没发出req,或者发出req了但是addr_ok=0(等于没发出),反正allowin=0,所以req=0)不向外发出请求(req=0),n_state = S_ADDR
4c_state = S_ADDR 且 allowin=1 且 addr_ok=0 (向外发出请求,但是没有得到addr_ok,此时allowin=1所以继续发送)向外发出请求(req=1),n_state = S_ADDR
5c_state = S_DATA 且 allowin=1 且data_ok=1 (处于连续不中断:发送接收发送接收)向外发出请求(req=1)
6c_state = S_DATA 且 data_ok = 0 (发送接收发送没有接收到,此时数据尚未接收,不可发送req,跳转到LAT状态,等待数据接收)不向外发出请求(req=0),n_state = LAT
7c_state = S_DATA 且 data_ok=1 且 addr_ok = 0 且 allowin(发送接收发送接收,说明发出的地址都收到数据了,但是地址没有ok,所以跳转到S_ADDR继续向外req)向外发出请求(req=1),n_state = S_ADDR
8c_state = S_LAT 且 allowin=1 且 data_ok = 1 (发送接收发送没有接收到接收,说明之前没有接收到的数据,这次终于得到返回的数据了,这时allowin=1,所以跳回S_DATA继续发出req)向外发出请求(req=1),n_state = S_DATA
9c_state = S_LAT 且 allowin=0 且 data_ok = 1 (发送接收发送没有接收到接收,说明之前没有接收到的数据,这次终于得到返回的数据了,但是此时不allowin=0,所以跳回S_ADDR,并且不发出req)不发出请求(req=0),n_state = S_ADDR
10c_state = S_LAT 且 allowin=1 且 data_ok = 0 (发送接收发送没有接收到还是没有接收到,说明发出地址后,还没得到返回的数据了,继续等)不向外发出请求(req=0),n_state = S_LAT

状态设计(2)——引入flush

  1. 本小节所描述的状态机没有考虑flush的影响,即当检测到例外后,wb_exc_flush会清空整个流水线,如果在清空的时候,地址发出去(地址已经接收了),但是数据尚未返回,对此情况本人决定使用一个标志寄存器存储这种情况。需要data_ok对该标志寄存器清零。当该标志寄存器为0时,说明是真正接收到的数据(没想到还是用寄存器给控制了,本来不想用这种sb方法的),原书:P193页针对该情况给出了两种方案:
  • “给状态机引入一个新的状态(该状态表明第一个data_ok并丢弃当次返回的指令数据)”

  • “IF级新增一个触发器,复位值为0。当遇到前述pre-IF级的第2种情况和IF级的情况2-2时,该触发器置1;在收到data_ok时,该触发器置0。当该触发器置1时,组合逻辑将IF级的ready_go抹成零,从而达到了丢弃第一个返回的指令数据的设计”

    看来我原来的想法是第2个设计(sb设计),还是用状态机吧这样看起来更明了,后期也方便修改

    1. 状态转移图V2.0:

在这里插入图片描述

  1. 因为当flush=1时,allowin必定为0。所以引入flush后,状态机中只增加了一个S_FLUSH状态,剩余的状态及跳转条件无需修改基本没变(原状态机只修改了两处:S_LATS_LAT和S_DATAS_LAT),直接还是由allowin来控制了。

状态设计(3)——引入stall

因为ID阶段会判断出:乘/除mfhi/mflo、lw读regsiter等stall,因此需要在IF阶段引入stall控制:(我与原书的想法一样,本来我也不想用这种sb方法的(sb之处:用了一个32位的寄存器),但是想不到其他方法了,就翻了翻书,发现书上也是这种方法):

原书P192:“设置一组触发器来保存IF级取回的指令,当该级触发器存有有效数据时,则选择该组触发器保存的数据作为IF级取回的指令送往ID级,在ID级allowin为1后,该指令立即进入ID级。此时IF级的ready_go信号不再只看类SRAM总线接口返回的data_ok,还要观察来自“保存已返回指令的触发器”的有效信号,如果该组触发器保存着有效返回值,那么IF级ready_go也一定为1”。

状态转移图V3.0:

在这里插入图片描述

状态转移表V3.0:相对V2.0进行了大修改:

在这里插入图片描述

  • 尽可能删掉了allowin,而是使用addr_ok进行判断(因为addr_ok的前提就是allowin)

  • 更换了S_LAT的功能,之前我是这样想的:在c_state=S_DATA状态时,若遇到(data_ok=0,addr_ok=0(因为若data_ok为0,则addr_ok必为0))则跳转至S_LAT,后来发现没有这个必要,直接还在S_DATA状态就好了,因为检测到data_ok才会发起req,才会有addr_ok=1,若是没检测到data_ok,也不会发起req,继续等着就好。

  • S_DATA -> S_DATA [label = “~(addr_ok ^ data_ok)”];

    addr_ok = 0 且 data_ok=0,说明没有收到数据,继续等着

    addr_ok = 1 且 data_ok=1,说明都收到了,继续在S_DATA

  • 所以我为S_LAT赋予了新的功能,就是stall情况下,如果addr_ok=1,但是data_ok还没回来,那么就跳到S_LAT这个状态,等着数据被Latch,以便后面接着用这个数据

data_sram

数据SRAM,主要在EXE、MEM阶进行控制,尤其要参照P186页的两张表格。

  1. inst_sram只涉及读,因此addr_ok和data_ok比较简单,而data_sram涉及读写,因此,有必要再次明确下addr_ok和data_ok的含义:
  • addr_ok:该次请求的地址传输OK,读:地址被接收;写:地址和数据被接收

  • data_ok:该次请求的数据传输OK,读:数据返回;写:数据写入完成。

    1. data_sram可能会出现以下冲突:

      sw:addr_oklwxxxxxxsw:data_oklw:addr_oklw:data_ok

      即发生swlw写后读冲突,并且sw的addr_ok之后,若干个周期后才收到data_ok,而在期间lw已经来了

      1. 我在犹豫:sw没收到data_ok就直接阻塞,不管后面是不是lw指令。还是:没收到data_ok不阻塞,等到遇到了lw指令后,发现还没有dat_ok时再阻塞。想了一下,还是直接阻塞吧,不然遇到这种情况:lwrs/rt寄存器时,lw若没收到data_ok,还要判断旁路,设计更复杂,另一方面,CPU主要是计算用的,应该不会频繁涉及大量的lw/sw操作(我猜的),大量的数据搬移直接用DMA算了。

data_sram(2)

针对lwx/swx等指令(这里的x表示lw/lwl/lb等)

  • 【分类1】:在EXE阶段没有检测到addr_ok,直接stall,然后exe_to_mem_bus清零

  • 【分类2】:在EXE阶段检测到了addr_ok,再分为如下3种情况

    1. lw寄存器(有旁路):则在MEM阶段stall,然后mem_to_wb_bus清零

      1. lw/swlw:第一条的lw/sw已经收到了addr_ok(否则属于【分类1】),此时第2条指令处于EXE阶段,需要判断是否收到第一条指令的data_ok,

        若收到则处理情况与【分类1】相同:开始检测第2条lw是否addr_ok等等。

        若没收到,则stall,此时不将exe_to_mem_bus清零的(这样是避免清空MEM阶段的数据,因为旁路判断需要MEM的数据)。此时第1条指令处理MEM阶段且没有收到data_ok,则第1条指令的处理与【分类2】的情况1处理相同

        1. lwsw:

          如果lwsw涉及旁路,那么会在ID阶段就检测出来,会产生:lwbubblesw这种情况,这种情况反而比较简单,因为lw在MEM阶段没有收到data_ok就直接阻塞了,处理方式与【分类2】的情况1是一样,无需刻意处理该情况。

          如果lwsw不涉及旁路,此时处理方式与【分类2】的情况2是一样的,唯一的区别是这时将exe_to_mem_bus给清零(因为不需要判断sw指令的旁路),不清零也其实没影响,但是我看着还是清零舒服。因为这里没影响是因为sw指令的作用只是将数据存入data_sram,不会影响后续指令的处理,而不是因为指令本身不需要清零,所以我是清了的。(无所谓,可清可不清)

LAB10——完结

在这里插入图片描述

看下了时序报告,后面应该要优化下时序了,时序太紧张了:

在这里插入图片描述

LAB10——Debug

c_state复位

c_state一直没有复位,本人查看半天没有看出任何问题:

  1. 修改STATE的localparam为parameter/define

    1. 使用计数器复位:(后面把这个计数器复位给删掉了,因为不是这个问题)

      在这里插入图片描述

最后发现还是有问题,然后我就直接拷贝另一个复位成功的always块,在上面修改c_state,c_state竟然复位成功了,然后复盘找到了这个bug:少写了else

在这里插入图片描述

无语:vivado综合也不报error,也没有warning。

pc报错

在这里插入图片描述

在这里插入图片描述

想起来了:忘了修改if_pc和next_pc了

beq报错

/media/sf_ucas19_20_all/release/func_lab9/start.S:26

bfc00008: 100001ab b bfc006b8 <locate>

/media/sf_ucas19_20_all/release/func_lab9/start.S:27

bfc0000c: 00000000 nop

/media/sf_ucas19_20_all/release/func_lab9/start.S:30

bfc00010: 3c088000 lui t0,0x8000

在执行上面的指令时,正确是执行到bfc00008遇到beq,是要跳走的,但是波形上是在跳转前还执行了bfc00010指令(不应该执行的),经排查发现,req对bfc00004指令,成功握手了两次,而且出现了req=0,但是addr_ok的情况(我是没意识到会出现这种情况的):

在这里插入图片描述

又读了下原书,书上没明说,但是确实指出了(P186):“只有在clk的上升沿同时看到req和addr_ok为1的时候才是一次成功的请求握手”,于是增加了信号:inst_hand_addr_ok = inst_sram_req & inst_sram_addr_ok,使用inst_hand_addr_ok来进行判断。(当然,data_sram也要进行相应的修改)。

add自加操作

若是在IF阶段阻塞了,需要将if_to_id_data进行清零,否则若是ID阶段还是“addiu a0, a0, -4088”这种自加操作,会一直加下去

beq报错(2)

上一次beq报错解决后,因为我加了个flush,所以解决了,但是后面又更改了一些信号,再次影响了beq:

在这里插入图片描述

经查看波形,发现:beq_bne_addr有问题,beq_ben_addr的表达式为:

assign beq_bne_addr = (jump_beq | jump_bne) ? if_pc + {imm_ext[29:0], 2’h0}: 32’hFFFF_FFFF;

发现ID阶段的if_pc全是0,被flush掉了,因此对IF.v中的if_to_id_flush的操作对象进行修改,只将instruction给flush掉,不flush掉pc。

然后终于跑起来了!!!

lw报错

在这里插入图片描述

发现是data_ok的地址敲错了,敲成addr_ok了,更改如下:

在这里插入图片描述

状态机跳错

在这里插入图片描述

在这里插入图片描述

s_data_s_data与s_data_s_lat同时为1,导致两者相或后使S_DATA跳至S_FLUSH(正常是S_DATAS_DATA),经排查,给s_data_s_data添加了if_allowin控制:

在这里插入图片描述

敲错了exe_axi_stall

在这里插入图片描述

敲错了,应该是data_ok(之前的addr_ok是复制下来的,忘了改了)

更改为:

在这里插入图片描述

转移延迟槽指令

在这里插入图片描述

经排查,是因为没有执行0xbfc1e314对应的instruction:

在这里插入图片描述

从test.s中可以看到,bfc1e314地址存储的指令为lui指令,且紧跟在beq指令之后(beq s5,s5,bfc1e320,是必定要跳的)

  • 按我之前的理解:beq既然要跳转,那就不会执行后面的指令(之前我看到的都是beq后面紧跟着的是NOP指令),但是通过vivado报错知道:是要执行lui指令的
  • 现在我的理解是:将与分支不冲突的指令放在转移延迟槽中(现在我怀疑漏看了书上的内容)。
  • 既然不论是否跳转,都要执行beq后面的指令(其他跳转指令应当也是如此了),那么就需要重新设计电路了。怪不得刚开始看原书P193的“考虑转移延迟槽”部分是感觉云里雾里,现在重读一遍确实有道理
  • 需要设计跳转存储电路,否则当inst_sram_data_ok=0时,可能将jump_bus给清零
  • 需要在IF.v中添加额外控制跳转的指令
  • 将ID.v、IF.v中的jump_en等信号给重命名:因为容易混淆,将jump_en改为jump_to_en,将jump_addr改为jump_to_addr。

最终修改如下:

在这里插入图片描述

swl报错

在这里插入图片描述

经分析,是swl指令的问题。data_sram_size的生成逻辑中,下图圈中的值应当为0:(之前误写成1了)

在这里插入图片描述

例外传递出错

在这里插入图片描述

经排查发现EXE阶段检测出adel例外,但是没有传递给MEM阶段,很快发现是exe_to_mem_flush出了问题:

在这里插入图片描述

更改为:

在这里插入图片描述

EXE的流水线阻塞

现象:后面的debug_wb_pc一直为全0:

在这里插入图片描述

经排查,发现EXE.v中exe_axi_stall始终为高,阻塞了流水线,致使传递给MEM的数据始终为0,现做如下修改:

在这里插入图片描述

改为(增加了exe_flush控制):

在这里插入图片描述

除此之外,还对以下信号增加了exe_flush控制:

在这里插入图片描述

adel出错

在这里插入图片描述

从图中看出两者只有第0比特有问题,经过溯源,发现问题出现在exe_exc_data_mux:

  • 在EXE.v中,LAB10之前,data_sram_addr是直接等于mem_result的,但是在LAB10中,当指令为lw/swl时,固定将data_sram_addr[1:0]置为2’b00,这影响了exe_exc_data_mux的取值

    在这里插入图片描述

  • 因此需要修改exe_exc_data_mux的取值如下:

    在这里插入图片描述

c0_bd报错

在这里插入图片描述

经排查,因为出现了这种情况:

br 到 inst_sram没取到指令 到 取到了指令(该指令发生例外)

在inst_sram没取到指令期间,存储的bd检测被清零了,故最后wb_rf_wdata相对于rf的值多了4,现在ID.v中增加inst_sram_data_ok控制:仅当if_to_id_flush_ff1为0时才更新ins_branch_ff1(注:assign id_c0_ff1=ins_branch_ff1)。

在这里插入图片描述

例外取消

在这里插入图片描述

经波形查看,发现是ID阶段的问题,在ID阶段尚未获得指令(即inst_sram_data_ok=0)时,检测到了软件中断,此时是不可以将中断传递下去的,需要锁住,但是锁住之后,依然报错:

在这里插入图片描述

在这里插入图片描述

虽说报错的时间不一样,但是报错的信息是一样的,都是wb_rf_wdata差了一个固定值:8

经反复查看波形,本人意识到了是本人对软件中断和参考ref对软件中断理解的不同:

  • 本人理解:遇到软件中断,传回ID阶段完事。
  • ref:遇到软件中断需要固定执行3条指令

这样就遇到一个问题如下:

IF到ID到EXE到MEM到WB各阶段指令如下(其中无效表示:由于IF取指时没有得到inst_sram_data_ok导致的指令是无效的(NOP)):

  • IF指令ID指令(有效)EXE指令 (无效)到 MEM指令(无效)到 WB指令(wb_c0_has_int首次为1)

  • 按我的理解,遇到软件中断,直接将WB阶段检测到的wb_c0_has_int传给ID阶段,然后在EXE阶段检测出来,最后再传递给WB阶段报出来。

  • 但是ref是这样参考的:ID指令(有效)到 EXE指令(有效)到 MEM指令(有效),也就是说ref的指令都是有效的,所以从检测到wb_c0_has_int首次为1后(假设此时没有跳转指令,且此时WB阶段的PC记作wb_pc),则最后得到的epc_pc = wb_pc + 12

  • 但是对于这种情况:ID指令(有效)EXE指令(无效)MEM指令(无效),我的硬件实现的epc_pc = wb_pc + 4

  • 这就导致我的理解和ref的理解产生的偏差(目前我是这样认为的),于是我被迫“欺骗”了测试:即遇到软件中断则确保有3个有效指令后才报出例外,具体如下:

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    即通过计数器soft_in_cnt来计数,并确保3个有效指令后,才将id_exc_bus[5]给拉高。

  • 因为这里“迎合”了软件,因此不确定是不是本人对软件中断的理解有问题,因此,所有“迎合”操作都是在通过`LAB10_SOFT_INT来进行控制,当不进行“迎合”时,直接注释掉该宏即可。

例外取消(2)

在进行上一小节debug时,遇到了个问题记录一下:

  • 刚开始我是这样命名的:if_to_id_flush_ffx(没有_no_):

    assign next_soft_int_cnt = (~if_to_id_flush_ff1) + (~if_to_id__flush_ff2) + (~if_to_id_flush_ff3);

    然而对比波形,发现不是我想要的三个变量相加

  • 于是决定不采用取反符号相加,而是这样命名if_to_id_no_flush_ffx:

    assign next_soft_int_cnt = if_to_id_no_flush_ff1 + if_to_id_no_flush_ff2 + if_to_id_no_flush_ff3;

    其中的取反操作在下图红框中:

    在这里插入图片描述

    最终的结果是我想要的三者相加

vivado综合bug

我使用lab10给的mycpu_verify下的vivado直接综合,发现报错,重试几次后还是报错。最终本人使用lab9_8的vivado工程,将工程名由mycpu_prj1.xpr重命名为mycpu.xpr,并将lab10的.v文件添加进来,如此在上面进行修改OK。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Greate AUK

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值