2013 FPGA OK
用这种语言编写的模型能够使用verilog仿真器进行验证。
模块的定义从关键字module开始,到关键字endmodule结束,每条Verilog HDL语句以“;”做为结束 模块的实际意义是代表硬件电路上的逻辑实体。 模块的描述方式有行为建模和结构建模之分。 模块之间是并行运行的。
整个系统需要一个顶层模块(Top-module)。调用子模块来实现整体功能,
模块定义行:module module_name (port_list); module <模块名>(<端口列表>); 【端口列表】是输入、输出和双向端口的列表,这些端口用来与其他模块进行连接。 调用模块实例的一般形式为:
<模块名><参数列表><实例名>(<端口列表>);
其中参数列表是传递到子模块的参数值,参数传递的典型应用是定义门级时延。 1. 说明部分包括:指定数据对象为寄存器型、存储器型、线型以及过程块,
寄存器,线网,参数:reg, wire, parameter 端口类型说明行:input, output, inout双向 函数、任务:function, task, 等
input [2:0] a; [n:0]表示该信号的位宽(总线或单根信号线)。
2. 结束行,以endmodule结束,注意后面没有分号了。
module my_and2(A,B,C);其中module 是模块的保留字,my_and2是模块名,相当于器件名。()内是该模块的端口声明,定义了该模块的管脚名,是该模块与其他模块通讯的外部接口,相当于器件的pin 。 在模块中只能写assign语句或者always语句。
在一个模块中的多个assign与always之间都是并行语句
` timescale 1ns /100ps
此语句说明时延时间单位为1ns并且时间精度为100ps (时间精度是指所有的时延必须被限定在0.1ns内)
`timescale 1ns/ 1ns
以反引号“ ` ”开始的第一条语句是编译器指令, `timescale 将模块中所有时延的单位设置为1 ns,时间精度为1 ns。例如,在连续赋值语句中时延值#1和#2分别对应时延1 ns和2 ns。
2.3 数据流描述方式
某个值被指派给线网变量。
assign [delay] LHS_net = RHS_ expression;
时延定义了右边表达式操作数变化与赋值给左边表达式之间的持续时间。如果没有定义时延值, 缺省时延为0。 在硬件上就是用一根线连起来了,注意,assign中只能用“=”号。 连续赋值语句是并发执行的
赋值目标只能是线网,而且这种赋值行为没有任何附加的判断条件
连续赋值语句的赋值目标可以是:
·标量线网 ·向量线网 ·向量的常数型位选择; ·向量的常数型部分选择; ·上述类型的任意连接结果(使用连接操作符)
等号右端操作数上的数据是始终处于被监控状态的,一旦这些数据发生变化就会引起赋值语句的执行。 跟在assign之后。默认时延是0
连续赋值语句中的时延可以指定3类时延值:上升时延、下降时延和关闭时延(值变为x)。 assign #(rise, fall, turn-off) LHS_target = RHS_expression; 2.4 行为描述方式
3.2 顺序行为建模
initial和always都不仅仅是单独的一条语句,它们引导一个“过程”或者说一个“结构”,
跟在initial和always之后的都是一段程序,所有其他顺序行为语句都必须包含在这段程序中。可以说,initial和always的出现是顺序行为描述的开始。
这段程序会被封装成一个“程序块”,可以用begin-end(顺序语句块)封装,也可以用fork-join(并行语句块)封装。 并行语句块 fork-join封装 并行语句块中的 语句彼此之间都是并行执行的,和书写顺序无关。
1) initial语句:此语句只执行一次。
2) always语句:此语句总是循环执行, 或者说此语句重复执行。
所有的initial初始化语句和always语句在0时刻并发执行。
initial之后要跟多条过程语句,就要把这些语句封装为“程序块”。顺序语句块(begin-end---{-}用来代表一个语句块)是最常使用的封装结构,在begin和end之间的过程语句是按 书写顺序 依次执行的
module FA_Seq (A, B, Cin, Sum, Cout); input A, B, Cin; output Sum, Cout; reg Sum, Cout; reg T1, T2, T3; always
@ ( A or B or Cin ) begin
Sum = (A ^ B) ^ Cin; T1 = A & Cin; T2 = B & Cin; T3 = A & B;
Cout = (T1| T2) | T3; end endmodule
parameter SIZE = 1024;
reg[7:0] RAM [0:SIZE-1];
reg RibReg; initial
begin: SEQ_BLK_A integer Index; RibReg = 0;
for (Index = 0; Index < SIZE; Index = Index+1) RAM[Index] = 0; end
在always里面赋值语句用“<=”(以后学深了再探讨阻塞赋值和非阻塞赋值) always @ ( posedge Clk or posedge Rst ) begin
if (Rst)
Q <= 1’b0; else
Q <= D;
上面括号内的内容也是敏感变量,而posedge代表“上升沿“,就是信号由低到高,相应的,negedge代表“下降沿”,就是信号由高到低。即整个always 语句当敏感变量clk或rst有上升沿时执行。因此,当Rst 由0变为1 时,Q被复位,在时钟上升沿时,D被采样到Q。
程序中begin之后的标识符“SEQ_BLK_A”是顺序语句块的名称,并不是所有的语句块都要求有名称。在这段过程语句中因为出现了局部变量Index,所以要求这个程序块必须有名称标记。
时延可以细分为两种类型:
1) 语句间时延: 这是时延语句执行的时延。
Sum = (A ^ B) ^ Cin; #4 T1 = A & Cin;
2) 语句内时延: 这是右边表达式数值计算与左边表达式赋值间的时延。 Sum = #3 (A^ B) ^ Cin;
如果在过程赋值中未定义时延,缺省值为0时延,也就是说,赋值立即发生。
begin: ONLY_ONCE
reg [3:0] Pal; //需要4位, Pal才能取值8。 for (Pal = 0; Pal < 8; Pal = Pal + 1) begin
{PA, PB, PCi} = Pal;
#5 $display (“PA, PB, PCi = %b%b%b”, PA, PB, PCi, “ : : : PCo, PSum=%b%b”, PCo, PSum); end
end
初始化语句中的顺序过程(begin-end)必须标记。在这种情况下, ONLY_ONCE是顺序过程标记。如果在顺序过程内没有局部声明的变量,就不需要该标记。 //输出显示:
initial
$monitor (\
第二条初始化语句调用系统任务$monitor。只要参数表中指定的变量值发生变化就打印指定的字符串。
注意只有小写的关键词才是保留字。例如,标识符always(这是个关键词)与标识符ALWAYS(非关键词)是不同的。
Always [timing_control] procedural_statement
其中,timing_control是时序控制,形式可以是时延控制(即等待一个确定的时间),或事件控制(即等待某一时间发生或某一条件为真);procedural_statement是过程语句。
用符号“@”定义了事件控制的always语句。事件控制还有一种是“电平敏感事件”,使用关键词“wait”定义。 3.2.2 时序控制
时序控制有两种形式:时延控制 和 事件控制。 (1)时延控制
·语句前时延 ·单独时延 · 语句内时延
如果时延表达式的值是0,则称之为显示零时延,它和不定义时延是完全不同的。 后者默认时延是0,程序继续执行不受任何影响,
而显式零时延将使整个程序的运行在当前仿真时间暂停,使其处于一种“等待”状态,等待所有其他正在被执行的事件全都执行完毕后,才将整个进程唤醒继续执行,处于等待状态时,仿真时间不前进。
(2)事件控制
边沿触发事件 和 电平敏感事件。
·边沿触发事件 使用符号@定义,形式如下: @ event procedural_statement
其中“@”是边沿事件使用的符号,event是边沿事件,procedural_statement是过程语句。 和时延控制类似,事件控制也可以独立成为一条语句。 @ event;
运行到这条语句时,整个程序在此被暂停,进入等待状态,一直等到这条语句中指定的事件发生后,才能继续执行后面的程序。
event可以是多个事件,这些事件之间只需要用关键字“or”分隔开即可。
注意:在verilog中,上升沿不仅仅是从0到1这一种情况,下降沿也不仅仅是从1到0这种情况。 ·电平敏感事件
wait (condition) procedural_statement condition就是电平敏感事件,prcedural_statement是过程语句。 执行到wait时,检查condition定义的条件是否满足,满足就接着执行随后的过程语句;否则进入等待状态,知道条件满足为止。
3.2.4 过程性赋值
过程性赋值是最常见的赋值形式:等号左侧是赋值目标,右侧是表达式。它有以下几个特点: · 过程性赋值只出现在initial语句和always语句内;
· 过程性赋值只能给寄存器变量赋值; 只有寄存器类型数据能够在这两种语句中被赋值。 ·赋值表达式的右端可以是任何表达式。
事实上,Verilog有两种过程性赋值:阻塞过程赋值和非阻塞过程赋值。之前程序中出现的都是阻塞过程赋值,其标志是使用赋值符号“=”,而非阻塞过程赋值使用赋值符号“<=”,二者在功能和特点上有很大区别。
来自always语句和initial语句(切记只有寄存器类型数据可以在这两种语句中赋值)的值能够驱动门或开关,而来自于门或连续赋值语句(只能驱动线网)的值能够反过来用于触发always语句和initial语句。 (1)阻塞过程赋值
“阻塞”是从阻塞过程赋值的工作过程中得来的。它先计算右侧表达式的值,然后赋值给等号左端目标,而且在完成整个赋值之前不能被其它语句打断。也就是说在某一条阻塞过程赋值语句正在执行时,处于其后的其它赋值语句都不能执行。
(2)非阻塞过程赋值
非阻塞过程赋值操作符是“<=”,非阻塞过程赋值只能用于给寄存器赋值。 赋值过程都包括两个子过程: ·过程1,计算右侧表达式的值; ·过程2,给左侧目标赋值。
对阻塞过程赋值而言,这两个子过程可以视为连续完成的,而且在完成赋值之前不允许其后的其它语句执行。而对于非阻塞赋值,所谓“在某个时刻完成赋值”,其实是在这个时刻开始执行子过程1,在这个时刻结束时执行子过程2,这两个子过程之间有一个微小的事件间隔。在这个间隔期间,这条非阻塞赋值语句后面的其它的语句也可以执行。
(4)过程性连续赋值
连续赋值适用于线网,过程赋值适用于寄存器。但是还有一类赋值方式,它既能对线网赋值也能对寄存器赋值(但不能是寄存器的位选择或部分选择),这种赋值方式被称为过程性连续赋值。它属于过程性赋值而非连续赋值,所以它能出现在always和initial语句中(连续赋值语句不能出现在always和initial内)。但是它又有连续赋值的一些特性,就是在过程性连续赋值语句中,右端表达式中操作数的任何变化都会引起赋值语句的重新执行。
过程性赋值语句有两种类型:assign-deassign(赋值-重新赋值),force-release(强制-释放虽然它也可以用于对寄存器赋值,但主要用于对线网赋值)。
assign用于对寄存器赋值,deassign用于取消之前由assign赋给某寄存器的值。也就是说,使用assign为寄存器赋值后,这个值将一直保存在寄存器上,直到遇到deassign为止。看下例:
module DEF(D, Clr, Clk, Q); input D, Clr, Clk; output Q;