Verilog入门指北

基本结构

Verilog程序的基本结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module MUX21a(a, b, s, y);
input a, b, s;
output y;

assign y = (s ? a: b);
endmodule


% 输入列表及端口合并
module MUX21a(
input a,
input b,
input s,
output y);

assign y = (s ? a: b);
endmodule

上面是一个2选1多路选择器的Verilog描述。

具体来说,一个Verilog程序即为一个模块,所以以modle开始,以endmodule结束。同时模块具有端口列表以及端口列表声明(这两部分可以合并到端口列表中)。

随后则是Verilog程序正式逻辑功能部分。其可以用下面的形式表示:

basic-structure

数据类型

总的来说,Verilog中的数据类型可以被分为两大类:网线类(net)、变量类(variable)

Verilog 最常用的 2 种数据类型就是线网(wire)与寄存器(reg),其余类型可以理解为这两种数据类型的扩展或辅助。

因连续赋值语句和过程赋值语句的激活特点不同,故赋值目标特点也不同,前者不需要保存,后者需要保存,因此规定两种数据类型,net型用于连续赋值的赋值目标或门原语的输出,且仿真时不需要分配内存空间,variable用于过程赋值的赋值目标,且仿真时需要分配内存空间。

将一个信号定义成net型还是varible型,由以下两方面决定

  1. 使用何种赋值语句对该信号进行赋值,
    1. 如果是连续赋值或门原语赋值或例化语句赋值,则定义成net型(wire);
    2. 如果是过程赋值则定义成variable型(reg)。
  2. 对于端口信号来说,
    1. input信号和inout信号必须定义成net型(wire)的;
    2. output信号可以是net型的也可以是variable型(reg)的,决定于如何对其赋值(同a)。

网线类数据

网线类中的数据类型包含如下类型:

wire 线类(使用最多,其他很少使用)。wire 类型表示硬件单元之间的物理连线,由其连接的器件输出端连续驱动。
wand 线与
wor 线或
tri 三态
triand 三态与
trior 三态或
tri0 下拉电阻
tri1 wire
trireg 电容性线网
supply0
supply1 电源

变量类型

Verilog语言中的逻辑值有四种:

1:逻辑1,高电平,数字1
0:逻辑0,低电平,数字0
x:不确定
z:高阻态

变量类中的数据类型包含如下类型:

reg 寄存器型(使用最多,其他很少使用)。寄存器(reg)用来表示存储单元,它会保持数据原有的值,直到被改写。
integer 整形
real 实数型
time 时间型
realtime 实时间型

例如在 always 块中,寄存器可能被综合成边沿触发器,在组合逻辑中可能被综合成 wire 型变量。寄存器不需要驱动源,也不一定需要时钟信号。在仿真时,寄存器的值可在任意时刻通过赋值操作进行改写。

变量位宽

当位宽大于 1 时,wire 或 reg 即可声明为向量的形式。例如:

1
2
3
4
reg [3:0]      counter ;    //声明4bit位宽的寄存器counter
wire [32-1:0] gpio_data; //声明32bit位宽的线型变量gpio_data
wire [8:2] addr ; //声明7bit位宽的线型变量addr,位宽范围为8:2
reg [0:31] data ; //声明32bit位宽的寄存器变量data, 最高有效位为0

Verilog 支持可变的向量域选择,例如:

1
2
3
4
5
6
7
8
9
reg [31:0]     data1 ;
reg [7:0] byte1 [3:0];
integer j ;
always@* begin
for (j=0; j<=3;j=j+1) begin
byte1[j] = data1[(j+1)*8-1 : j*8];
//把data1[7:0]…data1[31:24]依次赋值给byte1[0][7:0]…byte[3][7:0]
end
end

对信号重新进行组合成新的向量时,需要借助大括号。例如:

1
2
3
wire [31:0]    temp1, temp2 ;
assign temp1 = {byte1[0][7:0], data1[31:8]}; //数据拼接
assign temp2 = {32{1'b0}}; //赋值32位的数值0

整数,实数,时间寄存器变量

整形

整数类型用关键字 integer 来声明。声明时不用指明位宽,位宽和编译器有关,一般为32 bit。reg 型变量为无符号数,而 integer 型变量为有符号数。例如:

1
2
3
4
5
6
7
8
9
reg [31:0]      data1 ;
reg [3:0] byte1 [7:0]; //数组变量,后续介绍
integer j ; //整型变量,用来辅助生成数字电路
always@* begin
for (j=0; j<=3;j=j+1) begin
byte1[j] = data1[(j+1)*8-1 : j*8];
//把data1[7:0]…data1[31:24]依次赋值给byte1[0][7:0]…byte[3][7:0]
end
end

实数

实数用关键字 real 来声明,可用十进制或科学计数法来表示。实数声明不能带有范围,默认值为 0。如果将一个实数赋值给一个整数,则只有实数的整数部分会赋值给整数。例如:

1
2
3
4
5
6
7
8
9
10
real        data1 ;
integer temp ;
initial begin
data1 = 2e3 ;
data1 = 3.75 ;
end

initial begin
temp = data1 ; //temp 值的大小为3
end

时间

Verilog 使用特殊的时间寄存器 time 型变量,对仿真时间进行保存。其宽度一般为 64 bit,通过调用系统函数 $time 获取当前仿真时间。例如:

1
2
3
4
5
time       current_time ;
initial begin
#100 ;
current_time = $time ; //current_time 的大小为 100
end

数组

在 Verilog 中允许声明 reg, wire, integer, time, real 及其向量类型的数组。

数组维数没有限制。线网数组也可以用于连接实例模块的端口。数组中的每个元素都可以作为一个标量或者向量,以同样的方式来使用,形如:**<数组名>[<下标>]**。对于多维数组来讲,用户需要说明其每一维的索引。例如:

1
2
3
4
5
integer          flag [7:0] ; //8个整数组成的数组
reg [3:0] counter [3:0] ; //由4个4bit计数器组成的数组
wire [7:0] addr_bus [3:0] ; //由4个8bit wire型变量组成的数组
wire data_bit[7:0][5:0] ; //声明1bit wire型变量的二维数组
reg [31:0] data_4d[11:0][3:0][3:0][255:0] ; //声明4维的32bit数据变量数组

常量

参数用来表示常量,用关键字 parameter 声明,只能赋值一次。例如:

1
2
3
parameter      data_width = 10'd32 ;
parameter i=1, j=2, k=3 ;
parameter mem_size = data_width * 10 ;

字符串

字符串保存在 reg 类型的变量中,每个字符占用一个字节(8bit)。因此寄存器变量的宽度应该足够大,以保证不会溢出。

字符串不能多行书写,即字符串中不能包含回车符。如果寄存器变量的宽度大于字符串的大小,则使用 0 来填充左边的空余位;如果寄存器变量的宽度小于字符串大小,则会截去字符串左边多余的数据。例如,为存储字符串 “run.runoob.com”, 需要 14*8bit 的存储单元:

1
2
3
4
reg [0: 14*8-1]       str ;
initial begin
str = "run.runoob.com";
end

总结

总的来说:

  1. 针对大部分的程序、wire和reg变量类型就够了。其他类型一般用的并不是很多,除非是比较高层的硬件程序设计。
  2. 要区分wire和reg的根本区别,即wire表示硬件单元之间的物理连线;reg表示存储单元。
    1. 因此在实际电路设计中,input和inout类型必须用wire型;output可以定义为reg,也可以定义为wire。
    2. assign语句中变量需要定义成wire型,使用wire必须搭配assign。

操作符

Verilog中的操作符与其他语言基本类似:

operator1

operator2

  1. 由于Verilog也具有整形等数据类型,因此也分逻辑操作符(如逻辑与&&)和按位逻辑操作符(如逻辑或||)。

  2. 比较关系符中,其结果可能是逻辑真(1),逻辑假(0),不确定(x)

  3. 比较关系符中,除了经典的==!=,还有===(case等于)以及!==(case不等)。后两者中,返回结果只能是0或1,,对于x、z认为是确定的值,参加比较。

  4. Verilog中还有一些特殊的操作符,如拼接和复制拼接。

    1
    2
    3
    4
    Y= {4’b1001, 2’b11};  %100111
    Y= {4{2’b01}}; %01010101
    Y= {{4{2’b01}}, 2’b11};

赋值语句

赋值语句分为两种:连续赋值和过程赋值。

连续赋值

连续赋值语句是 Verilog 数据流建模的基本语句,用于对 wire 型变量进行赋值。例如:

1
assign   LHS_target = RHS_expression ;

LHS(left hand side) 指赋值操作的左侧,RHS(right hand side)指赋值操作的右侧。

assign 为关键词,任何已经声明 wire 变量的连续赋值语句都是以 assign 开头,例如:

1
2
wire      Cout, A, B ;
assign Cout = A & B ; //实现计算A与B的功能

需要说明的是:

  • LHS_target 必须是一个标量或者线型向量,而不能是寄存器类型。
  • RHS_expression 的类型没有要求,可以是标量或线型或存器向量,也可以是函数调用。
  • 只要 RHS_expression 表达式的操作数有事件发生(值的变化)时,RHS_expression 就会立刻重新计算,同时赋值给 LHS_target。

Verilog 还提供了另一种对 wire 型赋值的简单方法,即在 wire 型变量声明的时候同时对其赋值。wire 型变量只能被赋值一次,因此该种连续赋值方式也只能有一次。例如下面赋值方式和上面的赋值例子的赋值方式,效果都是一致的。

1
2
wire      A, B ;
wire Cout = A & B ;

连续性赋值总是处于激活状态,任何操作数的改变都会影响表达式的结果

过程赋值

过程性赋值是在 initial 或 always 语句块里的赋值,赋值对象是寄存器、整数、实数等类型。

连续性赋值总是处于激活状态,任何操作数的改变都会影响表达式的结果;过程赋值只有在语句执行的时候,才会起作用。这是连续性赋值与过程性赋值的区别。

Verilog 过程赋值包括 2 种语句:阻塞赋值与非阻塞赋值。

阻塞赋值

阻塞赋值属于顺序执行,即下一条语句执行前,当前语句一定会执行完毕。

阻塞赋值语句使用等号 = 作为赋值符。

前面的仿真中,initial 里面的赋值语句都是用的阻塞赋值。

非阻塞赋值

非阻塞赋值属于并行执行语句,即下一条语句的执行和当前语句的执行是同时进行的,它不会阻塞位于同一个语句块中后面语句的执行。

非阻塞赋值语句使用小于等于号 <= 作为赋值符。

基本语句块

连续语句块

在Verilog模块中,除always语句块之外,assign语句赋值可以被看作是连续语句块。连续语句的特点即为连续语句块的特点。

  1. 语法上,有关键词“assign”来标识;
  2. 连续赋值语句不能出现在过程块中(initial/always);
  3. 连续赋值语句主要用来对组合逻辑进行建模以及线网数据间进行描述;
  4. 左侧被赋值的数据类型必须是线网型数据(wire)
  5. always语句中还可以使用if、case、for循环等语句,其功能更加强大。

过程语句块

过程语句块一般指always语句块。

其基本格式如下:

1
2
always @(敏感信号条件表)
各类顺序语句;

例如:

1
2
always @ (posedge CLK)
Q=D;

特点:

  1. always语句本身不是单一的有意义的一条语句,而是和下面的语句一起构成一个语句块,称之为过程块;过程块中的赋值语句称过程赋值语句;
  2. 该语句块不是总处于激活状态,当满足激活条件时才能被执行,否则被挂起,挂起时即使操作数有变化,也不执行赋值,赋值目标值保持不变;
  3. 赋值目标必须是reg型的。

其中敏感信号条件表有两种类型:

  1. 边沿敏感
  2. 电平敏感

边沿敏感

边沿敏感是指当一个信号的上升或者下降沿到来时,语句块就被激活并开始执行。

其中,posedge用来表示上升沿;negedge用来表示下降沿。

例如:

1
2
3
4
always @(posedge clk)


always @(negedge clk)

always-pos

电平敏感

电平敏感是指当信号列表中的人一个信号有变化时激活该语句块。

例如:

1
2
3
always @(a, b, c)

always @(a or b or c)

always-neg

always语句块中如果有多条赋值语句必须将其用begin end包括起来,assign语句中没有begin end。

always-rule

同时,上面操作符中提到的赋值操作符中有阻塞赋值和非阻塞赋值。所以这里的begin和end中的多条赋值语句采用不同的赋值符号也会进行不同的执行。如:

1
2
3
4
begin
m=a*b;
y=m;
end

当m=a*b 执行完才能执行y=m 。当m赋值完成后,才能执行y的赋值,y得到的是m的新值。

1
2
3
4
begin
m<=a*b;
y<=m;
end

m=a*b 和y=m并行执行 。m和y的赋值并行执行,y得到的是m的旧值。

值得注意的是:

  1. 设计组合电路时常用阻塞赋值;
  2. 设计时序电路时常用非阻塞赋值;
  3. 但不是绝对的。
  4. 不建议在一个always块中混合使用阻塞赋值和非阻塞赋值

完整案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module CNT4 (CLK,Q);
input CLK;
output [3:0] Q;

reg [3:0] Q1;

always @ (posedge CLK)
begin
Q1<=Q1+1;
end

assign Q=Q1;

endmodule

基本语句

if语句

在用if语句设计“组合电路”时要注意,如果条件不完整,会综合出锁存器

1
2
3
4
5
6
7
8
9
% 使用else使分支完整
always @(a,b)
if (sel) Q=a;
else Q=b;

% 使用初值使分支完整
always @(a,b)
Q=a;
if (sel) Q=b;

case语句

如果条件描述不完整,则会综合出寄存器;在设计组合电路时要注意使条件描述完整。加default语句可以使条件完整。如果条件描述完整也可以不加default语句。

1
2
3
4
5
6
7
8
9
case (表达式)
取值1: 语句1;
取值2: 语句2;
取值3: 语句3;
...
...
default: 默认语句;
endcase

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module MUX41 (a,b,c,d,s1,s0,y);
input a,b,c,d,s1,s0;
output reg y;
//reg y;
always @(a,b,c,d,s1,s0)
begin
case ({s1,s0})
2’b00 : y=a;
2’b01 : y=b;
2’b10 : y=c;
2’b11 : y=d;
default : y=0;
endcase
end
endmodule

case-statement

描述风格

  1. 结构化描述(也称门级描述)(全部用门原语和底层模块调用)
  2. 数据流级描述(全部用assign语句)
  3. 行为级描述(全部用always语句配合if、case语句等)

实际描述是三种混合的,举例:用门级描述、数据流描述、行为描述分别设计数据选择器。

多路选择器

多路选择器的Verilog 门级描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module mux4_to_1 (out, i0, i1, i2, i3, s1, s0);
output out;
input i0, i1, i2, i3;
input s1, s0;

wire s1n, s0n;
wire y0, y1, y2, y3;

not (s1n, s1);
not (s0n, s0);

and (y0, i0, s1n, s0n);
and (y1, i1, s1n, s0);
and (y2, i2, s1, s0n);
and (y3, i3, s1, s0);

or (out, y0, y1, y2, y3);
endmodule

多路选择器的Verilog 数据流描述

1
2
3
4
5
6
7
8
9
module mux4_to_1  (out, i0, i1, i2, i3, s1, s0);

output out,
input i0, i1, i2, i3;
input s1, s0;

assign out = (~s1 & ~s0 & i0)|(~s1 & s0 & i1) |(s1 & ~s0 & i2) |(s1 & s0 & i3) ;
endmodule

多路选择器的Verilog 行为级描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module mux4_to_1(out, i0, i1, i2, i3, s1, s0);
output out;
input i0, i1, i2, i3;
input s1, s0;
reg out;
always @(s1 or s0 or i0 or i1 or i2 or i3)
begin
case ({s1, s0})
2'b00: out = i0;
2'b01: out = i1;
2'b10: out = i2;
2'b11: out = i3;
default: out = 1'bx;
endcase
end
endmodule

模块调用方法

模块1:

1
2
3
4
5
6
module DFF(CLK,D,Q)
output reg Q;
input CLK,D;
always @ (posedge CLK)
Q<=D;
endmodule

调用模块1:

1
2
3
4
5
6
7
8
9
10
11
12
13
module examp (clk,d,a,q)
output q;
input clk,d,a;

wire d1;
wire q1;

DFF dff1(.CLK(clk),.D(d1),.Q(q1));
DFF dff2(q1,d,q);

or (d1,a,q);

endmodule

其中参数的传递方法也有两种,一种是具名参数传递,一种是顺序传递。

具名参数传递格式如下:

(.底层端口名1(外接信号名1),.底层端口名2(外接信号名2),…)

如:

1
DFF dff1(.CLK(clk),.D(d1),.Q(q1));

顺序传递方法如下:

(外接信号名1,外接信号名2,…)

1
DFF dff2(q1,d,q);

原语调用

Verilog语言提供已经设计好的门,称为门原语(primitive,共12个),这些门可直接调用,不用再对其进行功能描述。

调用格式如下:

门原语名 实例名 (端口连接)

如:

1
and (out, in1, in2); 

and

所有门原语如下:

  1. and:与
  2. or:或
  3. xor:异或
  4. nand:与非
  5. nor:或非
  6. xnor:同或
  7. not:非
  8. buf:缓冲器
  9. bufif1:控制端1有效缓冲器
  10. bufif0:控制端0有效缓冲器
  11. notif1:控制端1有效非门
  12. notif0:控制端0有效非门

三态门

1
2
3
4
5
6
bufif1 b1 (out, in, ctrl);
bufif0 b0 (out, in, ctrl);

notif1 n1 (out, in, ctrl);
notif0 n0 (out, in, ctrl);

端口列表中前面是输出,中间是输入,最后是使能端,输出个数不限。

Powered by Hexo and Hexo-theme-hiker

Copyright © 2019 - 2024 My Wonderland All Rights Reserved.

UV : | PV :