Categories: 硬件设计.

概述

本次更新涵盖 3 个提交(7cfbf1197f44b868d2e77),涉及 5 个文件,净变更 +332 / -222 行。成果:所有性能测试通过,主频 70MHz,Cache 布局优化

核心改动分为三部分:分支重定向去重与取指数据一致性修复(core_top.v / pc_reg.v)、Difftest Store 事件精确化(core_top.v)、I-Cache / D-Cache 状态机重构与 Block RAM 映射(icache_two_way.v / dcache_two_way.v)。


修改内容

1. 分支重定向去重:id_br_sent

文件core_top.v:362-376

问题:当流水线停顿时(如 I-Cache Miss 等待 AXI 响应),ID 阶段的分支判定结果 id_br_taken_safe 会持续有效。每个时钟周期都会重新触发分支重定向,导致 PC 被反复改写、延迟槽指令被反复冲刷,引发流水线行为混乱。

修复:新增 id_br_sent 寄存器,记录已发送过的分支重定向。当同一 PC 的分支已被处理且流水线未推进时,抑制重复触发:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
reg  id_br_sent;
reg [31:0] id_br_sent_pc;

assign id_br_taken_raw = id_br_taken && pipline_is_not_stalled;
assign id_br_taken_safe = id_br_taken_raw && (!id_br_sent || id_pc != id_br_sent_pc);
assign id_br_redirect = id_br_taken_safe && (id_br_target != id_pc + 32'd4);

always @(posedge clk) begin
if (rst || wb_ex || wb_is_ertn) begin
id_br_sent <= 1'b0;
id_br_sent_pc <= 32'b0;
end else if (!(if_ready_go===1'b0) && id_allow_in) begin
id_br_sent <= 1'b0;
id_br_sent_pc <= 32'b0;
end else if (id_br_taken_safe) begin
id_br_sent <= 1'b1;
id_br_sent_pc <= id_pc;
end
end

当流水线正常推进(if_ready_go && id_allow_in)时清除标记,允许下一条分支被处理。


2. PC 寄存器简化:移除 pc_br_taken_memory

文件pc_reg.v:22-70

问题:前一版本在 pc_reg.v 中引入了 pc_target_memory / pc_br_taken_memory 延迟寄存器,试图捕获被错过的分支脉冲。但该机制引入了额外的状态复杂性,且在 id_br_sent 去重逻辑生效后已不再需要。

修复:移除 pc_target_memorypc_br_taken_memory 相关的全部逻辑,回归直接处理:

1
2
3
4
5
6
7
8
9
end else if (pc_br_taken && pipline_is_not_stalled) begin
if_pc <= nextpc;
nextpc <= pc_br_target;
if_inst_tlb_ex <= inst_tlb_ex;
end else begin
casez (!(pre_if_ready_go===1'b0)&&if_allow_in)
// ... normal PC+4 / BTB path ...
endcase
end

分支重定向仍然在 casez 之前处理,保证 IF 停顿时不会错过跳转。id_br_sent 保证了不会重复触发。


3. 取指数据 PC 匹配:inst_fetch_pc

文件core_top.v:298-360

问题:I-Cache 命中响应或 AXI 取指返回的数据可能对应之前的 PC(流水线停顿期间 PC 未变但请求已发出)。原设计使用 inst_sram_rdata_safe 作为缓存数据,但不检查该数据是否对应当前 if_pc,导致停顿恢复后使用了陈旧的取指结果。

修复:新增 inst_fetch_pc 寄存器,在取指请求被接受时(inst_sram_req && inst_sram_addr_ok)记录对应的 PC。if_inst 选择逻辑增加 PC 匹配检查:

1
2
3
4
assign if_inst = rst ? 32'h02800000 :
(id_br_redirect || br_cancel_fallthrough) ? 32'h02800000 :
(real_inst_data_ok && inst_sram_rdata_pc_match) ? inst_sram_rdata :
(inst_sram_rdata_safe_valid && inst_sram_rdata_safe_pc == if_pc) ? inst_sram_rdata_safe : 32'h02800000;

同时新增 if_suppress_dup_fetch_req,当当前周期 inst_sram_addr 与上一周期已采样的 if_last_sampled_pc 相同时,抑制重复取指请求,避免 AXI 总线上的无效事务。


4. Difftest Store 事件精确化

文件core_top.v:2451-2537

问题:原 Difftest Store 事件使用 1-bit 的 cmt_inst_st_en 和 32-bit 的 cmt_memvis_data,无法区分 Store 的字节粒度(st.b / st.h / st.w)。此外,cmt_memvis_paddr 直接使用虚拟地址,未经过 DMW 翻译,导致 NEMU 比对时物理地址不匹配。

修复

4a. 字节使能cmt_inst_st_en 从 1-bit 扩展为 8-bit,根据 wb_wdram_num 生成对应的字节使能掩码:

1
2
3
4
5
cmt_inst_st_en <= wb_is_store ? ((wb_wdram_num == 2'b00) ? 8'b0000_0100 :
(wb_wdram_num == 2'b10) ? 8'b0000_0010 :
(wb_wdram_num == 2'b01) ? 8'b0000_0001 :
8'b0000_0000) :
8'b0000_0000;

4b. 数据扩展cmt_memvis_data 从 32-bit 扩展为 64-bit,低 32 位为字节对齐后的 Store 数据,高 32 位补零:

1
2
3
cmt_memvis_data <= (wb_wdram_num == 2'b01) ? {32'b0, ({24'b0, wb_dram_wdata[7:0]} << (8 * wb_data_addr[1:0]))} :
(wb_wdram_num == 2'b10) ? {32'b0, ({16'b0, wb_dram_wdata[15:0]} << (8 * {wb_data_addr[1], 1'b0}))} :
{32'b0, wb_dram_wdata};

4c. DMW 物理地址翻译:新增 wb_store_paddr,根据 csr_crmd_dacsr_crmd_pg、DMW 窗口配置将 Store 的虚拟地址翻译为物理地址:

1
2
3
4
assign wb_store_paddr = (csr_crmd_da && !csr_crmd_pg) ? wb_data_addr :
wb_store_dmw0_en ? {csr_dmw0_pseg, wb_data_addr[28:0]} :
wb_store_dmw1_en ? {csr_dmw1_pseg, wb_data_addr[28:0]} :
wb_data_addr;

cmt_memvis_paddr 改为使用 wb_store_paddr,使 Difftest 比对使用正确的物理地址。

4d. MMIO Store 识别:新增 wb_store_mmio,识别地址范围 0xbfe0_00000xbfff_ffff 的 MMIO Store 操作,确保外设写入也被上报给 NEMU。


5. Cache 状态机重构:新增 S_LOOKUP 状态

文件icache_two_way.vdcache_two_way.v

问题:原设计中,I-Cache 和 D-Cache 在 S_IDLE 状态接收到请求后,同周期内完成 Tag 查找、命中判定、数据选择和状态跳转。这条组合逻辑路径包含 SRAM 读取 + Tag 比较 + 数据 MUX,在 FPGA 上是时序瓶颈。同时,所有 Cache 阵列(data0/data1)使用 reg 声明,综合工具默认映射为分布式 RAM(LUT RAM),无法利用 FPGA 的 Block RAM 资源。

修复:新增 S_LOOKUP 状态,将 Tag 查找与命中处理分为两个周期:

1
2
原:S_IDLE → (命中) S_HIT_RESP / (未命中) S_REF_REQ / (Uncached) S_UNC_REQ
新:S_IDLE → S_LOOKUP → (命中) S_HIT_RESP / (未命中) S_REF_REQ / (Cacop) S_CACOP_RESP

S_IDLE 接收请求时,将对应 Index 的 Cache 行数据读入寄存器 data0_r / data1_r

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
S_IDLE: begin
if (req) begin
req_index <= index_l;
// ... 锁存请求参数 ...
data0_r <= data0[index_l];
data1_r <= data1[index_l];
state <= uncached ? S_UNC_REQ : S_LOOKUP;
end
end
S_LOOKUP: begin
// 使用 data0_r / data1_r 进行命中判定和数据选择
if (hit_now) begin
resp_data <= select_word(hit_way_now ? data1_r : data0_r, req_word);
state <= S_HIT_RESP;
end else begin
state <= S_REF_REQ;
end
end

影响:Tag 查找路径从 “SRAM 读取 + 比较 + MUX” 缩短为 “寄存器读取 + 比较 + MUX”,关键路径延迟显著降低。代价是命中延迟增加 1 周期。


6. Cache 参数化与 Block RAM 映射

文件icache_two_way.vdcache_two_way.v

修复:将硬编码的 Cache 参数提取为 localparam

1
2
3
localparam CACHE_INDEX_BITS = 8;
localparam CACHE_LINES = 1 << CACHE_INDEX_BITS;
localparam CACHE_TAG_BITS = 32 - 4 - CACHE_INDEX_BITS;

所有阵列声明改用参数化宽度:

1
2
3
4
reg [CACHE_TAG_BITS-1:0] tag0 [0:CACHE_LINES-1];
reg [CACHE_TAG_BITS-1:0] tag1 [0:CACHE_LINES-1];
(* ram_style = "block" *) reg [127:0] data0 [0:CACHE_LINES-1];
(* ram_style = "block" *) reg [127:0] data1 [0:CACHE_LINES-1];

(* ram_style = "block" *) 属性指示综合工具将 Cache 数据阵列映射为 Block RAM,而非分布式 LUT RAM。对于 256 项 × 128-bit 的阵列(每路 4KB),Block RAM 的读取延迟为 1 个时钟周期(同步读),与 S_LOOKUP 状态的流水化设计匹配。

Tag 阵列(tag0/tag1)和元数据阵列(valid0/valid1/dirty0/dirty1/used)保持分布式 RAM,因为它们的读取需要在 S_IDLE 同周期完成以决定 S_LOOKUP 的跳转方向。


7. 调试日志清理

文件core_top.vpc_reg.v

移除了 7cfbf11 提交中引入的 CoreMark PC 调试日志([CMPCDBG][PCDBG]),共约 70 行 $display 代码。这些日志在 97f44b8 提交中确认问题已修复后删除。


修改文件清单

文件 变更
core_top.v 分支去重、取指 PC 匹配、Difftest Store 精确化、调试日志清理
pc_reg.v 移除 pc_br_taken_memory 延迟逻辑,回归直接处理
icache_two_way.v 新增 S_LOOKUP 状态、参数化、Block RAM 映射
dcache_two_way.v 新增 S_LOOKUP 状态、参数化、Block RAM 映射
ID_REg.v 移除多余空行