)
让我们从以下几个问题中 引入到绿皮书第四章interface - clocking block - class的学习如果没有 Interface当我们要验证一个有 100 根信号线的 AXI 总线设计时测试平台的代码写起来会遇到什么痛点1. 在verilog中如果不用interface就必须在testbench顶层把这100多根线逐一声明为wire 或reg然后一个个接到DUT和Driver/Monitor上极其繁琐且易出错wire [3:0] axi_awid; wire [31:0] axi_awaddr; wire [7:0] axi_awlen; wire [2:0] axi_awsize; // ... 90 多根信号线的声明 ... my_axi_dut u_dut( .clk (clk), // //连线 );如果设计人员增加了一个用户自定义信号就需要逐层修改顶层声明、端口列表和例化参数。2.Driver和Monitor是纯软件的class如果没有interface要把100根线传进Driver里面去驱动波形只能在Driver类里面声明100个引脚或者用指针把100根线一个个传递进去class axi_driver; // Driver 内部要存 100 个信号指针 virtual task drive_write_address( input [3:0] id, input [31:0] addr, input [7:0] len, // ... 必须把 100 个信号端口全部作为参数传进来 ... ); // ... endtask endclass面向对象OOP失效 class类核心优势是封装和抽象没有interfaceDriver无法封装协议直接退化成一段冗长的函数库。3.竞争冒险 clocking Block 和Modport丢失没有interface 无法使用Clocking Block 和Modport为了避免竞争冒险 只能在Driver里大量写#1#2来延时避开上升沿采样竞争极易出现某个信号延时没对齐导致采样到旧值或产生亚稳态。没有modport 显式约束方向当误写后就会导致波形出现信号冲突、多驱动很难debug。有了interface100根信号在代码只需要一行定义//打包 100根线打包在一个容器里 axi_if main_axi_if(clk,rstn); //内部信号 //顶层连接 一行 把捆好的线接到DUT上 my_axi_dut u_dut( .clk (clk), .rstn (rstn), .axi_bus(main_axi_if) ); //传入软件类Driver 只需接一个指针掌控100根线 virtual axi_if v_if main_axi_if;在 Verilog 学习和 TB 编写经历中会有“仿真波形明明在上升沿变了但采样出来却还是旧值或者慢了一拍”的时序困惑这就是验证中的竞争冒险问题。// 假想 Verilog 测试平台 always (posedge clk) begin data 8hA5; // RTL 内部在上升沿改变 data end always (posedge clk) begin $display(data %h, data); // TB 也在上升沿读取 data end仿真器处理同一个时钟上升沿posedge clk时硬件逻辑更新和测试平台采样处于同一个事件队列时间点。如果先进行RTL赋值那采样的就是新值。如果先进行TB读取data那采样的就是旧值。SV给出的解决方案就是上面interface中的Clocking Block时钟块clocking block通常定义在interface内部他的核心机制是建立时间采样保持时间驱动。原理①输入采样input skewskewclocking block会在时钟上升沿到来之前的极其微小的时间点 去提前看并锁存接口信号。所以即时RTL在上升沿瞬间改变信号CB采样到的也是上升沿发生前那一刻的稳定旧值完美模拟真实硬件触发器的setup时间行为消除采样竞争。②输出驱动当通过CB驱动信号时 cb.data8hffCB会在时钟上升沿到来之后的微小时间点才把信号传给DUT。 保证驱动信号不会和时钟沿死磕完美模拟真实硬件的Hold时间和propagation delay传输延迟。//interface中定义clocking block interface my_if (input bit clk,input bit rstn); logic [7:0] data; logic valid; //定义时钟块 clocking cb (posedge clk); input data; //对于TB来说data是需要采样 output valid; //对于tb来说valid是驱动 endclocking endinterface应用在Driver或Monitor中只要加上.cb后缀// monitor 中不同担心竞争采样到的必定是时钟沿前的稳定数据 (v_if.cb); $display(at time %0t,data sampled%0h,$time,v_if.cb.data); //driver 中 驱动信号会自动延迟推出产生波形 (v_if.cb); v_if.cb.valid 1b1;如果我们要验证一个路由器Router每次发包都要包含src_addr源地址、dst_addr目的地址、payload数据载荷以及计算包长的函数get_length()。如果你用 Verilog 的思路和用 SV 的class思路你觉得在代码组织上会有什么最本质的区别1 verilog 的思路是用静态的硬件电路思维去构造测试SV的class 使用 “动态的软件与数据流思维去包装激励与环境”verilog在verilog中所有变量都是静态物理存在的 如果要发一个包需要定义一堆独立的reg变量再写一个task去驱动verilog 数据是数据 task是task reg [31:0] src_addr; reg [31:0] dst_addr; reg..... task send_packet; input [31:0] s,d; begin //驱动硬件型号 end数据和处理数据的逻辑是分离的如果10个数据包同时在总线上传输无法很好维护10个独立的包。SV classSV的class把数据和针对这些数据的操作打包在一起形成一个自给自足的数据模板//SV Class 把包的属性和计算包的方法打包在一起 class Packet; bit [31:0] src_addr; bit [31:0] dst_addr; bit [7:0] payload[$]; //针对这个包的方法直接写在Class内部 function int get_length(); return payload.size() 8; endfunction endclass2 内存管理静态物理站位和动态生命周期verilog 中每一个变量在仿真第0时刻起就在内存占用了固定位置无法在仿真运行过程中动态创造和销毁一个新的数据包。SV class动态创建class本身是一章蓝图不占用具体的内存空间。只有当使用new的时刻仿真器才会在内存里动态分配一个真正的对象。p1 new();p2 new();包处理完后SV会自动释放不再使用的内存3硬编码和继承与多态原本的router只支持标准包项目升级需要支持一种带优先级的高级包。verilog修改task加大量if elsecopy代码新写一个priority_packet,SV class (extends)class PriorityPacket externds Packet; //Packet 是“父亲”父类 prioritypacket是子类继承了父类的所有方法 变量 bit [2:0] priority_level; endclass只需要在新建的子类里 新增特有的属性在现代大规模芯片验证中 Testbench本质上就是一个软件系统。要模拟成千上万、各种各样带随机约束的数据包Transaction在设计DUT里穿梭。用 Class 的思路我们才能把激励抽象成一个个动态的“对象”从而搭建出高重用性、高扩展性的企业级验证环境。