Verilog HDL
文章目录
这里链接到Verilog HDL 设计 ,里面主要讲编程规范和设计方法;
更新履历
2017-10-13,创建并更新;
2019-05-09,更新第一部分,综述;
2020-11-01,更新丰富内容;
综述
硬件描述语言(Hardware Description Language)是硬件设计人员和电子设计自动化(EDA)工具之间的界面。其主要目的是用来编写设计文件,建立电子系统行为级的仿真模型。
在EDA技术领域汇总把用HDL语言建立的数字模型称为软核(Soft IP Core),把用HDL建模和综合后生成的网表称为固核(Firm IP Core),对这些模块的重复利用缩短了开发时间,提高了产品开发率,提高了设计效率。
语法巩固
模块(module)
作为高级语言的一种,Verilog
语言以模块集合的形式来描述数字系统,其中每一个模块都有接口部分,用来描述与其他模块之间的链接。
一般来说,一个文件就是一个模块,但并不绝对如此。这些模块是并行运行的,但通常用一个高层模块来定义一个封闭的系统,包括测试数据和硬件描述。这一高层模块将调用其他模块的实例。
模块代表硬件上的逻辑实体,其范围可以从简单的门到整个大的系统,比如一个计数器、一个存储子系统、一个微处理器等。
模块可以根据描述方法的不同定义成为行为型或结构型 (或者是二者的组合):
-
行为型模块通过传统的编程语言结构定义数字系统(模块)的状态,如使用
if
条件语句、赋值语句等。 -
结构型模块将数字系统(模块)的状态表达为具有层次概念的互相链接的子模块。其最底层的原件必须是基元或已定义过的行为型模块。
Verilog
的基元包括门电路,如与非门,和传输二极管(开关);
-
模块的结构如下:
module <模块名> (端口列表); <定义> <模块条目> endmodule // end of module name
其中,
<模块名>
是模块唯一性的标识符;<端口列表>
是输入,输出和双向端口的列表,这些端口用来与其他模块进行链接;<定义>
一段程序用来指定数据对象为寄存器类型、存储器类型、线性或过程模块,诸如函数块和任务块:而<模块条目>
可以是initial
结构、always
结构、连续赋值或模块实例。 -
下面是一个与非模块(NAND)的行为型描述,输出
out
是输入in1
和in2
相与后求反的结果。// 与非门的行为型描述 module NAND (in1,in2,out); input in1,in2; output out; // 连续赋值语句 assign out = ~(in1&in2): endmodule //end of NAND
in1,in2
和out
端口指定为线型的。assign
连续赋值语句不间断的监视等式右边变量,一旦其发生变化,右端表达式被重新赋值后结果传给等式左端进行输出。
Verilog语言的三种描述方法
-
结构型描述
是通过实例进行描述的方法。将
verilog
预定义的基元实例 嵌入到语言中,监视实例的输入,一旦其中任何一个发生变化,便重新运算并输出。 -
数据流型描述
是一种描述组合功能 的方法,
assign
连续赋值语句来实现。连续赋值语句完成如下的组合功能:等式右边的所有变量受持续监控,每当这些变量中有任何一个发生变化,整个表达式被重新赋值并送给等式左端。这种描述方法只能用来实现组合功能。 -
行为型描述
是一种使用高级语言 的方法,它和用软件编程语言描述没有什么不同。具有很强的通用性和有效性。它是通过行为实例来实现的,关键词是
always
,其含义是,一旦赋值给定,仿真器便等待变量的下一次变化,有无限循环之意。
数值
Verilog HDL
的数值集合有以下四个基本的值组成:
值 | 含义 |
---|---|
0 | 代表逻辑0或假装态 |
1 | 代表逻辑1或真状态 |
x | 逻辑不定态 |
z | 高阻态 |
reg型和wire型
Verilog
中变量的物理数据类型分为线型和寄存器型两种。这两种类型的变量在定义时要设置位宽,缺省值为一位 。变量的每一位可以是0,1,X或Z。X代表一个未被预置初始状态的变量或时由与两个或跟多个驱动装置试图将之设定为不同的值而引起的冲突型线型变量,Z代表高阻状态或浮空量。
-
线型数据包括:
wire,wand,wor等几种类型。在被一个以上激励源驱动时,不同的线型数据有各自决定其最终值的分辨办法。
-
寄存器型数据与线型数据的区别在于:
寄存器类型数据保持最后一次的赋值,而线型数据需要持续的驱动。
-
不同数据类型驱动规则图:
端口类型 | 模块外端 | 模块内端 |
---|---|---|
输入端口 | 线型/寄存器型 | 线型 |
输出端口 | 线型 | 线型/寄存器型 |
输入输出端口 | 线型 | 线型 |
- 只能由线型/寄存器型驱动线型
- 使用
assign
语句被连续赋值的变量必须是线型 的;在always,initial
程序块(行为模块)中用 = 赋值的变量必须是寄存器型的.
assign
assign
相当于连线,一般是将一个变量的值不间断的赋值给另一个变量,就像把这两个变量连在一起,所以习惯性的当作连线用,比如把一个模块的输出给另一个模块的输入。
assign
的功能属于组合逻辑的范畴,应用范围可以概括为以下三点:
- 连续赋值;
- 连线;
- 对
wire
型变量赋值,wire
是线网,相当于实际的连线,如果要用assign
直接连接,就用wire
型变量。wire
型变量的值随时变化。
要更好的把握assign
的使用,Verilog
中有几个要点需要深入理解和掌握:
- 在
Verilog module
中的所有过程快(如initial块和always块)、连续赋值语句(如assign语句)和实例引用都是并行进行的。在同一module
中着三者出现的先后顺序没有关系。 - 只有连续赋值语句
assign
和实例引用语句可以独立于过程块而存在与module
的功能定义部分。 - 连续赋值
assign
语句独立于过程块,所以不能在always
过程中使用assign
语句。
关系操作的逻辑运算
缩减运算操作符
缩减操作都是对于一个向量,或者说多比特(BIT)信号进行操作的。
名称 | Verilog关键词 |
---|---|
缩减与 | & |
缩减或 | | |
按位异或 | ^ |
-
缩减与(&):对于一个多位操作数进行缩减与操作,先将它的最高位与从次高位进行与操作,其结果再与第二次高位进行与操作,直到最低位。
例如,&(4‘b1011) 的结果为0。
-
缩减或(|):对于一个多位操作数进行缩减或操作,先将它的最高位与次高位进行或操作,其结果再与第二次高位进行或操作,直到最低位。
例如,|(4’b1011)的结果为1。
-
缩减异或(^):对于一个多位操作数进行缩减异或操作,先将它的最高位与次高位进行异或操作,其结果再与第二次高位进行异或操作,直到最低位。
例如,^(4’b1011)的结果为1。
部分关系操作的逻辑运算实现
- 利用缩减或操作可以判断一个数是否全为0
- 利用缩减与操作可以判断一个数是否全为1
- 利用缩减异或/同或操作可以判断一个数是否含有偶数/奇数个1
- 利用缩减或与按位异或可以判断两个数是否相等
相等与不相等的关系操作与逻辑操作实现
关系操作 | 逻辑操作 |
---|---|
a == b | !(|(a^b)) |
a != b | (|(a^b)) 或 |(a~^b) |
Verilog 中的倒序操作
拼接操作是Verilog
语法中经常用到的操作。对于拼接操作,一个自然而然的运用场景就是把一些逻辑上有联系的信号结合成“总线”。
例如,可以把这样很多控制信号搜集成控制总线:
Control_Bus = {enable, wr_enable, rd_enable, configure_enable};
RGB_Data = {8'hFF,8'h00,8'h00};
在模块中,利用" [ ] " 来提取信号,例如:
wr_enable_from_bus = Control_Bus[2];
移位操作与拼接操作
- 以逻辑左移为例:
a = 8'b1111_0000;
b = 8'b1100_0000;
b = (a<<2'd2); // 移位操作表示
b = {a[5:0],2'b00}; // 拼接操作表示
- 以循环左移为例:
a = 8'b1111_0010;
b = 8'b1100_1011;
b = {a[5:0],a[7:6]}; // 拼接操作表示
注意: 要用拼接操作实现,注释不能少。
拼接操作进行倒序
如果输入是一个若干比特的信号,要求输出是输入的倒序,那么可以这样完成:
input [7:0] forward,
output [7:0] reversed,
assign reversed = {forward[0],forward[1],forward[2],forward[3],
forward[4],forward[5],forward[6],forward[7]
};
该例子中的输入只有8 bit
,这样写似乎还可以接受。但是,如果输入是很宽的位数呢,该怎么办?
reversed[23] <= forward[0 ];
reversed[22] <= forward[1 ];
reversed[21] <= forward[2 ];
reversed[20] <= forward[3 ];
reversed[19] <= forward[4 ];
reversed[18] <= forward[5 ];
reversed[17] <= forward[6 ];
reversed[16] <= forward[7 ];
reversed[15] <= forward[8 ];
reversed[14] <= forward[9 ];
reversed[13] <= forward[10];
reversed[12] <= forward[11];
reversed[11] <= forward[12];
reversed[10] <= forward[13];
reversed[9 ] <= forward[14];
reversed[8 ] <= forward[15];
reversed[7 ] <= forward[16];
reversed[6 ] <= forward[17];
reversed[5 ] <= forward[18];
reversed[4 ] <= forward[19];
reversed[3 ] <= forward[20];
reversed[2 ] <= forward[21];
reversed[1 ] <= forward[22];
reversed[0 ] <= forward[23];
才24 bit
,还可以嘛,那么如果更多呢?1920
呢?
这时候我们可以进行这样的操作,那就是,用一个比较简单的print
函数,利用循环语句,加上需要变化的变量,找到规律后,执行此脚本,将打印的结果复制过来即可。
比如,写了一个Python的脚本,语法很简单:
for i in range(1920):
num = int(i/24)
num24 = 23 - i%24
if i%24 == 0:
print ("\tend\n\t8'd{: <2}\t:begin\n\t\t\treversed[{: <2}] <= buffer2[{: ^4}];".format(num,num24,i))
else:
print ("\t\t\treversed[{: <2}] <= forward[{: ^4}];".format(num24,i))
这样的脚本在解释器里运行之后,就会得到想要的Veriolg
语句,详情见文件result.txt
从此只要能找到数的关联,管你有多少,尽管放码过来,狮吼功加上一阳指:“你过来呀~”。
正当我们洋洋得意之时,迎来了当头棒喝,有简单的方法啊!
利用变量定义时的顺序
废话不多说,直接上代码:
module reverse_bits(
input [7:0] forward,
output [0:7] reversed
);
assign reversed = forward;
endmoudle //end of reverse_bits
所以刚才那24比特位宽的可以直接这样写:
reg [23:0] forward;
reg [0:23] reversed;
reversed [0:23] <= forward [23:0];
结束,对了,你为啥要写脚本?但是如果遇到的条件比较多,即使变量定义的时候倒序,脚本还是有用武之地的。
还有gennerate
在Verilog语法中,还有gennerate关键词,可以用来批量生成相同的逻辑实例;(一开始的时候不知道,后来使用之后是真香)
唯一可以被综合成电路的循环
这里介绍唯一可以被综合成电路的循环:常数循环次数的for循环 。关键词for
的一般形式是:
for(variable = start_value; continue_condition: cicrle_exress) begin
operations
end
其中,variable是一个变量名;start_value是变量的初始值;continue_condition是循环的继续条件;circle_express是每个循环的步进操作;operations是每次循环的操作。要想这个循环能被综合,for
的循环次数必须确定。
for(loop = 0; loop < 10; loop = loop + 1) //可综合,循环次数固定为10
for(loop = 0; loop < 10; loop = loop + 2) //可综合,循环次数固定为5
for(loop = variable_a; loop < 10; loop = loop + 1) //不可综合,初始值为可变变量
for(loop = 0; loop < variable_a; loop = loop + 1) //不可综合,结束条件为变量
for(loop = 0; loop < 10; loop = loop + variable_a) //不可综合,步长为变量