当前位置: 首页 > news >正文

Wishbone BFM 设计与实现:从手写总线到自动化自检

摘要
在 FPGA 验证中,总线接口(如 Wishbone)的握手时序最容易被忽视,也最容易导致“波形对但逻辑错”的隐性问题。本文将拆解一个我在实际项目中使用的 Wishbone Master BFM(总线功能模型),涵盖接口定义、任务封装、字节使能控制与自动化比对。核心目标:把繁琐的总线握手封装成“调用即走”的任务,让测试用例聚焦业务逻辑,让自检融入每一次读写。

一、Wishbone 总线协议“温故”——只讲必须用的那点事

Wishbone 标准(B4/B8)最核心的握手信号就这几根:

  • cyc:总线周期有效,Master 发起。

  • stb:选通信号,表示当前周期有数据传输。

  • we:写使能(1 为写,0 为读)。

  • adrdatselbe:地址、数据、字节使能。

  • ack:Slave 回传,表示读写完成。

标准单次读写的时序逻辑

  1. Mastercycstb拉高,同时给出adrweseldat(写时)。

  2. Slave收到后处理,完成后将ack拉高至少一个周期。

  3. Master检测到ack后,在下一个时钟沿拉低cycstb,结束传输。

经验坑ack可能不连续(多个周期后才有),也可能连续拉高(流水线)。BFM必须用wait(wb_ack == 1'b1)阻塞等待,而不是简单地在posedge后直接采样。

二、BFM 在验证环境中的“身位”——为什么要自己写?

我见过不少验证工程师在testbench里直接用forceassign强行驱动总线,结果导致:

  • 每次修改总线时序都要重新写一遍信号赋值;

  • 多字节访问(8/16/32 位)的be计算分散在多个地方;

  • 断言和比对混在驱动代码里,极难维护。

BFM 的核心价值在于封装。它把总线时序“锁”在任务内部,对外暴露的只有wb_wr(addr, data)wb_rd(addr)这种语义级接口。这样,测试用例的编写者完全不需要关心cyc何时拉高、ack何时回来,只需要关注“我要写什么、我要读什么、结果对不对”。

同时,BFM是自动化自检的第一道防线——因为所有的总线操作都必须经过同一套驱动代码,只要把这套代码做稳,后续的比对和断言就有了可信的基础。

三、Wishbone Master BFM 详细设计思路

3.1 接口定义:显式声明,不依赖外部信号

我的BFM使用wirereg显式声明所有总线信号,并把时钟设为wire输入,从外部驱动。这样BFM可以独立编译,也方便在多个testbench中复用。

  • wb_clk:由上层testbench产生,BFM内部只是采样和驱动。

  • wb_cyc/wb_stb/wb_weMaster输出,均为reg,初始为 0。

  • wb_adr/wb_wdat/wb_beMaster输出,位宽固定。

  • wb_rdat/wb_ackSlave输出,Master输入,为wire

这种“输入输出分离”的设计,让BFM天生支持与DUT直接连接,也支持在虚拟环境(如UVMdriver)中通过config_db传递。

3.2 任务封装:单次写、单次读、读比较

我设计了三个核心任务:

  • wb_wr:完成一次写操作,支持 8/16/32 位写入(通过wr_bat控制)。

  • wb_rd:完成一次读操作,返回读到的 32 位数据。

  • wb_rd_cmp:读回数据并与期望值比对,自动打印Pass/Fail

每个任务都遵循同样的时序结构:

  1. 等待时钟上升沿(@ (posedge wb_clk));

  2. 驱动信号并加入#1延迟(避免零延时竞争);

  3. 阻塞等待wb_ack == 1'b1

  4. 采样数据后,在下一个时钟沿撤销所有控制信号。

3.3 字节使能(BE)的工程处理

Wishbonesel信号(我这里用wb_be)决定了哪个字节有效。对于非对齐访问和 8/16 位操作,be的计算必须根据adr[1:0]灵活生成。

我的实现逻辑是:

  • 32 位:wb_be = 4'b1111

  • 16 位:根据wb_adr[1]判断是低 16 位(0011)还是高 16 位(1100

  • 8 位:根据wb_adr[1:0]的 4 种取值分别给0001001001001000

实践注意:这里的地址wb_adr我统一按字节地址输入(rd_adr_byte),这样在 8 位访问时,wb_adr[1:0]天然对应字节偏移,不需额外换算。这是很多新手容易搞混的地方。

四、WB_BFM 代码

// ================================================== // 1. 声明 BFM 内部交互的全局线网(严格定义位宽) // ================================================== wire wb_clk; reg wb_cyc; reg wb_stb; reg wb_we; reg [2:0] wb_cti; reg [31:0] wb_adr; reg [3:0] wb_be; reg [31:0] wb_wdat; wire [31:0] wb_rdat; wire wb_ack; // ================================================== // 2. 标准 Wishbone 总线任务(位宽全部显式声明) // ================================================== task wb_wr; input [1:0] wr_bat; // 32/16/8 位写 input [31:0] wr_adr; input [31:0] wr_dat; begin @ (posedge wb_clk); #1; wb_stb = 1'b1; wb_cyc = 1'b1; wb_we = 1'b1; wb_cti = 3'b000; wb_adr = wr_adr; wb_wdat = wr_dat; case(wr_bat) 2'b00: wb_be = 4'b1111; // 32位写 2'b01: wb_be = wb_adr[1] ? 4'b1100 : 4'b0011; // 16位写 2'b10: begin // 8位写 case(wb_adr[1:0]) 2'd0: wb_be = 4'b0001; 2'd1: wb_be = 4'b0010; 2'd2: wb_be = 4'b0100; 2'd3: wb_be = 4'b1000; endcase end endcase wait(wb_ack == 1'b1); @ (posedge wb_clk); #1; wb_stb = 1'b0; wb_cyc = 1'b0; wb_we = 1'b0; wb_cti = 3'b000; wb_adr = 32'b0; wb_wdat = 32'b0; wb_be = 4'b0; end endtask task wb_rd; input [1:0] rd_bat; input [31:0] rd_adr_byte; output [31:0] rdat_32; // 局部变量显式声明 reg [31:0] rdat_16; reg [31:0] rdat_8; begin @ (posedge wb_clk); #1; wb_stb = 1'b1; wb_cyc = 1'b1; wb_we = 1'b0; wb_cti = 3'b000; wb_adr = rd_adr_byte; case(rd_bat) 2'b00: wb_be = 4'b1111; // 32位读 2'b01: wb_be = wb_adr[1] ? 4'b1100 : 4'b0011; // 16位读 2'b10: begin // 8位读 case(wb_adr[1:0]) 2'd0: wb_be = 4'b0001; 2'd1: wb_be = 4'b0010; 2'd2: wb_be = 4'b0100; 2'd3: wb_be = 4'b1000; endcase end endcase wait(wb_ack == 1'b1); @ (posedge wb_clk); #1; case(rd_bat) 2'b00: rdat_32 = wb_rdat; 2'b01: rdat_32 = wb_adr[1] ? wb_rdat[31:16] : wb_rdat[15:0]; 2'b10: begin case(wb_adr[1:0]) 2'd0: rdat_8 = wb_rdat[7:0]; 2'd1: rdat_8 = wb_rdat[15:8]; 2'd2: rdat_8 = wb_rdat[23:16]; 2'd3: rdat_8 = wb_rdat[31:24]; endcase rdat_32 = rdat_8; end endcase wb_stb = 1'b0; wb_cyc = 1'b0; wb_we = 1'b0; wb_cti = 3'b000; wb_adr = 32'b0; wb_be = 4'b0; end endtask task wb_rd_cmp; input [31:0] rd_adr_byte; input [31:0] rd_cmp_data; reg [31:0] rdat_32; begin @ (posedge wb_clk); #1; wb_stb = 1'b1; wb_cyc = 1'b1; wb_we = 1'b0; wb_cti = 3'b000; wb_adr = rd_adr_byte; wb_be = 4'b1111; wait(wb_ack == 1'b1); @ (posedge wb_clk); #1; rdat_32 = wb_rdat; wb_stb = 1'b0; wb_cyc = 1'b0; wb_we = 1'b0; wb_cti = 3'b000; wb_adr = 32'b0; wb_be = 4'b0; if(rdat_32 != rd_cmp_data) begin $display("[ERROR] 地址 %h 读回不一致!期望 %h,实际得到 %h", rd_adr_byte, rd_cmp_data, rdat_32); end else begin $display("[INFO ] 地址 %h 读回验证通过: %h", rd_adr_byte, rdat_32); end end endtask

五、功能扩展与典型踩坑

5.1 突发传输的预留设计

上面代码中wb_cti固定为3'b000(经典循环)。如果要做突发传输(如连续读多个 32 位数据),只需改为:

  • 第一个周期wb_cti = 3'b001(常量地址突发)或3'b010(增量突发);

  • 中间周期保持wb_cti = 3'b001/010

  • 最后一个周期wb_cti = 3'b111(结束)。

我通常会在wb_rd中增加一个burst_len参数,循环执行内部逻辑,但要注意:突发传输下ack可能不是每个周期都拉高,而wait(wb_ack == 1'b1)只适用于单次传输。实战中我会单独封装一个wb_burst_rd任务,在每次ack后再驱动下一个地址。

5.2 等待周期的插入

有些总线测试需要验证Slaveack之前插入多个等待周期(stall状态)。我的BFM不需要额外修改,因为wait(wb_ack == 1'b1)会自然阻塞,无论等待几个周期。如果需要模拟 Master 主动插入等待(比如降低stb),则需在@ (posedge wb_clk)前加入repeat(N) @ (posedge wb_clk)来强制拉长控制信号。但实际工作中,我不建议在 Master BFM 里主动插等待,因为这是Slave的行为;Master应尽量快速驱动,让Slave全权控制时序。

六、测试用例中的实际调用

下面是在一个具体testbench中的调用示例,展示了如何用BFM完成自动化写读比对:

module tb_wishbone_example; // 例化 DUT 和 BFM 接口信号 wire wb_clk; // ... (省略时钟生成和 DUT 例化) initial begin // 初始化 BFM 控制信号 wb_cyc = 0; wb_stb = 0; wb_we = 0; wb_cti = 0; wb_adr = 0; wb_wdat = 0; wb_be = 0; // 等待复位释放 @ (posedge wb_clk); #100; // 1. 32 位写操作 wb_wr(2'b00, 32'h1000, 32'hA5A5A5A5); // 2. 32 位读并自动比对 wb_rd_cmp(32'h1000, 32'hA5A5A5A5); // 3. 16 位写(低 16 位) wb_wr(2'b01, 32'h1004, 32'h00001234); // 4. 读回低 16 位并比对 wb_rd_cmp(32'h1004, 32'h00001234); // 5. 8 位写(字节偏移 2) wb_wr(2'b10, 32'h1006, 32'h000000AA); // 6. 读回字节偏移 2 并比对(实际读 32 位,但只关注低 8 位) wb_rd_cmp(32'h1006, 32'h000000AA); $display("[DONE] 所有测试验证通过!"); $finish; end endmodule

经验之谈wb_rd_cmp任务里固定用了wb_be = 4'b1111来读全部 32 位。对于 8/16 位验证,我推荐读回完整 32 位后再在测试用例里做掩码比对,而不是在BFM里截断 —— 这样可以同时检查其他字节是否意外被修改,属于“过度验证”中的有效手段。


总结

Wishbone BFM的设计价值不在于“写得多复杂”,而在于“封装得有多干净”。这套BFM我用了近三年,从最初的单次读写扩展到现在的多粒度访问和自动化比对。它让我再也不用在每次仿真结束后手扒波形去核对寄存器值,而是直接在仿真日志里看到明确的[INFO][ERROR]

我的核心原则BFM是一次性投入,但换来的是所有测试用例的持续复用和自动化自检。写好BFM的那天,就是验证效率质变的那天。

http://www.jsqmd.com/news/1125442/

相关文章:

  • 什么叫Padding Oracle
  • 说说程序员、博客、论坛及个人专业相关知识的提高
  • 基于大数据Hadoop+Spark的汽车销售数据分析系统设计与实现任务书
  • Claude Code 封号与“隐藏标记“争议:一份基于公开资料的核验清单
  • 用 QClaw + SQL Server 搭建私有企业知识库——中小企业的“有边界记忆”方案
  • 基于STM32单片机智能窗帘窗户光敏定时遥控温湿度语音物联网设计12(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_
  • 大文件分片上传完整案例
  • 网页自动化实战指南:从零构建高效工作流
  • 苏州本地GEO获客标杆!环境工程企业AI全域收录破5.2万条
  • 【学生调研报告】网上银行安全架构与安全方案研究
  • 从零构建系统工具:先写验收脚本,再补漂亮交互
  • 无货源自动拍单发货软件靠谱吗?新手先看货源关联和规格匹配一件代发工具教程解析
  • 课堂教学PPT模板推荐哪家?这6个平台教师亲测可用
  • 来博客园的基本是写程序的,好像是废话,缩小点范围,来这里起嘛证明,大家都想学习进步,都是同道中的同道中人。兴趣,往高一点说叫理想,是我们共同的动力,从上一文中再次得到印证。
  • AI编程代理Codex:从安装配置到项目实战的完整指南
  • 基于SpringBoot智慧房屋租赁管理系统的设计与实现任务书
  • 【Aspose-CAD for Java】DWG转PDF实战:精准控制布局与图层,告别空白与错位
  • 基于SpringBoot前后端分离的宠物服务平台开发任务书
  • 2025 全国高联一试 A 卷
  • 五大神经网络核心原理与实战:从CNN到GAN的直观理解与代码实现
  • 从离线分析到实时对话:JoyAI-VL-Interaction如何重塑视频AI交互范式
  • 终极ComfyUI TensorRT插件指南:3-10倍AI绘画加速,释放你的RTX显卡潜能
  • 自动扩缩容:3 种策略的适用场景
  • REACTOS RtlGetVersion 函数实现分析
  • Oracle数据库
  • 终极指南:如何用AI让Monika与你自由对话 - MonikA.I模组完全教程
  • 解决Ant发送邮件显示HTML源码问题:MIME类型配置详解
  • 三菱FX3U PLC运动轴控制与伺服调试实战
  • 如何永久保存微信聊天记录:你的数字记忆完全指南
  • Claude 全面解析:从基础原理到实战应用指南