结构化数字设计
数字化结构设计
层次建模的概念
设计方法学
两种基本的设计方法:自底向上
和自顶向下
设计
1.自底向上
2.自顶向下
通常情况下,两种方法混合使用。
模块
Verilog使用模块(module)的概念来代表一个基本的功能块。
一个模块可以是一个元件,也可以是低层次模块的组合
1.模块声明
在Verilog中,模块声明是由关键字module开始,关键字endmodule必须出现在模块定义的结尾。每个模块必须有一个模块名,由它唯一的标志这个模块。模块的端口列表则描述这个模块的输入和输出端口
1 | module <模块名>(<模块端口列表>); |
例如在脉动进位计数器的例子中,T触发器可以定义为:
1 | module T_FF (q, clock ,reset); |
使用Verilog可以在每个模块内4个抽象层次进行描述,定义如下:
- 行为或算法级:注重实现的算法,并不注重硬件实现的细节
- 数据流级:说明数据的流程对模块进行描述,数据如何在各个寄存器之间流动,以及如何处理这些数据。
- 门级:从组成电路的逻辑门及其之间的相互关系的角度设计模块。
- 开关级:通过开关,储存节点及其互连关系来设计模块。
2.模块实例
模块声明类似于一个模板,使用这个模板就可以创建实际的对象。当一个模块被调用的时候,Verilog会根据模板创建一个唯一的模块对象,每个对象都有其各自的名字、变量、参数和输入/输出(I/O)接口。
从模板创建对象的过程称为实例化
(instantiation),创建的对象称为实例
(instance)
在Verilog中,不允许在模块声明中嵌套模块,也就是在模块声明的module和endmodule关键字之间不能再包含模块声明。模块之间的相互调用是通过实例引用来完成的。
1 | //它引用了4个触发器。它们之间的连接参见2.2节 |
ps:关键字必须是小写字母
基本概念
Verilog中的基本词法约定与c语言类似。
数字声明
指明位数的数字
指明位数的数字表现形式为
1
<size>'<base format><number>
$
$用来指明数字的宽度(二级制的个数),只能用十进制表示。Base format表示用什么进制 1
2
34'b1111//这是一个4位的二进制数
12'habc//这是一个12位的十六进制数
16'd255//这是一个16位的十进制数
不指明位数的数字
没有指定基数默认为十进制数。如果没有指定宽度,则默认为32位
1
2
3234566//这是一个32位的十进制数
'hc3//这是一个32位的十六进制数
'o21//这是一个32位的八进制数X和Z值
x表示不确定值,z表示高阻值;不区分x,y的大小写。
1
212'h13x//这是一个12位的十六进制数,四个最低位不确定6'hx//这是一个6位的十六进制数,所有位都不确定
32'bz//这是一个32位的高阻值 16进制为基数的表示中x或z表示4位,在8进制,x,z表示3位,在2进制中x,z代表1位。如果某数的最高位是0,x或z,verilog语言约定将分别使用0,x,z自动对这个数进行扩展,填满余下的更高位。如果最高位是1,余下的更高位用0来扩展。
复数
对于常数,我们可以通过在表示位宽的数字前面增加一个减号来表示它是一个负数,因为表示大小的常数总是正的。
1
2
3-6'd3//这是一个6位的用二进制补码形式存储的十进制数3,表示负数
-6'sd3//这是一个6位的用于带符号算术运算的负数
4'd-2//非法说明下划线和问号
除了第一个字符,下划线可以出现在数字中的任何位置,它的作用只是提高可读性,在编译阶段会被忽略,?是z的另一种表示。
1
12'b1111_0000_1010//用下划线符号来提高可读性4'b10??//相当于4'b10zz
转义标识符
转义标识符以反斜线“\”开始,以空白符(空格、制表符和换行符)结束。Verilog将反斜线和空白符之间的字符逐个进行处理。
1
2\a+b-C //译者注:与a+b-c等同
\**my_name** //译者注:如果作为标识符则与**my_name**等同
数据类型
值的种类
Verilog使用四值逻辑和八种信号强度来对实际的硬件电路建模。
| 值的级别 | 硬件电路中的条件 |
| ———— | ———————— |
| 0 | 逻辑0,条件为假 |
| 1 | 逻辑0,条件为假 |
| x | 逻辑值不确定 |
| z | 高阻,浮动状态 || 强度等级 | 类型 | 程度 |
| ———— | —— | —— |
| supply | 驱动 | 最强 |
| strong | 驱动 | |
| pull | 驱动 | |
| large | 储存 | |
| weak | 驱动 | |
| medium | 储存 | |
| small | 储存 | |
| highz | 高阻 | 最弱 |线网
线网(net:代表了一类数据类型,包括wire,wand,wor,tri,trireg等)标识硬件单元之间的连接。
就像在真实的电路中一样,线网由其连接器件的输出连续驱动。
线网一般使用关键字wire进行声明。如果没有明显说明向量,则默认线网的位宽为1
线网的默认值为z(trireg类型的线网例外,其默认值为x),线网的值由驱动源确定,如果没有驱动源,则线网的值为z
1
2
3wire a;//声明上面的电路中a是wrie(连线)类型
wire b,c;//声明上面的电路中b和c也是wire(连线)类型
wire d = 1'b0;//连线d在声明时,d被赋值为逻辑值0
寄存器
寄存器用来表示储存元件,它保持原有的树脂,直到被改写。
寄存器数据类型一般通过关键字reg来声明,默认值为X
1
2
3
4
5
6reg reset;//声明能保持数值的变量reset
initia1 //这个结构将在以后讨论
begin
reset = 1'b1;//把reset初始化为1,使数字电路复位
#100 reset=1'b0;//经过100个时间单位后,reset置逻辑0
end
向量
线网和寄存器类型的数据均可以声明为向量(位宽大于1)。若在声明中没有指定位宽,则默认为标量(1位)
1
2
3
4
5wire a;//标量线网变量,默认
wire [7:0] bus;//8位的总线
wire [31:0] busA,busB,busC;//3条32位宽的总线
reg clock;//标量寄存器,默认
reg [0:40] virtual_addr;//向量寄存器,41位宽的虚拟地址向量域选择
对于上面例子中声明的向量,我们可以指定它的某一位或若干个相邻位。
1
2
3
4busA[7] //向量busA的第7位
bus[2:0]//向量bus的最低3位
//如果写成bus[0:2]是非法的,因为高位应该写在范围说明的左侧
virtual_addr[0:1]//向量virtual_addr的两个最高位可变的向量域选择
[
+:width]:从起始位开始递增,位宽为width。 [
-:width]:从起始位开始递减,位宽为width。
起始位可以是一个变量,但是位宽必须是一个常量。下面的例子说明了可变的向量域选择的使用方法:
1
2
3
4
5
6
7
8
9
10
11
12reg[255:0]data1;//data1【255】是最高有效位
reg[0:255]data2;//data2【0】是最高有效位
reg[7:0]byte;
//用变量选择向量的一部分
byte=data1[31-:8];//从第31位算起,宽度为8位,相当于data131:24】
byte=data1[24+:8];//从第24位算起,宽度为8位,相当于data1【31:24】
byte=data2[31-:8];//从第31位算起,宽度为8位,相当于data2【24:31】
byte=data2[24+:8];//从第24位算起,宽度为8位,相当于data2【24:311//超始位可以是变量,但宽度必须是常数。因此可以通过可变域选择,//用循环语句选取一个很长的向量的所有位
for(j=0;j<=32;j=j+1)
byte=data1[(j*8)+8];//次序是【7:0】,【15:81..【255:248】
//用于初始化向量的一个域
data1[(byteNum*8)+:8] = 8'b0;//如果byteNum=1,共有8位被清零,【15:8】数字,实数和时间寄存器的数据类型
除reg类型之外,Verilog还支持integer,real和time寄存器数据类型。
整数
用关键字integer进行声明,虽然可以用reg类型的寄存器作为通用变量,但声明一个整体类型的变量来完成计数会更为方便。
整数默认位宽为宿主机的字的位数,最小应为32位。
1
2integer counter;//一般用途的变量,作为计数器initial
coumter=-1;//把-1存储到计数器中实数
用real来声明,实数声明不能带有范围,默认值为0。
1
2
3
4
5
6
7
8
9real delta;//定义一个名为delta的实型变量
initial
begin
delta = 4e10;//delta被赋值,用科学记数法表示
delta = 2.13;//delta被赋值为2.13
end
integer i;//定义一个名为的整型变量
initial
i = delta;//1得到值2(2.13取整数部分)时间寄存器
仿真是按仿真时间进行的,其宽度与具体实现有关,最小为64位。用time来声明。通过调用系统函数$time可以得到当前的仿真时间。
1
2time save_simtime;//定义时间类型的变量savesim time initial
save sim time=$time;//把当前的仿真时间记录下来数组
在Verilog中允许声明reg,integer,time,real,realtime及其向量类型的数组,对数组的维数没有限制。
形如<数组名><下标>。对于多维数组来讲,用户需要说明其每一维的索引。举例如下:
1
2
3
4
5
6
7
8integer count[0:7];//由8个计数变量组成的数组
reg boo1[31:0];//由32个1位的布尔(boolean)寄存器变量组成的数组
time chk_point[1:100];//由100个时间检查变量组成的数组
reg [4:0] port_id[0:7];//由8个端口标识变量组成的数组,端口变量的位宽为5
integer matrix[4:0][0:255];//二维的整数型数组
reg[63:0] array_4d[15:0][7:0][7:0][255:0];//四维64位寄存器型数组
wire[7:0] w_array2[5:0];//声明8位向量的数组
wire w_array1[7:0][5:0];//声明1位线型变量的二维数组不要将数组和线网或寄存器向量混淆起来,向量是一个单独的元件,它的位宽是n;数组由多个元件组成,其中每个元件元素的赋值位n或1。
下面的例子显示了对数组元素的赋值:
1
2
3
4
5
6
7
8count[5]=0;//把count数组中的第5个整数型单元(32位)复位
chk_point[100]=0;//把chk_point数组中的第100个时间型单元(64位)复位
port_id[3]=0;//把port_id数组中的第3个寄存器型单元(5位)复位
matrix[1][0]=33559;//把数组中第1行第0列的整数型单元(32位)置为33559
array_4d[0][0][0][0][15:0]=0;//把四维数组中索引号为【o】【O】【0】【O】的寄存器型单元
//的0-15位都置为0
port_id=0;//非法,企图写整个数组
matrix[1] = 0;//非法,企图写数组的整个第2行,即从matrix【1】【0】直到matrix【1】【255】
寄存器
在数字电路仿真中,需要对寄存器文件ROM和RAM建模。如果需要访问储存器中的一个特定字,可以将字的地址作为数组的下标来完成。
1
2
3reg memibit[0:1023];//1K的1位存储器mem1bit
reg[7:0] membyte[0:1023];//1K的字节(8位)存储器membyte
membyte[511]//取出存储器membyte中地址511处所存的字节参数
Verilog允许使用关键字parameter在模块内定义常数。参数代表常数,不能像变量那样赋值,但是每个模块实例的参数值可以在编译阶段被重载。
1
2
3parameter port_id=5;//定义常数portid为5
parameter cache_1ine_width=256;//定义高速缓冲器总线宽度为常数256
parameter signed[15:0] WIDTH;//把参数WIDTH规定为有正负号,宽度为16位Verilog中的局部参数使用关键字localparam来定义,其作用等同于参数,区别在于它的值不能改变。
1
2
3
4localparam state1 = 4'b0001,
state2 = 4'b0010,
state3 = 4'b0100,
state4 = 4'b1000;字符串
字符串保存在reg类型的变量中,每个字符占用8位(一个字节),因此寄存器变量的宽度应足够大。
如果寄存器变量的宽度大于字符串的大小,则Verilog使用0来填充左边的空余位;如果寄存器变量的宽度小于字符串的大小,则Verilog截去字符串最左边的位。
1
2
3reg[8*18:1] string_value;//声明变量string_vaiue,其宽度为18个字节
initial
string_value = "Hello Verilog World";//字符串可以存储在变量中有一些特殊字符在显示字符串时有特定的意义,例如换行符,制表符和显示参数值。如果需要在字符串中显示这些特殊字符,则必须加前缀转义字符。
转义字符 | 显示的字符 |
---|---|
\n | 换行 |
\t | tab(制表空格) |
%% | % |
\\\ | \\ |
\\” | “ |
\000 | 1到3个八进制数字字符 |