掌握 Verilog 用户定义基本单元:高级逻辑建模技术
标准 Verilog 基本单元(如 nand、not)并不总能轻松或充分表示复杂逻辑。可定义新的基本单元——用户定义基本单元(UDP)来建模组合或时序逻辑。
所有 UDP 均有且仅有一个输出,取值只能是 0、1 或 X,不支持 Z。任何值为 Z 的输入将被视为 X。
Verilog UDP 符号
UDP 可与 module 定义同级编写,但不能置于 module 和 endmodule 之间。可有多个输入端口但仅一个输出端口,不支持双向端口。所有端口信号必须为标量(1 位宽)。
硬件行为用基本单元状态表描述,在 table 和 endtable 间列出输入组合及对应输出。输入输出信号值用以下符号表示:
| 符号 | 说明 |
|---|---|
| 0 | 逻辑 0 |
| 1 | 逻辑 1 |
| x | 未知,可为 0 或 1。可用作输入/输出或时序 UDP 当前状态 |
| ? | 0、1 或 x。不可作为 UDP 输出 |
| - | 无变化,仅允许用于 UDP 输出 |
| ab | 从 a 到 b 的变化,a、b 为 0、1 或 x |
| * | 等同于 ??,表示输入任意变化 |
| r | 等同于 01,输入上升沿 |
| f | 等同于 10,输入下降沿 |
| p | 潜在正边沿:0→1、0→x 或 x→1 |
| n | 潜在负边沿:1→0、x→0、1→x |
组合 UDP 示例
// 输出必须为端口列表首个信号 primitive mux (out, sel, a, b); output out; input sel, a, b; table // sel a b out 0 1 ? : 1; 0 0 ? : 0; 1 ? 0 : 0; 1 ? 1 : 1; x 0 0 : 0; x 1 1 : 1; endtable endprimitive
? 表示该信号可以是 0、1 或 x,不影响最终输出决定。
下面的测试平台实例化 UDP 并施加随机激励。
module tb; reg sel, a, b; reg [2:0] dly; wire out; integer i; // 实例化 UDP - 注意 UDP 不能使用端口名连接 mux u_mux ( out, sel, a, b); initial begin a <= 0; b <= 0; $monitor("[T=%0t] a=%0b b=%0b sel=%0b out=%0b", $time, a, b, sel, out); // 在不同随机延迟后驱动 a、b、sel for (i = 0; i < 10; i = i + 1) begin dly = $random; #(dly) a <= $random; dly = $random; #(dly) b <= $random; dly = $random; #(dly) sel <= $random; end end endmodule
仿真日志
xcelium> run [T=0] a=0 b=0 sel=x out=0 [T=4] a=1 b=0 sel=x out=x [T=5] a=1 b=1 sel=x out=1 [T=10] a=1 b=1 sel=1 out=1 [T=15] a=0 b=1 sel=1 out=1 [T=28] a=0 b=0 sel=1 out=0 [T=33] a=0 b=0 sel=0 out=0 [T=38] a=1 b=0 sel=0 out=1 [T=40] a=1 b=1 sel=0 out=1 [T=51] a=1 b=1 sel=1 out=1 [T=54] a=0 b=0 sel=1 out=0 [T=62] a=1 b=0 sel=1 out=0 [T=67] a=1 b=1 sel=1 out=1 [T=72] a=0 b=1 sel=1 out=1 [T=80] a=0 b=1 sel=0 out=0 [T=84] a=0 b=0 sel=0 out=0 [T=85] a=1 b=0 sel=0 out=1 xmsim: *W,RNQUIE: Simulation is complete.
时序 UDP 示例
时序逻辑可为电平敏感或边沿敏感,故有两类时序 UDP。输出端口在 UDP 定义中须声明为 reg 类型,可选在 initial 语句中初始化。
时序 UDP 在输入和输出字段间增加一个由 : 分隔的字段,表示当前状态。
电平敏感 UDP
primitive d_latch (q, clk, d); output q; input clk, d; reg q; table // clk d q q+ 1 1 : ? : 1; 1 0 : ? : 0; 0 ? : ? : -; endtable endprimitive
表中最后一行的连字符 - 表示 q+ 无变化。
module tb; reg clk, d; reg [1:0] dly; wire q; integer i; d_latch u_latch (q, clk, d); always #10 clk = ~clk; initial begin clk = 0; $monitor ("[T=%0t] clk=%0b d=%0b q=%0b", $time, clk, d, q); #10; // 观察 X 的效果 for (i = 0; i < 50; i = i+1) begin dly = $random; #(dly) d <= $random; end #20 $finish; end endmodule
仿真日志
xcelium> run [T=0] clk=0 d=x q=x [T=10] clk=1 d=1 q=1 [T=13] clk=1 d=0 q=0 [T=14] clk=1 d=1 q=1 [T=17] clk=1 d=0 q=0 [T=20] clk=0 d=1 q=0 [T=28] clk=0 d=0 q=0 [T=30] clk=1 d=1 q=1 [T=38] clk=1 d=0 q=0 [T=39] clk=1 d=1 q=1 [T=40] clk=0 d=1 q=1 [T=42] clk=0 d=0 q=1 [T=47] clk=0 d=1 q=1 [T=50] clk=1 d=0 q=0 [T=55] clk=1 d=1 q=1 [T=59] clk=1 d=0 q=0 [T=60] clk=0 d=0 q=0 [T=61] clk=0 d=1 q=0 [T=64] clk=0 d=0 q=0 [T=67] clk=0 d=1 q=0 [T=70] clk=1 d=0 q=0 [T=73] clk=1 d=1 q=1 [T=74] clk=1 d=0 q=0 [T=77] clk=1 d=1 q=1 [T=79] clk=1 d=0 q=0 [T=80] clk=0 d=0 q=0 [T=84] clk=0 d=1 q=0 [T=86] clk=0 d=0 q=0 [T=87] clk=0 d=1 q=0 [T=90] clk=1 d=1 q=1 [T=91] clk=1 d=0 q=0 [T=100] clk=0 d=0 q=0 [T=110] clk=1 d=0 q=0 Simulation complete via $finish(1) at time 111 NS + 0
边沿敏感 UDP
下面以 D 触发器为例建模边沿敏感 UDP。注意时钟上升沿用 01 或 0? 表示。
primitive d_flop (q, clk, d); output q; input clk, d; reg q; table // clk d q q+ // 在 clk 上升沿 (01) 获取输出 0 : ? : 0; (01) 1 : ? : 1; (0?) 1 : 1 : 1; (0?) 0 : 0 : 0; // 忽略 clk 下降沿 (?0) ? : ? : -; // 忽略稳态 clk 下的数据变化 ? (??): ? : -; endtable endprimitive
测试平台中实例化 UDP,在随机时钟周期后驱动随机 d 输入。
module tb; reg clk, d; reg [1:0] dly; wire q; integer i; d_flop u_flop (q, clk, d); always #10 clk = ~clk; initial begin clk = 0; $monitor ("[T=%0t] clk=%0b d=%0b q=%0b", $time, clk, d, q); #10; for (i = 0; i < 20; i = i+1) begin dly = $random; repeat(dly) @(posedge clk); d <= $random; end #20 $finish; end endmodule
从波形可见输出 q 延迟 1 个时钟周期跟随输入 d,符合 D 触发器预期行为。
仿真日志
xcelium> run [T=0] clk=0 d=x q=x [T=10] clk=1 d=1 q=x [T=20] clk=0 d=1 q=x [T=30] clk=1 d=1 q=1 [T=40] clk=0 d=1 q=1 [T=50] clk=1 d=1 q=1 [T=60] clk=0 d=1 q=1 [T=70] clk=1 d=0 q=1 [T=80] clk=0 d=0 q=1 [T=90] clk=1 d=1 q=0 [T=100] clk=0 d=1 q=0 [T=110] clk=1 d=1 q=1 [T=120] clk=0 d=1 q=1 [T=130] clk=1 d=1 q=1 [T=140] clk=0 d=1 q=1 [T=150] clk=1 d=0 q=1 [T=160] clk=0 d=0 q=1 [T=170] clk=1 d=0 q=0 [T=180] clk=0 d=0 q=0 [T=190] clk=1 d=0 q=0 [T=200] clk=0 d=0 q=0 [T=210] clk=1 d=1 q=0 [T=220] clk=0 d=1 q=0 [T=230] clk=1 d=1 q=1 [T=240] clk=0 d=1 q=1 [T=250] clk=1 d=1 q=1 [T=260] clk=0 d=1 q=1 [T=270] clk=1 d=1 q=1 [T=280] clk=0 d=1 q=1 [T=290] clk=1 d=1 q=1 [T=300] clk=0 d=1 q=1 [T=310] clk=1 d=1 q=1 [T=320] clk=0 d=1 q=1 [T=330] clk=1 d=1 q=1 [T=340] clk=0 d=1 q=1 [T=350] clk=1 d=1 q=1 [T=360] clk=0 d=1 q=1 [T=370] clk=1 d=0 q=1 [T=380] clk=0 d=0 q=1 [T=390] clk=1 d=0 q=0 [T=400] clk=0 d=0 q=0 [T=410] clk=1 d=1 q=0 [T=420] clk=0 d=1 q=0 [T=430] clk=1 d=1 q=1 [T=440] clk=0 d=1 q=1 [T=450] clk=1 d=1 q=1 [T=460] clk=0 d=1 q=1 [T=470] clk=1 d=1 q=1 [T=480] clk=0 d=1 q=1 Simulation complete via $finish(1) at time 490 NS + 0
Verilog
- Verilog 入门:数字电路设计完整教程
- 掌握 Verilog 拼接操作:实用指南
- 深入理解 Verilog 赋值语句:类型、语法与最佳实践
- Verilog阻塞与非阻塞赋值:关键区别详解
- 掌握Verilog函数:高效RTL设计的复用利器
- 理解Verilog任务:函数vs任务、语法与用法全解析
- Verilog 时钟发生器设计与实现指南
- 掌握 Verilog 数学函数:高效硬件设计的利器
- 掌握 Verilog $timeformat:精度控制与显示格式化
- 理解与打印 Verilog 任意作用域下的 Timescale
- 掌握 Verilog 文件 I/O:打开、读取、写入和关闭
- Verilog 入门指南:Hello World 示例