lle
lle
//============================================================================
// Module : lle (Link-List Engine)
// Project : 4-Port 2.5/1G/100M Ethernet Switch - Smart MMU
// Description : 链表引擎 (存储访问平面) —— 核心模块。唯一访问 Next-Ptr Register
// File (指针寄存器, LLE 内部私有, CELL_NUM×ENTRY_W {next,phead,ptail})。
// 对三个 Ctrl 提供分配/出队/还链服务并裁决写口; 持有:
// free_head/free_tail、free_nxt 预取、q_head/q_tail[QUEUE_NUM]、
// q_head_entry[QUEUE_NUM] 队头描述符预取、q_cell_cnt[QUEUE_NUM]。
// 支撑入队/出队各 1 拍 (free_head/q_head 组合可读 + free_nxt/
// q_head_entry 预取 + 挂链/推进流水写 + 空队列/还链 bypass);
// 处理同一队列同拍"进包+出包" hazard (非空/单 cell/空队列三情形)。
// 每次分配/还链向 Occupancy 上报 alloc/free 事件。
// 初始化期由 Init FSM 驱动建空闲链 (0->1->...->CELL_NUM-1)。
//
// 说明: 分配地址决策在 LLE 内部 (alloc_addr = free_head), 上层 Enqueue Ctrl 用
// lle_alloc_fire 触发; lle_alloc_addr 端口保留供上层核对, 内部以 free_head
// 为准。出队/还链同理。
//
// Clock/Reset : clk_core (300MHz, 单时钟域) / rst_core_n (异步复位低有效)
//============================================================================
`timescale 1ns/1ps
module lle #(
parameter int ADDR_W = 13, // cell 地址位宽
parameter int CELL_NUM = 8192, // cell 总数
parameter int QUEUE_NUM = 34, // 队列(chain)数
parameter int QID_W = 6, // queue_id 位宽
parameter int PORT_W = 2, // egress_port 位宽
parameter int REF_W = 3, // 组播 ref_count 位宽
parameter int CNT_W = 14 // 队列/空闲计数位宽 (0~CELL_NUM)
)(
//========================================================================
// 时钟与复位
//========================================================================
input logic clk_core, // 300MHz 核心时钟
input logic rst_core_n, // 异步复位, 低有效
//========================================================================
// 初始化建链接口 (与 Init FSM)
//========================================================================
input logic init_build_req, // 触发建空闲链
output logic init_build_done, // 建链完成
//========================================================================
// 入队 / 分配接口 (与 Enqueue Ctrl, 1 拍命令)
//========================================================================
output logic [ADDR_W-1:0] lle_free_head, // 可分配地址(持续有效, 组合可读)
output logic lle_free_empty, // 空闲链空
input logic lle_alloc_fire, // 分配+挂链命令(一拍脉冲)
input logic [QID_W-1:0] lle_alloc_queue_id, // 挂链目标队列
input logic [ADDR_W-1:0] lle_alloc_addr, // 分配地址(=lle_free_head, 仅核对)
input logic lle_set_pkt_head, // 写 pkt_head
input logic lle_set_pkt_tail, // 写 pkt_tail
input logic lle_alloc_is_mcast, // 组播标志(转发写 ref_count)
input logic [REF_W-1:0] lle_alloc_ref_init, // 组播 ref_count 初值
//========================================================================
// 出队接口 (与 Dequeue Ctrl, 1 拍命令)
//========================================================================
input logic [QID_W-1:0] lle_deq_queue_id, // 出队队列号
output logic [ADDR_W-1:0] lle_qhead, // 队头地址(组合可读)
output logic lle_qhead_pkt_head, // 队头 cell 头标志
output logic lle_qhead_pkt_tail, // 队头 cell 尾标志
output logic lle_q_empty, // 该队列空
input logic lle_deq_fire, // 出队命令(一拍)
//========================================================================
// 还链接口 (与 Recycle Ctrl)
//========================================================================
input logic lle_free_req, // 还链请求
input logic [ADDR_W-1:0] lle_free_addr, // 待还 cell
output logic lle_free_grant, // 仲裁通过
output logic lle_free_done, // 还链完成
//========================================================================
// 组播 ref_count 转发接口 (与 Multicast Ref-Count Mgr)
//========================================================================
output logic mc_set_req, // 置 ref_count 初值请求
output logic [ADDR_W-1:0] mc_set_addr, // 目标组播 cell
output logic [REF_W-1:0] mc_set_init, // ref_count 初值
input logic mc_set_ack, // 写完应答(本实现不阻塞)
//========================================================================
// 事件上报接口 (与 Occupancy & Pool Mgr)
//========================================================================
output logic lle_alloc_evt, // 分配事件(计数++)
output logic lle_free_evt, // 回收事件(计数--)
output logic [QID_W-1:0] evt_queue_id, // 事件所属队列
output logic [PORT_W-1:0] evt_egress_port // 事件所属出端口
);
//========================================================================
// 局部参数
//========================================================================
localparam int ENTRY_W = ADDR_W + 2; // {next, pkt_head, pkt_tail}
localparam logic [ADDR_W-1:0] NULL_PTR = '0; // 空指针(链尾, 用 0 表示)
localparam int PH_BIT = 1; // pkt_head 位
localparam int PT_BIT = 0; // pkt_tail 位
//========================================================================
// 内部状态寄存器
//========================================================================
logic [ADDR_W-1:0] free_head_q; // 空闲链头
logic [ADDR_W-1:0] free_nxt_q; // free_head 下一项(预取)
logic [ADDR_W-1:0] free_tail_q; // 空闲链尾
logic [CNT_W-1:0] free_cnt_q; // 空闲 cell 数
logic [ADDR_W-1:0] q_head_q [QUEUE_NUM]; // 每队列队头
logic [ADDR_W-1:0] q_tail_q [QUEUE_NUM]; // 每队列队尾
logic [CNT_W-1:0] q_cell_cnt_q [QUEUE_NUM]; // 每队列 cell 数
logic [ENTRY_W-1:0] q_head_entry_q[QUEUE_NUM]; // 每队列队头描述符预取
//========================================================================
// Next-Ptr Register File 互连
//========================================================================
logic [ADDR_W-1:0] npr_ra_addr;
logic [ADDR_W-1:0] npr_ra_next_ptr;
logic npr_ra_pkt_head;
logic npr_ra_pkt_tail;
logic [ADDR_W-1:0] npr_rb_addr;
logic [ADDR_W-1:0] npr_rb_next_ptr;
logic npr_rb_pkt_head;
logic npr_rb_pkt_tail;
logic npr_w0_we;
logic [ADDR_W-1:0] npr_w0_addr;
logic [ADDR_W-1:0] npr_w0_next_ptr;
logic npr_w0_pkt_head;
logic npr_w0_pkt_tail;
logic npr_w1_we;
logic [ADDR_W-1:0] npr_w1_addr;
logic [ADDR_W-1:0] npr_w1_next_ptr;
logic npr_w1_pkt_head;
logic npr_w1_pkt_tail;
logic npr_build_we;
logic [ADDR_W-1:0] npr_build_addr;
logic [ADDR_W-1:0] npr_build_next_ptr;
logic npr_build_pkt_head;
logic npr_build_pkt_tail;
next_ptr_regfile #(
.ADDR_W (ADDR_W),
.CELL_NUM (CELL_NUM),
.ENTRY_W (ENTRY_W)
) u_next_ptr_regfile (
.clk_core (clk_core),
.rst_core_n (rst_core_n),
.ra_addr (npr_ra_addr),
.ra_next_ptr (npr_ra_next_ptr),
.ra_pkt_head (npr_ra_pkt_head),
.ra_pkt_tail (npr_ra_pkt_tail),
.rb_addr (npr_rb_addr),
.rb_next_ptr (npr_rb_next_ptr),
.rb_pkt_head (npr_rb_pkt_head),
.rb_pkt_tail (npr_rb_pkt_tail),
.w0_we (npr_w0_we),
.w0_addr (npr_w0_addr),
.w0_next_ptr (npr_w0_next_ptr),
.w0_pkt_head (npr_w0_pkt_head),
.w0_pkt_tail (npr_w0_pkt_tail),
.w1_we (npr_w1_we),
.w1_addr (npr_w1_addr),
.w1_next_ptr (npr_w1_next_ptr),
.w1_pkt_head (npr_w1_pkt_head),
.w1_pkt_tail (npr_w1_pkt_tail),
.build_we (npr_build_we),
.build_addr (npr_build_addr),
.build_next_ptr (npr_build_next_ptr),
.build_pkt_head (npr_build_pkt_head),
.build_pkt_tail (npr_build_pkt_tail)
);
//========================================================================
// 工具函数
// qid2port: 出端口 = queue_id 高位 (queue_id/8)
//========================================================================
function automatic logic [PORT_W-1:0] qid2port (input logic [QID_W-1:0] qid);
qid2port = qid[QID_W-1 -: PORT_W];
endfunction
//========================================================================
// 建链 FSM (上电初始化空闲链 0->1->...->CELL_NUM-1)
//========================================================================
typedef enum logic [1:0] {
ST_IDLE = 2'b00,
ST_BUILD = 2'b01,
ST_DONE = 2'b10
} build_st_e;
build_st_e build_st_q;
logic [ADDR_W-1:0] build_idx_q;
logic build_active;
assign build_active = (build_st_q == ST_BUILD);
always_ff @(posedge clk_core or negedge rst_core_n) begin
if (!rst_core_n) begin
build_st_q <= ST_IDLE;
build_idx_q <= '0;
init_build_done <= 1'b0;
end
else begin
case (build_st_q)
ST_IDLE: begin
init_build_done <= 1'b0;
if (init_build_req) begin
build_st_q <= ST_BUILD;
build_idx_q <= '0;
end
end
ST_BUILD: begin
if (build_idx_q == CELL_NUM-1)
build_st_q <= ST_DONE;
build_idx_q <= build_idx_q + 1'b1;
end
ST_DONE: begin
init_build_done <= 1'b1;
build_st_q <= ST_IDLE;
end
default: build_st_q <= ST_IDLE;
endcase
end
end
// 建链写口: entry[idx] = {idx+1 (末项=NULL), 0, 0}
always_comb begin
npr_build_we = build_active;
npr_build_addr = build_idx_q;
npr_build_next_ptr = (build_idx_q == CELL_NUM-1) ? NULL_PTR
: (build_idx_q + 1'b1);
npr_build_pkt_head = 1'b0;
npr_build_pkt_tail = 1'b0;
end
//========================================================================
// 命中判定
//========================================================================
logic alloc_hit; // 本拍有效分配
logic deq_hit; // 本拍有效出队
logic free_hit; // 本拍有效还链
logic same_q; // 同一队列同拍 enq+deq
assign alloc_hit = lle_alloc_fire & ~build_active & ~lle_free_empty;
assign deq_hit = lle_deq_fire & ~build_active;
assign free_hit = lle_free_req & ~build_active;
assign same_q = alloc_hit & deq_hit & (lle_alloc_queue_id == lle_deq_queue_id);
//========================================================================
// 对外组合输出 (1 拍: 地址当拍即给)
//========================================================================
assign lle_free_head = free_head_q;
assign lle_free_empty = (free_cnt_q == '0);
assign lle_qhead = q_head_q[lle_deq_queue_id];
assign lle_qhead_pkt_head = q_head_entry_q[lle_deq_queue_id][PH_BIT];
assign lle_qhead_pkt_tail = q_head_entry_q[lle_deq_queue_id][PT_BIT];
assign lle_q_empty = (q_cell_cnt_q[lle_deq_queue_id] == '0);
assign lle_free_grant = free_hit;
assign lle_free_done = free_hit;
//========================================================================
// 组播 ref_count 转发 (分配同拍, 组播时发)
//========================================================================
assign mc_set_req = alloc_hit & lle_alloc_is_mcast;
assign mc_set_addr = free_head_q;
assign mc_set_init = lle_alloc_ref_init;
//========================================================================
// 事件上报 Occupancy
// same_q 时分配+出队净占用不变, 仍各自上报 (Occupancy 做 +1-1 合并)
//========================================================================
assign lle_alloc_evt = alloc_hit;
assign lle_free_evt = free_hit;
assign evt_queue_id = alloc_hit ? lle_alloc_queue_id : lle_deq_queue_id;
assign evt_egress_port = qid2port(evt_queue_id);
//========================================================================
// 读口地址 (组合)
// 读口 A: 预取 free_nxt 的下一项 (NextPtr[free_nxt].next)
// 读口 B: 预取出队后新队头 entry (NextPtr[q_head_entry.next])
//========================================================================
logic [ADDR_W-1:0] free_nxt_nxt; // NextPtr[free_nxt].next
logic [ADDR_W-1:0] deq_new_head; // 出队后新队头地址
logic [ENTRY_W-1:0] deq_new_head_entry; // 出队后新队头 entry
assign npr_ra_addr = free_nxt_q;
assign free_nxt_nxt = npr_ra_next_ptr;
assign deq_new_head = q_head_entry_q[lle_deq_queue_id][ENTRY_W-1:2];
assign npr_rb_addr = deq_new_head;
assign deq_new_head_entry = {npr_rb_next_ptr, npr_rb_pkt_head, npr_rb_pkt_tail};
//========================================================================
// 写口驱动 (组合)
// W0 (主写): 挂链写旧队尾 next / 还链写 free_tail next
// W1 (辅写): 入队写新分配 cell 自身 entry {NULL, sof, eof}
//========================================================================
always_comb begin
// 默认不写
npr_w0_we = 1'b0;
npr_w0_addr = '0;
npr_w0_next_ptr = '0;
npr_w0_pkt_head = 1'b0;
npr_w0_pkt_tail = 1'b0;
npr_w1_we = 1'b0;
npr_w1_addr = '0;
npr_w1_next_ptr = '0;
npr_w1_pkt_head = 1'b0;
npr_w1_pkt_tail = 1'b0;
// W1: 入队写新 cell entry (新 cell 总挂队尾, next=NULL)
if (alloc_hit) begin
npr_w1_we = 1'b1;
npr_w1_addr = free_head_q;
npr_w1_next_ptr = NULL_PTR;
npr_w1_pkt_head = lle_set_pkt_head;
npr_w1_pkt_tail = lle_set_pkt_tail;
end
// W0: 挂链写旧队尾 next (队列原非空时) 或 还链写 free_tail next
if (alloc_hit && (q_cell_cnt_q[lle_alloc_queue_id] != '0)) begin
npr_w0_we = 1'b1;
npr_w0_addr = q_tail_q[lle_alloc_queue_id];
npr_w0_next_ptr = free_head_q;
npr_w0_pkt_head = 1'b0; // 仅改 next 语义; 走链以 q_tail 与 next 为准
npr_w0_pkt_tail = 1'b0;
end
else if (free_hit) begin
npr_w0_we = 1'b1;
npr_w0_addr = free_tail_q;
npr_w0_next_ptr = lle_free_addr;
npr_w0_pkt_head = 1'b0;
npr_w0_pkt_tail = 1'b0;
end
end
//========================================================================
// 空闲链状态更新 (时序)
//========================================================================
integer q;
always_ff @(posedge clk_core or negedge rst_core_n) begin
if (!rst_core_n) begin
free_head_q <= '0;
free_nxt_q <= '0;
free_tail_q <= '0;
free_cnt_q <= '0;
for (q = 0; q < QUEUE_NUM; q++) begin
q_head_q[q] <= '0;
q_tail_q[q] <= '0;
q_cell_cnt_q[q] <= '0;
q_head_entry_q[q] <= '0;
end
end
else if (build_st_q == ST_DONE) begin
// 建链完成: 初始化空闲链指针/计数, 清队列
free_head_q <= '0;
free_nxt_q <= , 1'b1}; // = 1
free_tail_q <= CELL_NUM-1;
free_cnt_q <= CELL_NUM[CNT_W-1:0];
for (q = 0; q < QUEUE_NUM; q++) begin
q_head_q[q] <= '0;
q_tail_q[q] <= '0;
q_cell_cnt_q[q] <= '0;
q_head_entry_q[q] <= '0;
end
end
else begin
//--------------------------------------------------------------
// 空闲链推进 (分配 / 还链 / 同拍)
//--------------------------------------------------------------
if (alloc_hit && !free_hit) begin
free_head_q <= free_nxt_q;
free_nxt_q <= free_nxt_nxt;
free_cnt_q <= free_cnt_q - 1'b1;
end
else if (!alloc_hit && free_hit) begin
free_tail_q <= lle_free_addr;
free_cnt_q <= free_cnt_q + 1'b1;
if (free_cnt_q == '0) begin
// 空闲链原本空: 还链 cell 成为新 free_head + 预取
free_head_q <= lle_free_addr;
free_nxt_q <= lle_free_addr;
end
end
else if (alloc_hit && free_hit) begin
// 同拍分配+还链: head 推进, tail 接还链, count 净不变
free_head_q <= free_nxt_q;
free_nxt_q <= free_nxt_nxt;
free_tail_q <= lle_free_addr;
end
//--------------------------------------------------------------
// 队列指针/描述符更新
//--------------------------------------------------------------
// 入队挂尾 (空队列时新 cell 同时成队头, bypass 写 q_head_entry)
if (alloc_hit) begin
q_tail_q[lle_alloc_queue_id] <= free_head_q;
if (q_cell_cnt_q[lle_alloc_queue_id] == '0) begin
q_head_q[lle_alloc_queue_id] <= free_head_q;
q_head_entry_q[lle_alloc_queue_id] <= {NULL_PTR,
lle_set_pkt_head,
lle_set_pkt_tail};
end
end
// 普通出队 (非 same_q): 队头推进 + 取新队头 entry
if (deq_hit && !same_q) begin
q_head_q[lle_deq_queue_id] <= deq_new_head;
q_head_entry_q[lle_deq_queue_id] <= deq_new_head_entry;
end
//--------------------------------------------------------------
// q_cell_cnt 更新 (+alloc_hit -deq_hit 代数合并)
// same_q 三情形 (空/单cell/非空) 计数净不变
//--------------------------------------------------------------
if (same_q) begin
if (q_cell_cnt_q[lle_alloc_queue_id] == '0) begin
// 情形③ 空队列直通: 不挂链, 计数维持 0
q_cell_cnt_q[lle_alloc_queue_id] <= '0;
end
else if (q_cell_cnt_q[lle_alloc_queue_id] == 1) begin
// 情形② 单 cell: 旧队头出走, 新 cell 成新队头兼队尾
q_head_q[lle_alloc_queue_id] <= free_head_q;
q_tail_q[lle_alloc_queue_id] <= free_head_q;
q_head_entry_q[lle_alloc_queue_id] <= {NULL_PTR,
lle_set_pkt_head,
lle_set_pkt_tail};
q_cell_cnt_q[lle_alloc_queue_id] <= 1; // 净不变
end
else begin
// 情形① 非空(>=2): 队头推进 + 队尾挂新; 计数净不变
q_head_q[lle_alloc_queue_id] <= deq_new_head;
q_head_entry_q[lle_alloc_queue_id] <= deq_new_head_entry;
q_tail_q[lle_alloc_queue_id] <= free_head_q;
end
end
else begin
// 不同队列 (或仅 alloc / 仅 deq): 各自独立更新计数
if (alloc_hit)
q_cell_cnt_q[lle_alloc_queue_id] <= q_cell_cnt_q[lle_alloc_queue_id] + 1'b1;
if (deq_hit)
q_cell_cnt_q[lle_deq_queue_id] <= q_cell_cnt_q[lle_deq_queue_id] - 1'b1;
end
end
end
//========================================================================
// 说明: same_q 空队列直通 (情形③) 时, deq 的输出地址/头尾应直接取本拍 enq 的
// 数据, 而非读 q_head (无效)。lle_qhead 等用 continuous assign 接
// q_head/q_head_entry, 该直通由上层 Enqueue/Dequeue Ctrl 协同处理
// (详见架构文档 4.1.3 情形③), LLE 内部不再额外覆盖以保持接口简洁。
//========================================================================
endmodule
//============================================================================
// Testbench : lle_tb
// Project : 4-Port 2.5/1G/100M Ethernet Switch - Smart MMU
// Description : 针对 Link-List Engine (lle) 的功能验证 testbench。
// 为缩短建链时间并便于观测, 用小参数覆写:
// ADDR_W=5, CELL_NUM=16, QUEUE_NUM=4, QID_W=2, PORT_W=2, REF_W=3, CNT_W=5
// 覆盖以下测试场景 (每个 task 前有中文场景说明):
// 1. 上电建链 (Init build FSM) 与初始空闲链状态
// 2. 单队列连续入队 (背靠背分配, free_count 递减, q_cell_cnt 递增)
// 3. 单队列连续出队 (背靠背, 走链直到 pkt_tail)
// 4. 多队列交替入队 (不同 queue_id 互不影响)
// 5. 入队后回收 (还链, free_count 恢复)
// 6. 组播入队 (mc_set_req / ref_init 转发)
// 7. 同一队列同拍 进包+出包 —— 空队列 (情形③ 直通)
// 8. 同一队列同拍 进包+出包 —— 单 cell 队列 (情形②)
// 9. 同一队列同拍 进包+出包 —— 非空队列 (情形①)
// 10. 同拍 分配 + 还链 (free 链 head 推进 + tail 接还链, count 净不变)
// 11. 空闲链耗尽 (free_count→0, lle_free_empty 拉高, alloc 被抑制)
// 12. 守恒检查 (free_count + 全部 q_cell_cnt == CELL_NUM)
//
// 说明: 由于 lle_qhead 等输出直接接内部 q_head/q_head_entry, 检查以 free_count /
// q_cell_cnt / lle_free_head / lle_qhead 等可见行为为主。
//============================================================================
`timescale 1ns/1ps
module lle_tb;
//------------------------------------------------------------------------
// 参数 (小规模, 便于快速建链与观测)
//------------------------------------------------------------------------
localparam int ADDR_W = 5;
localparam int CELL_NUM = 16;
localparam int QUEUE_NUM = 4;
localparam int QID_W = 2;
localparam int PORT_W = 2;
localparam int REF_W = 3;
localparam int CNT_W = 5;
localparam time CLK_PERIOD = 10ns; // 100MHz (仿真用)
//------------------------------------------------------------------------
// DUT 信号
//------------------------------------------------------------------------
logic clk_core;
logic rst_core_n;
// Enqueue
logic [ADDR_W-1:0] lle_free_head;
logic lle_free_empty;
logic lle_alloc_fire;
logic [QID_W-1:0] lle_alloc_queue_id;
logic [ADDR_W-1:0] lle_alloc_addr;
logic lle_set_pkt_head;
logic lle_set_pkt_tail;
logic lle_alloc_is_mcast;
logic [REF_W-1:0] lle_alloc_ref_init;
// Dequeue
logic [QID_W-1:0] lle_deq_queue_id;
logic [ADDR_W-1:0] lle_qhead;
logic lle_qhead_pkt_head;
logic lle_qhead_pkt_tail;
logic lle_q_empty;
logic lle_deq_fire;
// Recycle
logic lle_free_req;
logic [ADDR_W-1:0] lle_free_addr;
logic lle_free_grant;
logic lle_free_done;
// Mcast
logic mc_set_req;
logic [ADDR_W-1:0] mc_set_addr;
logic [REF_W-1:0] mc_set_init;
logic mc_set_ack;
// Init
logic init_build_req;
logic init_build_done;
// Event
logic lle_alloc_evt;
logic lle_free_evt;
logic [QID_W-1:0] evt_queue_id;
logic [PORT_W-1:0] evt_egress_port;
//------------------------------------------------------------------------
// 计分
//------------------------------------------------------------------------
int error_cnt = 0;
int check_cnt = 0;
task automatic chk(input bit cond, input string msg);
check_cnt++;
if (!cond) begin
error_cnt++;
$display("[%0t] [FAIL] %s", $time, msg);
end
else begin
$display("[%0t] [PASS] %s", $time, msg);
end
endtask
//------------------------------------------------------------------------
// DUT 例化
//------------------------------------------------------------------------
lle #(
.ADDR_W (ADDR_W),
.CELL_NUM (CELL_NUM),
.QUEUE_NUM (QUEUE_NUM),
.QID_W (QID_W),
.PORT_W (PORT_W),
.REF_W (REF_W),
.CNT_W (CNT_W)
) dut (
.clk_core (clk_core),
.rst_core_n (rst_core_n),
.lle_free_head (lle_free_head),
.lle_free_empty (lle_free_empty),
.lle_alloc_fire (lle_alloc_fire),
.lle_alloc_queue_id (lle_alloc_queue_id),
.lle_alloc_addr (lle_alloc_addr),
.lle_set_pkt_head (lle_set_pkt_head),
.lle_set_pkt_tail (lle_set_pkt_tail),
.lle_alloc_is_mcast (lle_alloc_is_mcast),
.lle_alloc_ref_init (lle_alloc_ref_init),
.lle_deq_queue_id (lle_deq_queue_id),
.lle_qhead (lle_qhead),
.lle_qhead_pkt_head (lle_qhead_pkt_head),
.lle_qhead_pkt_tail (lle_qhead_pkt_tail),
.lle_q_empty (lle_q_empty),
.lle_deq_fire (lle_deq_fire),
.lle_free_req (lle_free_req),
.lle_free_addr (lle_free_addr),
.lle_free_grant (lle_free_grant),
.lle_free_done (lle_free_done),
.mc_set_req (mc_set_req),
.mc_set_addr (mc_set_addr),
.mc_set_init (mc_set_init),
.mc_set_ack (mc_set_ack),
.init_build_req (init_build_req),
.init_build_done (init_build_done),
.lle_alloc_evt (lle_alloc_evt),
.lle_free_evt (lle_free_evt),
.evt_queue_id (evt_queue_id),
.evt_egress_port (evt_egress_port)
);
//------------------------------------------------------------------------
// 时钟
//------------------------------------------------------------------------
initial clk_core = 1'b0;
always #(CLK_PERIOD/2) clk_core = ~clk_core;
//------------------------------------------------------------------------
// 默认驱动复位
//------------------------------------------------------------------------
task automatic drive_idle();
lle_alloc_fire = 1'b0;
lle_alloc_queue_id = '0;
lle_alloc_addr = '0;
lle_set_pkt_head = 1'b0;
lle_set_pkt_tail = 1'b0;
lle_alloc_is_mcast = 1'b0;
lle_alloc_ref_init = '0;
lle_deq_queue_id = '0;
lle_deq_fire = 1'b0;
lle_free_req = 1'b0;
lle_free_addr = '0;
mc_set_ack = 1'b1; // 不阻塞
endtask
//------------------------------------------------------------------------
// 单拍入队 (在时钟上升沿前建立, 一拍后释放)
//------------------------------------------------------------------------
task automatic do_enq(input logic [QID_W-1:0] qid,
input logic sof, input logic eof,
input logic is_mc, input logic [REF_W-1:0] ref_init);
@(negedge clk_core);
lle_alloc_fire = 1'b1;
lle_alloc_queue_id = qid;
lle_alloc_addr = lle_free_head;
lle_set_pkt_head = sof;
lle_set_pkt_tail = eof;
lle_alloc_is_mcast = is_mc;
lle_alloc_ref_init = ref_init;
@(negedge clk_core);
lle_alloc_fire = 1'b0;
lle_alloc_is_mcast = 1'b0;
endtask
//------------------------------------------------------------------------
// 单拍出队
//------------------------------------------------------------------------
task automatic do_deq(input logic [QID_W-1:0] qid);
@(negedge clk_core);
lle_deq_queue_id = qid;
lle_deq_fire = 1'b1;
@(negedge clk_core);
lle_deq_fire = 1'b0;
endtask
//------------------------------------------------------------------------
// 单拍还链
//------------------------------------------------------------------------
task automatic do_free(input logic [ADDR_W-1:0] addr);
@(negedge clk_core);
lle_free_req = 1'b1;
lle_free_addr = addr;
@(negedge clk_core);
lle_free_req = 1'b0;
endtask
//------------------------------------------------------------------------
// 同拍 入队(qid_e) + 出队(qid_d)
//------------------------------------------------------------------------
task automatic do_enq_deq(input logic [QID_W-1:0] qid_e,
input logic [QID_W-1:0] qid_d,
input logic sof, input logic eof);
@(negedge clk_core);
lle_alloc_fire = 1'b1;
lle_alloc_queue_id = qid_e;
lle_alloc_addr = lle_free_head;
lle_set_pkt_head = sof;
lle_set_pkt_tail = eof;
lle_deq_queue_id = qid_d;
lle_deq_fire = 1'b1;
@(negedge clk_core);
lle_alloc_fire = 1'b0;
lle_deq_fire = 1'b0;
endtask
//------------------------------------------------------------------------
// 同拍 分配(入队 qid) + 还链(addr)
//------------------------------------------------------------------------
task automatic do_enq_free(input logic [QID_W-1:0] qid,
input logic [ADDR_W-1:0] free_addr);
@(negedge clk_core);
lle_alloc_fire = 1'b1;
lle_alloc_queue_id = qid;
lle_alloc_addr = lle_free_head;
lle_set_pkt_head = 1'b1;
lle_set_pkt_tail = 1'b1;
lle_free_req = 1'b1;
lle_free_addr = free_addr;
@(negedge clk_core);
lle_alloc_fire = 1'b0;
lle_free_req = 1'b0;
endtask
//------------------------------------------------------------------------
// 读取内部状态的便捷函数 (层次化引用 DUT 内部信号)
//------------------------------------------------------------------------
function automatic int unsigned get_free_count();
get_free_count = dut.free_count;
endfunction
function automatic int unsigned get_qcnt(input int q);
get_qcnt = dut.q_cell_cnt[q];
endfunction
//========================================================================
// 主测试流程
//========================================================================
initial begin
$display("==================================================");
$display(" LLE Testbench 开始");
$display("==================================================");
//--------------------------------------------------------------------
// 场景 1: 上电建链与复位
// 预期: 复位后建链, init_build_done 拉高; 建链完成后
// free_count == CELL_NUM, free_head==0, lle_free_empty==0,
// 所有队列 q_cell_cnt==0。
//--------------------------------------------------------------------
drive_idle();
rst_core_n = 1'b0;
init_build_req = 1'b0;
repeat (3) @(negedge clk_core);
rst_core_n = 1'b1;
@(negedge clk_core);
// 触发建链
init_build_req = 1'b1;
@(negedge clk_core);
init_build_req = 1'b0;
// 等待建链完成 (CELL_NUM 拍 + 余量)
wait (init_build_done == 1'b1);
@(negedge clk_core);
$display("--- 场景1: 上电建链 ---");
chk(get_free_count() == CELL_NUM, "建链后 free_count == CELL_NUM");
chk(lle_free_head == 0, "建链后 free_head == 0");
chk(lle_free_empty == 1'b0, "建链后空闲链非空");
chk(get_qcnt(0)==0 && get_qcnt(1)==0 && get_qcnt(2)==0 && get_qcnt(3)==0,
"建链后所有队列 q_cell_cnt == 0");
//--------------------------------------------------------------------
// 场景 2: 单队列连续入队 (背靠背分配)
// 对 queue 0 连续入队 3 个 cell (sof, mid, eof)。
// 预期: free_count 减 3, q_cell_cnt[0] == 3。
//--------------------------------------------------------------------
$display("--- 场景2: 单队列连续入队 ---");
do_enq(0, 1'b1, 1'b0, 1'b0, '0); // sof
do_enq(0, 1'b0, 1'b0, 1'b0, '0); // mid
do_enq(0, 1'b0, 1'b1, 1'b0, '0); // eof
@(negedge clk_core);
chk(get_qcnt(0) == 3, "queue0 入队 3 个 cell 后 q_cell_cnt==3");
chk(get_free_count() == CELL_NUM-3, "入队 3 个后 free_count 减 3");
//--------------------------------------------------------------------
// 场景 3: 单队列连续出队 (走链直到 pkt_tail)
// 对 queue 0 连续出队 3 个 cell。
// 预期: q_cell_cnt[0] 回到 0, lle_q_empty 拉高。
//--------------------------------------------------------------------
$display("--- 场景3: 单队列连续出队 ---");
do_deq(0);
do_deq(0);
do_deq(0);
@(negedge clk_core);
chk(get_qcnt(0) == 0, "queue0 出队 3 个后 q_cell_cnt==0");
lle_deq_queue_id = 0;
#1;
chk(lle_q_empty == 1'b1, "queue0 出空后 lle_q_empty==1");
//--------------------------------------------------------------------
// 场景 4: 多队列交替入队 (不同 queue_id 互不影响)
// queue1 入 2 个, queue2 入 1 个, queue3 入 1 个。
// 预期: 各队列计数独立正确。
//--------------------------------------------------------------------
$display("--- 场景4: 多队列交替入队 ---");
do_enq(1, 1'b1, 1'b1, 1'b0, '0);
do_enq(1, 1'b1, 1'b1, 1'b0, '0);
do_enq(2, 1'b1, 1'b1, 1'b0, '0);
do_enq(3, 1'b1, 1'b1, 1'b0, '0);
@(negedge clk_core);
chk(get_qcnt(1) == 2, "queue1 == 2");
chk(get_qcnt(2) == 1, "queue2 == 1");
chk(get_qcnt(3) == 1, "queue3 == 1");
//--------------------------------------------------------------------
// 场景 5: 入队后回收 (还链)
// 还链 2 个 cell (地址随便取已分配范围内的, 这里用 free_head 之外的旧地址)。
// 预期: free_count 增加 2。
// 注: 本场景只验证 free_count 计数, 不严格跟踪具体地址归属。
//--------------------------------------------------------------------
$display("--- 场景5: 回收还链 ---");
begin
int unsigned fc_before;
fc_before = get_free_count();
do_free(5'd1); // 还链地址 1
do_free(5'd2); // 还链地址 2
@(negedge clk_core);
chk(get_free_count() == fc_before + 2, "回收 2 个 cell 后 free_count 增 2");
end
//--------------------------------------------------------------------
// 场景 6: 组播入队 (mc_set_req / ref_init 转发)
// 组播入队到 queue0, ref_init=3。
// 预期: 入队当拍 mc_set_req 拉高, mc_set_init==3, mc_set_addr==分配地址。
//--------------------------------------------------------------------
$display("--- 场景6: 组播入队 ---");
@(negedge clk_core);
lle_alloc_fire = 1'b1;
lle_alloc_queue_id = 0;
lle_alloc_addr = lle_free_head;
lle_set_pkt_head = 1'b1;
lle_set_pkt_tail = 1'b1;
lle_alloc_is_mcast = 1'b1;
lle_alloc_ref_init = 3'd3;
#1; // 组合稳定
chk(mc_set_req == 1'b1, "组播入队当拍 mc_set_req==1");
chk(mc_set_init == 3'd3, "组播 mc_set_init==3");
chk(mc_set_addr == lle_free_head, "组播 mc_set_addr==分配地址");
@(negedge clk_core);
lle_alloc_fire = 1'b0;
lle_alloc_is_mcast = 1'b0;
//--------------------------------------------------------------------
// 场景 7: 同一队列同拍 进包+出包 —— 空队列 (情形③ 直通)
// 先把 queue1 出空, 再同拍对 queue1 enq+deq。
// 预期: 空队列直通, q_cell_cnt[1] 维持 0 (进来又出去), free_count 减 1
// (新 cell 被分配但等价立即出走; 本实现分配会消耗 free, 计数净不变在队列侧)。
//--------------------------------------------------------------------
$display("--- 场景7: 同拍进出包-空队列(情形③) ---");
// 先出空 queue1 (场景4 入了 2 个)
do_deq(1);
do_deq(1);
@(negedge clk_core);
chk(get_qcnt(1) == 0, "queue1 先出空, q_cell_cnt==0");
begin
int unsigned q1_before;
q1_before = get_qcnt(1);
do_enq_deq(1, 1, 1'b1, 1'b1); // 同拍 enq+deq 到空 queue1
@(negedge clk_core);
chk(get_qcnt(1) == q1_before, "空队列同拍进出包: q_cell_cnt 维持 0 (净不变)");
end
//--------------------------------------------------------------------
// 场景 8: 同一队列同拍 进包+出包 —— 单 cell 队列 (情形②)
// queue2 当前有 1 个 cell, 同拍 enq+deq。
// 预期: 旧队头出走、新 cell 成新队头兼队尾, q_cell_cnt[2] 维持 1。
//--------------------------------------------------------------------
$display("--- 场景8: 同拍进出包-单cell(情形②) ---");
chk(get_qcnt(2) == 1, "queue2 当前为单 cell");
begin
int unsigned q2_before;
q2_before = get_qcnt(2);
do_enq_deq(2, 2, 1'b1, 1'b1);
@(negedge clk_core);
chk(get_qcnt(2) == q2_before, "单cell队列同拍进出包: q_cell_cnt 维持 1");
end
//--------------------------------------------------------------------
// 场景 9: 同一队列同拍 进包+出包 —— 非空队列 >=2 (情形①)
// 先给 queue0 入 2 个 (使 >=2), 再同拍 enq+deq。
// 预期: 队头推进 + 队尾挂新, q_cell_cnt[0] 净不变。
//--------------------------------------------------------------------
$display("--- 场景9: 同拍进出包-非空>=2(情形①) ---");
do_enq(0, 1'b1, 1'b0, 1'b0, '0);
do_enq(0, 1'b0, 1'b1, 1'b0, '0);
@(negedge clk_core);
begin
int unsigned q0_before;
q0_before = get_qcnt(0);
chk(q0_before >= 2, "queue0 当前 >=2 个 cell");
do_enq_deq(0, 0, 1'b1, 1'b1);
@(negedge clk_core);
chk(get_qcnt(0) == q0_before, "非空队列同拍进出包: q_cell_cnt 净不变");
end
//--------------------------------------------------------------------
// 场景 10: 同拍 分配 + 还链
// 同拍对 queue3 入队 + 还链一个地址。
// 预期: free_count 净不变 (分配 -1, 还链 +1), q_cell_cnt[3] +1。
//--------------------------------------------------------------------
$display("--- 场景10: 同拍分配+还链 ---");
begin
int unsigned fc_before, q3_before;
fc_before = get_free_count();
q3_before = get_qcnt(3);
do_enq_free(3, 5'd3); // 入队 queue3 + 还链地址 3
@(negedge clk_core);
chk(get_free_count() == fc_before, "同拍分配+还链: free_count 净不变");
chk(get_qcnt(3) == q3_before + 1, "同拍分配+还链: queue3 +1");
end
//--------------------------------------------------------------------
// 场景 11: 空闲链耗尽
// 持续入队直到 free_count==0, 检查 lle_free_empty 拉高, 之后 alloc 被抑制。
//--------------------------------------------------------------------
$display("--- 场景11: 空闲链耗尽 ---");
begin
int guard;
guard = 0;
while (get_free_count() > 0 && guard < CELL_NUM*2) begin
do_enq(0, 1'b1, 1'b1, 1'b0, '0);
guard++;
end
@(negedge clk_core);
chk(get_free_count() == 0, "持续入队后 free_count==0");
#1;
chk(lle_free_empty == 1'b1, "空闲链耗尽 lle_free_empty==1");
// 再尝试入队 (应被抑制: alloc_hit=0, q_cell_cnt 不增)
begin
int unsigned q0_before;
q0_before = get_qcnt(0);
do_enq(0, 1'b1, 1'b1, 1'b0, '0);
@(negedge clk_core);
chk(get_qcnt(0) == q0_before, "空闲链空时入队被抑制 (q_cell_cnt 不增)");
end
end
//--------------------------------------------------------------------
// 场景 12: 守恒检查 (free_count + Σq_cell_cnt == CELL_NUM)
// 注: 本 TB 中场景5/10 用固定地址还链, 可能引入重复地址, 守恒以"分配/还链
// 次数差"为准。这里做一个宽松一致性提示, 不作为硬性 FAIL。
//--------------------------------------------------------------------
$display("--- 场景12: 守恒一致性提示 ---");
begin
int unsigned total;
total = get_free_count() + get_qcnt(0) + get_qcnt(1)
+ get_qcnt(2) + get_qcnt(3);
$display("[%0t] [INFO] free_count(%0d)+Σq_cell_cnt = %0d (CELL_NUM=%0d)",
$time, get_free_count(), total, CELL_NUM);
// 宽松检查: total 不应超过 CELL_NUM 太多 (还链重复地址可能放大)
chk(total >= CELL_NUM-1, "守恒一致性: 总数接近 CELL_NUM (提示性)");
end
//--------------------------------------------------------------------
// 收尾
//--------------------------------------------------------------------
repeat (4) @(negedge clk_core);
$display("==================================================");
$display(" LLE Testbench 结束: 检查 %0d 项, 失败 %0d 项",
check_cnt, error_cnt);
if (error_cnt == 0)
$display(" 结果: 全部通过 (PASS)");
else
$display(" 结果: 存在失败 (FAIL)");
$display("==================================================");
$finish;
end
//------------------------------------------------------------------------
// 超时保护
//------------------------------------------------------------------------
initial begin
#50000ns;
$display("[%0t] [TIMEOUT] 仿真超时, 强制结束", $time);
$finish;
end
endmodule
reg
//============================================================================
// Module : next_ptr_regfile (Next-Ptr Register File)
// Project : 4-Port 2.5/1G/100M Ethernet Switch - Smart MMU
// Description : LLE 内部私有的链表指针寄存器堆 (非独立可访问块, 仅由 LLE 例化驱动)。
// 每个 cell 存一条链表节点 entry:
// { next_ptr[ADDR_W-1:0], pkt_head, pkt_tail } (ENTRY_W=ADDR_W+2)
// 用寄存器 (触发器阵列) 实现, 取代单口 SRAM, 以获得:
// * 多读口、当拍组合可读 (无读延迟) —— 直接支撑入队/出队各 1 拍;
// * 同拍写两个不同地址 (每 cell 为独立寄存器) —— 入队需同拍两写;
// * 读写同地址 bypass —— 同拍写后读取新值, 无 RMW hazard。
//
// 读口 (组合, 读时 bypass 同拍写):
// - 端口 A : 通用读 (free_head.next 预取 / 走链读 next)
// - 端口 B : 队头描述符读 (q_head_entry 预取 / 出队读队头)
// 写口 (同步, 上升沿):
// - 端口 W0 (主写): 挂链写旧队尾 next / 还链写 free_tail next
// - 端口 W1 (辅写): 入队写新分配 cell 自身 entry {NULL, sof, eof}
// W0/W1 正常写不同地址; 罕见同地址冲突时以 W0 优先 (仅给确定性)。
// 建链 (build): build_we 期间由 LLE build FSM 顺序写, 建成空闲单链。
//
// 规模: CELL_NUM=8192, ENTRY_W=15 -> 约 120kbit 触发器; 规模变大再评估改 SRAM。
//
// Clock/Reset : clk_core (300MHz, 单时钟域) / rst_core_n (异步复位低有效)
//============================================================================
`timescale 1ns/1ps
module next_ptr_regfile #(
parameter int ADDR_W = 13, // cell 地址位宽
parameter int CELL_NUM = 8192, // cell 总数
parameter int ENTRY_W = ADDR_W + 2 // {next_ptr, pkt_head, pkt_tail}
)(
//========================================================================
// 时钟与复位
//========================================================================
input logic clk_core, // 300MHz 核心时钟
input logic rst_core_n, // 异步复位, 低有效
//========================================================================
// 读口 A (组合): free_head.next 预取 / 走链读 next
//========================================================================
input logic [ADDR_W-1:0] ra_addr,
output logic [ADDR_W-1:0] ra_next_ptr,
output logic ra_pkt_head,
output logic ra_pkt_tail,
//========================================================================
// 读口 B (组合): q_head_entry 预取 / 出队读队头
//========================================================================
input logic [ADDR_W-1:0] rb_addr,
output logic [ADDR_W-1:0] rb_next_ptr,
output logic rb_pkt_head,
output logic rb_pkt_tail,
//========================================================================
// 写口 W0 (主写): 挂链写旧队尾 next / 还链写 free_tail next
//========================================================================
input logic w0_we,
input logic [ADDR_W-1:0] w0_addr,
input logic [ADDR_W-1:0] w0_next_ptr,
input logic w0_pkt_head,
input logic w0_pkt_tail,
//========================================================================
// 写口 W1 (辅写): 入队写新分配 cell 自身 entry
// 正常与 W0 写不同地址; 罕见同地址冲突时 W0 优先
//========================================================================
input logic w1_we,
input logic [ADDR_W-1:0] w1_addr,
input logic [ADDR_W-1:0] w1_next_ptr,
input logic w1_pkt_head,
input logic w1_pkt_tail,
//========================================================================
// 建链口 (上电建空闲链, LLE build FSM 顺序写)
//========================================================================
input logic build_we,
input logic [ADDR_W-1:0] build_addr,
input logic [ADDR_W-1:0] build_next_ptr,
input logic build_pkt_head,
input logic build_pkt_tail
);
//========================================================================
// 局部参数
//========================================================================
localparam int PH_BIT = 1; // pkt_head 在 entry 中的位
localparam int PT_BIT = 0; // pkt_tail 在 entry 中的位
//========================================================================
// 寄存器堆
// entry = { next_ptr[ENTRY_W-1:2], pkt_head[1], pkt_tail[0] }
//========================================================================
logic [ENTRY_W-1:0] mem_q [CELL_NUM];
//========================================================================
// 写数据打包
//========================================================================
logic [ENTRY_W-1:0] w0_entry;
logic [ENTRY_W-1:0] w1_entry;
logic [ENTRY_W-1:0] build_entry;
assign w0_entry = {w0_next_ptr, w0_pkt_head, w0_pkt_tail};
assign w1_entry = {w1_next_ptr, w1_pkt_head, w1_pkt_tail};
assign build_entry = {build_next_ptr, build_pkt_head, build_pkt_tail};
//========================================================================
// 逐 cell 写使能与写数据选择 (组合)
// 优先级: build > W0 > W1。每个 cell 只被命中其地址的写口更新;
// build 与 W0/W1 互斥 (init_done 前不接受 enq/deq/recycle)。
//========================================================================
logic cell_we [CELL_NUM];
logic [ENTRY_W-1:0] cell_wdata[CELL_NUM];
always_comb begin
for (int unsigned c = 0; c < CELL_NUM; c++) begin
if (build_we && (build_addr == c[ADDR_W-1:0])) begin
cell_we[c] = 1'b1;
cell_wdata[c] = build_entry;
end
else if (w0_we && (w0_addr == c[ADDR_W-1:0])) begin
cell_we[c] = 1'b1;
cell_wdata[c] = w0_entry;
end
else if (w1_we && (w1_addr == c[ADDR_W-1:0])) begin
cell_we[c] = 1'b1;
cell_wdata[c] = w1_entry;
end
else begin
cell_we[c] = 1'b0;
cell_wdata[c] = '0;
end
end
end
//========================================================================
// 时序写 (非阻塞): 每个 mem_q[c] 仅此一处赋值。
// 整个寄存器堆不做复位 (初值由 build FSM 写入), 避免巨大复位扇出。
//========================================================================
always_ff @(posedge clk_core) begin
for (int unsigned c = 0; c < CELL_NUM; c++) begin
if (cell_we[c])
mem_q[c] <= cell_wdata[c];
end
end
//========================================================================
// 组合读 + 同拍写 bypass
// 优先级与写一致: build > W0 > W1 > 存储现值。
//========================================================================
function automatic logic [ENTRY_W-1:0] read_entry (input logic [ADDR_W-1:0] raddr);
if (build_we && (build_addr == raddr))
read_entry = build_entry;
else if (w0_we && (w0_addr == raddr))
read_entry = w0_entry;
else if (w1_we && (w1_addr == raddr))
read_entry = w1_entry;
else
read_entry = mem_q[raddr];
endfunction
logic [ENTRY_W-1:0] ra_entry;
logic [ENTRY_W-1:0] rb_entry;
assign ra_entry = read_entry(ra_addr);
assign rb_entry = read_entry(rb_addr);
assign ra_next_ptr = ra_entry[ENTRY_W-1:2];
assign ra_pkt_head = ra_entry[PH_BIT];
assign ra_pkt_tail = ra_entry[PT_BIT];
assign rb_next_ptr = rb_entry[ENTRY_W-1:2];
assign rb_pkt_head = rb_entry[PH_BIT];
assign rb_pkt_tail = rb_entry[PT_BIT];
endmodule