Lab17 V14.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信号,用于将数据清零 |
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.0 | Lab13与Lab3~Lab12没有任何联系,是一个独立的个体,因此没有`define Lab13,且由于代码过与简单,不予绘制专门的电路图 |
V11.0 | Lab14 将tlb.v添加进工程 修改了IF、ID、EXE、MEM、WB等接口信号,用于tlb 添加了TLB的C0寄存器 |
V12.0 | Lab15(还是对应原书Lab14) 添加了tlbr、tlbwi、tlbp指令例外 取指、数据访问的MMU |
V13.0 | Lab16 Lab16_pre此版本为Lab16的预处理版本,主要进行了以下处理: 从此版本开始,后面的代码的独立模块使用VHDL进行编写,仅Verilog调用VHDL时使用Verilog进行编写。 重写“类SRAM-AXI转接桥”,支持AXI的burst传输 添加容量为16Byte的全相联ICache(该Cache只有一行)和DCache(同ICache) 使修改后的代码通过Lab15的测试 |
V14.0 | Lab17 将Lab16设计的Cache集成到CPU中,成为ICache 将Lab16设计的Cache集成到CPU中,成为DCache |
Top顶层
接口信号
MYCPU_AXI_TOP.v(new TOP)
名称 | 宽度 | 方向 | 描述 |
---|---|---|---|
中断 | |||
int | 6 | I | 恒为6’h0(我猜:这应该是中断信号) |
AXI 时钟与复位 | |||
aclk | 1 | I | 时钟信号,来自clk_pll的输出时钟 |
aresetn | 1 | I | 复位信号,低电平复位 |
AXI 写请求通道 | |||
awid[n:0] | 4 | O | 写请求标识号 |
awaddr[m:0] | 32 | O | 写请求地址 |
awsize[2:0] | 3 | O | 写请求数据宽度 |
awlen[3:0] | 8 | O | 写请求数据长度 |
awburst[1:0] | 2 | O | 写请求类型 |
awavalid | 1 | O | 写请求有效信号 |
awready | 1 | I | 写请求接收准备好信号 |
AXI 写数据通道 | |||
wid[n:0] | 4 | O | 写数据标识号,与写请求标识号对应 |
wdata[j:0] | 32 | O | 写数据 |
wstrb[k:0] | 4 | O | 写数据屏蔽信号,1位对应8个数据位 |
wvalid | 1 | O | 写数据有效信号 |
wready | 1 | I | 写数据接收准备好信号 |
AXI 写响应通道 | |||
bid[n:0] | 4 | I | 写响应标识号,与写请求标识号对应 |
bresp[1:0] | 2 | I | 写响应状态 |
wvalid | 1 | I | 写响应有效信号 |
wready | 1 | O | 写响应接收准备好信号 |
AXI 读请求通道 | |||
arid[n:0] | 4 | O | 读请求标识号 |
araddr[m:0] | 32 | O | 读请求地址 |
arsize[2:0] | 3 | O | 读请求数据宽度 |
arlen[3:0] | 8 | O | 读请求数据长度 |
arburst[1:0] | 2 | O | 读请求类型 |
aravalid | 1 | O | 读请求有效信号 |
arready | 1 | I | 读请求接收准备好信号 |
AXI 读响应通道 | |||
rid[n:0] | 4 | I | 读数据标识号,与读请求标识号对应 |
rdata[j:0] | 32 | I | 读数据 |
rresp[1:0] | 2 | I | 读响应状态 |
rvalid | 1 | I | 读数据有效信号 |
rready | 1 | O | 写读数据接收准备好信号 |
MYCPU_TOP.v(old TOP)
名称 | 宽度 | 方向 | 描述 |
---|---|---|---|
时钟与复位 | |||
clk | 1 | I | 时钟信号,来自clk_pll的输出时钟 |
resetn | 1 | I | 复位信号,低电平同步复位 |
取指端访存接口 | |||
inst_sram_req | 1 | O | 请求信号,为1时有读写请求,为1时无读写请求 |
inst_sram_wr | 1 | O | 为1表示该次是写请求,为0表示该次是读请求 |
inst_sram_size | 2 | O | 该次请求传输的字节数,0:1byte,1:2bytes,2:4bytes |
inst_sram_wstrb | 4 | O | 该次写请求的字节写使能 |
inst_sram_addr | 32 | O | 该次请求的地址 |
inst_sram_wdata | 32 | O | 该次写请求的写数据 |
inst_sram_addr_ok | 1 | I | 该次写请求的地址传输OK,读:地址被接收;写:地址和数据被接收 |
inst_sram_data_ok | 1 | I | 该次请求的数据传输OK,读:数据返回;写:数据写入完成 |
inst_sram_rdata | 32 | I | 该次写请求返回的读数据 |
数据端访存接口 | |||
data_sram_req | 1 | O | 请求信号,为1时有读写请求,为1时无读写请求 |
data_sram_wr | 1 | O | 为1表示该次是写请求,为0表示该次是读请求 |
data_sram_size | 2 | O | 该次请求传输的字节数,0:1byte,1:2bytes,2:4bytes |
data_sram_wstrb | 4 | O | 该次写请求的字节写使能 |
data_sram_addr | 32 | O | 该次请求的地址 |
data_sram_wdata | 32 | O | 该次写请求的写数据 |
data_sram_addr_ok | 1 | I | 该次写请求的地址传输OK,读:地址被接收;写:地址和数据被接收 |
data_sram_data_ok | 1 | I | 该次请求的数据传输OK,读:数据返回;写:数据写入完成 |
data_sram_rdata | 32 | I | 该次写请求返回的读数据 |
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_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冲突)
接口信号
名称 | 宽度 | 方向 | 描述 |
---|---|---|---|
时钟与复位 | |||
clk | 1 | I | 时钟信号,来自clk_pll的输出时钟 |
resetn | 1 | I | 复位信号,低电平同步复位 |
与TOP | |||
inst_sram_req | 1 | O | 请求信号,为1时有读写请求,为1时无读写请求 |
inst_sram_wr | 1 | O | 为1表示该次是写请求,为0表示该次是读请求 |
inst_sram_size | 2 | O | 该次请求传输的字节数,0:1byte,1:2bytes,2:4bytes |
inst_sram_wstrb | 4 | O | 该次写请求的字节写使能 |
inst_sram_addr | 32 | O | 该次请求的地址 |
inst_sram_wdata | 32 | O | 该次写请求的写数据 |
inst_sram_addr_ok | 1 | I | 该次写请求的地址传输OK,读:地址被接收;写:地址和数据被接收 |
inst_sram_data_ok | 1 | I | 该次请求的数据传输OK,读:数据返回;写:数据写入完成 |
inst_sram_rdata | 32 | I | 该次写请求返回的读数据 |
与TLB | |||
if_s0_vpn2 | 19 | O | tlb: s0_vpn2 |
if_s0_odd_page | 1 | O | tlb: s0_odd_pagd |
if_s0_asid | 8 | O | tlb: s0_asid |
if_s0_found | 1 | I | tlb: s0_found |
if_s0_index | $clog2(`TLBNUM) | I | tlb: s0_index |
if_s0_pfn | 20 | I | tlb: s0_pfn |
if_s0_c | 3 | I | tlb: s0_c |
if_s0_d | 1 | I | tlb: s0_d |
if_s0_v | 1 | I | tlb: s0_v |
与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) |
if_to_id_flush | 1 | O | IF传给ID的数据部分清零(PC不清零) |
id_tlb_exc | 1 | I | ID阶段的tlb 例外 |
与EXE | |||
exe_tlb_flush | 1 | I | EXE阶段检测到的tlb_flush使能信号 |
exe_inst_tlb_exc | 1 | I | EXE阶段的tlb 例外(为什么不叫exe_tlb_exc呢,是为了与EXE阶段检测到读/写SRAM时检测到的tlb_exc区别开) |
与MEM | |||
mem_tlb_flush | 1 | I | MEM阶段检测到的tlb_flush使能信号 |
mem_tlb_exc | 1 | I | MEM阶段的tlb 例外 |
与WB | |||
wb_jump_en | 1 | I | 检测到EXC,然后决定是否jump |
wb_jump_pc | 32 | I | 检测到EXC,jump的PC值 |
wb_exc_flush | 1 | I | WB阶段检测到EXC,对流水线清空 |
wb_tlb_flush | 1 | I | WB阶段检测到的tlb_flush使能信号 |
接口时序
ID.v
接口信号
名称 | 宽度 | 方向 | 描述 |
---|---|---|---|
时钟与复位 | |||
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读数据 |
与IF | |||
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) |
if_to_id_flush | 1 | I | IF传给ID的数据部分清零(PC不清零) |
id_tlb_exc | 1 | O | ID阶段的tlb 例外 |
与EXE | |||
exe_to_id_allowin | 1 | I | pipe allowin |
id_to_exe_vld | 1 | O | pipe valid |
id_to_exe_data | 179 | 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} {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_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 | 正在进行乘法或除法运算 |
exe_ins_mfc0 | 1 | I | 指令mfc0传递至EXE阶段 |
exe_ins_lwx | 1 | I | lwx类指令传递至EXE阶段 |
exe_tlb_flush | 1 | I | EXE阶段检测到的tlb_flush使能信号 |
与MEM | |||
mem_bypass_bus | 38 | I | {mem_rf_we:1, mem_rf_dst_addr:5, mem_rf_data:32} |
mem_ins_mfc0 | 1 | I | 指令mfc0传递至MEM阶段 |
mem_stall_en | 1 | I | MEM阶段的stall |
mem_tlb_flush | 1 | I | MEM阶段检测到的tlb_flush使能信号 |
与WB | |||
wb_to_rf_bus | 41 | I | {rf_we:4, rf_addr:5, rf_data:32} |
wb_exc_flush | 1 | I | WB阶段检测到EXC,对流水线清空 |
wb_c0_has_int | 1 | I | WB阶段的SSR检测到中断 |
wb_ins_mfc0 | 1 | I | 指令mfc0传递至WB阶段 |
wb_tlb_flush | 1 | I | WB阶段检测到的tlb_flush使能信号 |
接口信号(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 |
接口时序
电路设计
图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_req | 1 | O | 请求信号,为1时有读写请求,为1时无读写请求 |
data_sram_wr | 1 | O | 为1表示该次是写请求,为0表示该次是读请求 |
data_sram_size | 2 | O | 该次请求传输的字节数,0:1byte,1:2bytes,2:4bytes |
data_sram_wstrb | 4 | O | 该次写请求的字节写使能 |
data_sram_addr | 32 | O | 该次请求的地址 |
data_sram_wdata | 32 | O | 该次写请求的写数据 |
data_sram_addr_ok | 1 | I | 该次写请求的地址传输OK,读:地址被接收;写:地址和数据被接收 |
data_sram_data_ok | 1 | I | 该次请求的数据传输OK,读:数据返回;写:数据写入完成 |
data_sram_rdata | 32 | I | 该次写请求返回的读数据 |
与IF | |||
exe_inst_tlb_exc | 1 | O | EXE阶段的tlb 例外(为什么不叫exe_tlb_exc呢,是为了与EXE阶段检测到读/写SRAM时检测到的tlb_exc区别开) |
与ID | |||
exe_to_id_allowin | 1 | O | pipe allowin |
id_to_exe_vld | 1 | I | pipe valid |
id_to_exe_data | 179 | 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} {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_bus | 41 | O | {exe_rf_we:4, exe_rf_dst_addr:5, exe_rf_data:32} |
exe_ins_mfc0 | 1 | O | 指令mfc0传递至EXE阶段 |
exe_ins_lwx | 1 | O | lwx类指令传递至EXE阶段 |
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_to_id_allowin | 1 | I | pipe allowin |
exe_to_mem_vld | 1 | O | pipe valid |
exe_to_mem_data | 138 | O | {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中 | 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 |
mem_ins_mtc0 | 1 | I | MEM阶段的mtc0指令 |
mem_ins_tlbr | 1 | I | MEM阶段的tlbr指令 |
mem_ins_tlbwi | 1 | I | MEM阶段的tlbwi指令 |
mem_tlb_flush | 1 | I | MEM阶段的tlb_flush操作 |
与WB | |||
wb_exc_flush | 1 | I | WB阶段检测到EXC,对流水线清空 |
exe_tlbp_en | 1 | O | EXE的tlbp指令使能信号 |
entryhi_vpn2 | 19 | I | c0寄存器entryhi:vpn2 |
entryhi_asid | 8 | I | c0寄存器entryhi:asid |
wb_ins_mtc0 | 1 | I | WB阶段的mtc0指令 |
wb_ins_tlbr | 1 | I | WB阶段的tlbr指令 |
wb_ins_tlbwi | 1 | I | WB阶段的tlbwi指令 |
wb_tlb_flush | 1 | I | WB阶段的tlb_flush操作 |
与TLB | |||
exe_s1_vpn2 | 19 | O | tlb: s1_vpn2 |
exe_s1_odd_page | 1 | O | tlb: s1_odd_pagd |
exe_s1_asid | 8 | O | tlb: s1_asid |
exe_s1_found | 1 | I | tlb: s1_found |
exe_s1_index | $clog2(`TLBNUM) | I | tlb: s1_index |
exe_s1_pfn | 20 | I | tlb: s1_pfn |
exe_s1_c | 3 | I | tlb: s1_c |
exe_s1_d | 1 | I | tlb: s1_d |
exe_s1_v | 1 | I | tlb: s1_v |
与IF、ID | |||
exe_tlb_flush | 1 | O | EXE阶段检测到的tlb_flush使能信号 |
接口信号(ALU.v)
暂时不需要时钟和复位,纯组合逻辑
名称 | 宽度 | 方向 | 描述 |
---|---|---|---|
时钟与复位 | |||
clk | 1 | I | 时钟信号,来自clk_pll的输出时钟 |
resetn | 1 | I | 复位信号,低电平同步复位 |
与ID内部信号 | |||
alu_shamt | 6 | I | ALU移位(R-指令的shamt部分) |
alu_op | 19 | I | ALU操作(加、减、乘除、位运算) |
alu_add_ov | 1 | O | 加法/减法溢出 |
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的输出时钟 |
与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读数据 |
data_sram_data_ok | 1 | I | 该次请求的数据传输OK,读:数据返回;写:数据写入完成 |
与IF | |||
mem_tlb_exc | 1 | O | MEM阶段的tlb 例外 |
与ID | |||
mem_bypass_bus | 38 | O | {mem_rf_we:4, mem_rf_dst_addr:5, mem_rf_data:32} |
mem_ins_mfc0 | 1 | O | 指令mfc0传递至MEM阶段 |
mem_stall_en | 1 | O | MEM阶段的stall |
与EXE | |||
mem_to_exe_allowin | 1 | O | pipe allowin |
exe_to_mem_vld | 1 | I | pipe valid |
exe_to_mem_data | 138 | I | {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中 | 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 |
mem_ins_mtc0 | 1 | I | MEM阶段的mtc0指令 |
mem_ins_tlbr | 1 | I | MEM阶段的tlbr指令 |
mem_ins_tlbwi | 1 | I | MEM阶段的tlbwi指令 |
mem_tlb_flush | 1 | I | MEM阶段的tlb_flush操作 |
与WB | |||
wb_to_mem_allowin | 1 | I | pipe allowin |
mem_to_wb_vld | 1 | O | pipe valid |
mem_to_wb_data | 128 | O | { mem_to_wb_exc_bus:56, rf_we:4, rf_dst_addr:5, mem_result:32, pc:32(其实可以删掉pc,这里是debug显示用的,可以叫debug_pc)} |
wb_exc_flush | 1 | I | WB阶段检测至EXC,对流水线清空 |
wb_tlb_flush | 1 | I | WB阶段的tlb_flush清空操作 |
与IF\ID\EXE | |||
mem_tlb_flush | 1 | O | MEM阶段的tlb_flush清零操作 |
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的写数据(与原书保持一致) |
与IF | |||
wb_jump_en | 1 | I | 检测到EXC,然后决定是否jump |
wb_jump_pc | 32 | I | 检测到EXC,jump的PC值 |
wb_exc_flush | 1 | O | WB阶段检测到EXC,对流水线清空 |
exe_tlbp_en | 1 | I | EXE的tlbp指令使能信号 |
entryhi_vpn2 | 19 | O | c0寄存器entryhi:vpn2 |
entryhi_asid | 8 | O | c0寄存器entryhi:asid |
wb_ins_mtc0 | 1 | O | WB阶段的mtc0指令 |
wb_ins_tlbr | 1 | O | WB阶段的tlbr指令 |
wb_ins_tlbwi | 1 | O | WB阶段的tlbwi指令 |
wb_tlb_flush | 1 | O | WB阶段的tlb_flush操作 |
与MEM | |||
wb_to_mem_allowin | 1 | O | pipe allowin |
mem_to_wb_vld | 1 | I | pipe valid |
mem_to_wb_data | 128 | I | { 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_bus | 41 | O | {rf_we:4, rf_addr:5, rf_data:32} |
wb_exc_flush | 1 | O | WB阶段检测到EXC,对流水线清空 |
wb_c0_has_int | 1 | O | WB阶段的SSR检测到中断 |
wb_ins_mfc0 | 1 | O | 指令mfc0传递至WB阶段 |
与TLB | |||
exe_s1_found | 1 | I | EXE阶段TLB的s1_found |
exe_s1_index | $clog2(`TLBNUM) | I | EXE阶段TLB的s1_index |
wb_we | 1 | O | TLB的写端口 |
wb_w_index | $clog2(`TLBNUM) | O | TLB的写端口 |
wb_w_vpn2 | 19 | O | TLB的写端口 |
wb_w_asid | 8 | O | TLB的写端口 |
wb_w_g | 1 | O | TLB的写端口 |
wb_w_pfn0 | 20 | O | TLB的写端口 |
wb_w_c0 | 3 | O | TLB的写端口 |
wb_w_d0 | 1 | O | TLB的写端口 |
wb_w_v0 | 1 | O | TLB的写端口 |
wb_w_pfn1 | 20 | O | TLB的写端口 |
wb_w_c1 | 3 | O | TLB的写端口 |
wb_w_d1 | 1 | O | TLB的写端口 |
wb_w_v1 | 1 | O | TLB的写端口 |
wb_r_index | $clog2(`TLBNUM) | O | TLB的读端口 |
wb_r_vpn2 | 19 | I | TLB的读端口 |
wb_r_asid | 8 | I | TLB的读端口 |
wb_r_g | 1 | I | TLB的读端口 |
wb_r_pfn0 | 20 | I | TLB的读端口 |
wb_r_c0 | 3 | I | TLB的读端口 |
wb_r_d0 | 1 | I | TLB的读端口 |
wb_r_v0 | 1 | I | TLB的读端口 |
wb_r_pfn1 | 20 | I | TLB的读端口 |
wb_r_c1 | 3 | I | TLB的读端口 |
wb_r_d1 | 1 | I | TLB的读端口 |
wb_r_v1 | 1 | I | TLB的读端口 |
接口信号(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读数据 |
c0_has_int | 1 | O | 发生中断 |
c0_badvaddr_i | 32 | I | badvaddr的数据输入 |
LAB10_Opt——后记
cpu_clk约束的是50MHz,关键路径也是看该时钟。
关键路径确认
在implementation前,怀疑有以下关键路径:
- EXE阶段的加/减法生成的数据ID阶段的旁路判断
- 从ID阶段的jump_addr的生成(因为涉及32-bits的加法)到IF阶段的next_pc
- IF阶段的pc+4
- 从ID阶段的jump_addr的生成(因为涉及32-bits的加法)到IF阶段的next_pc
注:这里没有乘/除法,因为乘除法都是多周期的,应该不是关键路径,上述关键路径主要考虑的是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
组合逻辑前移
-
IF.v中的jump_addr的优先级是一定要低于wb_jump_pc的:
因此此处不予修改。
-
EXE.v中data_1与data_2的数据选择也无法优化,因为就过了两组数据选择器,即使前移,关键路径还是要过两级
-
ALU.v中的add_din1、add_din2、add_cin也无法优化:
-
ALU.v中的alu_out中的关键路径前移:
alu_out中,slt与sltu是涉及加法结果的,而且stl的组合逻辑比sltu更复杂,因此先首先判断alu_slt_mux,其次是alu_sltu_mux,最后是(alu_add | alu_sub_mux | alu_jb)
-
上述的alu_out最后通过旁路,成了ID.v中的exe_rf_data,下面开始针对exe_rf_data进行优化:
在旁路function中,感觉有个地方可以优化:
这里只有一个数据,如果依然通过{32{bypass_en}}进行与判断是不是不如使用数据选择器?(但是我感觉综合的结果应该是相同的,所以这里应该改不改不所谓)
-
调整旁路rs_data与rt_data中的EXE旁路的优先级:
-
优化了bge、ls、lse等逻辑(这些组合逻辑服务于jump_beqz、jmp_bltz等跳转指令):
具体优化为:之前rs_bge_zero的输入依赖于rs_eq_zero、rs_bg_zero,优化后所有的输入只与id_rs_data有关,相互之间没有任何依赖关系,并且对逻辑进行了人为地化简。
-
去掉了不必要的加法:
进一步,上面的加法也调用vivado的IP
-
jump_adder感觉没必要优化,虽说只有需要判断的b开头的跳转指令需要运算,j开头的跳转指令直接跳,但是提升b开头的跳转指令,意义不大,下面已经将逻辑都写到一起了,剩下的让vivado自己去优化吧。
-
至此,能改的都已经改了。
-
-
-
再次仿真
都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:转接桥设计要求 |
---|---|
1 | aresetn有效期间,AXI Master端的所有valid类输出必须为0,所有ready类输出不能为X值 |
2 | AXI 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后,才可拉低,这是协议硬性规定的) |
6 | AXI Master端在发起读请求时,要先确保该请求与“已发出请求但尚未接收到写响应的写请求”不存在地址相关。最简单直接的解决方式是,只要有写请求,就停止发起读请求直至Master端收到写响应。更精细、高效的处理方式是记录那些“已发出请求但尚未接收到写响应的写请求”的地址、位宽、字节写使能等信息,后续请求查询并比较这些信息后再决定是否发出 |
7 | 在转接桥内部不允许做写到读的数据前递,如有写后读相关,一定通过阻塞读来处理 |
类SRAM-AXI转接桥设计(2)
-
按照原书P196的要求,指令的ID号为0,数据的ID号为1:arid(0或1)/awid(固定为1)/wid(固定为1)。
如arid(读请求)在取指是置为0,取数据时置为1。
-
由于取指的地址不连续性(比如beq指令),因此没法使用AXI的burst传输,因此burst固定为2’b01,即INCR:增量突发,传输过程中,地址递增。增加量取决AxSIZE的值。
因为不能burst传输,因此arlen/awlen固定为0:
-
没有原子操作,也不需要原子锁,故置arlock/awlock为0:即Noraml access
-
不需要Cache,故置arcache/awcache为0:
-
指令和数据都不需要保护,因为没有区分权限,所以arprot/awprot置0:
-
原书P196:忽略rresp、rlast:我理解的为什么要忽略:因为不需要burst传输,因此rlast其实是没有用的,即忽略掉。而rresp可以这样理解:AXI下面挂了好多的slaver,但是要读的地址恰好没有挂载slaver,因此读没有响应。但是这里是不存在这种情况的,所以忽略rresp,靠判断rvalid就可以了
同理写响应通道的bresp也忽略。
-
原书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”有可能成为新的关键路径,因此需要将预留缓存。 |
3 | AXI上最多支持几个“已完成读请求握手但数据尚未返回的读事务”,就预留同样数目的rdata缓存 | 暂定Yes | 我不理解,因为数据返回后可以立即被用掉,不存在阻塞的情况。除非CPU是多发射的,否则不需要缓存。 截止LAB10_Opt设计的CPU,最多存在2个“已完成读请求握手但数据尚未返回的读事务”:一个是取指,一个是load指令。但是二者即使req后,接收的数据顺序发生颠倒,也没有任何影响(在LAB10_Opt中,二者就是顺序不定的) 标注一下:设计阶段不理解此项!!! |
4 | 取指对应的arid恒为0,load对应的arid恒为1 | No | 无需考虑,因为类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信号。 | Yes | data_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种方案
-
方案一:一旦类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,关键路径受到影响!!!
-
方案二:在方案一的基础上加一级寄存器,即一旦类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个周期的时序:
-
1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|
req | ARAVALID | data_ok | ||||
req | ARAVALID | data_ok | ||||
req | ARAVALID | data_ok |
显然流水线性能骤降,所以方案二必然被否掉!
如果在“AXI输出数据类SRAM”中间插入一级寄存器,隔离AXI输出数据到类SRAM,那么会得到如下表中的情况:(如果不隔离,“MEMID的旁路”有可能成为新的critical path)
1 | 2 | 3 | 4 | 5 | 6 | 7 | |||
---|---|---|---|---|---|---|---|---|---|
req | ARAVALID | RVALID | data_okk | ||||||
req | ARAVALID | RVALID | data_okk | ||||||
req | ARAVALID | RVALID | data_okk |
这种情况的流水线效率更低,且目前“MEMID的旁路”还不是critical path,故暂不在输出插入寄存器。
- 方案三:修改类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恶化,但是方案三肯定是会使其恶化的。
- emo了好久后,最终诞生了方案四:预取指。即在方案二的基础上增加预取指操作:提前将指令取出来(inst SRAM是只读不写的,因此不存在“写后读”这种情况,取出来的指令一定可以使用),并缓存,例如当前pc是要取32’hbfc0_0000地址,那么我就连续取出32’hbfc0_0004、32’hbfc0_0008、……地址处的指令。这样,即使插入了一级寄存器(见方案二),那么只要检测到缓存的地址中已有对应的地址,就可以立即取出:
1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|
reqq | ARAVALIDD | data_ok | ||||
ARAVALIDD() | ||||||
req | data_ok | |||||
ARAVALIDD() | ||||||
req | data_ok | |||||
ARAVALIDD() | ||||||
req | data_ok | |||||
ARAVALIDD() | ||||||
req | data_ok | |||||
ARAVALIDD() | ||||||
req |
只要遇到的不是跳转指令,上表中的流水线可以一直持续下去。
每遇到一个跳转指令,就浪费一个周期。(目前暂未想到如何才能不浪费这一个周期,就这样吧)
状态机设计(2)
在采用36.6节方案四的基础上,进一步做以下设计
- 针对inst_sram只缓存2个数据(为什么不是缓存1个:即使考虑流水线阻塞、flush、例外等对数据的影响,缓存个数1个也够了,但是我是想设计成一个FIFO的,就按2个缓存来吧),因为再大容量也没那么多数据可缓存(除非流水线阻塞的时候继续缓存):AXI最快也只能一个周期传来一个数据。
- 设计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
序号 | 读地址 | 读出数据 | 正确数据 |
---|---|---|---|
1 | bfc0_0000 | 2408ffff | 2408ffff |
2 | bfc0_0004 | 2408ffff | 2408ffff |
3 | bfc0_000C | 100001ab | 00000000 |
4 | bfc0_0008 | 100001ab | 100001ab |
经本人检查,确定前几个周期只有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设计
- 原书P219:“match0[0] = (s0_vpn2 === tlb_vpn2[0]) && ((s0_asid == tlb_asid[0]) || tlb_g[0]);”,我就再想:如果当前TLB中没有找到,但是同时写通道要write的数据就是要search的数据,按书中的进行判断岂不是就漏了?这样会不会引发一个问题:“判断没有search数据,导致触发例外,再次向TBL写该数据呢(注:这会与原书P214:“上面的查找算法要求命中的项数不能多于一项,这是由软件来保证的”)”
- 经过思考,本人认为不会发生上述情况,因为写TLB是通过例外触发实现的,一旦触发例外,说明流水线已经阻塞了,不会第2次读取,而一旦流水线重新启动,说明数据已经写回了,可以search到,就不会出现search到2个相同的数据的情况。
- 另一方面,考虑3.2节中的RF.v,这里也没有考虑要写的同时,读同一个数据(“写后读”问题),因为都通过旁路或阻塞进行解决了。
- 经过思考,本人认为不会发生上述情况,因为写TLB是通过例外触发实现的,一旦触发例外,说明流水线已经阻塞了,不会第2次读取,而一旦流水线重新启动,说明数据已经写回了,可以search到,就不会出现search到2个相同的数据的情况。
Lab13——完结
Lab13很简单,直接一次就通过了:
就几个小文件,这时序报告也没有意义。
LAB14——后记
tlb.v结构
TLB:translation lookaside buffer
Lab13实现的tlb.v应该放在哪个hierarchy呢?
- 原书P220:“TLB模块中只有两套查找逻辑,一套用于取指,另一套用于访存”
- 原书P220:“所有指令的取指和访存都有可能读TLB的内容用于虚实地址转换”
- 经过认真读取原书P220,可以知道如下情况:IF阶段的取指与TLB交互,EXE发起的访存命令与TLB交互,TLB例外与TLB有交互,TLB指令与TLB交互,
- 原书P221:“pre-IF级向TLB模块输入vpn2、odd_page和asid之后,当拍就能获得物理页号pfn。然后,将pfn和nextPC的[11:0]拼接起来就可以作为类SRAM接口上的请求地址进行输出了(jack注:unmapped除外)……”
- 综上,为了避免接口的复杂,将tlb.v置于与IF.v、ID.v的同级hirearchy下。这样IF.v出来的search port 0(我就决定0号的search port给取指用了,1号的给访存指令用)可以直接连上,EXE给出的访存地址也可以直接连上。
- 补充:根据TLB原理 - 知乎 (zhihu.com)中所述,TLB缓存的是虚拟地址与物理地址之间的映射关系,而下一章就是Cache,Cache缓存的就是数据了。明白这一点就清晰多了。
- 经过认真读取原书P220,可以知道如下情况:IF阶段的取指与TLB交互,EXE发起的访存命令与TLB交互,TLB例外与TLB有交互,TLB指令与TLB交互,
- 原书P220:“所有指令的取指和访存都有可能读TLB的内容用于虚实地址转换”
Lab14目标
不考虑TLB例外,只实现指令和寄存器
- 目标一:三条指令:TLBR、TLBWI、TLBP
- 目标二:四个寄存器:Index、EntryHi、EntryLo0、EntryLo1
- 42.3节~42.8节中的图片主要摘自:MD00091-2B-MIPS64PRA-AFP-06.03和MIPS32™ Architecture For Programmers Volume II: The MIPS32™ Instruction Set
- 目标二:四个寄存器:Index、EntryHi、EntryLo0、EntryLo1
TLBR
例外:无
- 参考书P220
- TLBR在WB阶段读TLB,并更新相关CP0寄存器
- 由于TLBR指令会更新EntryHi寄存器的asid域,但是所有指令的取指和访存指令的访存器都有可能读取EntryHi寄存器的asid域去查TLB,因此要解决与TLBWI指令一样的冲突问题。
- 具体解决措施为:当在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后,才取指。
- 由于TLBR指令会更新EntryHi寄存器的asid域,但是所有指令的取指和访存指令的访存器都有可能读取EntryHi寄存器的asid域去查TLB,因此要解决与TLBWI指令一样的冲突问题。
- TLBR在WB阶段读TLB,并更新相关CP0寄存器
具体如上图: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
例外:无
- 参考书P220
- TLBWI在WB阶段才写TLB,
- 所有指令的取指和访存指令的访存都有可能读TLB的内容用于虚实地址转换,而TLBWI的写发生在WB级,那么这种写后读相关会引发冲突。该冲突的处理与TLBR指令的处理相同。
- TLBWI在WB阶段才写TLB,
TLBP
例外:无
- 参考书P220
- 在EXE阶段复用访存的tlb接口,对TLB发起查找请求
- TLBP与MTC0的“写后读”冲突,直接通过阻塞进行解决
- 在EXE阶段复用访存的tlb接口,对TLB发起查找请求
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:
- 综合报的critical warning:timing loop。发现是IF.v中的if_tlb_exc、if_flush形成的timing loop,直接将if_flush的等号右边的if_tlb_exc给删掉了(想了一下,删掉前后功能没有任何影响)
- 另一个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控制
优化
-
尝试优化tlbnum_2_index.v,发现没有任何效果
-
其他逻辑无法优化(太简单了,没啥可优化的)
-
没办法,只能降频了(耻辱!)
改为了40MHz:
-
-
LAB15——完结
Lab16——后记
本Lab16并不是真正的Lab16,应该是Lab16_pre,是Lab16的预处理,后面将会在Lab16的基础上进行Lab16.1的设计,Lab16.1才是完整的Lab16
感觉还是麻烦此,直接搞Lab16了。
概念明晰
摘自《计算机体系结构基础》第3版
-
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。
-
Cache与下一层存储的数据关系,即写策略,分为写穿透和写回两种
- 写回Cache:存数指令修改下一层存储的值,如果将修改后的值暂时放在Cache中,当Cache替换回下一层存储时再写回,则称为写回Cache
- 写穿透:如果每条存数指令都要立即更新下一层存储的值,则称为写穿透Cache。
- Cache的替换策略:随机替换、LRU替换、FIFO替换
Cache接口
- 原书P227给的参数:Cache容量为8KB,两路组相联,Cache行大小为16字节,CPU是按字节寻址。
- 由于Cache行大小为16字节,故
- Cache容量为8KB,即B,行大小为16字节,即B,又因为是2路组相连,故 ,同时(Index只指明是哪个行,并不指明是第0路的行还是第1路的行)
- 由于CPU是32位的CPU,最大可寻址容量为B。目前已有Offset和Index,现在只需要用找出Tag具体是哪一路就好了。由于采用二路组相连,因此一路的大小为Cache的一半,即4KB,即B,故。
- Cache容量为8KB,即B,行大小为16字节,即B,又因为是2路组相连,故 ,同时(Index只指明是哪个行,并不指明是第0路的行还是第1路的行)
- 由于Cache行大小为16字节,故
- 或者另一种思路:由于CPU是32bits,确定一个Byte,需要32bits,现在已经用掉的(Offset+Index=12bits),故还需要才能确定唯一的Byte。
临时语
春节期间没有写CPU,学了下VHDL。从本小节开始,后续内容尽量采用VHDL实现,只有在Verilog调用VHDL时才使用Verilog。
Cache规格
-
容量:8KB。一行存储:16个字节。组相连:二路组相连。
-
替换算法:伪随机替换算法
-
写策略:写回写分配(书P228:是因为这样写操作在发生Cache
Miss时的处理流程和读操作的处理流程几乎是一样的)
- Cache访问地址的域划分:
-
-
31~12 | 11~4 | 3~0 |
---|---|---|
Tag | Index | offset |
-
offset:行内偏移,因为一行有16个字节,所以offset需要比特数:
-
Index:Cache总容量是8KB,所以二路组相连情况下,一路大小是4KB,4KB共有4KB/16B=行,所以Index需要8-bit
-
Tag:物理地址是32B,,所以Tag需要20-bit
-
Cache不采用“关键字”优先
-
Tag和Data并行访问:
虚Index实Tag(简称VIPT):原书的思想(P228~P229)是分两个周期访问Cache,第一个周期是Index(地址的[11:4]),第二个周期是比较从TLB那里获取的[31:12]。
但是目录mycpu_sram的接口如下:没有引出Index和TLB,因此,需要对其进行修改。
- Cache采用阻塞式设计
-
-
Cache接口分析
首先,需要明确Cache的地位:CPUCacheAXI
Cache接口整体分析
在类SRAM-AXI转接桥设计(1)小节中,结构如下:
Cache与CPU流水线的交互接口
对比原书P233表10-2和P185表8-1,可以发现二者是相同的,具体如下:
除了size信号,其他信号都可以对得上。
原书P185表8-1 | 原书P233表10-2 |
---|---|
clk | clk |
req | valid |
wr | op |
size | / |
addr | index |
tag | |
offset | |
wstrb | wstrb |
wdata | wdata |
addr_ok | addr_ok |
data_ok | data_ok |
rdata | rdata |
Cache与AXI总线接口的交互接口
原书P234的Cache模块与AXI总线的交互接口中,显然不是完整的AXI接口,连最起码的设备id号都没有。为此需要重新分析与对应:
- 读和写请求不会同时发生,无论是ICache还是DCache,同一时间下最多只能是读or写二者之一,不会同时读和写。因此原书P234表10-3给的接口实际上相当于将原始的“读写合并”拆分为独立的“读通道”和“写通道”以适应AXI总线的需要
- 由于Lab11有“预取指”设计,影响了AXI的burst的传输需求,故需要重新规划。
重新规划
- 在Lab11的设计中,本人自己额外加了预取指,致使“类SRAM-AXI转接桥”中会连续给出读地址,但是Lab16的Cache要求AXI采用burst传输,一次读取16个字节,这意味着不需要Lab11的“预取指”设计了,故需要将其舍弃。
- 但是Lab11的“预取指”采用的是状态机控制,故状态机也对其舍弃。这样的话,需要重写整个“类SRAM-AXI转接桥”。
- 既然要重写“类SRAM-AXI转接桥”,故需要先写完转接桥后再加入Cache。但是既然都要重写了,为了方便后面加入Cache,本人准备采取以下措施:
- 但是Lab11的“预取指”采用的是状态机控制,故状态机也对其舍弃。这样的话,需要重写整个“类SRAM-AXI转接桥”。
- 重写的类SRAM-AXI转接桥采用burst传输,一次读取16个Byte
- 加入一个“伪Cache”,这个“伪Cache”只有一行,并且采用全相连的方式,只存储16个Byte的数据,采用FIFO的方式进行数据替换。
- 先不进行Lab16的测试,而是先将写好的“类SRAM-AXI转接桥”进行Lab15的测试。
- 本人重新阅读了Lab11~Lab15的文档记录,发现仅Lab11有CPU与AXI接口的编写,Lab12~Lab15没有,因此在进行Lab16时,仅需参考Lab11的代码。
- 由于只有一行Cache,所以index对Cache来说是无用的。因为无论如何都只能读取那一行Cache
- 本人重新阅读了Lab11~Lab15的文档记录,发现仅Lab11有CPU与AXI接口的编写,Lab12~Lab15没有,因此在进行Lab16时,仅需参考Lab11的代码。
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
读时序:
- 第一个时钟周期:将请求虚地址中的[11:4]位作为索引值送往Cache,Cache中对应同一index的Cache行读出来
- 第二个时钟周期:得到Cache读出的两个Cache行的Tag信息(单周期出结果),将其锁存下来的物理地址[31:12]进行相等比较。
写时序与读时序相似。如果命中,则生成要写入的Index、路号、offset、写使能(写32位数据哪些字节)并将写数据传入Write Buffer。在下一拍,由Write Buffer向Cache发出请求,将Write Buffer里缓存的数据写入命中的那个Cache行的对应位置上,同时将这一Cache行的脏位D置为1。
细节设计
- 只要没hit,就4个数据全部替换
- 原书P229提出Write Buffer,避免引入RAM输出端到RAM输入端的路径。Wirte Buffer会在下一个周期更新Cache。
- 由于Write Buffer的存在,导致可能会出现如下情况:write_hitread_hit,且hit的是同一个位置。此时(原书只精确到字,没有精确到字节)只判断hit的是不是同一个字,如果是则阻塞读,否则不阻塞读操作。
- 注意Cache采用的是写回分配策略,见48.4节。如果写hit了,那Write Buffer会在下一周期,将该行修改,并且将该行的dirty位置1。如果没有hit,那么需要先发起读请求(地址与Write Buffer中的一样),这样会就hit了,再对hit的行进行修改。
- 由于Write Buffer的存在,导致可能会出现如下情况:write_hitread_hit,且hit的是同一个位置。此时(原书只精确到字,没有精确到字节)只判断hit的是不是同一个字,如果是则阻塞读,否则不阻塞读操作。
- 原书P229提出Write Buffer,避免引入RAM输出端到RAM输入端的路径。Wirte Buffer会在下一个周期更新Cache。
状态机
主状态机:
序号 | c_state | n_state | 条件 |
---|---|---|---|
1 | IDLE | IDLE | valid=0 or raw_stall (raw_stall:read after write stall。即与Hit Write冲突) |
2 | IDLE | LOOKUP | valid=1 and raw_stall=0 (表示CPU发来Cache请求,且此时Write Buffer没有Hit Write) |
3 | LOOKUP | IDLE | (valid=0 and hit=1)or(valid=1 and raw_stall =1 and hit=1) 这里的hit=read_hit or write_hit |
4 | LOOKUP | LOOKUP | (valid=1 and hit=1 and raw_stall =0) |
5 | LOOKUP | MISS | hit=0 |
6 | MISS | MISS | axi_wr_rdy=0 |
7 | MISS | REPLACE | axi_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) |
8 | REPLACE | REPLACE | axi_rd_rdy=0 |
9 | REPLACE | REFILL | axi_rd_rdy=1 (AXI总线接口模块反馈回来的axi_rd_rdy=1,表示对AXI总线发起的缺失Cache的读请求将被接收) |
10 | REFILL | REFILL | axi_ret_last=0 or axi_ret_valid=0 (说明最后一个数据还没有返回) |
11 | REFILL | IDLE | axi_ret_last=1 and axi_ret_valid=1 (说明最后一个AXI数据已经返回) |
write buffer状态机:
序号 | wb_c_state | wb_n_state | 条件 |
---|---|---|---|
1 | S_IDLE | S_IDLE | write_hit=0 or wb_is_empty=0 write_hit=0 or (write_hit=1 and writing_ram=1) (writing_ram即Write Buffer正在向Cache写数据) |
2 | S_IDLE | S_WRITE | write_hit=1 or wb_is_empty=1 write_hit=1 and writing_ram=0 |
3 | S_WRITE | S_WRITE | write_hit=1 or wb_is_empty=1 writing_ram=1 or (write_hit=1 and writing_ram=0) |
4 | S_WRITE | S_IDLE | hit_wite=0 and wb_is_empty=0 |
与AXI接口
- 根据原书P233~224的所述的原则,只将AXI的部分接口放在Cache中,且所有信号均在一个周期处理完毕。
- AXI可以使用burst传输,每次传输个字节,也可以一次将128-bit的数据直接传输完毕
摘自:【AXI】解读AXI协议中的burst突发传输机制_axi burst突发-CSDN博客
- 但是根据书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——后记
规划
- 为了贯彻落实48.6的计划,决定重写AXI接口。
- 由于之前的代码采用了inst_sram、data_sram的控制是写在一起的,不太方便独立出来单独控制,于是将原书P254~P255的任务二和任务三都在本次Lab中实现:即同时实现ICache和DCache。
CPU接口与Cache接口——size
-
根据48.5.2节中的表格,可以将CPU接口与Cache接口连接起来。
-
但是表格中缺少与CPU接口中的“size”的对应,经发现IF_STAGE的size与arsize相连。arsize/awsize都是突发数据长度。
-
原书P234有言:“我们给出的设计建议是:对于读操作,AXI总线接口模块每个周期至多给Cache模块返回32位数据,Cache模块将返回的数据填入Cache的Bank RAM中或者直接将其返回给CPU流水线;对于写操作,Cache模块在一个周期内直接将一个Cache行的数据传给AXI总线接口模块,AXI总线接口内部设一个16字节的写缓存保存这些数,然后再慢慢地以Burst方式发出去。”
-
inst_sram_size的值固定为2’h2
-
data_sram_size的值为:
-
综上,需要在“sram_axi_bridge.vhd”文件中,单独增加对size这个信号的处理,很简单的处理,主要是对比特位的0填充控制。
-
然而在进一步设计的时候,本人怀疑这个信号无用,于是将data_sram_size的值修改为2’b10(inst_sram_size的值不用修改,因为之前就是2’b10),然后重新跑仿真,发现正确:
-
于是决定直接忽略掉这两个信号
-
-
-
AXI接口与Cache接口
原书P196表8-4 | 原书P234表10-3 |
---|---|
读 | |
arid | |
araddr | rd_addr |
arlen | const: 8’d3(表示传输3+1=4个数据量) |
arsize | rd_type(3’b010) |
arburst | const: 2’b01 |
arlock | const: 2’b0 |
arcache | const: 4’b0 |
arprot | const: 3’b0 |
arvalid | rd_req |
arready | rd_rdy |
rid | |
rdata | ret_data |
rresp | |
rlast | ret_last |
rvalid | ret_valid |
rready | |
写 | |
awid | const: 4’d1 |
awaddr | wr_addr |
awlen | const: 8’d3(表示传输3+1=4个数据量) |
awsize | rd_type(3’b010) |
awburst | const: 2’b01 |
awlock | const: 2’b0 |
awcache | const: 4’b1 |
awprot | const: 3’b0 |
awvalid | wr_req and wr_rdy |
awready | |
wid | const: 4’d1 |
wdata | |
wstrb | const: 4’b1111 |
wlast | const: 1’b1 |
wvalid | |
wready | |
bid | |
bresp | |
bvalid | |
bready | |
wr_wstrb | |
wr_data |
-
AXI接口与Cache接口的设计可以参考之前的设计
-
读操作采用burst传输,一次传输32bits。
-
写操作使AXI模块内部的Cache,保存16个Bytes,然后再以Burst的方式发出去。
-
根据AMBA3 AXI手册中对arcache、awcache的描述:
根据描述,对于arcache,将其设置为全0
对于awcache,将其设置为4’b0001,即Bufferable only。
至于arburst、awburst:二者都选择INCR。
-
原书对rd_type、wr_type的设定绝对是有预谋的:原书的解释中rd_type/wr_type为3’b100时表示一个Cache行,而正好一行是16个字节。这与arsize、awsize刚好对应上:
-
-
-
整体设计
-
之前的设计是对地址等信号寄存一拍再进行处理,本次设计打算不优化这条关键路径了,直接进行处理,这样最简单。
-
针对AXI的读:数据Cache的优先级高于指令Cache
-
针对AXI的读:虽然AXI支持连续burst,但是为了方便控制,在第一次burst结束之前(即ret_last回来之前)禁止发起第二次burst传输。不需要,靠rid判断就行了,也不麻烦。
-
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
- Lab12:func_lab9(带AXI的仿真)
- Lab13:tlb_verify(纯TLB,没有CPU)
- Lab14:测试TLB相关指令和CP0寄存器(只是测试TLB部分没有对CPU功能进行验证)
- Lab15:tlb_func(仅是测试TLB部分)
- Lab14:测试TLB相关指令和CP0寄存器(只是测试TLB部分没有对CPU功能进行验证)
- Lab13:tlb_verify(纯TLB,没有CPU)
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有问题”。然而暂时没有时间去解决了(烂尾了=_=!)