将陆续上传本人写的新书《自己动手写处理器》(尚未出版),今天是第六篇。我尽量每周四篇
2.3 Verilog HDL简单介绍
本书实现的OpenMIPS处理器是使用Verilog HDL编写的,所以本章接下来的几节将介绍Verilog HDL的一些基本知识。包含语法、结构等。由于本书并非一本讲授Verilog HDL的专门书籍,所以此处介绍的内容并非Verilog HDL的所有,仅仅是一些基础知识。以及在OpenMIPS处理器实现过程中会使用到的知识。
读者假设对Verilog HDL有进一步了解的需求。能够參考相关书籍,这方面有很多很优秀的书籍。
笔者推荐《数字系统设计与Verilog HDL(第4版)》,本章关于Verilog HDL的介绍也部分參考了该书。
Verilog HDL由GDA(Gateway Design Automation)公司的Phil Moorby于1983年首创,之后,Moorby又设计了Verilog-XL仿真器,Verilog-XL仿真器大获成功,也使得Verilog HDL得到推广使用。
1989年,Cadence收购了GDA,1990年。Cadence公开公布了Verilog HDL。并成立了OVI(Open Verilog International)组织。专门负责Verilog HDL的发展。因为Verilog HDL具有简洁、高效、易用、功能强等长处,逐渐为众多设计者所接受和喜爱。
其发展经历了几个关键节点。
- 1995年,Verilog HDL成为IEEE标准,称为IEEE Standard 1364-1995(Verilog-1995)。
- 2001年。IEEE Standard 1364-2001(Verilog-2001)获得通过。其对Verilog-1995做了扩充和增强,另外。改动了一些语法结构,使之更易于使用。
- 2002年,为了使综合器输出的结果和基于IEEE Standard 1364-2001的仿真和分析工具的结果相一致。推出了IEEE 1364[1].1-2002标准,其对Verilog HDL的RTL级综合定义了一系列的建模准则。
- 2005年。Verilog HDL再次进行了更新,即IEEE Standard 1364-2005(Verilog-2005)。该版本号仅仅是对上一版本号的细微修正。这个版本号还包含了一个相对独立的新部分,即Verilog-AMS (Analog and Mixed-Signal),这个扩展使得传统的Verilog HDL能够对集成模拟和混合信号的系统进行建模。
Verilog HDL具有下述特点。
(1)Verilog HDL是在C语言的基础上发展而来的,就语法结构而言,Verilog HDL继承了C语言的非常多语法结构,两者有很多相似之处。
(2)既适于可综合的电路设计,也可胜任电路与系统的仿真。
(3)能在多个层次上对所设计的系统加以描写叙述,从开关级、门级、寄存器传输级(RTL)到行为级,都能够胜任。同一时候Verilog HDL不正确设计规模施加不论什么限制。
(4)灵活多样的电路描写叙述风格,可进行行为描写叙述。也可进行结构描写叙述;支持混合建模,一个设计中的各个模块能够在不同的设计层次上建模和描写叙述。
(5)内置多种基本逻辑门,如and、or和nand等,可方便的进行门级结构描写叙述;内置多种开关级元件。如pmos、nmos和cmos等,可进行开关级的建模。
(6)用户定义原语(UDP)创建的灵活性。用户定义的原语既能够是组合逻辑。也能够是时序逻辑;通过编程语言接口(PLI)机制可进一步扩展Verilog HDL语言的描写叙述能力。
2.4 Verilog HDL中模块的结构
Verilog程序的基本设计单元是“模块”(Module),一个模块有其特定的结构,如图2-6所看到的。
Verilog的模块全然定义在module与endmodulekeyword之间,每一个模块包含四个主要部分:模块声明、port定义、数据类型说明和逻辑功能描写叙述。
例如以下是一个实现32位加法器的模块。有两个输入信号in1、in2。两者相加的结果通过out输出。
module add32(in1, in2, out); // 模块声明
input in1, in2; // port定义,此处是输入port
output out; // port定义,此处是输出port
wire[31:0] in1, in2, out; // 数据类型说明,此处都是wire型
assign out = in1 + in2; // 逻辑功能描写叙述
endmodule
以下结合该加法器的样例,对Module的基本结构进行说明。
1、模块声明
模块声明包含模块名字。以及输入、输出port列表。其格式例如以下。
module 模块名(port1, port2, port3……);
2、port定义
明白说明模块port的方向(输入、输出、双向等),其格式例如以下。
input port1, port2, port3 ……; // 输入port
output port1, port2, port3 ……; // 输出port
inout port1, port2, port3 ……; // 双向port
3、数据类型说明
对模块中全部用到的信号(包含port信号、节点信号等)都必须进行数据类型的定义。
Verilog HDL提供了各种信号类型。以下是几种定义信号类型的样例。各数据类型的详细含义将在2.5.2节详述。
reg a; // 定义信号a的数据类型为reg型
wire[31:0] out ; // 定义信号out的数据类型为32位wire型
对于port,能够将数据类型说明与port定义放在一条语句中完毕,于是。上文的32位加法器能够改为例如以下形式。
module add32(in1, in2, out);
input wire[31:0] in1, in2; // 将port定义与类型说明放在一条语句
output wire[31:0] out;
assign out = in1 + in2;
endmodule
对于port,还能够将port定义、数据类型说明都放在模块声明中,而不再放在模块内部。于是,上文的32位加法器还能够改为例如以下形式,更为简便。
// 将port定义、数据类型说明放在模块声明中
module add32(input wire[31:0] in1,
input wire[31:0] in2,
output wire[31:0] out);
assign out = in1 + in2;
endmodule
4、逻辑功能描写叙述
模块中最核心的部分就是逻辑功能描写叙述。能够有多种方法在模块中描写叙述和定义逻辑功能。
几种基本方法例如以下。将在2.6节详述。
- 用assign持续赋值语句定义
- 用always过程块定义
- 调用元件(也称为元件例化)
2.5 Verilog HDL基本要素
2.5.1 常量
Verilog中的常量(Constant)有三种:整数、实数、字符串。在OpenMIPS的实现过程中仅仅使用到了整数常量。所以,此处也仅介绍整数常量。
其格式如图2-7所看到的。
一些整数常数的样例例如以下。
8'b11000101 // 宽度为8位的二进制数。数值为11000101。等于十进制的197
8'h8a // 宽度为8位的十六进制数,数值为8a。等于十进制的138
5'o27 // 宽度为5位的八进制数,数值为27。等于十进制的23
4'd10 // 宽度为4位的十进制数,数值为10
假设没有明白指明进制,那么默认是十进制。
2.5.2 变量声明与数据类型
Verilog中变量声明的格式如图2-8所看到的。仅仅有数据类型、变量名是必要的。其它部分都能够省略。
假设省略符号和位宽,那么依据数据类型设置为默认值。假设省略元素数,那么默认声明元素数为1。
数据类型能够是net型、variable型。分别介绍例如以下。
1、net型变量
net型相当于硬件电路中各种物理连接,其特点是输出的值紧跟输入值的变化而变化。net型变量包含多种类型。如表2-1所看到的。
本书在实现OpenMIPS处理器的时候仅仅使用到了当中的wire类型。wire是最经常使用的net型变量,Verilog HDL模块中的输入、输出信号在没有明白指定数据类型时,都被默觉得wire型。
wire型信号能够用作不论什么表达式的输入,也能够用作assign语句和实例元件的输出,如前文中的32位加法器对out信号的赋值。对于综合器而言。wire型变量的取值可为0、1、X、Z,当中0表示低电平、逻辑0。1表示高电平、逻辑1;X表示不确定或未知的逻辑状态。Z表示高阻态。假设没有赋值。默觉得高阻态Z。
2、variable型变量
variable型变量是能够保存上次写入数据的数据类型,一般相应硬件上的一个触发器或锁存器等存储元件,但并非绝对的,在综合器综合的时候。会依据其被赋值的情况来详细确定是映射成连线还是映射为存储元件。variable型变量也包含多种类型,如表2-2所看到的。本书在实现OpenMIPS处理器的时候仅仅使用到了当中的reg类型。
variable型变量必须在过程语句(initial或always)中实现赋值,这样的赋值方式称为过程赋值。将在2.6节详述。
2.5.3 向量
图2-8变量声明格式中的位宽假设为1,那么相应的变量为标量,假设不为1。那么相应的变量为向量。默觉得标量。向量的位宽用以下的形式定义。
[MSB : LSB]
冒号左边的数字表示向量的最高有效位MSB(Most Significant Bit),冒号右边的数字表示向量的最低有效位LSB(Least Significant Bit)。比如。
wire [3:0] bus; // 4位的wire型向量bus,当中bus[3]是最高位,bus[0]是最低位
reg [31:5] ra; // 27位的reg型向量ra。当中ra[31]是最高位。ra[5]是最低位
reg [0:7] rc; // 8位的reg型向量rc,当中rc[0]是最高位,rc[7]是最低位
向量有两种。一种是向量类向量。一种是标量类向量,能够使用keyword区分,例如以下。
wire vectored [7:0] databus; // 使用keywordvectored,表示是向量类向量
reg scalared [31:0] rega; // 使用keywordscalared,表示是标量类向量
假设没有明白指出,那么默认是标量类向量。本书也仅仅用到了标量类向量。对标量类向量能够随意选中当中的一位或相邻几位,分别称为位选择(bit-select)和域选择(part-select)。比如。
A = rega[6]; // 位选择。将向量rega的当中一位赋值给变量A
B = rega[5:2]; // 域选择,将向量rega的第5、4、3、2位赋值给变量B
在OpenMIPS的实现过程中,使用到了存储器。存储器可看做是二维的向量。
例如以下就是一个存储器的定义,定义了一个深度为64。每一个元素宽度为32bit的存储器。
reg [31:0] mem[63:0]; // mem是深度为64,字长为32bit的存储器
2.5.4 运算符
Verilog HDL中定义的运算符包含:算术运算符、逻辑运算符、位运算符、关系运算符、等式运算符、缩位运算符、移位运算符、条件运算符和位拼接运算符。
详情如表2-3所看到的。
表2-3中的大部分运算都非常好理解,本书不再详释,仅仅做例如以下几点说明。
(1)等式运算符中的“==”与“===”的差别是:对于“==”运算。參与比較的两个操作数必须逐位相等。其结果才为1,假设某些值是不定态X或高阻态Z。那么得到的结果是不定值X。而对于“===”运算,则要求对參与运算的操作数中为不定态X或高阻态Z的位也进行比較,两个操作数必须全然一致,其结果才为1,否则结果为0。比如。
reg [4:0] a = 5'b11x01;
reg [4:0] b = 5'b11x01;
针对上面的a、b,“a==b”的返回结果为X,而“a===b”的返回结果为1。
(2)缩位运算符与位运算的运算符号、逻辑运算法则都是一样的,可是缩位运算符是对单个操作数进行与、或、异或的递推运算,它放在操作数的前面,可以将一个矢量减为一个标量。
比如。
reg [3:0] a;
b = &a; // 等效于b = ((a[0] & a[1]) & a[2]) & a[3]
而位运算须要对两个操作数按相应位分别进行逻辑运算。比如。
wire [3:0] a = 4'b0011;
wire [3:0] b = 4'b0101;
那么a&b = 4'b0001。a|b = 4'b0111
(3)位拼接运算符:用来将两个或多个信号的某些位拼接起来。其格式例如以下。
{比特序列0, 比特序列1,…… }
比如,在进行加法运算时。可将和与进位输出拼接在一起使用。
input [3:0] ina,inb; // 加法输入
output [3:0] sum; // 加法的和
output cout; // 进位
assign {cout, sum} = ina + inb; // 将和与进位拼接在一起
位拼接还能够用来反复信号的某些位,其格式例如以下。
{反复次数{被反复数据}}
利用上面的功能,能够实现对信号的符号扩展,比如。
//将Data的符号位进行扩展,s_data = {Data[7],Data[7],Data[7],Data[7],Data}
wire [7:0] Data;
wire [11:0] s_data;
s_data = {{4{Data[7]}},Data};
(4)运算符的优先级如图2-9所看到的。