硬件描述语言Verilog
综合与仿真
Verilog,用代码的形式描述硬件的功能,最终在硬件电路上实现该功能。
综合:编写rtl代码,从库里面选择用到的问期间,这些期间会按照“逻辑”搭建成“门”电路。
不可综合或不推荐综合代码:initial, task/function, for, while/repeat/forever, integer, 三态门, casex/casez, force/wait/fork, #n
推荐使用的代码:reg/wire, parameter, assign/always, if else/case, 算术运算符(+ - * / %), 赋值运算符(组合逻辑= 时序逻辑<=), 关系运算符(<= != >), 逻辑运算符(&& || !), 位运算符(~ | ^ &), 移位运算符(<< >>), 拼接运算符({})
模块结构
模块(module)是Verilog的基本描述单位,是用于描述某个设计功能或结构及与其他模块通信的外部接口。
例:端口定义
%端口定义
module block1(a,b,c,d)
%I/O说明
input a,b,c;
output d;
%信号类型声明
wire x;
%功能描述
assign d=a|x;
assign x=(b&~c);
endmodule
三种描述电路的逻辑功能方法:
- assign语句:
assign x=(b&~c)
; - 用元件例化(instantiate):
and myand1(f,a,b,c)
; - 用
always
块语句:需要用begin end
语句成对出现。
需要先建立以下概念:
- 只有寄存器类型的信号才可以在always和initial 语句中进行赋值,类型定义通过reg语句实现。
- always 语句是一直重复执行,由敏感表(always 语句括号内的变量)中的变量触发。
- always 语句从0 时刻开始。
- 在begin 和end 之间的语句是顺序执行,属于串行语句。
例:计数器
%计数器
always @(posedge clk)
begin
if(load)
out=data;
else
out=data+1+cin;
end
例:8位全加器
module adder8(cont,sum,a,b,cin);
output cout;
output [7:0]sum;
input [7:0]a,b;
input [7:0]cin;
assign{cout,sum}=a+b+cin;
endmodule
例:8位计数器
module counter8(ouot,cout,data,load,clk);
output [7:0]out;
input cout;
input [7:0]data;
input load,cin,clk;
reg [7:0]out;
always @(posedge clk)
begin
if(load)
out<=data; //同步预置数据
else
out<=out+cin;//加1计数
end
assign cout=&out&cin;//如果out和cin所有位为1,则cout为1
endmodule
基本语法
符号
- 空白:空格\b、tab\t、换行\n
- 注释://
- 关键字:小写
- 标识符:首字母必须是字母或下划线,关键字不能用来做标识符
常量
程序运行中不能改变的
-
整数
-
简单十进制格式
-
基数格式
如果定义的长度比为常量指定的长度长,通常在左边填0 补位。但是如果数最左边一位为x 或z ,就相应地用x 或z 在左边补位。例如:
10'b10 左边添0 占位, 0000000010 10'bx0x1 左边添x 占位, x x x x x x x 0 x 1 如果长度定义得更小,那么最左边的位相应地被截断。例如: 3 ' b1001 _ 0011 与3'b011 相等 5'H0FFF 与5'H1F 相等
-
-
parameter常量(或符号常量)
-
格式 parameter 参数名1=表达式,参数名2=表达式;
-
-
x和z值
- x表示不确定值(就是我可以不在乎他是0和1,如卡诺图中有的值)
- z表示高阻。一个为z 的值总是意味着高阻抗,一个为0 的值通常是指逻辑0 。在门的输入或一个表达式中的为“z ”的值通常解释成“x ”。 x 值和z 值都是不分大小写的,也就是说,值0x1z 与值0X1Z 相同。
-
实数:十进制格式和科学计数法两种,Verilog中实数通过四舍五入的方法变为整数
变量
nets型
wire型向量(总线)
wire [7:0] wire1,wire2;
表示线位宽为8,共有2条总线
-
wire 和 tri 定义
线网类型主要有wire 和tri 两种。线网类型用于对结构化器件之间的物理连线的建模。如器件的管脚,内部器件如与门的输出等。以上面的加法器为例,输入信号A, B是由外部器件所驱动,异或门X1的输出S1是与异或门X2输入脚相连的物理连接线,它由异或门X1所驱动。 -
两者区别:tri 主要用于定义三态的线网。
-
由于线网类型代表的是物理连接线,因此它不存贮逻辑值。必须由器件所驱动。通常由assign进行赋值。如
assign A = B ^ C
; -
当一个wire 类型的信号没有被驱动时,缺省值为Z(高阻)。
-
信号没有定义数据类型时,缺省为 wire 类型。如一位全加器的端口信号 A, B, SUM等,没有定义类型,故缺省为wire 线网类型
reg型
reg 是最常用的寄存器类型,寄存器类型通常用于对存储单元的描述,如D型触发器、 ROM等。存储器类型的信号当在某种触发机制下分配了一个值,在分配下一个值之时保留原值。但必须注意的是, reg 类型的变量,不一定是存储单元,如在always 语句中进行描述的必须用reg 类型的变量。
reg 类型定义:`reg [msb:lsb] reg1, reg2, ... ,reg N`,msb 和lsb 定义了范围,并且均为常数值表达式。范围定义是可选的;如果没有定义范围,缺省值为1 位寄存器。在过程中被赋值的信号,往往代表触发器。
寄存器类型的值可取负数,但若该变量用于表达式的运算中,则按无符号类型处理。
- reg:常代表触发器;
- integer:32位带符号整型
- real:64位带符号实数型变量
- time:无符号时间变量
例:用reg型变量生成逻辑组合举例
module rw1(a,b,out1,out2); input a,b; output out1,out2; reg out1; reg out2; assign out2=a; always @(b) out1<=~b;endmodule
reg型变量既可以生成触发器,也可以生成组合逻辑,wire型变量只能生成组合逻辑。
语句
过程语句
initial语句:仅执行一次,常用于仿真初始化
always语句:不断重复执行,只要满足规定条件。
always块有过的敏感信号是,一定要用if-else-if
语句,而不能采用多个if
语句!!!
always@(敏感信息列表如:posedge clk or negedge rst)//clk上升沿或rst下降沿 begin 只能使用regester型变量 一个变量只能在一个always中赋值 end
块语句
begin-end
语句:顺序
fork-join
语句:并行
例:用顺序块和延迟控制组合产生一个时序波型(8位)
parameter d=50;reg [7:0] r;begin #d r='h45'; #d r='hF1'; #d r='h10'; #d r='hB2';end
例:用并行块和延迟控制组合产生一个时序波形(8位)
reg [7:0] r;fork #20 r='h45'; #40 r='hF1'; #60 r='h10'; #80 r='hB2';join
赋值语句
assign
语句只能给wire类型赋值
always
通常用register类型赋值
非阻塞赋值:b<=a
,在块结束时才完成赋值操作
always @(posedge clk) begin b<=a; c<=b; end
在一次正跳变后b=a,c=!=a;上述代码相当于两个串联的触发器,有一个时钟周期的落后。
阻塞赋值:b=a
,在该语句结束时就完成赋值操作
条件语句
使用条件语句应注意列出所有条件分支,否则当条件不满足时,编译器会生成一个锁存器保持原值
在组合逻辑电路设计中,应避免生成隐含锁存器时序电路设计则不会。
if-else
语句
例:十进制计数器
module count10(clk,reset,load,a,q,cout); input clk,reset,load; input[3:0] a,q; output cout; reg[3:0] q; reg cout; always@(posedge clk) begin if(reset)//同步复位 begin q<=0; cout<=0; end else if(load)//同步置数 q<=a; else if(q==9)//计数 begin q<=0; cout<=1; end else begin q<=q+1; cout<=0; end endendmodule
例:
module code4_2(in,out); input [3:0] in; output [1:0] out; reg [1:0] out; always @(in) //不是用时钟,赋值用的阻塞方式,当in变化是触发 begin if(in[3]) out=3; else if(in[2]) out=2; else if(in[1]) out=1; else out=0; endendmodule
case
语句:case/casex/casez
三种
例:
module code4_3(P,Q): input [3:0] P; output [1:0] Q; reg [1:0] Q; always@(P) begin case(P) 4'b0001:Q=2'b11; //4位的二进制数0001 4'b0010:Q=2'b10; 4'b0011:Q=2'b01; 4'b0100:Q=2'b00; default:Q=2'bXX; //xx是无所谓的值 endcase endendmodule
循环语句
四种形式的循环语句for while repeat forever
,只能在initial always
中使用。
例:用for语句描述的7人投票表决器
module vote7(pass,vote); output pass; input [6:0] vote; reg [2:0] sum; integer i; reg pass; always @(vote) begin sum=0; for(i=0;i<=6;i=i+1) if(vote[i]) sum =sum+1; if(sum[2]) pass=1; else pass=0; endendmodule
例:用repeat循环语句实现4位乘法器
module multiplier4_repeat(a,b,out); parameter width=4; input [width:1] a,b; output [2*wisth:1] out; reg[2*width:1] out; reg[2*wisth:1] regb; reg[2*wisth:1] rega; integer i; always@(a or b) begin out=0; rega=0; regb=0; repeat(width) begin if(regb[1]==1) out=out+rega; rega=rega<<1 regb=regb<<1 end endendmodule
例:用while语句对一个8位的二进制数中值为1的位进行计数
module count1s_while(count,rega,clk);
output [3:0] count;
input [7:0] rega;
reg [3:0] count;
always@(posedge clk)
begin:count1
reg[7:0] tempreg;
count = 0;
tempreg = rega;
while(tempreg)
begin
if(tempreg[0])
count =count+1;
tempreg = tempreg>>1;
end
end
endmodule
forever:无条件执行,一般用在initial中,常用在仿真。
task任务
任务的关键字为task
和endtask
add1(d1,d2,out)
对上述任务调用
函数
函数的关键字为function
和endfunction
out=decode(d)
对上述函数进行调用
例:用function模块设计ALU单元
编译预处理
“编译预处理”是指在编译时,先对这些语句进行预处理,然后再将结果与Verilog HDL源程序一起进行编译。
``define`语句
``define`语句:用一个简单的标识符(或称宏名)来代表一个复杂的字符串(即宏内容)。
`define width 8 //用width代替数字8reg[`width-1:0] a,b,c;
define宏定义+`inlude "file.v"文件包含来实现参数模块化设计:
-
新建参数模块文件(我命名为para.v);
-
在para.v文件中使用’define宏定义参数(部分、有错误):
//`define+name+参数 `define STATE_INIT 3'd0`define STATE_IDLE 3'd1``define STATE_WRIT 3'd2`define STATE_READ 3'd3`define STATE_WORK 3'd4`define STATE_RETU 3'd5
3. 在需要调用参数的文件init.v中使用``include “para.v”`:
``include “para.v”`
4. 在init.v文件需要参数的地方使用`name 调用(部分):
state_init <= `STATE_INIT;
若直接在module中通过localparam或者parameter进行参数定义的话,会带来两个问题:代码长度增加,不够美观;不利于参数和代码修改;
define` 与`localparam`和`parameter`最大的区别就是
define可以跨文件传递参数;
parameter只能在模块间传递参数;
localparam`只能在其所在的module中起作用,不能参与参数传递。
``include`语句
``include`语句:一个源文件可将另一个源文件的内容调用。vivado在一个project中可以自动形成调用关系,无需再include。
关于"文件包含"处理的四点说明:
一个
include命令只能指定一个被包含的文件,如果要包含n个文件,要用n个
include命令。注意下面的写法是非法的``include"aaa.v"“bbb.v”```include
命令可以出现在Verilog HDL源程序的任何地方,被包含文件名可以是相对路径名,也可以是绝对路径名。例如:
’include"parts/count.v"`可以将多个
include命令写在一行,在
include命令行,只可以出空格和注释行。例如下面的写法是合法的。
'include "fileB" 'include "fileC" //including fileB and fileC
- 如果文件1包含文件2,而文件2要用到文件3的内容,则可以在文件1用两个``include`命令分别包含文件2和文件3,而且文件3应出现在文件2之前。
这个``includel`里面定义的主要是一些参数,然后给另外的文件进行一个调用
``timescale`语句
``timescale`语句:时间尺度语句,用于定义时间和时间精度。
//格式 `timescale 时间单位/时间精度
`timescale 1ns/10ps
时间单位:用于定义模块中仿真时间和延迟时间的基准单位。
时间精度:用来声明该模块的仿真时间和延迟时间的精确程度。