Verilog Note
Verilog Note
端口
input
output
inout
inout必须是wire(or other net types),通常通过assign赋值。设为高阻
Z时,可以读取端口的输入;设为其他值时,可以实现输出(其本身的值也变为设定的值)
功能描述方法
- 结构描述:and/or/nor ...
- 数据流描述:assign
- 行为描述:always/initial
运算符
| 类别 | 运算符 | 是否可综合 | 硬件本质 |
|---|---|---|---|
| 算术运算符 | +, -, * | ✅ 是 | 加法器、减法器、乘法器 |
/, % | ⚠️ 限制 | 极度消耗资源,通常只在操作数为 $2^n$ 时被优化为移位 | |
** (幂运算) | ❌ 否 | 仅在操作数为常数时,部分工具支持预计算 | |
| 逻辑运算符 | !, &&, ` | ` | |
| 按位运算符 | ~, &, ` | , ^, ~^` | ✅ 是 |
| 关系运算符 | >, <, >=, <= | ✅ 是 | 比较器 |
| 等值运算符 | ==, != | ✅ 是 | 比较器 |
===, !== | ❌ 否 | 全等/不全等,包含对 X 和 Z 的比较,硬件无法实现 | |
| 移位运算符 | <<, >>, <<<, >>> | ✅ 是 | 连线偏移(常数移位)或桶形移位器(变量移位) |
| 缩减运算符 | &, ~&, ` | , ~ | , ^` |
| 条件运算符 | ? : | ✅ 是 | 多路选择器 (MUX) |
| 拼接运算符 | { }, {n{ }} | ✅ 是 | 纯连线,不消耗逻辑资源 |
控制语句
| 语句 | 推荐用途 | 综合风险 |
|---|---|---|
if-else | 实现带优先级的逻辑(如:判断 A 还是 B 优先) | 组合逻辑缺失 else 会产生 Latch |
case | 实现指令译码、状态机跳转 | 缺失 default 会产生 Latch |
for | 实现位反转、大规模重复的组合逻辑连线 | 循环次数太大或包含复杂逻辑会使面积爆炸 |
while/repeat | 仅限 Testbench,用于产生波形 | 绝大多数情况不可综合 |
避坑指南:
在写 always @(posedge clk) 的时序逻辑时,if 后面不写 else 是安全的(会综合成带使能端的触发器)。但在写 always @(*) 的组合逻辑时,if 没写 else 或者 case 没写 default 是灾难性的。
always 块的敏感列表
always @(*):综合工具会自动推断出所有输入信号,适用于组合逻辑。always @(posedge clk):适用于时序逻辑,触发条件为时钟上升沿。always @(posedge clk or negedge rst):适用于时序逻辑带异步复位,触发条件为时钟上升沿或复位信号的下降沿。(低电平复位)
Tips
! 敏感列表(@ 后面的括号)里的所有信号,要么全都带边沿关键字(posedge/negedge),要么全都不带(用于组合逻辑 always @(*))。混用会导致综合工具无法正确推断电路行为,会综合失败。
! 禁止多处给同一个信号赋值,否则会产生多驱动冲突。
模块的参数和端口
最新的参数和端口定义方式:
module adder #(
parameter WIDTH = 8 // 定义一个可配置的位宽
)(
input [WIDTH-1:0] a, // 位宽由参数决定
input [WIDTH-1:0] b,
output [WIDTH-1:0] sum
);
assign sum = a + b;
endmodule旧版的参数和端口定义方式:
module adder(a, b, sum);
parameter WIDTH = 8; // 定义一个可配置的位宽
input [WIDTH-1:0] a; // 位宽由参数决定
input [WIDTH-1:0] b;
output [WIDTH-1:0] sum;
assign sum = a + b;
endmodule调用:
adder #(.WIDTH(16)) my_adder (
.a(input_a),
.b(input_b),
.sum(output_sum)
);当你调用自己写的模块或者库里的 IP 核时,必须给它起个“实例名”,调用原语(Verilog 内置模块,如 and, or, not 等)时不需要实例名。
参数传递可以用位置映射或者命名映射,推荐使用命名映射以提高代码可读性。
常数
- 二进制:
4'b1010(4位二进制数,值为10) - 八进制:
8'o12(8位八进制数,值为10) - 十进制:
16'd255(16位十进制数,值为255) - 十六进制:
32'hDEADBEEF(32位十六进制数,值为3735928559)
阻塞赋值(=)和非阻塞赋值(<=)
阻塞赋值 (=) —— 映射为“连线”
阻塞赋值在执行时,下一条语句必须等这一条执行完才能开始,具有明显的顺序性。
硬件本质: 串行的组合逻辑门。
非阻塞赋值 (<=) —— 映射为“寄存器”
在一个时序块中,所有的非阻塞赋值会先计算右边的值,等块结束时,再统一更新到左边的变量。
硬件本质: D 触发器 (D-Flip Flop)。
仿真
X 具有传染性:X + 1 还是 X,~X 还是 X。仿真时clk等信号要记得初始化,否则会始终是 X,导致仿真结果全是 X。
时间设置
``timescale 1ns / 1ps` 时间尺度/精度
always #5 clk = ~clk; 模拟板载的 100MHz 时钟。(5ns 反转一次,T=10ns,f=100MHz)
or
initial begin
// 强大之处:开始前可以设置延时,进行其他操作
forever #5 clk = ~clk;
endWarning
forever 和 initial 都是纯仿真语法,不能综合
系统任务 $
$display("At time %t, position = %d", $time, position); // 打印并换行
$write // 打印不换行
initial $monitor("SW changed! SW = %b", SW); // 监视的变量变动时打印
$stop // 暂停仿真(可以继续)
$finish // 结束仿真(不能继续)
$time
$realtime
$readmemb // 二进制读取文件
$readmemh("code.txt", mem); // 十六进制 reg [3:0] mem [7:0]| 格式字符 | 含义 | 打印效果示例(假设变量值为 4'b1010) |
|---|---|---|
%d 或 %D | 十进制 (Decimal) | 10 |
%b 或 %B | 二进制 (Binary) | 1010 |
%o 或 %O | 八进制 (Octal) | 12 |
%h 或 %H | 十六进制 (Hexadecimal) | a(自动小写) |
%t 或 %T | 当前仿真时间 (Time) | 50ns(根据 ``timescale` 自动带单位输出) |
%c 或 %C | ASCII 字符 | 将数值转为对应的字符输出 |
%s 或 %S | 字符串 (String) | 输出对应的字符串 |
%v 或 %V | 净网/信号强度 (Strength) | 打印信号的物理驱动强度(门级仿真常用) |
%m 或 %M | 分层路径名 (Hierarchical) | 打印当前模块在整个设计中的顶层实例化路径 |
时序控制
延时 #
#10 a = b; // 等待 10 个时间单位后,执行赋值
a = #10 b; // 立刻计算 b 的值,但等待 10 个时间单位后才赋给 a事件控制 @
边沿触发(Edge-triggered):映射为触发器
always @(posedge clk or negedge rst_n) ...电平敏感(Level-sensitive):映射为组合逻辑电路
always @(a or b or sel)...
always @(*)...过程块内部“中途等待”:不可综合!
initial begin
a = 0;
@ (posedge clk);
b = 1;
end等待声明 wait
initial begin
wait (ready == 1'b1); // 等待直到表达式为真
data_out = data_in;
end!在绝大多数 RTL 设计中不可综合
延迟定义块 specify
???
Z/X
综合
硬件上不存在 X,综合时作为无关项优化。
FPGA 内部没有断开连线的物理开关,综合器会把Z状态优化掉,或者将其等效转换为选择器(MUX)
Z能综合的唯一场景:芯片的物理引脚(I/O Pad)
门电路运算(与、或、非)
X如果结果能绝对确定(e.g.1'b0 & 1'bx,1'b1 | 1'bx),就输出确定值;如果有不确定,就输出XZ在逻辑门运算中,一律被当做X处理。- 比如:
1'bz & 1'b1的运算结果是X。
- 比如:
条件判断
- 如果条件表达式的结果算出来包含
X或Z(例如if (a == 1'bx)),仿真器会判定为“假”(False) - 在三态选择
A ? B : C中:如果条件A变成了X或Z,仿真器会把B和C按位进行比较。如果某一位相同,就输出该位的值;如果不同,该位输出X。
算术运算(+, -, *, /)
- 只要输入数据中包含任何一个
X或Z,整个算术运算的结果全部变成X。
比较
使用 全等 === 和 不全等 !== 可以严格对齐比较 0, 1, X, Z
直接用 == 和 !=比较在遇到 X, Z 时直接变 X