Verilog 简明教程
Verilog 简明教程
0. 心智模型(先建立,再学语法)
Verilog 代码分两类,物理上必须分开:
- 设计代码(可综合):描述电路结构,进 FPGA;
- 测试代码(不可综合):产生输入、观察输出,只活在仿真器里。
判断标准:这句话能不能翻译成真实硬件(门、触发器、连线)?
电路 = 一张依赖图 + 两类边:
- 组合边(
assign/always @(*)):持续传导,输入一变输出立刻跟着变,零延迟; - 寄存器节点(
always @(posedge clk)):只在时钟沿把输入快照进输出,两个沿之间与输入脱钩——它引入"状态/历史",允许反馈环(环必须被寄存器打破)。
形式化:组合电路 \(y = f(x)\);时序电路 \(y_t = f(x_t, s_{t-1})\),寄存器是状态 \(s\) 的载体。
并行语义:块与块之间并发(一屋子同时运转的电路),块内语句顺序执行。没有主循环、没有调用——实例化是复制一块电路并连线,不是函数调用。
仿真的定位:验证"逻辑对不对"。时序正确性不靠仿真,靠静态时序分析(STA)和上板。仿真里的时间、启动、信号都是理想化抽象;真实电路有延迟、毛刺、亚稳态和混沌上电。
1. 程序骨架
module 模块名(端口1, 端口2, ...); // 模块 = 带接口的电路块/图纸
input wire a; // 输入(只能是 wire)
output reg y; // 输出(wire 或 reg,见 §2)
// ... 电路描述 ...
endmodule
- 端口不写类型默认
wire;建议在文件头加`default_nettype none防止隐式 wire 掩盖拼写错误; - 实例化:
DFF u0 (.d(d[0]), .clk(clk), .q(q[0]));(名称映射,推荐)或按位置DFF u1 (q[1], clk, d[1]); - 实例是物理拷贝:
u0、u1是两份独立硬件,各自占资源,并行工作,无运行时关系。
类比:module ≈ 带端口的元件图纸。区别于 C++ class:实例化不是创建对象,是"拿图纸造零件再焊起来";模块间通信只有连线,没有方法调用;无继承多态,用组合代替。
2. 数据类型:只记两种
| 类型 | 是什么 | 什么时候用 |
|---|---|---|
wire |
导线,被持续驱动 | assign 左边、模块间连线、实例输出 |
reg |
过程块里的变量 | always/initial 块内赋值的信号 |
铁律:assign 配 wire,always 配 reg。
reg 是历史命名错误,不表示触发器:综合出什么看 always 写法。端口分三类:input(只能 wire)、output(wire 或 reg)、inout(只能 wire)。
四值逻辑:0、1、x(未知)、z(高阻/悬空)。仿真初始所有信号为 x。
字面量:4'b1010、8'hFF、8'd255;下划线只助读;位宽不够截高位;负数赋给无符号 reg → 补码全 1。
parameter:例化时替换的常量参数,≈ C++ template<int W> 的值参数化:
module counter #(parameter W = 8)(...);
counter #(.W(32)) u1 (...); // 例化点覆盖,defparam 禁止
只有值、几乎无类型检查;结构级参数化靠 generate/genvar。
3. 电路的三种写法
3.1 连续赋值(组合逻辑)
assign y = a & b | c;
assign led = &sw; // 归约与:全 1 则 1
assign {cout, sum} = a + b + cin; // 级联赋值
3.2 always 块——两种模式,别混
// 时序逻辑(触发器)
always @(posedge clk) // 可加 or negedge rst_n
q <= d; // 非阻塞赋值 <=
// 组合逻辑
always @(*) begin // * = 块内读到的所有信号
if (sel) y = a; // 阻塞赋值 =
else y = b;
end
| 时序 | 组合 | |
|---|---|---|
| 敏感表 | posedge clk |
(*) |
| 赋值符 | <= |
= |
| 综合为 | 触发器 | 门电路 |
| 要求 | — | if/case 穷尽,否则推断 latch |
触发条件 vs 动作:@(...) 里是"什么时候醒"(posedge clk = 名为 clk 的信号 0→1 跳变那一瞬),后面的语句是"醒来干什么"。clk/q/d 全是用户自定义标识符,不是系统内置。
阻塞 vs 非阻塞:= 立即生效(C 语义);<= 块结束时统一更新(模拟同一沿采样的真实触发器)。时序块用 = 或跨块驱动同一信号 = 隐蔽 bug。
异步复位模板:
always @(posedge clk or negedge rst_n)
if (!rst_n) q <= 0;
else q <= d;
3.3 门级原语(了解)
and g1 (w, a, b); // 输出在前
bufif1 (bus, data, en); // 三态门:en=1 导通,否则 z
4. 常用语法
// 条件与多路
if (...) ...; else ...;
case (opcode)
3'b000: y = a + b;
3'b001, 3'b010: y = a - b;
default: y = 0; // 组合逻辑必须写 default
endcase
assign y = sel ? a : b; // 三目 = 数据选择器
// 位操作
{ a[3:2], b[1:0] } // 拼接
{4{bit}} // 复制
^data // 归约异或(奇偶校验)
// 存储器(大数组慎综合)
reg [7:0] mem [0:255];
// 函数(组合计算复用,0 延迟,纯函数)与任务(可带时序,testbench 用)
function [7:0] f(input [7:0] a, b); f = a + b; endfunction
for 可综合(静态展开);while/forever/repeat 仅 testbench。
5. Testbench 模板(仿真专用,随便写)
`timescale 1ns/1ps
module tb();
reg [2:0] sw = 0; // 激励 → reg;初始化避免 x
wire led; // 观察 → wire
integer i;
led uut(.sw(sw), .led(led));
always #50 clk = ~clk; // 时钟发生器(clk 需 reg)
initial begin
for (i = 0; i < 8; i = i + 1) begin
#100 sw = i;
end
$display("done"); $finish;
end
endmodule
系统任务(全部不可综合):$display、$write、$monitor(信号一变就打印)、$finish、$stop、$time、$random、$readmemh。
6. 可综合 / 不可综合速查
可综合:assign、always @(posedge clk)、always @(*)、if/case/for(静态)、parameter、实例化、原语门。
不可综合(只进 testbench):# 延迟、initial(FPGA 初值部分工具支持但别依赖 ASIC)、$ 系列、fork/join、wait、forever/while/repeat、===/!==(case 等)、force、命名事件、trireg/wor/wand。
灰色危险区:组合块不穷尽(出 latch)、多 always 块驱动同一 reg(竞争)、敏感表漏信号。
7. 完整例子:带复位的参数化计数器
module counter #(parameter W = 4)(
input wire clk,
input wire rst_n,
output reg [W-1:0] count
);
always @(posedge clk)
if (!rst_n) count <= 0;
else count <= count + 1'b1;
endmodule
8. 仿真 vs 现实:时序问题的三层处理
- RTL 仿真(功能层):门延迟 = 0,看不到毛刺、亚稳态、jitter。
assign #3 y = a & b;这类行为延迟只影响仿真、不可综合——别在 RTL 里塞#; - STA(时序层,主战场):实现后基于物理延迟查表,穷举所有路径检查 setup/hold 裕量。XDC 里
create_clock -period 10 [get_ports clk]是喂给它的;WNS < 0就改设计(流水、降组合层级)或降频; - 时序仿真(门级 + SDF 反标):实现后把真实延迟反标进仿真,能看到毛刺与竞争。辅助手段,慢,主力是 STA。
启动差异:仿真 t=0 一切为 x、initial 瞬时执行;现实有电源 ramp-up、晶振起振、比特流加载。⇒ 没有复位的设计上板必炸,复位是把仿真隐含假设显式化。
9. 常见错误清单
| 现象 | 原因 |
|---|---|
Illegal assignment |
always 里给 wire 赋值(或反之) |
| 信号值是 x | 拼写错,隐式 wire 生效 |
| 组合逻辑出 latch | if/case 没写全 |
| 仿真对、上板错 | #、initial、初值等仿真专用构造混入 RTL |
| 功能时好时坏 | 时序块用 = 或多块驱动同一信号 |
| 例化端口错 | 位置映射顺序错 → 换名称映射 |
综合报 let 等 not found |
实例化名字笔误,或文件没加入 Design Sources |
10. 与 Vivado 流程的对应
设计代码 → Synthesis → Implementation → XDC 约束(PACKAGE_PIN + IOSTANDARD + create_clock)→ Bitstream → 上板。仿真走 testbench,两套代码分目录。注意:烧写前 DRC 强制要求 IOSTANDARD;纯组合逻辑无时钟约束属正常。
附:一句话速记
- assign 配 wire,always 配 reg;reg 是变量不是寄存器;
@括号里 = 什么时候醒,后面 = 醒来干什么;- 时序用
<=配 posedge,组合用=配(*)且写全; - module 是图纸,实例化是造零件焊起来;
- 仿真管逻辑,STA 管时序,上板管物理;
- parameter ≈ 模板值参数,只有值、无类型检查。

浙公网安备 33010602011771号