流水账(CPU设计实战)——lab10_Opt~lab17

Lab17 V14.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总线的测试 该版本存在软件中断这一遗留问题(详见32.15小节)
V7.1在约束50MHz(PLL出来2个时钟:50和100MHz)的条件下,V7.0留有的裕度已经很小了,本V7.1版本主要在V7.0电路的基础上对电路时序进行优化。
V8.0文件变化得较多,包括: 修改了confreg.v sram_wrap.v替换为axi_wrap_ram.v 增加了axi_wrap.v 将soc_sram_lite_top.v替换为soc_axi_lite_top.v 增加了2个IP:axi_crossbar_1x2、axi_ram 更改了顶层,之前顶层是mycpu_top,现在将其重命名为mycpu_sram,在其上新增一顶层,其名为mycpu_top,可以这样理解: mycpu_top(old) + axi_interface = mpycpu_top(new)
V9.0相对Lab11没有任何变化,仅仅是修改了CPU_CDE_SRAM/rlt/CONFREG/confreg.v里的宏RANDOM_SEED进行测试
V10.0Lab13与Lab3~Lab12没有任何联系,是一个独立的个体,因此没有`define Lab13,且由于代码过与简单,不予绘制专门的电路图
V11.0Lab14 将tlb.v添加进工程 修改了IF、ID、EXE、MEM、WB等接口信号,用于tlb 添加了TLB的C0寄存器
V12.0Lab15(还是对应原书Lab14) 添加了tlbr、tlbwi、tlbp指令例外 取指、数据访问的MMU
V13.0Lab16 Lab16_pre此版本为Lab16的预处理版本,主要进行了以下处理: 从此版本开始,后面的代码的独立模块使用VHDL进行编写,仅Verilog调用VHDL时使用Verilog进行编写。 重写“类SRAM-AXI转接桥”,支持AXI的burst传输 添加容量为16Byte的全相联ICache(该Cache只有一行)和DCache(同ICache) 使修改后的代码通过Lab15的测试
V14.0Lab17 将Lab16设计的Cache集成到CPU中,成为ICache 将Lab16设计的Cache集成到CPU中,成为DCache

Top顶层

接口信号

MYCPU_AXI_TOP.v(new TOP

名称宽度方向描述
中断
int6I恒为6’h0(我猜:这应该是中断信号)
AXI 时钟与复位
aclk1I时钟信号,来自clk_pll的输出时钟
aresetn1I复位信号,低电平复位
AXI 写请求通道
awid[n:0]4O写请求标识号
awaddr[m:0]32O写请求地址
awsize[2:0]3O写请求数据宽度
awlen[3:0]8O写请求数据长度
awburst[1:0]2O写请求类型
awavalid1O写请求有效信号
awready1I写请求接收准备好信号
AXI 写数据通道
wid[n:0]4O写数据标识号,与写请求标识号对应
wdata[j:0]32O写数据
wstrb[k:0]4O写数据屏蔽信号,1位对应8个数据位
wvalid1O写数据有效信号
wready1I写数据接收准备好信号
AXI 写响应通道
bid[n:0]4I写响应标识号,与写请求标识号对应
bresp[1:0]2I写响应状态
wvalid1I写响应有效信号
wready1O写响应接收准备好信号
AXI 读请求通道
arid[n:0]4O读请求标识号
araddr[m:0]32O读请求地址
arsize[2:0]3O读请求数据宽度
arlen[3:0]8O读请求数据长度
arburst[1:0]2O读请求类型
aravalid1O读请求有效信号
arready1I读请求接收准备好信号
AXI 读响应通道
rid[n:0]4I读数据标识号,与读请求标识号对应
rdata[j:0]32I读数据
rresp[1:0]2I读响应状态
rvalid1I读数据有效信号
rready1O写读数据接收准备好信号

MYCPU_TOP.v(old TOP

名称宽度方向描述
时钟与复位
clk1I时钟信号,来自clk_pll的输出时钟
resetn1I复位信号,低电平同步复位
取指端访存接口
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_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_AXI_TOP.v

|____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_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该次写请求返回的读数据
与TLB
if_s0_vpn219Otlb: s0_vpn2
if_s0_odd_page1Otlb: s0_odd_pagd
if_s0_asid8Otlb: s0_asid
if_s0_found1Itlb: s0_found
if_s0_index$clog2(`TLBNUM)Itlb: s0_index
if_s0_pfn20Itlb: s0_pfn
if_s0_c3Itlb: s0_c
if_s0_d1Itlb: s0_d
if_s0_v1Itlb: s0_v
与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不清零)
id_tlb_exc1IID阶段的tlb 例外
与EXE
exe_tlb_flush1IEXE阶段检测到的tlb_flush使能信号
exe_inst_tlb_exc1IEXE阶段的tlb 例外(为什么不叫exe_tlb_exc呢,是为了与EXE阶段检测到读/写SRAM时检测到的tlb_exc区别开)
与MEM
mem_tlb_flush1IMEM阶段检测到的tlb_flush使能信号
mem_tlb_exc1IMEM阶段的tlb 例外
与WB
wb_jump_en1I检测到EXC,然后决定是否jump
wb_jump_pc32I检测到EXC,jump的PC值
wb_exc_flush1IWB阶段检测到EXC,对流水线清空
wb_tlb_flush1IWB阶段检测到的tlb_flush使能信号

接口时序

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不清零)
id_tlb_exc1OID阶段的tlb 例外
与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阶段
exe_tlb_flush1IEXE阶段检测到的tlb_flush使能信号
与MEM
mem_bypass_bus38I{mem_rf_we:1, mem_rf_dst_addr:5, mem_rf_data:32}
mem_ins_mfc01I指令mfc0传递至MEM阶段
mem_stall_en1IMEM阶段的stall
mem_tlb_flush1IMEM阶段检测到的tlb_flush使能信号
与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阶段
wb_tlb_flush1IWB阶段检测到的tlb_flush使能信号

接口信号(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_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该次写请求返回的读数据
与IF
exe_inst_tlb_exc1OEXE阶段的tlb 例外(为什么不叫exe_tlb_exc呢,是为了与EXE阶段检测到读/写SRAM时检测到的tlb_exc区别开)
与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
mem_ins_mtc01IMEM阶段的mtc0指令
mem_ins_tlbr1IMEM阶段的tlbr指令
mem_ins_tlbwi1IMEM阶段的tlbwi指令
mem_tlb_flush1IMEM阶段的tlb_flush操作
与WB
wb_exc_flush1IWB阶段检测到EXC,对流水线清空
exe_tlbp_en1OEXE的tlbp指令使能信号
entryhi_vpn219Ic0寄存器entryhi:vpn2
entryhi_asid8Ic0寄存器entryhi:asid
wb_ins_mtc01IWB阶段的mtc0指令
wb_ins_tlbr1IWB阶段的tlbr指令
wb_ins_tlbwi1IWB阶段的tlbwi指令
wb_tlb_flush1IWB阶段的tlb_flush操作
与TLB
exe_s1_vpn219Otlb: s1_vpn2
exe_s1_odd_page1Otlb: s1_odd_pagd
exe_s1_asid8Otlb: s1_asid
exe_s1_found1Itlb: s1_found
exe_s1_index$clog2(`TLBNUM)Itlb: s1_index
exe_s1_pfn20Itlb: s1_pfn
exe_s1_c3Itlb: s1_c
exe_s1_d1Itlb: s1_d
exe_s1_v1Itlb: s1_v
与IF、ID
exe_tlb_flush1OEXE阶段检测到的tlb_flush使能信号

接口信号(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的输出时钟
与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,读:数据返回;写:数据写入完成
与IF
mem_tlb_exc1OMEM阶段的tlb 例外
与ID
mem_bypass_bus38O{mem_rf_we:4, mem_rf_dst_addr:5, mem_rf_data:32}
mem_ins_mfc01O指令mfc0传递至MEM阶段
mem_stall_en1OMEM阶段的stall
与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
mem_ins_mtc01IMEM阶段的mtc0指令
mem_ins_tlbr1IMEM阶段的tlbr指令
mem_ins_tlbwi1IMEM阶段的tlbwi指令
mem_tlb_flush1IMEM阶段的tlb_flush操作
与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_tlb_flush1IWB阶段的tlb_flush清空操作
与IF\ID\EXE
mem_tlb_flush1OMEM阶段的tlb_flush清零操作

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_en1I检测到EXC,然后决定是否jump
wb_jump_pc32I检测到EXC,jump的PC值
wb_exc_flush1OWB阶段检测到EXC,对流水线清空
exe_tlbp_en1IEXE的tlbp指令使能信号
entryhi_vpn219Oc0寄存器entryhi:vpn2
entryhi_asid8Oc0寄存器entryhi:asid
wb_ins_mtc01OWB阶段的mtc0指令
wb_ins_tlbr1OWB阶段的tlbr指令
wb_ins_tlbwi1OWB阶段的tlbwi指令
wb_tlb_flush1OWB阶段的tlb_flush操作
与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阶段
与TLB
exe_s1_found1IEXE阶段TLB的s1_found
exe_s1_index$clog2(`TLBNUM)IEXE阶段TLB的s1_index
wb_we1OTLB的写端口
wb_w_index$clog2(`TLBNUM)OTLB的写端口
wb_w_vpn219OTLB的写端口
wb_w_asid8OTLB的写端口
wb_w_g1OTLB的写端口
wb_w_pfn020OTLB的写端口
wb_w_c03OTLB的写端口
wb_w_d01OTLB的写端口
wb_w_v01OTLB的写端口
wb_w_pfn120OTLB的写端口
wb_w_c13OTLB的写端口
wb_w_d11OTLB的写端口
wb_w_v11OTLB的写端口
wb_r_index$clog2(`TLBNUM)OTLB的读端口
wb_r_vpn219ITLB的读端口
wb_r_asid8ITLB的读端口
wb_r_g1ITLB的读端口
wb_r_pfn020ITLB的读端口
wb_r_c03ITLB的读端口
wb_r_d01ITLB的读端口
wb_r_v01ITLB的读端口
wb_r_pfn120ITLB的读端口
wb_r_c13ITLB的读端口
wb_r_d11ITLB的读端口
wb_r_v11ITLB的读端口

接口信号(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_Opt——后记

在这里插入图片描述

cpu_clk约束的是50MHz,关键路径也是看该时钟。

关键路径确认

在implementation前,怀疑有以下关键路径:

  1. EXE阶段的加/减法生成的数据ID阶段的旁路判断
    1. 从ID阶段的jump_addr的生成(因为涉及32-bits的加法)到IF阶段的next_pc
      1. IF阶段的pc+4

注:这里没有乘/除法,因为乘除法都是多周期的,应该不是关键路径,上述关键路径主要考虑的是32-bit的加法。

关键路径确认(2)

在这里插入图片描述

优化前

从vivado的implementation结果可看出关键路径EXEIF

  • 推测为:EXE加/减法结果ID旁路jump_addrIF的inst_sram_addr

  • 经查看源码,人为确定Critical path为:

    exe_datadata_1和data_2减法(EXEID旁路)id_rs_data rs_bg_zero rs_bge_zero jump_bgezjump_bgez_addrID阶段的jump_to_addrIF阶段的jump_addr next_pcinst_sram_addr

  • 根据vivado的Implementation结果:

    在这里插入图片描述

    优化前

    可知,大部分的延时都是线延时,但是我还是先尝试优化Logic Delay。

关键路径优化

优化方法可以见链接:FPGA时序分析之关键路径(Critical Path)【华为静态时序分析资料】【数字IC笔试面试】【流水线】【重定时retiming】【STA静态时序分析】_fpga关键路径-CSDN博客

但是由于不可插入寄存器(会破坏流水线时序),不可将组合逻辑前前移或后移(这也没法移,EXE阶段的加法也不可能移到ID阶段,本来就是旁路设计)。故本人决定采用以下方法对时序进行优化:

  • 对EXE阶段的加/减法进行优化:调用vivado的IP,使用DSP进行加速
  • 将关键路径移至尽可能靠近MUX的地方
  • 将vivado的综合、实现策略更改为速度最优化,让vivado去优化扇入和扇出

ALU——adder

在这里插入图片描述

加法器IP有两个选项,一者是Fabric,一者是DSP48,这里使用DSP48

组合逻辑前移

  1. IF.v中的jump_addr的优先级是一定要低于wb_jump_pc的:

    在这里插入图片描述

    因此此处不予修改。

    1. EXE.v中data_1与data_2的数据选择也无法优化,因为就过了两组数据选择器,即使前移,关键路径还是要过两级

      在这里插入图片描述

      1. ALU.v中的add_din1、add_din2、add_cin也无法优化:

        在这里插入图片描述

        1. ALU.v中的alu_out中的关键路径前移:

          在这里插入图片描述

          在这里插入图片描述

          alu_out中,slt与sltu是涉及加法结果的,而且stl的组合逻辑比sltu更复杂,因此先首先判断alu_slt_mux,其次是alu_sltu_mux,最后是(alu_add | alu_sub_mux | alu_jb)

        2. 上述的alu_out最后通过旁路,成了ID.v中的exe_rf_data,下面开始针对exe_rf_data进行优化:

          在旁路function中,感觉有个地方可以优化:

          在这里插入图片描述

          这里只有一个数据,如果依然通过{32{bypass_en}}进行与判断是不是不如使用数据选择器?(但是我感觉综合的结果应该是相同的,所以这里应该改不改不所谓)

        3. 调整旁路rs_data与rt_data中的EXE旁路的优先级:

          在这里插入图片描述

        4. 优化了bge、ls、lse等逻辑(这些组合逻辑服务于jump_beqz、jmp_bltz等跳转指令):

          在这里插入图片描述

          具体优化为:之前rs_bge_zero的输入依赖于rs_eq_zero、rs_bg_zero,优化后所有的输入只与id_rs_data有关,相互之间没有任何依赖关系,并且对逻辑进行了人为地化简。

        5. 去掉了不必要的加法:

          在这里插入图片描述

          进一步,上面的加法也调用vivado的IP

          在这里插入图片描述

        6. jump_adder感觉没必要优化,虽说只有需要判断的b开头的跳转指令需要运算,j开头的跳转指令直接跳,但是提升b开头的跳转指令,意义不大,下面已经将逻辑都写到一起了,剩下的让vivado自己去优化吧。

          在这里插入图片描述

        7. 至此,能改的都已经改了。

再次仿真

都pass了

在这里插入图片描述

结果对比

经过上述更改后(没有更改vivado的综合、实现策略),得到结果为:

在这里插入图片描述

优化后

无语了:与Lab10的结果相比,属实反向优化了

在这里插入图片描述

优化后

在这里插入图片描述
在这里插入图片描述

优化前(左)优化后(右)

但是,有一说一,逻辑延时确实从5.061ns减少到4.774ns,但是线延时涨得也太离谱了。

修改综合策略

修改前的Synthesis、Implementation策略都是vivado默认的策略。,这里修改如下:

在这里插入图片描述

最终结果如下:

在这里插入图片描述

在这里插入图片描述

也就是在逻辑延时和线延时之间平衡了一下

在这里插入图片描述

重新定制加法器

我现在重新考虑为什么线延时增加这么多,怀疑是加法器使用的是DSP,而不是Fabric,导致增加了不少飞线,于是本人重新定制了两个加法器:alu_adder、jump_br_adder:

在这里插入图片描述

在这里插入图片描述

综合与实现策略与34.8小节中的保持一致,最终结果如下:

在这里插入图片描述

在这里插入图片描述

可以看出,相对于优化前,还是改善了不少的,Lab10的WNS只有0.059ns,这里的WNS有0.281ns,后面就尽量不在这条关键路径上增加逻辑。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

优化前(左)优化后(DSP:中)优化后(Fabric:右)

优化后(Fabric)的最大Logic Dealy只有4.281ns

增加对照实验

我想观察下,在相同的综合策略下电路的优化,于是这里增加了“Lab10在34.8小节的策略”下的结果:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

可以看到,优化前,即使修改了综合策略,这里的Logic Delay最大是也达到了4.905ns

LAB10_Opt——后记

经过“优化”,逻辑延时降低了许多,而线延时增大了不少,但是使用Fabric的加法器,逻辑延时相对于线延时变化更大,最终时序有所改善。

LAB11——后记

AXI总线

以下内容多摘自《计算机体系结构基础》

AXI总线主设备的主要信号定义如表6.1所示。可以看到,AXI总线主要分为5个独立的通道,分别为写请求、写数据、写响应、读请求、读响应。每个通道采用握手协议独立传输。

表 6.1: AXI总线主要信号定义

引脚名称方向描述
AWID[n:0]输出写请求标识号
AWADDR[m:0]输出写请求地址
AWSIZE[2:0]输出写请求数据宽度
AWLEN[3:0]输出写请求数据长度
AWBURST[1:0]输出写请求类型
AWAVALID输出写请求有效信号
AWREADY输入写请求接收准备好信号
WID[n:0]输出写数据标识号,与写请求标识号对应
WDATA[j:0]输出写数据
WSTRB[k:0]输出写数据屏蔽信号,1位对应8个数据位
WVALID输出写数据有效信号
WREADY输入写数据接收准备好信号
BID[n:0]输入写响应标识号,与写请求标识号对应
BRESP[1:0]输入写响应状态
WVALID输入写响应有效信号
WREADY输出写响应接收准备好信号
ARID[n:0]输出读请求标识号
ARADDR[m:0]输出读请求地址
ARSIZE[2:0]输出读请求数据宽度
ARLEN[3:0]输出读请求数据长度
ARBURST[1:0]输出读请求类型
ARAVALID输出读请求有效信号
ARREADY输入读请求接收准备好信号
RID[n:0]输入读数据标识号,与读请求标识号对应
RDATA[j:0]输入读数据
RRESP[1:0]输入读响应状态
RVALID输入读数据有效信号
RREADY输出写读数据接收准备好信号

valid & ready

原书P198:“在AXI总线协议中,valid和ready这对握手信号置为有效时没有先后关系。事实止,AXI规范中还明确了每个通道中valid和ready的依赖关系:

  • valid 的置有效一定不能依赖于ready是否有效
  • ready的置有效可以依赖于valid是否有效。

上述严格限制是为了避免死锁。因此提醒大家:一定不要根据ready是否为1来决定如何置valid

原书P199:“主、从双方进行交互时,交互的是数据。这就涉及下面一系列问题:

  • 如何区分这些数据呢?通过地址来区分
  • 寻址的粒度最小到什么度?AXI总线的寻址粒度是字节
  • 每次交互的数据量可能有多有少,怎么办?主方会告诉从方传输数据量的大小”

原书P201:“当系统中集成了多个主方,或者主方支持乱序执行机制,那么总线乱序响应会显著提升性能。本书实践任务只是设计了一个单核的静态流水线CPU,通过总线的乱序响应获得的性能提升很少。”

类SRAM-AXI转接桥设计(1)

在这里插入图片描述

原书P203:“需要提醒的是,类SRAM-AXI转接桥中的类SRAM是从端而不是主端,千万不要把这两个接口上的信号方向弄反。

序号原书P204:转接桥设计要求
1aresetn有效期间,AXI Master端的所有valid类输出必须为0,所有ready类输出不能为X值
2AXI Master端的所有valid输出信号的置1逻辑中,一定不能允许组合逻辑来自同通道的ready输入信号(时序逻辑来自ready是可以的
3对于AXI Master端的所有ready输出信号,一定不能允许组合逻辑来自同一通道的valid输入信号(时序逻辑来自valid是可以的)
4无论读、写请求,类SRAM接口上的事务要和AXI接口上的事务严格一一对应。特别要注意,既不要将类SRAM接口上的一个事务在AXI总线上重复发送多次,也不要将类SRAM发来的多个地址连续的读事务或写事务合并成一个AXI总线事务。
5在AXI Master端的读请求、写请求、写数据通道上,如果Master输出的valid置为1的时候对应的ready是0,那么在ready信号变为1之前,不允许Master变更该通道的所有输出信号。这一点与类SRAM总线不同,类SRAM总线允许中途更改请求 (valid一旦拉高,必须等ready为1后,才可拉低,这是协议硬性规定的
6AXI Master端在发起读请求时,要先确保该请求与“已发出请求但尚未接收到写响应的写请求”不存在地址相关。最简单直接的解决方式是,只要有写请求,就停止发起读请求直至Master端收到写响应。更精细、高效的处理方式是记录那些“已发出请求但尚未接收到写响应的写请求”的地址、位宽、字节写使能等信息,后续请求查询并比较这些信息后再决定是否发出
7在转接桥内部不允许做写到读的数据前递,如有写后读相关,一定通过阻塞读来处理

类SRAM-AXI转接桥设计(2)

  1. 按照原书P196的要求,指令的ID号为0,数据的ID号为1:arid(0或1)/awid(固定为1)/wid(固定为1)。

    如arid(读请求)在取指是置为0,取数据时置为1。

    1. 由于取指的地址不连续性(比如beq指令),因此没法使用AXI的burst传输,因此burst固定为2’b01,即INCR:增量突发,传输过程中,地址递增。增加量取决AxSIZE的值。

      在这里插入图片描述

      因为不能burst传输,因此arlen/awlen固定为0:

      在这里插入图片描述

      1. 没有原子操作,也不需要原子锁,故置arlock/awlock为0:即Noraml access

        在这里插入图片描述

        1. 不需要Cache,故置arcache/awcache为0:

          在这里插入图片描述

        2. 指令和数据都不需要保护,因为没有区分权限,所以arprot/awprot置0:

          在这里插入图片描述

        3. 原书P196:忽略rresp、rlast:我理解的为什么要忽略:因为不需要burst传输,因此rlast其实是没有用的,即忽略掉。而rresp可以这样理解:AXI下面挂了好多的slaver,但是要读的地址恰好没有挂载slaver,因此读没有响应。但是这里是不存在这种情况的,所以忽略rresp,靠判断rvalid就可以了

          同理写响应通道的bresp也忽略。

        4. 原书P197:忽略bid:可以看到arid/awid/wid都没有忽略,唯一忽略的就是bid(写响应通道的id)。我的理解:因为CPU只向data_sram写数据,没有向inst_sram写数据,因此AXI工作正常的话,写响应的bid一定是data_sram的ID,因此没必要判断bid是0还是1,所以就忽略了。

综上,固定的输出如下:

在这里插入图片描述

后面只需要完成对剩下的输出控制即可:(粗暴地讲,Lab11只需要完成以下这些输出信号的逻辑设计)

在这里插入图片描述

类SRAM-AXI转接桥设计(3)

针对原书P204中的“转接桥的设计建议”分析

序号原书采纳纯属个人分析
1除了可以置为常值的信号外,所有AXI Master端的输出直接来自触发器Q端Yes通过34.2节的分析得知jump_addr处于关键路径上,而jump_addr最终赋给inst_sram_addr,倘若不是触发器的Q端输出,那么关键路径又要变恶劣了。(当然这里加了一级FF,那么对应的信号都要加上)
2为读数据预留缓存,从rdata端口上收到的数据先保存到这个预留缓存中Yes通过34.2节的分析得知“(EXEID旁路)id_rs_data”处于关键路径上。 那么可以推测:从AXI读出来的rdata倘若直接赋给load需要读的数据,那么“(MEMID旁路)id_rs_data”有可能成为新的关键路径,因此需要将预留缓存。
3AXI上最多支持几个“已完成读请求握手但数据尚未返回的读事务”,就预留同样数目的rdata缓存暂定Yes我不理解,因为数据返回后可以立即被用掉,不存在阻塞的情况。除非CPU是多发射的,否则不需要缓存。 截止LAB10_Opt设计的CPU,最多存在2个“已完成读请求握手但数据尚未返回的读事务”:一个是取指,一个是load指令。但是二者即使req后,接收的数据顺序发生颠倒,也没有任何影响(在LAB10_Opt中,二者就是顺序不定的) 标注一下:设计阶段不理解此项!!!
4取指对应的arid恒为0,load对应的arid恒为1No无需考虑,因为类SRAM接口已经将这种情况给屏蔽了,仅需要考虑类SRAM与AXI交互
5控制AXI读写的状态机分为独立的4个状态机:读请求通道一个,读响应通道一个,写请求和写数据共用一个,写响应一个Yes确实,对总线协议,还是用状态机控制比较好。设计成4个独立的状态机可以较好地与类SRAM接口配合。 感觉2个够了,目前没体会到4个状态机的好处:见36.10节
6类SRAM Slave端接的输入信号不用锁存后再使用Yes类SRAM的Master端已经缓存了,这里当然不需要再锁存了,毕竟也不是critical path
7数据端发来的类SRAM总线的读请求优先级固定高于取指端发来的类SRAM总线的读请求Yes没有这条规定不会影响功能(因为LAB10_Opt的类SRAM的取指和load的顺序就是不定的),但是会影响性能,因为load每晚一个周期就会阻塞一个周期流水线。
8类SRAM Slave端输出的addr_ok和data_ok信号若是来自组合逻辑,那么这个组合逻辑中不要引入AXI接口上的valid和ready信号。Yesdata_ok很好理解,因为数据都缓存了,data_ok肯定也要缓存的,否则时序就乱了 addr_ok:首先需要明确的是addr_ok与34.2节中提到的critical path是没有关系,二者没有重合的逻辑。其次,即使不引入一级寄存器也不会破坏时序,因为data_ok那里缓存了一级,addr_ok可以缓存也可以不缓存,若缓存一级,addr_ok仍然比data_ok先获得(即:不会出现同一请求data_ok比addr_ok先到达的情况)。 addr_ok:之所以要缓存一级,是因为要锁住req:在36.3节中第5条明确指出:valid一旦拉高,必须等ready为1后,才可拉低,这是协议硬性规定的。

状态机设计(1)

在36.3节中第5条明确指出:**valid一旦拉高,必须等ready为1后,才可拉低,这是协议硬性规定的。**这句话对时序和性能产生了影响,以至于本人再次理解了原书中的“缓存”2字。

针对锁存valid,粗暴地,本人想到以下4种方案

  1. 方案一:一旦类SRAM发起req,组合逻辑直接告诉类SRAM:addr_ok=1,这样类SRAM的状态机就会等待inst_sram_data_ok信号,至于何时等到,等到过程中遇到流水线stall、flush等操作,由类SRAM的状态机处理。这样做是最简单的,而且功能不受影响,更改的信号最少。但是这里可能会使critical path恶化,因为34.2节分析出的critical path的终点是inst_sram_addr,inst_sram_addr最终会给到AXI协议的araddr,而协议上并没有说AXI得到araddr后,怎么处理araddr,关键路径受到影响!!!

    1. 方案二:在方案一的基础上加一级寄存器,即一旦类SRAM发起req,组合逻辑直接告诉类SRAM:addr_ok=1,同时将req、inst_sram_addr进行寄存器保存,将寄存器保存一周期后的数据输出给AXI总线。这样就切断了critical path。但是流水线受影响严重:

      LAB10_Opt无阻塞时:reqdata_ok

      方案二无阻塞时:reqaxi的reqdata_ok。

      这样会使流水线的效率到少降低一半,因为在无阻塞情况下,类SRAM的req是当data_ok=1时才发出请求,那么就会形成例如以下7个周期的时序:

1234567
reqARAVALIDdata_ok
reqARAVALIDdata_ok
reqARAVALIDdata_ok

显然流水线性能骤降,所以方案二必然被否掉!

如果在“AXI输出数据类SRAM”中间插入一级寄存器,隔离AXI输出数据到类SRAM,那么会得到如下表中的情况:(如果不隔离,“MEMID的旁路”有可能成为新的critical path)

1234567
reqARAVALIDRVALIDdata_okk
reqARAVALIDRVALIDdata_okk
reqARAVALIDRVALIDdata_okk

这种情况的流水线效率更低,且目前“MEMID的旁路”还不是critical path,故暂不在输出插入寄存器。

  1. 方案三:修改类SRAM的状态机,使其满足36.3节中第5条:**valid一旦拉高,必须等ready为1后,才可拉低,这是协议硬性规定的。**这样功能是没问题,但是还是那个问题,inst_sram_addr直接给到AXI总线,critical pah肯定是会受影响的(我肓猜logic delay可能还好,net delay会增加不少)。但是本人不倾向于用这种方法:一方面,这违背了Lab11的初心,Lab11的目的是写“类SRAM-AXI转换桥”,而不是将类SRAM修改成AXI接口(这等同于逃避了问题本身)。另一方面,本人在LAB10_Opt后,痛定思痛,决定不再使critical path恶化,但是方案三肯定是会使其恶化的。
    1. emo了好久后,最终诞生了方案四:预取指。即在方案二的基础上增加预取指操作:提前将指令取出来(inst SRAM是只读不写的,因此不存在“写后读”这种情况,取出来的指令一定可以使用),并缓存,例如当前pc是要取32’hbfc0_0000地址,那么我就连续取出32’hbfc0_0004、32’hbfc0_0008、……地址处的指令。这样,即使插入了一级寄存器(见方案二),那么只要检测到缓存的地址中已有对应的地址,就可以立即取出:
1234567
reqqARAVALIDDdata_ok
ARAVALIDD()
reqdata_ok
ARAVALIDD()
reqdata_ok
ARAVALIDD()
reqdata_ok
ARAVALIDD()
reqdata_ok
ARAVALIDD()
req

只要遇到的不是跳转指令,上表中的流水线可以一直持续下去。

每遇到一个跳转指令,就浪费一个周期。(目前暂未想到如何才能不浪费这一个周期,就这样吧)

状态机设计(2)

在采用36.6节方案四的基础上,进一步做以下设计

  1. 针对inst_sram只缓存2个数据(为什么不是缓存1个:即使考虑流水线阻塞、flush、例外等对数据的影响,缓存个数1个也够了,但是我是想设计成一个FIFO的,就按2个缓存来吧),因为再大容量也没那么多数据可缓存(除非流水线阻塞的时候继续缓存):AXI最快也只能一个周期传来一个数据。
    1. 设计4个状态机:读请求、读响应、写请求、写响应

读请求状态机

在这里插入图片描述

状态机太过简单,不画时序图了

上面设计的状态机有问题,还是比较复杂的,遂画时序图:

在这里插入图片描述

在这里插入图片描述

序号条件说明
1
2
3
4
5
6
7
8
9
10

写请求状态机

在这里插入图片描述

状态机太过简单,不画时序图了

“写后读”细粒度

当遇到要写AXI时,使用寄存器暂存该寄存器的地址,同时拉高该寄存器的标志位,该标志位直至收到AXI的写响应确定写完后才可清零。

当我想要细粒度应对“写后读”时,发现已经没有这个必要了。在Lab10的类SRAM中,sw指令没有收到data_ok,是直接阻塞流水线的,不会允许后面的lw指令对外发出req。当时为了图简单直接照搬了取指的inst_sram_req:遇到data_ok后,才可发出req,没遇到就禁止对外发出req。

现在看来图简单也会给自己埋坑(甩锅环节:原书P195:“和取指相比,显然访存不需要考虑……,比取指更简单”)

既然如此,无法细粒度操作了,就这样吧。也不用判断“写后读”这种情况了,在Lab10的时候,类SRAM已经将这种情况给干掉了。

我现在似乎有点理解36.5节的第5条为什么要建议用4个状态机了,原书的意思可能是要考虑这种“写后读”等等一系列复杂的操作,但是我在类SRAM中,已经处理了这些问题。

LAB11——完结

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

关键路径没有变,还是LAB10_Opt中的关键路径,但是Logic Dealy增加了,Net Dealy减少了,怀疑是因为插入寄存器的缘故。

LAB11——BUG

AXI协议理解错了???

在这里插入图片描述

在这里插入图片描述

从波形中可以看出:

读握手成功地址依次为:bfc0_0000、bfc0_0004、bfc0_000C、bfc0_0008

读回数据依次为:2408ffff、2408ffff、100001ab、100001ab

序号读地址读出数据正确数据
1bfc0_00002408ffff2408ffff
2bfc0_00042408ffff2408ffff
3bfc0_000C100001ab00000000
4bfc0_0008100001ab100001ab

经本人检查,确定前几个周期只有AXI的读通道和读响应,其余通道没有req。实在不理解,为何bfc0_000C处的地址会读出bfc0_0008的数据。(araddr从bfc0_0004跳转至bfc0_000C是状态机跳转的正常情况,下一跳就跳回bfc0_0008了),我现在暂时怀疑是我对AXI协议的理解有问题。

去学习AXI协议去了:发现36.3节的第5条:在AXI Master端的读请求、写请求、写数据通道上,如果Master输出的valid置为1的时候对应的ready是0,那么在ready信号变为1之前,不允许Master变更该通道的所有输出信号。这一点与类SRAM总线不同,类SRAM总线允许中途更改请求(valid一旦拉高,必须等ready为1后,才可拉低,这是协议硬性规定的)

经排查是这个问题,所以36.8节设计的时序图和状态机也是有问题的。

jump_en出错

在这里插入图片描述

经排查,发现原因是:lw取出的数据,给jump命令时,产生了旁路。但是lw尚未data_ok,jump_addr的取指也没有data_ok。两者都没有data_ok就导致jump_addr_lat_vld被置为1(其实不能置为1,因为lw的旁路数据还没有到来)。

在这里插入图片描述

于是本人将mem_stall_en引入ID.v中,控制这个jump_en:当没有发生lwjump的旁路时阻塞时,才允许jump_en。

在这里插入图片描述

LAB12

根据原书P207提出的三种随机延迟类型进行测试:

本人使用以下参数:

修改CPU_CDE_SRAM/rlt/CONFREG/confreg.v里的宏RANDOM_SEED为以下三种:

`define RANDOM_SEED {7’b1010101,16’h00FF}//无延迟:Lab11

`define RANDOM_SEED {7’b1010101,16’hA736}//长延迟

`define RANDOM_SEED {7’b1010101,16’h38FF}//短延迟

长延迟仿真:

在这里插入图片描述

短延迟仿真:

在这里插入图片描述

Lab13——后记

regfile设计

  1. 原书P219:“match0[0] = (s0_vpn2 === tlb_vpn2[0]) && ((s0_asid == tlb_asid[0]) || tlb_g[0]);”,我就再想:如果当前TLB中没有找到,但是同时写通道要write的数据就是要search的数据,按书中的进行判断岂不是就漏了?这样会不会引发一个问题:“判断没有search数据,导致触发例外,再次向TBL写该数据呢(注:这会与原书P214:“上面的查找算法要求命中的项数不能多于一项,这是由软件来保证的”)”
    1. 经过思考,本人认为不会发生上述情况,因为写TLB是通过例外触发实现的,一旦触发例外,说明流水线已经阻塞了,不会第2次读取,而一旦流水线重新启动,说明数据已经写回了,可以search到,就不会出现search到2个相同的数据的情况。
      1. 另一方面,考虑3.2节中的RF.v,这里也没有考虑要写的同时,读同一个数据(“写后读”问题),因为都通过旁路或阻塞进行解决了。

Lab13——完结

Lab13很简单,直接一次就通过了:

在这里插入图片描述

在这里插入图片描述

就几个小文件,这时序报告也没有意义。

LAB14——后记

tlb.v结构

TLB:translation lookaside buffer

Lab13实现的tlb.v应该放在哪个hierarchy呢?

  1. 原书P220:“TLB模块中只有两套查找逻辑,一套用于取指,另一套用于访存”
    1. 原书P220:“所有指令的取指和访存都有可能读TLB的内容用于虚实地址转换”
      1. 经过认真读取原书P220,可以知道如下情况:IF阶段的取指与TLB交互,EXE发起的访存命令与TLB交互,TLB例外与TLB有交互,TLB指令与TLB交互,
        1. 原书P221:“pre-IF级向TLB模块输入vpn2、odd_page和asid之后,当拍就能获得物理页号pfn。然后,将pfn和nextPC的[11:0]拼接起来就可以作为类SRAM接口上的请求地址进行输出了(jack注:unmapped除外)……”
        2. 综上,为了避免接口的复杂,将tlb.v置于与IF.v、ID.v的同级hirearchy下。这样IF.v出来的search port 0(我就决定0号的search port给取指用了,1号的给访存指令用)可以直接连上,EXE给出的访存地址也可以直接连上。
        3. 补充:根据TLB原理 - 知乎 (zhihu.com)中所述,TLB缓存的是虚拟地址与物理地址之间的映射关系,而下一章就是Cache,Cache缓存的就是数据了。明白这一点就清晰多了。

Lab14目标

不考虑TLB例外,只实现指令和寄存器

  1. 目标一:三条指令:TLBR、TLBWI、TLBP
    1. 目标二:四个寄存器:Index、EntryHi、EntryLo0、EntryLo1
      1. 42.3节~42.8节中的图片主要摘自:MD00091-2B-MIPS64PRA-AFP-06.03和MIPS32™ Architecture For Programmers Volume II: The MIPS32™ Instruction Set

TLBR

在这里插入图片描述

例外:无

  1. 参考书P220
    1. TLBR在WB阶段读TLB,并更新相关CP0寄存器
      1. 由于TLBR指令会更新EntryHi寄存器的asid域,但是所有指令的取指和访存指令的访存器都有可能读取EntryHi寄存器的asid域去查TLB,因此要解决与TLBWI指令一样的冲突问题。
        1. 具体解决措施为:当在ID阶段检测到TLBR指令后,将if_flush、id_flush置位。当在EXE阶段检测到TLBR时,将if_flush、id_flush、exe_flush置位,在MEM阶段检测到TLBR时,将if_flush、id_flush、exe_flush、mem_flush置位,当在WB阶段检测到TLBR时,将if_flush、id_flush、exe_flush、mem_flush、wb_flush置位。在WB阶段检测到TLBR的下一个周期,重新更新next_pc,更新next_pc后,才取指。

在这里插入图片描述

具体如上图:INS6是TLBR指令,在ID阶段检测出该指令后即进行if_flush、id_flush操作。在WB阶段检测到TLBR后,将(wb_pc_ff1 + 3’h4)赋给next_pc(因为AXI可能阻塞了,此时if_pc不确定是PC6还是PC7,上图的波形图是按没有任何AXI阻塞进行绘制的)。在WB阶段检测到tblr指令的下一个周期,才启动流水线继续流水。

TLBWI

在这里插入图片描述

例外:无

  1. 参考书P220
    1. TLBWI在WB阶段才写TLB,
      1. 所有指令的取指和访存指令的访存都有可能读TLB的内容用于虚实地址转换,而TLBWI的写发生在WB级,那么这种写后读相关会引发冲突。该冲突的处理与TLBR指令的处理相同。

TLBP

在这里插入图片描述

例外:无

  1. 参考书P220
    1. 在EXE阶段复用访存的tlb接口,对TLB发起查找请求
      1. TLBP与MTC0的“写后读”冲突,直接通过阻塞进行解决

Index

在这里插入图片描述

在这里插入图片描述

EntryHi

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

EntryLo0/EntryLo1

在这里插入图片描述

在这里插入图片描述

LAB14——Debug

tlbwi/tlbr跳转地址

在这里插入图片描述

一直卡在0xbfc00828,经排查发现WB阶段的跳转地址敲错了

保留指令例外

在这里插入图片描述

debug半天没找到原因,最后发现EXE阶段报了保留指令例外,才想起来忘记更新保留指令了:

在这里插入图片描述

在这里插入图片描述

更新后,pass了。

LAB14——完结

在这里插入图片描述
在这里插入图片描述

这里插入了两张PASS!!图,因为下图的exe_s1_odd_page我不知道要改为何值,改为exe_pc[12]与exe_pc[11]均PASS。估计要到Lab15才会用到exe_s1_odd_page吧。

在这里插入图片描述

最后贴上时序图

在这里插入图片描述

有点懵,相对Lab11:建立时间的裕度变好了,保持时间裕度变差了。

LAB15——后记

生成inst_ram.coe

根据书P225的要求,需要重新生成inst_ram.coe。

我将生成的部分保存成lab15(原书配套的代码是没有lab15的)

下图是更改前后对比(左:lab14,右:lab15)

在这里插入图片描述

为了确保生成的inst_ram.coe导入成功,本人直接在Lab14上跑了该inst_ram.coe:

在这里插入图片描述

发生Error,说明导入成功。

odd_page

在44节中留了个问题,就是不知道exe_s1_odd_page该连何值。现在可以解答了,书P221:“next_PC的[12]连接到TLB模块的s0_odd_page”,所以在EXE阶段的exe_s1_odd_page应当连接的是访存地址的[12]。

ExcCode

原书P341的ExcCode没有TLB的内容(注:其实书P215~P216给出了TLB的例外),这里给出:

摘自:MD00091-2B-MIPS64PRA-AFP-06.03:

在这里插入图片描述

LAB15——Debug

小bug

就几个小bug:

  1. 综合报的critical warning:timing loop。发现是IF.v中的if_tlb_exc、if_flush形成的timing loop,直接将if_flush的等号右边的if_tlb_exc给删掉了(想了一下,删掉前后功能没有任何影响)
    1. 另一个bug是因为忘了将EXE.v中的addr给map了(中间写代码隔了一夜,早上醒来我以为map了,但是并没有),完善之后,就pass了。

关键路径

最终implementation出了意外:

在这里插入图片描述

在这里插入图片描述

在设计的时候,引入地址虚实转换时,就注定要在关键路径上添加逻辑,这个是无法避免的,但是出了时序问题,还是要解决的。

我直接修改了判断if_addr_is_unmapped的生成逻辑下述代码:

在这里插入图片描述

更改如下:在这里插入图片描述
在这里插入图片描述

然而最终结果反而更坏了。

在这里插入图片描述
在这里插入图片描述

确立关键路径

Lab10_Opt在34.2节中确立的路径如下:

exe_datadata_1和data_2减法(EXEID旁路)id_rs_data rs_bg_zero rs_bge_zero jump_bgezjump_bgez_addrID阶段的jump_to_addrIF阶段的jump_addr next_pcinst_sram_addr

Lab15的关键路径已经发生了变化,需要重新确立:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

根据上图的schematic,确定critical path如下:

tlb_entryhi_vpn2(重命名)wb_tlb_entryhi (截断)entryhi_vpn2 (重命名)if_s0_vpn2(重命名)s0_vpn2(tlb.v)match0(tlbnum_2_index.v)din在tlbnum_2_index.v中参与组合逻辑运算(tlbnum_2_index.v)dout_index(重命名)s0_indexs0_pfn (重命名)if_s0_vpn2(重命名)s0_vpn2if_s0_pfn if_addr_mappedinst_sram_addrinst_sram_addr_lat

而在下图中:有63出现,决定优化tlb.v、tlbnum_2_index.v

为了区别优化前后,优化后的代码使用Verilog宏LAB15_OPT控制

在这里插入图片描述

优化

  1. 尝试优化tlbnum_2_index.v,发现没有任何效果

    1. 其他逻辑无法优化(太简单了,没啥可优化的)

      1. 没办法,只能降频了(耻辱!)

        在这里插入图片描述

        改为了40MHz:

        在这里插入图片描述

LAB15——完结

在这里插入图片描述

Lab16——后记

本Lab16并不是真正的Lab16,应该是Lab16_pre,是Lab16的预处理,后面将会在Lab16的基础上进行Lab16.1的设计,Lab16.1才是完整的Lab16

感觉还是麻烦此,直接搞Lab16了。

概念明晰

摘自《计算机体系结构基础》第3版

  1. Cache索引的方式:直接相连、全相连、组相连

    下图的图c)就是二路组相连。访问 Cache 时地 址可分为 3 个部分: 偏移 (Offset)、 索引 (Index) 和标签 (Tag)。以两路组相 联为例, Cache 中第 0、 2、 4、 6 号位置为一路 (这里称为第 0 路), 第 1、 3、 5、 7 为另一路 (这里称为第 1 路), 每路 4 个 Cache 块。 对于内存的第 12 号块, 因为 12 除以 4 余数为 0, 所 以既可以把第 12 号块放到 Cache 第 0 路的第 0 号位置 (即 Cache 的第 0 号位置), 也可以放到 第 1 路的第 0 号位置 (即 Cache 的第 1 号位置), 如图 9. 24c 所示

    Offset是块内地址,在地址的低位,通过Offset来指定访问对象在块内的具体位置。

    Index是用来索引Cache块的,将其作为地址来访问Cache。

    地址的高位就是Cache的Tag,用于和Cache中保存的Tag进行比较,如果相等就给出命中信号Hit。

    在这里插入图片描述

  2. Cache与下一层存储的数据关系,即写策略,分为写穿透和写回两种

  • 写回Cache:存数指令修改下一层存储的值,如果将修改后的值暂时放在Cache中,当Cache替换回下一层存储时再写回,则称为写回Cache
  • 写穿透:如果每条存数指令都要立即更新下一层存储的值,则称为写穿透Cache。
    1. Cache的替换策略:随机替换、LRU替换、FIFO替换

Cache接口

  1. 原书P227给的参数:Cache容量为8KB,两路组相联,Cache行大小为16字节,CPU是按字节寻址。
    1. 由于Cache行大小为16字节,故
      1. Cache容量为8KB,即B,行大小为16字节,即B,又因为是2路组相连,故 ,同时(Index只指明是哪个行,并不指明是第0路的行还是第1路的行)
        1. 由于CPU是32位的CPU,最大可寻址容量为B。目前已有Offset和Index,现在只需要用找出Tag具体是哪一路就好了。由于采用二路组相连,因此一路的大小为Cache的一半,即4KB,即B,故。
  • 或者另一种思路:由于CPU是32bits,确定一个Byte,需要32bits,现在已经用掉的(Offset+Index=12bits),故还需要才能确定唯一的Byte。

临时语

春节期间没有写CPU,学了下VHDL。从本小节开始,后续内容尽量采用VHDL实现,只有在Verilog调用VHDL时才使用Verilog。

Cache规格

  1. 容量:8KB。一行存储:16个字节。组相连:二路组相连。

    1. 替换算法:伪随机替换算法

      1. 写策略:写回写分配(书P228:是因为这样写操作在发生Cache

        Miss时的处理流程和读操作的处理流程几乎是一样的)

        1. Cache访问地址的域划分:
31~1211~43~0
TagIndexoffset
  • offset:行内偏移,因为一行有16个字节,所以offset需要比特数:

  • Index:Cache总容量是8KB,所以二路组相连情况下,一路大小是4KB,4KB共有4KB/16B=行,所以Index需要8-bit

  • Tag:物理地址是32B,,所以Tag需要20-bit

    1. Cache不采用“关键字”优先

      1. Tag和Data并行访问:

        虚Index实Tag(简称VIPT):原书的思想(P228~P229)是分两个周期访问Cache,第一个周期是Index(地址的[11:4]),第二个周期是比较从TLB那里获取的[31:12]。

        但是目录mycpu_sram的接口如下:没有引出Index和TLB,因此,需要对其进行修改。

        在这里插入图片描述

        1. Cache采用阻塞式设计

Cache接口分析

首先,需要明确Cache的地位:CPUCacheAXI

Cache接口整体分析

在类SRAM-AXI转接桥设计(1)小节中,结构如下:

在这里插入图片描述

Cache与CPU流水线的交互接口

对比原书P233表10-2和P185表8-1,可以发现二者是相同的,具体如下:

除了size信号,其他信号都可以对得上。

原书P185表8-1原书P233表10-2
clkclk
reqvalid
wrop
size/
addrindex
tag
offset
wstrbwstrb
wdatawdata
addr_okaddr_ok
data_okdata_ok
rdatardata

Cache与AXI总线接口的交互接口

原书P234的Cache模块与AXI总线的交互接口中,显然不是完整的AXI接口,连最起码的设备id号都没有。为此需要重新分析与对应:

  1. 读和写请求不会同时发生,无论是ICache还是DCache,同一时间下最多只能是读or写二者之一,不会同时读和写。因此原书P234表10-3给的接口实际上相当于将原始的“读写合并”拆分为独立的“读通道”和“写通道”以适应AXI总线的需要
    1. 由于Lab11有“预取指”设计,影响了AXI的burst的传输需求,故需要重新规划。

重新规划

  1. 在Lab11的设计中,本人自己额外加了预取指,致使“类SRAM-AXI转接桥”中会连续给出读地址,但是Lab16的Cache要求AXI采用burst传输,一次读取16个字节,这意味着不需要Lab11的“预取指”设计了,故需要将其舍弃。
    1. 但是Lab11的“预取指”采用的是状态机控制,故状态机也对其舍弃。这样的话,需要重写整个“类SRAM-AXI转接桥”。
      1. 既然要重写“类SRAM-AXI转接桥”,故需要先写完转接桥后再加入Cache。但是既然都要重写了,为了方便后面加入Cache,本人准备采取以下措施:
  • 重写的类SRAM-AXI转接桥采用burst传输,一次读取16个Byte
  • 加入一个“伪Cache”,这个“伪Cache”只有一行,并且采用全相连的方式,只存储16个Byte的数据,采用FIFO的方式进行数据替换。
  • 先不进行Lab16的测试,而是先将写好的“类SRAM-AXI转接桥”进行Lab15的测试。
    1. 本人重新阅读了Lab11~Lab15的文档记录,发现仅Lab11有CPU与AXI接口的编写,Lab12~Lab15没有,因此在进行Lab16时,仅需参考Lab11的代码。
      1. 由于只有一行Cache,所以index对Cache来说是无用的。因为无论如何都只能读取那一行Cache

Cache架构(全相连)

新的结构框图:之前只是单纯的类SRAM-AXI转接桥,现在是多了ICache和DCache,仅当发现有miss的时候才访问AXI总线。由于ICache只读不写,DCache有读有写,因此可将DCache+AXI主端用来当作原书Lab16的测试。

在这里插入图片描述

SRAM_AXI_BRIDGE

|____CACHE_TOP(ICACHE)

| |____CACHE

| |____CACHE_TO_AXI

|____CACHE_TOP(DCACHE)

|____CACHE

|____CACHE_TO_AXI

Cache时序

原书P228

读时序:

  1. 第一个时钟周期:将请求虚地址中的[11:4]位作为索引值送往Cache,Cache中对应同一index的Cache行读出来
    1. 第二个时钟周期:得到Cache读出的两个Cache行的Tag信息(单周期出结果),将其锁存下来的物理地址[31:12]进行相等比较。

写时序与读时序相似。如果命中,则生成要写入的Index、路号、offset、写使能(写32位数据哪些字节)并将写数据传入Write Buffer。在下一拍,由Write Buffer向Cache发出请求,将Write Buffer里缓存的数据写入命中的那个Cache行的对应位置上,同时将这一Cache行的脏位D置为1。

细节设计

  1. 只要没hit,就4个数据全部替换
    1. 原书P229提出Write Buffer,避免引入RAM输出端到RAM输入端的路径。Wirte Buffer会在下一个周期更新Cache。
      1. 由于Write Buffer的存在,导致可能会出现如下情况:write_hitread_hit,且hit的是同一个位置。此时(原书只精确到字,没有精确到字节)只判断hit的是不是同一个字,如果是则阻塞读,否则不阻塞读操作。
        1. 注意Cache采用的是写回分配策略,见48.4节。如果写hit了,那Write Buffer会在下一周期,将该行修改,并且将该行的dirty位置1。如果没有hit,那么需要先发起读请求(地址与Write Buffer中的一样),这样会就hit了,再对hit的行进行修改。

状态机

主状态机:

序号c_staten_state条件
1IDLEIDLEvalid=0 or raw_stall (raw_stall:read after write stall。即与Hit Write冲突)
2IDLELOOKUPvalid=1 and raw_stall=0 (表示CPU发来Cache请求,且此时Write Buffer没有Hit Write)
3LOOKUPIDLE(valid=0 and hit=1)or(valid=1 and raw_stall =1 and hit=1) 这里的hit=read_hit or write_hit
4LOOKUPLOOKUP(valid=1 and hit=1 and raw_stall =0)
5LOOKUPMISShit=0
6MISSMISSaxi_wr_rdy=0
7MISSREPLACEaxi_wr_rdy=1 (即使是read_hit=0导致进入MISS,也要判断axi_wr_rdy是否为1。 因为wr_rdy=1表示AXI总线内部16字节写缓存为空 因为可能存在这种情况:写缓存的数据,正好是本次本次要读的数据。AXI读写分离,这样可能会造成读到的数据是old data(比如100个周期之前的缓存数据一直没有被写进AXI,一直在缓存),因此要即使是read_hi=0造成的MISS,也要等待wr_rdy=1) (另一方面,在REPLACE状态时,如果是write_hit=0,且被替换的行是dirty的,需要将dirty的行写入AXI,故这个状态是需要等待wr_rdy=1)
8REPLACEREPLACEaxi_rd_rdy=0
9REPLACEREFILLaxi_rd_rdy=1 (AXI总线接口模块反馈回来的axi_rd_rdy=1,表示对AXI总线发起的缺失Cache的读请求将被接收)
10REFILLREFILLaxi_ret_last=0 or axi_ret_valid=0 (说明最后一个数据还没有返回)
11REFILLIDLEaxi_ret_last=1 and axi_ret_valid=1 (说明最后一个AXI数据已经返回)

write buffer状态机:

序号wb_c_statewb_n_state条件
1S_IDLES_IDLEwrite_hit=0 or wb_is_empty=0 write_hit=0 or (write_hit=1 and writing_ram=1) (writing_ram即Write Buffer正在向Cache写数据)
2S_IDLES_WRITEwrite_hit=1 or wb_is_empty=1 write_hit=1 and writing_ram=0
3S_WRITES_WRITEwrite_hit=1 or wb_is_empty=1 writing_ram=1 or (write_hit=1 and writing_ram=0)
4S_WRITES_IDLEhit_wite=0 and wb_is_empty=0

与AXI接口

  1. 根据原书P233~224的所述的原则,只将AXI的部分接口放在Cache中,且所有信号均在一个周期处理完毕。
    1. AXI可以使用burst传输,每次传输个字节,也可以一次将128-bit的数据直接传输完毕

摘自:【AXI】解读AXI协议中的burst突发传输机制_axi burst突发-CSDN博客

在这里插入图片描述

  1. 但是根据书P234所言:“我们给出的设计建议是:对于读操作,AXI总线接口模块每个周期至多给Cache模块返回32位数据,Cache模块将返回的数据填入Cache的Bank RAM中或者直接将其返回给CPU流水线;对于写操作,Cache模块在一个周期内直接将一个Cache行的数据传给AXI总线接口模块,AXI总线接口模块内部设一个16字节的写缓存保存这些数,然后再慢慢地以Burst方式发出去。”

因此,Lab16采用直接一次性一个周期将所有数据写出。

Lab16——Debug

process(all)

在这里插入图片描述

我用的是vivado2019.2,不曾想不支持process(all),无语了,只能把敏感变量写全了(敏感变量好长啊)

Verilog翻译成VHDL

以下是chatGPT-3.5翻译:

+:32

assign a = b[c[3:2] +: 32];

翻译结果:

a <= b(to_integer(unsigned(c(3 downto 2))) to (to_integerunsigned(c(3 downto 2))) + 31));

重复位

assign a = {32{b}} & c;

翻译结果:

a <= (others => b) & c;

三目运算符

assign a = b ? c : d;

翻译结果:

process(b, c, d)

begin

a <= select b = ‘1’ ? c : d;

end process;

等号比较

这样写报错:因为(way0_tag = tag_lat)比较结果是一个boolean类型,即使std_logic,还是报错。

way0_hit <= std_logic(way0_tag = tag_lat) and (way0_v = ‘1’);

修改如下:

way0_hit <= ‘1’ when (way0_tag = tag_lat) and (way0_v = ‘1’) else ‘0’;

function中不允许使用process

process(addr_ok, raw_stall, index, index_lat, wb_index_lat)is begin

if c_state = REFILL and (random_num = ref_way) then

addr_out := index_lat;

elsif (wb_c_state = S_WRITE) and (wb_offset_lat_high2 = ref_offset_high2) then

addr_out := wb_index_lat;

elsif addr_ok = ‘1’ and op = '0’then

addr_out := index;

else

– useless : keep index_lat to reduce power

addr_out := index_lat;

end if;

end process;

修改如下:(仅仅是注释掉了process和end process,其他不作变化,if-else还在用)

–process(addr_ok, raw_stall, index, index_lat, wb_index_lat)is begin

if c_state = REFILL and (random_num = ref_way) then

addr_out := index_lat;

elsif (wb_c_state = S_WRITE) and (wb_offset_lat_high2 = ref_offset_high2) then

addr_out := wb_index_lat;

elsif addr_ok = ‘1’ and op = '0’then

addr_out := index;

else

– useless : keep index_lat to reduce power

addr_out := index_lat;

end if;

–end process;

variable使用:=

下面的wea是variable类型

wea <= (others => ‘0’);

修改如下:

wea := (others => ‘0’);

Replace

在这里插入图片描述

vivado的testbench报错,该报错信息对应cache_top.v中的replace_wrong信号:

在这里插入图片描述

replace_wrong与do_wr信号相关,而do_wr又来自axi_wr_req,遂观察波形:

在这里插入图片描述

从中可以看出在axi_wr_req发起使能的时候,写数据是错的。

而写数据等于replace_data:

在这里插入图片描述

于是检查replace_data:

在这里插入图片描述

数据是在c_state=LOOKUP的时候更新。于是本人怀疑是在c_state=LOOKUP的时候,同时wb_c_state尚有未更新完毕的数据,导致从RAM中读出的数据滞后一个周期。

最终问题定位如下:

连续向way0的0地址写数据(连续hit),突然tag发生了变化但是还是向way0的0地址写数据(此时miss)。这个时候c_state处于LOOKUP,而wb_c_state还处于S_WRITE。即c_state发现miss,需要将dirty数据写给axi,之后再更新数据。但是这里的dirty数据还不是最新的数据,因为wb_c_state还处于S_WRITE状态。

解决方式:从write buffer中引出一个旁路

然而引出旁路后还是有问题,经发现:

与replace_data相对比的数据的最高字节始终为0xff,这显然不科学:

在这里插入图片描述

于是乎,本人为了应对这个testbench,只好:强行将axi_wr_data的最高字节固定为0xff(只是暂时,后面肯定要改回来)

在这里插入图片描述

然而此处的波形就正常了。

replace报错

本人没有找到问题,后来查看波形,发现Refill的时候,w0_wea0~w0_wea3和w1_wea0~w1_wea3的值始终为全0。经排查发现是笔误:

在这里插入图片描述

红圈中的值应为(others => ‘1’)

tagv的初始化

记得对两块tagv的RAM进行全0的初始化:(虽然没初始化仿真结果也是对的)

在这里插入图片描述

Lab16——完结

在这里插入图片描述

约束的是100MHz,implementation结果为:

在这里插入图片描述
资源开销:

在这里插入图片描述

Lab17——后记

规划

  1. 为了贯彻落实48.6的计划,决定重写AXI接口。
    1. 由于之前的代码采用了inst_sram、data_sram的控制是写在一起的,不太方便独立出来单独控制,于是将原书P254~P255的任务二和任务三都在本次Lab中实现:即同时实现ICache和DCache。

CPU接口与Cache接口——size

  1. 根据48.5.2节中的表格,可以将CPU接口与Cache接口连接起来。

    1. 但是表格中缺少与CPU接口中的“size”的对应,经发现IF_STAGE的size与arsize相连。arsize/awsize都是突发数据长度。

      1. 原书P234有言:“我们给出的设计建议是:对于读操作,AXI总线接口模块每个周期至多给Cache模块返回32位数据,Cache模块将返回的数据填入Cache的Bank RAM中或者直接将其返回给CPU流水线;对于写操作,Cache模块在一个周期内直接将一个Cache行的数据传给AXI总线接口模块,AXI总线接口内部设一个16字节的写缓存保存这些数,然后再慢慢地以Burst方式发出去。”

        1. inst_sram_size的值固定为2’h2

        2. data_sram_size的值为:

          在这里插入图片描述

        3. 综上,需要在“sram_axi_bridge.vhd”文件中,单独增加对size这个信号的处理,很简单的处理,主要是对比特位的0填充控制。

        4. 然而在进一步设计的时候,本人怀疑这个信号无用,于是将data_sram_size的值修改为2’b10(inst_sram_size的值不用修改,因为之前就是2’b10),然后重新跑仿真,发现正确:

          在这里插入图片描述

        5. 于是决定直接忽略掉这两个信号

AXI接口与Cache接口

原书P196表8-4原书P234表10-3
arid
araddrrd_addr
arlenconst: 8’d3(表示传输3+1=4个数据量)
arsizerd_type(3’b010)
arburstconst: 2’b01
arlockconst: 2’b0
arcacheconst: 4’b0
arprotconst: 3’b0
arvalidrd_req
arreadyrd_rdy
rid
rdataret_data
rresp
rlastret_last
rvalidret_valid
rready
awidconst: 4’d1
awaddrwr_addr
awlenconst: 8’d3(表示传输3+1=4个数据量)
awsizerd_type(3’b010)
awburstconst: 2’b01
awlockconst: 2’b0
awcacheconst: 4’b1
awprotconst: 3’b0
awvalidwr_req and wr_rdy
awready
widconst: 4’d1
wdata
wstrbconst: 4’b1111
wlastconst: 1’b1
wvalid
wready
bid
bresp
bvalid
bready
wr_wstrb
wr_data
  1. AXI接口与Cache接口的设计可以参考之前的设计

    1. 读操作采用burst传输,一次传输32bits。

      1. 写操作使AXI模块内部的Cache,保存16个Bytes,然后再以Burst的方式发出去。

        1. 根据AMBA3 AXI手册中对arcache、awcache的描述:

          在这里插入图片描述

          在这里插入图片描述

          根据描述,对于arcache,将其设置为全0

          对于awcache,将其设置为4’b0001,即Bufferable only。

          至于arburst、awburst:二者都选择INCR。

          在这里插入图片描述

        2. 原书对rd_type、wr_type的设定绝对是有预谋的:原书的解释中rd_type/wr_type为3’b100时表示一个Cache行,而正好一行是16个字节。这与arsize、awsize刚好对应上:

          在这里插入图片描述

整体设计

  1. 之前的设计是对地址等信号寄存一拍再进行处理,本次设计打算不优化这条关键路径了,直接进行处理,这样最简单。

    1. 针对AXI的读:数据Cache的优先级高于指令Cache

      1. 针对AXI的读:虽然AXI支持连续burst,但是为了方便控制,在第一次burst结束之前(即ret_last回来之前)禁止发起第二次burst传输。不需要,靠rid判断就行了,也不麻烦。

        1. AXI的Burst写时序:

          在这里插入图片描述

Lab17——Debug

不添加ICache,直接跑

原书P254有言:“强烈推荐采用以下方式:先不添加ICache,运行func_all94通过,以确保TLB模块添加后不会影响原有功能的正确性。另外,本任务需要修改AXI转换桥,以支持Burst传输。”

在这里插入图片描述

然后就没加ICache,然后果然就报错了

tagv的初始化

这两块tagv的RAM记得初始化全零,如49.5节所述

bug定位

bug:使用Lab17的代码直接跑func9中的coe,报错,报错结果与使用Lab14的结果一样,因此bug应该是在Lab14中留下的,需要首先解决Lab14的bug

  1. Lab12:func_lab9(带AXI的仿真)
    1. Lab13:tlb_verify(纯TLB,没有CPU)
      1. Lab14:测试TLB相关指令和CP0寄存器(只是测试TLB部分没有对CPU功能进行验证)
        1. Lab15:tlb_func(仅是测试TLB部分)

Lab13及之后的工程是加了TLB的,Lab12是没有加TLB的。Lab12跑func_lab9中的inst_ram.coe没有问题,但是Lab17跑的时候却有问题,而此时还没有将Cache添加进Lab17,因此bug应当是由TLB产生的。

为此,本人决定使用Lab14、Lab15的代码,先过一遍func9,从而定位bug。

为此,本人决定使用Lab14、Lab15的代码,先过一遍func_all94,从而定位bug。(过func9没有意义,因为func9的testbench没有考虑TLB,因此必须用func_all94)

时钟全Z

在这里插入图片描述

笔误,把tlb.v中的clk声明成了19-bit

最后定位了bug,原来是之前的错误设计代码忘记删掉了。

另外,本人也不打算继续在这个版本上进行debug了,因为后面要移至VHDL上。

bug解决

lw指令报错:

在这里插入图片描述

一直没有找到问题,直到发现有笔误:将后面的exe_addr_is_unmapped修改为data_sram_addr_tmp后就OK了

在这里插入图片描述

不添加ICache的情况下通过测试

在这里插入图片描述

添加ICache & DCache

跑不动了

中到bfc006f4时就跑不动了

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

此时卡在了ins_sw这条指令上,经排查发现exe_stall_en为1阻塞了流水线。

进一步发现,无论跑多久,data_sram_data_ok始终为0。

在这里插入图片描述

然后发现Cache的状态机一直卡在MISS状态了:

在这里插入图片描述

后来发现是之前的仿真没有清理掉,需要关闭vivado,删除所有生成的文件,然后两次打开vivado,再次启动仿真。

拼接bug

rd_buffer_cnt是一个3-bit的寄存器,本人需要将其左移,然后右边补零:

rd_buffer_cnt <= rd_buffer_cnt(1 downto 0) & ‘1’;

仿真结果却不是我想要的(要么是3’b0,要么是3’b1),为此修改如下:(终于有3’b111这样的值了)

rd_buffer_cnt(2 downto 1) <= rd_buffer_cnt(1 downto 0);

rd_buffer_cnt(0) <= ‘1’;

AXI的ret_last有问题

在这里插入图片描述
在这里插入图片描述

为何当arid=0的请求是正常的(返回4个数据之后跟着一个ret_last),但是arid=1的请求却是错误的:返回1个数据就跟着ret_last了。从波形上可以看到burst的相关设置都一样啊!无语了!

Lab17——完结

未完结,因为“AXI的ret_last有问题”。然而暂时没有时间去解决了(烂尾了=_=!)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Greate AUK

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

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

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

打赏作者

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

抵扣说明:

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

余额充值