4.【Verilog】Verilog UDP 基础知识
第一步分析与整理Verilog UDP 基础知识1. UDP 概念与分类UDPUser Defined Primitive用户自定义原语是 Verilog 提供的一种扩展机制允许用户像使用内置门and、or 等一样调用自己设计的基本逻辑单元。限制UDP 内部不能调用其他 module 或 primitive即不能嵌套调用方式与门级原语相同例化。两种类型组合逻辑 UDP输出仅由当前输入决定无状态。时序逻辑 UDP输出由当前输入和内部状态寄存器共同决定。2. UDP 定义格式primitive UDP_name ( output_name, list_of_inputs) ; // 端口列表输出必须第一个 output_declaration ; // 输出端口声明output list_of_input_declaration ; // 输入端口声明input [reg_declaration ;] // 仅时序逻辑需要声明输出为 reg [initial_statement ;] // 可选时序逻辑中初始化 reg 输出 table list_of_table_entries ; // 真值表/状态表 endtable endprimitive3. 端口声明规则重点规则说明输入端口只能标量1 位可以有多个输出端口唯一且必须是第一个标量1 位方向仅支持 input / output不支持 inout时序逻辑输出必须声明为reg类型用于保存状态端口顺序例化时按定义顺序连接4. 初始化initial仅用于时序逻辑 UDP为输出reg赋初值0/1/x。可选语句如果不写初始值为x。5. 状态表table … endtable核心部分定义输入到输出的映射关系类似真值表。表项取值0、1、x注意不支持z任何传入的z都会被当作x处理。组合逻辑表格式input1 input2 ... : output ;时序逻辑表格式input1 input2 ... : current_state : next_state ;后面会细讲第二步费曼教学法 – 通俗讲解 UDP作为验证工程师。今天的主题是UDP一个 Verilog 中“古老但有趣”的特性。我将用厨房做菜的比喻帮你彻底搞懂它。一、UDP 是什么为什么要有它1.1 比喻自带调料 vs 自制调料内置门and/or/not就像超市买的成品调料方便、标准、够用。UDP就像你自己研磨的秘制调料可以做出内置门无法直接实现的特殊逻辑比如一个 JK 触发器、一个多路选择器甚至一个带异步复位的锁存器。1.2 为什么你现在很少见到 UDP大部分复杂逻辑用always块写可综合且易读。UDP 限制多单输出、标量、无 inout、不能处理z。但它在某些场景仍有价值仿真性能UDP 被仿真器优化成高效的查找表比always快。模型加密提供 UDP 黑盒只露出表保护 IP。教学理解状态机的最简形式。二、UDP 的骨架一张“魔法卡片”2.1 定义格式解剖primitive 我的逻辑 ( 输出, 输入1, 输入2, ... ); output 输出; input 输入1, 输入2; // 时序逻辑才需要下面两行 reg 输出; initial 输出 0; table // 输入组合 : 输出 0 0 : 1; 0 1 : 0; 1 0 : 0; 1 1 : 1; endtable endprimitive调用它就像调用and门我的逻辑 u1 (Y, A, B);2.2 核心规则记忆口诀输出排第一唯一且单比特像班长一个班只有一个输入可以是多个但每人只能带 0/1/x 三顶帽子不许戴高阻z戴了也会被当成x时序逻辑要寄存初始化不能忘表格是灵魂逐行写映射三、手把手写两个完整的 UDP 例子例1组合逻辑 UDP – 一个“三输入多数表决器”功能三个输入中至少两个为 1 时输出 1否则 0。primitive majority3 (Z, A, B, C); output Z; input A, B, C; table // A B C : Z 0 0 0 : 0; 0 0 1 : 0; 0 1 0 : 0; 0 1 1 : 1; 1 0 0 : 0; 1 0 1 : 1; 1 1 0 : 1; 1 1 1 : 1; // 如果输入出现 x为了安全输出 x可省略默认 x 输出 x ? ? ? : x; // ? 表示可以是 0/1/x 中任意值这条作为兜底 endtable endprimitive关注点状态表完整覆盖 2^3 8 种组合且给出了 x 处理。如果某组合缺失仿真时出现该组合会输出x不确定容易误报。建议写全或加默认行。测试调用wire out; majority3 m1 (out, sig1, sig2, sig3);例2时序逻辑 UDP – 一个“下降沿触发的 D 触发器”功能时钟下降沿时采样 D输出 Q其他时间 Q 保持。primitive dff_edge (Q, CLK, D); output Q; input CLK, D; reg Q; // 时序逻辑输出必须是 reg initial Q 0; // 初始状态为 0 table // CLK D : 当前 Q : 下一 Q // 下降沿触发 (01) 0 : ? : 0 ; // 01 表示从 0 到 1 的跳变注意下降沿是 1-0用 (10) 表示 // 纠正下降沿应使用 (10) (10) 0 : ? : 0 ; (10) 1 : ? : 1 ; // 时钟稳定时输出保持不变 0 (??) : ? : - ; // - 表示不变 1 (??) : ? : - ; // 如果时钟跳变是 x则输出 x (?x) ? : ? : x ; (x?) ? : ? : x ; // 忽略 D 在边沿时的变化由上述三行已经覆盖 endtable endprimitive时序 UDP 表项格式输入1 输入2 : 当前状态 : 下一状态(10)表示从 1 到 0 的变化下降沿(01)表示上升沿(??)表示任意电平无跳变-表示输出保持不变?表示可以是 0/1/x用于输入*表示同前一行相同可简化这个例子复杂工作中一般不会手写但理解它能让你看懂仿真器自带的 UDP 模型。四、工作中如何学习与应用 – 验证工程师的实战建议4.1 学习 UDP 的正确姿势由浅入深步骤做什么目的1写一个组合逻辑 UDP如 2 输入与门、或门掌握表格语法感受与普通 module 的区别2写一个1 位全加器 UDP熟悉多输入表格编写3尝试用 UDP 实现JK 触发器理解时序表及边沿表示法4仿真并故意制造输入z观察被当成x的行为强化边界条件认知4.2 应用场景 – 真正常用的是“仿真模型”模拟一个外部芯片的行为比如一个串行 ADC其输出逻辑复杂但可以用 UDP 快速建模。加密 IP 核供应商可能会提供 UDP 形式的 netlist避免泄漏 RTL。加速仿真某些特定逻辑如 CRC 生成用 UDP 表比always快数十倍。4.3 注意事项教训分享端口顺序极易错例化时第一个信号必须连到 UDP 的输出端口。我见过有人写成my_udp (A, B, Y)导致逻辑完全反了。不要指望 UDP 处理高阻如果你用tran连到 UDP 输入z会被转为x可能掩盖总线竞争问题。组合 UDP 的表必须完整缺项 输出x导致仿真不匹配。可以用? ? : x兜底但要确认是否合理。时序 UDP 的初始化没有initial时输出为x可能导致整个状态机锁死在x。仿真前检查。4.4 一个真实工作中的例子场景你需要验证一个 MCU 的 IO 口配置寄存器该寄存器控制一个简单的“去抖动”逻辑。去抖动逻辑可以用 UDP 描述方便在测试平台中例化。// 去抖动 UDP输入信号连续两拍为 1 才输出 1否则输出 0 primitive debouncer (Q, CLK, D); output Q; input CLK, D; reg Q; initial Q 0; table // CLK D : current : next (10) 0 : ? : 0 ; (10) 1 : ? : 1 ; (10) * : ? : - ; // * 表示与上一行 D 相同即保持 0 ? : ? : - ; 1 ? : ? : - ; endtable endprimitive这样你在 testbench 中可以直接debouncer u_db (db_out, clk, noisy_in);比写一个always (posedge clk) …更直观且仿真速度快。五、总结学了 UDP我能吹什么理解深度你知道了 Verilog 最底层的建模方法之一明白仿真器如何用查表处理逻辑。实用价值遇到老代码或加密 IP 时能看懂 UDP 模块需要极简模型时可以自己写。面试加分被问到“如何实现一个 JK 触发器”时除了 RTL还能说出 UDP 方法展现知识广度。最终建议不要沉迷 UDP把它当作 Verilog 世界里的“化石文物”去了解即可。工作中 99% 的时间你都会用always和标准单元库。但一旦遇到那 1%你就能从容应对。还记得开头厨房调料比喻吗UDP 就是你的秘制调料做好后能反复用但大多数人还是直接用超市货。知道怎么磨就行不用天天磨。