//以下是编译指令,定义时间单位和时间精度
`timescale 1ns / 100ps
//以下是module名称, 端口列表
module HelloVlog ( Clock, Reset_n, A_in, B_in, Sel_in, A_xor_out, B_xor_out );
//以下是输入和输出端口声明
input Clock;
input Reset_n;
input [1:0] A_in;
input [1:0] B_in;
input Sel_in;
output A_xor_out;
output B_xor_out;
//以下是线网和寄存器声明
wire A_xor_wire;
wire B_xor_wire;
wire [1:0] result;
reg eq0, eq1, eq2, eq3;
reg A_xor_out;
reg B_xor_out;
//行为描述, DFF_A
always @ (posedge Clock or negedge Reset_n)
if (~Reset_n)
A_xor_out <= 0;
else
A_xor_out <= A_xor_wire;
//行为描述, DFF_B
always @ (posedge Clock or negedge Reset_n)
if (~Reset_n)
B_xor_out <= 0;
else
B_xor_out <= B_xor_wire;
//数据流描述, XOR_A
assign #1 A_xor_wire = eq0 ^ eq1 ;
//结构化描述, XOR_B
xor #1 XOR_B ( B_xor_wire, eq2, eq3 );
//数据流描述, MUX2
assign #3 result = (Sel_in) ? B_in : A_in;
//行为描述, DECODE2
always @ ( result )
begin
case ( result )
2'b00 : begin
{eq3, eq2, eq1, eq0} = #2 4'b0001 ;
$display ("At time %t - ",$time,"eq0 = 1");
end
2'b01 : begin
{eq3, eq2, eq1, eq0} = #2 4'b0010 ;
$display ("At time %t - ",$time,"eq1 = 1");
end
2'b10 : begin
{eq3, eq2, eq1, eq0} = #2 4'b0100 ;
$display ("At time %t - ",$time,"eq2 = 1");
end
2'b11 : begin
{eq3, eq2, eq1, eq0} = #2 4'b1000 ;
$display ("At time %t - ",$time,"eq3 = 1");
end
default : ;
endcase
end
//module结束
endmodule
三种描述方式
- 数据流描述:采用assign语句,称为连续赋值语句;
- 行为描述:使用always或者initial语句块,称为过程赋值语句;
- 结构化描述:例化已有的功能模块;结构化描述分为三种:Module实例化,实例化已有module;门实例化,实例化基本的门电路原语;用户定义原语(UDP)实例化。
基本语词
Verilog HDL对大小写敏感,VHDL对大小写不敏感。Verilog中所有关键字(保留字)都是小写。内部信号名(标识符)大小写均可,标识符可以是字母、数字、$(美元符号)及_(下划线)的任意组合,同时需要以字母或者下划线开头。
端口
- input:从外界读取数据,在模块内不可写
- output: 向外界发送数据,在模块内不可读
- inout:可读可写
端口默认是wire型,如果希望输出端口能保存数据,则把它声明成reg型。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uY9wNhpQ-1623397829239)(https://i.loli.net/2021/05/28/O534ZmKzFhWT9MG.png)]
逻辑值与常量
逻辑值
在二进制计数中,单位逻辑值只有"1"和"0"两种状态。而Verilog中,为了对电路进行精确建模,增加了两种逻辑状态"X"和"Z"。
- "X"表示未知值或者不关心,用作信号状态时表示未知,用在casex或者casez的条件判断中,表示不关心;
- "z"表示高阻状态,也就是没有任何驱动,通常用来对三态总线进行建模。
常量
Verilog中常量有三种:
- 整数型
- 实数型
- 字符串型
书写格式:
长度’数值符号 数字
- 长度可有可无;
- 数值符号中,h-十六进制、0-八进制、b-二进制、d-十进制。如果长度比后面数字的实际位数多,则自动在数字左边补0;如果位数少,则自动截断数字左边超出的位数。
- 数字中的下划线没有意义,用来增强可读性。
- Verilog中实数型变量可以采用十进制,也可以采用科学计数法,如:13_2.18e2表示13218
- 字符串是双引号中的字符序列,例如:“Hello World”。字符串是8位ASCII码值的序列,"Hello World"需要11个字节存储,存储方法如下:
reg[1:8*11]Message;
.......
Message = "Hello World";
变量类型
Verilog中有两个变量类型:
- 线网型:表示电路间的物理连线;
- 寄存器型:Verilog中一个抽象的存储数据单元。
遵循规则:
- 在always和initial语句中赋值的变量,一定是寄存器变量;
- 在assign中赋值的一定是线网变量。
线网类型
- 如果没有驱动器连接到网络类型的变量上,则该变量就是高阻的,即其值为z。常用的网络数据类型包括wire型和tri型。这两种变量都是用于连接器件单元,它们具有相同的语法格式和功能。
- 之所以提供这两种名字来表达相同的概念是为了与模型中所使用的变量的实际情况相一致。wire型变量通常是用来表示单个门驱动或连续赋值语句驱动的网络型数据,tri型变量则用来表示多驱动器驱动的网络型数据。
- 如果wire型或tri型变量没有定义逻辑强度(logic strength),在多驱动源的情况下,逻辑值会发生冲突从而产生不确定值。
- wire型数据常用来表示用于以assign关键字指定的组合逻辑信号。Verilog程序模块中输入输出信号类型缺省时自动定义为wire型。wire型信号可以用作任何方程式的输入,也可以用作“assign”语句或实例元件的输出。
寄存器类型
寄存器类型在Verilog语言中同城表示一个存储数据的空间。
- reg:最常用的寄存器类型数据,可以是1位或多位,或是二位数组(存储器);
reg[3:0] mem[1:7] //定义一个存储器,地址位0~7,每个存储单元4位
- integer:整形数据,存储一个至少32位整数;
- time:时间类型,存储一个至少64位的时间值;
- real,realtime:实数和实数时间寄存器。
在Verilog中不存在一条语句可以对整个存储器赋值必须对每个单元独立赋值。integer变量通常用于高层次建模,也常用于for语句的索引中:
initial
begin:ACCESS //此处begin-end块必须有一模块名,因为块中定义了局部变量
integer i;
for(i = 0;i <= 7;i = i+1)
begin
mem[i]=i;
end
end
变量的物理意义
“线网”变量可以理解为电路模块中的连线,但“寄存器”并严格对应电路上的存储单元,包括触发器(flip-flop)或者锁存器(latch)。从纯粹的语言表达角度说,寄存器类型变量的值,从一个赋值到下一个赋值被保存下来,而且在仿真过程中不会丢失。在Verilog仿真工具对语言进行仿真时,寄存器类型的变量会占用仿真环境的物理内存,这与C语言类似。寄存器在被赋值后,便一直保存在内存中,保持该值不变,直到再次赋值。线网类型是不占用仿真内存的,它的值是由当前所有驱动该线网的其他变量决定的。
驱动与赋值
- 线网是被驱动的,该值不被保持,在任意一个仿真步进上都需要重新计算;
- 寄存器是被赋值的,且该值在仿真过程中被保持,直到下一次赋值。
如对eq0和eq1进行异或:
assign A_xor_wire = eq0 ^ eq1;
也可以用另一种描述方式:
reg A_xor_wire;
always @(eq0 or eq1)
A_xor_wire = eq0 ^ eq1;
这两者描述的目的一样,都是一个异或门。
- 第一种使用assign语句,这是连续赋值语句,实际上是连续驱动过程,也就是说,在仿真的任意时刻,当前时刻的eq0和eq1相异或的结果决定了线网变量A_xor_wire的值,不管eq0、eq1变化与否,这个驱动过程一直存在,因此称为连续驱动;
- 第二种使用always语句,有,敏感信号列表@(eq0 or eq1),因此,这个语句只有在eq0、eq1发生变化时才会执行。在其他时刻A_xor_wire保持不不变。从仿真意义上来讲,需要一个存储单元,也就是所说的寄存器,来保存A_xor_wire变量的中间值;
- 不管使用哪种方式,所描述的是一样的组合逻辑电路,第二种虽然在语言种被定义为reg型,但并不是对应硬件上的触发器,而实Verilog语言仿真语义上的寄存器概念。
参数parameter
- 参数是一种常量,通常出现在module内部,常被用作定义状态机的状态、数据位宽和延时大小等。
- 参数的值可以在编译时被改变,因此它又经常被用于一些参数可调的模块中,在实例化模块时,可以根据需要配置参数
- 与define不同,define是全局的定义,而parameter是出现在模块内部的局部定义,而且可以被灵活改变。
parameter delay = 2; //局部变量
`define a 8 //全局变量
Verilog中的并发与顺序
- 与在处理器上运行的软件不同的是,硬件电路之间的工作是并行发。
- 为了描述硬件的并行性,Verilog语言本身就具有并发的特性。在Verilog语言的module中,所有的描述语言(包括连续赋值语言,行为语句块:always和initial,模块实例块等)之间是并行发生的任何功描述语句,在Verilog的module中的顺序都不重要。
在语句块(always和initial)内部,则可以存在两种语句组:
- begin…end:顺序语句组;
- fork…join:并行语句组。
操作数、操作符和表达式
操作符
大部分与C语言相同
& //按位与
^ //按位异或
| //按位或
~ //按位取反
&& //逻辑与
|| //逻辑或
?: //条件操作符
^~或~^ //按位异或非(同或)
{m,n} //m和n连接起来,产生更大的向量
{n{m}} //将m重复n次
若& | ^只有一个操作数,称为缩减运算。运算对数组的所有的元素都执行并返回一位结果。
- &m是将m中所有比特相与,最后的结果为1bit。例:&4‘b1111 = 1&1&1&1 = 1’b1,&4’b1101 = 1&1&0&1 = 1’b0。
- |m
- ^m
二进制数值
一个6位二进制整形变量中:
- 无符号数能表示范围是:0~63;
- 有符号数采用二进制补码方式,能表示范围是-32~31。最高位是符号位,1-负数,0-正数。
操作数
Verilog中操作数有如下几种:
- 常数
- 参数
- 线网
- 寄存器
- 向量的位选择
- 向量的部分选择
- 存储器单元
- 系统函数或者用户自定义函数调用的返回值
在选择操作数时,需要注意的事:操作数的极性。 - 无符号数
- 线网类型
- 一般寄存器变量
- 基数格式表示形式的整数常数
- 有符号数
- 整型寄存器变量
- 十进制形式的整型常量
- 首先讨论常量,如果采用基数格式表示一个数,例如:-4’d12,其二进制表示方式是:1111_1111_1111_1111_1111_1111_1111_0100(1100的补码),由于基数格式的整数为无符号数,因此-4’d12的值就是十进制的4294967284。
- 当采用普通十进制数来表示-12时,虽然它的二进制表示与上面的数相同,但-12是个有符号数,它在运算时就表示十进制的-12。
reg[4:0] Opreg; //一个5位的reg型,存储无符号数integer Opint; //一个32位的integer型,存储有符号数//做如下运算Opreg = -4'd12/4; //被赋值29,(-4'd12/4)的最低5位Opint = -4'd12/4; //被赋值1073741821.共32位Opreg = -12/4; //被赋值29,(-12/4)的最低5位Opint = -12/4; //被赋值-3,采用32位的二进制补码表示
系统任务和系统函数
在Verilog中预先定义了一些任务或者函数,用于完成一些特殊的功能,它们被称为系统任务和系统函数。
显示任务
$display是显示任务,通常用来显示变量值、字符串,以及仿真时间等信息。
$display("At tine %t-",$time,"eq0=1"); //显示时间,%t时间格式,$time产生模拟时间的系统函数,返回值显示在字符串中%t位置$diaplay("The value of ABC is %d",ABC); //显示当前ABC变量值
文件输入/输出任务
系统函数 f o p e n fopen fopen用于打开一个文件,并返回一个整数的文件指针。然后, f d i s p l a y fdisplay fdisplay就可以使用这个文件指针向文件中写入信息。写完后,则用 f c l o s e fclose fclose关闭这个文件。
integer Write_Out_File;//定义一个文件指针Write_Out_File = $fopen("Write_Out_File.txt");$fdisplay(Write_Out_File,"@%h\n%h",Mpi_addr,Data_in);//Mpi_addr,Data_in分别显示在两个%h的位置$fclose(Write_Out_File);
可以使用 r e a d m e m b readmemb readmemb或者 r e a d m e m h readmemh readmemh从文件中读数据,这个文件中数据格式是一定的。如
reg [7:0] DataSource[0:47];$readmemh("Read_In_File.txt",DataSource);//将Read_In_File.txt中数据读入到DataSource中,然后可直接使用
Read_In_File.txt数据文件格式如下,@2f表示地址是十六进制,24表示该地址的数据。
@2f24@2e81...
其他系统任务和函数
仿真控制任务
- $finish - 使仿真器推出
- $stop - 使仿真挂起
时序验证任务和仿真时间函数
Verilog仿真器可以检查设计时序,以及返回当前仿真时间
- $setup - 系统任务用来检查建立时间
- $hold - 系统任务用来检查保持时间
- $time - 系统函数用来返回一个64位的模拟时间
概率分布函数
- $random - 系统函数可以用来返回一个32位的有符号整型随机数。