本身动手写处理器之第二阶段(2)——Verilog HDL简单介绍

将陆续上传本人写的新书《本身动手写处理器》(还没有出版),今天是第六篇。我尽可能每周四篇算法

 

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获得推广使用。spa

1989年,Cadence收购了GDA,1990年。Cadence公开公布了Verilog HDL。并成立了OVI(Open Verilog International)组织。专门负责Verilog HDL的发展。由于Verilog HDL具备简洁、高效、易用、功能强等长处,逐渐为众多设计者所接受和喜好。scala

其发展经历了几个关键节点。设计

  •   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具备下述特色。code

      (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位加法器的模块。有两个输入信号in一、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的基本结构进行说明。

      一、模块声明

      模块声明包含模块名字。以及输入、输出port列表。其格式例如如下。

module 模块名(port1, port2, port3……);

      二、port定义

      明白说明模块port的方向(输入、输出、双向等),其格式例如如下。

input   port1, port2, port3 ……;          // 输入port
output  port1, port2, port3 ……;          // 输出port
inout   port1, port2, port3 ……;          // 双向port

      三、数据类型说明

      对模块中所有用到的信号(包含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

      四、逻辑功能描写叙述

      模块中最核心的部分就是逻辑功能描写叙述。可以有多种方法在模块中描写叙述和定义逻辑功能。

几种基本方法例如如下。将在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型。分别介绍例如如下。

      一、net型变量

      net型至关于硬件电路中各类物理链接,其特色是输出的值紧跟输入值的变化而变化。net型变量包含多种类型。如表2-1所看到的。

      本书在实现OpenMIPS处理器的时候仅仅使用到了当中的wire类型。wire是最常用的net型变量,Verilog HDL模块中的输入、输出信号在没有明白指定数据类型时,都被默以为wire型。

wire型信号可以用做不论什么表达式的输入,也可以用做assign语句和实例元件的输出,如前文中的32位加法器对out信号的赋值。对于综合器而言。wire型变量的取值可为0、一、X、Z,当中0表示低电平、逻辑0。1表示高电平、逻辑1;X表示不肯定或未知的逻辑状态。Z表示高阻态。假设没有赋值。默以为高阻态Z。

      二、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的第五、四、三、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所看到的。

相关文章
相关标签/搜索