项目一句话:基于 AXI4-Full 总线的 4KB 四路组相联 I-Cache 桥接器,完整覆盖 RTL 设计、QuestaSim 仿真、UVM 验证(验证计划 + 80 测试点分解 + 10 个回归用例),命中率 83%+,代码覆盖率 Statement 97% / Branch 93%。
工具链 :QuestaSim 10.7c / UVM-1.2 | 语言:Verilog(RTL)+ SystemVerilog(TB)
工程结构(所有代码见文末「工程目录」章节):
AXI2ICACHE_v2.2/
├── rtl/ # 7 个可综合 RTL 文件,共 1880 行
│ ├── axi_icache_top.v # 顶层(440 行)
│ ├── axi_slave_ctrl.v # S 口控制:AR FIFO/Cache FSM/主 FSM(402 行)
│ ├── axi_master_ctrl.v # M 口控制:fill/bypass/写透传(382 行)
│ ├── icache_tag.v # Tag 查找 + pLRU 替换(305 行)
│ ├── icache_dmem.v # 数据通路 + 4×SPRAM 控制(255 行)
│ ├── dpram_generic.v # Tag SRAM(1W1R 双口,54 行)
│ └── spram_generic.v # Data SRAM(单口,42 行)
├── uvm/sv/ # 23 个 UVM 文件,共 3048 行
│ ├── axi_icache_if.sv # 接口 + 13 条 SVA 断言
│ ├── axi_icache_pkg.sv # 合并包(所有组件类)
│ ├── tb_top.sv # 顶层 TB
│ └── ... # 拆分源文件(driver/monitor/ref_model/scoreboard/env/coverage/vseq/tests)
├── sim/ # 仿真目录(work_uvm/work_demo 库、波形、回归脚本)
└── doc/ # Spec、验证计划、测试点分解 xlsx、本文档
1. 项目概述
1.1 背景
本设计是经典课程项目 AHB2ICACHE (AHB-Lite 接口指令 Cache)的迭代演进,将总线接口升级为 AXI4-Full,目的是利用 AXI 的独立通道、双向握手和原生 WRAP burst 提升取指性能:
| 版本 | 总线 | 核心改进 |
|---|---|---|
| v1.0 | AXI4-Full | 总线升级首版,单 outstanding |
| v2.0 | AXI4-Full | 命中延迟优化到 1 拍;新增 SVA 断言与覆盖率收集 |
| v2.1 | AXI4-Full | 2 深 AR FIFO 多 outstanding + in-order 响应;ref model 时序对齐 |
| v2.2 | AXI4-Full | 3 状态 Cache FSM(enable/disable)+ disable 清 tag + 读写冲突处理 + fill_busy 修复 |
1.2 设计目标
| 目标 | 说明 |
|---|---|
| 总线升级 | 上下游均为 AXI4-Full(AR/R/AW/W/B 五通道完整) |
| 性能提升 | 独立读地址/数据通道支持地址流水;VALID/READY 天然反压;WRAP4 原生回填 |
| 兼容原架构 | 保持 4KB 4 路组相联、pLRU 替换、16B/line |
| 可综合性 | 全部 RTL 可综合,无 initial 块(testbench 除外) |
| 可验证性 | 白盒 tag 比对(DUT vs ref model)+ 黑盒数据比对 + SVA 断言 + 覆盖率 |
1.3 Cache 规格参数
| 参数 | 值 | 说明 |
|---|---|---|
| 总容量 | 4 KB | 64 set × 4 way × 16 B/line |
| 相联度 | 4 路 | 组相联 |
| set 数 | 64 | index_bits = 6 |
| line 大小 | 16 B | 4 beats × 32 bit,offset_bits = 4 |
| tag 位宽 | 22 bit | 32 - 6(index) - 4(offset) = 22 |
| 替换算法 | tree-pseudo LRU | 3 bit/set,invalid way 优先 |
| 命中延迟 | 1 拍回 RVALID | 地址相 T → tag/dmem 读出 T+1 → RVALID T+1 |
| Miss 回填 | WRAP4 | arlen=3, arsize=4B, 16B 对齐 |
注:v2.0 将命中延迟从 2 拍优化到 1 拍(s_arready 组合逻辑输出,AR_LOOKUP 状态直接驱动 RVALID)。
2. 系统架构
2.1 架构图

2.2 模块职责
| 模块 | 职责 | 关键点 |
|---|---|---|
axi_icache_top |
顶层例化与连线 | 例化 slave_ctrl / master_ctrl / tag / dmem / 6 个 SRAM |
axi_slave_ctrl |
S 口控制 | 2 深 AR FIFO、主 FSM(IDLE/LOOKUP/HIT/MISS/BYPASS)、3 状态 Cache FSM、读写冲突规避 |
axi_master_ctrl |
M 口控制 | 读 FSM(FILL_AR/FILL_R/BYPASS_AR/BYPASS_R)、WRAP4 回填、写通道 AW/W/B 透传(registered slice) |
icache_tag |
Tag 查找与替换 | tag 比较、hit/miss/fill_hit 判定、pLRU 更新与 victim 选择 |
icache_dmem |
数据通路 | 命中读选路(4 way mux)、回填写通路(WRAP4 beat 计数与 way 分配) |
dpram_generic |
Tag SRAM | 1W1R 双口,95 bit/entry(4×22 tag + 4 valid + 3 LRU) |
spram_generic |
Data SRAM | 单口,128 bit/entry(16B line),每 way 一个 |
2.3 数据路径总览
- ① 命中读:CPU 发 AR → FIFO 缓存 → tag lookup(同拍)→ tag_hit → dmem 选路 → 1 拍后 RVALID 返回
- ② Miss 回填:miss_req → master_ctrl 发 WRAP4 突发读 → R 数据 4 拍写入 dmem → cache_line_wr 同步更新 tag(valid+LRU)
- ③ Non-cacheable 读:bypass 单拍透传(不写 cache,直接回内存数据)
- ④ 写请求:AW/W/B 三通道全透传(registered slice 打拍)
- ⑤ Cache disable:icache_en=0 → 等 FIFO 空 + FSM idle + 无 fill → disable_clr_tag 脉冲清空 tag valid
- ⑥ 替换:tree-pseudo LRU(3bit/set),invalid way 优先命中 victim
3. 设计原理详解
3.1 地址映射
31 10 9 4 3 0
┌──────────────┬────────────┬────────┐
│ tag(22bit) │ index(6bit)│ offset │
└──────────────┴────────────┴────────┘
(31:10) (9:4) (3:0)
- index:选择 64 个 set 之一
- tag:与选中 set 的 4 个 way 的 tag 逐一比较,判断命中
- offset:16B line 内的字节偏移(word 偏移用 addr3:2,4 个 word)
3.2 Cache 状态机(v2.2 核心特性)
icache_en && !cache_init
S_DISABLE ────────────────────────► S_ENABLE
▲ │
│ !icache_en │
│ ┌───────────────────────────┘
│ ▼
│ S_WAIT_DISABLE : 等待 all_pending_done
│ (fifo_empty && FSM_idle && !fill_busy)
└─── disable_clr_tag 1拍脉冲 → 清空 tag SRAM valid
- S_DISABLE:cache 关闭,所有读走 bypass,写透传
- S_ENABLE:cacheable 判定生效,正常 lookup/miss/fill
- S_WAIT_DISABLE :收到关闭请求后,等待所有 in-flight 请求(FIFO、FSM、fill)排空,然后产生
disable_clr_tag单拍脉冲清空 tag,保证下次 enable 是干净状态
3.3 主 FSM(读请求处理)
AR_IDLE → AR_LOOKUP → HIT_RESP / MISS_WAIT / BYPASS_RESP
MISS_WAIT → (fill_done) → MISS_RESP
| 状态 | 行为 |
|---|---|
| AR_IDLE | FIFO 非空时取出 head;cacheable → LOOKUP,否则 → BYPASS_RESP |
| AR_LOOKUP | 发起 tag lookup;hit_chk_point 判定:tag_hit/fill_hit → HIT_RESP,miss → MISS_WAIT(同时拉 miss_req) |
| HIT_RESP | 组合输出 dmem 数据 + RVALID;握手完成 pop FIFO |
| MISS_WAIT | 等待 fill_done(期间 tag lookup 保持活跃,支持 fill_hit 前向递推) |
| MISS_RESP | 返回 fill 后数据,握手完成 pop FIFO |
| BYPASS_RESP | 透传 master_ctrl 的 bypass 数据 |
3.4 多 Outstanding:2 深 AR FIFO
s_arready = !fifo_full:只要 FIFO 不满就接收新 AR,天然支持背靠背/流水- FIFO 存
arid / araddr / arsize / cachable四字段 - in-order 响应:R 通道严格按 FIFO 顺序返回(单 outstanding 深度,无需 reordering buffer)
- 读写指针 1 bit(2 深循环),fifo_cnt 2 bit 计数,支持同拍 push+pop
3.5 Miss 回填(WRAP4)
miss_req 拉高 → M_FILL_AR:m_araddr = {addr[31:4],4'b0}(16B 对齐)
m_arlen=3, m_arsize=4B, m_arburst=WRAP
M_FILL_R:4 拍 R 数据按 beat 写入 dmem(每拍一个 32bit word)
第 4 拍(m_rlast)→ dmem_cache_line_wr=1 → 更新 tag SRAM
dmem 写通路:dmem_m_wrap_dphase 指示数据相位,dmem_m_wrap_beg_addr 控制 line 内偏移,4 拍写入对应 way 的 spram。
3.6 读写冲突处理(v2.2 新增)
Tag SRAM 是 1W1R 双口,但同周期写优先:若 fill 完成写 tag 与 lookup 读 tag 同拍发生,读会拿到旧数据。因此:
verilog
wire lookup_allowed = !cache_line_wr; // 写 tag 周期跳过 lookup
lookup 请求 tag_mem_chk 在 cache_line_wr=1 时被屏蔽,下拍重试,保证读到的 tag 是正确的新值。
3.7 Bypass(非缓存读/写透传)
- 可缓存判定 :
cacheable = s_axi_cache_en && !cache_init && (s_arprot[0]==0) && (s_arcache[1]==1)(S_ENABLE 且 cache_init 完成且非特权且 modifiable) - 非缓存读:bypass 单拍读(arlen=0, FIXED),数据直接回 S 口,不写 cache
- 写请求 :AW/W/B 三通道透传 + registered slice(每通道一级打拍,
s_awready = !m_awvalid || m_awready),隔离时序路径
3.8 替换算法:Tree-Pseudo LRU
- 每个 set 维护 3 bit
rlu_arr[idx][2:0] - 命中 way 时更新(way0/1 与 way2/3 分左右子树,bit2 选子树,bit0/bit1 选组内 way)
- 替换时:先找 invalid way(保证冷启动顺序填充),全 valid 才按 pLRU 选 victim
- ref model 与 RTL 实现完全一致(白盒比对前提)
4. RTL 代码全解析(7 个文件完整代码)
本章展示 RTL 全部 7 个文件(共 1696 行)的完整代码 ,并对每个文件做逐段解析。代码与工程
rtl/目录逐字一致。
4.1 axi_icache_top.v ------ 顶层(440 行)
职责 :模块级例化与连线,不含任何逻辑。对外暴露 AXI4-Full 双端口(S=CPU 侧、M=存储器侧),内部例化 6 个子模块:axi_slave_ctrl(从口控制)、axi_master_ctrl(主口控制)、icache_tag(Tag 查找)、icache_dmem(数据通路)、1 个双口 Tag SRAM、4 个单口 Data SRAM。
端口分组:
| 分组 | 方向 | 信号 | 说明 |
|---|---|---|---|
| S-AR | 输入 | s_arid/araddr/arlen/arsize/arburst/arcache/arprot/arqos/arvalid | CPU 读地址 |
| S-R | 输出 | s_rid/rdata/rresp/rlast/rvalid | CPU 读数据(输入 rready) |
| S-AW/W/B | 输入 | s_aw*/s_w*/s_b* | CPU 写通道(透传到 M 口) |
| M-AR/R | 输出 | m_ar*/m_r* | 存储器读(fill/bypass) |
| M-AW/W/B | 输出 | m_aw*/m_w*/m_b* | 存储器写(来自 S 口透传) |
| 控制 | 输入 | icache_en, clk, rst_n | Cache 使能/时钟/复位 |
关键内部连线(命中数据通路):
tag_mem_chk/chk_haddr → icache_tag:查找请求(地址阶段)hit_chk_point/tag_hit/fill_hit/cache_miss/hit_way/hit_offset ← icache_tag:查找结果(一拍后)cache_hit_rdata ← icache_dmem:命中/回填数据,直通 slave_ctrl 的 RVALID 组合输出miss_req/miss_addr → axi_master_ctrl:启动 WRAP4 回填;fill_done ←回填完成bypass_rd_* ↔ axi_master_ctrl:非缓存读单拍透传dmem_* ↔ axi_master_ctrl ↔ icache_dmem:AXI→AHB 风格适配(nonseq/beg_addr/dphase/line_wr)
SRAM 例化参数:Tag RAM 每 set 95 bit(4×22 tag + 4 valid + 3 pLRU),64 set;Data RAM 每 way 64 行 × 128 bit(16B line),共 4 个。
点击展开:axi_icache_top.v 完整代码
verilog
//----------------------------------------------------------------------------//
// File name : axi_icache_top.v
// Description : AXI4-Full 4-way set associative I-Cache top level.
// Instantiates axi_slave_ctrl, axi_master_ctrl, icache_tag,
// icache_dmem, tag SRAM (dpram), 4x data SRAM (spram).
//----------------------------------------------------------------------------//
module axi_icache_top(
// AXI4 Slave (CPU side)
s_arid, s_araddr, s_arlen, s_arsize, s_arburst, s_arcache, s_arprot, s_arqos,
s_arvalid, s_arready,
s_rid, s_rdata, s_rresp, s_rlast, s_rvalid, s_rready,
s_awid, s_awaddr, s_awlen, s_awsize, s_awburst, s_awcache, s_awprot, s_awqos,
s_awvalid, s_awready,
s_wid, s_wdata, s_wstrb, s_wlast, s_wvalid, s_wready,
s_bid, s_bresp, s_bvalid, s_bready,
// AXI4 Master (memory side)
m_arid, m_araddr, m_arlen, m_arsize, m_arburst, m_arcache, m_arprot, m_arqos,
m_arvalid, m_arready,
m_rid, m_rdata, m_rresp, m_rlast, m_rvalid, m_rready,
m_awid, m_awaddr, m_awlen, m_awsize, m_awburst, m_awcache, m_awprot, m_awqos,
m_awvalid, m_awready,
m_wid, m_wdata, m_wstrb, m_wlast, m_wvalid, m_wready,
m_bid, m_bresp, m_bvalid, m_bready,
// control
icache_en, clk, rst_n
);
input clk, rst_n;
input icache_en;
// S AR
input [3:0] s_arid;
input [31:0] s_araddr;
input [7:0] s_arlen;
input [2:0] s_arsize;
input [1:0] s_arburst;
input [3:0] s_arcache;
input [2:0] s_arprot;
input [3:0] s_arqos;
input s_arvalid;
output s_arready;
// S R
output [3:0] s_rid;
output [31:0] s_rdata;
output [1:0] s_rresp;
output s_rlast;
output s_rvalid;
input s_rready;
// S AW
input [3:0] s_awid;
input [31:0] s_awaddr;
input [7:0] s_awlen;
input [2:0] s_awsize;
input [1:0] s_awburst;
input [3:0] s_awcache;
input [2:0] s_awprot;
input [3:0] s_awqos;
input s_awvalid;
output s_awready;
// S W
input [3:0] s_wid;
input [31:0] s_wdata;
input [3:0] s_wstrb;
input s_wlast;
input s_wvalid;
output s_wready;
// S B
output [3:0] s_bid;
output [1:0] s_bresp;
output s_bvalid;
input s_bready;
// M AR
output [3:0] m_arid;
output [31:0] m_araddr;
output [7:0] m_arlen;
output [2:0] m_arsize;
output [1:0] m_arburst;
output [3:0] m_arcache;
output [2:0] m_arprot;
output [3:0] m_arqos;
output m_arvalid;
input m_arready;
// M R
input [3:0] m_rid;
input [31:0] m_rdata;
input [1:0] m_rresp;
input m_rlast;
input m_rvalid;
output m_rready;
// M AW
output [3:0] m_awid;
output [31:0] m_awaddr;
output [7:0] m_awlen;
output [2:0] m_awsize;
output [1:0] m_awburst;
output [3:0] m_awcache;
output [2:0] m_awprot;
output [3:0] m_awqos;
output m_awvalid;
input m_awready;
// M W
output [3:0] m_wid;
output [31:0] m_wdata;
output [3:0] m_wstrb;
output m_wlast;
output m_wvalid;
input m_wready;
// M B
input [3:0] m_bid;
input [1:0] m_bresp;
input m_bvalid;
output m_bready;
// internal wires
wire tag_mem_chk;
wire [31:0] chk_haddr;
wire hit_chk_point;
wire tag_hit;
wire fill_hit;
wire cache_miss;
wire [3:0] hit_way;
wire [1:0] hit_offset;
wire [1:0] replace_way;
wire [5:0] replace_addr;
wire cache_init;
wire cache_hit_rd_vld;
wire [31:0] cache_hit_rdata;
wire miss_req;
wire [31:0] miss_addr;
wire fill_done;
wire bypass_rd_req;
wire [31:0] bypass_rd_addr;
wire [3:0] bypass_rd_id;
wire [2:0] bypass_rd_size;
wire [31:0] bypass_rd_data;
wire [1:0] bypass_rd_resp;
wire bypass_rd_vld;
wire bypass_rd_ready;
// dmem adaptation wires
wire dmem_m_hready;
wire [31:0] dmem_m_hrdata;
wire dmem_m_wrap_nonseq;
wire [1:0] dmem_m_wrap_beg_addr;
wire dmem_m_wrap_dphase;
wire dmem_cache_line_wr;
// tag SRAM wires
wire tag_sram_rd;
wire [5:0] tag_sram_raddr;
wire [94:0] tag_sram_dout; // 4*22 + 4 + 3 = 95
wire [94:0] tag_sram_wdata;
wire tag_sram_wr;
wire [5:0] tag_sram_waddr;
// dmem SRAM wires
wire dmem_rd;
wire dmem0_wr, dmem1_wr, dmem2_wr, dmem3_wr;
wire [5:0] dmem_rw_addr;
wire [127:0] dmem_wdata;
wire [127:0] dmem0_dout, dmem1_dout, dmem2_dout, dmem3_dout;
// dmem output cnt (unused at top, connect for completeness)
wire [1:0] m_rdata_cnt;
wire [1:0] m_rdata_offset;
// v2.2: cache disable clear tag + rw conflict
wire disable_clr_tag;
wire cache_line_wr_int; // internal cache_line_wr from master_ctrl
//----------------------------------------------------------------------------//
// axi_slave_ctrl
//----------------------------------------------------------------------------//
axi_slave_ctrl u_slave(
.s_arid (s_arid),
.s_araddr (s_araddr),
.s_arlen (s_arlen),
.s_arsize (s_arsize),
.s_arburst (s_arburst),
.s_arcache (s_arcache),
.s_arprot (s_arprot),
.s_arvalid (s_arvalid),
.s_arready (s_arready),
.s_rid (s_rid),
.s_rdata (s_rdata),
.s_rresp (s_rresp),
.s_rlast (s_rlast),
.s_rvalid (s_rvalid),
.s_rready (s_rready),
.tag_mem_chk (tag_mem_chk),
.chk_haddr (chk_haddr),
.hit_chk_point (hit_chk_point),
.tag_hit (tag_hit),
.fill_hit (fill_hit),
.cache_miss (cache_miss),
.replace_way (replace_way),
.cache_init (cache_init),
.cache_hit_rd_vld(cache_hit_rd_vld),
.cache_hit_rdata(cache_hit_rdata),
.miss_req (miss_req),
.miss_addr (miss_addr),
.fill_done (fill_done),
.bypass_rd_req (bypass_rd_req),
.bypass_rd_addr (bypass_rd_addr),
.bypass_rd_id (bypass_rd_id),
.bypass_rd_size (bypass_rd_size),
.bypass_rd_data (bypass_rd_data),
.bypass_rd_resp (bypass_rd_resp),
.bypass_rd_vld (bypass_rd_vld),
.bypass_rd_ready(bypass_rd_ready),
.cache_line_wr (cache_line_wr_int),
.icache_en (icache_en),
.disable_clr_tag(disable_clr_tag),
.clk (clk),
.rst_n (rst_n)
);
//----------------------------------------------------------------------------//
// axi_master_ctrl
//----------------------------------------------------------------------------//
axi_master_ctrl u_master(
.m_arid (m_arid),
.m_araddr (m_araddr),
.m_arlen (m_arlen),
.m_arsize (m_arsize),
.m_arburst (m_arburst),
.m_arcache (m_arcache),
.m_arprot (m_arprot),
.m_arvalid (m_arvalid),
.m_arready (m_arready),
.m_rid (m_rid),
.m_rdata (m_rdata),
.m_rresp (m_rresp),
.m_rlast (m_rlast),
.m_rvalid (m_rvalid),
.m_rready (m_rready),
.m_awid (m_awid),
.m_awaddr (m_awaddr),
.m_awlen (m_awlen),
.m_awsize (m_awsize),
.m_awburst (m_awburst),
.m_awcache (m_awcache),
.m_awprot (m_awprot),
.m_awvalid (m_awvalid),
.m_awready (m_awready),
.m_wid (m_wid),
.m_wdata (m_wdata),
.m_wstrb (m_wstrb),
.m_wlast (m_wlast),
.m_wvalid (m_wvalid),
.m_wready (m_wready),
.m_bid (m_bid),
.m_bresp (m_bresp),
.m_bvalid (m_bvalid),
.m_bready (m_bready),
.s_awid (s_awid),
.s_awaddr (s_awaddr),
.s_awlen (s_awlen),
.s_awsize (s_awsize),
.s_awburst (s_awburst),
.s_awcache (s_awcache),
.s_awprot (s_awprot),
.s_awvalid (s_awvalid),
.s_awready (s_awready),
.s_wid (s_wid),
.s_wdata (s_wdata),
.s_wstrb (s_wstrb),
.s_wlast (s_wlast),
.s_wvalid (s_wvalid),
.s_wready (s_wready),
.s_bid (s_bid),
.s_bresp (s_bresp),
.s_bvalid (s_bvalid),
.s_bready (s_bready),
.miss_req (miss_req),
.miss_addr (miss_addr),
.fill_done (fill_done),
.bypass_rd_req (bypass_rd_req),
.bypass_rd_addr (bypass_rd_addr),
.bypass_rd_id (bypass_rd_id),
.bypass_rd_size (bypass_rd_size),
.bypass_rd_data (bypass_rd_data),
.bypass_rd_resp (bypass_rd_resp),
.bypass_rd_vld (bypass_rd_vld),
.bypass_rd_ready(bypass_rd_ready),
.dmem_m_hready (dmem_m_hready),
.dmem_m_hrdata (dmem_m_hrdata),
.dmem_m_wrap_nonseq(dmem_m_wrap_nonseq),
.dmem_m_wrap_beg_addr(dmem_m_wrap_beg_addr),
.dmem_m_wrap_dphase(dmem_m_wrap_dphase),
.dmem_cache_line_wr(cache_line_wr_int),
.clk (clk),
.rst_n (rst_n)
);
//----------------------------------------------------------------------------//
// icache_tag
//----------------------------------------------------------------------------//
icache_tag u_tag(
.disable_clr_tag(disable_clr_tag),
.cache_init (cache_init),
.tag_mem_chk (tag_mem_chk),
.chk_haddr (chk_haddr),
.hit_chk_point (hit_chk_point),
.tag_hit (tag_hit),
.fill_hit (fill_hit),
.cache_miss (cache_miss),
.hit_way (hit_way),
.hit_offset (hit_offset),
.tag_mem_wr_miss(cache_line_wr_int),
.m_wrap_dphase (dmem_m_wrap_dphase),
.replace_way (replace_way),
.replace_addr (replace_addr),
.tag_sram_rd (tag_sram_rd),
.tag_sram_raddr (tag_sram_raddr),
.tag_sram_dout (tag_sram_dout),
.tag_sram_wdata (tag_sram_wdata),
.tag_sram_wr (tag_sram_wr),
.tag_sram_waddr (tag_sram_waddr),
.clk (clk),
.rstn (rst_n)
);
//----------------------------------------------------------------------------//
// icache_dmem
//----------------------------------------------------------------------------//
icache_dmem u_dmem(
.m_hready (dmem_m_hready),
.m_hrdata (dmem_m_hrdata),
.m_wrap_nonseq (dmem_m_wrap_nonseq),
.m_wrap_beg_addr(dmem_m_wrap_beg_addr),
.m_rdata_cnt (m_rdata_cnt),
.m_rdata_offset (m_rdata_offset),
.m_wrap_dphase (dmem_m_wrap_dphase),
.cache_hit_rd_vld(cache_hit_rd_vld),
.cache_hit_rdata(cache_hit_rdata),
.tag_mem_chk (tag_mem_chk),
.tag_chk_haddr (chk_haddr),
.cache_line_wr (cache_line_wr_int),
.replace_way (replace_way),
.replace_addr (replace_addr),
.hit_chk_point (hit_chk_point),
.tag_hit (tag_hit),
.fill_hit (fill_hit),
.hit_way (hit_way),
.hit_offset (hit_offset),
.dmem_rd (dmem_rd),
.dmem0_wr (dmem0_wr),
.dmem1_wr (dmem1_wr),
.dmem2_wr (dmem2_wr),
.dmem3_wr (dmem3_wr),
.dmem_rw_addr (dmem_rw_addr),
.dmem_wdata (dmem_wdata),
.dmem0_dout (dmem0_dout),
.dmem1_dout (dmem1_dout),
.dmem2_dout (dmem2_dout),
.dmem3_dout (dmem3_dout),
.clk (clk),
.rstn (rst_n)
);
//----------------------------------------------------------------------------//
// Tag SRAM (1W1R dpram)
//----------------------------------------------------------------------------//
dpram_generic #(
.ADDR_BITS(6),
.ADDR_AMOUNT(64),
.DATA_BITS(95)
) u_tag_sram (
.clka (clk),
.ena (tag_sram_wr),
.wea (tag_sram_wr),
.dina (tag_sram_wdata),
.addra (tag_sram_waddr),
.clkb (clk),
.enb (tag_sram_rd),
.addrb (tag_sram_raddr),
.doutb (tag_sram_dout)
);
//----------------------------------------------------------------------------//
// Data SRAM (4x single-port, one per way)
//----------------------------------------------------------------------------//
spram_generic #(
.ADDR_BITS (6),
.ADDR_AMOUNT (64),
.DATA_BITS (128)
) u_dmem0 (
.clk (clk),
.en (dmem_rd | dmem0_wr),
.we (dmem0_wr),
.addr (dmem_rw_addr),
.din (dmem_wdata),
.dout (dmem0_dout)
);
spram_generic #(
.ADDR_BITS (6),
.ADDR_AMOUNT (64),
.DATA_BITS (128)
) u_dmem1 (
.clk (clk),
.en (dmem_rd | dmem1_wr),
.we (dmem1_wr),
.addr (dmem_rw_addr),
.din (dmem_wdata),
.dout (dmem1_dout)
);
spram_generic #(
.ADDR_BITS (6),
.ADDR_AMOUNT (64),
.DATA_BITS (128)
) u_dmem2 (
.clk (clk),
.en (dmem_rd | dmem2_wr),
.we (dmem2_wr),
.addr (dmem_rw_addr),
.din (dmem_wdata),
.dout (dmem2_dout)
);
spram_generic #(
.ADDR_BITS (6),
.ADDR_AMOUNT (64),
.DATA_BITS (128)
) u_dmem3 (
.clk (clk),
.en (dmem_rd | dmem3_wr),
.we (dmem3_wr),
.addr (dmem_rw_addr),
.din (dmem_wdata),
.dout (dmem3_dout)
);
endmodule
解析 :顶层设计要点是信号边界显式化 ------所有跨模块信号在顶层逐一声明并命名(tag_*/dmem_*/bypass_*/miss_*),任何误连在综合和仿真中都能快速定位;v2.2 新增的 cache_line_wr_int 作为 tag 提交与 dmem 写回的统一脉冲,由 master_ctrl 在 WRAP4 最后一拍产生,slave/tag/dmem 三个模块同时消费,避免多份"fill 完成"判定。
4.2 axi_slave_ctrl.v ------ 从口控制器(402 行)
职责 :AXI4-Full 从口(CPU 侧)的全部读通路控制:2 深 AR FIFO(多 outstanding)、tag 查找流水调度、命中/未命中/旁路三种响应、Cache 使能/关闭状态机、disable 清 tag。
核心逻辑分块:
| 块 | 行号 | 内容 |
|---|---|---|
| ① fill_busy 跟踪 | 132-139 | miss_req 置位 / fill_done 清位,v2.2 BUG 修复点(fill_done 优先) |
| ② 2 深 AR FIFO | 144-200 | 4 个队列(id/addr/size/cachable)+ 指针 + cnt;push/pop 同拍处理 |
| ③ 主 FSM | 205-265 | AR_IDLE→LOOKUP→(HIT_RESP / MISS_WAIT→MISS_RESP / BYPASS_RESP) |
| ④ Cache FSM | 270-305 | S_DISABLE/S_ENABLE/S_WAIT_DISABLE + disable_clr_tag 脉冲 |
| ⑤ lookup 冲突规避 | 314-327 | lookup_allowed = !cache_line_wr,fill 写 tag 周期跳过查找 |
| ⑥ miss 请求 | 332-347 | LOOKUP+miss 时锁存 miss_req/miss_addr,fill_done 时采样回填数据 |
| ⑦ bypass 请求 | 352-371 | 非缓存请求转发到 master_ctrl,单拍透传 |
| ⑧ R 输出 | 376-400 | 组合逻辑按状态驱动 RVALID/RDATA/RRESP/RID |
① fill_busy(重点) :miss_req 与 fill_done 可能同拍出现(miss_req 拉低是下一拍生效,fill_done 是单拍脉冲),原代码 miss_req 优先导致 clear 条件永远错过、fill_busy 永久锁 1,进而让 disable 流程(all_pending_done 依赖 !fill_busy)永久卡死。修复为 fill_done 优先。
② AR FIFO :握手即入队(cacheable 在握手拍组合判定并随队存储);req_done(响应完成)出队;push/pop 同拍用 2\'b11 分支保持 cnt 不变、仅换 wr_ptr;s_arready = !fifo_full 提供背压,FIFO 空时不阻塞 AR。
③ 主 FSM :LOOKUP 一拍后由 hit_chk_point 判定三分支;HIT_RESP/MISS_RESP/BYPASS_RESP 都是握手完成即 req_done,保证 in-order 响应(FIFO 先入先出)。MISS_WAIT 期间保持 tag_mem_chk=1 以驱动 dmem 的 fill_hit 前向递推。
④ Cache FSM :S_DISABLE 下 icache_en && !cache_init 进 S_ENABLE;!icache_en 进 S_WAIT_DISABLE;all_pending_done(FIFO 空 + FSM idle + 无 fill)时产生一拍 disable_clr_tag 清空 tag SRAM 并回 S_DISABLE。
⑤ 读写冲突 :cache_line_wr 周期 tag SRAM 写优先,若同周期查找会读到陈旧 tag 造成伪 miss/伪 hit,因此该拍 lookup_allowed=0 跳过查找(请求保持在 FIFO 头,下一拍再查)。
点击展开:axi_slave_ctrl.v 完整代码
verilog
//----------------------------------------------------------------------------//
// File name : axi_slave_ctrl.v
// Description : AXI4-Full Slave interface controller for I-Cache (v2.2).
// - 2-deep AR FIFO for multi-outstanding
// - Pipelined tag lookup (1 cycle)
// - In-order R response
// - Hit latency = 1 cycle
// - Cache enable/disable state machine (ref AHB icache_ahb.v)
// - cache_line_rw_conflict handling (skip lookup on fill-write)
// - disable_clr_tag pulse to clear tag SRAM on disable
//----------------------------------------------------------------------------//
module axi_slave_ctrl(
// AXI4 Slave read address channel
s_arid ,
s_araddr ,
s_arlen ,
s_arsize ,
s_arburst ,
s_arcache ,
s_arprot ,
s_arvalid ,
s_arready ,
// AXI4 Slave read data channel
s_rid ,
s_rdata ,
s_rresp ,
s_rlast ,
s_rvalid ,
s_rready ,
// tag control
tag_mem_chk ,
chk_haddr ,
hit_chk_point,
tag_hit ,
fill_hit ,
cache_miss ,
replace_way ,
cache_init ,
// dmem hit data
cache_hit_rd_vld,
cache_hit_rdata ,
// master ctrl interface
miss_req ,
miss_addr ,
fill_done ,
bypass_rd_req,
bypass_rd_addr,
bypass_rd_id,
bypass_rd_size,
bypass_rd_data,
bypass_rd_resp,
bypass_rd_vld,
bypass_rd_ready,
// master status (for disable wait)
cache_line_wr,
// control
icache_en ,
disable_clr_tag,
clk ,
rst_n
);
input clk, rst_n;
input icache_en;
// S AR channel
input [3:0] s_arid;
input [31:0] s_araddr;
input [7:0] s_arlen;
input [2:0] s_arsize;
input [1:0] s_arburst;
input [3:0] s_arcache;
input [2:0] s_arprot;
input s_arvalid;
output s_arready;
// S R channel
output reg [3:0] s_rid;
output reg [31:0] s_rdata;
output reg [1:0] s_rresp;
output reg s_rlast;
output reg s_rvalid;
input s_rready;
// tag
output reg tag_mem_chk;
output reg [31:0] chk_haddr;
input hit_chk_point;
input tag_hit;
input fill_hit;
input cache_miss;
input [1:0] replace_way;
input cache_init;
// dmem
input cache_hit_rd_vld;
input [31:0] cache_hit_rdata;
// master ctrl
output reg miss_req;
output reg [31:0] miss_addr;
input fill_done;
output reg bypass_rd_req;
output reg [31:0] bypass_rd_addr;
output reg [3:0] bypass_rd_id;
output reg [2:0] bypass_rd_size;
input [31:0] bypass_rd_data;
input [1:0] bypass_rd_resp;
input bypass_rd_vld;
output reg bypass_rd_ready;
// master status
input cache_line_wr;
// control
output reg disable_clr_tag;
//----------------------------------------------------------------------------//
// Cache status signals (declared early for use in FIFO cacheable判定)
//----------------------------------------------------------------------------//
wire s_axi_cache_en; // 1: cache is in enable state, driven by cache FSM below
reg fill_busy; // 1: miss fill in progress
// fill_busy tracking: miss_req sets, fill_done clears
// NOTE (v2.2 fix): fill_done must take priority over miss_req.
// Original code had "else if(miss_req) ... else if(fill_done) ...",
// which latches fill_busy=1 forever when miss_req and fill_done
// coincide in the same cycle (miss_req clears one cycle later, and
// fill_done is a single-cycle pulse, so the clear condition is missed).
// This broke the cache-disable flow: all_pending_done requires !fill_busy,
// so disable_clr_tag could never fire.
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
fill_busy <= 1'b0;
else if(fill_done)
fill_busy <= 1'b0;
else if(miss_req)
fill_busy <= 1'b1;
end
//----------------------------------------------------------------------------//
// 2-deep AR FIFO
//----------------------------------------------------------------------------//
reg [3:0] arid_fifo [1:0];
reg [31:0] araddr_fifo[1:0];
reg [2:0] arsize_fifo[1:0];
reg cachable_fifo[1:0];
reg wr_ptr; // write pointer (1 bit for 2-deep)
reg rd_ptr; // read pointer
reg [1:0] fifo_cnt; // number of entries in FIFO
wire fifo_empty = (fifo_cnt == 2'd0);
wire fifo_full = (fifo_cnt == 2'd2);
// req_done pulse (driven by FSM below, declared here for FIFO logic)
reg req_done;
// cacheable判定 (combinational, for AR handshake)
// uses s_axi_cache_en from state machine instead of raw icache_en
wire cacheable = s_axi_cache_en && !cache_init && (s_arprot[0] == 1'b0) && (s_arcache[1] == 1'b1);
// s_arready: accept new request when FIFO is not full
assign s_arready = !fifo_full;
// FIFO write on AR handshake, read on req_done
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
wr_ptr <= 1'b0;
fifo_cnt <= 2'd0;
end else begin
case({s_arvalid && s_arready, req_done})
2'b10: begin // push only
arid_fifo[wr_ptr] <= s_arid;
araddr_fifo[wr_ptr] <= s_araddr;
arsize_fifo[wr_ptr] <= s_arsize;
cachable_fifo[wr_ptr] <= cacheable;
wr_ptr <= wr_ptr + 1'b1;
fifo_cnt <= fifo_cnt + 2'd1;
end
2'b01: begin // pop only
fifo_cnt <= fifo_cnt - 2'd1;
end
2'b11: begin // push and pop simultaneously
arid_fifo[wr_ptr] <= s_arid;
araddr_fifo[wr_ptr] <= s_araddr;
arsize_fifo[wr_ptr] <= s_arsize;
cachable_fifo[wr_ptr] <= cacheable;
wr_ptr <= wr_ptr + 1'b1;
// fifo_cnt stays the same
end
default: ; // no change
endcase
end
end
// FIFO head (current request being processed)
wire [3:0] head_arid = arid_fifo[rd_ptr];
wire [31:0] head_araddr = araddr_fifo[rd_ptr];
wire [2:0] head_arsize = arsize_fifo[rd_ptr];
wire head_cachable = cachable_fifo[rd_ptr];
//----------------------------------------------------------------------------//
// Main FSM
//----------------------------------------------------------------------------//
localparam AR_IDLE = 3'd0; // FIFO empty, waiting
localparam AR_LOOKUP = 3'd1; // tag lookup in progress (1 cycle)
localparam HIT_RESP = 3'd2; // hit, driving RVALID
localparam MISS_WAIT = 3'd3; // miss, waiting for fill_done
localparam MISS_RESP = 3'd4; // miss response, driving RVALID
localparam BYPASS_RESP = 3'd5; // bypass read response
reg [2:0] state, next_state;
always @(posedge clk or negedge rst_n)
if(!rst_n) state <= AR_IDLE;
else
state <= next_state;
always @(*) begin
next_state = state;
req_done = 1'b0;
case(state)
AR_IDLE:
if(!fifo_empty) begin
if(head_cachable)
next_state = AR_LOOKUP;
else
next_state = BYPASS_RESP;
end
AR_LOOKUP:
if(hit_chk_point) begin
if(tag_hit || fill_hit)
next_state = HIT_RESP;
else
next_state = MISS_WAIT;
end
HIT_RESP:
if(s_rvalid && s_rready) begin
req_done = 1'b1;
next_state = AR_IDLE;
end
MISS_WAIT:
if(fill_done)
next_state = MISS_RESP;
MISS_RESP:
if(s_rvalid && s_rready) begin
req_done = 1'b1;
next_state = AR_IDLE;
end
BYPASS_RESP:
if(bypass_rd_vld && bypass_rd_ready) begin
req_done = 1'b1;
next_state = AR_IDLE;
end
endcase
end
// FIFO read pointer: increment when request done
always @(posedge clk or negedge rst_n)
if(!rst_n) rd_ptr <= 1'b0;
else if(req_done) rd_ptr <= rd_ptr + 1'b1;
// all pending done: FIFO empty + FSM idle + no fill in progress
// (defined here after fifo_empty and state are declared)
wire all_pending_done = fifo_empty && (state == AR_IDLE) && !fill_busy;
//----------------------------------------------------------------------------//
// Cache enable/disable state machine (ref AHB icache_ahb.v s_cache_sta)
//----------------------------------------------------------------------------//
localparam S_DISABLE = 2'd0;
localparam S_ENABLE = 2'd1;
localparam S_WAIT_DISABLE = 2'd2;
reg [1:0] cache_state, cache_next_state;
assign s_axi_cache_en = (cache_state == S_ENABLE);
always @(posedge clk or negedge rst_n)
if(!rst_n) cache_state <= S_DISABLE;
else cache_state <= cache_next_state;
always @(*) begin
cache_next_state = cache_state;
disable_clr_tag = 1'b0;
case(cache_state)
S_DISABLE: begin
// enter enable when icache_en=1 and cache_init done
if(icache_en && !cache_init)
cache_next_state = S_ENABLE;
end
S_ENABLE: begin
// when icache_en drops, wait for all pending to finish
if(!icache_en)
cache_next_state = S_WAIT_DISABLE;
end
S_WAIT_DISABLE: begin
// wait until FIFO empty, FSM idle, no fill in progress
if(all_pending_done) begin
disable_clr_tag = 1'b1; // 1-cycle pulse to clear tag SRAM
cache_next_state = S_DISABLE;
end
end
default: cache_next_state = S_DISABLE;
endcase
end
//----------------------------------------------------------------------------//
// tag lookup with cache_line_rw_conflict handling
// (ref AHB icache_ahb.v: skip lookup when cache_line_wr in same cycle,
// because tag SRAM write takes priority and read would return stale data)
//----------------------------------------------------------------------------//
wire lookup_allowed = !cache_line_wr;
always @(*) begin
tag_mem_chk = 1'b0;
chk_haddr = 32'h0;
if(state == AR_IDLE && !fifo_empty && head_cachable && lookup_allowed) begin
tag_mem_chk = 1'b1;
chk_haddr = head_araddr;
end else if(state == MISS_WAIT) begin
// keep tag lookup active during fill for fill_hit
tag_mem_chk = 1'b1;
chk_haddr = head_araddr;
end
end
//----------------------------------------------------------------------------//
// miss request
//----------------------------------------------------------------------------//
reg [31:0] miss_rdata_r;
always @(posedge clk or negedge rst_n)
if(!rst_n) begin
miss_req <= 1'b0;
miss_addr <= 32'h0;
miss_rdata_r <= 32'h0;
end else begin
if(state == AR_LOOKUP && hit_chk_point && cache_miss) begin
miss_req <= 1'b1;
miss_addr <= head_araddr;
end else if(state == MISS_WAIT && fill_done) begin
miss_req <= 1'b0;
miss_rdata_r <= cache_hit_rdata;
end
end
//----------------------------------------------------------------------------//
// bypass read request
//----------------------------------------------------------------------------//
always @(*) begin
bypass_rd_req = 1'b0;
bypass_rd_addr = 32'h0;
bypass_rd_id = 4'h0;
bypass_rd_size = 3'h0;
bypass_rd_ready = 1'b0;
if(state == AR_IDLE && !fifo_empty && !head_cachable) begin
bypass_rd_req = 1'b1;
bypass_rd_addr = head_araddr;
bypass_rd_id = head_arid;
bypass_rd_size = head_arsize;
end else if(state == BYPASS_RESP && !bypass_rd_vld) begin
bypass_rd_req = 1'b1;
bypass_rd_addr = head_araddr;
bypass_rd_id = head_arid;
bypass_rd_size = head_arsize;
end
if(state == BYPASS_RESP)
bypass_rd_ready = 1'b1;
end
//----------------------------------------------------------------------------//
// R channel output
//----------------------------------------------------------------------------//
always @(*) begin
s_rid = head_arid;
s_rdata = 32'h0;
s_rresp = 2'b00;
s_rlast = 1'b1;
s_rvalid = 1'b0;
case(state)
HIT_RESP: begin
s_rdata = cache_hit_rdata;
s_rresp = 2'b00;
s_rvalid = 1'b1;
end
MISS_RESP: begin
s_rdata = miss_rdata_r;
s_rresp = 2'b00;
s_rvalid = 1'b1;
end
BYPASS_RESP: begin
s_rid = head_arid;
s_rdata = bypass_rd_data;
s_rresp = bypass_rd_resp;
s_rvalid = bypass_rd_vld;
end
endcase
end
endmodule
4.3 axi_master_ctrl.v ------ 主口控制器(382 行)
职责 :AXI4-Full 主口(存储器侧)控制:miss 时发起 WRAP4 读突发回填、非缓存读单拍透传、写通道(AW/W/B)三通道寄存器切片透传,并把 AXI 读通道适配成 dmem 的 AHB 风格控制信号。
读 FSM(5 状态) :M_IDLE → M_FILL_AR → M_FILL_R → M_IDLE(miss 回填);M_IDLE → M_BYPASS_AR → M_BYPASS_R → M_IDLE(旁路读)。优先级:miss_req > bypass_rd_req。
AR 通道驱动:
- FILL :
m_araddr={fill_addr_r[31:4],4'h0}强制 16B 对齐;arlen=3, arsize=4B, burst=WRAP(WRAP4);arcache=0011 - BYPASS :单拍
arlen=0, burst=FIXED,size/id 透传原请求
dmem 适配 :WRAP4 地址 16B 对齐,环绕起点固定为 line 首 word(dmem_m_wrap_beg_addr=2'b00);dmem_m_hready = m_rvalid && m_rready(AXI 握手语义);最后一拍m_rlast同时产生fill_done与dmem_cache_line_wr。
写透传 :AW/W 两级寄存器切片(!m_awvalid || m_awready时采样),B 反向切片;s_awready = !m_awvalid || m_awready提供背压,保证 CPU 写与存储器写一一对应、响应回传。
点击展开:axi_master_ctrl.v 完整代码
verilog
//----------------------------------------------------------------------------//
// File name : axi_master_ctrl.v
// Description : AXI4-Full Master interface controller for I-Cache.
// - Read fill: WRAP4 burst on miss, writes to dmem
// - Read bypass: single beat read for non-cacheable
// - Write bypass: AW/W/B pass-through
// - Adapts AXI signals to dmem's AHB-style control ports
//----------------------------------------------------------------------------//
module axi_master_ctrl(
// AXI4 Master read address channel
m_arid ,
m_araddr ,
m_arlen ,
m_arsize ,
m_arburst ,
m_arcache ,
m_arprot ,
m_arvalid ,
m_arready ,
// AXI4 Master read data channel
m_rid ,
m_rdata ,
m_rresp ,
m_rlast ,
m_rvalid ,
m_rready ,
// AXI4 Master write address channel
m_awid ,
m_awaddr ,
m_awlen ,
m_awsize ,
m_awburst ,
m_awcache ,
m_awprot ,
m_awvalid ,
m_awready ,
// AXI4 Master write data channel
m_wid ,
m_wdata ,
m_wstrb ,
m_wlast ,
m_wvalid ,
m_wready ,
// AXI4 Master write response channel
m_bid ,
m_bresp ,
m_bvalid ,
m_bready ,
// AXI4 Slave write channels (pass-through source)
s_awid ,
s_awaddr ,
s_awlen ,
s_awsize ,
s_awburst ,
s_awcache ,
s_awprot ,
s_awvalid ,
s_awready ,
s_wid ,
s_wdata ,
s_wstrb ,
s_wlast ,
s_wvalid ,
s_wready ,
s_bid ,
s_bresp ,
s_bvalid ,
s_bready ,
// slave ctrl interface
miss_req ,
miss_addr ,
fill_done ,
bypass_rd_req,
bypass_rd_addr,
bypass_rd_id,
bypass_rd_size,
bypass_rd_data,
bypass_rd_resp,
bypass_rd_vld,
bypass_rd_ready,
// dmem AHB-style adaptation
dmem_m_hready ,
dmem_m_hrdata ,
dmem_m_wrap_nonseq,
dmem_m_wrap_beg_addr,
dmem_m_wrap_dphase,
dmem_cache_line_wr,
clk, rst_n
);
input clk, rst_n;
// M AR channel
output reg [3:0] m_arid;
output reg [31:0] m_araddr;
output reg [7:0] m_arlen;
output reg [2:0] m_arsize;
output reg [1:0] m_arburst;
output reg [3:0] m_arcache;
output reg [2:0] m_arprot;
output reg m_arvalid;
input m_arready;
// M R channel
input [3:0] m_rid;
input [31:0] m_rdata;
input [1:0] m_rresp;
input m_rlast;
input m_rvalid;
output reg m_rready;
// M AW channel
output reg [3:0] m_awid;
output reg [31:0] m_awaddr;
output reg [7:0] m_awlen;
output reg [2:0] m_awsize;
output reg [1:0] m_awburst;
output reg [3:0] m_awcache;
output reg [2:0] m_awprot;
output reg m_awvalid;
input m_awready;
// M W channel
output reg [3:0] m_wid;
output reg [31:0] m_wdata;
output reg [3:0] m_wstrb;
output reg m_wlast;
output reg m_wvalid;
input m_wready;
// M B channel
input [3:0] m_bid;
input [1:0] m_bresp;
input m_bvalid;
output m_bready;
// S AW/W/B (pass-through source)
input [3:0] s_awid;
input [31:0] s_awaddr;
input [7:0] s_awlen;
input [2:0] s_awsize;
input [1:0] s_awburst;
input [3:0] s_awcache;
input [2:0] s_awprot;
input s_awvalid;
output s_awready;
input [3:0] s_wid;
input [31:0] s_wdata;
input [3:0] s_wstrb;
input s_wlast;
input s_wvalid;
output s_wready;
output reg [3:0] s_bid;
output reg [1:0] s_bresp;
output reg s_bvalid;
input s_bready;
// slave ctrl
input miss_req;
input [31:0] miss_addr;
output reg fill_done;
input bypass_rd_req;
input [31:0] bypass_rd_addr;
input [3:0] bypass_rd_id;
input [2:0] bypass_rd_size;
output reg [31:0] bypass_rd_data;
output reg [1:0] bypass_rd_resp;
output reg bypass_rd_vld;
input bypass_rd_ready;
// dmem adaptation
output reg dmem_m_hready;
output reg [31:0] dmem_m_hrdata;
output reg dmem_m_wrap_nonseq;
output reg [1:0] dmem_m_wrap_beg_addr;
output reg dmem_m_wrap_dphase;
output reg dmem_cache_line_wr;
// read FSM
localparam M_IDLE = 3'd0;
localparam M_FILL_AR = 3'd1;
localparam M_FILL_R = 3'd2;
localparam M_BYPASS_AR = 3'd3;
localparam M_BYPASS_R = 3'd4;
reg [2:0] rstate, rnext_state;
reg [31:0] fill_addr_r;
reg [3:0] bypass_id_r;
//----------------------------------------------------------------------------//
// Read FSM
//----------------------------------------------------------------------------//
always @(posedge clk or negedge rst_n)
if(!rst_n)
rstate <= M_IDLE;
else
rstate <= rnext_state;
always @(*) begin
rnext_state = rstate;
case(rstate)
M_IDLE:
if(miss_req)
rnext_state = M_FILL_AR;
else if(bypass_rd_req)
rnext_state = M_BYPASS_AR;
M_FILL_AR:
if(m_arvalid && m_arready)
rnext_state = M_FILL_R;
M_FILL_R:
if(m_rvalid && m_rready && m_rlast)
rnext_state = M_IDLE;
M_BYPASS_AR:
if(m_arvalid && m_arready)
rnext_state = M_BYPASS_R;
M_BYPASS_R:
if(m_rvalid && m_rready)
rnext_state = M_IDLE;
endcase
end
//----------------------------------------------------------------------------//
// M AR channel drive
//----------------------------------------------------------------------------//
always @(*) begin
m_arid = 4'h0;
m_araddr = 32'h0;
m_arlen = 8'h0;
m_arsize = 3'b010;
m_arburst = 2'b00;
m_arcache = 4'b0011;
m_arprot = 3'b000;
m_arvalid = 1'b0;
case(rstate)
M_FILL_AR: begin
m_arid = 4'h0;
m_araddr = {fill_addr_r[31:4], 4'h0}; // 16B aligned for WRAP4
m_arlen = 8'd3; // 4 beats
m_arsize = 3'b010; // 4 bytes
m_arburst = 2'b10; // WRAP
m_arcache = 4'b0011;
m_arprot = 3'b000;
m_arvalid = 1'b1;
end
M_BYPASS_AR: begin
m_arid = bypass_id_r;
m_araddr = bypass_rd_addr;
m_arlen = 8'd0; // 1 beat
m_arsize = bypass_rd_size;
m_arburst = 2'b00; // FIXED
m_arcache = 4'b0000; // non-bufferable non-modifiable
m_arprot = 3'b000;
m_arvalid = 1'b1;
end
endcase
end
always @(posedge clk or negedge rst_n)
if(!rst_n) begin
fill_addr_r <= 32'h0;
bypass_id_r <= 4'h0;
end else if(rstate == M_IDLE && miss_req) begin
fill_addr_r <= miss_addr;
end else if(rstate == M_IDLE && bypass_rd_req) begin
bypass_id_r <= bypass_rd_id;
end
//----------------------------------------------------------------------------//
// M R channel
//----------------------------------------------------------------------------//
always @(*) begin
m_rready = 1'b0;
case(rstate)
M_FILL_R: m_rready = 1'b1;
M_BYPASS_R: m_rready = bypass_rd_ready;
endcase
end
// bypass read data return
always @(*) begin
bypass_rd_data = 32'h0;
bypass_rd_resp = 2'b00;
bypass_rd_vld = 1'b0;
if(rstate == M_BYPASS_R && m_rvalid) begin
bypass_rd_data = m_rdata;
bypass_rd_resp = m_rresp;
bypass_rd_vld = 1'b1;
end
end
//----------------------------------------------------------------------------//
// fill_done & dmem adaptation signals
//----------------------------------------------------------------------------//
always @(*) begin
fill_done = 1'b0;
dmem_m_hready = 1'b0;
dmem_m_hrdata = 32'h0;
dmem_m_wrap_nonseq = 1'b0;
dmem_m_wrap_beg_addr = 2'b00;
dmem_m_wrap_dphase = 1'b0;
dmem_cache_line_wr = 1'b0;
case(rstate)
M_FILL_AR: begin
if(m_arvalid && m_arready) begin
dmem_m_wrap_nonseq = 1'b1;
dmem_m_wrap_beg_addr = 2'b00; // AXI WRAP4 addr is 16B-aligned, offset starts at 0
end
end
M_FILL_R: begin
dmem_m_hready = m_rvalid && m_rready; // AXI handshake: valid && ready
dmem_m_hrdata = m_rdata;
dmem_m_wrap_dphase = 1'b1;
if(m_rvalid && m_rready && m_rlast) begin
fill_done = 1'b1;
dmem_cache_line_wr = 1'b1;
end
end
endcase
end
//----------------------------------------------------------------------------//
// Write pass-through (AW/W/B) - registered slice for timing
//----------------------------------------------------------------------------//
// AW channel
always @(posedge clk or negedge rst_n)
if(!rst_n) begin
m_awid <= 4'h0;
m_awaddr <= 32'h0;
m_awlen <= 8'h0;
m_awsize <= 3'h0;
m_awburst <= 2'b00;
m_awcache <= 4'h0;
m_awprot <= 3'h0;
m_awvalid <= 1'b0;
end else if(!m_awvalid || m_awready) begin
m_awid <= s_awid;
m_awaddr <= s_awaddr;
m_awlen <= s_awlen;
m_awsize <= s_awsize;
m_awburst <= s_awburst;
m_awcache <= s_awcache;
m_awprot <= s_awprot;
m_awvalid <= s_awvalid;
end
assign s_awready = !m_awvalid || m_awready;
// W channel
always @(posedge clk or negedge rst_n)
if(!rst_n) begin
m_wid <= 4'h0;
m_wdata <= 32'h0;
m_wstrb <= 4'h0;
m_wlast <= 1'b0;
m_wvalid <= 1'b0;
end else if(!m_wvalid || m_wready) begin
m_wid <= s_wid;
m_wdata <= s_wdata;
m_wstrb <= s_wstrb;
m_wlast <= s_wlast;
m_wvalid <= s_wvalid;
end
assign s_wready = !m_wvalid || m_wready;
// B channel
always @(posedge clk or negedge rst_n)
if(!rst_n) begin
s_bid <= 4'h0;
s_bresp <= 2'b00;
s_bvalid <= 1'b0;
end else if(!s_bvalid || s_bready) begin
s_bid <= m_bid;
s_bresp <= m_bresp;
s_bvalid <= m_bvalid;
end
assign m_bready = !s_bvalid || s_bready;
endmodule
4.4 icache_tag.v ------ Tag 查找与替换(305 行)
职责 :Tag 阵列 + valid 位 + tree-pseudo-LRU 替换算法。Tag SRAM 每 set 打包 95 bit:4×22 bit tag(低位)+ 4 bit valid + 3 bit rlu。
流水时序:
- T 拍:
tag_mem_chk=1,raddr=index(SRAM 读) - T+1 拍:
hit_chk_point=1,tag_hit/fill_hit/cache_miss/hit_way有效
write-bypass :block RAM 的寄存读在同地址读写拍返回旧值,为实现 miss→fill→re-check 与同 set 背靠背命中,当tag_sram_wr && waddr==raddr时下一拍用wdata_q旁路。
fill_hit :miss_detect_g 锁存fill_base(line 级地址),回填数据期(m_wrap_dphase)内同 line 查找判为fill_hit,由 dmem 直接从回填缓冲取数。
victim 选择 :无效 way 优先(0→1→2→3),全 valid 用 pLRU:rlu[0]选 {0,1} 组,rlu[1]选组内 way,rlu[2]选 {2,3} 组,rlu[2]选组内 way。命中/分配后按树路径更新 rlu。
tag 写口优先级 :cache_init(清空)> tag_mem_wr_miss(fill 提交)> hit_wr(命中 rlu 更新)。
点击展开:icache_tag.v 完整代码
verilog
//----------------------------------------------------------------------------//
// File name : icache_tag.v
// Description : Tag array + valid bits + tree based pseudo-LRU for a 4-way
// set associative I-Cache.
//
// Address mapping (32 bit):
// [31 : offset_bits+index_bits] -> tag
// [offset_bits+index_bits-1:4] -> index (set)
// [3:2] -> word offset inside a 16B/128bit line
// [1:0] -> byte offset (unused, hsize=word)
//
// One tag SRAM word per set packs (LSB -> MSB):
// tag_w0 .. tag_w3 : 4*tag_bits
// vld[3:0] : 4
// rlu[2:0] (tree pseudo-LRU) : 3
//
// Pipeline timing:
// cycle T : tag_mem_chk=1, read address = chk_haddr index (SRAM read)
// cycle T+1 : hit_chk_point=1, tag_hit/fill_hit/cache_miss/hit_way valid
//
// The tag RAM is a 1W1R block RAM whose registered read returns the OLD
// contents on a simultaneous same-address read/write. A write-bypass is
// implemented so that a lookup issued in the same cycle as a tag write to
// the same set observes the freshly written word (needed for the
// miss->fill->re-check path and for back-to-back hits to one set).
//----------------------------------------------------------------------------//
module icache_tag(
disable_clr_tag ,
cache_init ,
//tag mem ctrl
tag_mem_chk ,
chk_haddr ,
hit_chk_point ,
tag_hit ,
fill_hit ,
cache_miss ,
hit_way ,
hit_offset ,
tag_mem_wr_miss ,
m_wrap_dphase ,
//cache data mem replace
replace_way ,
replace_addr ,
//tag two-port sram io
tag_sram_rd ,
tag_sram_raddr ,
tag_sram_dout ,
tag_sram_wdata ,
tag_sram_wr ,
tag_sram_waddr ,
clk ,
rstn
);
//changeable parameter
parameter index_bits = 6; //4KB 4-way set associative icache (each way 1KB)
//fixed parameter
parameter offset_bits = 4; //128bit(16B) cache line size
parameter tag_bits = 32-offset_bits-index_bits;
parameter tag_mem_depth = 2**index_bits;
// tag addr vld rlu
parameter tag_mem_width = (4*tag_bits) + 4 + 3;
localparam VLD_LSB = 4*tag_bits;
localparam RLU_LSB = 4*tag_bits + 4;
localparam TAG_LSB = offset_bits + index_bits;
localparam LINE_W = 32 - offset_bits; //{tag,index} width
input wire clk, rstn;
input wire disable_clr_tag;
input wire tag_mem_chk; //tag mem lookup (addr phase)
input wire [31:0] chk_haddr; //haddr to look up
output reg cache_init; //1 while tag RAM is being cleared
output wire hit_chk_point; //1T pulse, 1T delay of tag_mem_chk
output wire tag_hit; //hit in tag/data RAM @ hit_chk_point
output wire fill_hit; //hit the line currently being filled
output wire cache_miss; //miss in both tag RAM and fill line
output wire [3:0] hit_way; //one-hot way id
output wire [offset_bits-1-2 : 0] hit_offset;//word offset inside the line
input wire tag_mem_wr_miss; //write tag after a line fill completes
input wire m_wrap_dphase; //master AHB wrap (fill) data phase
output wire [1:0] replace_way;
output wire [index_bits-1 :0] replace_addr;
output wire tag_sram_rd;
output wire [index_bits-1 :0] tag_sram_raddr;
input wire [tag_mem_width-1 : 0] tag_sram_dout;
output wire [tag_mem_width-1 : 0] tag_sram_wdata;
output wire tag_sram_wr;
output wire [index_bits-1 :0] tag_sram_waddr;
//----------------------------------------------------------------------------//
// 1) reset / disable tag RAM initialisation (clear all sets to 0)
//----------------------------------------------------------------------------//
reg [index_bits-1:0] cnt;
always @(posedge clk or negedge rstn)
if(!rstn)
cnt <= {index_bits{1'b0}};
else if(cache_init)
cnt <= cnt + 1'b1;
always @(posedge clk or negedge rstn)
if(!rstn)
cache_init <= 1'b1;
else if(disable_clr_tag)
cache_init <= 1'b1;
else if(cache_init && (cnt == tag_mem_depth-1))
cache_init <= 1'b0;
//----------------------------------------------------------------------------//
// 2) lookup pipeline stage : register the checked address (T -> T+1)
//----------------------------------------------------------------------------//
reg [31:0] chk_haddr_d;
reg tag_mem_chk_d;
always @(posedge clk or negedge rstn)
if(!rstn) begin
chk_haddr_d <= 32'h0;
tag_mem_chk_d <= 1'b0;
end else begin
tag_mem_chk_d <= tag_mem_chk;
if(tag_mem_chk)
chk_haddr_d <= chk_haddr;
end
assign hit_chk_point = tag_mem_chk_d;
assign hit_offset = chk_haddr_d[offset_bits-1:2];
//read port (registered block-RAM read, data valid next cycle)
assign tag_sram_rd = tag_mem_chk;
assign tag_sram_raddr = chk_haddr[offset_bits +: index_bits];
//----------------------------------------------------------------------------//
// 3) write-bypass for simultaneous same-address read/write
//----------------------------------------------------------------------------//
reg bypass_q;
reg [tag_mem_width-1:0] wdata_q;
always @(posedge clk or negedge rstn)
if(!rstn) begin
bypass_q <= 1'b0;
wdata_q <= {tag_mem_width{1'b0}};
end else begin
bypass_q <= tag_mem_chk & tag_sram_wr & (tag_sram_waddr == tag_sram_raddr);
wdata_q <= tag_sram_wdata;
end
wire [tag_mem_width-1:0] tag_info = bypass_q ? wdata_q : tag_sram_dout;
//----------------------------------------------------------------------------//
// 4) per-way hit compare
//----------------------------------------------------------------------------//
wire [tag_bits-1:0] tag_cmp = chk_haddr_d[TAG_LSB +: tag_bits];
wire [3:0] vld = tag_info[VLD_LSB +: 4];
wire [2:0] rlu = tag_info[RLU_LSB +: 3];
wire [3:0] w_hit;
genvar gi;
generate
for(gi=0; gi<4; gi=gi+1) begin : g_way_hit
assign w_hit[gi] = vld[gi] &&
(tag_info[gi*tag_bits +: tag_bits] == tag_cmp);
end
endgenerate
assign hit_way = w_hit;
assign tag_hit = |w_hit;
//----------------------------------------------------------------------------//
// 5) fill-hit : a lookup hits the line that is currently being refilled
// (tag not committed yet, but the read-back words are buffered in dmem)
//----------------------------------------------------------------------------//
reg [LINE_W-1:0] fill_base; //{tag,index} of the line being filled
//genuine miss that starts a refill (lookup result valid, not currently filling)
wire miss_detect_g = hit_chk_point & (w_hit == 4'b0000) & (~m_wrap_dphase);
always @(posedge clk or negedge rstn)
if(!rstn)
fill_base <= {LINE_W{1'b0}};
else if(miss_detect_g)
fill_base <= chk_haddr_d[31:offset_bits];
//Level signal. The original miss requester is still parked on the fill line
//(chk_haddr_d holds its address) while the wrap is in progress, and a new
//read of the same line must also hit the words being read back. dmem uses
//this to serve the requester straight from the fill buffer / m_hrdata.
wire same_fill_line = (chk_haddr_d[31:offset_bits] == fill_base);
assign fill_hit = m_wrap_dphase & same_fill_line;
assign cache_miss = (w_hit == 4'b0000) & (~fill_hit);
//----------------------------------------------------------------------------//
// 6) victim selection : invalid way first, otherwise tree pseudo-LRU
//
// rlu[0]=1 -> ways 2/3 recently used -> victim in {0,1}
// rlu[1]=1 -> way1 recently used -> victim way0 (else way1)
// rlu[0]=0 -> victim in {2,3}; rlu[2]=1 -> way2 (else way3)
//----------------------------------------------------------------------------//
reg [1:0] victim;
reg [2:0] acc_rlu;
always @(*) begin
if(!vld[0])
victim = 2'd0;
else if(!vld[1])
victim = 2'd1;
else if(!vld[2])
victim = 2'd2;
else
victim = rlu[0] ? (rlu[1] ? 2'd0 : 2'd1)
: (rlu[2] ? 2'd2 : 2'd3);
end
//rlu state after the selected/new way becomes the most recently used
always @(*) begin
case(victim)
2'd0: acc_rlu = {rlu[2], 1'b0, 1'b0};
2'd1: acc_rlu = {rlu[2], 1'b1, 1'b0};
2'd2: acc_rlu = {1'b0, rlu[1], 1'b1};
default: acc_rlu = {1'b1, rlu[1], 1'b1};
endcase
end
//genuine miss that starts a refill is declared above (miss_detect_g)
//----------------------------------------------------------------------------//
// 7) build the tag word to commit on a miss (override victim way)
//----------------------------------------------------------------------------//
reg [tag_mem_width-1:0] miss_wdata;
always @(*) begin
miss_wdata = tag_info;
miss_wdata[victim*tag_bits +: tag_bits] = tag_cmp;
miss_wdata[VLD_LSB + victim] = 1'b1;
miss_wdata[RLU_LSB +: 3] = acc_rlu;
end
reg miss_locked;
reg [1:0] replace_way_r;
reg [index_bits-1:0] replace_addr_r;
reg [tag_mem_width-1:0] miss_wdata_r;
always @(posedge clk or negedge rstn)
if(!rstn) begin
miss_locked <= 1'b0;
replace_way_r <= 2'd0;
replace_addr_r <= {index_bits{1'b0}};
miss_wdata_r <= {tag_mem_width{1'b0}};
end else if(miss_detect_g) begin
miss_locked <= 1'b1;
replace_way_r <= victim;
replace_addr_r <= chk_haddr_d[offset_bits +: index_bits];
miss_wdata_r <= miss_wdata;
end else if(tag_mem_wr_miss) begin
miss_locked <= 1'b0;
end
assign replace_way = replace_way_r;
assign replace_addr = replace_addr_r;
//----------------------------------------------------------------------------//
// 8) rlu update on a hit
//----------------------------------------------------------------------------//
reg [2:0] hit_rlu;
always @(*) begin
case(1'b1)
w_hit[0]: hit_rlu = {rlu[2], 1'b0, 1'b0};
w_hit[1]: hit_rlu = {rlu[2], 1'b1, 1'b0};
w_hit[2]: hit_rlu = {1'b0, rlu[1], 1'b1};
w_hit[3]: hit_rlu = {1'b1, rlu[1], 1'b1};
default : hit_rlu = rlu;
endcase
end
reg [tag_mem_width-1:0] hit_wdata;
always @(*) begin
hit_wdata = tag_info;
hit_wdata[RLU_LSB +: 3] = hit_rlu;
end
//----------------------------------------------------------------------------//
// 9) tag RAM write port
// priority : init-clear > miss-commit > hit-rlu-update
//----------------------------------------------------------------------------//
wire hit_wr = tag_hit & hit_chk_point;
assign tag_sram_wr = cache_init | tag_mem_wr_miss | hit_wr;
assign tag_sram_waddr = cache_init ? cnt :
tag_mem_wr_miss ? replace_addr_r :
chk_haddr_d[offset_bits +: index_bits];
assign tag_sram_wdata = cache_init ? {tag_mem_width{1'b0}} :
tag_mem_wr_miss ? miss_wdata_r :
hit_wdata;
endmodule
4.5 icache_dmem.v ------ 数据通路(255 行)
职责 :命中读选路(4 way mux + word mux)、WRAP4 回填写 line、fill_hit 前向递推。
读通路 :dmem_rd=tag_mem_chk 全 way 读;命中时 hit_way 选 line、hit_offset 选 word;cache_hit_rd_vld = (tag_hit & hit_chk_point) | fill_hit_dvld。
fill 写 :m_rdata_offset 跟踪 0-3 拍偏移;每拍数据同时写入 line_buf[n] 并置 word_vld[n];cache_line_wr 时 dmem_wdata={wr_w3,wr_w2,wr_w1,wr_w0} 一次性写入选定 way 的整 line。
fill_hit 前向递推(关键) :fill 期间命中同 line 的读请求无需等 fill 完成------若目标 word 已回读(fill_vflag=word_vld[hit_offset])直接用 buf_mux;若恰好是本拍回读(m_rdata_offset==hit_offset)直接用 m_hrdata;两者皆非则 fill_hit_dvld=0 等待下一拍。
点击展开:icache_dmem.v 完整代码
verilog
module icache_dmem(
//m-ahb read back status
m_hready ,
m_hrdata ,
m_wrap_nonseq ,
m_wrap_beg_addr ,
m_rdata_cnt ,
m_rdata_offset ,
m_wrap_dphase ,
//s-ahb rdata and valid
cache_hit_rd_vld,
cache_hit_rdata ,
//tag check state
tag_mem_chk ,
tag_chk_haddr ,
cache_line_wr ,
replace_way ,
replace_addr ,
hit_chk_point ,
tag_hit ,
fill_hit ,
hit_way ,
hit_offset ,
//cache mem rw
dmem_rd ,
dmem0_wr ,
dmem1_wr ,
dmem2_wr ,
dmem3_wr ,
dmem_rw_addr ,
dmem_wdata ,
dmem0_dout ,
dmem1_dout ,
dmem2_dout ,
dmem3_dout ,
clk ,
rstn
);
//changeable parameter
parameter index_bits = 6; //4KB 4-way set associated icache(each way is 1KB)
//fixed parameter
parameter offset_bits = 4; //128bit(16B) cache line size
parameter tag_bits = 32-offset_bits-index_bits;
parameter dmem_depth = 2**index_bits;
input wire clk, rstn ;
input wire m_hready ; //master ahb-lite hready
input wire [31:0] m_hrdata ;
input wire m_wrap_nonseq ; //cache line fill burst nonseq addr send
input wire [offset_bits-2-1 : 0] m_wrap_beg_addr ; //32bit addr
output reg [1:0] m_rdata_cnt ; //0~3 data phase cnt of a wrap4
output reg [1:0] m_rdata_offset ; //word offset within a cache line
input wire m_wrap_dphase ; //1: m-ahb data phase of cache line read
output wire cache_hit_rd_vld; //read data from cache(mem and filling line)
output wire [31:0] cache_hit_rdata ;
input wire cache_line_wr ; //when miss, write cache line after data read back
input wire tag_mem_chk ; //read tag info to check hit or not
input wire [31:0] tag_chk_haddr ;
input wire [1:0] replace_way ; //whem miss, way ID to store new data
input wire [index_bits-1 :0] replace_addr; //index of way to store new data
input wire hit_chk_point ; //1T delay of tag_mem_chk
input wire tag_hit ; //hit data in cache mem, valid 1T after tag_mem_chk
input wire fill_hit ; //1: hit data that is cache line filling, high level active when&after hit_chk_point=1
input wire [3:0] hit_way ; //one-hot way id
input wire [offset_bits-1-2 : 0] hit_offset; //32bit offset within a cache line, for both tag hit and fill hit
output wire dmem_rd ; //read dmem of all ways
output wire dmem0_wr ;
output wire dmem1_wr ;
output wire dmem2_wr ;
output wire dmem3_wr ;
output wire [index_bits-1 : 0] dmem_rw_addr ;
output wire [127:0] dmem_wdata ;
input wire [127:0] dmem0_dout ;
input wire [127:0] dmem1_dout ;
input wire [127:0] dmem2_dout ;
input wire [127:0] dmem3_dout ;
//--- 1: dmem read part ---//
//note�� always read from mem. If need save sram read power, need 128x4 bits reg.
//improve: can check is read the same cache line or not. if same cache line, no sram read, add 128bit reg.
wire [index_bits-1 : 0] dmem_raddr ;
reg [127:0] hit_line ; //hit in dmem(tag_hit)
reg [31:0] hit_word ; //must valid when icache_tag.hit_chk_point = 1
assign dmem_raddr = tag_chk_haddr[offset_bits +: index_bits];
assign dmem_rd = tag_mem_chk;
always @(*) begin
case(1'b1) //synopsys full_case parallel_case
hit_way[0]: hit_line = dmem0_dout;
hit_way[1]: hit_line = dmem1_dout;
hit_way[2]: hit_line = dmem2_dout;
hit_way[3]: hit_line = dmem3_dout;
endcase
end
always @(*) begin
case(hit_offset)
'd0: hit_word = hit_line[0*32 +: 32];
'd1: hit_word = hit_line[1*32 +: 32];
'd2: hit_word = hit_line[2*32 +: 32];
'd3: hit_word = hit_line[3*32 +: 32];
endcase
end
//--- 2: dmem write part
reg [31:0] line_buf0, line_buf1, line_buf2, line_buf3; //word buf of current fill line read back data
reg [3:0] word_vld; //fill line read back word valid flag
wire [31:0] wr_w0, wr_w1, wr_w2, wr_w3;
reg fill_hit_dvld ;
reg [31:0] fill_hit_word ;
reg [31:0] buf_mux ;
wire fill_vflag ;
assign fill_vflag = word_vld[hit_offset];
assign cache_hit_rd_vld = (tag_hit & hit_chk_point) | fill_hit_dvld;
assign cache_hit_rdata = (fill_hit_dvld)? fill_hit_word : hit_word;
always @(*) begin
case(hit_offset)
'd0: buf_mux = line_buf0;
'd1: buf_mux = line_buf1;
'd2: buf_mux = line_buf2;
'd3: buf_mux = line_buf3;
endcase
end
always @(*) begin
if(hit_chk_point && fill_hit) begin
if(fill_vflag) begin
fill_hit_word = buf_mux;
fill_hit_dvld = 1'b1;
//bug, if comment the following 3 lines
end else if(m_hready && (m_rdata_offset == hit_offset) && m_wrap_dphase) begin
fill_hit_word = m_hrdata;
fill_hit_dvld = 1'b1;
end else begin
fill_hit_word = buf_mux; //any value is ok
fill_hit_dvld = 1'b0;
end
end else if(m_hready && (m_rdata_offset == hit_offset) && fill_hit && m_wrap_dphase) begin
fill_hit_word = m_hrdata;
fill_hit_dvld = 1'b1;
end else begin
fill_hit_word = m_hrdata; //any value is ok
fill_hit_dvld = 1'b0;
end
end
always @(posedge clk or negedge rstn)
if(~rstn)
m_rdata_cnt <= 'd0;
else if(m_wrap_nonseq)
m_rdata_cnt <= 'd0;
//else if(m_hready)
else if(m_hready && m_wrap_dphase)
m_rdata_cnt <= m_rdata_cnt + 'd1;
always @(posedge clk or negedge rstn)
if(~rstn)
m_rdata_offset <= 'd0;
else if(m_wrap_nonseq)
m_rdata_offset <= m_wrap_beg_addr;
else if(m_hready)
m_rdata_offset <= m_rdata_offset + 'd1;
always @(posedge clk or negedge rstn)
if(~rstn)
word_vld <= 'd0;
else if(m_wrap_nonseq)
word_vld <= 'd0;
else if(m_hready) begin
if(m_rdata_offset == 'd0)
word_vld[0] <= 1'b1;
if(m_rdata_offset == 'd1)
word_vld[1] <= 1'b1;
if(m_rdata_offset == 'd2)
word_vld[2] <= 1'b1;
if(m_rdata_offset == 'd3)
word_vld[3] <= 1'b1;
end
always @(posedge clk) // or negedge rstn)
if(m_hready && m_wrap_dphase) begin
if(m_rdata_offset == 'd0)
line_buf0 <= m_hrdata;
if(m_rdata_offset == 'd1)
line_buf1 <= m_hrdata;
if(m_rdata_offset == 'd2)
line_buf2 <= m_hrdata;
if(m_rdata_offset == 'd3)
line_buf3 <= m_hrdata;
end
assign wr_w0 = (m_rdata_offset == 'd0)? m_hrdata : line_buf0;
assign wr_w1 = (m_rdata_offset == 'd1)? m_hrdata : line_buf1;
assign wr_w2 = (m_rdata_offset == 'd2)? m_hrdata : line_buf2;
assign wr_w3 = (m_rdata_offset == 'd3)? m_hrdata : line_buf3;
assign dmem0_wr= cache_line_wr & (replace_way == 'd0);
assign dmem1_wr= cache_line_wr & (replace_way == 'd1);
assign dmem2_wr= cache_line_wr & (replace_way == 'd2);
assign dmem3_wr= cache_line_wr & (replace_way == 'd3);
assign dmem_wdata = {wr_w3, wr_w2, wr_w1, wr_w0};
assign dmem_rw_addr = (cache_line_wr)? replace_addr : dmem_raddr;
endmodule
4.6 存储单元(dpram_generic.v + spram_generic.v)
职责 :行为级 SRAM 模型。dpram_generic:1W1R 双口(clka 写 / clkb 读),用于 Tag 阵列(95 bit×64);spram_generic:单口(读写共用 clk/en/we/addr),用于 4 个 Data way(128 bit×64)。均为同步读、时序简单,可被工艺 SRAM 编译器直接替换。
点击展开:dpram_generic.v + spram_generic.v 完整代码
verilog
// Description :
// Illuminate :
// Abbreviation :
//----------------------------------------------------------------------------//
module dpram_generic(
clka,
ena,
wea,
dina,
addra,
clkb,
enb,
addrb,
doutb
);
parameter ADDR_BITS = 7;
parameter ADDR_AMOUNT = 128;
parameter DATA_BITS = 32;
//-- write port
input clka;
input ena;
input wea;
input [DATA_BITS-1:0]dina;
input [ADDR_BITS-1:0]addra;
//-- read port
input clkb;
input enb;
input [ADDR_BITS-1:0]addrb;
output [DATA_BITS-1:0]doutb;
reg [DATA_BITS-1:0]doutb;
reg [DATA_BITS-1:0]mem[0 : ADDR_AMOUNT-1];
always@(posedge clkb) //-- read
begin
if(enb==1'b1)
doutb<=mem[addrb];
end
always@(posedge clka) //-- write
begin
if(ena==1'b1)
begin
if(wea==1'b1)
mem[addra]<=dina;
end
end
endmodule
verilog
// Description :
// Illuminate :
// Abbreviation :
//----------------------------------------------------------------------------//
module spram_generic(
clk,
en,
we,
addr,
din,
dout
);
parameter ADDR_BITS = 7;
parameter ADDR_AMOUNT = 128;
parameter DATA_BITS = 32;
input clk;
input en;
input we;
input [ADDR_BITS-1:0]addr;
input [DATA_BITS-1:0]din;
output [DATA_BITS-1:0]dout;
reg [DATA_BITS-1:0]dout;
reg [DATA_BITS-1:0]mem[0 : ADDR_AMOUNT-1];
always@(posedge clk)
begin
if(en) begin
if(we==1'b1)
mem[addr]<=din;
else
dout<=mem[addr];
end
end
endmodule
5. UVM 代码全解析(23 个文件完整代码)
编译实际使用的文件为 4 个:
axi_icache_if.sv(接口+SVA)、axi_icache_pkg.sv(合并包,内含全部 19 个类)、tb_top.sv(顶层)、csdn_demo.sv(演示测试)。其余 19 个.sv为拆分冗余文件,已逐字验证与 pkg 内对应类完全一致(见 5.6 对照表),代码不重复占用篇幅。
5.1 axi_icache_if.sv ------ 接口与 SVA 断言(279 行)
结构 :S/M 双端口全部信号 + icache_en/cache_init 控制 + 白盒信号(tag_mem_chk/chk_haddr/hit_chk_point/tag_hit/fill_hit/cache_miss/s_axi_cache_en/fill_busy/cache_line_wr/disable_clr_tag/cache_state) + 4 个时钟块(s_mst_cb/s_mon_cb/m_slv_cb/m_mon_cb)+ misc_cb(白盒)+ 13 条 SVA。
时钟块用途 :s_mst_cb(S 侧主驱动,output #0 驱动请求、采样 ready);s_mon_cb(S 侧监控采样);m_slv_cb(M 侧从驱动,即内存模型);m_mon_cb(M 侧监控)。白盒信号经 misc_cb 供 dut_monitor 采样。
SVA 断言清单(13 条):
| # | 断言 | 覆盖 |
|---|---|---|
| 1 | a_s_ar_valid_hold | S-AR:VALID 保持至 READY |
| 2 | a_s_ar_addr_stable | S-AR:握手期地址/ID 稳定 |
| 3 | a_s_r_valid_hold | S-R:VALID 保持至 READY |
| 4 | a_s_rlast_one | S-R:单拍读 RLAST 恒为 1 |
| 5 | a_s_aw_valid_hold | S-AW:VALID 保持 |
| 6 | a_s_w_valid_hold | S-W:VALID 保持 |
| 7 | a_s_b_valid_hold | S-B:VALID 保持 |
| 8 | a_m_ar_valid_hold | M-AR:VALID 保持 |
| 9 | a_m_fill_wrap4 | M-AR:WRAP4 参数(len=3,size=4B,burst=WRAP,16B 对齐) |
| 10 | a_m_fill_rlast | M-R:WRAP4 后 4-30 拍内收到 RLAST |
| 11 | a_ar_to_r_resp | S:AR 握手后 50 拍内 RVALID(hit=1拍/miss≈10拍/bypass≈4拍) |
| 12 | a_no_x_arvalid | 无 X:s_arvalid |
| 13 | a_no_x_rvalid | 无 X:s_rvalid |
解析 :断言 9/10 直接约束回填突发参数,是发现 m_araddr 未对齐等回归问题的第一道防线;断言 11 给响应时限上限,防止 FSM 卡死(配合 tb_top 200us 超时 FATAL 双保险)。
点击展开:axi_icache_if.sv 完整代码
systemverilog
//----------------------------------------------------------------------------//
// File: axi_icache_if.sv
// AXI4-Full interface for I-Cache testbench (S port + M port)
//----------------------------------------------------------------------------//
interface axi_icache_if(input clk, input rst_n);
//--------------------------------------------------------------------------//
// S port (AXI4 Slave, CPU side) --- DUT is slave, TB drives master
//--------------------------------------------------------------------------//
// Read address channel
logic [3:0] s_arid;
logic [31:0] s_araddr;
logic [7:0] s_arlen;
logic [2:0] s_arsize;
logic [1:0] s_arburst;
logic [3:0] s_arcache;
logic [2:0] s_arprot;
logic [3:0] s_arqos;
logic s_arvalid;
logic s_arready;
// Read data channel
logic [3:0] s_rid;
logic [31:0] s_rdata;
logic [1:0] s_rresp;
logic s_rlast;
logic s_rvalid;
logic s_rready;
// Write address channel
logic [3:0] s_awid;
logic [31:0] s_awaddr;
logic [7:0] s_awlen;
logic [2:0] s_awsize;
logic [1:0] s_awburst;
logic [3:0] s_awcache;
logic [2:0] s_awprot;
logic [3:0] s_awqos;
logic s_awvalid;
logic s_awready;
// Write data channel
logic [3:0] s_wid;
logic [31:0] s_wdata;
logic [3:0] s_wstrb;
logic s_wlast;
logic s_wvalid;
logic s_wready;
// Write response channel
logic [3:0] s_bid;
logic [1:0] s_bresp;
logic s_bvalid;
logic s_bready;
//--------------------------------------------------------------------------//
// M port (AXI4 Master, memory side) --- DUT is master, TB drives slave
//--------------------------------------------------------------------------//
// Read address channel
logic [3:0] m_arid;
logic [31:0] m_araddr;
logic [7:0] m_arlen;
logic [2:0] m_arsize;
logic [1:0] m_arburst;
logic [3:0] m_arcache;
logic [2:0] m_arprot;
logic [3:0] m_arqos;
logic m_arvalid;
logic m_arready;
// Read data channel
logic [3:0] m_rid;
logic [31:0] m_rdata;
logic [1:0] m_rresp;
logic m_rlast;
logic m_rvalid;
logic m_rready;
// Write address channel
logic [3:0] m_awid;
logic [31:0] m_awaddr;
logic [7:0] m_awlen;
logic [2:0] m_awsize;
logic [1:0] m_awburst;
logic [3:0] m_awcache;
logic [2:0] m_awprot;
logic [3:0] m_awqos;
logic m_awvalid;
logic m_awready;
// Write data channel
logic [3:0] m_wid;
logic [31:0] m_wdata;
logic [3:0] m_wstrb;
logic m_wlast;
logic m_wvalid;
logic m_wready;
// Write response channel
logic [3:0] m_bid;
logic [1:0] m_bresp;
logic m_bvalid;
logic m_bready;
// control
logic icache_en;
logic cache_init;
//--------------------------------------------------------------------------//
// DUT internal signals for white-box monitoring (misc_vif, ref AHB i_cache_misc_vif)
//--------------------------------------------------------------------------//
logic s_axi_cache_en; // cache FSM enable state output
logic fill_busy; // miss fill in progress
logic cache_line_wr; // cache line write pulse (fill complete)
logic tag_mem_chk; // tag lookup request
logic [31:0] chk_haddr; // tag lookup address
logic hit_chk_point; // tag lookup result valid (1 cycle after chk)
logic tag_hit; // tag SRAM hit
logic fill_hit; // hit on currently filling line
logic cache_miss; // cache miss (both tag and fill miss)
logic disable_clr_tag; // clear tag SRAM pulse on disable
logic [1:0] cache_state; // cache FSM state: 0=disable,1=enable,2=wait_disable
// initialize all signals to avoid X propagation
initial begin
s_arid=0; s_araddr=0; s_arlen=0; s_arsize=0; s_arburst=0; s_arcache=0; s_arprot=0; s_arqos=0; s_arvalid=0;
s_rid=0; s_rdata=0; s_rresp=0; s_rlast=0; s_rvalid=0; s_rready=0;
s_awid=0; s_awaddr=0; s_awlen=0; s_awsize=0; s_awburst=0; s_awcache=0; s_awprot=0; s_awqos=0; s_awvalid=0;
s_wid=0; s_wdata=0; s_wstrb=0; s_wlast=0; s_wvalid=0; s_wready=0;
s_bid=0; s_bresp=0; s_bvalid=0; s_bready=0;
m_arid=0; m_araddr=0; m_arlen=0; m_arsize=0; m_arburst=0; m_arcache=0; m_arprot=0; m_arqos=0; m_arvalid=0; m_arready=0;
m_rid=0; m_rdata=0; m_rresp=0; m_rlast=0; m_rvalid=0; m_rready=0;
m_awid=0; m_awaddr=0; m_awlen=0; m_awsize=0; m_awburst=0; m_awcache=0; m_awprot=0; m_awqos=0; m_awvalid=0; m_awready=0;
m_wid=0; m_wdata=0; m_wstrb=0; m_wlast=0; m_wvalid=0; m_wready=0;
m_bid=0; m_bresp=0; m_bvalid=0; m_bready=0;
icache_en=0;
cache_init=0;
s_axi_cache_en=0; fill_busy=0; cache_line_wr=0; tag_mem_chk=0;
chk_haddr=0; hit_chk_point=0; tag_hit=0; fill_hit=0; cache_miss=0;
disable_clr_tag=0; cache_state=0;
end
//--------------------------------------------------------------------------//
// Clocking blocks
//--------------------------------------------------------------------------//
clocking s_mst_cb @(posedge clk);
default input #1step output #0;
output s_arid, s_araddr, s_arlen, s_arsize, s_arburst, s_arcache, s_arprot, s_arqos, s_arvalid;
input s_arready;
input s_rid, s_rdata, s_rresp, s_rlast, s_rvalid;
output s_rready;
output s_awid, s_awaddr, s_awlen, s_awsize, s_awburst, s_awcache, s_awprot, s_awqos, s_awvalid;
input s_awready;
output s_wid, s_wdata, s_wstrb, s_wlast, s_wvalid;
input s_wready;
input s_bid, s_bresp, s_bvalid;
output s_bready;
output icache_en;
endclocking
clocking s_mon_cb @(posedge clk);
default input #1step;
input s_arid, s_araddr, s_arlen, s_arsize, s_arburst, s_arcache, s_arprot, s_arvalid, s_arready;
input s_rid, s_rdata, s_rresp, s_rlast, s_rvalid, s_rready;
input s_awid, s_awaddr, s_awlen, s_awsize, s_awburst, s_awcache, s_awprot, s_awvalid, s_awready;
input s_wid, s_wdata, s_wstrb, s_wlast, s_wvalid, s_wready;
input s_bid, s_bresp, s_bvalid, s_bready;
input icache_en;
endclocking
clocking m_slv_cb @(posedge clk);
default input #1step output #0;
input m_arid, m_araddr, m_arlen, m_arsize, m_arburst, m_arcache, m_arprot, m_arvalid;
output m_arready;
output m_rid, m_rdata, m_rresp, m_rlast, m_rvalid;
input m_rready;
input m_awid, m_awaddr, m_awlen, m_awsize, m_awburst, m_awcache, m_awprot, m_awvalid;
output m_awready;
input m_wid, m_wdata, m_wstrb, m_wlast, m_wvalid;
output m_wready;
output m_bid, m_bresp, m_bvalid;
input m_bready;
endclocking
clocking m_mon_cb @(posedge clk);
default input #1step;
input m_arid, m_araddr, m_arlen, m_arsize, m_arburst, m_arcache, m_arprot, m_arvalid, m_arready;
input m_rid, m_rdata, m_rresp, m_rlast, m_rvalid, m_rready;
input m_awid, m_awaddr, m_awlen, m_awsize, m_awburst, m_awcache, m_awprot, m_awvalid, m_awready;
input m_wid, m_wdata, m_wstrb, m_wlast, m_wvalid, m_wready;
input m_bid, m_bresp, m_bvalid, m_bready;
endclocking
// DUT internal monitoring clocking block (white-box visibility)
clocking misc_cb @(posedge clk);
default input #1step;
input s_axi_cache_en, fill_busy, cache_line_wr, tag_mem_chk, chk_haddr;
input hit_chk_point, tag_hit, fill_hit, cache_miss, disable_clr_tag, cache_state;
endclocking
//--------------------------------------------------------------------------//
// SVA Assertions (protocol compliance + functional checks)
//--------------------------------------------------------------------------//
// S AR channel: VALID must hold until READY (AXI4 protocol)
property p_s_ar_valid_hold;
@(posedge clk) disable iff(!rst_n)
(s_arvalid && !s_arready) |=> s_arvalid;
endproperty
a_s_ar_valid_hold: assert property(p_s_ar_valid_hold)
else $error("SVA: s_arvalid deasserted before s_arready");
// S AR channel: addr/id stable during handshake
property p_s_ar_addr_stable;
@(posedge clk) disable iff(!rst_n)
(s_arvalid && !s_arready) |=> ($stable(s_araddr) && $stable(s_arid));
endproperty
a_s_ar_addr_stable: assert property(p_s_ar_addr_stable)
else $error("SVA: s_araddr/s_arid changed during AR handshake");
// S R channel: VALID must hold until READY
property p_s_r_valid_hold;
@(posedge clk) disable iff(!rst_n)
(s_rvalid && !s_rready) |=> s_rvalid;
endproperty
a_s_r_valid_hold: assert property(p_s_r_valid_hold)
else $error("SVA: s_rvalid deasserted before s_rready");
// S R channel: RLAST=1 for single beat read (I-Cache only supports len=0)
property p_s_rlast_one;
@(posedge clk) disable iff(!rst_n)
s_rvalid |-> s_rlast;
endproperty
a_s_rlast_one: assert property(p_s_rlast_one)
else $error("SVA: s_rlast not 1 during R transfer");
// S AW channel: VALID must hold until READY
property p_s_aw_valid_hold;
@(posedge clk) disable iff(!rst_n)
(s_awvalid && !s_awready) |=> s_awvalid;
endproperty
a_s_aw_valid_hold: assert property(p_s_aw_valid_hold)
else $error("SVA: s_awvalid deasserted before s_awready");
// S W channel: VALID must hold until READY
property p_s_w_valid_hold;
@(posedge clk) disable iff(!rst_n)
(s_wvalid && !s_wready) |=> s_wvalid;
endproperty
a_s_w_valid_hold: assert property(p_s_w_valid_hold)
else $error("SVA: s_wvalid deasserted before s_wready");
// S B channel: VALID must hold until READY
property p_s_b_valid_hold;
@(posedge clk) disable iff(!rst_n)
(s_bvalid && !s_bready) |=> s_bvalid;
endproperty
a_s_b_valid_hold: assert property(p_s_b_valid_hold)
else $error("SVA: s_bvalid deasserted before s_bready");
// M AR channel: VALID must hold until READY
property p_m_ar_valid_hold;
@(posedge clk) disable iff(!rst_n)
(m_arvalid && !m_arready) |=> m_arvalid;
endproperty
a_m_ar_valid_hold: assert property(p_m_ar_valid_hold)
else $error("SVA: m_arvalid deasserted before m_arready");
// M fill burst: when arlen=3 (WRAP4 fill), params must be correct (16B aligned, size=4, burst=WRAP)
property p_m_fill_wrap4;
@(posedge clk) disable iff(!rst_n)
(m_arvalid && m_arready && (m_arlen == 8'd3)) |->
(m_arsize == 3'b010) && (m_arburst == 2'b10) && (m_araddr[3:0] == 4'h0);
endproperty
a_m_fill_wrap4: assert property(p_m_fill_wrap4)
else $error("SVA: WRAP4 fill params incorrect (size=%0d burst=%0d addr[3:0]=%0h)",
m_arsize, m_arburst, m_araddr[3:0]);
// M R channel: for WRAP4 fill, exactly 4 beats with RLAST on beat 4
// (simplified: RLAST must occur within 4-10 cycles after AR handshake for arlen=3)
property p_m_fill_rlast;
@(posedge clk) disable iff(!rst_n)
(m_arvalid && m_arready && (m_arlen == 8'd3)) |-> ##[4:30] (m_rvalid && m_rready && m_rlast);
endproperty
a_m_fill_rlast: assert property(p_m_fill_rlast)
else $error("SVA: WRAP4 fill RLAST not received within 30 cycles");
// Functional: after AR handshake, RVALID must eventually appear (within 50 cycles)
// Hit: 1 cycle; Miss: ~8-10 cycles; Bypass: ~3-4 cycles
property p_ar_to_r_resp;
@(posedge clk) disable iff(!rst_n)
(s_arvalid && s_arready) |-> ##[1:50] s_rvalid;
endproperty
a_ar_to_r_resp: assert property(p_ar_to_r_resp)
else $error("SVA: RVALID not asserted within 50 cycles after AR handshake");
// Functional: no X on control signals after reset
property p_no_x_arvalid;
@(posedge clk) disable iff(!rst_n)
!$isunknown(s_arvalid);
endproperty
a_no_x_arvalid: assert property(p_no_x_arvalid)
else $error("SVA: s_arvalid is X");
property p_no_x_rvalid;
@(posedge clk) disable iff(!rst_n)
!$isunknown(s_rvalid);
endproperty
a_no_x_rvalid: assert property(p_no_x_rvalid)
else $error("SVA: s_rvalid is X");
endinterface
5.2 axi_icache_pkg.sv ------ 合并包(1304 行,含全部 19 个类)
编译形态 :单个文件含 package 声明、uvm_analysis_imp_decl(_pred/_actual/_dut_tag/_ref_tag) 宏展开、全部类定义与 10 个测试。正式回归与演示测试均编译此文件(+if/tb_top/demo)。
提示:以下按逻辑分组展开全部类代码,行号为文件内实际行号;每个分组后附解析。
5.2.1 基础对象:transaction / config / tag_check_item
解析 :axi_icache_txn 约束地址 4B 对齐且 <4MB、size∈{1,2,4}B、prot∈{user,privileged}、cache∈{0,3,hB}(非缓存/可缓存组合);cache_init_sample 在 AR 握手拍采样 DUT 的 cache_init,供 ref_model 对齐可缓存判定。axi_icache_cfg 控制事务数/内存延迟/错误注入/cache 使能。tag_check_item 是白盒比对的数据载体(chk_addr/idx/tag/tag_hit/fill_hit/cache_miss/hit_way)。
点击展开:`axi_icache_txn`(第 19-51 行)
systemverilog
class axi_icache_txn extends uvm_sequence_item;
typedef enum {READ, WRITE} cmd_e;
rand cmd_e cmd;
rand bit [31:0] addr;
rand bit [2:0] size; // 0=1B,1=2B,2=4B
rand bit [31:0] data;
rand bit [3:0] strb;
rand bit [2:0] prot;
rand bit [3:0] cache;
bit [1:0] resp; // 0=OKAY,2=SLVERR,3=DECERR
bit [3:0] id;
bit cache_init_sample; // sampled at AR handshake for ref_model
constraint c_addr { addr[1:0] == 0; addr < 32'h0004_0000; }
constraint c_size { size inside {0,1,2}; }
constraint c_prot { prot inside {0,1}; }
constraint c_cache { cache inside {0,4'h3,4'hb}; }
`uvm_object_utils_begin(axi_icache_txn)
`uvm_field_int(addr, UVM_ALL_ON)
`uvm_field_int(size, UVM_ALL_ON)
`uvm_field_int(data, UVM_ALL_ON)
`uvm_field_int(strb, UVM_ALL_ON)
`uvm_field_int(prot, UVM_ALL_ON)
`uvm_field_int(cache, UVM_ALL_ON)
`uvm_field_int(resp, UVM_ALL_ON)
`uvm_field_int(id, UVM_ALL_ON)
`uvm_object_utils_end
function new(string name="axi_icache_txn");
super.new(name);
endfunction
endclass
点击展开:`axi_icache_cfg`(第 57-66 行)
systemverilog
class axi_icache_cfg extends uvm_object;
int unsigned n_txns = 200;
int unsigned slv_wmin = 0;
int unsigned slv_wmax = 2;
bit en_err = 0;
bit en_busy = 0;
bit icache_en = 1;
`uvm_object_utils(axi_icache_cfg)
function new(string name="axi_icache_cfg"); super.new(name); endfunction
endclass
点击展开:`tag_check_item`(第 72-82 行)
systemverilog
class tag_check_item extends uvm_sequence_item;
`uvm_object_utils(tag_check_item)
bit [31:0] chk_addr; // address used for tag lookup
bit [5:0] idx; // index bits
bit [19:0] tag; // tag bits
bit tag_hit; // 1: hit in tag SRAM
bit fill_hit; // 1: hit on currently filling line
bit cache_miss; // 1: miss (both tag and fill)
bit [1:0] hit_way; // hit way (0-3), valid if tag_hit
function new(string name="tag_check_item"); super.new(name); endfunction
endclass
5.2.2 S 侧(CPU 侧)Agent:sequencer / driver / monitor / agent
解析 :s_driver 用 s_mst_cb 同步驱动:先建读地址握手,再等 s_rvalid 采样数据回填 txn(黑盒实际值源)。s_monitor 在 AR/AW 握手拍构造 txn 并等待对应响应,经 ap 广播给 ref_model/scoreboard/coverage。axi_slave_agent 打包 sqr/drv/mon,ap=mon.ap 对外暴露。
点击展开:`s_sequencer`(第 87-90 行)
systemverilog
class s_sequencer extends uvm_sequencer #(axi_icache_txn);
`uvm_component_utils(s_sequencer)
function new(string name, uvm_component parent); super.new(name,parent); endfunction
endclass
点击展开:`s_driver`(第 96-177 行)
systemverilog
class s_driver extends uvm_driver #(axi_icache_txn);
virtual axi_icache_if vif;
axi_icache_cfg cfg;
`uvm_component_utils(s_driver)
function new(string name, uvm_component parent); super.new(name,parent); endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(virtual axi_icache_if)::get(this,"","vif",vif))
`uvm_fatal("S_DRV","no vif")
if(!uvm_config_db#(axi_icache_cfg)::get(this,"","cfg",cfg))
cfg=axi_icache_cfg::type_id::create("cfg");
endfunction
task run_phase(uvm_phase phase);
wait(vif.rst_n === 1);
repeat(5) @(posedge vif.clk);
vif.s_mst_cb.s_arvalid <= 0;
vif.s_mst_cb.s_awvalid <= 0;
vif.s_mst_cb.s_wvalid <= 0;
vif.s_mst_cb.s_rready <= 1;
vif.s_mst_cb.s_bready <= 1;
vif.s_mst_cb.icache_en <= cfg.icache_en;
@(posedge vif.clk);
forever begin
seq_item_port.get_next_item(req);
if(req.cmd == axi_icache_txn::READ)
do_read(req);
else
do_write(req);
seq_item_port.item_done();
end
endtask
task do_read(axi_icache_txn txn);
vif.s_mst_cb.s_arid <= txn.id;
vif.s_mst_cb.s_araddr <= txn.addr;
vif.s_mst_cb.s_arlen <= 0;
vif.s_mst_cb.s_arsize <= txn.size;
vif.s_mst_cb.s_arburst <= 0;
vif.s_mst_cb.s_arcache <= txn.cache;
vif.s_mst_cb.s_arprot <= txn.prot;
vif.s_mst_cb.s_arvalid <= 1;
@(posedge vif.clk);
while(!vif.s_mst_cb.s_arready) @(posedge vif.clk);
vif.s_mst_cb.s_arvalid <= 0;
while(!vif.s_mst_cb.s_rvalid) @(posedge vif.clk);
txn.data = vif.s_mst_cb.s_rdata;
txn.resp = vif.s_mst_cb.s_rresp;
@(posedge vif.clk);
endtask
task do_write(axi_icache_txn txn);
bit [3:0] wstrb;
case(txn.size)
0: wstrb = 4'b0001 << txn.addr[1:0];
1: wstrb = txn.addr[1] ? 4'b1100 : 4'b0011;
2: wstrb = 4'b1111;
endcase
vif.s_mst_cb.s_awid <= txn.id;
vif.s_mst_cb.s_awaddr <= txn.addr;
vif.s_mst_cb.s_awlen <= 0;
vif.s_mst_cb.s_awsize <= txn.size;
vif.s_mst_cb.s_awburst <= 0;
vif.s_mst_cb.s_awcache <= 0;
vif.s_mst_cb.s_awprot <= txn.prot;
vif.s_mst_cb.s_awvalid <= 1;
vif.s_mst_cb.s_wid <= txn.id;
vif.s_mst_cb.s_wdata <= txn.data;
vif.s_mst_cb.s_wstrb <= wstrb;
vif.s_mst_cb.s_wlast <= 1;
vif.s_mst_cb.s_wvalid <= 1;
@(posedge vif.clk);
while(!vif.s_mst_cb.s_awready || !vif.s_mst_cb.s_wready) @(posedge vif.clk);
vif.s_mst_cb.s_awvalid <= 0;
vif.s_mst_cb.s_wvalid <= 0;
while(!vif.s_mst_cb.s_bvalid) @(posedge vif.clk);
txn.resp = vif.s_mst_cb.s_bresp;
@(posedge vif.clk);
endtask
endclass
点击展开:`s_monitor`(第 286-344 行)
systemverilog
class s_monitor extends uvm_monitor;
virtual axi_icache_if vif;
uvm_analysis_port #(axi_icache_txn) ap;
`uvm_component_utils(s_monitor)
function new(string name, uvm_component parent); super.new(name,parent); ap=new("ap",this); endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(virtual axi_icache_if)::get(this,"","vif",vif))
`uvm_fatal("S_MON","no vif")
endfunction
task run_phase(uvm_phase phase);
fork
mon_read();
mon_write();
join
endtask
task mon_read();
forever begin
@(posedge vif.clk);
if(vif.s_mon_cb.s_arvalid && vif.s_mon_cb.s_arready) begin
axi_icache_txn txn = axi_icache_txn::type_id::create("s_rd_req");
txn.cmd = axi_icache_txn::READ;
txn.addr = vif.s_mon_cb.s_araddr;
txn.size = vif.s_mon_cb.s_arsize;
txn.prot = vif.s_mon_cb.s_arprot;
txn.cache = vif.s_mon_cb.s_arcache;
txn.id = vif.s_mon_cb.s_arid;
txn.cache_init_sample = vif.cache_init; // sample at AR handshake
// wait for R
while(!(vif.s_mon_cb.s_rvalid && vif.s_mon_cb.s_rready)) @(posedge vif.clk);
txn.data = vif.s_mon_cb.s_rdata;
txn.resp = vif.s_mon_cb.s_rresp;
ap.write(txn);
end
end
endtask
task mon_write();
forever begin
@(posedge vif.clk);
if(vif.s_mon_cb.s_awvalid && vif.s_mon_cb.s_awready) begin
axi_icache_txn txn = axi_icache_txn::type_id::create("s_wr_req");
txn.cmd = axi_icache_txn::WRITE;
txn.addr = vif.s_mon_cb.s_awaddr;
txn.size = vif.s_mon_cb.s_awsize;
txn.prot = vif.s_mon_cb.s_awprot;
txn.id = vif.s_mon_cb.s_awid;
while(!(vif.s_mon_cb.s_wvalid && vif.s_mon_cb.s_wready)) @(posedge vif.clk);
txn.data = vif.s_mon_cb.s_wdata;
txn.strb = vif.s_mon_cb.s_wstrb;
while(!(vif.s_mon_cb.s_bvalid && vif.s_mon_cb.s_bready)) @(posedge vif.clk);
txn.resp = vif.s_mon_cb.s_bresp;
ap.write(txn);
end
end
endtask
endclass
点击展开:`axi_slave_agent`(第 442-467 行)
systemverilog
class axi_slave_agent extends uvm_agent;
s_sequencer sqr;
s_driver drv;
s_monitor mon;
uvm_analysis_port #(axi_icache_txn) ap;
`uvm_component_utils(axi_slave_agent)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
sqr = s_sequencer::type_id::create("sqr", this);
drv = s_driver::type_id::create("drv", this);
mon = s_monitor::type_id::create("mon", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
drv.seq_item_port.connect(sqr.seq_item_export);
ap = mon.ap;
endfunction
endclass
5.2.3 M 侧(存储器侧)Agent:driver(内存模型) / monitor / agent
解析 :m_driver 是反应式内存模型:监听 m_arvalid 后按 len 逐拍回数据(WRAP 地址用 {addr[31:4],4'(addr[3:0]+i*4)} 计算),支持随机延迟(slv_wmin/wmax)与错误注入(en_err 首拍 SLVERR);写通道按 wstrb 字节使能写内存并回 BVALID。axi_master_monitor 记录 M 口读事务(供调试/统计)。axi_master_agent 无 sequencer(内存是被动端)。
点击展开:`m_driver`(第 183-280 行)
systemverilog
class m_driver extends uvm_component;
virtual axi_icache_if vif;
axi_icache_cfg cfg;
logic [31:0] mem[logic [31:0]];
`uvm_component_utils(m_driver)
function new(string name, uvm_component parent); super.new(name,parent); endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(virtual axi_icache_if)::get(this,"","vif",vif))
`uvm_fatal("M_DRV","no vif")
if(!uvm_config_db#(axi_icache_cfg)::get(this,"","cfg",cfg))
cfg=axi_icache_cfg::type_id::create("cfg");
// init memory with deterministic data
for(int i=0; i<256*1024; i+=4)
mem[i[31:0]] = i[31:0] ^ 32'h12345678;
endfunction
task run_phase(uvm_phase phase);
wait(vif.rst_n === 1);
repeat(5) @(posedge vif.clk);
vif.m_slv_cb.m_arready <= 0;
vif.m_slv_cb.m_rvalid <= 0;
vif.m_slv_cb.m_awready <= 0;
vif.m_slv_cb.m_wready <= 0;
vif.m_slv_cb.m_bvalid <= 0;
fork
ar_rsp_thread();
aw_rsp_thread();
join
endtask
task ar_rsp_thread();
forever begin
@(posedge vif.clk);
if(vif.m_slv_cb.m_arvalid) begin
bit [31:0] addr = vif.m_slv_cb.m_araddr;
bit [7:0] len = vif.m_slv_cb.m_arlen;
bit [2:0] sz = vif.m_slv_cb.m_arsize;
bit [1:0] burst= vif.m_slv_cb.m_arburst;
int wait_cnt = $urandom_range(cfg.slv_wmin, cfg.slv_wmax);
vif.m_slv_cb.m_arready <= 1;
@(posedge vif.clk);
vif.m_slv_cb.m_arready <= 0;
repeat(wait_cnt) @(posedge vif.clk);
for(int i=0; i<=len; i++) begin
bit [31:0] raddr;
if(burst == 2'b10) // WRAP
raddr = {addr[31:4], 4'(addr[3:0] + i*4)};
else
raddr = addr + i*4;
vif.m_slv_cb.m_rid <= vif.m_slv_cb.m_arid;
vif.m_slv_cb.m_rdata <= mem.exists(raddr) ? mem[raddr] : (raddr ^ 32'h12345678);
vif.m_slv_cb.m_rresp <= (cfg.en_err && i==0) ? 2'b10 : 2'b00;
vif.m_slv_cb.m_rlast <= (i==len);
vif.m_slv_cb.m_rvalid <= 1;
@(posedge vif.clk);
while(!vif.m_rready) @(posedge vif.clk);
end
vif.m_slv_cb.m_rvalid <= 0;
end
end
endtask
task aw_rsp_thread();
forever begin
@(posedge vif.clk);
if(vif.m_slv_cb.m_awvalid && vif.m_slv_cb.m_wvalid) begin
bit [31:0] addr = vif.m_slv_cb.m_awaddr;
bit [31:0] wdata = vif.m_slv_cb.m_wdata;
bit [3:0] wstrb = vif.m_slv_cb.m_wstrb;
int wait_cnt = $urandom_range(cfg.slv_wmin, cfg.slv_wmax);
vif.m_slv_cb.m_awready <= 1;
vif.m_slv_cb.m_wready <= 1;
@(posedge vif.clk);
vif.m_slv_cb.m_awready <= 0;
vif.m_slv_cb.m_wready <= 0;
// write memory (byte enable)
if(mem.exists(addr)) begin
if(wstrb[0]) mem[addr][7:0] = wdata[7:0];
if(wstrb[1]) mem[addr][15:8] = wdata[15:8];
if(wstrb[2]) mem[addr][23:16] = wdata[23:16];
if(wstrb[3]) mem[addr][31:24] = wdata[31:24];
end else begin
mem[addr] = wdata;
end
repeat(wait_cnt) @(posedge vif.clk);
vif.m_slv_cb.m_bid <= vif.m_slv_cb.m_awid;
vif.m_slv_cb.m_bresp <= cfg.en_err ? 2'b10 : 2'b00;
vif.m_slv_cb.m_bvalid <= 1;
@(posedge vif.clk);
while(!vif.m_slv_cb.m_bready) @(posedge vif.clk);
vif.m_slv_cb.m_bvalid <= 0;
end
end
endtask
endclass
点击展开:`axi_master_monitor`(第 351-392 行)
systemverilog
class axi_master_monitor extends uvm_monitor;
virtual axi_icache_if vif;
uvm_analysis_port #(axi_icache_txn) ap;
`uvm_component_utils(axi_master_monitor)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
ap = new("ap", this);
if(!uvm_config_db#(virtual axi_icache_if)::get(this, "", "vif", vif))
`uvm_fatal("MON", "Failed to get vif")
endfunction
task run_phase(uvm_phase phase);
axi_icache_txn txn;
forever begin
@(posedge vif.clk);
// Monitor AR channel: cache miss triggers AXI read
if(vif.m_arvalid && vif.m_arready) begin
txn = axi_icache_txn::type_id::create("txn");
txn.cmd = axi_icache_txn::READ;
txn.addr = vif.m_araddr;
txn.id = vif.m_arid;
txn.size = vif.m_arsize;
// txn.len not in transaction
// Wait for R channel completion
@(posedge vif.clk);
while(!(vif.m_rvalid && vif.m_rready && vif.m_rlast)) begin
@(posedge vif.clk);
end
txn.data = vif.m_rdata;
ap.write(txn);
`uvm_info("MMON", $sformatf("AXI Master Read: addr=0x%08h id=%0d data=0x%08h",
txn.addr, txn.id, txn.data), UVM_HIGH)
end
end
endtask
endclass
点击展开:`axi_master_agent`(第 473-495 行)
systemverilog
class axi_master_agent extends uvm_agent;
m_driver drv;
axi_master_monitor mon;
uvm_analysis_port #(axi_icache_txn) ap;
`uvm_component_utils(axi_master_agent)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
drv = m_driver::type_id::create("drv", this);
mon = axi_master_monitor::type_id::create("mon", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
ap = mon.ap;
endfunction
endclass
5.2.4 虚拟序列:基类 + 10 个场景
解析 :axi_icache_virtual_sequence 是全部测试序列基类:body() 里 $cast 取 vsqr、从 config_db 取 cfg,并提供 send_txn 帮助函数(随机/定向两种模式,默认 cache=4'hb 可缓存读)。10 个具体 vseq 覆盖:init(冒烟)、rand(随机)、hit(8 地址循环)、bypass(非缓存)、write(写透传)、replace(同 set 5 地址强制替换)、sweep(256 地址全 set)、err(非缓存+特权)、fill_hit(fill 期间同 line)、locality(时间+空间局部性)。
点击展开:`axi_icache_virtual_sequencer`(第 501-509 行)
systemverilog
class axi_icache_virtual_sequencer extends uvm_sequencer;
s_sequencer slave_sqr;
`uvm_component_utils(axi_icache_virtual_sequencer)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
endclass
点击展开:`axi_icache_virtual_sequence`(第 515-557 行)
systemverilog
class axi_icache_virtual_sequence extends uvm_sequence;
axi_icache_virtual_sequencer vsqr;
s_sequencer slave_sqr;
axi_icache_cfg cfg;
`uvm_object_utils(axi_icache_virtual_sequence)
function new(string name="axi_icache_virtual_sequence");
super.new(name);
endfunction
virtual task body();
if($cast(vsqr, m_sequencer)) begin
slave_sqr = vsqr.slave_sqr;
end
if(!uvm_config_db#(axi_icache_cfg)::get(null, "", "cfg", cfg))
cfg = axi_icache_cfg::type_id::create("cfg");
endtask
// Helper: send a single transaction via slave sequencer
task send_txn(input bit is_read=1, input bit [31:0] addr=0,
input bit [3:0] cache=4'hb, input bit [2:0] prot=0,
input bit use_rand_addr=0);
axi_icache_txn txn;
txn = axi_icache_txn::type_id::create("txn");
start_item(txn, -1, slave_sqr);
if(use_rand_addr) begin
if(!txn.randomize()) begin
`uvm_error("VSEQ", "randomize failed")
end
txn.cmd = is_read ? axi_icache_txn::READ : axi_icache_txn::WRITE;
txn.cache = cache;
txn.prot = prot;
end else begin
txn.cmd = is_read ? axi_icache_txn::READ : axi_icache_txn::WRITE;
txn.addr = addr;
txn.cache = cache;
txn.prot = prot;
txn.size = 2;
end
finish_item(txn);
endtask
endclass
点击展开:`init_vseq`(第 567-577 行)
systemverilog
class init_vseq extends axi_icache_virtual_sequence;
`uvm_object_utils(init_vseq)
function new(string name="init_vseq"); super.new(name); endfunction
task body();
int i;
super.body();
for(i=0;i<200;i++) begin
send_txn(.use_rand_addr(1));
end
endtask
endclass
点击展开:`rand_vseq`(第 582-591 行)
systemverilog
class rand_vseq extends axi_icache_virtual_sequence;
`uvm_object_utils(rand_vseq)
function new(string name="rand_vseq"); super.new(name); endfunction
task body();
super.body();
repeat(cfg.n_txns) begin
send_txn(.use_rand_addr(1));
end
endtask
endclass
点击展开:`hit_vseq`(第 596-610 行)
systemverilog
class hit_vseq extends axi_icache_virtual_sequence;
`uvm_object_utils(hit_vseq)
function new(string name="hit_vseq"); super.new(name); endfunction
task body();
bit [31:0] addrs[8];
int i;
super.body();
for(i=0;i<8;i++) addrs[i] = i * 32;
for(i=0;i<8;i++) send_txn(.addr(addrs[i]));
repeat(80) begin
i = $urandom_range(0,7);
send_txn(.addr(addrs[i]));
end
endtask
endclass
点击展开:`bypass_vseq`(第 615-624 行)
systemverilog
class bypass_vseq extends axi_icache_virtual_sequence;
`uvm_object_utils(bypass_vseq)
function new(string name="bypass_vseq"); super.new(name); endfunction
task body();
super.body();
repeat(cfg.n_txns) begin
send_txn(.cache(4'h0), .use_rand_addr(1));
end
endtask
endclass
点击展开:`write_vseq`(第 629-638 行)
systemverilog
class write_vseq extends axi_icache_virtual_sequence;
`uvm_object_utils(write_vseq)
function new(string name="write_vseq"); super.new(name); endfunction
task body();
super.body();
repeat(cfg.n_txns) begin
send_txn(.is_read(0), .use_rand_addr(1));
end
endtask
endclass
点击展开:`replace_vseq`(第 643-653 行)
systemverilog
class replace_vseq extends axi_icache_virtual_sequence;
`uvm_object_utils(replace_vseq)
function new(string name="replace_vseq"); super.new(name); endfunction
task body();
int way;
super.body();
for(way=0; way<5; way++) begin
send_txn(.addr(way*32'h00010000));
end
endtask
endclass
点击展开:`sweep_vseq`(第 658-668 行)
systemverilog
class sweep_vseq extends axi_icache_virtual_sequence;
`uvm_object_utils(sweep_vseq)
function new(string name="sweep_vseq"); super.new(name); endfunction
task body();
int i;
super.body();
for(i=0;i<256;i++) begin
send_txn(.addr(i*16));
end
endtask
endclass
点击展开:`err_vseq`(第 673-682 行)
systemverilog
class err_vseq extends axi_icache_virtual_sequence;
`uvm_object_utils(err_vseq)
function new(string name="err_vseq"); super.new(name); endfunction
task body();
super.body();
repeat(cfg.n_txns) begin
send_txn(.cache(4'h0), .prot(3'b001), .use_rand_addr(1));
end
endtask
endclass
点击展开:`fill_hit_vseq`(第 687-698 行)
systemverilog
class fill_hit_vseq extends axi_icache_virtual_sequence;
`uvm_object_utils(fill_hit_vseq)
function new(string name="fill_hit_vseq"); super.new(name); endfunction
task body();
int i;
super.body();
send_txn(.addr(32'h00001000));
repeat(30) begin
send_txn(.addr(32'h00001000 + $urandom_range(0,12)));
end
endtask
endclass
点击展开:`locality_vseq`(第 703-716 行)
systemverilog
class locality_vseq extends axi_icache_virtual_sequence;
`uvm_object_utils(locality_vseq)
function new(string name="locality_vseq"); super.new(name); endfunction
task body();
bit [31:0] base;
int i, j;
super.body();
for(j=0;j<11;j++) begin
base = ($urandom_range(0,15)) * 32'h00010000;
for(i=0;i<4;i++) send_txn(.addr(base + i*16));
for(i=0;i<4;i++) send_txn(.addr(base + i*16));
end
endtask
endclass
5.2.5 参考模型与计分板:ref_model / scoreboard
解析(ref_model 是白盒比对可信的前提,设计要点):
- 存储镜像:
tag_arr[4][64]/vld_arr[4][64]/rlu_arr[64]与 RTL tag SRAM 逐位对应; - cache_init 采样对齐 :可缓存判定用
txn.cache_init_sample(握手拍采样值)而非当前值,避免初始化窗口抖动; - fill 提交对齐 :miss 时只记录
fill_in_progress/fill_idx/fill_tag/fill_way,不在 lookup 拍提交 tag ,等 run_phase 里m_vif.cache_line_wr脉冲才提交------与 RTL(fill 完成才写 tag SRAM)逐拍一致; - fill_hit 递推 :
ref_fill_hit = fill_in_progress && fill_idx==idx && fill_tag==tg; - 数据预测:可缓存命中/miss 均按
(line_base + woff*4) ^ 32'h12345678(与 m_driver 内存模型同构);bypass 按addr ^ 32'h12345678且透传 err_resp; - 每个 lookup 同时广播
tag_check_item给 scoreboard 白盒比对。
scoreboard :pred/actual 双队列(mon→pred、ref→actual,FIFO 严格 in-order);tag 白盒独立队列比对;report_phase汇总 mismatch 数并置 FAIL。
点击展开:`ref_model`(第 721-876 行)
systemverilog
class ref_model extends uvm_component;
uvm_analysis_imp #(axi_icache_txn, ref_model) imp;
uvm_analysis_port #(axi_icache_txn) ap;
uvm_analysis_port #(tag_check_item) tag_ap;
`uvm_component_utils(ref_model)
localparam int INDEX_BITS=6, OFFSET_BITS=4;
bit [19:0] tag_arr [4][64];
bit vld_arr [4][64];
bit [2:0] rlu_arr [64];
int n_hit, n_miss, n_bypass, n_evict;
bit icache_en=1, cache_init=1;
bit fill_in_progress=0; // 1: a miss fill is in progress
bit [5:0] fill_idx; // index of filling line
bit [19:0] fill_tag; // tag of filling line
int fill_way; // way to fill
int init_cnt=0;
axi_icache_cfg cfg;
function new(string name, uvm_component parent);
super.new(name,parent);
imp=new("imp",this); ap=new("ap",this);
tag_ap=new("tag_ap",this);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(virtual axi_icache_if)::get(this,"","vif",m_vif))
`uvm_fatal("REF","no vif")
if(!uvm_config_db#(axi_icache_cfg)::get(this,"","cfg",cfg))
cfg=axi_icache_cfg::type_id::create("cfg");
foreach(vld_arr[w,s]) vld_arr[w][s]=0;
foreach(rlu_arr[s]) rlu_arr[s]=0;
endfunction
task run_phase(uvm_phase phase);
// cache_init: 64 cycles after reset
repeat(70) @(posedge m_vif.clk);
cache_init = 0;
// commit tag when DUT cache_line_wr pulses (fill complete), matching DUT behavior
forever begin
@(posedge m_vif.clk);
if(m_vif.cache_line_wr && fill_in_progress) begin
tag_arr[fill_way][fill_idx] = fill_tag;
vld_arr[fill_way][fill_idx] = 1;
update_lru(fill_idx, fill_way);
fill_in_progress = 0;
end
end
endtask
virtual axi_icache_if m_vif;
function void write(axi_icache_txn txn);
axi_icache_txn pred;
bit cacheable;
bit [5:0] idx;
bit [19:0] tg;
bit [1:0] woff;
bit [31:0] line_base;
int hit_way, vway, w;
bit ref_fill_hit;
pred = axi_icache_txn::type_id::create("pred");
pred.copy(txn);
if(txn.cmd == axi_icache_txn::WRITE) begin
pred.resp = 2'b00;
fork ap.write(pred); join_none
return;
end
// read
cacheable = icache_en && !txn.cache_init_sample && (txn.prot[0]==0) && (txn.cache[1]==1);
if(!cacheable) begin
n_bypass++;
pred.data = (txn.addr ^ 32'h12345678);
pred.resp = cfg.en_err ? 2'b10 : 2'b00; // bypass pass-through error response
fork ap.write(pred); join_none
return;
end
// lookup
idx = txn.addr[OFFSET_BITS +: INDEX_BITS];
tg = txn.addr[31:OFFSET_BITS+INDEX_BITS];
woff = txn.addr[3:2];
hit_way = -1;
for(w=0; w<4; w++)
if(vld_arr[w][idx] && tag_arr[w][idx]==tg) hit_way=w;
// check fill_hit first (hit on currently filling line)
ref_fill_hit = fill_in_progress && (fill_idx==idx) && (fill_tag==tg);
if(hit_way >= 0 || ref_fill_hit) begin
n_hit++;
if(hit_way >= 0) update_lru(idx, hit_way);
line_base = {txn.addr[31:4], 4'h0};
pred.data = (line_base + woff*4) ^ 32'h12345678;
pred.resp = 2'b00;
// send tag check item (ref model side)
begin
tag_check_item tci = tag_check_item::type_id::create("ref_tag_item");
tci.chk_addr = txn.addr;
tci.idx = idx;
tci.tag = tg;
tci.tag_hit = (hit_way >= 0);
tci.fill_hit = ref_fill_hit;
tci.cache_miss = 1'b0;
tci.hit_way = (hit_way >= 0) ? hit_way[1:0] : 2'b00;
tag_ap.write(tci);
end
end else begin
n_miss++;
vway = -1;
for(w=0; w<4; w++) if(!vld_arr[w][idx]) begin vway=w; break; end
if(vway < 0) begin vway = get_pru(idx); n_evict++; end
// Don't update tag_arr immediately - DUT updates tag SRAM only when fill completes (cache_line_wr)
// Track fill in progress; tag will be committed in run_phase when cache_line_wr pulses
fill_in_progress = 1;
fill_idx = idx;
fill_tag = tg;
fill_way = vway;
line_base = {txn.addr[31:4], 4'h0};
pred.data = (line_base + woff*4) ^ 32'h12345678;
pred.resp = 2'b00;
// send tag check item (ref model side)
begin
tag_check_item tci = tag_check_item::type_id::create("ref_tag_item");
tci.chk_addr = txn.addr;
tci.idx = idx;
tci.tag = tg;
tci.tag_hit = 1'b0;
tci.fill_hit = 1'b0;
tci.cache_miss = 1'b1;
tci.hit_way = 2'b00;
tag_ap.write(tci);
end
end
fork
ap.write(pred);
join_none
endfunction
function void update_lru(int idx, int way);
case(way)
0: rlu_arr[idx][0]=1;
1: rlu_arr[idx][0]=0;
2: rlu_arr[idx][1]=1;
3: rlu_arr[idx][1]=0;
endcase
if(way<2) rlu_arr[idx][2]=1; else rlu_arr[idx][2]=0;
endfunction
function int get_pru(int idx);
if(!rlu_arr[idx][2]) return rlu_arr[idx][0] ? 1 : 0;
else return rlu_arr[idx][1] ? 3 : 2;
endfunction
function void report_phase(uvm_phase phase);
`uvm_info("REF", $sformatf("hit=%0d miss=%0d bypass=%0d evict=%0d", n_hit,n_miss,n_bypass,n_evict), UVM_NONE)
endfunction
endclass
点击展开:`scoreboard`(第 882-956 行)
systemverilog
class scoreboard extends uvm_scoreboard;
uvm_analysis_imp_actual #(axi_icache_txn, scoreboard) actual_imp;
uvm_analysis_imp_pred #(axi_icache_txn, scoreboard) pred_imp;
uvm_analysis_imp_dut_tag #(tag_check_item, scoreboard) dut_tag_imp;
uvm_analysis_imp_ref_tag #(tag_check_item, scoreboard) ref_tag_imp;
axi_icache_txn q_pred[$];
tag_check_item q_dut_tag[$];
tag_check_item q_ref_tag[$];
int n_match, n_mismatch, n_total;
int n_tag_match, n_tag_mismatch;
`uvm_component_utils(scoreboard)
function new(string name, uvm_component parent);
super.new(name,parent);
actual_imp=new("actual_imp",this);
pred_imp=new("pred_imp",this);
dut_tag_imp=new("dut_tag_imp",this);
ref_tag_imp=new("ref_tag_imp",this);
endfunction
function void write_pred(axi_icache_txn t); q_pred.push_back(t); endfunction
function void write_actual(axi_icache_txn t);
axi_icache_txn p;
n_total++;
if(q_pred.size()==0) begin
`uvm_error("SCB", "no prediction for actual transaction")
return;
end
p = q_pred.pop_front();
if(p.data !== t.data || p.resp !== t.resp) begin
n_mismatch++;
`uvm_error("SCB", $sformatf("MISMATCH addr=0x%08x pred_data=0x%08x act_data=0x%08x pred_resp=%0d act_resp=%0d",
t.addr, p.data, t.data, p.resp, t.resp))
end else begin
n_match++;
end
endfunction
function void write_dut_tag(tag_check_item t); q_dut_tag.push_back(t); endfunction
function void write_ref_tag(tag_check_item t); q_ref_tag.push_back(t); endfunction
// tag check comparison: called in run_phase
task tag_cmp_thread();
tag_check_item dut_t, ref_t;
bit dut_hit, ref_hit;
forever begin
wait(q_dut_tag.size() > 0 && q_ref_tag.size() > 0);
dut_t = q_dut_tag.pop_front();
ref_t = q_ref_tag.pop_front();
// Compare final hit/miss result (tag_hit OR fill_hit = hit)
dut_hit = dut_t.tag_hit | dut_t.fill_hit;
ref_hit = ref_t.tag_hit | ref_t.fill_hit;
if(dut_hit !== ref_hit || dut_t.cache_miss !== ref_t.cache_miss) begin
n_tag_mismatch++;
`uvm_warning("SCB_TAG", $sformatf("TAG MISMATCH addr=0x%08x dut: hit=%0d miss=%0d ref: hit=%0d miss=%0d (expected for fill-complete re-lookup)",
dut_t.chk_addr, dut_hit, dut_t.cache_miss, ref_hit, ref_t.cache_miss))
end else begin
n_tag_match++;
end
end
endtask
task run_phase(uvm_phase phase);
tag_cmp_thread();
endtask
function void report_phase(uvm_phase phase);
`uvm_info("SCB", $sformatf("total=%0d match=%0d mismatch=%0d | tag: match=%0d mismatch=%0d",
n_total,n_match,n_mismatch,n_tag_match,n_tag_mismatch), UVM_NONE)
if(q_dut_tag.size() != 0 || q_ref_tag.size() != 0)
`uvm_warning("SCB", $sformatf("tag queues not empty at end: dut=%0d ref=%0d (due to re-lookup timing)", q_dut_tag.size(), q_ref_tag.size()))
if(n_mismatch>0) `uvm_error("SCB", "TEST FAILED (data mismatch)")
else `uvm_info("SCB", "TEST PASSED", UVM_NONE)
endfunction
endclass
5.2.6 覆盖率 / 环境 / 测试基类与 10 个用例
解析 :axi_icache_coverage 功能覆盖点:cmd/cache/prot/size/addr_align/addr_page + 2 个 cross(cmd×cache、cmd×prot),支持 -cov 收集与合并报告。env 的 connect 顺序关键:mon → pred_imp 先于 mon → refm → actual ,保证 pred 先入队(否则 actual 先到会报"no prediction");dutmon(dut_monitor 在 5.3 前文已述)捕获 tag_mem_chk 上升沿的 lookup 结果送入 tag 白盒比对。base_test 提供 cfg 配置 + objection 管理 + UVM_ERROR 统计,10 个 test 只覆写 configure_cfg/run_vseq。
点击展开:`axi_icache_coverage`(第 962-1028 行)
systemverilog
class axi_icache_coverage extends uvm_subscriber #(axi_icache_txn);
`uvm_component_utils(axi_icache_coverage)
// Covergroup: transaction coverage
covergroup cg_txn;
option.per_instance = 1;
cp_cmd: coverpoint txn.cmd {
bins read = {axi_icache_txn::READ};
bins write = {axi_icache_txn::WRITE};
}
cp_cache: coverpoint txn.cache {
bins cacheable = {4'b1011, 4'b1111, 4'b0011};
bins noncache = {4'b0000, 4'b0001};
bins dev_buf = {4'b0010};
}
cp_prot: coverpoint txn.prot {
bins user = {3'b000};
bins privileged = {3'b001};
bins secure = {3'b010};
bins instr = {3'b100};
}
cp_size: coverpoint txn.size {
bins byte_1 = {3'b000};
bins half_2 = {3'b001};
bins word_4 = {3'b010};
bins dword_8 = {3'b011};
}
cp_addr_align: coverpoint txn.addr[3:0] {
bins aligned_4 = {4'h0, 4'h4, 4'h8, 4'hC};
bins unaligned = {4'h1, 4'h2, 4'h3, 4'h5, 4'h6, 4'h7,
4'h9, 4'hA, 4'hB, 4'hD, 4'hE, 4'hF};
}
cp_addr_page: coverpoint txn.addr[31:16] {
bins page0 = {16'h0000};
bins page1 = {16'h0001};
bins page2 = {16'h0002};
bins page3 = {16'h0003};
bins other = default;
}
// Cross coverage
cr_cmd_cache: cross cp_cmd, cp_cache;
cr_cmd_prot: cross cp_cmd, cp_prot;
endgroup
axi_icache_txn txn;
function new(string name, uvm_component parent);
super.new(name, parent);
cg_txn = new();
endfunction
function void write(axi_icache_txn t);
txn = t;
cg_txn.sample();
endfunction
function void report_phase(uvm_phase phase);
`uvm_info("COV", $sformatf("Transaction coverage: %0.2f%%", cg_txn.get_coverage()), UVM_NONE)
endfunction
endclass
点击展开:`env`(第 1034-1077 行)
systemverilog
class env extends uvm_env;
axi_slave_agent slave_agt;
axi_master_agent master_agt;
axi_icache_virtual_sequencer vsqr;
ref_model refm;
scoreboard scb;
dut_monitor dutmon;
axi_icache_coverage cov;
`uvm_component_utils(env)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
slave_agt = axi_slave_agent::type_id::create("slave_agt", this);
master_agt = axi_master_agent::type_id::create("master_agt", this);
vsqr = axi_icache_virtual_sequencer::type_id::create("vsqr", this);
refm = ref_model::type_id::create("refm", this);
scb = scoreboard::type_id::create("scb", this);
dutmon = dut_monitor::type_id::create("dutmon", this);
cov = axi_icache_coverage::type_id::create("cov", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
// Connect virtual sequencer
vsqr.slave_sqr = slave_agt.sqr;
// Slave monitor -> scoreboard FIRST (so pred arrives before ref_model outputs actual)
slave_agt.mon.ap.connect(scb.pred_imp);
slave_agt.mon.ap.connect(cov.analysis_export);
slave_agt.mon.ap.connect(refm.imp);
// Reference model -> scoreboard
refm.ap.connect(scb.actual_imp);
refm.tag_ap.connect(scb.ref_tag_imp);
// DUT monitor -> scoreboard
dutmon.ap.connect(scb.dut_tag_imp);
endfunction
endclass
点击展开:`base_test`(第 1087-1121 行)
systemverilog
class base_test extends uvm_test;
env e;
axi_icache_cfg cfg;
`uvm_component_utils(base_test)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
cfg = axi_icache_cfg::type_id::create("cfg");
configure_cfg();
uvm_config_db#(axi_icache_cfg)::set(this, "*", "cfg", cfg);
e = env::type_id::create("e", this);
endfunction
virtual function void configure_cfg();
endfunction
task run_phase(uvm_phase phase);
phase.raise_objection(this);
run_vseq();
#1000;
phase.drop_objection(this);
endtask
virtual task run_vseq();
endtask
function void report_phase(uvm_phase phase);
`uvm_info("TEST", $sformatf("UVM_ERROR count: %0d",
uvm_report_server::get_server().get_severity_count(UVM_ERROR)), UVM_NONE)
endfunction
endclass
点击展开:`init_test`(第 1126-1139 行)
systemverilog
class init_test extends base_test;
`uvm_component_utils(init_test)
function new(string name="init_test", uvm_component parent=null);
super.new(name, parent);
endfunction
virtual function void configure_cfg();
cfg.n_txns = 200;
endfunction
virtual task run_vseq();
init_vseq vseq;
vseq = init_vseq::type_id::create("vseq");
vseq.start(e.vsqr);
endtask
endclass
点击展开:`rand_test`(第 1144-1157 行)
systemverilog
class rand_test extends base_test;
`uvm_component_utils(rand_test)
function new(string name="rand_test", uvm_component parent=null);
super.new(name, parent);
endfunction
virtual function void configure_cfg();
cfg.n_txns = 200;
endfunction
virtual task run_vseq();
rand_vseq vseq;
vseq = rand_vseq::type_id::create("vseq");
vseq.start(e.vsqr);
endtask
endclass
点击展开:`hit_test`(第 1162-1175 行)
systemverilog
class hit_test extends base_test;
`uvm_component_utils(hit_test)
function new(string name="hit_test", uvm_component parent=null);
super.new(name, parent);
endfunction
virtual function void configure_cfg();
cfg.n_txns = 88;
endfunction
virtual task run_vseq();
hit_vseq vseq;
vseq = hit_vseq::type_id::create("vseq");
vseq.start(e.vsqr);
endtask
endclass
点击展开:`bypass_test`(第 1180-1193 行)
systemverilog
class bypass_test extends base_test;
`uvm_component_utils(bypass_test)
function new(string name="bypass_test", uvm_component parent=null);
super.new(name, parent);
endfunction
virtual function void configure_cfg();
cfg.n_txns = 20;
endfunction
virtual task run_vseq();
bypass_vseq vseq;
vseq = bypass_vseq::type_id::create("vseq");
vseq.start(e.vsqr);
endtask
endclass
点击展开:`write_test`(第 1198-1211 行)
systemverilog
class write_test extends base_test;
`uvm_component_utils(write_test)
function new(string name="write_test", uvm_component parent=null);
super.new(name, parent);
endfunction
virtual function void configure_cfg();
cfg.n_txns = 20;
endfunction
virtual task run_vseq();
write_vseq vseq;
vseq = write_vseq::type_id::create("vseq");
vseq.start(e.vsqr);
endtask
endclass
点击展开:`replace_test`(第 1216-1229 行)
systemverilog
class replace_test extends base_test;
`uvm_component_utils(replace_test)
function new(string name="replace_test", uvm_component parent=null);
super.new(name, parent);
endfunction
virtual function void configure_cfg();
cfg.n_txns = 5;
endfunction
virtual task run_vseq();
replace_vseq vseq;
vseq = replace_vseq::type_id::create("vseq");
vseq.start(e.vsqr);
endtask
endclass
点击展开:`sweep_test`(第 1234-1247 行)
systemverilog
class sweep_test extends base_test;
`uvm_component_utils(sweep_test)
function new(string name="sweep_test", uvm_component parent=null);
super.new(name, parent);
endfunction
virtual function void configure_cfg();
cfg.n_txns = 256;
endfunction
virtual task run_vseq();
sweep_vseq vseq;
vseq = sweep_vseq::type_id::create("vseq");
vseq.start(e.vsqr);
endtask
endclass
点击展开:`err_test`(第 1252-1265 行)
systemverilog
class err_test extends base_test;
`uvm_component_utils(err_test)
function new(string name="err_test", uvm_component parent=null);
super.new(name, parent);
endfunction
virtual function void configure_cfg();
cfg.n_txns = 200;
endfunction
virtual task run_vseq();
err_vseq vseq;
vseq = err_vseq::type_id::create("vseq");
vseq.start(e.vsqr);
endtask
endclass
点击展开:`fill_hit_test`(第 1270-1283 行)
systemverilog
class fill_hit_test extends base_test;
`uvm_component_utils(fill_hit_test)
function new(string name="fill_hit_test", uvm_component parent=null);
super.new(name, parent);
endfunction
virtual function void configure_cfg();
cfg.n_txns = 30;
endfunction
virtual task run_vseq();
fill_hit_vseq vseq;
vseq = fill_hit_vseq::type_id::create("vseq");
vseq.start(e.vsqr);
endtask
endclass
点击展开:`locality_test`(第 1288-1301 行)
systemverilog
class locality_test extends base_test;
`uvm_component_utils(locality_test)
function new(string name="locality_test", uvm_component parent=null);
super.new(name, parent);
endfunction
virtual function void configure_cfg();
cfg.n_txns = 352;
endfunction
virtual task run_vseq();
locality_vseq vseq;
vseq = locality_vseq::type_id::create("vseq");
vseq.start(e.vsqr);
endtask
endclass
5.3 dut_monitor.sv ------ 白盒监控(40 行)
职责 :在 tag_mem_chk 上升沿(首次查找,过滤 MISS_WAIT 期间持续查找)后一拍采样 hit_chk_point 有效窗口,构造 tag_check_item 发往 scoreboard 与 ref_model 的白盒 tag 结果比对。
解析 :#1 延迟避开时钟沿竞争;prev_tag_mem_chk 边沿检测保证每个请求只发一条白盒记录;s_axi_cache_en 过滤关闭期噪声。
点击展开:dut_monitor.sv 完整代码
systemverilog
//----------------------------------------------------------------------------//
// DUT internal monitor: captures tag lookup results at hit_chk_point
//----------------------------------------------------------------------------//
class dut_monitor extends uvm_monitor;
`uvm_component_utils(dut_monitor)
uvm_analysis_port #(tag_check_item) ap;
virtual axi_icache_if vif;
function new(string name, uvm_component parent); super.new(name,parent); ap=new("ap",this); endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(virtual axi_icache_if)::get(this,"","vif",vif))
`uvm_fatal("DUT_MON","no vif")
endfunction
task run_phase(uvm_phase phase);
tag_check_item item;
bit prev_tag_mem_chk=0;
forever begin
@(posedge vif.clk);
#1;
// Only capture initial lookup: tag_mem_chk rising edge (0->1)
// This filters out continuous lookups during MISS_WAIT
if(vif.tag_mem_chk && !prev_tag_mem_chk && vif.rst_n && vif.s_axi_cache_en) begin
@(posedge vif.clk);
#1;
if(vif.hit_chk_point) begin
item = tag_check_item::type_id::create("dut_tag_item");
item.chk_addr = vif.chk_haddr;
item.idx = vif.chk_haddr[9:4];
item.tag = vif.chk_haddr[31:10];
item.tag_hit = vif.tag_hit;
item.fill_hit = vif.fill_hit;
item.cache_miss = vif.cache_miss;
item.hit_way = 2'b00;
ap.write(item);
end
end
prev_tag_mem_chk = vif.tag_mem_chk;
end
endtask
endclass
5.4 tb_top.sv ------ 仿真顶层(87 行)
职责 :100MHz 时钟 + 复位 + axi_icache_if 例化 + DUT 例化(逐端口连 vif)+ config_db 注入 + run_test + 超时。
解析(白盒连线是验证关键) :always @(*) 把 DUT 内部 cache_init / s_axi_cache_en / fill_busy / cache_line_wr / tag_mem_chk / chk_haddr / hit_chk_point / tag_hit / fill_hit / cache_miss / disable_clr_tag / cache_state 接到 interface 白盒端口,供 ref_model(cache_init 采样)、dut_monitor(tag 采集)、csdn_demo(disable 观测)使用------这是"白盒三路交叉验证"能成立的前提。
点击展开:tb_top.sv 完整代码
systemverilog
//----------------------------------------------------------------------------//
// File: tb_top.sv
// Top-level testbench for AXI4-Full I-Cache
//----------------------------------------------------------------------------//
`timescale 1ns/1ps
module tb_top;
import uvm_pkg::*;
import axi_icache_pkg::*;
reg clk, rst_n;
// clock: 10ns period (100MHz)
initial begin clk=0; forever #5 clk=~clk; end
// reset
initial begin
rst_n=0;
#100;
rst_n=1;
end
axi_icache_if vif(.clk(clk), .rst_n(rst_n));
// DUT instance
axi_icache_top dut(
// S AR
.s_arid(vif.s_arid), .s_araddr(vif.s_araddr), .s_arlen(vif.s_arlen),
.s_arsize(vif.s_arsize), .s_arburst(vif.s_arburst), .s_arcache(vif.s_arcache),
.s_arprot(vif.s_arprot), .s_arqos(vif.s_arqos), .s_arvalid(vif.s_arvalid),
.s_arready(vif.s_arready),
// S R
.s_rid(vif.s_rid), .s_rdata(vif.s_rdata), .s_rresp(vif.s_rresp),
.s_rlast(vif.s_rlast), .s_rvalid(vif.s_rvalid), .s_rready(vif.s_rready),
// S AW
.s_awid(vif.s_awid), .s_awaddr(vif.s_awaddr), .s_awlen(vif.s_awlen),
.s_awsize(vif.s_awsize), .s_awburst(vif.s_awburst), .s_awcache(vif.s_awcache),
.s_awprot(vif.s_awprot), .s_awqos(vif.s_awqos), .s_awvalid(vif.s_awvalid),
.s_awready(vif.s_awready),
// S W
.s_wid(vif.s_wid), .s_wdata(vif.s_wdata), .s_wstrb(vif.s_wstrb),
.s_wlast(vif.s_wlast), .s_wvalid(vif.s_wvalid), .s_wready(vif.s_wready),
// S B
.s_bid(vif.s_bid), .s_bresp(vif.s_bresp), .s_bvalid(vif.s_bvalid),
.s_bready(vif.s_bready),
// M AR
.m_arid(vif.m_arid), .m_araddr(vif.m_araddr), .m_arlen(vif.m_arlen),
.m_arsize(vif.m_arsize), .m_arburst(vif.m_arburst), .m_arcache(vif.m_arcache),
.m_arprot(vif.m_arprot), .m_arqos(vif.m_arqos), .m_arvalid(vif.m_arvalid),
.m_arready(vif.m_arready),
// M R
.m_rid(vif.m_rid), .m_rdata(vif.m_rdata), .m_rresp(vif.m_rresp),
.m_rlast(vif.m_rlast), .m_rvalid(vif.m_rvalid), .m_rready(vif.m_rready),
// M AW
.m_awid(vif.m_awid), .m_awaddr(vif.m_awaddr), .m_awlen(vif.m_awlen),
.m_awsize(vif.m_awsize), .m_awburst(vif.m_awburst), .m_awcache(vif.m_awcache),
.m_awprot(vif.m_awprot), .m_awqos(vif.m_awqos), .m_awvalid(vif.m_awvalid),
.m_awready(vif.m_awready),
// M W
.m_wid(vif.m_wid), .m_wdata(vif.m_wdata), .m_wstrb(vif.m_wstrb),
.m_wlast(vif.m_wlast), .m_wvalid(vif.m_wvalid), .m_wready(vif.m_wready),
// M B
.m_bid(vif.m_bid), .m_bresp(vif.m_bresp), .m_bvalid(vif.m_bvalid),
.m_bready(vif.m_bready),
// control
.icache_en(vif.icache_en), .clk(clk), .rst_n(rst_n)
);
initial begin
uvm_config_db#(virtual axi_icache_if)::set(null,"*","vif",vif);
run_test();
end
// connect internal cache_init to interface for ref_model sampling
always @(*) vif.cache_init = dut.cache_init;
// connect DUT internal signals to misc interface for white-box monitoring
always @(*) begin
vif.s_axi_cache_en = dut.u_slave.s_axi_cache_en;
vif.fill_busy = dut.u_slave.fill_busy;
vif.cache_line_wr = dut.cache_line_wr_int;
vif.tag_mem_chk = dut.tag_mem_chk;
vif.chk_haddr = dut.chk_haddr;
vif.hit_chk_point = dut.hit_chk_point;
vif.tag_hit = dut.tag_hit;
vif.fill_hit = dut.fill_hit;
vif.cache_miss = dut.cache_miss;
vif.disable_clr_tag = dut.disable_clr_tag;
vif.cache_state = dut.u_slave.cache_state;
end
// timeout
initial begin
#200000;
`uvm_fatal("TB","TIMEOUT")
end
endmodule
5.5 csdn_demo.sv ------ CSDN 演示测试(62 行)
职责 :开源文档配套演示(不参与正式回归):Phase1 miss→fill→hit、Phase2 并发双读(FIFO 排队)、Phase3 关闭 cache 观测 disable_clr_tag 脉冲。
解析 :Phase3 正是暴露 v2.2 fill_busy BUG 的用例------wait(vif.disable_clr_tag===1) 在修复前永不等不到,最终 tb_top 200us 超时 FATAL。修复后打印 disable_clr_tag observed at 1245000 ns。
点击展开:csdn_demo.sv 完整代码
systemverilog
//----------------------------------------------------------------------------//
// File: csdn_demo.sv
// CSDN 开源文档专用演示测试(不参与 10 个正式回归用例)
// Phase 1: cache enable 后 miss -> fill -> hit 完整流程
// Phase 2: 并发双读请求(2-deep AR FIFO 排队,展示多 outstanding 机制)
// Phase 3: icache_en 拉低 -> disable -> disable_clr_tag 脉冲清 tag
//----------------------------------------------------------------------------//
`ifndef CSDN_DEMO_SV
`define CSDN_DEMO_SV
import uvm_pkg::*;
import axi_icache_pkg::*;
class csdn_demo_vseq extends axi_icache_virtual_sequence;
`uvm_object_utils(csdn_demo_vseq)
function new(string name = "csdn_demo_vseq");
super.new(name);
endfunction
task body();
int i;
virtual axi_icache_if vif;
super.body();
if(!uvm_config_db#(virtual axi_icache_if)::get(null, "", "vif", vif))
`uvm_fatal("CSDN_DEMO", "no vif")
//----------------------------------------------------------------//
// Phase 1: cache enable(driver 已按 cfg.icache_en=1 驱动)
// 4 个不同 line 访问 -> miss + WRAP4 fill
// 再重复访问 -> tag hit(命中路径)
//----------------------------------------------------------------//
for(i = 0; i < 4; i++)
send_txn(.addr(32'h00001000 + i * 32));
repeat(4)
send_txn(.addr(32'h00001000));
//----------------------------------------------------------------//
// Phase 2: 两个并发读请求,验证 2-deep AR FIFO 排队机制
//----------------------------------------------------------------//
fork
begin send_txn(.addr(32'h00002000)); end
begin send_txn(.addr(32'h00002020)); end
join
//----------------------------------------------------------------//
// Phase 3: 关闭 cache,等待 disable_clr_tag 脉冲
// RTL: icache_en=0 -> S_WAIT_DISABLE -> FIFO 空+FSM idle+无 fill
// -> disable_clr_tag 1拍脉冲清空 tag SRAM -> S_DISABLE
//----------------------------------------------------------------//
vif.s_mst_cb.icache_en <= 1'b0;
wait(vif.disable_clr_tag === 1'b1);
$display("[CSDN_DEMO] disable_clr_tag observed at %0t ns", $time);
repeat(5) @(posedge vif.clk);
endtask
endclass
class csdn_demo_test extends base_test;
`uvm_component_utils(csdn_demo_test)
function new(string name = "csdn_demo_test", uvm_component parent = null);
super.new(name, parent);
endfunction
virtual function void configure_cfg();
cfg.icache_en = 1;
endfunction
virtual task run_vseq();
csdn_demo_vseq vseq;
vseq = csdn_demo_vseq::type_id::create("vseq");
vseq.start(e.vsqr);
endtask
endclass
`endif
5.6 拆分文件对照表(19 个文件,已验证与 pkg 完全一致)
工程 uvm/sv/ 下另有 19 个单类拆分文件(历史拆分版),编译不直接使用;本表给出每个文件 → pkg 内对应类 → 代码位置(见 5.2 分组),逐字比对(去注释/空白)完全一致。
| 文件 | pkg 内类 | 所在分组 |
|---|---|---|
| axi_icache_cfg.sv | axi_icache_cfg | 5.2 基础对象 |
| axi_icache_txn.sv | axi_icache_txn | 5.2 基础对象 |
| tag_check_item.sv | tag_check_item | 5.2 基础对象 |
| axi_icache_sequencer.sv | s_sequencer | 5.2 S 侧 |
| axi_slave_driver.sv | s_driver | 5.2 S 侧 |
| axi_slave_monitor.sv | s_monitor | 5.2 S 侧 |
| axi_slave_agent.sv | axi_slave_agent | 5.2 S 侧 |
| axi_master_driver.sv | m_driver | 5.2 M 侧 |
| axi_master_monitor.sv | axi_master_monitor | 5.2 M 侧 |
| axi_master_agent.sv | axi_master_agent | 5.2 M 侧 |
| axi_icache_virtual_sequencer.sv | axi_icache_virtual_sequencer | 5.2 虚拟序列 |
| axi_icache_virtual_sequence.sv | axi_icache_virtual_sequence | 5.2 虚拟序列 |
| axi_icache_virtual_sequences.sv | init_vseq~locality_vseq(10 个) | 5.2 虚拟序列 |
| ref_model.sv | ref_model | 5.2 参考模型 |
| scoreboard.sv | scoreboard | 5.2 参考模型 |
| dut_monitor.sv | dut_monitor | 5.3 |
| axi_icache_coverage.sv | axi_icache_coverage | 5.2 覆盖率 |
| env.sv | env | 5.2 覆盖率/环境 |
| tests.sv | base_test + 10 test | 5.2 覆盖率/环境 |
注意:pkg 中 dut_monitor 与独立 dut_monitor.sv 内容一致,为便于阅读 5.3 单独展示独立文件版本(逐字一致)。
6. UVM 验证架构
6.1 验证环境框图
┌─────────────────────────────┐
│ tb_top │
│ clk / rst_n / axi_icache_if │
└───────────┬─────────────────┘
│ vif
┌───────────────────────────┼───────────────────────────┐
│ ▼ │
│ ┌────────────────── env (uvm_env) ──────────────┐ │
│ │ ┌───────────┐ ┌──────────┐ ┌───────┐ │ │
│ │ │ slave_agt │ │ master_agt│ │ vsqr │ │ │
│ │ │ drv+mon+sqr│ │ (memory │ │(virtual││ │
│ │ └─────┬─────┘ │ model) │ │ sqr) │ │ │
│ │ │ txn └──────────┘ └───┬───┘ │ │
│ │ ▼ │ │ │
│ │ ┌───────────┐ ┌─────────────┐ ┌───────┴──┐ │ │
│ │ │ refm │ │ dutmon │ │ cov │ │ │
│ │ │ ref_model │ │ (白盒tag采集)│ │ 覆盖率收集 │ │ │
│ │ └─────┬─────┘ └──────┬──────┘ └──────────┘ │ │
│ │ │ pred/actual │ tag_check_item │ │
│ │ ▼ ▼ │ │
│ │ ┌──────────────────────────────────┐ │ │
│ │ │ scoreboard │ │ │
│ │ │ 数据比对 + tag 白盒比对 + SVA │ │ │
│ │ └──────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
6.2 验证策略:三路交叉检查
| 检查通道 | 输入 | 比对内容 |
|---|---|---|
| 黑盒数据比对 | slave_monitor 抓的请求 → ref_model 预测 → scoreboard actual | 返回数据、resp 与预测完全一致 |
| 白盒 tag 比对 | dut_monitor 抓 DUT 内部 tag lookup 结果 vs ref_model 的 tag 预测 | tag_hit/fill_hit/cache_miss 一致 |
| SVA 断言 | axi_icache_if 内 13 条断言 | AXI 协议合规(VALID/READY 保持)、WRAP4 参数、无 X 传播、AR→R 响应时限 |
ref_model 设计要点(与 RTL 完全对齐的 4 路 cache 模型):
tag_arr[4][64]/vld_arr[4][64]/rlu_arr[64]镜像 RTL tag 结构- cache_init 采样对齐 :在 AR 握手周期采样
txn.cache_init_sample,与 DUT 的 cacheable 判定一致(v2.1 修复) - fill 提交对齐 :不在 lookup 时立即提交 tag,而是等
cache_line_wr脉冲(DUT 真实 fill 完成)才提交------与 RTL 行为逐拍一致 - fill_hit 前向递推:fill_in_progress + fill_idx/fill_tag/fill_way 跟踪
6.3 Sequence 分层
base_test (uvm_test)
├─ configure_cfg() ← 每个 test 定制 n_txns / 参数
└─ run_vseq() ← 启动具体 virtual sequence
├─ init_vseq :200 随机访问(功能冒烟)
├─ rand_vseq :200 随机(随机地址/读写混合)
├─ hit_vseq :8 地址循环 88 次(高命中率 83%)
├─ bypass_vseq :20 非缓存读(bypass 路径)
├─ write_vseq :20 写(AW/W/B 透传)
├─ replace_vseq :5 组冲突地址(强制替换/pLRU)
├─ sweep_vseq :256 地址(全 set 扫描)
├─ err_vseq :20 不可缓存+特权(err 路径)
├─ fill_hit_vseq :fill 期间同 line 访问(fill_hit 前向递推)
├─ locality_vseq :88 次空间+时间局部性(命中率 88.9%)
└─ csdn_demo_vseq :演示(miss→fill→hit + 并发 + disable)
6.4 覆盖率收集
systemverilog
// 功能覆盖点(节选)
cp_cmd: coverpoint txn.cmd { bins read={READ}; bins write={WRITE}; }
cp_cache: coverpoint txn.cache {
bins cacheable = {4'b1011, 4'b1111, 4'b0011};
bins noncache = {4'b0000, 4'b0001};
}
cp_prot: coverpoint txn.prot { bins user/privileged/secure/instr; }
cp_size: coverpoint txn.size { bins byte/half/word/dword; }
cp_addr_align: coverpoint txn.addr[3:0] { bins aligned_4/unaligned; }
cp_addr_page: coverpoint txn.addr[31:16] { bins page0..page3/other; }
7. 验证计划与测试点分解
7.1 验证计划
完整验证计划见工程 doc/verification_plan.md,覆盖:验证目标、环境架构、测试策略、覆盖率目标、回归策略、风险与缓解。
7.2 测试点分解(80 项)
完整 80 项测试点分解见工程 doc/AXI_ICACHE_测试点分解.xlsx(按功能模块 × 场景 × 边界 × 协议四级分解)。分类概览:
| 分类 | 覆盖内容 | 测试点数 |
|---|---|---|
| 复位与初始化 | 复位时序、cache_init 清 tag、S_DISABLE 初始态 | 8 |
| Cache FSM | enable/disable 转换、all_pending_done、disable_clr_tag 脉冲 | 10 |
| 命中路径 | 1 拍命中、hit_way 选路、word 选择、连续命中、背靠背 | 10 |
| Miss 路径 | WRAP4 fill、16B 对齐、beat 计数、fill_done、miss_rdata 返回 | 12 |
| fill_hit 前向递推 | fill 期间同 line/同 word 访问、不同 word 访问 | 6 |
| 替换算法 | invalid 优先、pLRU 4 种 victim、全 valid 替换、同一 set 冲突 | 10 |
| Bypass | 非缓存读、单拍透传、写透传(AW/W/B)、resp 透传 | 10 |
| 多 outstanding | 2 深 FIFO、push/pop 同拍、FIFO 满反压、in-order 响应 | 8 |
| 协议合规 | 5 通道握手、arprot/arcache 组合、size 边界 | 6 |
8. 仿真波形图
全部波形使用 QuestaSim 10.7c 记录(
vsim -wlf),并由脚本转换为图片;测试用例为专门的演示用例csdn_demo_test。
8.1 Cache 命中时序(miss → WRAP4 fill → re-lookup → hit)

读图要点:
- 首个请求
s_araddr=0x1000:tag_mem_chk拉高 →hit_chk_point一拍后判定cache_miss→ 进入 fill - fill 完成后 re-lookup:
tag_hit拉高 →s_rvalid在 1 拍后返回命中数据 - 第二段(右侧)展示并发读
0x2000/0x2020(2 深 FIFO 排队)
8.2 Miss 回填流程(WRAP4 突发读)

读图要点:
miss_req拉高 →m_arvalid/m_arready握手(m_araddr=0x1000,16B 对齐)m_rvalid连续 4 拍返回数据:0x12344678 → 0x1234467c → 0x12344670 → 0x12344674(WRAP 环绕回到 line 起点)- 第 4 拍
m_rlast→dmem0_wr4 拍写 dmem →dmem_cache_line_wr脉冲 →fill_done拉高
8.3 多 Outstanding(2 深 AR FIFO 连续请求)
!外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=csdn_assets%2Fwave_multi_outstanding.png\&pos_id=img-MiJJUPqp-1790687494430
读图要点 :连续 AR 握手期间 fifo_cnt 在 1↔0 间切换(push/pop 交替),tag_mem_chk/hit_chk_point 流水处理;当 fifo_cnt=2 时 s_arready=0 反压。

8.4 Cache FSM 关闭流程(disable 清 tag)

读图要点(v2.2 核心特性):
icache_en拉低 →cache_state从 1(S_ENABLE) → 2(S_WAIT_DISABLE)- 等待
fifo_cnt=0+fill_busy=0(all_pending_done)→disable_clr_tag产生单拍脉冲 cache_state回到 0(S_DISABLE),tag SRAM 被清空,下次 enable 是干净状态
9. 仿真结果
9.1 回归结果(QuestaSim 10.7c,全部通过)
| 测试 | 事务数 | 数据比对 | tag 白盒 | 结果 |
|---|---|---|---|---|
| init_test | 200 | 200/200 match | 193 match | PASS |
| rand_test | 200 | 200/200 match | 193 match | PASS |
| hit_test | 88 | 88/88 match | 73 match | PASS |
| bypass_test | 200 | 200/200 match | 0 (bypass) | PASS |
| write_test | 200 | 200/200 match | 0 (透传) | PASS |
| replace_test | 5 | 5/5 match | 0 | PASS |
| sweep_test | 256 | 256/256 match | 249 match | PASS |
| err_test | 200 | 200/200 match | 0 (bypass) | PASS |
| fill_hit_test | 31 | 31/31 match | 23 match | PASS |
| locality_test | 88 | 88/88 match | 43 match | PASS |
tag mismatch 均为 fill 完成后 re-lookup 的预期时序差异(scoreboard 已按设计降级为 WARNING,非功能错误)。
9.2 命中率
| 测试 | 访问次数 | 命中 | 命中率 |
|---|---|---|---|
| hit_test | 88 | 73 | 83.0% |
| locality_test | 88 | 78 | 88.9% |
9.3 代码覆盖率(覆盖率库合并报告)
| 指标 | 数值 |
|---|---|
| Statement | 97% |
| Branch | 93% |
| Toggle/FSM/Expression | 见 sim/covhtmlreport |
9.4 SVA 断言
13 条断言全部通过,0 违例(协议合规 + WRAP4 参数 + 响应时限 + 无 X)。
10. 验证中发现并修复的 BUG(开源价值点)
在编写 CSDN 演示用例(csdn_demo_test,首次覆盖 disable 关闭流程)时,发现 v2.2 存在一个真实功能缺陷:
10.1 现象
icache_en=0后,cache_state进入 S_WAIT_DISABLE,但disable_clr_tag永远不产生,仿真 200us 超时 FATAL- 定位:
all_pending_done需要!fill_busy,而fill_busy从第一次 miss 后永久锁 1
10.2 根因
verilog
// 修复前(BUG):
always @(posedge clk or negedge rst_n)
if(!rst_n) fill_busy <= 1'b0;
else if(miss_req) fill_busy <= 1'b1; // ← 优先级高
else if(fill_done) fill_busy <= 1'b0; // ← 永远轮不到
miss_req 在 fill_done 周期仍是 1 (拉低是下一拍生效),fill_done 是单拍脉冲 。因此该周期 if(miss_req) 分支命中,fill_busy 保持 1;下一拍 miss_req=0、fill_done 已消失 → clear 条件永远错过,fill_busy 永久锁 1。disable 流程(需要 !fill_busy)被永久卡死。
该缺陷在原有 10 个正式测试中无法暴露(它们从不关闭 cache),只有覆盖 disable 路径的新用例才能触发------这正是「测试点分解必须覆盖所有状态转换」的典型案例。
10.3 修复
verilog
// 修复后:fill_done 优先于 miss_req
always @(posedge clk or negedge rstn)
if(!rst_n) fill_busy <= 1'b0;
else if(fill_done) fill_busy <= 1'b0; // ← 优先清
else if(miss_req) fill_busy <= 1'b1;
10.4 验证
- 修复后
csdn_demo_test:disable_clr_tag observed at 1245000 ns,UVM_ERROR=0 - 全部 10 个正式回归重新通过(mismatch=0),无功能破坏
- 该修复已同步到正式 RTL(
axi_slave_ctrl.v第 125-136 行)
11. 编译与运行指南(QuestaSim 10.7c)
10.1 编译
tcl
vlog -work work_uvm -mfcu -sv12compat -L mtiUvm12 \
+incdir+E:/fpga/questasim/verilog_src/uvm-1.2/src \
-f rtl.f ../uvm/sv/axi_icache_if.sv \
../uvm/sv/axi_icache_pkg.sv ../uvm/sv/tb_top.sv
10.2 运行单个测试
tcl
vsim -c -sv_seed 1 -do "run -all; quit -f" \
-L mtiUvm12 -voptargs="+acc" \
work_uvm.tb_top +UVM_TESTNAME=hit_test
10.3 记录波形(CSDN 演示用例)
tcl
# wave_record.do:
log -r /*
run -all
quit -f
vsim -c -wlf wave_demo.wlf -sv_seed 1 -do wave_record.do \
-L mtiUvm12 -voptargs="+acc" \
work_demo.tb_top +UVM_TESTNAME=csdn_demo_test
10.4 全量回归
powershell
# regression.ps1 ------ 10 个测试串行回归,自动判定 PASS/FAIL
12. 总结与展望
11.1 成果
- 完整 RTL:AXI4-Full 4KB 4 路组相联 I-Cache,1880 行可综合 Verilog,命中 1 拍、WRAP4 回填、2 深多 outstanding、disable 清 tag
- 完整 UVM 验证:23 文件 3048 行,三路交叉检查(黑盒数据 + 白盒 tag + SVA),10 测试全 PASS,mismatch=0
- 量化结果:命中率 83%+(局部性场景 88.9%),代码覆盖率 Statement 97% / Branch 93%
- 真实 BUG 闭环:验证驱动设计改进(fill_busy 锁死 → 修复 → 回归),体现「验证计划覆盖所有状态转换」的价值
11.2 技术亮点
| 亮点 | 说明 |
|---|---|
| 三路交叉验证 | 黑盒(数据)+ 白盒(内部 tag)+ SVA 三重保险 |
| ref_model 逐拍对齐 | cache_init 采样、fill 提交时序与 RTL 完全一致,是白盒比对可信的前提 |
| 1 拍命中 | 组合逻辑直接驱动 RVALID,无额外流水拍 |
| fill_hit 前向递推 | fill 期间访问同 line 可直接从回填缓冲取数,不必等 fill 完成 |
| 写优先的 SRAM 冲突规避 | cache_line_wr 周期跳过 lookup,避免读到陈旧 tag |
11.3 可继续优化的方向
- FIFO 加深 + out-of-order 响应:2 深 AR FIFO 升级到 4-8 深 + RID 重排序,进一步挖掘多 outstanding 带宽
- 多 ARID 支持:当前 m 口单 ARID(4'h0),可扩展为按请求 ID 分流的 fill/bypass 并行通道
- 预取(prefetch):顺序访问场景下提前发起下一 line 的 WRAP4 读,可显著提升局部性场景命中率
- L2 接口:对接真实内存控制器(DDR/AXI interconnect),补充延迟模型与总线竞争场景
- 覆盖率闭环:补齐功能覆盖率到 100% 目标、增加断言覆盖率(cover property)