告别“变量”思维!深入硬件描述核心,搞懂“线”与“寄存器”的本质区别。
各位未来的芯片设计师,欢迎回到《Verilog十日谈》。
在Day 1,我们完成了从软件思维到硬件思维的革命性转变,并用一个简单的与门,跑通了设计->仿真的完整工业流程。现在,你已经知道你的代码不是在写程序,而是在绘制一张电路蓝图。
今天,我们要深入这张蓝图的“筋骨”所在。如果说Day 1我们搭建了骨架,那么今天就是要搞清连接骨架的“肌腱”与“神经”。
你是否曾对以下问题感到困惑?
wire 和 reg 到底有什么区别?为什么Testbench里驱动信号用 reg,而模块输出又定义成 wire?
我的代码明明语法没错,为什么仿真就是不出波形?
信号显示为红色'X'或高阻'Z'?
如何像搭积木一样,构建复杂的数字系统?
如果你的答案是“是”,那么今天的课程将为你拨开迷雾。理解 wire 和 reg,是写出正确、可综合Verilog代码的**第二道基石**。
一、 思维破壁:你的“变量”不是变量,是硬件连接!
请再次忘记C语言中的int, float!在Verilog中,我们描述的是**物理连接**和**数据存储**。
wire(线):*它代表的是一条**物理连接线**,用于连接器件的端口。它的值由**驱动源**决定,本身**不能存储任何值**。
核心特质: 它需要被持续驱动。如果没有驱动,它就是高阻态 z。如果被多个源驱动,它的值由驱动强度决定(通常会导致未知 x)。
一个比喻:
wire 就像一根电线,它本身不带电,电灯泡(接收端)亮不亮,完全取决于电线另一头的开关和电池(驱动源)的状态。
reg(寄存器):它的名字具有极大的误导性!它**不一定**会生成实际的硬件寄存器。它代表的是一个**数据存储单元**,能够在某个时刻被赋值,并保持这个值直到下一次赋值。
核心特质: 它可以在 always 和 initial 块中被赋值。
一个比喻:reg 更像一个带锁的盒子,你可以往里面放东西(赋值),它会一直保存着,直到你再次打开锁改变它。
黄金法则:
assign 语句的左侧必须是 wire 类型。
always 和 initial 块内被赋值的信号,必须定义为 reg 类型。模块的输入端口只能是 wire。模块的输出端口可以是 wire 或 reg,但取决于其驱动方式。
二、 实战一:用“结构描述法”构建一个异或门
在Day 1,我们用了行为描述 (assign y = a & b;) 。今天,我们用更底层的**结构描述**方式来构建一个异或门,这将完美展示 wire 的作用。
我们知道: y = a ^ b = (~a & b) | (a & ~b)
创建新项目
新建 xor_gate.v 文件,输入以下代码:
// 使用基本的与、或、非门运算符来构建一个异或门module xor_gate (input a,input b,output y);// 定义内部连接线wire a_not, b_not;wire and_out1, and_out2;// 使用运算符实现逻辑门功能// 非门:产生 a 的非assign a_not = ~a;// 非门:产生 b 的非assign b_not = ~b;// 与门:计算 ~a & bassign and_out1 = a_not & b;// 与门:计算 a & ~bassign and_out2 = a & b_not;// 或门:将两个与门的输出合并,得到最终结果 y = (~a & b) | (a & ~b)assign y = and_out1 | and_out2;endmodule
点击编译,并查看RTL图。
你会看到一个清晰的门级电路结构,而那些 wire 类型的信号 (a_not, b_not 等),正是连接这些门的**线**!这就是 wire 的本质。
三、 实战二:Testbench进阶与 reg 的深入理解
现在,为我们的异或门搭建测试平台。我们将通过这个Testbench,彻底理解 reg 在测试中的用法。
新建 tb_xor_gate.v 文件,输入以下代码:
`timescale 1ns/1nsmodule tb_xor_gate();// 1. 定义激励信号// 注意:它们要驱动DUT的输入,所以在always/initial块中赋值,必须定义为 reg!reg a, b;// 注意:它们要连接DUT的输出,所以定义为 wirewire y;// 2. 实例化被测设计xor_gate u_xor_gate (.a (a),.b (b),.y (y));// 3. 产生激励initial begin// 初始化a = 1'b0;b = 1'b0;#20;// 生成所有4种输入组合,观察输出a = 1'b0; b = 1'b1;#20;a = 1'b1; b = 1'b0;#20;a = 1'b1; b = 1'b1;#20;// 额外加一个case,确保波形完整a = 1'b0; b = 1'b0;#20;$stop;endendmodule
关键解释:
为什么 a 和 b 是 reg?因为在这个Testbench模块内部,是 initial 块在给a 和 b 赋值。它们是被Testbench**驱动**的,需要一个“数据存储”的抽象来描述它们值的变化。
为什么 y 是 wire?因为它仅仅是将被测模块 u_xor_gate 的输出端口连接到了这里,它是一条**连接线**。
四、 联合仿真:在波形中验证逻辑
- 按照Day 1的流程,设置好仿真,启动ModelSim。运行仿真,查看波形。
请仔细观察:
当 a 和b 相同时,y 为 0。当 a 和 b 不同时,y 为 1。同时,你还可以在波形图中看到内部连接线 and_out1 和 and_out2 的变化,这能帮助你更深层次地理解电路的运作流程。**这就是仿真的强大之处——让你能窥探芯片内部的任何一根“线”!
五、 模块的层次化设计:工程师的必备技能
现在,我们已经有两个“芯片”了:and_gate(Day1设计) 和 xor_gate。如何将它们组合成一个更复杂的系统?答案就是:模块实例化。
让我们设计一个 logic_unit,它包含一个与门和一个异或门。
新建 logic_unit.v 文件:
module logic_unit (input [1:0] sel, // 2位选择信号input x,input y,output out);wire and_out, xor_out; // 内部连接线// 实例化我们已经设计好的与门模块and_gate u_and (.a (x),.b (y),.y (and_out) // 输出连接到内部线 and_out);// 实例化我们刚刚设计的异或门模块xor_gate u_xor (.a (x),.b (y),.y (xor_out) // 输出连接到内部线 xor_out);// 根据选择信号 sel,选择输出哪一个结果// 这是一个2选1选择器,我们用条件运算符实现assign out = (sel == 2'b00) ? and_out :(sel == 2'b01) ? xor_out :1'b0; // 默认输出0endmodule
为
logic_unit 编写Testbench并进行仿真。这将是一个绝佳的练习,让你真正体验如何像搭积木一样构建数字系统。
【今日源码下载】
本文的完整源码(xor_gate.v, tb_xor_gate.v, logic_unit.v)已准备好。
点击文末链接下载
【明日预告】
今天,我们彻底剖析了Verilog的“筋骨”——wire和reg,并迈入了模块化设计的大门。你已经具备了构建复杂系统的基本能力。
但是,组合逻辑的世界里暗藏玄机!一个巨大的陷阱正等着90%的初学者。
**Day 3:组合逻辑的双刃剑:assign与always@(*)与Latch陷阱**
我们将:深入讲解描述组合逻辑的另一种强大方式:always@(*) 块。揭示一个由if或case语句不完整而引发的**锁存器**大坑!总结出安全编写组合逻辑的“军规”。
2134