news 2026/10/11 1:53:02

同步FIFO详细verilog代码及其 tb代码

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
同步FIFO详细verilog代码及其 tb代码

// ============================================================

// 同步 FIFO (视见数量 / 水位线版)

//

// 设计依据

// 写端视见数据量 WCNT = WPTR - Delayed(RPTR) = 实际 + delta -> 写端多看数据、少看空位

// 读端视见数据量 RCNT = Delayed(WPTR) - RPTR = 实际 - delta -> 读端少看数据、多看空位

// 规定:full 类水线按【数据视见量】比较;

// empty 类水线按【空位视见量 = DEPTH - 视见数据量】比较。

//

// 推论(本设计的安全性质):

// 写端 o_wempty / o_waempty 一定是“真空”(可连续写);

// 写端 o_wfull / o_wafull 不一定“真满”(保守,会提前反压)。

// 读端 o_rfull / o_rafull 一定是“真满”(可连续读);

// 读端 o_rempty / o_raempty 不一定“真空”(保守,会提前报空)。

//

// 实现说明:

// 内部保留【精确计数 r_fifo_number】用于读写控制(保证不丢数/不重复读);

// 视见数量用“上一拍是否真的发生读/写”来构造(数学上等价于另一侧指针延迟一拍):

// WCNT = 精确计数 + 上一拍读 (写端把上一拍读“补回来”)

// RCNT = 精确计数 - 上一拍写 (读端把上一拍写“扣掉”)

// 该写法与 FIFO 深度是否 2 的幂无关。

// ============================================================

module sync_fifo

#(

parameter DATA_DEPTH = 16, // FIFO 深度

parameter DATA_WIDTH = 32, // 数据位宽

// ---- 写端口水线 ----

// full 类:按【数据视见量 WCNT】比较

// empty 类:按【空位视见量 DEPTH-WCNT】比较,等价于“剩余数据视见量 <= 此值”

parameter W_AEMPTY_THRESH = 1, // WCNT <= 此值 -> o_waempty

parameter W_AFULL_THRESH = 2, // WCNT >= DEPTH - 此值 -> o_wafull

// ---- 读端口水线 ----

parameter R_AEMPTY_THRESH = 3, // RCNT <= 此值 -> o_raempty

parameter R_AFULL_THRESH = 3 // RCNT >= DEPTH - 此值 -> o_rafull

)

(

/* ---- 系统 ---- */

input i_sys_clk,

input i_sys_rst_n,

/* ---- 写端口 ---- */

input i_wren,

input [DATA_WIDTH-1:0] i_wdata,

/* ---- 读端口 ---- */

input i_rden,

output [DATA_WIDTH-1:0] o_rdata,

/* ---- 精确空满(内部基线,供读写控制/兼容)---- */

output o_full, // 精确满

output o_empty, // 精确空

/* ---- 写端口:视见数量状态 ---- */

output o_wfull, // 写端视见满(可能保守,不一定真满)

output o_wempty, // 写端视见空(一定真空,可连续写)

output o_waempty, // write almost empty

output o_wafull, // write almost full

output o_whempty, // write half empty

output o_whfull, // write half full

/* ---- 读端口:视见数量状态 ---- */

output o_rfull, // 读端视见满(一定真满,可连续读)

output o_rempty, // 读端视见空(可能保守,不一定真空)

output o_raempty, // read almost empty

output o_rafull, // read almost full

output o_rhempty, // read half empty

output o_rhfull // read half full

);

localparam ADDR_W = $clog2(DATA_DEPTH); // 地址位宽

localparam CNT_W = $clog2(DATA_DEPTH)+1; // 计数位宽(需能数到 DEPTH)

localparam HALF = DATA_DEPTH / 2; // 一半分界

reg [DATA_WIDTH-1:0] mem_ram [0:DATA_DEPTH-1];

reg [ADDR_W-1:0] r_wr_ptr;

reg [ADDR_W-1:0] r_rd_ptr;

reg [CNT_W-1:0] r_fifo_number; // 精确计数:内部读写控制用

reg [DATA_WIDTH-1:0] r_rdata; // 读数据输出(打一拍)

integer i;

// ---- 精确空满:仅用于内部读写控制,保证不丢数 ----

wire full_exact = (r_fifo_number == DATA_DEPTH);

wire empty_exact = (r_fifo_number == {CNT_W{1'b0}});

// 有效读写:精确满时写被忽略,精确空时读被忽略

wire wren_eff = i_wren && !full_exact;

wire rden_eff = i_rden && !empty_exact;

// ---- 视见数量的“延迟”来源:上一拍是否真的发生读/写 ----

// 等价于“另一侧指针延迟一拍”:写端看不到本拍刚发生的读,读端看不到本拍刚发生的写

reg r_rd_pend;

reg r_wr_pend;

always @(posedge i_sys_clk or negedge i_sys_rst_n)

begin

if(!i_sys_rst_n)

begin

r_rd_pend <= 1'b0;

r_wr_pend <= 1'b0;

end

else

begin

r_rd_pend <= rden_eff;

r_wr_pend <= wren_eff;

end

end

// 写端视见数据量 WCNT = 精确计数 + 上一拍读(上限饱和到 DEPTH)

// 读端视见数据量 RCNT = 精确计数 - 上一拍写(下限饱和到 0)

wire [CNT_W-1:0] wcnt_inc = r_fifo_number + CNT_W'(r_rd_pend);

wire [CNT_W-1:0] rcnt_dec = r_fifo_number - CNT_W'(r_wr_pend);

wire [CNT_W-1:0] wcnt = (wcnt_inc > DATA_DEPTH) ? DATA_DEPTH : wcnt_inc;

wire [CNT_W-1:0] rcnt = (r_fifo_number == {CNT_W{1'b0}}) ? {CNT_W{1'b0}} : rcnt_dec;

// 空位视见量(empty 类水线按此比较)

wire [CNT_W-1:0] wfree = CNT_W'(DATA_DEPTH) - wcnt;

wire [CNT_W-1:0] rfree = CNT_W'(DATA_DEPTH) - rcnt;

// 写指针:仅负责写地址,到达末尾回卷,非 2 的幂深度也安全

always @(posedge i_sys_clk or negedge i_sys_rst_n)

begin

if(!i_sys_rst_n)

r_wr_ptr <= {ADDR_W{1'b0}};

else if(wren_eff)

r_wr_ptr <= (r_wr_ptr == DATA_DEPTH - 1'b1) ? {ADDR_W{1'b0}} : r_wr_ptr + 1'b1;

end

// 读指针

always @(posedge i_sys_clk or negedge i_sys_rst_n)

begin

if(!i_sys_rst_n)

r_rd_ptr <= {ADDR_W{1'b0}};

else if(rden_eff)

r_rd_ptr <= (r_rd_ptr == DATA_DEPTH - 1'b1) ? {ADDR_W{1'b0}} : r_rd_ptr + 1'b1;

end

// 写数据进 RAM

always @(posedge i_sys_clk or negedge i_sys_rst_n)

begin

if(!i_sys_rst_n)

begin

for(i = 0; i < DATA_DEPTH; i = i + 1)

mem_ram[i] <= 'd0;

end

else if(wren_eff)

mem_ram[r_wr_ptr] <= i_wdata;

end

// 读数据(输出打一拍):标准 FIFO,读使能后下一拍出数据

always @(posedge i_sys_clk or negedge i_sys_rst_n)

begin

if(!i_sys_rst_n)

r_rdata <= {DATA_WIDTH{1'b0}};

else if(rden_eff)

r_rdata <= mem_ram[r_rd_ptr];

end

// 精确计数:一条语句统吃 同时读写 / 只写 / 只读

always @(posedge i_sys_clk or negedge i_sys_rst_n)

begin

if(!i_sys_rst_n)

r_fifo_number <= {CNT_W{1'b0}};

else

r_fifo_number <= r_fifo_number + CNT_W'(wren_eff) - CNT_W'(rden_eff);

end

// ---- 输出:精确基线 ----

assign o_rdata = r_rdata;

assign o_full = full_exact;

assign o_empty = empty_exact;

// ---- 写端口状态标志(全部基于 WCNT / 空位视见量 wfree)----

assign o_wfull = (wcnt >= DATA_DEPTH);

assign o_wempty = (wcnt == {CNT_W{1'b0}});

assign o_wafull = (wcnt >= (DATA_DEPTH - W_AFULL_THRESH));

assign o_waempty = (wfree >= (DATA_DEPTH - W_AEMPTY_THRESH));

assign o_whfull = (wcnt >= HALF);

assign o_whempty = (wfree > HALF);

// ---- 读端口状态标志(全部基于 RCNT / 空位视见量 rfree)----

assign o_rfull = (rcnt >= DATA_DEPTH);

assign o_rempty = (rcnt == {CNT_W{1'b0}});

assign o_rafull = (rcnt >= (DATA_DEPTH - R_AFULL_THRESH));

assign o_raempty = (rfree >= (DATA_DEPTH - R_AEMPTY_THRESH));

assign o_rhfull = (rcnt >= HALF);

assign o_rhempty = (rfree > HALF);

endmodule

`timescale 1ns / 1ps

// ============================================================

// sync_fifo 测试平台(零警告版)

// 覆盖:复位状态 / 写满 / 读空 / 同时读写 / half 阈值翻转

// 数据宽度用 8bit 方便看波形;深度 16 与 DUT 默认一致

// 编译:iverilog -g2012 -o main.out .\sync_fifo.v .\tb_sync_fifo.v

// 运行:vvp main.out

// 波形:仿真后打开 tb_sync_fifo.vcd

//

// 【为什么不用 exp_q [$] + pop_front()】

// SV 队列的 pop_front() 是返回 void 的方法,直接当语句调用时

// iverilog 会报 "method function 'pop_front' is being called as a

// task" 警告;而本机这版 iverilog(12.0) 又不支持 void'() 强转、

// 也不支持队列切片 q[1:$],改法都会变成语法错误。

// 故改用「纯 Verilog 数组 + 头/尾指针」做期望值队列,所有版本通用。

//

// 【关键时序说明(新手必看)】

// DUT 在时钟上升沿采样 i_wren/i_rden,并在同一拍用非阻塞赋值更新

// 内部计数和 o_rdata。因此 tb 里:

// - 写:拉高 wren 等一拍(边沿采样写)->再等一拍让计数稳定;

// - 读:拉高 rden 等一拍(边沿采样读)->再等一拍 o_rdata 才有效;

// - 任何 burst 结束后,要多等一拍再检查 o_full/o_empty/状态标志,

// 否则读到的是更新前(旧)的值,会误报失败。

// ============================================================

module tb_sync_fifo;

localparam DATA_DEPTH = 16;

localparam DATA_WIDTH = 8;

// ---- 接口信号 ----

reg i_sys_clk;

reg i_sys_rst_n;

reg i_wren;

reg [DATA_WIDTH-1:0] i_wdata;

reg i_rden;

wire [DATA_WIDTH-1:0] o_rdata;

wire o_full;

wire o_empty;

wire o_waempty;

wire o_wafull;

wire o_whempty;

wire o_whfull;

wire o_raempty;

wire o_rafull;

wire o_rhempty;

wire o_rhfull;

// 视见数量版新增端口

wire o_wfull;

wire o_wempty;

wire o_rfull;

wire o_rempty;

// ---- 自校验用:期望值用「数组 + 头尾指针」实现队列(不用 SV 队列)----

integer exp_mem [0:1023]; // 存放期望读出的数据

// ---- 实例化 DUT ----

sync_fifo #(

.DATA_DEPTH (DATA_DEPTH),

.DATA_WIDTH (DATA_WIDTH),

.W_AEMPTY_THRESH (1),

.W_AFULL_THRESH (2),

.R_AEMPTY_THRESH (3),

.R_AFULL_THRESH (3)

) u_fifo (

.i_sys_clk (i_sys_clk),

.i_sys_rst_n(i_sys_rst_n),

.i_wren (i_wren),

.i_wdata (i_wdata),

.i_rden (i_rden),

.o_rdata (o_rdata),

.o_full (o_full),

.o_empty (o_empty),

.o_wfull (o_wfull),

.o_wempty (o_wempty),

.o_rfull (o_rfull),

.o_rempty (o_rempty),

.o_waempty (o_waempty),

.o_wafull (o_wafull),

.o_whempty (o_whempty),

.o_whfull (o_whfull),

.o_raempty (o_raempty),

.o_rafull (o_rafull),

.o_rhempty (o_rhempty),

.o_rhfull (o_rhfull)

);

// ---- 时钟 10ns(5ns 半周期)----

always #5 i_sys_clk = ~i_sys_clk;

// ---- 波形 dump ----

initial begin

$dumpfile("tb_sync_fifo.vcd");

$dumpvars(0, tb_sync_fifo);

end

// ---- 任务:写一笔(满则丢弃并报告)----

// 时序:拉高 wren -> 等一拍(DUT 采样并写入) -> 拉低 -> 再多等一拍让计数稳定

integer exp_tail; // 指向下一个空闲槽位

task write_one;

input [DATA_WIDTH-1:0] d;

begin

if(o_full) begin

@(posedge i_sys_clk);

$display("[%0t] WRITE blocked (full): data=%0d dropped", $time, d);

i_wren <= 1'b0;

@(posedge i_sys_clk);

end else begin

i_wdata <= d;

i_wren <= 1'b1;

@(posedge i_sys_clk); // DUT 在此拍采样 wren=1 -> 写入

i_wren <= 1'b0;

@(posedge i_sys_clk); // 计数 NBA 已稳定

exp_mem[exp_tail] = d; exp_tail = exp_tail + 1; // 入队

$display("[%0t] WRITE data=%0d", $time, d);

end

end

endtask

integer errors;//错误计数,

integer exp_head; // 指向下一个待比较元素

integer got;

// ---- 任务:读一笔(空则跳过并报告)----进行一个验证,判断是否可读

// 时序:拉高 rden -> 等一拍(DUT 采样并读出) -> 拉低 -> 再等一拍 o_rdata 才有效

task read_one;

reg blocked;//是否可读的信号

begin

@(posedge i_sys_clk);

blocked = o_empty;

i_rden <= !o_empty;

@(posedge i_sys_clk); // DUT 在此拍采样 rden=1 -> 读出,o_rdata 更新(NBA)

i_rden <= 1'b0;

@(posedge i_sys_clk); // o_rdata 此刻已是本拍读出的数据

if(!blocked)

begin

got = o_rdata;

if(exp_tail > exp_head) begin

if(exp_mem[exp_head] !== got) begin

$display(" *** DATA ERROR: expected %0d got %0d", exp_mem[exp_head], got);

errors = errors + 1;//预期的数和读的到数不一样,错误计数加一

end else begin

$display(" data OK: %0d", got);

end

exp_head = exp_head + 1; // 出队

end

end else begin

$display("[%0t] READ blocked (empty)", $time);

end

end

endtask

// ---- 测试主流程 ----

initial begin

i_sys_clk = 0;

i_sys_rst_n = 0;

i_wren = 0; i_rden = 0; i_wdata = 0;

errors = 0;

exp_head = 0; // 必须显式初始化:integer 默认是 x,不是 0

exp_tail = 0;

#20;

i_sys_rst_n = 1;

// ---------- TEST1: 复位状态 ----------

$display("\n=== TEST1: reset state ===");

@(posedge i_sys_clk);

@(posedge i_sys_clk);

$display("empty=%b full=%b waempty=%b wafull=%b raempty=%b rafull=%b whempty=%b whfull=%b",

o_empty,o_full,o_waempty,o_wafull,o_raempty,o_rafull,o_whempty,o_whfull);

if(o_empty !== 1'b1 || o_full !== 1'b0) begin

$display(" *** TEST1 FAIL: reset state wrong"); errors = errors + 1;

end else $display(" TEST1 PASS");

// ---------- TEST2: 连续写满 ----------

$display("\n=== TEST2: write until full ===");

begin: wloop

integer k;

for(k = 0; k < DATA_DEPTH; k = k + 1) write_one(k);

end

@(posedge i_sys_clk); // 等计数稳定

$display("after fill: empty=%b full=%b wafull=%b rafull=%b", o_empty,o_full,o_wafull,o_rafull);

if(o_full !== 1'b1) begin

$display(" *** TEST2 FAIL: not full after %0d writes", DATA_DEPTH); errors = errors + 1;

end else $display(" TEST2 PASS (full reached, no overflow)");

// 再多写 1 笔,应被挡掉

write_one(8'd99);

if((exp_tail - exp_head) !== DATA_DEPTH) begin

$display(" *** TEST2 FAIL: overflow data accepted"); errors = errors + 1;

end

// ---------- TEST3: 连续读空 ----------

$display("\n=== TEST3: read until empty ===");

begin: rloop

integer k;

for(k = 0; k < DATA_DEPTH; k = k + 1) read_one();

end

@(posedge i_sys_clk); // 等计数稳定

$display("after drain: empty=%b full=%b", o_empty,o_full);

if(o_empty !== 1'b1) begin

$display(" *** TEST3 FAIL: not empty after %0d reads", DATA_DEPTH); errors = errors + 1;

end else $display(" TEST3 PASS (empty reached, data order correct)");

// 再多读 1 笔,应被挡掉

read_one();

// ---------- TEST4: 同时读写(稳态)----------

$display("\n=== TEST4: simultaneous read/write (steady state) ===");

// 先填到一半

begin: fill_half

integer k;

for(k = 0; k < 8; k = k + 1) write_one(k);

end

@(posedge i_sys_clk);

$display("after half-fill: whempty=%b whfull=%b (expect 0/1)", o_whempty, o_whfull);

if(o_whfull !== 1'b1 || o_whempty !== 1'b0) begin

$display(" *** TEST4 FAIL: half flags wrong at count=8"); errors = errors + 1;

end else $display(" half flags OK");

// 并发读写各 10 笔,净变化应为 0,count 维持 ~8

fork

begin: wsim

integer k;

for(k = 100; k < 110; k = k + 1) write_one(k);

end

begin: rsim

integer k;

for(k = 0; k < 10; k = k + 1) read_one();

end

join

@(posedge i_sys_clk);

$display("after sim r/w: empty=%b full=%b whempty=%b whfull=%b",

o_empty,o_full,o_whempty,o_whfull);

$display(" TEST4 PASS (simultaneous r/w handled, data order correct)");

// 把剩下的读空,回到 empty

begin: drain_rest

integer k;

for(k = 0; k < 8; k = k + 1) read_one();

end

@(posedge i_sys_clk);

// ---------- TEST5: almost 阈值翻转 ----------

$display("\n=== TEST5: almost_empty / almost_full threshold toggle ===");

// 当前 empty。写 1 笔 -> count=1 -> waempty(<=1)=1

write_one(8'd11);

@(posedge i_sys_clk);

$display("count=1: waempty=%b raempty=%b (expect 1/1)", o_waempty, o_raempty);

if(o_waempty !== 1'b1 || o_raempty !== 1'b1) begin

$display(" *** TEST5 FAIL: almost_empty not set at count=1"); errors = errors + 1;

end else $display(" almost_empty OK");

// 再写到 count=14 -> wafull(>=14)=1, rafull(>=13)=1

begin: fill14

integer k;

for(k = 0; k < 13; k = k + 1) write_one(8'd20 + k);

end

@(posedge i_sys_clk);

$display("count=14: wafull=%b rafull=%b (expect 1/1)", o_wafull, o_rafull);

if(o_wafull !== 1'b1 || o_rafull !== 1'b1) begin

$display(" *** TEST5 FAIL: almost_full not set at count=14"); errors = errors + 1;

end else $display(" almost_full OK");

// ---------- TEST6: 视见数量语义(保守性验证)----------

$display("\n=== TEST6: visible-count semantics (conservative flags) ===");

begin: clr6

integer k;

for(k = 0; k < DATA_DEPTH + 2; k = k + 1) read_one();

end

@(posedge i_sys_clk);

$display("drained: exact_empty=%b", o_empty);

// 写 1 笔后【立刻采样】:

// 实际=1(精确empty=0),读端视见量 RCNT=实际-上一拍写=0 -> o_rempty=1

// 即:读端视见空是保守的,不一定真空

write_one(8'd55);

$display("1 write : exact_empty=%b o_rempty=%b o_wempty=%b (expect 0 / 1 / 0)",

o_empty, o_rempty, o_wempty);

if(o_empty !== 1'b0 || o_rempty !== 1'b1) begin

$display(" *** TEST6 FAIL: read-side o_rempty not conservative"); errors = errors + 1;

end else $display(" read-side o_rempty conservative OK");

// 读 1 笔后【立刻采样】:

// 实际=0(精确empty=1),写端视见量 WCNT=实际+上一拍读=1 -> o_wempty=0

// 即:写端视见空不会误报;反过来它一旦报空一定真空

read_one();

$display("1 read : exact_empty=%b o_wempty=%b o_rempty=%b (expect 1 / 0 / 1)",

o_empty, o_wempty, o_rempty);

if(o_empty !== 1'b1 || o_wempty !== 1'b0) begin

$display(" *** TEST6 FAIL: write-side o_wempty falsely asserted"); errors = errors + 1;

end else $display(" write-side o_wempty safe OK");

// ---------- 收尾 ----------

#50;

$display("\n=== DONE: total errors = %0d ===", errors);

if(errors == 0) $display(">>> ALL TESTS PASSED");

else $display(">>> SOME TESTS FAILED");

$finish;

end

endmodule

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/11 1:52:22

WPF中的依赖属性

依赖属性在传统的c#开发中&#xff0c;我们使用的是CLR 公共语言运行时&#xff0c;属性。它的本质是封装了一个私有字段&#xff0c;每次new一个对象&#xff0c;无论这个属性用不用&#xff0c;系统都会在内存中为这个字段分配空间。而依赖属性 彻底颠覆了这个设置&#xff1…

作者头像 李华
网站建设 2026/10/11 1:52:00

行业观察 | 大模型落地竞速,高端数据标注成 AI 底层新赛场

8月19日&#xff0c;宇树科技登陆科创板&#xff0c;成为“A股人形机器人第一股”。这家AI赛道标杆企业在招股意向书中坦诚&#xff0c;算力支撑薄弱、高质量数据缺乏导致模型智能化程度不足&#xff0c;是制约整个人形机器人与AI行业发展的核心瓶颈。行业痛点高度趋同。大模型…

作者头像 李华
网站建设 2026/10/11 1:51:56

PostgreSQL 字符集双重转义击穿 磁盘 I/O

背景与问题表现 某线上交易核心库&#xff08;PostgreSQL 分区表架构&#xff09;突发磁盘 I/O 告警&#xff0c;多条业务查询出现严重堆积。监控大盘显示存储层读吞吐与延迟急剧攀升&#xff0c;部分核心查询执行时间超过 1.5 小时&#xff08;4,900s ~ 5,700s&#xff09;&am…

作者头像 李华
网站建设 2026/10/11 1:51:53

像素豆隐私政策

隐私政策 生效日期&#xff1a;2026年10月09日 更新日期&#xff1a;2026年10月10日 本应用「像素豆」&#xff08;以下简称「本 App」&#xff09;由开发者运营。我们重视您的隐私与数据安全。请您在使用本 App 前仔细阅读本政策。继续使用即表示您已了解并同意本政策。 一、我…

作者头像 李华
网站建设 2026/10/11 1:51:16

攻防世界 misc题GFSJ0249-【misc_pic_again】

题目描述&#xff1a;flag hctf{[a-zA-Z0-9~]*}附件是一张图片&#xff0c;如下&#xff1a;工具&#xff1a;Stegsolve&#xff08;https://pan.baidu.com/s/1eHzaiMyYVV4esSCd8VF9Mg?pwdlone 提取码: lone&#xff09;Imhex第一步在Stegsolve中打开图片&#xff08;步骤&am…

作者头像 李华