Verilog 学习笔记(3)——Verilog 语言基础(1)

Verilog 学习笔记(3)——Verilog 语言基础(1)

Contents

Verilog 语言基础(1)

Verilog 的三种描述方法

Verilog 语言主要有三种描述硬件的方法:

  • 数据流描述:采用 assign 语句,主要描述信号之间的赋值关系。这种语句又被称为连续赋值语句。
  • 行为描述:使用 always 或 initial 语句块,更加侧重于描述电路的行为。其中出现的语句被称为过程赋值语句。
  • 结构化描述:实例化已有的功能模块。

我们先介绍一些基本的词法和数据流描述。

基本词法

Verilog 区分大小写,但是还是建议不要通过大小写来区分定义的变量。在 Verilog 语言中,所有的关键字(保留字)都为小写。内部信号名(标识符)大小写都可以,可以是字母、数字、$、_、以及它们的任意组合,但是第一个字符要是字母或下划线。

注释方式也是//与/* */。

注意,在有的地方(比如一些逻辑综合工具中),为了控制工具的编译过程,一些指令也是以注释的方式出现的(不过目前不会碰到)。

模块与端口

大型设计往往是由一个个模块构成的,Verilog 中,模块(module)是基本的组成单位。通常建议一个 Verilog 文件只放一个 module 定义,并且使文件名称与 module 名称一致。下面我以上一章的四位加法器为例介绍 Verilog 的 module 中的各个组成部分。

module adder_4(             //adder_4为模块名称
    input [3:0] A,
    input [3:0] B,
    output [4:0] sum );     //端口的声明,[n:0]表示位宽,后面会讲
                            //注意最后一个变量后没有分号,而括号后面有分号

                            //如果有中间变量可以在这里声明,或者端口也可以在这里声明
assign sum = A+B;           //语句

endmodule                   //模块结束

同时端口也可以在声明部分进行声明,比如上面的部分可以写成

module adder_4( A,B,sum );

    input [3:0] A;
    input [3:0] B;
    output [4:0] sum;

另外,也可以把 input(output)一起声明

module adder_4(             
    input [3:0] A, [3:0] B,
    output [4:0] sum ); 

但是切记!input 和 output 在这里之间分隔是用逗号!不要因为换了行就用分号。

编译指令

Verilog 语言中提供了一些编译指令,指令前与 C 语言中的#不同,使用反引号`来标识。

比如上一章 testbench 中的第一句

`timescale 1ns/1ns

指延时规定,前者1ns 指延时单位为1ns ,后者表示时间精度为1ns。当该指令被编译后,如语句

assign #1.6 sum=A+B;

由于延时单位为1ns,故#1.6即指1.6ns 后执行该语句,同时由于时间精度为1ns,所以该语句将被四舍五入到2ns 后执行。其他的编译指令之后如果讲到了再介绍。

逻辑值与常量

逻辑值

众所周知,一般情况下单比特逻辑值是二值的(0和1)。而 Verilog 中增加了 X 和 Z :

  • X:表示未知,在条件判断语句中表示不关心
  • Z:表示高阻,即没有任何驱动

综合工具中并没有 X,只存在0,1,Z。

Z与X不区分大小写,即 0z1x​ 和 0Z1X​ 表示同一个逻辑值。

常量

暂时仅介绍整数常量,用[长度]'数值符号 数值 的形式表示。

比如 4'b1001 就表示一个位宽4比特的二进制整数1001。

若不表明位宽和数值符号则默认十进制数,比如:

  • 16表示十进制数16;
  • -15表示十进制-15,若二进制补码表示则为10001。

若注明长度比后面数值实际位宽多,则左边补0,若少,则左边多出部分截断。

线网

Verilog 中主要有两类变量类型:

  • 线网:表示电路间的物理连线。
  • 寄存器:抽象的存储数据单元。

线网变量用 assign 语句赋值,寄存器变量用 always 或 initial 语句赋值。暂时先只介绍线网中的 wire 类型。

wire

wire 表示电路中的物理连线,并不像其他的线网类型具有逻辑运算或保持功能。

input 和 output 一般默认为 wire 型。

但是注意,wire 并不是那个线,而是信号。举个例子。

wire A;
wire B;
assign B=A;

这里的 wire 是指信号 A 和信号 B,而 assign 才表示它们之间的驱动关系。

另外与 C 语言中不同的是,C 中的赋值指把此刻 A 的值赋值给 B,而 assign 却是一种连续驱动,即 B 的值时刻由 A 的值决定。

并且该语句具有方向性,而不是简单的线连接,即 A 能驱动 B 但 B 不能驱动 A。

向量(或许是这么翻译?)

即 vector ,一组信号的集合,通过赋予集合名称以便于访问其中的信号。

举个例子:

wire [7:0] w;

这个语句声明了一个类型为 wire,名为 w,位宽为8bit 的 vector。注意与数组的声明的区别,位宽要写在向量名之前。

取出某个或某片 wire 信号的方式与数组类似:

wire [2:0] out;
assign out=w[6:4];

vector 的声明

一般情况下向量的声明中,高位在前,低位在后,并最低位为零。但是理论上也可以有其他的声明方式,举些例子:

wire [7:0] w;       //8比特 wire
reg [4:1] x;        //4比特 reg,reg是寄存器类型之后会讲
output reg [0:0] y; //位宽为 1bit 的 reg 输出
input wire [3:-2] z;//6比特 wire 输入
wire [0:7] b;       //8比特 wire,但是 b[0] 为 MSB(most-significant bit)

虽然你可以随意声明,但中括号内前者代表 MSB,后者为 LSB(相关知识在数电中应该有)。另外,声明与使用中一定要保持一致。所以我还是建议统一用大值在前,0在后的声明方式。

可以同时声明多个位宽相同的变量:

wire [7:0] a1,a2;

该语句为声明两个位宽为 8bit 的 wire 型变量。

综合器会将未定义声明的信号声明为 1bit wire 型信号,可能会导致 bug,赋值过程中高位可能被截断。你可以在 warning 中发现隐式声明。如果不想看 warning ,可以使用编译指令

`default_nettype none

关闭隐式声明功能。

vector 的片选

举一些片选的例子(结合声明中的例子部分来看):

w[3:0]              //片选 w 中的低4位
x[1:1]              //片选 x 中的最低位
b[3:0]              //由于声明中是[0:3],故该片选不能实现
assign w[3:0]=b[0:3]//将上述b[0]赋值给w[3],……,b[3]赋值给w[0]

vector 的连接

可以使用{}将数个小的 vector 连接为大的 vector,如:

wire [7:0] x;
assign x={4'b1001,4'b0010};     //x 被赋值为 8'b10010010

注意括号内的 vector 必须已标明位宽。

同时连接的 vector 也可以作左值,比如:

input [15:0] in;
output [15:0] out;
assign {out[7:0],out[15:8]}=in;     //交换了输入前后八位的顺序后输出

一些 verilog 中的操作符

全列出来太多了而且其实我也并不全知道,就列几个常用的,如果有需要可以自己去查:

  • 算术运算:+ - * / % (我觉得应该都知道意思)
  • 位操作符:
    • ~:按位取反
    • &:按位与
    • |:按位或
    • ^:按位异或
  • 规约操作符:大致同位操作符,不过位操作符是在两个变量之间操作,而规约操作是在一个变量中对所有比特操作,如 &m 即将 m 中的所有比特相与(1位结果)。

  • 逻辑操作:! && || (在1位情况下与位操作相同,但多位下不是各位操作而是整个值的逻辑操作,且只有1位结果)
  • 关系操作:< > <= >=
  • 移位操作:<< >>
  • 条件操作:? :,如 sel ? m:n

优先级啥的基本和 C 语言差不多,拿不准就加括号,另外一个好的设计在一个语句中不需要太多复杂的操作。

牛刀小试

说了这么多,试着自己编写一个 module 吧。

试构建一个模块,其中输入为两个 3bit 位宽的变量 a,b,要求分别输出 a,b的按位或,逻辑或以及 a,b 合并按位取反的结果(其中 b 在高位)。

32

这里推荐一个网站,你可以理解为 HDL 的 oj,你可以在网站上输入自己的 Verilog 代码,而网站在线综合并仿真,以验证功能,如果输出与要求不符,还会给出正确的波形与你的波形以便于 debug。缺点是网站是外网有时候可能有点卡。

HDLBits:Problem(14)——Vectorgates

当然你也可以根据我上一章的方法试着自己编写一个 Testbench,使用 ModelSim 进行功能仿真。

下面给出该题的解答,仅供参考:

module top_module(
    input [2:0] a, b,
    output [2:0] out_or_bitwise,
    output out_or_logical,
    output [5:0] out_not );

    assign out_or_bitwise = a|b;
    assign out_or_logical = a||b;
    assign out_not        = {~b,~a};

endmodule

 

点赞 1

No Comments

Add your comment