verilog 简介
Verilog 的由来可以追溯到 1980 年代初期。它由 Phil Moorby 开发,并于 1984 年由 Gateway Design Automation 公司首次推出。最初,它旨在帮助工程师更高效地描述和模拟数字电路设计。Verilog 的设计灵感来自于之前的硬件描述语言,如 VHDL,但它更侧重于简化设计和仿真过程。1990 年,Verilog 被 IEEE 标准化,成为 IEEE 1364 标准,并随着时间的发展逐渐增加了更多的功能和改进。
Verilog 和 VHDL 区别
Verilog:
语法风格: 语法类似于 C 语言,更简洁,适合习惯编程的工程师。它强调设计的行为和结构。
抽象层次: 更适合于底层设计,强调硬件行为和时序。
类型系统: 类型系统较为宽松,支持多种数据类型,但不如 VHDL 强类型。
标准化和扩展: 最初由 Gateway Design Automation 开发,后被 IEEE 标准化为 IEEE 1364,后续版本包括 SystemVerilog。
VHDL:
语法风格: 语法类似于 Ada 语言,更冗长且强类型,适合需要严格类型检查和更复杂建模的设计。
抽象层次: 支持更高层次的抽象,如数据流建模和高级功能,适用于更复杂的设计和验证
类型系统: 具有严格的类型检查,支持自定义数据类型,能提供更高的设计安全性和准确性。
标准化和扩展: 由美国国防部开发并标准化为 IEEE 1076,后续版本包括 VHDL-2008 和其他扩展。
Verilog 和 C 的区别
Verilog 和 C 的主要区别在于它们的应用领域和执行模型。Verilog 是一种硬件描述语言,用于设计和模拟数字电路,它通过描述电路的结构和行为来实现硬件设计的目标,并且支持并行处理和时序控制。相比之下,C 语言是一种通用编程语言,主要用于软件开发,它专注于编写算法和处理数据,通常以顺序执行的方式运行程序。Verilog 的代码描述的是电路的硬件行为,而 C 的代码则是程序的逻辑和流程。
Verilog 的主要特性
- 行为级描述:使用过程化结构(如 always 块和 initial 块)来描述电路的功能,而不关注具体的硬件实现。这种方式更接近于软件编程,适用于复杂的逻辑行为
- 数据流描述:使用连续赋值语句(assign)来定义信号之间的关系,适用于简单的组合逻辑。这种方式强调数据如何流动而不是具体的时序。
- 结构化方式:使用门级和模块实例化语句来描述电路的层次结构。这种方式涉及到具体的硬件组件,例如逻辑门、寄存器等。
两类数据类型:
- 线网(wire)数据类型 ---- 线网表示物理元件之间的连线。
- 与寄存器(reg)数据类型---- 寄存器表示抽象的数据存储元件。
- 设计逻辑功能时,设计者可不用关心不影响逻辑功能的因素,例如工艺、温度等。
verilog 格式
- Verilog 是区分大小写的语言,变量名和关键字的大小写会影响其识别。
- 白符(如换行、制表符、空格)在语法中没有实际意义,可以在编译阶段被忽略。
- 可以在一行内编写代码,也可以跨多行编写,具体格式由开发者选择。
wire [1:0] X ;
assign X = (a == 1'b0) ? 2'b01 :
(b==1'b0) ? 2'b10 :
2'b11 ;
2
3
4
一、数字
1.1、进制
1、二进制整数(b或B)
2、十进制整数(d或D)
3、十六进制整数(h或H)
4、八进制整数(o或0)
例如:
8'b10101010 //位宽为8的数的二进制表示, 'b表示二进制
8'ha2 //位宽为8的数的十六进制,'h表示十六进制
2
3
1.2、字母
在数字电路中有2个特殊的字母 x 和 z 值
x:代表不定值,有可能是高电平,也有可能是低电平
z:代表高阻值,外部没有激励信号是一个悬空状态。z还有一种表达方式是可以写作 ?
8'b101010x0 //位宽为8的二进制数从低位数起第二位为不定值
8'b101010z1 //位宽为8的二进制数从低位数起第二位为高阻值
12'dz //位宽为12的十进制数其值为高阻值(第一种表达方式)
12'd? //位宽为12的十进制数其值为高阻值(第二种表达方式)
2
3
4
1.3、下划线
下划线用来分隔开数的表达以提高程序的可读性。
8'b1111_1010 //正确格式
8'b_0011_1010 //错误格式
2
二、数据类型
我们这里主要介绍常用的数据类型,其余的请自行百度。
2.1、wire
wire 类型用于表示硬件单元之间的物理连线,通常用于连接模块的输出和输入。
注:线网型还有其他数据类型,包括 wand、wor、triand、trireg 等。
wire 变量必须由一个连续的驱动源(如另一个模块的输出或组合逻辑)来驱动。如果没有任何驱动连接到 wire 变量,其缺省值为高阻态("Z"),表示该线没有被驱动。
wire A;
wire B = 1'b1;
2
2.2、reg
reg 类型用于表示存储单元,能够保持数据的原值。
reg a;
reg b,c;
2
2.3、parameter
parameter 是一种用于定义常量的类型。它在模块中被视为参数,可以用于设置模块的行为或特性。
parameter 的值在编译时确定,不能在运行时改变。它使得代码更加灵活和可重用,因为可以通过更改参数值来调整模块的功能。
局部参数用 localparam 来声明,其作用和用法与 parameter 相同,区别在于它的值不能被改变。所以当参数只在本模块中调用时,可用 localparam 来说明。
parameter data = 10'd1 ;
parameter a=1,b=2,c=3,d=4 ;
parameter size = data * 10 ;
2
3
2.4、数组
在 Verilog 中,可以声明多种数据类型的数组,包括 reg、wire、integer、time 和 real 类型。
reg 数组:用于存储数据的数组
reg [7:0] my_reg_array[0:3]; // 4个8位寄存器
wire 数组:用于连接信号的数组
wire my_wire_array[0:3]; // 4个线网
integer 数组:用于存储整数的数组。
integer my_integer_array[0:5]; // 6个整数
time 数组:用于存储时间值的数组。
time my_time_array[0:2]; // 3个时间值
real 数组:用于存储实数的数组。
real my_real_array[0:1]; // 2个实数
二维 reg 数组
reg [7:0] my_2d_array[0:3][0:2]; // 4x3的8位寄存器数组
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2.5、字符串
- 字符宽度:每个字符使用 8 位也就是1 字节,例如,字符串"wiki.lckfb.com"其中包含14个字符,则需要 14 * 8 = 112位(14字节)的寄存器变量。
注:
- 用于存储字符串的 reg 变量时,宽度必须足够大以容纳整个字符串。
- 如果 reg 变量的宽度大于字符串长度,剩余的位将用 0 填充。
- 如果宽度小于字符串长度,字符串将被截断,左侧的字符将被丢弃。
reg [111:0] string; // 声明一个足够大的寄存器变量来存储字符串
initial begin
string = "wiki.lckfb.com"; // 将字符串赋值给寄存器
end
2
3
4
使用事项:
- 字符串不能跨行,即字符串中不能包含回车符。
- 赋值字符串时,如果字符串太长或包含非法字符,可能导致编译错误。
- 如果需要在字符串中显示这些特殊的字符,则需要在前面加前缀转义字符 \ 。如下表所示:
三、运算符
3.1、位拼接运算符(Concatation)
位拼接运算符 {}
例如:
a = {1,2,3,4,5,6}
b = {a[0],a[2:5],a[1]}
这个时候b={1,3,4,5,6,2},就当于把a中第1位的数据拿到最后一位去。{}常用于拼接数据
2
3
4
5
6
3.2、缩减运算符(reduction operator)
例如:
reg [3:0] A;
reg B;
B = &A 等价于 B = ((A[0] & A[1]) & A[2]) & A[3];
2
3
4
3.3、移位运算符(Shift operators)
>> (右移)、<<(左移)、>>>(算数右移)、<<<(算数左移)
在Verilog-1995 中移位运算符只有两个,左移和右移。其用法为 A>>n 表示把操作数A右移n位,该移位是逻辑移位,移出的位用0添补。 在Verilog-2001 中增加算术运算符">>>“和”>>>",对于有符号数,执行算术移位操作时,将符号位填补移出的位,以保持数值的符号。
4’b0001<<1 = 5’b00010; //左移1位后用0填补低位
4’b0001<<2 = 6’b000100; //左移2位后用00填补低位
1<<6 = 32’b1000000; //左移6位后用000000填补低位
4’b1000>>1 = 4’b0100; //右移1位后,低1位丢失,高1位用0填补
4’b1000>>4 = 4’b0000; //右移4位后,低4位丢失,高4位用0填补
2
3
4
5
3.4、位、逻辑、关系、等式、位移运算符
算术运算符(+,-,×,/,%)
赋值运算符(=,<=)
关系运算符(>,<,>=,<=)
逻辑运算符(&&,||,!)
条件运算符(?:)
位运算符(~,|,^,&,^~)
移位运算符(<<,>>)
拼接运算符({ })
2
3
4
5
6
7
8
在Verilog中还有一个常用的三目运算符
例如
A = B ? C:T
2
3.5、优先级(位、逻辑、关系、等式、位移运算符)
学过C的朋友都知道,C也运算优先级,而Verilog也有优先级的说法。
四、标识符
- 标识符可以由字母、数字、美元符号 ($) 和下划线 (_) 组成。
- 标识符是区分大小写的,例如 myVar 和 myvar 被视为不同的标识符。
reg A ; //reg 为关键字, A 为标识符
reg jlc; //jlc 为标识符
reg JLC; //JLC 与 jlc 是 2 个不同的标识符
2
3
五、关键字
Verilog 和 C 语言类似,都因编写需要定义了一系列保留字,叫做关键字(或关键词)。这些保留字是识别语法的关键。列出了 Verilog 中的关键字,如下表所示。
常用的关键字:
module 模块定义开始
endmodule 模块定义结束
input 输入端口定义
output 输出端口定义
inout 双向端口定义
parameter 参数定义
wire 信号定义
reg 信号定义
always 产生reg信号语句的关键字
assign 产生wire信号语句的关键字
begin 语句的起始标志 等价于C语言中的 {
end 语句的结束标志 等价于C语言中的 }
case case语句起始标记
endcase case语句结束标记
default case语句分支,和switch中的default一样作用
if 判断语句
else
for 循环语句
posedge 是 "positive edge" 的缩写,指的是信号从低电平变为高电平的瞬间,也就是上升沿。
negedge 是 "negative edge" 的缩写,指的是信号从高电平变为低电平的瞬间,也就是下降沿。
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
27
28
29
六、阻塞赋值(Blocking)
在阻塞赋值中,赋值操作会立即执行,并且在完成之前会阻止代码的进一步执行。也就是说,后续的代码必须等待当前赋值操作完成后才能继续执行。通常使用 = 符号表示阻塞赋值。
a = b; // 阻塞赋值,a 会等 b 的值被赋值完成后再继续执行下一条语句
七、非阻塞赋值(Non-Blocking)
与阻塞赋值不同,非阻塞赋值允许赋值操作在后台异步执行,同时不会阻止后续代码的执行。通常使用 <= 符号表示非阻塞赋值。
a <= b; // 非阻塞赋值,a 会在时钟上升沿后更新为 b 的值
c <= d; // 非阻塞赋值,c 也会在时钟上升沿后更新为 d 的值
2
应用场景:
阻塞赋值:通常用于组合逻辑,因为它确保了赋值的顺序性,即一个信号的改变会影响到后续的计算。
非阻塞赋值:常用于时序逻辑,如寄存器和触发器,因为它允许在同一时间片内并行计算多个信号的RHS,然后在下一时刻统一更新,避免了不必要的毛刺(glitch)。
八、assign 和 always 区别
assign
类型:用于组合逻辑,语句使用时不能带时钟。
功能:在组合逻辑中,assign 语句用于为变量(通常是 wire 类型)赋值。
特点:assign 语句是连续赋值,它会自动更新变量的值,当右边的表达式变化时,左边的变量会立即更新。
always
类型:用于组合逻辑和时序逻辑,语句可以带时钟,也可以不带时钟。
功能:always 语句块用于描述在特定条件下(如时钟边沿或信号变化)执行的逻辑。
特点:always 块可以包含阻塞赋值(=)或非阻塞赋值(<=),并且用于创建时序逻辑或复杂的组合逻辑。
九、注释
单行注释:
// reg A;
多行注释:
/*
不靠买板赚钱
以培养中国工程师为己任
*/
reg A;
2
3
4
5
6