4.18层次建模的概念
- 设计学方法
- 分类:自顶向下、自底向上。
典型设计中,二者同时存在。设计人员定义顶层模块,逻辑设计者将计划划分为子模块,电路设计者对底层功能块电路进行优化设计。
电路设计者——开关级原语——底层功能块库。
逻辑设计者——库单元——结构描述设计。
- 模块
- module一个基本的功能块。可以是一个元件,也可以是一个低层次模块的组合。
- 设计方法:使用元件构建该功能块。
- 特点及优点:设计中多个地方使用,构建成模块以便代码重用。设计者修改模块时不影响其他部分的设计。
- 获取方式:高层模块通过接口(输入或输出)对模块进行调用,不显示其内部细节。
- 声明方式:以关键字module开始,关键字endmodule结束。模块名惟一的标识该模块,端口列表则描述该模块的输入与输出端口。
- 内部描述:每个模块内部可以在4个抽象层次中进行描述(行为或算法级、数据流级、门级、开关级)。P.S.允许设计者在一个模块中多层次混合使用。也允许设计者在一个设计所包含的各个模块采用不同的描述。【抽象层次的高低和设计的灵活性、工艺无关性的强弱成正比】
RTL描述:寄存器传输级描述,是指行为级与数据流级的一种混合描述(能被逻辑综合工具接受的)。
module *模块名*(*模块端口列表*)//模块声明过程
*模块内容*//该部分内容不允许再嵌套模块
endmodule
- 模块实例
- 模块声明类似于一个模板,调用该模板就可以创建实际对象(创建过程叫instantiation实例化,创建的对象叫instance实例)。调用该模板所创建(实例化)的每个对象(实例)都有其不同的信息:名字、变量、参数、输入/输出接口。
- 模块声明说明了模块如何工作,其内部结构与外部接口;而对模块进行调用必须通过对其实例化来完成(创建对象的过程)。
T_FF tff0(q[0],clk,reset);//调用D_FF,取名dff0
-
逻辑仿真的构成
-
在设计完成之后,需要对其逻辑设计的正确性验证,这样的测试过程叫做逻辑仿真。
-
过程实现:对模块施加激励,检查其输出。用于测试模块功能的块儿,称为激励块,也可称为测试台。可以选择多种测试台对设计块进行全面测试。
-
激励块的设计方式:1.包含关系:在激励块的声明中直接调用并驱动设计块(在此顶层块为激励块,由他控制对设计块的输入信号,以及检查设计块的输出信号); 2.并列关系:在虚拟的顶层模块(顶层模块的作用在此只是调用二者)中调用激励块和设计块(二者通过接口进行交互)。
-
实例
-
设计块:选择自顶向下的方法进行设计。
-
step 1:顶层描述。-step 2:定义设计块。- step 3:定义激励块。- step 4:检查仿真结果。
/***************************step 1*********************************/
module ripple_carry_counter(q,clk,reset);//声明顶层模块
output [3:0] q;
input clk,reset;
T_FF tff0(q[0],clk,reset);//调用T_FF模块
T_FF tff1(q[1],q[0],reset);
T_FF tff2(q[2],q[1],reset);
T_FF tff3(q[3],q[2],reset);
endmodule
/**************************step 2********************************/
module T_FF(q,clk,reset);//声明T_FF模块
output q;
input clk,reset;
wird d;
D_FF dff0(q,d,clk,reset);//调用D_FF模块
not n1(d,q);//非门
endmodule
module D_FF(q,d,clk,reset);//声明D_FF模块
output q;
input d,clk,reset;
reg q;
always @(posedge reset or negedge clk)
if(reset)
q<=1'b0;
else
q<=d;
endmodule
/**************************step 3******************************/
module stimulus;
reg clk;
reg reset;
wire[3:0] q;
ripple_carry_counter r1(q,clk,reset);//此处引用设计模块(采用包含关系的设计模块)
initial
clk = 1'b0;//初始clk为0
always
#5 clk = ~clk;//每5个时间单位时钟翻转一次,时钟信号的时钟周期为10个时间单位
initial
begin
reset = 1'b1;//初始reset为1
#15 reset = 1'b0;//在第15个时间单位时,reset翻转为0
#180 reset = 1'b1;//在第180+15个时间单位时,reset翻转为1
#10 reset = 1'b0;//在第10+180+15个时间单位时,reset翻转为0
#20 $finish;//在第20+10+180+15个时间单位时,终止仿真
end
initial
$monitor($time,"Output q=%d",q);
endmodule
4.22基本概念
-
词法约定
-
Verilog描述包含一个单词流。
-
【空白符】仅用于分割标识符,编译时被忽略。
-
【注释】单行注释// 多行注释/(内容)/
-
【操作符】单目、双目、三目。(单目操作符置于操作数前,双目操作符置于两操作符之间,三目操作符是两个单独的操作符,用来分割三个操作数。)
-
【数字声明】指明位数的数字<数字位宽度>’<基数格式><数字> :其中数字位宽度用十进制数来表示,合法的基数格式包括十进制('d或’D)十六进制('h或’H)二进制('b或’B)八进制('o或’O),数字用连续的阿拉伯数字0~10以及a、b、c、d、e、f来表示(其中不同的基数只能相应的使用其中的一部分)。 不指明位数的数字:没有指定基数则默认为十进制,没有指定宽度则默认为位宽度与仿真器和使用的计算机有关(最小32位)。
【X和Z值】不确定值用X表示,高阻值用Z表示:如果某数的最高位为0/x/z,Verilog语言约定将分别使用0/x/z自动对这个数进行扩展以填满余下的更高位。如果某数的最高位为1,则Verilog语言约定将使用0来扩展余下的更高位。举例如下:
12'h13x//这是一个12位的十六进制数,四个最低位不确定(在以十六进制为基数的表示中x/z代表4位) 32'bz //这是一个32位的高阻值(在以二进制为基数的表示中x/z代表1位) b'ox//这是一个6位的八进制数,所有位都不确定(在意八进制为基数的表示中x/z代表3位)
【复数】在表示位宽的数字前面加一个减号,来表明这个常数是个复数。
【下划线和问号】除了第一个字符,下划线可以出现在数字中的任何位置以提高可续性,编译阶段被忽略掉。而“?”是高阻值Z的另一种表示,可以表示“不必关心”的情况。 -
【字符串】“(内容)”:这样的一个用双引号括起来的字符队列叫做字符串。必须在一行中书写完,不能书写在多行中,亦不可包含回车符。
-
【关键字】用于定义语言结构的预留语言,实际上是一种特殊的标识符。Verilog中的关键字全部小写,关键字还包括系统任务和编译指令。
-
【标识符】程序代码中对象的名字,是程序员访问对象的手段。Verilog中的标识符由字母数字字符、下划线_、美元符号$组成,(标识符不可以以数字或美元符号为首,美元符号开始的标识符是为系统函数保留的)区分大小写。
-
【转义标识符】以反斜线(/)开始,以空白符( )结束。
-
数据类型
-
【值的种类】
值的级别 硬件电路中的条件 0 逻辑0,条件为假 1 逻辑1,条件为真 x 逻辑值不确定 z 高阻,浮动状态
除此之外,不同强度的驱动源之间的赋值冲突还使用强度值来解决。当同一个线网有两个不同强度信号驱动,则竞争结果值为高强度的值;如果两个信号强度相同,则竞争结果值为不确定值。
强度等级 | 类 型 | 程 度 |
---|---|---|
supply | 驱动 | 最强 |
strong | 驱动 | ↑ |
pull | 驱动 | 丨 |
large | 存储 | 丨 |
weak | 驱动 | 丨 |
medium | 存储 | 丨 |
small | 存储 | 丨 |
highz | 高阻 | 最弱 |
其中在各类线网中,只有trireg类型的线网可以具有存储强度。
- 【线网】硬件单元之间的连接即线网(net:不是一个关键字,代表一组数据类型,包括:wire,wand,wor,tri,triand,trior,trireg),由其连接器件的输出端连接驱动。一般使用关键词wire进行声明;值由其驱动源确定,若无驱动源则默认值为z(trireg类型的线网默认值为x);若无显式说明为向量,则默认线网的位宽为1。
- 【寄存器】一个保存数值的变量(register),在被改写之前保持原有数据。不同于线网,寄存器不需要驱动源,而且也不需要时钟信号,仿真过程的任意时刻都可以通过赋值手段改变。一般使用关键字reg声明,默认值为x;也可以为带符号(signed)类型的变量,这样就可以用于带符号的算术运算。
- 【向量】与标量相对,向量是位宽大于1且有方向的。线网和寄存器类型的数据均可以声明为向量。如果在声明中没有指定位宽,则默认为标量(1位)。通过[high# : low#]或[low# : high#]进行说明。方括号左边的数总是代表着最高有效位。
wire [31:0] busA,busB,busc;//3条32位宽的总线
reg [0:40] virtual_addr;//向量寄存器,41位宽的虚拟地址,最高有效位是第0位
5.13
- 【整数、实数和时间寄存器数据类型】
整数 是一种对数量进行操作的寄存器数据类型,关键字integer。(声明为reg类型的寄存器变量为无符号数,而integer类型的变量则为有符号数。)常用来声明一个整数类型的变量来完成计数功能,默认位宽为宿主机的字的位数,但最小为32位。
实数是一种实常量和实数寄存器数据类型,关键字real。可以用十进制或科学计数法来表示,默认值为0;若将实数赋给整数则实数取整(四舍五入)。
real delta;//定义一个名为delta的实型变量
initial
delta = 2.13;
integer i;//定义一个名为i的整型变量
initial
i = delta;//i得到值2
时间寄存器是一种在仿真时保存仿真时间的特殊时间寄存器数据类型,关键字time。其宽度与具体实现有关,最小64位。通过调用系统函数$time可以得到当前的仿真时间。(仿真时间以秒为单位,与真实时间的对应关系需要用户来定义)
time save_sim_time;//定义时间类型的变量save_sim_time
initial
save_sim_time = $timr;//把当前的仿真时间记录下来
-【数组】
数组中每个元素都可以作为一个标量或向量,使用方式<数组名>[<下标>],数组的维数没有限制,可以声明任意维数的数组,但是要注意多维数组需要说明每一维的索引。
数组≠线网≠寄存器向量 向量是一个单独的元件位宽为n;数组由多个元件组成,其中每个元件的位宽为n或1
time chk_point[1:100];//由100个时间检查变量组成的数组
reg[63:0] array_4d [15:0][7:0][7:0][255:0];//四维64位寄存器型数组.
count[5]=0;//把count数组中第5个整数型单元(32位)复位
array_4d[0][0][0][0][15:0] = 0;//把四维数组中索引号为[0][0][0][0]的寄存器型单元的0~15位都置为0
-【存储器】
使用寄存器的一位数组来表示存储器,对寄存器文件、RAM、ROM建模。如果需要访问存储器中的一个特定的字,可以通过将字的地址作为数组的下标来完成:一个字/元素就是数组的一个元素,由数组的索引来指定,位宽为1位或多位。
reg [7:0] membyte[0:1023];//八位的1k字节的存储器membyte
membyte[511];//取出存储器membyte中地址511所存的字节
-【参数】
参数代表常数,不能被赋值,但是可以通过参数重载语句(defparam)使得用户对模块实例进行定制。其类型和范围是可以进行定义的。关键字parameter。
parameter port_id = 5;//定义常数port_id为5
parameter signed [15:0] WIDTH;//把参数width规定为有正负号,宽度为16位
局部参数使用关键字localparam定义,其值不可以通过defparam或有序参数列表或命名参数赋值来直接修改。
-【字符串】
保存在reg类型的变量中,每个字符占用一个字节(8 bit),故声明保存字符串的reg变量时,其位宽应比字符串的位长稍大。(若寄存器变量的宽度大于字符串的大小,则自动用0来填充左边空余位;若寄存器变量的宽度小于字符串大小,则自动截掉字符串最左边的位。)
reg[8*18:1] string_value;//声明变量string_value,~~宽度是18个字节(?)~~
initial
string_value = "Hello Verilog World";//17个字符存在18个字节的寄存器变量中
特殊字符表格
转义字符 | 显示的字符 |
---|---|
\n | 换行 |
\t | TAB |
%% | % |
\ | \ |
" | " |
\ooo | 1~3个八进制数字字符(?) |
5.14
- 系统任务和编译指令
- 【系统任务/系统函数】常用操作有屏幕显示、线网值动态监视、暂停和结束仿真等。所有的任务都具有$的形式。
- eg.显示信息$display(p1,p2,p3,…,pn);是用于显示变量、字符串或表达式的主要系统任务,是最常用的系统函数之一(可类比于C语言中的printf函数)。
$display("Hello Verilog World");//显示小括号中的字符串
--Hello Vrilog World
%display("ID of the port is %b",port_id);//用二进制数字符显示port_id 5
--ID of the port is 00101
字符串 | 格式说明 |
---|---|
%d或%D | 用十进制显示变量 |
%b或%B | 用二进制显示变量 |
%h或%H | 用十六进制显示变量 |
%o或%O | 用八进制显示变量 |
%s或%S | 显示字符串 |
%c或%C | 显示ASCII字符 |
%m或%M | 显示层次名(?) |
%v或%V | 显示强度 |
%t或%T | 显示当前时间格式 |
%e或%E | 用科学记数法格式显示实数 |
%f或%F | 用十进制浮点数格式显示实数 |
%g或%G | 用科学计数法或十进制格式显示实数,显示较短格式 |
- eg.监视信息$monitor(p1,p2,p3,…,pn);是用于变量、信号名或被双引号括起来的字符串,动态监视信号值变化的手段。
initial
begin
$monitor($time,"Value of signals clock = %b reset = %b",clock,reset);//监视时钟和复位信号的时间和值
end
//监视语句部分输出
-- 0 Value of signals clock = 0 reset = 1
-- 5 Value of signals clock = 1 reset = 1
-- 10 Value of signals clock = 0 reset = 0
- eg.暂停仿真 $stop / 终止仿真 $finish
#100 $stop;//在仿真时刻为100单位时,暂停仿真
#900 $finish;//在仿真时刻1000是,终止仿真
-【编译指令】使用方式`< keyword >,下面介绍两种:
` define用于定义文本宏(可理解为一种快捷方式),在模块中使用预定义的常数或文本宏时,在宏名前加上 define的之前相同的前缀号(此处不好打出,意会即可)。
`define S $stop;//定义别名,可用`S来代替$stop
`define WORD_REG reg [31:0]//定义常用字符串,可用`WORD_REG reg32来定义一个32位的寄存器变量
·include用于将内含全局或公用定义的头文件包含在设计文件中
`include "header.v"//包含header.v文件,在该文件中主文件需要的内容
5.21
模块与端口
- 模块
- 模块以关键字module开始,模块名、端口列表、端口声明和参数声明(必须出现在其他部分的前面),endmodule语句作为模块语句的结束关键字。
- 组成部分:变量声明、数据流语句、底层模块实例、行为语句块以及任务和函数。(出现顺序不限,并且不为必须出现)
- 端口
- 端口,又称终端,是外界环境与模块进行交互的接口,调用模块(也可称为实例引用)只能通过端口进行。
- 端口列表:在模块中端口是可选的,如果没有模块和外部环境的交互,则可以不设端口列表。eg.若模块是用作仿真的顶层模块,调用设计模块,则它无需和周围环境交换信息,因此没有端口列表。
- 端口声明:所有出现在端口列表中的端口需在模块中进行声明。input输入端口,output输出端口,inout输入/输出双向端口。(以上三种声明皆默认为wire类型[连线数据类型],如果输出类型的端口[output]需要保存数值,则必须声明其为reg类型[寄存器数据类型]。不能将带有输入类型的端口声明为寄存器数据类型是因为reg类型的变量是用于保存数值的,而输入端口只反映与其相连的外部信号的变化。)
//ANSI C风格的端口声明
module fulladd4( output reg [3:0] sum,
output reg c_out,
input [3:0] a,b//默认为wire数据类型
input c_in//默认为wire数据类型);
<模块的内容>
endmodule
模块内部 | 模块外部 | |
---|---|---|
输入端口input | wire数据类型 | reg/wire数据类型 |
输出端口output | reg/wire数据类型 | wire数据类型 |
输入/输出端口inout | wire数据类型 | wire数据类型 |
- 注意未连接端口[允许设计者不在端口列表中给出]和非法连接端口[不符合端口连接规则]。
- 端口与外部信号的连接:按顺序连接,或名称连接。
按顺序连接:外部信号的端口名称顺序,与调用模块时定义端口列表的端口名称顺序一致。
按名称连接:端口连接可以以任意顺序出现,只需保证端口和外部信号的正确匹配即可。只要端口名称不变,即使端口列表顺序发生了改变,模块实例的端口连接也无进行调整。
module Top;
//下面声明连接变量
reg [3:0]A,B;
reg C_IN;
wire [3:0] SUM;
wire C_OUT;
//方法一:调用模块,按端口列表中的次序连接
fulladd4 fa_orderd(SUM,C_OUT,A,B,C_IN);
//方法二:调用模块,通过端口名誉外部信号连接
fulladd4 fa_byname(.c_out(C_OUT), .sum(SUM), .b(B), .c_in(C_IN), .a(A), );//在这里不需要连接的端口只需要忽略即可
...
endmodule
module fulladd4(sum, c_out, a, b, c_in);
output[3:0] sum;
output c_cout;
input[3:0] a,b;
input c_in;
...
endmodule
- 层次命名
-根层:设计中的顶层模块被称为“根模块”,它不能被其他模块所调用,是整个设计层次的起点。
-层次化:设计中的每个标识符都具有唯一的层次名。
-层次名:下面的层次由根模块分支出来,因此命名时,将用“.”分隔开,意为向下一个层次,依次叠加。如果需要显示层次,用户可以在系统任务$display中使用特殊字符%m。
5.23
数据流建模
在数字设计领域,RTL通常是指数据流建模和行为级建模的结合。
- 连续赋值语句
- 连续赋值语句是数据流建模的基本语句,它用于对线网进行赋值。以关键词assign开始,可以进行隐式声明即省略assign仅使用wire即可完成两条语句同样的功能。(注:即使没有对一个信号名进行声明,但是只要它被用在assign语句的左侧,那么该端口被默认为一个wire类型。)
- 特点:1.连续赋值语句总是处于激活状态,只要任意一个操作数发生变化,表达式就立刻重新计算并赋值。2.操作数可以是标量或是向量的线网或寄存器,也可以是函数调用。3.赋值延迟类似于门的延迟,用于控制对线网赋予新值的时间。其他特点见下列举例:
//连续赋值特点举例
assign out = i1 & i2;//连续赋值语句,out是线网,i1和i2也是线网
---------------------------
assign addr[15:0] = addr1_bits[15:0]^addr2_bits[15:0];//向量线网的连续赋值:左边是16位的向量线网,右边是16位的向量寄存器
------------------------------------------------------
assign {c_out, sum[3:0]} = a[3:0] + b{3:0} + c_in;//拼接操作:左边是标量线网和向量线网的拼接
//对比普通的连续赋值和隐式连续赋值
wire out;
assign out = in1 & in2;//普通的连续赋值
-----------------------
wire out = in1 & in2;//隐式连续赋值
- 延迟
- 用于控制任意操作数发生变化到语句左值被赋予新值之间的时间间隔。
- 指定赋值延迟的方法:普通赋值延迟、隐式赋值延迟、线网声明延迟。
普通赋值延迟:在连续赋值语句中说明延迟值,位于关键字assign后面。具有延迟惯性。(惯性延迟:在右边操作数发生变化后,左值准备发生变化的延迟时间内,右边的值又发生了变化,那么在计算时赋给左值的是右边的当前值。同样适用于门延迟)
隐式连续赋值延迟:等效于声明一个线网并且对其进行连续赋值。
线网声明延迟:声明线网的时候可以指定一个延迟,这样对改线网的任何赋值都会被推迟指定的时间。
assign #10 out = in1 & in2;//连续赋值语句中的延迟
-------------------------
wire #10 out = in1 & in2;//隐式连续赋值延迟=先把out定义为wire类型后再用连续赋值语句为out赋值
-------------------------
wire #10 out;
assign out = in1 & in2;//线网声明延迟