一、模块
端口定义
模块的端口声明了模块的输入输出口,在引用模块时其端口可以用两种方法连接:
- 在引用时,严格按照模块定义的端口顺序来连接,不用标明原模块定义时规定的端口名
- 在引用时用”
.
“符号,标明原模块是定义时规定的端口名
模块内容
I/O口
内部信号
在模块内部用到的和端口有关的 wire
和 reg
类型变量的声明
功能定义
模块中最重要的部分是逻辑功能定义部分。有3种方法可在模块中产生逻辑:
- 用
assign
声明语句 - 用实例元件
- 用
always
块
这三种逻辑功能时并行执行的,顺序并不会影响实现的功能
要点
always
块中的语句成为”顺序语句”,因为它们是顺序执行的- 两个或更多的
always
块都是同时执行,块内部的语句是顺序执行 - 在
Verilog
模块中所有的过程块(如:initial
块、always
块)、连续赋值语句、实例引用都是并行的 - 它们表示的是一种通过变量名相互连接的关系
- 在同一模块中这三者出现的先后次序没有关系
- 只有连续赋值语句
assign
和实例引用语句可以独立于过程块而存在于模块的功能定义部分
二、数据类型及其常量和变量
4个基本的数据类型: reg
型、wire
型、integer
型和 parameter
型
常量
数字
1)整数
2) x
和 z
值:x
代表不定值,z
代表高阻值
3)负数:位宽表达式前加一个减号
4)下划线:分隔开数的表达以提高程序可读性,只能用在具体数字之间
参数(parameter)型
参数型常数经常用于定义延迟时间和变量宽度。在模块或实例引用时,可通过参数传递改变在被引用模块或实例中已定义的参数
在一个模块中改变另一个模块中的参数时,需要使用defparam
命令
变量
wire
型
wire
型数据常用来表示以assign
关键字指定的组合逻辑信号。Verilog
程序模块中输入、输出类型信号默认自动定义为wire
型。wire
型信号可以用作任何方程式的输入,也可以用作assign
语句或实例元件的输出。
reg
型
reg
类型数据的默认初始值为不定值x
reg
型数据常用来表示always
模块内指定信号,常代表触发器,在always
块内被赋值的每一个信号都必须定义为reg
型
reg
型只表示被定义的信号将用在always
模块内
memory
型
定义一个名为mema的储存器,该储存器有256个8位的存储器。该存储器的地址范围是 0 到 255。
reg[7:0] mema[255:0];
三、运算符及表达式
按功能分:
- 算术运算符
+, -, *, /, %
- 赋值运算符
=, <=
- 关系运算符
>, <, >=, <=
- 逻辑运算符
&&, ||, !
- 条件运算符
? :
- 位运算符
~, |, ^, &, ^~
- 移位运算符
<<, >>
- 拼接运算符
{ }
- 其它
按所带操作数个数分类:
- 单目运算符
- 双目运算符
- 三目运算符
等式运算符
==
等于
!=
不等于
===
等于
!==
不等于
==
和===
的区别:
=== | 0 | 1 | x | z | == | 0 | 1 | x | z | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | x | x | |
1 | 0 | 1 | 0 | 0 | 1 | 0 | 1 | x | x | |
x | 0 | 0 | 1 | 0 | x | x | x | x | x | |
z | 0 | 0 | 0 | 1 | z | x | x | x | x |
缩减运算符
缩减运算符是对单个操作数进行或、与、非递推运算,最后的运算结果是1位的二进制数
四、赋值语句和块语句
赋值语句
非阻塞赋值方式b <= a;
- 在语句块中,上面语句所赋的变量值不能立即就为下面的语句所用;
- 块语句结束后才能完成这次赋值操作,而所赋的变量值是上一次赋值得到的;
- 在编写可综合的时序逻辑块时,这是最常用的赋值操作。
阻塞赋值方式b = a;
- 赋值语句执行完后,块才结束
b
的值在赋值语句执行完后立刻就改变的- 在时序逻辑中使用,可能会产生意想不到的后果
块语句
顺序块begin end
并行块fork join
块名
可以给每一个块取一个名字,只需要加在关键词begin
或fork
后面即可
- 可以在块内定义局部变量,即只在块内使用的变量。
- 可以允许块被其它语句调用,如
disable
语句。 - 所有的变量都是静态的,即所有的变量都只有一个唯一的存储地址,因此进入或跳出块并不影响存储在变量内的值
所以,块名就提供了一个在任何仿真时刻确认变量值的方法。
起始时间和结束时间
在并行块和顺序快中都有一个起始时间和结束时间的概念,当一个块嵌入另一个块时,块的起始时间和结束时间就很重要
五、条件语句、循环语句、块语句与生成语句
条件语句
else
总与它上面的最近的if
配对,可以用begin_end
块语句来确定配对关系
if_else
语句一定要有else
或者elseif
选项,否则会产生不必要的锁存器
case
语句
case
语句所有表达式的值的位宽必须相等
casez
语句来处理不考虑高阻值z
的比较过程
casex
语句则将高阻值z
和不定值都视为不必关心的情况
使用case
语句时一定不要忘记default
项,否则有可能产生不必要的锁存器
循环语句
forever
语句
forever
循环语句常用于产生周期性的波形,用来作为方针测试信号。它与always
语句不同之处在于不能独立写在程序中,而必须写在initial
块中
repeat
语句
while
语句
for
语句
生成块
循环生成语句
条件生成语句
case
生成语句
六、结构语句、系统任务、函数语句和显示系统任务
结构说明语句
initial
语句
例如用initial
语句对存储器初始化,整个初始化过程不需要任何仿真时间,即在0ns
时间内,便可以完成存储器的初始化工作
一个模块可以有多个initial
块,他们都是并行运行的
always
语句
如果一个always
语句没有时序控制,则这个always
语句将会使仿真器产生死锁
沿触发的 always
块常常描述时序行为,如有限状态机。
电平触发的always
块常常用来描述组合逻辑的行为
一个模块可以有多个always
块,他们都是并行运行的,多个always
块并没有前后之分
task
和function
说明语句
任务和函数有些不同,主要有4点:
- 函数只能与主模块共用同一个仿真时间单位,而任务可以定义自己的仿真时间单位
- 函数不能启动任务,而任务能启动其他任务和函数
- 函数至少有一个输入变量,而任务可以没有或者有多个任意类型的变量
- 函数返回一个值,而任务则不返回值
tast
说明语句
例:描述红绿黄交通灯行为的Verilog
模块,用到了任务。
module traffic_lights;
reg clkck,red,amber,green;
parameter on = 1,off = 0, red_tics=350,amber_tics=30, green_tics=200;
//交通灯初始化
initial red=off;
initial amber=off;
initial green=off;
//交通灯控制时序
always
begin
reg = on;//开红灯
light(red,red_tics);/调用等待任务/
green=on;//开绿灯
light(green,green_tics);//等待
amber=on;//开黄灯
light(amber,amber_tics);//等待
end
//定义交通灯开启时间的任务
task light;
output color;
input[31:0] tics;
begin
repeat(tics)
@(posedge clock);//等待tics个时钟的上升沿
color = off;//关灯
end
endtask
//产生时钟脉冲的always块
always
begin
#100 clock = 0;
#100 clock = 1;
end
endmodule
function
说明语句
函数的目的是返回一个用于表达式的值。
默认返回值为一位寄存器类型数据。
函数的使用规则:
- 函数的定义不能包含任何的时间控制语句,即任何用
#
、@
、或wait
来标识的语句。 - 函数不能启动任务
- 定义函数时要至少有一个输入参量
- 在函数的定义中必须有一条赋值语句给函数中的一个内部变量赋以函数的结果值,该内部变量具有和函数名相同的名字
//定义一个模块,其中包含能够计算偶校验位的函数(calc_parity)
module parity;
reg[31:0] addr;
reg parity;
initial
begin
addr = 32'h3456_789a;
#10 addr = 32'hc4c6_78ff;
#10 addr = 32'hff56_ff9a;
#10 addr = 32'h3faa_aaaa;
end
//每当地址值发生变化时,计算新的偶校验位
always@(addr)
begin
parity = calc_parity(addr);//第一次启动校验位计算函数
$display("Parity calculated = %b",calc_parity(addr));//第二次启动校验位计算函数
end
//定义偶校验位计算函数
function calc_parity;
input[31:0] address;
begin
//适当的设置输出值,使用隐含的内部寄存器calc_parity
calc_parity = ^address;//返回所有地址位的异或值
end
endfunction
endmodule
//左/右移位寄存器
//定义一个包含移位函数的模块
module shifter;
//左/右移位寄存器
`define LEFT_SHIFT 1'b0
`define RIGHT_SHIFT 1'b1
reg[31:0] addr, left_addr, right_addr;
reg control;
//每当新地址出现时就计算右移位和左移位的值
always@(addr)
begin
//调用下面定义的具有左右移位功能的函数
left_addr = shift(addr, `LEFT_SHIFT);
right_addr = shift(addr, `RIGHT_SHIFT);
end
//定义移位函数,其中输出是一个32位的值
function[31:0] shift;
input[31:0] address;
input control;
begin
//根据控制信号适当的设置输出值
shift = (cntrol == `LEFT_SHIFT) ? (address << 1) : (address >> 1);
end
endfunction
endmodule
自动递归函数
常量函数
带符号函数
常用系统任务
$display
和$write
任务
七、调试用系统任务和常用编译预处理语句
系统任务$monitor
该任务提供了监视和输出参数列表中的表达式或变量值的功能
时间度量系统函数$time
$time
可以返回一个64位的整数来表示当前仿真时刻值,该时刻是以模块的仿真时间尺度为基准的。而$realtime
作用一样,只不过返回的时间数字是一个实型数
系统任务$finish
作用是退出仿真器,返回主操作系统,也就是结束仿真过程
系统任务$stop
作用是把EDA工具设置成暂停模式,在仿真环境下给出一个交互式的命令提示符,将控制权交给用户。
系统任务$readmemb
和$readmemh
用来从文件中读取数据到存储器中
系统任务$random
这个系统函数提供了一个产生随机数的手段。当函数被调用时返回一个32位的随机数。它是一个带符号的整型数。
宏定义`define
用一个指定的标识符来代表一个字符串
“文件包含”处理`include
一个源文件可以将另外一个源文件的全部内容包含进来,即将另外的文件包含到本文件之中
时间尺度`timescale
timescale
命令用来说明跟在该命令后的模块的时间单位和时间精度
`timescale<时间单位>/<时间精度>
条件执行
条件执行标志允许设计者在运行时控制语句执行的流程。所有语句都被编译,但是有条件的执行它们。条件执行标志仅用于行为语句,系统任务关键字$test``$plusargs
用于条件执行