硬件描述语言HDL(Hardware Description Language)是一种利用形式化方法来描述电路和数字逻辑系统的语言
Verilog HDL 既是一种行为描述语言也是一种结构描述语言
主要作用:用于编写硬件电路系统的设计文件,建立电子系统行为级的仿真模型
Verilog HDL语言直接对应于现代电路设计的四个层次,将电路抽象成为五级
一个设计是由一个个模块(module)构成的。一个模块的设计如下:
模块具备以下特点:
Top-down设计思想:由于模块的以上特点,因此大型的数字电路设计可以通过设计不同的小模块来完成,最后通过顶层模块调用子模块来实现整体功能。
接口描述部分需要包含两方面的内容:
端口的定义部分;
adder8(cout,sum,a,b,cin);
adder8是模块名
( )内是该模块的端口声明;
( )内定义了该模块的管脚名,是该模块与其他模块通讯的外部接口,相当于器件的pin口;
模块的内容,包括I/O声明,内部信号、调用模块等的声明部分和功能定义语句;
input [7:0] a,b;input cin;output count;
// 其中input、output、inout是保留字,用于定义管脚信号的流向
// input [7:0] a,b; [n:0]表示该信号的位宽(总线或单根信号线)
// input cin;
// output count;
// 其中的input,output是保留字
可以将端口的定义部分和模块内容的定义和在一起,如以下写法:
module Signal_release(input CLK,input RST,input[8:0] signal,output reg [10:0]resignal);
assign {cout,sum}=a+b+cin;
用来产生各种逻辑(组合逻辑和时序逻辑),可以用多种方法进行描述,还可以实例化一个器件。在逻辑功能描述中,主要用到assign和always两个语句。以下提供逻辑功能描述的三种方法:
assign x = (b & ~c);//连续赋值语句常用于描述组合逻辑
and myand3 (f,a,b,c);
// and 是 门关键字
// myand3 是例化元件名
// 门元件例化
值得注意的是,每个实例元件的名字必须唯一。以避免与其他调用元件的实例相混淆。
always@(posedge clk)
begin
if(load)
out = data;
else
out = data + 1 + cin;
end
"always"块语句与assign语句是并发执行的,assign语句一定要放在“always”块语句之外
“always”块语句常用于描述时序逻辑,也可用于描述组合逻辑
定义:通过对电路结构的描述来建模。即通过对器件的调用(HDL概念称为例化),并使用线网来连接各器件的描述方式。
举例:设计一个1bit全加器的电路
首先我们设计由门电路设计一个1bit全加器
module FullAdditor(A,B,Cin,Sum,Cout);
input A;
input B;
input Cin;
output Sum;
output Cout;
wire S1,T1,T2,T3;
xor x1(S1,A,B);
xor x2(Sum,S1,Cin);
and A1(T3,A,B);
and A2(T2,B,Cin);
and A3(T1,A,Cin);
or O1(cout,T1,T2,T3);
endmodule
电路图表示,一个全加器由两个异或门、三个与门、一个或门构成。
S1,T1,T2,T3是门与门之间的连线,xor,and,or是Verilog HDL内置的门器件。
xor 表示调用一个内置的异或门,器件名称xor,代码实例化名x1。括号内中的A、B是输入,S1是输出。
`timescasle 1ns/100ps
module FullAdditor(A,B,Cin,Sum,Cout)
input A,B,Cin;
output Sum,Cout;
wire S1,T1,T2,T3;
assign #2 S1 = A ^ B;
assign #2 Sum = S1 ^ Cin;
assign #2 T3 = A & B;
assign #2 T1 = A & Cin;
assign #2 T2 = B & Cin;
endmodule
Verilog语言描述的硬件电路,在实际工作时,一旦商店,各个元器件均并行开始工作。
Verilog语言各实例单元、assign语句都是并行执行的,即各语句的执行与语句之间的顺序无关。
上述代码中当A有个变化时,S1、T3、T1将同时变化,S1的变化又会造成Sum的变化。
行为方式的建模是指采用对信号行为级的描述(不是结构级的描述)的方式来建模。
一般把用initial块语句或always块语句描述的归为行为建模方式
module FullAdditor(A,B,Cin,Sum,Cout)
input A,B,Cin;
output Sum,Cout;
reg Sum,Cout;
always@(A or B or Cin)
begin
{cout,Sum} = A + B + Cin;
end
endmodule
在示例中对1bit全加器采用了更加高级的描述方式,即直接采用"+"来描述加法
{Cout,Sum}表示对位数的扩展,因为有两个1bit相加会得到两位数据,低位放在Sum变量,高位放在Cout中
在实际的设计中,往往是多种设计模型混合的。一般,对顶层设计,采用结构描述方式,对低层模块,可采用数据流、行为级或两者结合的方式:
顶层模块 (FullAdditor_2bits)采用结构描述方式
底层模块(FA)可采用结构描述、数据流描述或行为级描述。
module FullAdditor_2bits(FA,FB,FCin,FSum,FCout)
parameter SIZE = 2;
input [SIZE-1:0] FA;
input [SIZE-1:0] FB;
input FCin;
output [SIZE-1:0] FSum;
output FCout;
wire FTemp;
FA_struct FA1(.A(FA[1]),.B(FB[1]),.Cin(FCin),.Sum(FSum[1]),.Cout(Ftemp));
FA_struct FA2(.A(FA[2]),.B(FB[2]),.Cin(FTemp),.Sum(FSum[2]),.Cout(FCout));
endmodule
关键字:事先定义好的确认符,用来组织语言结构;或者用于定义Verilog HDL提供的门元件(如and,not,or,buf)。
如源文件名、模块名、端口名、变量名、常量名、实例名等
四种基本数据类型为:integer型,parameter型,reg型,wire型
在程序运行过程中,其值不能被改变的量,称为常量
数字(包括整数,x和z值,负数)
整数型常量(即整常数)的4种进制表现形式:
二进制整数(b或B)
十进制整数(d或D)
十六进制整数(h或H)
八进制整数(o或O)
表达方式 | 说明 | 举例 |
---|---|---|
<位宽>'<进制><数字> | 完整表达 | 8’hc5或8’b11000011 |
<进制><数字> | 缺省位宽,位宽由计算机系统决定,32位或64位置(一般) | hc5 |
<数字> | 缺省进制为十进制,位宽默认为32位 | 197 |
x和z值
x表示不定值,z表示高阻值
8b'1001xxxx或8'h9x
8b'1010zzzz或8'haz
当用二进制表示时,已表明位宽的数若用x或z表示某些位,则只有在最左边的x或z具有扩展性。为清晰可见,最好直接写出每一位的值。
8'bzx = 8'bzzzz_zzzx
8'b1x = 8'b0000_001x
"?"是z的另一个表示符号,建议在case语句中使用?表示高阻态z
case z (select)
4'b ???1:out = a;
4'b ??1?:out = b;
4'b ?1??:out = c;
4'b 1???:out = d;
endcase
负数
在位宽前加一个减号,即表示负数
如:-8’d5 //5的补数, = 8b’11111011
parameter常量,即符号常量
用parameter来定义一个标识符,代表一个常量—称为符号常量
parameter 参数名1 = 表达式,参数名2 = 表达式,……;
parameter addwidth = 16;//合法格式
parameter addwidth = datawidth*2;//非法格式
参数是本地的,其定义只在本模块内有效
常用参数来定义延迟时间和变量宽度
在程序运行过程中,其值可以改变的量,称为变量
数据类型有19种,常用的有3种
网络型(nets type)
寄存器(register type)
数组(memory type)
定义:输出始终随输入的变化而变化的变量。表示结构实体(如门)之间的物理连接。
nets型变量不能储存值
以wire型变量为例:
最常用的nets型变量,常用来表示以assign语句赋值的组合逻辑信号。
模块中的输入/输出信号类型缺省为wire型
可用做任何方程式的输入,或“assign”语句和实例元件的输出。
wire 数据名1,数据名2,……,数据名m; wire[n:1] 数据名1,数据名2,……,数据名m; // 每条总线的位宽为n,共有m条总线
定义:对应具有状态保持作用的电路元件(如触发器、寄存器等),常用来表示过程块语句(如initial,always,task,function)内的指定信号。
以reg型变量为例:
- 在过程块中被赋值的信号,往往代表触发器。也可以代表组合逻辑信号
reg[n:0] 数据名1,数据名2,……,数据名n; reg 数据名1,数据名2,……,数据名n;
- 用reg型变量生成触发器举例
module rw2(clk,d,out1,out2) input clk,d; output out1,out2; reg out1; wire out2; assign out2 = d & ~out1; always @(posedge clk) begin out1 <= d; end endmodule
不推荐使用数组类型
定义:由若干个相同宽度的reg型向量构成的数组
memory型变量可描述RAM,ROM和reg文件
memory型变量通过扩展reg型变量的地址范围来生成
reg[n-1:0] 存储器名 [m-1:0]
// 上述语句定义了一个存储器:每个存储单元位宽为n,存储单元的个数为m
memory型变量与reg型变量的区别
- 含义不同
reg[n-1:0] rega; //一个n位的寄存器 reg mema [n-1:0]; //由n个1位寄存器组成的存储器
- 赋值方式不同
如果要对某存储器中的存储单元进行读写操作,必须要指明该单元在存储器的地址
rega = 0; //合法赋值语句 mema = 0; //非法赋值语句 mema[8] = 1; //合法赋值语句 mema[1023:0] = 0; //合法赋值语句 // 上述 [ ]内就指明了存储单元的地址
+ , − , ∗ , / , % +,-,*,/,\% +,−,∗,/,%
进行算术运算时,若某操作数为不定值x,则整个结果也为x
逻辑运算符把它的操作数当作布尔变量
非零的操作数被认为是真(1’b1);
零被认为是假(1’b0);
不确定的操作数如4’bxx00,被认为是不确定的,记为1’bx;
但4’bxx11被认为是真,记为1’b1;
若关系为真,则返回值为1;
若关系为假,则返回值为0;
若某操作数为不定值x,则返回值为x
所有的关系运算符优先级别相同
关系运算符的优先级低于算术运算符
运算结果为1位的逻辑值1或0或x;
等于运算符()和全等运算符(=)的区别:
使用等于运算符,两个操作数必须逐位相等,结果才为1;若某些位为x或z,这结果为x
使用全等运算符,若两个操作数的相应位完全一致(即同时是1,或同时是0,或同时是x,或同时是z),则结果为1;否则为0
所有的等式运算符优先级相同
= = = 和 ! = = 运算符用于case表达式的判别,又称为“case等式运算符”
位运算结果与操作数位数相同。位运算符中的双目运算符 要求 对两个操作数的相应位逐位进行运算;
两个不同长度的操作数进行位运算时,将自动按右端对齐,位数少的操作数会在高位用0补齐;
若A = 5’b11001,B = 3’b101
则 A & B = (5’b11001) & (5’b00101) = 5b’00001
用法:A>>n 或 A< 可用重复法简化表达式,如:{4{w}}等同于{w,w,w,w} 还可用嵌套方法简化书写,如: {b,{3{a,b}}}等同于{b,{a,b},{a,b},{a,b}},也等同于{b,a,b,a,b,a,b} 块语句有两种: begin_end语句——标识顺序执行的语句 fork_join语句——标识并行执行的语句 用begin_end标识的块 块内的语句是顺序执行的 每条语句的延迟时间是相当于前一条语句的仿真时间而言的 直到最后一条语句执行完,程序流程控制才跳出该顺序块 块内的语句是同时执行的 块内每条延迟时间是相对于程序流程控制进入到块内的仿真时间而言的 延迟时间的长短决定了赋值语句的时序 按时间排序,延迟时间最长的语句将是最后一个执行的语句 case语句的变体: 在case语句中,分支表达式每一位的值都是确定的(要么为1,要么为0) 在casez语句中,若分支表达式某些位的值为高阻值z,则不考虑对这些位的比较 在casex语句中,若分支表达式某些位的值为z或不定值x,则不考虑对这些位的比较 在分支表达式中,可用"?"来标识x或z 用casez描述的数据选择器 避免生成锁存器的原则: 如果用到if语句,最好写上else项; 如果用到case语句,最好写上default项 用for语句初始化memory 连续执行一条或多条语句n次 无条件连续执行forever后面的语句或语句块 forever 语句常用在测试模块中产生周期性的波形,作为仿真激励型号 常用disable语句跳出循环 forever语句不同于always语句,其不能独立写在程序中,一般用在initial语句块中 举例:产生时钟信号 编译预处理语句以西文符号`开头;且在编译时,编译系统先对编译预处理预处理语句进行预处理,然后将处理结果和源程序一起进行编译。 用一个指定的标识符(即宏名)来代表一个字符串(即宏内容);`define语句可以写在模块定义的外面或里面。宏名的有效范围为定义命令之后到源文件结束。 宏定义的作用: 以一个简单的名字来代替一个长的字符串或复杂表达式 以一个有含义的名字代替没有含义的数字和符号 文件包含语句——一个源文件可将另一个源文件的全部内容包含进来 `include"文件名" 值得注意的是`include语句只能指定一个被包含的文件; 若要包含n个文件,需要n个`include语句 注意此处的实例化使用了 # 来改变一些特有的parameter参数 定义:时间尺度语句——用于定义跟在该命令后模块的时间单位和时间精度 定义格式`timescale<时间单位>/<时间精度> 时间精度至少要和时间单位一样精确,时间精度值不能大于时间单位值 `timescale语句应用举例 但希望能够对一些信号进行运算并输出多个结果(即有多个输出变量时),适合采用任务结构 常常利用任务来帮助实现结构化的模块设计,将批量的操作以任务的形式独立出来,使设计简单明了。 任务的定义与调用必须在一个module模块内 任务被调用,需列出端口名列表,且必须与任务定义的I/O变量一一对应 一个任务可以调用其他任务和函数 当任务启动时,由v,w和x传入的变量赋给了a,b,c; 当任务完成后,输出通过c、d和e赋给了x、y和z。 举例:系统任务 一般用法为 定义: 函数的目的是通过返回一个用于某表达式的值,来响应输入信号。适于对不同变量采取同一运算的操纵 函数在模块内部定义,通常在本模块中调用,也能根据按模块层次分级命名的函数名从其他模块调用。而任务只能在同一模块内定义与调用 函数的定义不能包含任何时间控制语句 时间控制语句:用延迟#,事件控制@或等待wait标识的语句 函数不能调用任务task 定义函数时至少有一个输入参量,且不能有任何输出或输入的双向变量 在函数的定义中必须有一条赋值语句,给函数中的一个内部寄存器赋以函数的结果值,该内部寄存器与函数同名 对于阶乘模块的测试模块 定义:包含一个或一个以上的声明语句(如:过程赋值语句、任务调用、条件语句和循环语句等),在运行的全过程中,在定时控制下被反复执行。 在always块中被赋值的只能是register型变量(如reg,integer,real,time) 每个always块在仿真一开始便开始执行,当执行完块中最后一个语句,继续从always块的开头执行。 如果always块中包含一个以上的语句,则这些语句必须放在begin_end或fork_join块中 敏感信号表达式又称为事件表达式或敏感表,当其值改变时,则执行一遍块内语句 在敏感信号表达式中应列出影响块内取值的所有信号 敏感信号不要为x或z,否则会阻挡进程 当always块有多个敏感信号时,一定要采用if - else if语句,而不能采用并列的if语句。否则易造成一个寄存器有多个时钟驱动,将出现编译错误 用于对reg型变量赋值 赋值符号为 非阻塞赋值在块结束时才完成赋值操作。这在上述代码中体现为,c的值比b的值落后一个时钟周期。 如果初始状态下a = 1,b = 0,c = 0。那么 第一次执行块语句时:b = 1;c = 0; 第二次执行块语句时:b = 1;c = 1; 因此a的值需要经过两个时钟周期才会传递给c。 赋值符号为 阻塞赋值在该语句结束时就完成赋值操作。在一个快语句中,如果有多条阻塞赋值语句,在前面的赋值语句没有完成之前,后面的语句就不能被执行。 在上述代码中,初始状态下a = 1,b = 0,c = 0。那么 因此a的值会在第一次执行块语句时,传递给b和c。 一般可综合的阻塞赋值操作在RHS不能设定有延迟。因为本身阻塞语句已经具有顺序了。 所谓阻塞的概念是指在同一个always块中,其后面的赋值语句从概念上(即使不设定延迟)是在前一句赋值语句结束后再开始赋值的。 非阻塞赋值的操作可以看作两个步骤的过程: 在赋值时刻开始时,计算所有非阻塞赋值RHS表达式 在赋值时刻结束时,更新非阻塞赋值LHS表达式 非阻塞赋值操作只能用于对寄存器类型变量进行赋值,因此只能用在“initial”块和“always”块等过程块中。非阻塞赋值不允许用于连续赋值。 Verilog中没有“编译”的概念,而只有综合的概念。 综合的定义:将用HDL语言或图形方式描述的电路设计转换为实际门级电路(如触发器、逻辑门等),得到一个网表文件,用于进行适配(在实际器件中进行布局和布线) 在使用Verilog语言时,首先要有所要写的module在硬件上如何实现,而不是去想编译器如何去解释这个module。比如在决定reg是否定义时,需要考虑物理上是否存在这个寄存器。(9) 位拼接运算符
7. 块语句
(1) 顺序快
\\ 用顺序块和延迟控制组合产生一个时序波形
parameter d = 50;
reg[7:0] r;
begin
# d r = 'h35;
# d r = 'hE2;
# d r = 'h00;
# d r = 'hF7;
# d -> end_wave; //触发时间end_wave
end
(2) 并行块
\\ 用并行块和延迟控制组合产生一个时序波形
reg[7:0] r;
begin
# 50 r = 'h35;
# 100 r = 'hE2;
# 150 r = 'h00;
# 200 r = 'hF7;
# 250 -> end_wave; //触发时间end_wave
end
8. 条件语句
(1) if 语句
(2)case 语句
module mux_z(out,a,b,c,d,select)
output out;
input a,b,c,d,e;
input[3:0] select;
reg out;//必须声明
always@(select[3:0] or a or b or c or d or e)
begin
casez(select)
4'b???1:out = a;
4'b??1?:out = b;
4'b?1??:out = c;
4'b1???:out = d;
default:out = e;
endcase
end
endmodule
9. 循环语句
(1) for语句
begin:init_mem //init_mem是块的名字
reg[7:0]tempi; //存储器的地址变量
for(tempi = 0;tempi
(2) repeat语句
(3) while语句
(4) forever语句
initial
begin:Clocking
clk = 0;
#10 forever #10 clk =~clk
end
initial
begin:Stimulus
disable Clocking;//停止时钟
end
10. 编译预处理预计
(1) 宏定义
module test();
reg a,b,c,d,e,out;
`define expression a+b+c+d
// 值得注意的是,不能写成:`define expression a+b+c+d;,不能有分号。否则会使得分号也并入语句中,造成下面out赋值语句出错
assign out = `expression + e;
endmodule;
(2) `include语句
(4) `timescale语句
`timescale 1ps/1ns // 非法
`timescale 1ns/1ps // 合法
`timescale 10ns/1ns // 时间单位为10ns,时间精度为1ns
...... // 省略号表示省略了结构上的代码
reg sel;
initial
begin
#10 sel = 0; // 在10ns x 10时刻,sel变量被赋值为0
#10 sel = 1; // 在10ns x 20时刻,sel变量被赋值为1
end
......
11. 任务语句及函数语句
(1) 任务(task)
// 任务定义
task my_task;
input a,b;
inout c;
output d,e;
......
<语句> // 执行任务工作相应的语句
......
c = foo1;
d = foo2; // 对任务的输出变量赋值
e = foo3;
endtask
// 任务调用
my_task(v,w,x,y,z);
$random
为系统任务,返回一个32位的带符号的随机数$random % b;
// 其中 b>0,它给出一个范围在-b+1~b+1的随机数
{$random} % b;
// {$random} % b通过位拼接操作,产生一个0~14之间的随机数
(2) 函数(function)
// 函数的定义
function[7:0] gefun;
input [7:0] x;
...... // 进行运算
gefun = cout; // 赋值语句
endfunction
// 函数的调用
assign number = gefun(rega);
// Count the number of 0 in rega[7...0]
module count0s_function(number,rega);
output [7:0] number;
input [7:0] rega;
function[7:0] gefun; // 函数定义
input[7:0] x; // 只有输入变量
reg[7:0] count;
integer i;
begin
count = 0;
for(i = 0;i<=7;i = i + 1)
if(x[i] == 1'b0) count = count = 1;
gefun = count; // 返回函数返回值
end
endfunction
assign number = gefun(rega); // 调用函数
endmodule
// factorial function
module tryfunct(result,clk,reset,n);
// 函数定义
function[31:0] factorial;
input[3:0] op;
reg[3:0] ina;
begin
factorial = op?1:0;
for(ina = 2;ina <= op;ina = ina + 1)
factorial = ina*factorial;
end
endfunction
output[31:0] result;
input[3:0] n;
input reset,clk;
reg[31:0]result;
always@(posedge clk)
begin
if(!reset) result <= 0;
else result <= factorial(n);
end
endmodule
// The test module of tryfunct.v
`include "./tryfunct.v"
`timescale 1ns/100ps
`define clk_cycle 50
module tryfuncttop;
reg[3:0] n,i;
reg reset,clk;
wire[31:0] result;
// initial 表示了对各变量进行初始化,并产生激励波形
initial
begin
n = 0;
reset = 1;
clk = 0;
#100 reset = 0; // 产生复位信号
#100 reset = 1;
for(i = 0;i<=15;i+1) // 产生激励波形
begin
#200 n = i;
end
#100 $stop
end
always #'clk_cycle clk = ~clk;// 产生时钟波形
endmodule
任务(task)
函数(function)
目的或用途
可计算多个结果值
通过返回一个值,来响应输入信号
输入与输出
可为各种类型(包括inout型)
至少有一个输入变量,但不能有任何output或inout型变量
被调用
只可在过程赋值语句中调用,不能再连续赋值语句中调用(assign语句)
可作为表达式中的一个操作数来调用,在过程赋值和连续赋值语句中均可调用
调用其他任务和函数
任务可调用其他任务和函数
函数可调用其他函数,但不可调用其他任务
返回值
不向表达式返回值
向调用它的表达式返回一个值
12. Verilog语言过程语句
initial说明语句
always说明语句
只执行一次
不断重复执行,直到仿真结束
(1) always块语句
always@(posedge clk or negedge clear)
begin
if(!clear) qout = 0; //异步清零
else qout = 1;
end
`define half_period 50
module half_clk_top;
reg reset,clk; // 输入信号
wire clk_out; // 输出信号
always #half_period clk = ~clk;
endmodule
module always_demo (counter,tick,clk);
output [7:0] counter;
output tick;
input clk;
reg [7:0] counter;
reg tick;
always@(posedge clk)
begin
tick = ~tick; // 产生二分频信号
counter = counter + 1; // 产生8位二进制计数器
end
endmodule
(2) initial语句
13. Verilog语言赋值语句
(1) 连续赋值语句
assign c = a&b;//a,b,c均为wire型变量
(2) 过程赋值语句
<=
,如b <= a
;always@(posedge clk)
begin
b <= a;
c <= b;
end
=
,如b = a
;always@(posedge clk)
begin
b = a;
c = b;
end
三、 Verilog的代码综合