01.VerilogHDL的坤本素养
# 一、Verilog HAL语言基本要素
==准备好来自这门硬件编程语言的爱吧==~我热烈滴吻~
# 1、空白符
包括空格符(
\b
)、制表符(\t
)、换行符和换页符,编译时被忽略。示例:
initial begin a = 3'b100; b = 3'b010; end
1在加入空白符之后,代码变得更加可读:
initial begin a = 3'b100; b = 3'b010; end
1
2
3
4
5
# 2、注释
作用:用来写说明文字,不会被编译,是给可爱的同学们看的笔记,防止一觉睡醒看不懂了(汇编程序员的痛)。
两种写法:
单行注释:用
//
,直到行尾都算注释。assign a = b & c; // 这是单行注释:计算a等于b和c的按位与
1多行注释:用
/* */
,中间可以跨多行。/* 这是多行注释: 可以写很长的说明, 比如这个模块的功能是XXX */**重要规则**:
1
2
3
重要规则
多行注释不能嵌套!比如
/* 注释1 /* 注释2 */ 注释1 */
会报错。但多行注释里可以包含单行注释:
/* // 这是合法的 */
。
# 3、标识符(给变量/模块起名字)
作为一名类C语言,verilog十分有素养,所以它的标识符命名方式以及注意点和C语言的变量几乎没有区别
组成:字母、数字、
$
、_
,区分大小写,首字符必须为字母或下划线。示例:
count
、_CC_G5
(合法);30count
、out*
(非法)。转义标识符:以
\
开头,以空白结尾,可包含任意可打印字符(如\a+b=c
)。合法示例:
counter // 纯字母 _data_in // 下划线开头 $signal // 美元符号开头(较少用)
1
2
3非法示例:
3state // 数字开头 out#put // 包含非法字符# a+b // 包含运算符+
1
2
3
特殊技巧:转义标识符
如果非要起一个奇怪的名字(比如包含空格、符号),可以用
\
开头,空格结尾。示例:
\7400 // 实际名字是 "7400"(通常用于和传统电路编号兼容) \a+b=c // 实际名字是 "a+b=c" \*** // 实际名字是 "***"
1
2
3但不建议滥用,尽量用常规命名(如
and_gate
而不是\a&b
)。
# 4、关键字
就是说verilog里面已经用于设计的“标识符”不能让它换工作,这就是关键字,这点和C语言没有任何区别。总之有点:咱们是兄弟,你的就是我的,我的还是我的,那种味儿……==~emmmm~==
特点:
- 全部是小写,比如
always
是关键字,但ALWAYS
不是。 - 不能用来起名字!比如定义一个变量叫
module
会报错。
- 全部是小写,比如
常见关键字列表(不止这点,因为我用的框架不行):
关键字 作用 module
定义模块开头 input
声明输入端口 output
声明输出端口 reg
声明寄存器变量 wire
声明连线 always
描述时序/组合逻辑
# 5、数字(如何表示0、1、不定、高阻态?)
四种基本逻辑状态:
状态 | 含义 |
---|---|
0 | 低电平、逻辑0或假 |
1 | 高电平、逻辑1或真 |
x/X | 不确定或未知状态 |
z/Z | 高阻态 |
整数表示法:
<位宽>'<基数><数值>
位宽:二进制位的总数(如
4'b1011
表示4位二进制数)。基数:
基数符号 进制 合法字符 b
/B
二进制 0,1,x,X,z,Z,?, _
(下划线可忽略)o
/O
八进制 0-7,x,X,z,Z,?, _
d
/D
十进制 0-9, _
h
/H
十六进制 0-9, a-f, A-F, x,X,z,Z,?, _
示例:
8'b1010_1101 // 8位二进制,下划线提高可读性 16'hFF00 // 16位十六进制,等于65535 4'd10 // 4位十进制,实际存储为1010 3'b1x0 // 3位二进制,第二位未知
1
2
3
4易错点:
- 位宽不能是表达式:
(2+2)'b11
❌ - 负号必须在最左边:
-4'd3
✅,4'd-3
❌
- 位宽不能是表达式:
- 实数(浮点数)表示法:
- 两种写法:
- 直接写小数:
3.14
、0.5
(注意:.5
是错的,必须写0.5
)。 - 科学计数法:
2.5e3
(=2500)、1E-6
(=0.000001)。
- 直接写小数:
- 两种写法:
# 二、Verilog HDL基本数据类型
# 1. 物理数据类型:硬件电路的“材料”
核心思想:Verilog的数据类型是对实际硬件电路的抽象,比如电线、寄存器、存储器等。
# 1.1 连线型(Net Type)—— 相当于“电线”
作用:表示电路中的物理连接,不能存储数据,只能传递信号。
常见类型:
类型 功能说明 现实类比 wire
普通导线(默认类型) 铜线 tri
三态导线(可高阻态) 带开关的线 wor
/trior
多驱动时,实现“线或”逻辑 多个开关并联 wand
/triand
多驱动时,实现“线与”逻辑 多个开关串联 supply1
电源线(恒定高电平) VCC(+5V) supply0
地线(恒定低电平) GND(0V) 声明格式:
wire [位宽] 变量名; // 普通连线 tri [7:0] bus; // 8位三态总线 supply1 vdd; // 电源线
1
2
3关键特性:
默认初值为
z
(高阻态)。需要用
assign
或模块输出驱动:wire a; assign a = 1'b1; // 给电线a赋值高电平
1
2
# 1.2 寄存器型(Register Type)—— 相当于“存储单元”
作用:表示可以存储数据的硬件元件(如触发器、锁存器)。
关键字:
reg
声明格式:
reg [位宽] 变量名;
1示例:
reg q; // 1位寄存器(存储1bit数据) reg [7:0] data; // 8位寄存器(存储一个字节)
1
2关键特性:
默认初值为
x
(未知状态)。必须在
always
或initial
块中赋值:always @(posedge clk) begin q <= din; // 在时钟上升沿存储din的值 end
1
2
3注意:
reg
不一定是实际的寄存器!综合工具可能将其优化为组合逻辑。若需要明确符号,可用
reg signed
:reg signed [3:0] num; // 4位有符号数(范围-8到7) num = -2; // 存储为1110(补码)
1
2
# 2. 存储器型(Memory)—— 相当于“RAM/ROM”
作用:描述硬件中的存储阵列(如内存、寄存器堆)。
本质:一组
reg
的集合。声明格式:
reg [数据位宽] 存储器名 [地址数量];
1示例:
reg [7:0] ram [0:255]; // 256个8位存储单元(地址0~255) reg [31:0] rom [0:1023]; // 1KB的32位ROM
1
2操作方式:
按地址读写:
ram[0] = 8'hFF; // 给地址0写入255 data_out = rom[5]; // 读取地址5的数据
1
2重要限制:
- 不能一次性读写整个存储器!必须逐个地址操作。
- 综合时可能被映射为FPGA的Block RAM或寄存器堆。
# 3. 抽象数据类型:辅助设计的“工具”
# 3.1 整型(integer
)
作用:用于仿真时的数学运算(不直接对应硬件)。
特点:
- 32位有符号整数(范围
-2^31
到2^31-1
)。 - 常用于循环计数器、临时计算。
- 32位有符号整数(范围
示例:
integer i; for (i=0; i<10; i=i+1) begin // 循环10次 // 执行操作 end
1
2
3
4
# 3.2 时间型(time
)
作用:记录仿真时间(如测试延迟)。
特点:
- 64位无符号整数,单位由 ``timescale
定义(如
1ns`)。 - 常与
$time
系统函数配合使用。
- 64位无符号整数,单位由 ``timescale
示例
time start_time; initial begin start_time = $time; // 记录当前仿真时间 #10; // 延迟10个时间单位 $display("耗时:%t", $time - start_time); end
1
2
3
4
5
6
# 3.3 实型(real
)
作用:仿真时的浮点数计算(如模拟电路参数)。
示例:
real voltage; voltage = 3.3; // 表示3.3V电压
1
2
# 3.4 参数型(parameter
)—— 相当于“常量”
作用:定义模块中的固定值(如位宽、延迟时间)。
特点:
- 仿真前确定,运行时不可修改。
- 提高代码可读性和可维护性。
示例:
parameter WIDTH = 8; // 定义位宽为8 parameter DELAY = 10; // 定义延迟10ns reg [WIDTH-1:0] data; // 实际声明为reg [7:0] data
1
2
3
# 总结:数据类型的选择指南
类型 | 关键字 | 适用场景 | 硬件对应物 |
---|---|---|---|
连线型 | wire | 模块间信号连接 | 导线 |
寄存器型 | reg | 时序逻辑存储 | 触发器/锁存器 |
存储器型 | reg [] [] | RAM/ROM建模 | 存储阵列 |
整型/实型 | integer /real | 仿真计算 | 无直接对应 |
参数型 | parameter | 常量定义(如位宽) | 硬件配置参数 |
黄金法则:
- 连线用
wire
,存储用reg
。 - 存储器本质是
reg
数组,按地址访问。 integer
/real
仅用于仿真,不生成实际电路。
常见错误:
错误1:对
wire
用过程赋值(=
)。wire a; always @(*) a = b; // 错!wire必须用assign驱动
1
2错误2:试图整体操作存储器。
reg [7:0] mem [0:255]; mem = 0; // 错!必须逐个地址初始化
1
2
# 三、运算符和表达式
# 1、算术运算符:加减乘除
符号:+
、-
、*
、/
、%
(取模)
作用:数学运算,注意位宽规则!
module arithmetic;
reg [3:0] a = 4'b1100; // 12
reg [2:0] b = 3'b011; // 3
initial begin
$display("a + b = %b", a + b); // 12+3=15 → 4'b1111
$display("a - b = %b", a - b); // 12-3=9 → 4'b1001
$display("a * b = %b", a * b); // 12*3=36 → 但a只有4位,截断后=4'b0100(36%16=4)
$display("a / b = %b", a / b); // 12/3=4 → 4'b0100
$display("a %% b = %b", a % b); // 12%3=0 → 4'b0000
end
endmodule
2
3
4
5
6
7
8
9
10
11
12
- 结果位宽 = 操作数的最大位宽(如4位+3位=4位结果)。
- 除法/取模会舍去小数(如
7/3=2
)
# 2. 关系运算符:比较大小
符号:>
、<
、>=
、<=
输出:1(真)、0(假)、x(未知)
module compare;
reg [3:0] x = 4'b1010; // 10
reg [3:0] y = 4'b0011; // 3
initial begin
$display("x > y? %b", x > y); // 10>3 → 1
$display("x < y? %b", x < y); // 10<3 → 0
$display("x >=10? %b", x >= 4'd10); // 10>=10 → 1
end
endmodule
2
3
4
5
6
7
8
9
10
注意:若操作数含 x
,结果可能是 x
(如 4'b101x > 4'b0000
→ x
)。
# 3. 相等运算符:判等
符号:
==
(等于)、!=
(不等)→ 可能返回x
===
(全等)、!==
(非全等)→ 严格比较(包括x
和z
)
module equality;
reg [3:0] p = 4'b101x;
reg [3:0] q = 4'b1010;
initial begin
$display("p == q? %b", p == q); // 101x == 1010 → x(不确定)
$display("p === q? %b", p === q); // 101x === 1010 → 0(严格不等)
$display("p != q? %b", p != q); // 101x != 1010 → x
$display("p !== q? %b", p !== q); // 101x !== 1010 → 1(确实不等)
end
endmodule
2
3
4
5
6
7
8
9
10
11
何时用 ===
? 测试中精确匹配高阻态 z
或未知态 x
。
# 4. 逻辑运算符:真/假判断
符号:&&
(与)、||
(或)、!
(非)
规则:非0即真(0
=假,1
/x
/z
=真)
module logical;
reg a = 1'b1;
reg b = 1'b0;
reg c = 1'bx;
initial begin
$display("a && b = %b", a && b); // 1 && 0 → 0
$display("a || b = %b", a || b); // 1 || 0 → 1
$display("!a = %b", !a); // !1 → 0
$display("a && c = %b", a && c); // 1 && x → x(不确定)
end
endmodule
2
3
4
5
6
7
8
9
10
11
12
易错点:逻辑运算符会先化简操作数为1位(如 4'b1011
当作 1'b1
)。
# 5. 按位运算符:逐bit操作
符号:&
(与)、|
(或)、^
(异或)、~
(非)
module bitwise;
reg [3:0] m = 4'b1100;
reg [3:0] n = 4'b1010;
initial begin
$display("m & n = %b", m & n); // 1100 & 1010 → 1000
$display("m | n = %b", m | n); // 1100 | 1010 → 1110
$display("m ^ n = %b", m ^ n); // 1100 ^ 1010 → 0110(相同为0,不同为1)
$display("~m = %b", ~m); // ~1100 → 0011
end
endmodule
2
3
4
5
6
7
8
9
10
11
对比逻辑运算符:
&
是逐位与,&&
是整体逻辑与。- 示例:
4'b1100 && 4'b1010 → 1
(非0即真),但4'b1100 & 4'b1010 → 4'b1000
。
# 6. 移位运算符:左移/右移
符号:<<
(左移)、>>
(右移)
规则:空位补 0
,不循环移位!
module shift;
reg [3:0] num = 4'b1101; // 13
initial begin
$display("num << 1 = %b", num << 1); // 1101→1010(高位1丢弃,低位补0)
$display("num >> 2 = %b", num >> 2); // 1101→0011(低位01丢弃,高位补0)
end
endmodule
2
3
4
5
6
7
8
应用场景:
- 左移1位 ≈ 乘以2(
4'b0011<<1 → 4'b0110
,即3→6)。 - 右移2位 ≈ 除以4(
4'b1100>>2 → 4'b0011
,即12→3)。
# 7. 条件运算符:简化的if-else
符号:条件 ? 表达式1 : 表达式2
module conditional;
reg sel = 1'b1;
reg [3:0] in1 = 4'b1010;
reg [3:0] in2 = 4'b0101;
wire [3:0] out;
assign out = sel ? in1 : in2; // sel为1选in1,否则选in2
initial begin
$display("out = %b", out); // sel=1 → out=1010
end
endmodule
2
3
4
5
6
7
8
9
10
11
12
等效代码:
if (sel) out = in1;
else out = in2;
2
# 8. 连接与复制运算符:合并信号
符号:
- 连接
{a, b}
:将多个信号拼接。 - 复制
{n{a}}
:重复信号n次。
module concat;
reg [1:0] a = 2'b10;
reg [2:0] b = 3'b110;
initial begin
$display("{a, b} = %b", {a, b}); // 10 + 110 → 5'b10110
$display("{3{a}} = %b", {3{a}}); // 10重复3次 → 6'b101010
$display("{2{a}, 1'b0, b} = %b", {2{a}, 1'b0, b}); // 1010 + 0 + 110 → 7'b1010110
end
endmodule
2
3
4
5
6
7
8
9
10
典型用途:
- 扩展位宽:
{4{1'b1}}
生成4'b1111
。 - 组合总线:
{addr, data}
合并地址和数据。
# 总结:运算符优先级表
优先级 | 运算符 | 描述 | ||
---|---|---|---|---|
最高 | ! ~ | 逻辑非、按位非 | ||
* / % | 乘、除、取模 | |||
+ - | 加、减 | |||
<< >> | 移位 | |||
< <= > >= | 关系比较 | |||
== != === !== | 相等判断 | |||
& | 按位与 | |||
^ ^~ | 按位异或、同或 | |||
| | 按位或 | |||
&& | 逻辑与 | |||
最低 | | | | 逻辑或 |
黄金法则:
- 不确定优先级时,加括号!如
(a & b) || c
。 - 按位操作 vs 逻辑操作:看是否需要逐bit处理。
- 移位运算的空位永远补
0
。
# 四、模块(Module)—— Verilog的“积木块”
别怕,就是C语言的函数,有输入返回,就是他换了衣服
# 1. 模块是什么?
核心概念:模块是Verilog的基本设计单元,相当于电路中的一个功能盒子。
- 现实类比:就像乐高积木,每个模块实现特定功能(如计数器、加法器),通过拼接构建复杂系统。
# 2. 模块的四大组成部分
每个模块都包含以下结构(以D触发器为例):
// 1. 模块声明(定义"盒子"的名字和接口)
module dff (
input clk, // 2. 端口定义:输入时钟
input din, // 输入数据
output reg q // 输出数据(用reg存储)
);
// 3. 逻辑功能描述:当时钟上升沿到来时,存储din的值
always @(posedge clk) begin
q <= din;
end
endmodule // 4. 模块结束
2
3
4
5
6
7
8
9
10
11
12
13
# 3. 模块的端口定义
端口类型:
类型 | 方向 | 示例 |
---|---|---|
input | 输入信号 | input clk; |
output | 输出信号 | output q; |
inout | 双向信号(少见) | inout data_bus; |
端口数据类型:
默认是
wire
型(如input clk
等价于input wire clk
)。若输出需存储(如时序逻辑),需显式声明为
reg
:output reg q; // 输出q需要在always块中赋值
1
# 4. 模块的实例化:调用“积木”
场景:在顶层模块中调用子模块(如用两个D触发器构建移位寄存器)。
# 方法1:顺序连接(按位置对应)
module top;
wire clk, data_in;
wire stage1, stage2;
// 实例化第一个D触发器(端口顺序必须与模块定义一致!)
dff dff1 (clk, data_in, stage1);
// 实例化第二个D触发器
dff dff2 (clk, stage1, stage2);
endmodule
2
3
4
5
6
7
8
9
10
风险:若模块端口顺序变更,所有实例化需同步修改!
# 方法2:命名连接(推荐!)
module top;
wire clk, data_in;
wire stage1, stage2;
// 通过.端口名(信号名)明确对应关系
dff dff1 (
.clk(clk),
.din(data_in),
.q(stage1)
);
dff dff2 (
.clk(clk),
.din(stage1),
.q(stage2)
);
endmodule
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
优势:
- 顺序无关,可读性强。
- 避免因模块定义修改导致错误。
# 5. 模块的层次化设计
关键思想:自顶向下拆分功能,逐步细化。
示例:构建一个4位加法器
顶层模块:定义输入输出
module adder_4bit ( input [3:0] a, b, output [3:0] sum, output carry ); // 调用子模块(全加器) full_adder fa0 (a[0], b[0], 1'b0, sum[0], c1); full_adder fa1 (a[1], b[1], c1, sum[1], c2); full_adder fa2 (a[2], b[2], c2, sum[2], c3); full_adder fa3 (a[3], b[3], c3, sum[3], carry); endmodule
1
2
3
4
5
6
7
8
9
10
11子模块:实现全加器
module full_adder ( input a, b, cin, output sum, cout ); assign sum = a ^ b ^ cin; assign cout = (a & b) | (cin & (a ^ b)); endmodule
1
2
3
4
5
6
7
# 6. 模块的测试:Testbench
作用:模拟输入信号,验证模块功能。
示例:测试D触发器
module testbench;
reg clk, din; // 测试输入(用reg驱动)
wire q; // 测试输出
// 1. 实例化被测模块
dff uut (.clk(clk), .din(din), .q(q));
// 2. 生成时钟(周期=10ns)
initial begin
clk = 0;
forever #5 clk = ~clk; // 每5ns翻转一次
end
// 3. 提供测试激励
initial begin
din = 0;
#10 din = 1; // 10ns后输入变1
#10 din = 0;
#20 $finish; // 40ns后结束仿真
end
// 4. 打印结果
initial begin
$monitor("Time=%t, din=%b, q=%b", $time, din, q);
end
endmodule
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
输出结果:
Time=0, din=0, q=x
Time=5, din=0, q=0 // 第一个时钟上升沿
Time=15, din=1, q=1 // 第二个时钟上升沿
Time=25, din=0, q=0 // 第三个时钟上升沿
2
3
4
# 总结:模块设计要点
- 模块声明:
module 模块名(端口列表);
- 端口方向:
input
/output
/inout
,输出可声明为reg
。 - 实例化:推荐用命名连接(
.端口名(信号名)
)。 - 测试方法:
- 用Testbench生成时钟和激励。
- 通过
$monitor
或波形图观察输出。
常见错误:
- 错误1:在
always
块中对wire
赋值 → 应改用reg
。 - 错误2:模块实例化时端口顺序不匹配 → 用命名连接避免。