02.VerilogHDL程序设计和描述方式
# Verilog HDL程序设计
# 一、数据流建模
# 1. 数据流建模是啥?
核心思想:用“电线连接”的方式描述电路,直接表示信号如何从输入流动到输出。
类比:就像画电路图,用导线把各个元件连起来,比如 A和B接个与门,输出Y
。
# 2. 核心工具:连续赋值语句(assign
)
作用:告诉Verilog“这根线(wire)的值等于某个表达式的结果”。
# 2.1 显式连续赋值
语法:
wire Y; // 先声明线网 assign Y = A & B; // 再用assign赋值
1
2带延迟的例子:
assign #3 Y = A & B; // 计算A&B后,延迟3个单位时间再赋值给Y
1
# 2.2 隐式连续赋值
语法(声明时直接赋值):
wire Y = A & B; // 声明时直接赋值 wire #(2,3,4) Z = X; // 带延迟(上升2,下降3,关断4)
1
2带驱动强度的例子:
wire (strong1, weak0) Y = A | B; // 输出1时强驱动,0时弱驱动
1
# 3. 连续赋值的特点
目标必须是
wire
:不能给
reg
类型赋值!示例:
wire Y; // 正确 reg Y; // 错误!assign不能用于reg assign Y = A;
1
2
3
实时更新:
只要右边的信号(如
A
或B
)变化,立刻重新计算并赋值。示例:
assign Y = A + B; // A或B一变,Y立刻更新
1
并行执行:
所有
assign
语句同时工作,和代码顺序无关。示例:
assign Y = A & B; assign Z = C | D; // 这两行并行执行
1
2
延迟是“惯性延时”:
短于延迟的脉冲会被过滤掉。
示例:
assign #5 Y = A; // 如果A的脉冲宽度<5,Y不会变化
1
# 4. 实际代码示例
# 4.1 显式赋值(带延迟)
module example1(input [3:0] A, B, output [3:0] Y);
wire [3:0] Y;
assign #(3,2,4) Y = A & B; // 上升延迟3,下降2,关断4
endmodule
1
2
3
4
2
3
4
# 4.2 隐式赋值(带驱动强度)
module example2(input [3:0] M, N, output [3:0] W);
wire (strong0, weak1) [3:0] W = (M ^ N) & (M | N);
endmodule
1
2
3
2
3
# 5. 常见问题
什么时候用连续赋值?
- 描述组合逻辑(如与门、加法器、多路选择器)。
- 不适合时序逻辑(如触发器要用
always
块)。
忘记写
assign
会怎样?直接写
wire Y = A;
是隐式赋值,合法。但分开写时漏掉
assign
会报错:wire Y; Y = A; // 错误!缺少assign
1
2
能用在
always
块里吗?- 不能!
assign
是并行语句,always
是过程块,两者不兼容。
- 不能!
# 二、行为级建模
# 1. 行为级建模是啥?
核心思想:用"软件思维"描述硬件行为,重点关注电路做什么(功能),而不是具体怎么连线。 类比:就像写剧本告诉演员怎么演,而不是设计舞台的每个螺丝钉。
# 2. 核心工具:过程块(always/initial)
# 2.1 initial块 - 一次性剧本
特点:只执行一次,主要用于仿真初始化。
示例:
initial begin clk = 0; // 初始时钟为0 reset = 1; // 复位信号置1 #50 reset = 0;// 50单位时间后取消复位 end
1
2
3
4
5
# 2.2 always块 - 循环剧本
特点:不断重复执行,是硬件行为的核心描述方式。
敏感列表:决定何时触发:
// 组合逻辑:输入变化就执行 always @(a or b or sel) // 时序逻辑:时钟边沿触发 always @(posedge clk)
1
2
3
4
5
# 3. 过程赋值:硬件界的"变量赋值"
# 3.1 阻塞赋值(=) - 顺序执行
特点:像普通编程语言,一步完成计算和赋值。
适用场景:组合逻辑设计。
示例:
always @(a or b) begin temp = a & b; // 立即计算并赋值 y = temp | c; // 使用更新后的temp end
1
2
3
4
# 3.2 非阻塞赋值(<=) - 并行执行
特点:先计算右边,等时间步结束才统一赋值。
适用场景:时序逻辑设计(触发器)。
示例:
always @(posedge clk) begin q1 <= d; // 这三个赋值 q2 <= q1; // 是同时进行的 q3 <= q2; // 构成移位寄存器 end
1
2
3
4
5
# 4. 流程控制:硬件版的"if-else"
# 4.1 if-else语句
特点:带优先级的选择结构。
示例:
always @(sel or a or b) begin if (sel == 1'b1) y = a; // sel为1选a else y = b; // 否则选b end
1
2
3
4
5
6
# 4.2 case语句
特点:多路分支,无优先级。
特殊形式:
casez
:忽略z(高阻)和?(无关位)casex
:忽略x(未知)、z和?
示例(BCD译码器):
case(num) 4'd0: seg = 7'b1111110; // 显示数字0 4'd1: seg = 7'b0110000; // 显示数字1 default: seg = 7'bx; // 其他情况输出未知 endcase
1
2
3
4
5
# 5. 循环语句:硬件也能"循环"?
# 5.1 for循环 - 可综合的循环
特点:编译时会展开为硬件结构。
示例(位反转):
for(i=0; i<8; i=i+1) rev[7-i] = data[i]; // 生成8个并行的连线
1
2
# 5.2 其他循环(仿真用)
forever
:无限循环(如生成时钟)initial forever #10 clk = ~clk; // 每10ns翻转
1repeat
:固定次数循环repeat(5) @(posedge clk); // 等待5个时钟周期
1
# 6. 关键注意事项
reg不一定生成寄存器:
- 在always块中用
=
赋值可能生成组合逻辑 - 只有时钟触发的
<=
才会生成触发器
- 在always块中用
敏感列表要完整:
// 组合逻辑必须列出所有输入! always @(a or b or sel) // 漏掉sel会导致bug
1
2避免锁存器:
- if缺少else分支
- case缺少default
- 解决办法:确保所有路径都有赋值
# 三、 结构化建模是啥?
核心思想:像搭积木一样用现成的模块/门电路来构建系统,直接对应硬件层次结构,说白了就是面向对象设计。
类比:
- 模块级:用现成的CPU、内存等大组件拼电脑
- 门级:用与门、或门等小零件搭电路
- 开关级:用晶体管构建最基础的逻辑门
# 2. 模块级建模:调用现成模块
# 2.1 基本调用方式
// 定义子模块(积木)
module AND_gate(input a, b, output y);
assign y = a & b;
endmodule
// 顶层模块(拼积木)
module TOP(input x1, x2, output z);
wire w;
// 实例化子模块(两种方式)
AND_gate U1(x1, x2, w); // 方式1:按顺序连接
AND_gate U2(.a(w), .b(x1), .y(z)); // 方式2:按名称连接(推荐!)
endmodule
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 2.2 参数化模块
module #(parameter WIDTH=4)
Adder(input [WIDTH-1:0] a,b, output [WIDTH:0] sum);
assign sum = a + b;
endmodule
// 调用时修改参数
Adder #(8) U3(a_8bit, b_8bit, sum_8bit); // 8位加法器
1
2
3
4
5
6
7
2
3
4
5
6
7
# 3. 门级建模:调用基本逻辑门
# 3.1 Verilog内置门类型
门类型 | 关键字 | 示例 |
---|---|---|
与门 | and | and U1(out, a, b); |
或门 | or | or U2(out, a, b, c); |
非门 | not | not U3(out, in); |
三态缓冲器 | bufif1 | bufif1 U4(out, in, ctrl); |
# 3.2 实际案例:2-4译码器
module decoder(
input [1:0] sel,
input en,
output [3:0] y
);
wire not_sel0, not_sel1;
// 基本门调用
not U1(not_sel0, sel[0]);
not U2(not_sel1, sel[1]);
nand U3(y[0], en, not_sel1, not_sel0);
nand U4(y[1], en, not_sel1, sel[0]);
nand U5(y[2], en, sel[1], not_sel0);
nand U6(y[3], en, sel[1], sel[0]);
endmodule
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 4. 开关级建模:晶体管级描述
# 4.1 MOS开关类型
类型 | 关键字 | 功能 |
---|---|---|
NMOS | nmos | 控制=1时导通 |
PMOS | pmos | 控制=0时导通 |
CMOS | cmos | 双控制(nctrl和pctrl互补) |
双向开关 | tran | 两端直接连通 |
# 4.2 CMOS与非门实现
module NAND(
input a, b,
output y
);
supply1 VDD; // 电源
supply0 GND; // 地
pmos P1(y, VDD, a);
pmos P2(y, VDD, b);
nmos N1(y, mid, a);
nmos N2(mid, GND, b);
endmodule
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 5. 关键注意事项
端口连接陷阱:
// 危险!容易接反 Sub_module U1(a, b); // 安全推荐 Sub_module U1(.port_a(a), .port_b(b));
1
2
3
4参数传递优先级:
#(参数)
直接传递 >defparam
语句
开关级设计限制:
- 通常只用于ASIC设计
- FPGA综合经常忽略开关级细节
# 总结
- 核心价值:
- 模块化设计:提高代码复用性
- 贴近物理实现:适合性能优化
- 混合使用:高层用行为级,底层用结构化
- 典型应用:
- 数字IP核集成(如调用现成的存储器、PLL)
- 标准单元库开发
- 晶体管级电路设计
# ** 三种建模方式对比**
特性 | 结构化建模 | 行为级建模 | 数据流建模 |
---|---|---|---|
抽象级别 | 中等(门/模块级) | 高(算法级) | 中(信号流级) |
主要语句 | 模块/门实例化 | always/initial | assign |
可读性 | 直观但繁琐 | 最易读 | 中等 |
应用场景 | IP模块集成、标准单元设计 | 复杂逻辑/状态机 | 简单组合逻辑 |
硬件对应 | 直接映射 | 依赖综合工具 | 直接映射 |
帮助我们改善此页面 (opens new window)
上次更新: 2025/07/12, 16:01:22