hdm - FPGA知识点

更新时间:2024-04-27 02:00:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

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;

reg Q;

always @(negedge Clk) 在Clk的下降沿把D赋给Q

Q = D; always @(Clr) begin

if(!Clr)

assign Q = 0; 如果clr为0,assign赋值使Q被赋值为0,然后Q就始终为0, else

deassign Q;

end endmodule

上面这段程序中,第一个always会在Clk的下降沿把D赋给Q,但是第二个always内的过程性连续赋值语句会使第一个always的赋值失效。在第二个always中,如果clr为0,assign赋值使Q被赋值为0,然后Q就始终为0,而不管Clk如何变化,即Clk和D对Q无效;等到Clr变为1时,deassign被执行,对Q的过程性连续赋值被取消。之后Clk才能对Q产生影响。换句话说,在Clr为0是,Q一直被assign所霸占,只有Clr为1时,才会把Q释放掉,从而才会受到D的影响。

·force-release

当force语句应用于寄存器时,寄存器的当前值被force语句的值覆盖;当release语句应用于寄存器时,寄存器中的当前值将保持不变,直到被赋予新值。来看两段代码:

reg[2:0] Colt; Colt = 2;

force Colt = 1; //Colt被强制赋值为1

release Colt //对Colt的强制赋值被取消,从现在起Colt将保持值为1 assign Colt = 5;

force Colt = 3; //被强制赋值为3

release Colt; //对Colt的强制赋值被取消,assign Colt = 5重新生效,使得Colt的值变为5 用force语句对线网赋值时,将为线网替换所有驱动源,直到那个线网上执行release语句为止。 wire prt;

or #1 (prt, Std, Dzx); //定义一个或门,线网prt的驱动源 由或门的结构给出 initial begin

force prt = Dzx & Srd; #5;

release prt; end

上面的程序中,执行force语句将替换ptr原来的驱动源,ptr将一直保持这些驱动源,直到release把对prt的强制赋值取消之后,或门的prt驱动源才重新生效。

3.2.5 if语句

对于if语句,如果某个分支内的过程语句多于一条,就应该把它们放在begin-end块内。这里的begin-end相当于C中的{}。

3.2.6 case语句

注意:在比较条件表达式和分支表达式的值时,如果值的某些位出现了x或z,那么这些x和z和0、1一样有意义,所以只要在两个值的相同位置出现x或z,就认为这两个值相同。

·casex和casez

在casez语句中,出现在条件表达式和任意分支项表达式中的值z被认为是无关值,出现z的那个位在比较时被忽略。在casex语句中,值x和z都被认为是无关位。所谓无关位即在比较时可以忽略的位,只要其它的位相匹配即可。

3.2.7 循环语句

(1)forever是个永远循环执行的语句,不需要声明任何变量,其语法格式如下: Forever procedural_statement

其中,procedural_statement是过程语句,若过程语句多于一条,则应该放在begin-end块内。注意:forever语句中必须有某种形式的时序控制,否则forever会在0时延后 永远重复执行过程语句。forever的最主要用途是产生周期性的波形作为仿真测试信号。

(2)repeat循环语句

repeat循环是个重复执行若干次数的语句,带有一个控制循环次数的常量或者变量,其语法形式如下: repeat(loop_count) procedural_statement

其中loop_count是控制循环次数的常数或变量;如果控制循环次数的常数或变量的值不确定(为x或z)时,那么循环次数按0处理。

Sum = repeat(Count) @ (posedge Clk) Sum+1; //首先计算出Sum+1的值,然后等待Clk上升沿,最后为左端赋值

repeat(4) @(negedge Clockz); //等待Clockz上出现4次下降沿之后,才能继续执行repeat之后的语句 (3)while循环语句 (4)for循环语句

4.case 语句

case 语句是一个多路条件分支形式(相当于c语言的switch语句)用法如下: case(case_expr)

case_item_expr{ ,case_item_expr} : procedural_statement . . .

. . .

[default: procedural_statement]

endcase

case 语句首先对条件表达式case_expr 求值,第一个与条件表达式值相匹配的分支中的语句被执行。 case (HEX)

4'b0001 : LED = 7'b1111001; // 1 4'b0010 : LED = 7'b0100100; // 2 default :LED = 7'b1000000; // 0 endcase

case 的缺省项必须写,防止产生锁存器。

以$字符开始的标识符表示系统任务或系统函数。

系统任务提供了一种封装行为的机制。这种机制可在设计的不同部分被调用。任务可以返回0个或多个值。系统函数除只能返回一个值以外与任务相同。此外,函数在0时刻执行,即不允许延迟,而任务可以带有延迟。

系统任务和系统函数可以分成以下几类:

1.输出控制类系统函数:模拟过程的状态信息以及模拟结果的输出都必须按一定的格式进行表示,:$display,$write,$minitor等。

2.模拟时标类系统函数:比如$time和$realtime等。

3.进程控制类系统任务:对模拟进程控制,有$finish,$stop等。 4.文件读写类系统任务:用于控制对数据文件读写方式,如$readmem。 5.其他类:比如随机数产生系统函数$random。

$display(“格式控制输出和字符串”,输出变量名表); 输出结束后自动换行, $write(“格式控制输出和字符串”,输出变量名表); 后者不会。 系统任务$monitor

和$display与$write一样,同属于输出控制类,它的调用形式可以有以下三种: $monitor(“格式控制输出和字符串”,输出变量名表); $monitoron; $monitoroff;

以上第一种的格式和上面的$display完全一致,不同点是,$display每调用一次执行一次,$monitor则一旦被调用,就会随着对输出变量名表中的每一个变量检测,如果发现其中任何一个变量在模拟过程中发生了变化,就会按照$monitor中的格式,产生一次输出。

通常情况下$monitor的输出中会用到一个系统函数$time,如: $monitor($time,,“signal1=%b signal2=%b”,signal1,signal2);

对$time的返回值也可以进行格式控制,如: $monitor (“%d signal1=%b signal2=%b ”,$time, signal1,signal2);

由于$monitor一旦被调用后就会启动一个后台进程,因而不可能在有循环性质的表达中出现,如always过程块或者其他高级程序循环语句, $monitor通常位于initial过程块的最开始处,

格式说明符 %h 或 %H %d或 %D %o或%O %b或 %B %c或 %C %s或 %S 输出格式 以十六进制的格式输出 以十进制的格式输出 以八进制的格式输出 以二进制的格式输出 以ASCII字符形式输出 以字符串方式输出 %v或 %V %t或 %T %m 或 %M %e或 %E %f 或 %F %g 或 %G

3.4.3系统函数$time和$realtime

输出连线型数据的驱动强度 输出模拟系统所使用的时间单位 输出所在模块的分级名 将实型量以指数方式显示 将实型量以浮点方式显示 将实型量以上面两种种较短的方式显示 $time和$realtime属于模拟时标类系统函数,将返回从模拟程序开始执行到被调用时刻的时间,不同之处在于$time

返回的是64位整数,而$realtime返回的是一个实型数。

例 `timescale 10ns/1ns module time_demo; reg parameter initial begin

end

$display (“time value”); $monitor($time,,,“var=%b”,var); //$time换成$realtime,没有四舍五入误差的问题。 #delay #delay #1000

var=1; var=0; $finish; ar;

delay=1.6

endmodule

系统时间定标为10ns为计时单位,所以delay=1.6实际代表的时间是16ns,

输出时间应该是1.6和3.2,可是实际输出时2和3,这是因为$time在返回时间变量时进行了四舍五入。 显示结果如下: time value 0 var=x

2 var=1 3 var=0 4 .

3.4.4系统任务$finish和$stop

控制模拟进程。

$finish; $finish(n);

它的作用就是中止仿真器的运行,结束仿真过程。可以带上一个参数,参数n只能取以下三个值: 0:不输出任何信息。

1:输出结束仿真的时间和模拟文件的位置。

2:在1的基础上增加对CPU时间、机器内存占用情况等统计结果的输出。

如果$finish不指明参数时,默认为1。

$stop的调用方式相同和$finish相同, 参数也相同。不同的是,$stop的作用只相当于一个pause的暂停语句,,这是设计人员可以输入相应的命令,对模拟过程进行交互控制,比如用force/release语句,对某些信号实行强制性修改,在不退出仿真进程的前提下,进行模拟调试。

3.4.6系统任务$random产生一个随机数,

$random %b,b>0,它将产生一个范围在(-b+1)到(b-1)之间的随机数。

测试模块提供随机脉冲序列,如: reg[7:0] always

#(140+($random `)) ran_num=$random `

这样ran_num的值在-59~+59之间随机产生,且随机数产生的延时间隔在81~159之间变化。

以`(反引号)开始的某些标识符是编译器指令。在Verilog 语言编译时,特定的编译器指令在整个编译过程中有效

3.5.1 `define 和`undef

`define指令用于文本替换,它很像C语言中的#define 指令,如:

`define MAX_BUS_SIZE 32

. . .

reg [ `MAX_BUS_SIZE - 1:0 ] AddReg;

`undef 指令取消前面定义的宏。例如:

`define WORD 16 //建立一个文本宏替代。 . . .

wire [ `WORD : 1] Bus;

. . .

`undef WORD // 在`undef编译指令后, WORD的宏定义不再有效. 3.5.2 `ifdef、`else 和`endif 条件编译, `ifdef WINDOWS

parameter WORD_SIZE = 16 `else

parameter WORD_SIZE = 32

`endif

在编译过程中,如果已定义了名字为WINDOWS的文本宏,就选择第一种参数声明,否则选择第二种参数说明。`else 程序指令对于`ifdef 指令是可选的。

3.5.3 `default_nettype指定隐式线网类型,为那些没有被说明的连线定义线网类型。

`default_nettype wand 该实例定义的缺省的线网为线与类型。因此,如果在此指令后面的任何模块中没有说明的连线,那么该线网被假定为线与类型。

3.5.4 `include 嵌入内嵌文件的内容。

文件既可以用相对路径名定义,也可以用全路径名定义, 例如:

`include \

编译时,这一行由文件“../../primitives.v” 的内容替代。

`timescale time_unit / time_precision

time_unit 和time_precision 由值1、10、和100以及单位s、ms、us、ns、ps和fs组成。例如:

`timescale 1ns/100ps

表示时延单位为1ns, 时延精度为100ps。`timescale在模块说明外部出现, 并且影响后面所有的时延值。例如: and # (5.22, 6.17 ) Al (Z, A, B);

ran_num;

//规定了上升及下降时延值。

`timescale 10ns/1ns 那么5.22对应52ns, 6.17对应62ns。

直至遇到另一个`timescale指令或`resetall指令。当一个设计中的多个模块带有自身的`timescale编译指令时将发生什么?在这种情况下,模拟器总是定位在所有模块的最小时延精度上,并且所有时延都相应地换算为最小时延精度。例如,

`timescale 1ns/ 100ps module AndFunc (Z, A, B); output Z; input A, B;

and # (5.22, 6.17 ) Al (Z, A, B); endmodule

`timescale 10ns/ 1ns module TB; reg PutA, PutB; wire GetO;

initial begin PutA = 0; PutB = 0; #5.21 PutB = 1; #10.4 PutA = 1; #15 PutB = 0; end

AndFunc AF1(GetO, PutA, PutB); endmodule

在这个例子中,每个模块都有自身的`timescale编译器指令。`timescale编译器指令第一次应用于时延。因此,在第一个模块中,5.22对应5.2 ns, 6.17对应6.2 ns;

在第二个模块中5.21对应52 ns, 10.4对应104 ns, 15对应150 ns。

如果仿真模块TB,设计中的所有模块最小时间精度为100 ps。因此,所有延迟(特别是模块TB中的延迟)将换算成精度为100 ps。

延迟52 ns现在对应520*100 ps,104对应1040*100 ps,150对应1500*100 ps。

更重要的是,仿真使用100 ps为时间精度。如果仿真模块AndFunc,由于模块TB不是模块AddFunc的子模块,模块TB中的`timescale程序指令将不再有效。

`unconnected_drive pull1 . . .

/*在这两个程序指令间的所有未连接的输入端口为正偏电路状态(连接到高电平)*/ `nounconnected_drive

`unconnected_drive pull0 . . .

/*在这两个程序指令间的所有未连接的输入端口为反偏电路状态(连接到低电平)*/ `nounconnected_drive Verilog HDL有下列四种基本的值:

1) 0:逻辑0或“假” 2) 1:逻辑1或“真” 3) x:未知 4) z:高阻

z的值总是意味着高阻抗,一个为0的值通常是指逻辑0。

在门的输入或一个表达式中的为“z”的值通常解释成“x”。此外,x值和z值都是不分大小写的,也就是说, 值0x1z与值0X1Z相同

Verilog HDL中有三类常量: 1) 整型 2) 实数型

3) 字符串型

下划线符号(_)可以随意用在整数或实数中,它们就数量本身没有意义。它们能用来提高易读性;

整型数可以按如下两种方式书写:

1) 简单的十进制数格式 整数值代表一个有符号的数

2) 基数格式 以基数格式计数的数通常为无符号数。

[size ] 'base value

size 定义以位计的常量的位长;base为o或O(表示八进制),b或B(表示二进制),d或D(表示十进制),h或H(表示十六进制)之一;value是基于base的值的数字序列。值x和z以及十六进制中的a到f不区分大小写。

注意,x(或z)在十六进制值中代表4位x(或z),在八进制中代表3位x(或z),在二进制中代表1位x(或z)。 4'D2 4位十进制数 4'B1x_01 4位二进制数

7'Hx 7位x(扩展的x), 即xxxxxxx 4'hZ 4位z(扩展的z) , 即zzzz

4'd-4 非法:数值不能为负

8'h 2 A 在位长和字符之间, 以及基数和数值之间 允许出现空格 3' b001 非法: ` 和基数b之间不允许出现空格 (2+3)'b10 非法: 位长不能够为表达式

实数可以用下列两种形式定义: 1) 十进制计数法;例如2.0 5.678

2) 科学计数法; 这种形式的实数举例如下:23_5.1e2 其值为23510.0; 忽略下划线

实数如何隐式地转换为整数。实数通过四舍五入被转换为最相近的整数。

42.446, 42.45 转换为整数42 92.5, 92.699 转换为整数93

-15.62 转换为整数-16

字符串是双引号内的字符序列。字符串不能分成多行书写。例如:

用8位ASCII值表示的字符可看作是无符号整数。因此字符串是8位ASCII值的序列。为存储字符串“INTERNAL ERROR”,变量需要8*14位。

reg [1 : 8*14] Message; . . .

Message = \

3.7 数据类型

Verilog HDL 有两大类数据类型。 1) 线网类型。net type 表示Verilog结构化元件间的物理连线。它的值由驱动元件的值决定, 例如 连续赋值或门的输出。如果没有驱动元件连接到线网,线网的缺省值为z。

2) 寄存器类型。register type表示一个抽象的数据存储单元,它只能在always语句和initial语句中被赋值,并且它的值从一个赋值到另一个赋值被保存下来。寄存器类型的变量 具有x 的缺省值。

线网类型主要有wire 和tri 两种。线网类型用于对结构化器件之间的物理连线的建模。如器件 的管脚,内部器件如与门的输出等。以上面的与门为例,输入信号A,B是由外部器件所驱动。 由于线网类型代表的是物理连接线,因此它不存贮逻辑值。必须由器件所驱动。通常由assign 进行赋值。如 assign A = B ^ C;

当一个wire 类型的信号没有被驱动时,缺省值为Z (高阻)。 信号没有定义数据类型时,缺省为 wire 类型。

tri 主要用于定义三态的线网。

net_kind [msb:lsb] net1, net2, . . . , netN;

* wire * wor * wand * trior * triand * trireg * tri * tri1 * tri0 * supply0 * supply1

net_kind 是上述线网类型的一种。msb和lsb 是用于定义线网范围的常量表达式;如果没有定义范围,缺省的线网类型为1位。

wand [2:0] Addr; Addr是3位线与。

supply0用于对“地”建模,即低电平0;supply1网用于对电源建模,即高电平1;例如: supply0 Gnd, ClkGnd; supply1 [2:0] Vcc;

3.7.3 向量和标量线网

在定义向量线网时可选用关键词scalared 或vectored。如果一个线网定义时使用了关键词vectored, 那么就不允许位选择和部分选择该线网。换句话说,必须对线网整体赋值:

wire vectored [3:1] Grb;

//不允许位选择Grb[2]和部分选择Grb [3:2]

wor scalared [4:0] Best; //与wor [4:0] Best相同,如果没有定义关键词,缺省值为标量。 允许位选择Best [2]和部分选择Best [3:1]。

3.7.4 寄存器类型

有5种不同的寄存器类型。

* reg * integer * real * time * realtime 1. reg寄存器类型

reg [msb: lsb] reg1, reg2, . . . regN;

如果没有定义范围,缺省值为1位寄存器。例如: reg [3:0] Sat; //Sat为4 位寄存器。

reg Cnt; //1位寄存器。

寄存器可以取任意长度。寄存器中的值通常被解释为无符号数, 例如: reg [1:4] Comb; . . .

Comb = -2; //Comb 的值为14(1110),1110是2的补码。

Comb = 5; //Comb的值为15(0101)。

3. 书写规范建议 对数组类型,请按降序方式,如[7:0] ;

2. 存储器

存储器是一个寄存器数组。数组的维数不能大于2。

Reg [msb: lsb] memory1 [ upper1: lower1], memory2 [upper2: lower2],. . . ; 例如:

reg [0:3] MyMem [0:63] //MyMem为64个4位寄存器的数组。 reg Bog [1:5] 在后面 //Bog为5个1位寄存器的数组。

parameter ADDR_SIZE = 16 , WORD_SIZE = 8;

reg [1: WORD_SIZE] RamPar [ ADDR_SIZE-1 : 0], DataReg;

RamPar是存储器,是16个8位寄存器数组,而DataReg是8位寄存器。

存储器赋值不能在一条赋值语句中完成,但是寄存器可以。因此在存储器被赋值时,需要定义一个索引。

reg [1:5] Dig; //Dig为5位寄存器。 Dig = 5'b11011; 正确的, reg BOg[1:5]; //Bog为5个1位寄存器的存储器。 Bog = 5'b11011; 不正确:

有一种存储器赋值的方法是分别对存储器中的每个字赋值。例如: reg [0:3] Xrom [1:4]

Xrom[1] = 4'hA; Xrom[2] = 4'h8; Xrom[3] = 4'hF; Xrom[4] = 4'h2;

1) $readmemb (加载二进制值) 2) $readmemb (加载十六进制值)

这些系统任务从指定的文本文件中读取数据并加载到存储器。文本文件必须包含相应的二进制或者十六进制数。例如:

reg [1:4] RomB [7:1] ;

$ readmemb (\

$readmemb (\从地址6开始,并且持续到1。 $readmemb ( \从地址6读到地址4。

3. Integer寄存器类型

整数寄存器包含整数值。作为普通寄存器使用

integer integer1, integer2,. . . intergerN [msb:1sb] ;

msb和lsb是定义整数 数组界限 的常量表达式,一个整数最少容纳32位。 integer A, B, C; //三个整数型寄存器。 integer Hist [3:6]; //一组四个寄存器。

可存储有符号数,并且算术操作符提供2的补码运算结果。

整数不能作为位向量访问。例如,对于上面的整数B的说明,B[6]和B[20:10]是非法的。 将整数赋值给一般的reg类型变量,然后从中选取相应的位,如下所示:

reg [31:0] Breg;

integer Bint; //Bint[6]和Bint[20:10]是不允许的。

Breg = Bint; /*现在,Breg[6]和Breg[20:10]是允许的,并且从整数Bint获取相应的位值。*/

通过简单的赋值将整数转换为位向量。类型转换自动完成,不必使用特定的函数。从位向量 到 整数的转换也可以通过赋值完成。例如: integer J;

reg [3:0] Bcq;

J = 6; //J的值为32'b0000...00110。 Bcq = J; // Bcq的值为4'b0110。

Bcq = 4'b0101.

J = Bcq; //J的值为32'b0000...00101。

J = -6; //J 的值为 32'b1111...11010。 Bcq = J; //Bcq的值为4'b1010。

注意赋值总是从最右端的位向最左边的位进行;任何多余的位被截断。

4. time类型 寄存器用于存储和处理时间

time time_id1, time_id2, . . . ,time_idN [ msb:1sb];

未定义界限,每个标识符存储一个至少64位的时间值。时间类型的寄存器只存储无符号数 time Events [0:31]; //时间值数组。

time CurrTime; //CurrTime 存储一个时间值

5. real和realtime类型 实数寄存器(或实数时间寄存器)

real real_reg1, real_reg2, . . ., real_regN;

realtime realtime_reg1, realtime_reg2, . . . ,realtime_regN;

real说明的变量的缺省值为0。不允许对real声明 值域、位界限 或 字节界限。 当将值x和z赋予real类型寄存器时,这些值作0处理。

real RamCnt; . . .

RamCnt = 'b01x1Z; RamCnt在赋值后的值为'b01010。

3.8 参数

参数是一个常量。用于定义时延和变量的宽度。

parameter param1 = const_expr1, param2 = const_expr2, . . . , paramN = const_exprN;

parameter BIT = 1, BYTE = 8, PI = 3.14;

第四节 Verilog中的表达式

4.1 操作数

操作数可以是以下类型中的一种:

1) 常数 2) 参数 3) 线网 4) 寄存器 5) 位选择 6) 部分选择 7) 存储器单元 8) 函数调用

表达式中的整数值可被解释为有符号数或无符号数。如果表达式中是十进制整数,例如,12被解释为有符号数。如果整数是基数型整数(定长或非定长),那么该整数作为无符号数对待。下面举例说明。

更为重要的是对基数表示或非基数表示的负整数处理方式不同。非基数表示形式的负整数作为有符号数处理,而基数表示形式的负整数值作为无符号数。因此-44和-6'o54 (十进制的44等于八进制的54)在下例中处理不同。

integer Cone; . . .

Cone = -44/4 Cone = -6'o54/ 4;

注意-44和-6'o54以相同的位模式求值;但是-44作为有符号数处理,第一个字符中Cone的值为-11, 而-6'o54作为无符号数处理。第二个赋值中Cone的值为1073741813。

表达式中使用标量线网(1位)和向量线网(多位)。下面是线网说明实例。

wire [0:3] Prt; //Prt 为4位向量线网。 wire Bdq; //Bbq 是标量线网。

线网中的值被解释为无符号数。

assign Prt = -3; Prt被赋于位向量1101,实际上为十进制的13。 assign Prt = 4'HA; Prt被赋于位向量1010,即为十进制的10。

integer TemA, TemB; reg [1:5] State; time Que [1:5];

整型寄存器中的值被解释为有符号的二进制补码数,而reg寄存器或时间寄存器中的值被解释为无符号数。实数和实数时间类型寄存器中的值被解释为有符号浮点数。

TemA = -10; //TemA值为位向量10110,是10的二进制补码。 TemA = 'b1011; //TemA值为十进制数11。

State = -10; //State值为位向量10110,即十进制数22。 State = 'b1011; //State值为位向量01011,是十进制值11。

位选择从向量中抽取特定的位。net_or_reg_vector [bit_select_expr] State [1] && State [4] //寄存器位选择。 Prt [0] | Bbq //线网位选择。

表达式的值为x、z,或越界,则位选择的值为x。例如State [x]值为x。

State [1:4] //寄存器部分选择。 Prt [1:3] //线网部分选择。

选择范围越界 或 为x、z时, 部分选择的值为x。

存储器单元从存储器中选择一个字。形式如下:

memory [word_address]

reg [1:8] Ack, Dram [0:63];

Ack = Dram [60]; //存储器的第60个单元。

将存储器单元赋值给寄存器变量,然后对该寄存器变量 采用部分选择或位选择操作。例如,Ack [2] 和Ack [2:4]是合

法的表达式。

表达式中可使用函数调用。函数调用可以是系统函数调用(以$字符开始)或用户定义的函数调用 $time + SumOfEvents (A, B)

4.2 操作符

Verilog HDL中的操作符可以分为下述类型:

1) 算术操作符 2) 关系操作符 3) 相等操作符 4) 逻辑操作符 5) 按位操作符 6) 归约操作符 7) 移位操作符 8) 条件操作符 9) 连接和复制操作符

整数 除法 截断任何小数部分。7/4 结果为 1

取模操作符求出 与第一个操作符符号相同的余数。7%4 结果为 3 - 7%4 结果为 -3

算术操作符中的任意操作数是X或Z,那么整个结果为X。'b10x1 + 'b01111 结果为不确定数'bxxxxx

2. 无符号数和有符号数

执行算术操作和赋值时,注意哪些操作数为无符号数、哪些操作数为有符号数非常重要。无符号数存储在: * 线网 * 一般寄存器

* 基数格式表示形式的整数 有符号数存储在: * 整数寄存器

* 十进制形式的整数

reg [0:5] Bar;

integer Tab; . . .

Bar = -4'd12; //寄存器变量Bar的十进制数为52,向量值为110100。 Tab = -4'd12; / /整数Tab的十进制数为-12,位形式为110100。

-4'd12 / 4 //结果是1073741821。 -12 / 4 //结果是-3

Bar = 4 - 6; Tab = 4 - 6;

Bar被赋于十进制值62(-2的二进制补码),而Tab被赋于十进制值-2(位向量为111110)。

Bar = -2 + (-4); Tab = -2 + (-4);

Bar被赋于十进制值58(位向量为111010),而Tab被赋于十进制值-6(位向量为111010)。

关系操作符有:

* >(大于)* <(小于)* >=(不小于)* <=(不大于) 关系操作符的结果为真(1)或假(0)。如果操作数中有一位为X或Z,那么结果为X。

* = =(逻辑相等) * !=(逻辑不等) * = = =(全等) * != =(非全等) 在全等比较中,值x和z严格按位比较。

逻辑比较中,值x和z具有通常的意义,且结果可以不为x

在逻辑比较中,如果两个操作数之一包含x或z,结果为未知的值(x)。 Data = 'b11x0; Addr = 'b11x0;

那么:Data = = Addr 不定,也就是说值为x,但: Data = = = Addr 为真,也就是说值为1。

如果操作数的长度不相等,长度较小的操作数在左侧添0补位,例如:

==和!=是比较逻辑值,由于操作数中某些位可能是x,所以比较结果也有可能是x。

===和!==是按位比较,所以不会出现结果为x的情况。

逻辑操作符有: * && (逻辑与) * || (逻辑或) * !(逻辑非) 这些操作符在逻辑值0或1上操作。逻辑操作的结构为0或1 对于向量操作, 非0向量作为1处理。例如,假定: A_Bus = 'b0110; B_Bus = 'b0100; 那么:

A_Bus || B_Bus 结果为1 A_Bus && B_Bus 结果为 1

! A_Bus 与! B_Bus的结果相同。 结果为0。

如果任意一个操作数包含x,结果也为x。 !x 结果为x

按位操作符有:

* ~(一元非) * &(二元与) * |(二元或) * ^(二元异或) * ~^, ^~(二元异或非)

4.2.9 连接和复制操作

连接操作是将小表达式合并形成大表达式的操作。形式如下: {expr1, expr2, . . .,exprN}

wire [7:0] Dbus; wire [11:0] Abus;

assign Dbus [7:4] = {Dbus [0], Dbus [1], Dbus[2], Dbus[3]}; //以反转的顺序将低端4位赋给高端4位。 assign Dbus = {Dbus [3:0], Dbus [7:4]}; //高4位与低4位交换。

由于非定长常数的长度未知, 不允许连接非定长常数 {Dbus,5} //不允许连接操作非定长常数。

移位操作符有:

* << (左移) * >> (右移)

条件操作符根据条件表达式的值选择表达式,形式如下: cond_expr ? expr1 : expr2

如果cond_expr 为真(即值为1),选择expr1;如果cond_expr为假(值为0),选择expr2。

2.5 结构化描述形式

1) 内置门原语(在门级);

2) 开关级原语(在晶体管级); 3) 用户定义的原语(在门级); 4) 模块实例 (创建层次结构)。 nand #1 (Q, R, Qbar);

nand #1 (Qbar, S, Q,);

//在门实例语句中,实例名称是可选的。

门级模型是对电路的具体描述,主要是描述与、或、非等基本电路的连接方式;开关级建模则更加接近“底层”,它把最基本的MOS晶体管连接起来实现电路功能。 Verilog HDL中提供下列内置基本门: 1) 多输入门:and, nand,or, nor,xor,xnor

2) 多输出门:buf, not

3) 三态门:bufif0, bufif1, notif0,notif1

4) 上拉、下拉电阻:pullup, pulldown

5) MOS开关:cmos, nmos, pmos, rcmos, rnmos, rpmos

6) 双向开关:tran,tranif0, tranif1, rtran, rtranif0, rtranif1

gate_type [instance_name] (term1, term2, . . . ,termN);

primitive_type[instance_name] (term1, term2, .........,termN);

其中,primitive_type是26个基元之一,如and、cmos;instance_name是基元的实例名,是可选项。不同的基元输入输出端口的数量和功能都不同,termN表示输入输出端口。 输出端口在前

同一门类型的多个实例能够在一个结构形式中定义。语法如下:

gate_type

[instance_name1] (term11, term12, . . .,term1N), [instance_name2] (term21, term22, . . .,term2N), . . .

[instance_nameM] (termM1, termM2, . . .,termMN);

注意,instance_name是可选的;gate_type为前面列出的某种门类型。各term用于表示与门的输入/输出端口相连的线网或寄存器。

1) 多输入门:and, nand,or, nor,xor,xnor 只有单个输出,1个或多个输入

multiple_input_gate_type [instance_name] (OutputA, Input1, Input2, . . .,InputN); 第一个端口是输出,其它端口是输入。

and A1(Out1, In1, In2); 单元名为A1、输出为Out1、并带有两个输入In1和In2的两输入与门

and RBX (Sty, Rib, Bro, Qit, Fix); 单元名为RBX,输出为Sty,4个输入为Rib、Bro、Qit和Fix

xor (Bar, Bud[0],Bud[1], Bud[2]), (Car, Cut[0], Cut[1]),

(Sar, Sut[2], Sut[1], Sut[0], Sut[3]); 异或门,没有单元名。它的输出是Bar,三个输入分别为Bud[0]、Bud[1]和Bud[2]。这一个实例语句中还有两个相同类型的单元。

2) 多输出门:buf, 缓冲门 not 非门 只有单个输入,一个或多个输出。

multiple_output_gate_type [instance_name] (Out1, Out2, . . . OutN ,InputA); 其余的所有端口为输出端口。最后的端口是输入端口

buf B1 (Fan [0],Fan [1],Fan [2],Fan [3],Clk); Clk是缓冲门的输入。门B1有4个输出:Fan[0]到Fan[3]。 not N1 (PhA,PhB,Ready); Ready是非门的唯一输入端口 门N1有两个输出:PhA和PhB。

3) 三态门:bufif0, bufif1, not if0,notif1 对三态驱动器建模。这些门有一个输出、一个数据输入和一个控制输入 1表示控制信号不取反,0表示控制信号取反。bufif表示输出不取反,notif表示输出取反 tristate_gate[instance_name] (OutputA, IputB,ControlC);

根据控制输入,输出可被驱动到高阻状态,即值z。

对于bufif0,若通过控制输入为1,则输出为z;否则数据被传输至输出端。 对于bufif1,若控制输入为0,则输出为z。

对于notif0,如果控制输入为1,那么输出为z;否则输入数据值的非传输到输出端。 对于notif1,若控制输入为0;则输出为z。

bufif1 BF1 (Dbus,MemData,Strobe);当Strobe为0时,bufif1门BF1驱动输出Dbus为高阻;否则MemData被传输至Dbus。

notif0 NT2 (Addr, Abus, Probe); 当Probe为1时,Addr为高阻;否则Abus的非传输到Addr。

4) 上拉、下拉电阻:pullup, pulldown 没有输入 只有输出。上拉电阻将输出置为1。下拉电阻将输出置为0。

pull_gate [instance_name] (OutputA); 只包含1个输出 pullup PUP (Pwr); 上拉电阻实例名为PUP,输出Pwr置为高电平1。

5) MOS开关:c mos, n mos, p mos, rc mos, rn mos, rp mos

单向开关建模。即数据从输入流向输出,并且可以通过设置合适的控制输入关闭数据流。

MOS模型在仿真时表现为两种状态,开或关,即导通或不导通,所以MOS可作为开关使用。对于MOS来说,数据只能从输入端流向输出端,并且可以通过设置控制信号来关闭数据流,所以MOS是单向的 pmos、nmos、rnmos和rpmos都是三端口MOS开关,

pmos(p类型MOS管)、nmos(n类型MOS管),rnmos(r代表电阻)和rpmos开关 mos_type[instance_name](OutputA, InputB, ControlC);

gate_type[instance_name] (OutputA, InputB, ControlC); 第三个端口是控制输入端

如果nmos和rnmos开关的控制输入为0,pmos和rpmos开关的控制为1,那么开关关闭,即输出为z;如果控制是1,输入数据传输至输出;

与nmos和pmos相比,rnmos和rpmos在输入引线和输出引线之间存在高阻抗(电阻)。因此当数据从输入传输至输出时,对于rpmos和rmos,由于电阻带来的损耗,存在数据信号强度衰减。

pmos P1 (BigBus, SmallBus, GateControl); 实例名为P1 的pmos开关。 rnmos RN1 (ControlBit, ReadyBit, Hold); ?????

cmos和rcmos(cmos的高阻态版本)有4个端口:两个控制信号输入端。其形式如下:

(r)cmos[instance_name](OutputA, InputB, NControl, PControl); (r)cmos [instance_name] (OutputA, InputB, Ncontrol, PControl);

第三个端口为n通道控制输入,第四个端口为是P通道控制输入。

cmos(rcmos)开关行为与带有公共输入、输出的pmos(rpmos)和nmos(rnmos)开关组合十分相似。

6) 双向开关:tran rtran tranif0 rtranif0 tranif1 rtranif1

双向的,即数据可以双向流动,并且当数据在开关中传播时没有延时。后4个开关能够通过设置合适的控制信号来关闭。tran和rtran开关不能被关闭。不能关断的,始终处于打开状态,数据可以在两个端口之间自由流动。 tran或rtran(tran 的高阻态版本)

(r)tran [instance_name] (SignalA, SignalB ); 无条件地双向流动,即从SignalA向SignalB,反之亦然。

其它双向开关的实例语句的语法如下:

gate_type[instance_name] (SignalA, SignalB, ControlC);

前两个端口是双向端口,即数据从SignalA流向SignalB,反之亦然。第三个端口是控制信号。 对tranif0和rtranif0,ControlC是1;

对tranif1和rtranif1,Controlc是0;那么禁止双向数据流动。

对rtran、rtranif0和rtranif1,当信号通过开关传输时,信号强度减弱。

5.8 门时延

可以使用门时延定义门从任何输入到其输出的信号传输时延。 gate_type [delay] [instance_name] (terminal_list);

时延规定了门时延,即从门的任意输入到输出的传输时延。当没有强调门时延时,缺省的时延值为0。 门时延由三类时延值组成:

1) 上升时延 2) 下降时延 3) 截止时延

门时延定义可以包含0个、1个、2个或3个时延值。

无时延 1个时延(d) 2个时延(d1, d2) 3个时延 (dA, dB, dC)

not N1 (Qbar, Q); 没有定义时延,门时延为0。

nand #6 (Out, In1, In2); 所有时延均为6,上升时延和下降时延都是6 输出决不会是高阻态,截止时延不适用于与非门。转换到x的时延也是6

and #(3,5) (Out, In1, In2, In3); 上升时延被定义为3,下降时延为5,转换到x的时延是3和5中间的最小值,即3

notif1 #(2,8,6) (Dout, Din1, Din2); 上升时延为2,下降时延为8,截止时延为6,转换到x的时延是2、8和6中的最小值,即2。

对多输入门(例如与门和非门)和多输出门(缓冲门和非门)总共只能够定义2个时延(因为输出决不会是z)。 三态门共有3个时延

上拉、下拉电阻实例门不能有任何时延

门延迟也可采用min:typ:max形式定义。形式如下:

minimum: typical: maximum

nand #(2:3:4, 5:6:7) (Pout, Pin1, Pin2); 如果执行最大时延模拟,与非门单元使用上升时延4和 下降时延7。

4.3 模块实例化

调用低层子模块把所有模块连接成整个电路或者高层模块时,要使用模块实例化语句。

module_name instance_name(port_associations); 其中port_associations是端口关联声明。 4.3.1 端口关联声明

子模块的输入输出端口应该连接到高层模块中,完成这种连接的是高层模块中的线网和寄存器,其连接方式可以是“位置关联方式”和名称关联方式,二者形式如下:

port_expr //位置关联方式 .PortName(port_expr) //名称关联方式

Portname是子模块定义时给出的端口名称,port_expr是高层模块内定义的线网或寄存器变量,这个变量与子模块端口关联就实现了子模块与高层模块的连接。

在位置关联方式中,不需要给出子模块定义时给出端口名称PortName,只要把相应的prot_expr按指定的顺序排列就能和子模块的端口关联,这个排列顺序必须和子模块定义时给出的 端口顺序相同。

4.3.2 悬空端口

在模块实例化语句中,允许端口出现悬空,可以将端口表达式表示为空白来指定悬空端口。看下例: module DFF(Q, Qbar, Data, Preset, Clock); output Q, Qbar;

input Data, Preset, Clock; .............. endmodule

在高层模块中两次调用DFF模块,模块实例化语句如下:

DFF d1(.Q(Q), .Qbar( ), .Data(D), .Preset( ), .Clock(CK) );

//名称关联方式,其中输出端口Qbar和输入端口Preset的括号里都是空的,表明这两个端口被悬空。 DFF d2(QS, ,D, , CK); //位置关联方式,输出端口Qbar和输入端口Preset的位置空白,被悬空。 4.3.3 端口匹配

在端口关联时,如果出现关联端口长度不同的情况,处理方式是通过无符号数的右对齐并截断的方式进行匹配。 4.3.4 模块参数值

如果子模块内定义了参数,当这个子模块被引用时,高层模块能够改变子模块的参数值。改变模块参数值可以采用两种方式:参数定义语句(使用关键词defparam)和带参数值的模块引用。

(1)参数定义语句

参数定义语句需要使用关键词defparam,其形式如下:

defparam hier_path_name1 = value1, hier_path_name2 = value2, ..........;看下面的例子: 子模块半加器的描述如下: module HA(A, B, S, C); input A, B; output S, C;

parameter AND_DELAY = 1, XOR_DELAY = 2; //定义了两个参数 assign #XOR_DELAY S = A^B assign #AND_DELAY C = A&B endmodule

高层模块描述如下:

module TOP(NewA, NewB, NewS, NewC); input NewA, NewB; output NewS, NewC;

defparam ha1.XOR_DELAY = 5, ha1.AND_DELAY = 2; HA ha1(NewA, NewB, NewS, NewC); end module

在这个高层子模块中,引用了子模块HA,并将其实例命名为ha1,通过defparam语句给ha1的连个参数重新赋值。 (2)代参数值的模块引用

在这种方法中,模块实例化语句自身包含有新的参数值。实现方式如下: HA #(5,2) HA1(NewA, NewB, NewS, NewC);

给出参数值的方式是“#(5,2)”,形式上与时延定义相似,但意义完全不同。在这种方式中,模块实例化语句中参数值的顺序 必须与子模块中 声明参数的顺序相同。

在verilog中,用户可以定义任务和函数,而且它还内置了一些系统任务和系统函数用于实现某些特定的操作 5.1 任务

任务就是一段封装在“task-endtask”之间的程序。任务是通过调用来执行的,而且只有在调用时才执行,如果定义了任务,但是在整个过程中都没有调用它,那么这个任务是不会执行的。调用某个任务时可能需要它处理某些数据并返回操作结果,所以任务应当有接收数据的输入端和返回数据的输出端。另外,任务可以彼此调用,而且任务内

还可以调用函数。

注意:任务是不可综合的,它只能用于仿真。 5.1.1 任务定义 任务定义的形式如下: task task_id [declaration] procedural_statement endtask

其中,task_id是任务名;可选项declaration是端口声明语句和变量声明语句,任务接收输入值和返回输出值就是通过此处声明的端口进行的;procedural_statement是一段用来完成这个任务操作的过程语句,如果过程语句多于一条,应将其放在语句块内。

任务调用语句可以在initial语句和always语句中使用,其语法形式如下: task_id[(expr1, expr2, ........, exprN)];

task_id是要调用的任务名,expr1, expr2, ........是参数列表。参数列表给出传入任务的数据(进入任务的输入端)和接收返回结果的变量(从任务的输出端接收返回结果),任务调用语句中参数列表的顺序必须与任务定义中的端口声明顺序相同。任务调用语句是过程性语句,所以任务调用中接收返回数据的变量必须是寄存器类型。来看下例:

module Has_Task; parameter MAXBITS = 8; task Reverse_Bits; input [MAXBITS-1:0] Din; output [MAXBITS-1:0] Dout; integer K; begin

for(K=0; K

Dout[MAXBITS-K] = Din[K];

end endtask end module

下面调用Reverse_Bits的代码: reg[MAXBITS-1:0] Reg_X, New_Reg; Reverse_Bits(Reg_X, New_Reg);

其中,Reg_X的值作为输入数据送到任务的输入端Din,任务的返回值从其输出端Dout输出并交给New_Reg,在寄存器new_reg中得到返回值。

调用任务时,可以引用任务声明所在的模块内定义的任何变量。

任务内可以带有时序控制,如时延。但要注意,任务的输出值必须等到整个任务的全部语句都执行完之后才能返回。

5.2 函数

“function-endfunction”之间。函数与任务的不同之处在于: ·函数只能返回一个值,而任务可以有多个返回值;

·函数一经调用就必须立即执行,其内部不能包含任何时序控制,而任务内部可以有时序控制; ·函数可以调用函数,但是不能调用任务,而任务即可以调用任务也可以调用函数; ·函数至少有一个输入,而任务可以没有输入端。 5.2.1 函数定义

函数定义和任务定义一样,可以出现在模块内的任何位置,其形式如下: function [range] function_id; input_declaration other_declarations procedural_statement endfunction

这里的range用于指定函数的取值范围,是可选项,若没有指定,默认缺省值为1。 module Function_Example; parameter MAXBITS = 8;

function [MAXBITS-1:0] Reverse_Bits; input [MAXBITS-1:0] Din; integer K; begin

for(K=0; K

Reverse_Bits[MAXBITS-K] = Din[K];

end

endfunction endmodule

注意到没有,函数的定义中并没有声明输出,那么函数执行得到的结果如何返回呢?事实上,函数定义时,在函数内部已经隐性的声明了一个寄存器变量,该寄存器变量与函数名同名并且取值范围也相同。那么函数如何通过这个寄存器返回值?注意上例中的“Rever_Bits[MAXBITS-K] = Din[K];”这条语句,就是通过这条语句把Din[K]的值赋给寄存器Reverse_Bits[MAXBITS-K],同时也实现了值的返回。

5.2.2 函数调用

和任务一样,函数也是在被调用时才被执行的,调用函数的语句形式如下:

func_id(expr1, expr2, ........., exprN)

其中,func_id是要调用的函数名,expr1, expr2, ......exprN是传递给函数的输入参数列表,该输入参数列表的顺序必须与函数定义时 声明其输入的顺序相同。

reg[MAXBITS-1:0] New_Reg, Reg_X; New_Reg = Reverse_Bits(Reg_X);

与任务相似,在函数内部声明的所有寄存器都是静态的,当函数被调用时,这些寄存器的值不能被改变。 这些系统任务和系统函数可以分为以下几类:

·显示任务 ·文件输入/输出任务 ·时间标度任务 ·仿真控制任务 ·时序验证任务 ·PLA建模任务 ·随即建模任务 ·仿真时间函数 ·实数变换函数 ·随即函数 这些系统任务和系统函数基本都是针对仿真过程的,与硬件模型的功能无关。 5.3.1 显示任务

显示任务可以在仿真过程中用于信息显示和输出。这些显示任务又可以进一步分为3类: ·显示和写入任务 ·探测监控任务 ·连续监控任务 (1)显示和写入任务

task_name(format_specification1, argument_list1,format_specification2, argument_list2, format_specificationN,argument_listN);

其中,task_name是调用的系统任务名,format_specification是格式定义,armgument_list是传入系统任务的参数。task_name可以是下列指令之一:

· $display $displayb $displayo $displayh

显示任务将特定信息输出到 标准输出设备,并且带有行结束符(\\n); · $write $writeb $writeo $writeh 而写入任务输出特定信息时 不带有行结束符。

(2) 探测任务

探测任务用于在指定时间----显示仿真数据,共有4种类型: ·$strobe $strobeb $strobeh $strobeo always @ (posedge Rst)

$strobe(\当Rst出现一个上升沿时,$stobe任务将输出当前的Q值和仿真时刻。

探测任务与显示任务的不同之处在于:显示任务是在遇到该语句时执行,而探测任务则要推迟到当前时刻结束时

才执行。那什么是当前时刻呢?注意这些系统任务的执行是不占用仿真时间的,所以当前时刻应该在探测语句后的赋值语句,等当这个个赋值语句执行完之后,探测任务才执行。

(3)监控任务

监控任务将连续监控指定的参数,只要参数表中的参数发生变化,整个参数表就在当前仿真时刻结束时显示。监控任务有4种:

· $monitor $monitorb $monitorh $monitoro 监控任务的语法形式与显示任务相同,例如: initial

$monitor(\ \

该监控任务执行时,将对信号D、Clk和Q进行监控。如果这三个参数中有任何一个的值发生变化,就显示所有参数的值。另外两个系统任务$monitoroff和monitoron把监控任务关闭或开启。

5.3.2 文件输入/输出任务 (1)文件的打开和关闭

系统函数$fopen可以打开一个文件,其形式如下: integer file_pointer = $fopen(file_name);

$fopen将返回关于文件file_name的整数(指针),并把它赋给整形变量file_pointer。与之相应的是,系统函数$fclose可以通过文件指针关闭文件。形式如下:

$fclose(file_pointer); (2) 输出到文件

显示、写入、探测和监控系统任务都有用于向文件输出信息的相应版本,可用于将信息写入文件。这些任务在使用时只需要增加一个参数 即第一个参数,该参数都是文件指针(指示要把信息写入哪个文件)。

(3)从文件中读取数据

有两个系统任务能够用于从文本文件中读取数据并将数据加载到存储器,它们是: · $readmemb 读取二进制格式数 · $readmemh 读取十六进制格式数 其语法形式是:

task_id(\

还有一种方式可以把指定的数据放入指定的存储器地址单元内,就是在存放数据的文本文件内,给相应的数据规定其内存地址,形式如下:

@address_in_hexadecimal data

5.3.4 仿真控制任务

仿真控制任务用于使仿真进程停止,这类系统任务共有两个: ·$finish $stop 二者用法相同。

本文来源:https://www.bwwdw.com/article/muup.html

Top