目录
1、Verilog HDL基本知识 1.1 Verilog HDL的抽象级别1.系统级:用于对待设计模块的描述和功能的验证。
2.算法级:实现算法运行的模型。
3.RTL级:描述数据如何在寄存器间流动、传输的模型。
4.门级:描述逻辑门如何连接的模型。
5.开关级:描述器件模型。
1.2 Verilog HDL 最重要的基本概念并行性、层次结构性、可综合性
1.3 Verilog HDL 基本设计单元基本设计单元是模块(Block),类比C语言中的函数。
模块由两部分组成,一部分描述接口,一部分描述逻辑功能,
每个Veirlog程序包括端口定义、IO说明、内部信号说明、功能定义。
Verilog 语法中的模块例化。
FPGA 逻辑设计中通常是一个大的模块中包含了一个或多个功能子模块,Verilog 通过模块调用或模块实例化来实现这些子模块与高层模块的连接。【把系统划分为几个模块,每个模块对应一个module,一个module一个Verilog程序文件。顶层模块只做例化(调用其它模块),不做逻辑。】
2、Verilog基本知识逻辑值
0:低电平,即GND
1:高电平,即VCC
X:未知,可能是高可能是低
Z:高阻态,悬空,即三态门的0开关下的输出
数字进制
数字的表示:
<位宽><进制><数字> (位宽默认为系统位宽,进制默认十进制)
进制: 二进制binary 八进制octonary 十进制decimalism 十六进制hexadecimal
x不定值 z/?高阻值
负数: -8'd10 写在最前面
二进制表示:4’b0101,即四位二进制数0101
八进制表示:4’o7,即四位八进制数7
十进制表示:4’d2,即四位十进制数2(0101)
十六进制表示:4’ha,即四位十六进制数a(二进制1010)
注意:前面的4表示是的实际的位宽,如果没有指定,默认的是32位的位宽。可以在数之间加入下划线,如16’b1001_1010_1010_1001以增加可读性。
标识符
用于定义模块名,端口名,信号名,由字母、数字、$和_(下划线)组成,第一个字符必须是字母或者下划线,区分大小写。
不建议大小写混合使用,普通内部信号建议全部小写,参数定义建议大写。
采用一些前缀或后缀,比如:时钟采用 clk 前缀:clk_50m,clk_cpu;低电平采用_n 缀:enable_n;
主要有三种:寄存器数据类型、线网数据类型、参数数据类型
3.1 寄存器数据类型寄存器数据类型:表示一个抽象的数据存储单元(类似于寄存器),关键字是reg,默认的初始值是X,定义方法是:
//reg define reg [31:0] delay_cut; //32位寄存器 reg key_reg; //默认位宽为 1reg类型的数据只能在always语句和initial语句中被赋值,不能够给予初始值
如果语句描述的是时序逻辑电路,那么always语句将带有时钟信号,则该寄存器变量相当于一个触发器
如果语句描述的是组合逻辑电路,那么always语句不带有时钟信号,则该寄存器变量相当于一条硬件连接线
3.2 线网数据类型线网数据类型:表示结构实体之间的物理连线,不能够存储值,他的值是由它所连接的元件所控制的,只是一条连接线。关键字包括wire型和tri型,最常用是wire。默认是高阻态Z。
//wire define wire data_en; //数据使能信号 wire [7:0] data ; //数据 3.3 参数类型参数数据类型:实际上就是一个常量,用parameter定义(类似C语言中的define),右边必须有一个常量表达式。可以一次定义多个参数,但参数与参数之间需要用逗号隔开。
//parameter define parameter DATA_WIDTH = 8; //数据位宽为8位 //可以[DATA_WIDTH : 0]只修改参数值来修改位宽参数型数据用于定义的状态、数据位宽和延迟大小等,在模块调用时可以通过参数传递来改变调用模块中已定义的参数。
4、运算符①算术运算符(+,-,*,/,%): Verilog 实现乘除比较浪费组合逻辑资源,尤其是除法。所以 2 的指数次幂的乘除习惯用移位,非 2 的指数次幂的乘除调用现成的 IP。
+:a+b,表示相加
-:a-b,表示相减
*:a乘以b,表示相乘
/:a除以b,表示整除
%:a%b,表示取余
②关系运算符(>,<,>=,<=,==,!=):
>:a>b,表示a大于b
<:a<b,表示a小于b
>=:a>=b,表示a大于等于b
<=:a<=b,表示a小于等于b
==:a等于b,表示a等于b
!=:a!=b,表示a不等于b
③逻辑运算符( !、&& 、 ||):
!:!a,表示a的非
&&:a&&b,表示a与b
||:a||b,表示a或b
④条件操作符( a ? b : c):
?: :a?b:c,表示如果a为真则选择b,否则选择c
⑤位运算符( ~(取反)、&、 |、^(异或)):
~:表示按位取反
&:表示按位取与
|:表示按位取或
^:表示按位异或
⑥移位运算符(<<,>> ):
<<:a<<b,表示a左移b位
>>:a>>b,表示a右移b位
注意,不管是左移还是右移都会用0来补充空位,所以,左移位宽增加,右移位宽不变
⑦拼接运算符( {}):
{}:{a,b},表示将a和b拼接起来,作为一个新的信号或者变量
5、Verilog关键字 关键字 含义begin-end 里面是顺序执行的
fork-join 里面是并行执行的
块名:a.可以在块内定义局部变量 b.允许被其他语句调用 c.跳出块不影响块内局部变量的值
块语句的性质:嵌套块、命名块、命名块的禁用
6.2 条件语句if_else语句(1为真,0,x,z为假):
1.必须在过程块(有initial和always语句引导的)中使用
2.表达式值为X,Z时按假处理
3.if之后如果多句,要用begin end
4.不允许if嵌套
case语句:
case(表达式)
<分支>
<分支>
endcase
1.所有表达式的位宽必须相等,不能用’bx代替n’bx,
2.casez:比较时不考虑z
casex:比较时不考虑z和x
条件语句和case语句应该包含所有可能,否则会出现latch。
锁存器是电平触发的存储器,是组合逻辑产生的,寄存器是边沿触发的存储器,在时序电路中使用,由时钟触发产生的。
if 缺少 else 分支,case 缺少 default 分支会导致代码在综合过程中出现了 latch。latch只在组合逻辑电路中产生,也就是只有不带时钟的 always 语句中 if 或者 case 语句不完整才会产生 latch,带时钟的语句 if或者 case 语句不完整描述不会产生 latch。
四种循环语句:
1)forever语句:(必须写在initial语句块中)
格式:forever 语句
forever begin 多条语句 end
forever循环语句常用于产生周期性的波形,用来作为仿真测试信号。它与always语句不同处在于不能独立写在程序中,而必须写在initial块中。
//always initial clk = 1'b0; always #10 clk = ~clk; //forever initial begin clk = 1'b0; forever #10 clk = ~clk; end2)repeat语句:repeat(表达式),连续执行一条语句 n 次
repeat语句执行指定循环数,如果循环计数表达式的指不确定,即为x或z时,那么循环次数按0次处理。repeat语句的语法是为:
repeat(循环次数表达式) begin
语句块;
end
其中,循环次数表达式用于指定循环次数,可以是一个整数、变量或者数值表达式。如果是变量或者数值表达式,其数值只在第一次循环时得到计算,从而可以事先确定循环次数,语句快为重复执行的循环体。
3)while语句
while语句实现的是一种条件循环,只有在指定的循环条件为真时才会重复执行循环体,如果表达式在开始时不为真(假、x或z),那么过程语句将永远不会被执行。while循环的语法是:
while(循环执行条件表达式) begin
语句块;
end
在上述格式中,循环执行条件表达式代表了循环体得以继续重复执行时必须满足的条件,在每一次执行循环体之前,都需要对这个表达式是否成立进行判断。
while语句在执行时,首先判断循环执行条件表达式是否为真,如果真,执行后面的语句块,然后再重复判断循环执行条件表达式是否为真,如此下去,直到循环执行条件表达式不为真。
4)for语句
for通过以下三个步骤来决定语句的循环执行。
a) 先给控制循环次数的变量赋初值。
b) 判定控制循环的表达式的值,如为假则跳出,如为真则执行指定的语句后,转到第三步。
c) 执行一条赋值语句来修正控制循环变量次数的变量的值,然后返回第二步。
for,while,repeat是可综合的,但循环的次数需要在编译之前就确定,动态改变循环次数的语句是不可综合的。forever语句是不可综合的,主要用于产生各种仿真激励。
6.4 结构说明语句initial:
一般用作赋初始值 initial begin--end ,只执行一次,用于tb文件编写,产生激励信号或对reg变量赋初值。
always:
一直执行,但需要结合一定的时间控制才有作用。always的时间控制可以是电平触发也可是边沿触发。可以是单个信号也可以是多个信号,多个信号用or连接。
参考:Verilog中的阻塞与非阻塞赋值_今天不学习明天被卷️的博客-CSDN博客_verilog阻塞赋值
1)阻塞赋值(Blocking)
b = a;
计算 RHS 的值并更新 LHS。
在一个 always 块中,后面的赋值语句是在前一句赋值语句结束后才开始赋值的。即 always 块内的语句是一种顺序关系。
2)非阻塞赋值(Non-Blocking)
b <= a;
(1)赋值开始的时候,计算 RHS;
(2)赋值结束的时候,更新 LHS。
在计算非阻塞赋值的 RHS 以及 LHS 期间,允许其它的非阻塞赋值语句同时计算 RHS 和更新 LHS。
非阻塞赋值只能用于对寄存器类型的变量进行赋值,因此只能用在initial和always块等过程块中。
总结:
1.描述组合逻辑电路,用阻塞赋值,比如 assign 赋值语句和不带时钟的 always 赋值语句,这种电路结构只与输入电平的变化有关系
2.描述时序逻辑电路,用非阻塞赋值,综合成时序逻辑的电路结构,比如 带时钟的 always 语句;这种电路结构往往与触发沿有关系,只有在触发沿时才可能发生赋值的变化
注意 :同一个always中不要既用阻塞又用非阻塞,不允许在多个always中对同一个变量赋值(因为并行执行)。
1)assign语句(多描述组合逻辑):只有他和实例引用语句可以独立于过程块而存在于模块的功能定义部分
2)always块(多描述时序逻辑,也可描述组合逻辑):也叫做过程块语句
3)用实例元件:实例引用语句
assign 和 always 区别
assign 不能带时钟。 always 可以带也可以不带。在 always 不带时钟时,逻辑功能和 assign 完全一致,都只产生组合逻辑。比较简单的组合逻辑推荐使用 assign 语句,比较复杂的组合逻辑推荐使用 always 语句。
6.7 任务和函数语句task 和 function 说明语句分别用来定义任务和函数,利用任务和函数可以把一个很大的程序模块分解成许多较小的任务和函数,便于理解和调试。输入、输出和总线信号的值可以传入、传出任务和函数。
task能支持多种目的,能计算多个结果值,这些结果值之间能通过调用的task的输出或者总线端口输出。
function的目的是通过返回一个值来响应输入信号的值。
1)任务(task)
任务就是封装在task-endtask之间的一段语句。任务是通过调用来执行的,也只能通过调用来执行,如果定义了任务,但整个过程都没有调用它,它是不会执行的。调用任务时可能需要它来处理某些数据并返回操作结果,所以任务应当有接受数据的输入端和返回数据的输出端。另外,任务可以彼此调用,任务还可以调用函数。
task定义:
task <task name>; <port and data type declare>; <statement 1>; <statement 2>; <statement 3>; ... endtask注意:
1、在第一行task语句中不能列出端口名称。
2、任务的输入、输出和双向端口数量不受限制,甚至可以没有输入、输出和双向端口。
3、在任务定义的描述语句中,可以出现不可综合操作符合语句,但这样会造成任务不可综合。
4、在任务中可以调用其他的任务或函数,也可以调用自身。
5、在任务定义结构中不可出现initial和always语句。
6、在任务定义中可以出现“disable中止语句“,将中断正在执行的任务,但其是不可综合的。当任务被中断后,程序流程将返回调用任务的地方继续执行。
task的调用:
<任务名> (端口1,端口2,端口3,...,端口n);
注意:
1、任务调用语句只能出现在过程块中
2、任务调用语句和一条普通的行为描述语句的处理方法一致
3、当被调用输入、输出和双向端口时,任务调用语句必须包含端口名列表,且信号端口顺序和类型必须和任务定义结构中的顺序和类型一致。需要说明的是,任务的输出端口必须和寄存器类型的数据变量对应。
4、可综合任务只能实现组合逻辑,也就是说调用可综合任务的时间为0;
2)函数(function)
函数通过关键词function和endfunction定义,不允许输出端口声明,但可以有多个输入端口。
function定义:
function <返回值的类型和范围> (函数名);
function <return type and scope> (function name); <port define> begin <statement>; ... end endfunction注意:
1、函数定义只能在模块中完成,不能出现在过程块中
2、函数至少要有一个输入端口,不能包含输出端口和双向端口。
3、在函数体内,不能出现任何形式的时间控制语句,也不能使用disable中止语句
4、函数结构体中出现过程性语句
5、函数体内可以调用函数,但不能调用任务
function调用:
和任务一样,函数也是在被调用时才被执行的,调用语句的语句形式如下:
function-id(expr1,.....exprN);
其中,func-id是要调用的函数名,expr1,。。。exprN是传递给函数的输入参数列表,该输入参数列表的顺序必须与函数定义时声明其输入的顺序一致。
注意:
1、函数调用可以在过程块中完成,也可以在assigne这样的连续赋值语句中出现。
2、函数调用语句不能单独作为一条语句出现,只能作为赋值语句的右端操作数
区别:
1、function只能与主模块共用一个仿真时间单位,而task可以定义自己的仿真时间单位。
2、function不能包含task,task可以包含其它的task和function。
3、function至少有一个输入变量,而task可以没有或者有多个任意类型的变量。
4、function返回一个值,而task则不返回值。
7、常用函数常用函数:
$display("text %d",variate);
$monitor(p1,p2);
$monitor;
$monitoron;
$monitoroff;
$time;
$realtime;
$finish;//退出仿真器
$stop;//暂停
$random;//带符号整形随机数 ¥random%b 范围为(-b+1,b-1)的随机数
预编译处理
`define <宏名><宏内容>
`include"文件名"