当前位置:   article > 正文

【准研一学习】狂肝15小时整理的Verilog语言入门知识_verilog语言学习

verilog语言学习

闲言稍叙

Verilog和VHDL就是目前使用最多的两个硬件描述语言(HDL),如果阅读本文的你也是Verilog新手,这部分闲言或许对你有所启发。

作者本科是计算机科学与技术专业,现在是准研一,方向和硬件相关。
由于学艺不精,只会点C、Java,电路、信号、单片机等硬件课程都只懂皮毛。由于课题组研究需要,学习了Verilog语言并总结为本文。

C语言是软件描述语言,编码的核心目的在于经过编译、链接后能够产生机器能够识别的指令序列,进而完成代码功能。而Verilog是硬件描述语言,编码的核心目的在于描述门与门之间的连接,通过综合、实现所写的代码,产生可以转化为芯片的图纸,交由厂商通过光刻来生产所设计的电路,最终经过封装、测试,即通常所称的芯片。

要学习Verilog首先需要一个编程平台,有Vivado、Modelsim等,其中Vivado是用的最多的,但是运行比较慢,Modelsim运行的快,但是界面丑,这个看个人喜好安装就好。

有编程平台后,通过在网站上刷题和看书,逐渐就可以上手了。那么下面列举出我学习Verilog所使用过的网站、书籍:

网站
1.HDLBits
网站地址
该网站是全英文的,里面有100多道练习题,题解可以在Github上找到。

2.VLab Verilog OJ
网站地址
这是由中科大团队开发的Verilog在线评测系统,类似于软件编程的洛谷等网站。
其中每个题都会给出比较充分的知识点叙述,然后再让你动手编码,目的在于帮助大家学习Verilog。
有挺多的简单题,适合我这样的零基础菜鸟,但这个网站没有题解,网上只能查到前27题的题解。

其他网站:

书籍

  • 《EDA技术与Verilog HDL》 王金明编著 清华大学出版社
    这本书是基于Vivado平台的,讲了Vivado的基本操作、Verilog语法和一些实例,硬件用EGO1实验板。

  • 《自己动手写CPU》 雷思磊 电子工业出版社
    这本书是基于Modelsim平台的,主要是使用Verilog设计能运行mips32指令的处理器。

  • 《Vivado入门与FPGA设计实例》 廉玉欣 侯博雅编著 电子工业出版社
    这本书也是基于Vivado和EGO1的,介绍了不少组合逻辑和时序逻辑的基本器件设计,如加法器设计。

一、简介

Verilog HDL(Hardware Description Language)是IC设计者常用的两种HDL之一,另一种是VHDL,通过比较Verilog和C语言的区别可以更好地理解它。
Verilog和C语言的区别:

  1. 描述对象不同:
    Verilog语法和C接近,但C描述的是算法逻辑,依赖于硬件对其进行实现。而Verilog是硬件描述语言,描述的是硬件本身。
  2. 处理方式不同:
    C需要经过编译、汇编、链接来将代码转化为可由计算机执行的二进制代码,而Verilog则需要经过综合来生成描述门与门之间互联关系的网表文件。
  3. 执行方式不同:
    Verilog的部分描述语句可以并行执行,而C只能串行执行。

二、模块

2.1 模块是Verilog的设计实体

Verilog的基本设计思想是自顶向下(top-down),设计实体是模块,所有Verilog代码都将在模块内书写。
如下图所示,先定义顶层模块,分析顶层模块中所需的各个子模块,然后进一步对各个子模块进行分解和设计,最终可以将一个大的系统分解为多个子系统。
在Verilog中,成熟的、封装好的模块称为IP(intelligent property),调用已有IP的本质就是模块实例化。
在这里插入图片描述

2.2 模块声明

模块是Verilog的基本设计实体,模块声明的第一行指定了模块名称和端口列表,下面若干行指定了每个端口的方向、位宽、数据类型。
例如定义一个32位加法器模块:

module add32(in1,in2,out);
  input wire[31:0] in1,in2;
  output wire[31:0] out;
  assign out=in1+in2;
endmodule
  • 1
  • 2
  • 3
  • 4
  • 5

其中in1,in2,out是该模块的三个端口,采用上述代码较为简洁,也可以采用如下定义:

module add32(
  input wire[31:0] in1,
  input wire[31:0] in2,
  output wire[31:0] out
);
  assign out=in1+in2;
endmodule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

2.3 模块的实例化

模块实例化有两种方式,
一是按照名称:
.模板中端口名称 (要连接到端口的线名),
二是按照位置
和定义时一一对应
下面我们将在top_module顶层模块中实例化mod_a,如图所示:
在这里插入图片描述
其中mod_a实现的功能是将输入的a、b、c、d四个信号相与、相或,赋给out1和out2,mod_a的声明如下:

module mod_a(
    output out1, out2,
    input in1,in2,in3,in4
);
    assign out1 = in1 & in2 & in3 & in4;
    assign out2 = in1 | in2 | in3 | in4;   
endmodule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在top_module中两种实例化mod_a模块的代码如下:

module top_module( 
    input a, 
    input b, 
    input c,
    input d,
    output out1,
    output out2
);
    //基于端口位置的实例化如下
    mod_a  inst_1(
        out1,out2,a,b,c,d
    );
    //基于端口名称的实例化如下
    mod_a inst_2(
              .out1(out1),
              .out2(out2),
              .in1(a),
              .in2(b),
              .in3(c),
              .in4(d)
    );
endmodule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

三、Verilog基本要素

3.1 数字

在Verilog HDL中,整型常量即整常数有以下四种进制表示形式:

  1. 二进制整数(b或B)
  2. 十进制整数(d或D)
  3. 十六进制整数(h或H)
  4. 八进制整数(o或O)

数字表达方式有以下三种:

  1. <位宽><进制><数字>这是一种全面的描述方式。

  2. <进制><数字>在这种描述方式中,数字的位宽采用缺省位宽(这由具体的机器系统决定,但至少32位)。

  3. <数字>在这种描述方式中,采用缺省进制十进制。

举例:
8’b11000101 相当于十进制的197
8’h8a 相当于十进制138
12 即十进制的12

3.2 变量

声明格式如下:
<数据类型><符号><位宽><变量名> <元素数>
其中数据类型和变量名是必要的,其余均可省略。
数据类型可以是net型、variable型,

  1. net型变量
    相当于硬件电路中的物理连接,特点是输出的值随着输入值的变化而变化。
    在这里插入图片描述
  2. variable型变量
    该变量是有存储功能的数据类型
    在这里插入图片描述
    当变量声明中的位宽大于1时,对应的变量是向量。
    例:wire [3:0] bus;
    该语句声明了4位的wire型向量bus,其中冒号前面的是最高有效位(MSB,Most Significant Bit),冒号后面的是最低有效位(LSB,Least Significant Bit)。

3.3 运算符

1.算术运算符

 +,,×,/,
  • 1

其中/和%不可综合

2.赋值运算符

 =,<=
  • 1

3.关系运算符

<=,>=,>,<
  • 1

4.逻辑运算符

&&,||,!
  • 1

5.条件运算符

 ?:
  • 1

6.位运算符

~,|,^,&,^~或者~^
  • 1

7.移位运算符

 <<,>>
  • 1

8.位拼接运算符

{ }
  • 1

9.等式运算符

 == , != , !==,===
  • 1

其中===和!==不可综合

10.缩位运算符

&,~&|, ~|^,~^,^~)
  • 1

在这些运算符中,多数都很好理解。
因此,后面只对位运算符,位拼接运算符,等式运算符,缩位运算符进行说明。
对运算符的说明

  1. 位运算符
~ :按位取反   	 	a=1001   ~a=0110
& :按位与   	 	a=1001   b=0001   a&b=0001
| :按位或      	 	a=1001   b=0001   a|b=1001
^ :按位异或	     	a=1001   b=0001   a^b=1000
^~ :按位同或(异或非) a=1001   b=0001  a^~b=0111
  • 1
  • 2
  • 3
  • 4
  • 5
  1. 等式运算符中== 和===的区别
==运算中,如果某些位是x或z,则比较结果是x
===运算中,对于某些位是x或z的,也进行比较,两个操作数必须完全一致,结果才为1:
reg [4:0] a=5'b11x01;
reg [4:0] b=5'b11x01;
针对上面的a和b,a==b返回x,而a===b返回1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. 位拼接运算符
用来将两个或多个信号的某些位拼接起来。例如,在进行加法运算时,可以将和与进位输出拼接在一起使用:
input [3:0] ina,inb;
output [3:0] sum;
output cout;
assign {cout,sum}=ina+inb;
  • 1
  • 2
  • 3
  • 4
  • 5
  1. 缩位运算符
缩位运算符是对单个操作数的递推运算,它放在操作数的前面,能将一个矢量缩减为一个标量。
如:reg [3:0] a;
b=&a; 
该代码等效于b=((a[0]&a[1])&a[2])&a[3]
  • 1
  • 2
  • 3
  • 4

四、Verilog行为语句

4.1 过程语句

always过程语句
always过程语句是重复执行的,可综合的。
格式:
always@(<敏感信号表达式>)
begin
//语句序列
end
always过程语句是有触发条件的,触发条件写在敏感信号表达式中。

敏感信号表达式的格式:

  1. 电平敏感型
always@(in1 or in2)
always@(in1,in2)
用or和逗号都表示任一信号可触发事件
  • 1
  • 2
  • 3
  1. 边沿敏感性
posedge表示上升沿,negedge表示下降沿。
为32位加法器添加一个时钟同步信号clk
always@(posedge clk)
begin
  out=in1+in2;
end
在时钟信号的上升沿才会进行加法运算。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

initial过程语句
initial过程语句不带出发条件且仅执行一次。
initial语句通常用于仿真模块中对激励向量的描述,或者用于给寄存器变量赋初值。
它是面向模拟仿真的过程语句,通常不能被逻辑综合工具支持。

4.2 块语句

块语句是由块标识符begin-end或者fork-join界定的一组语句,当块语句只包含一条语句时,块标识符可以缺省。
下面分别介绍串行块begin-end和并行块fork-join
串行块begin-end
串行块中的语句按串行方式顺序执行

module wave1
parameter cycle=10;
reg wave;
initial
  begin
  #(CYCLE/2) wave=0;
  #(CYCLE/2) wave=1;
  #(CYCLE/2) wave=0;
  #(CYCLE/2) wave=1;
  end
initial $monitor($time,,,”wave=%b”,wave);
endmodule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

并行块fork-join
并行块fork-join中的所有语句是并发执行的。例如:

fork
  regb=rega;
  regc=regb;
join
  • 1
  • 2
  • 3
  • 4

用fork-join并行块产生信号波形

module wave2;
parameter CYCLE=5;
reg wave;
initial
  fork wave=0;
  #(CYCLE) wave=1;
  #(2*CYCLE) wave=0;
  #(3*CYCLE) wave=1;
  #(4*CYCLE) wave=0;
  #(5*CYCLE) wave=1;
  #(6*CYCLE)  $stop;
  join
initial $monitor($time,,,”wave=%b”,wave);
endmodule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

4.3 赋值语句

1.连续赋值(Continuous Assignment)语句
用assign对wire型变量进行赋值的语句。
等式右边的任何变化都将随时反映到左边去。

2.过程赋值(Procedural Assignment)语句
1)非阻塞赋值(Non-Blocking)
赋值符号为”<=”,是并行执行的
非阻塞赋值在整个过程块结束时才完成赋值操作

2)阻塞赋值(Blocking)
赋值符号为”= “,是串行执行的
在这里插入图片描述

4.4 条件语句

4.4.1 if-else语句

格式有三种:
在这里插入图片描述
其中语句序列可以是单句,可以是多句,多句时要用begin…end块语句括起来。

4.4.2 case语句

格式如下:

case(敏感表达式)1:语句序列1;2:语句序列2;
 ...
 值n:语句序列n;
 default:语句序列n+1;
endcase
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

根据敏感表达式的值,来选择对应的语句序列进行执行。

casez语句中,对于值为高阻z的位无需比较。
casex语句中,对于值为z或者x的位无需比较。
此外,在casez和casex中还可以使用?来表示无需比较的位

4.5 循环语句

  1. for语句
    for(循环变量初始化;循环条件;修改循环变量)
    执行语句序列;
  2. forever语句
    forever begin
    执行语句序列;
    end
  3. repeat语句
    repeat(循环次数表达式) begin
    执行语句序列
    end
  4. while语句
    while(循环条件) begin
    语句序列
    end

4.6 编译指示语句

1. 宏替换 `define
格式:`define 宏名 变量或名字
`define可以用简单的宏名替代复杂的表达式

2. `include语句
`include是文件包含语句,它可将一个文件全部包含到另一个文件中,格式如下:
`include “文件名”

3. 条件编译语句`ifdef   `else  `endif
通过条件编译语句可以指定内容进行编译。
`ifdef 宏名
  语句序列
`endif
`ifdef 宏名
  语句序列1
`else
  语句序列2
`endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

五、Testbench

5.1 为什么需要Testbench

Verilog是用来设计电路的,并且模块是Verilog的设计实体,那么设计好的模块需要验证其功能和性能是否符合预期目标,Testbench正是为了满足这一需要产生的。下图展示了Testbench的功能。
概念:
Testbench的本质仍是Verilog模块,但它是用于产生激励信号,对所设计的电路进行测试的特殊Verilog模块。
在这里插入图片描述

5.2 Testbench的目的和结构

编写Testbench的目的
编写Testbench的主要目的是对使用 HDL设计的电路进行仿真验证,测试设计电路的功能、性能是否与预期的目标相符。
编写Testbench进行测试的过程如下:
1)产生激励(就是给被测试模块输入向量)
2)将产生的激励加入到被测试模块并观察其输出响应
3)将输出响应与期望值进行比较

Testbench的基本结构
module <Testbench名>;
<变量定义声明>
<使用initial或者always语句产生激励波形>
<待测试模块例化>
<监控和比较输出响应>
endmodule

5.3 激励产生的方式

Testbench的激励有几种产生方式,

  1. HDL描述方式
    Verilog作为硬件描述语言,既可以用于设计硬件电路,也可以用于产生仿真激励。

  2. 文本输入方式
    使用HDL来产生复杂数据结构的激励较为麻烦,Verilog提供了读入文本文件的系统函数 r e a d m e m b 和 readmemb和 readmembreadmemh,分别用于从文本文件读入二进制和十六进制数据,存放到Verilog自定义的memory中,Verilog再从memory中取出数据将激励施加到被测模块。

  3. 编程语言接口(PLI)方式
    仿真工具提供了PLI,PLI指将C程序嵌入到HDL设计中,用户可以用C写扩展的系统任务和函数,扩充了HDL语言的功能。

5.3.1 产生时钟的方式

1.使用initial方式产生占空比为50%的时钟

inial
begin
	clk_1=0;
	forever
		#50 clk_1=~clk_1;
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

注意:要给时钟赋初值,时钟缺省值为z,取反仍为z,如果没有赋初值,时钟就会一直处于高阻z状态

  1. 使用always方式
initial 
	clk_2=0;
always
	#50 clk_2=~clk_2;
  • 1
  • 2
  • 3
  • 4
  1. 使用repeat产生确定数量的时钟
initial
begin
	clk_3=0;
	repeat(6)
		#50	clk_3=~clk_3;
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. 产生占空比非50%的时钟
initial
	clk_4=0;
always
beign
	#30 clk_4=~clk_4;
	#20 clk_4=~clk_4;
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

下图展示了采用上面3种方式生成的时钟,
图中clk_1使用forever产生10MHz时钟,
clk_2使用always产生10MHz时钟,
clk_3使用repeat指定循环6次,产生3个周期的时钟,
clk_4通过设定不同的延时(延时30ns后置1,延时10ns后置0)产生占空比非50%的时钟。
在这里插入图片描述

5.3.2 产生复位信号的方式

同步复位和异步复位的概念

同步复位指复位信号是否生效依赖于时钟上升沿的到来,
异步复位则无需等待时钟上升沿,只要复位信号有效就能对系统进行复位。

在使用Verilog产生复位信号的激励有如下几种方式:
1)异步复位

initial
begin
	rst=1;
	#100;
	rst=0;
	#500;
	rst=1;
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2)同步复位

initial
begin
	rst=1;
	@(negedge clk);
	rst=0;
	#30;
	@(negedge clk);
	rst=1;
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

5.4 仿真结果分析的方式

运行仿真后,可以通过查看波形、显示信息和LOG文件的方式来分析仿真结果。

查看波形是最基本的方式,指的是根据时钟和激励来查看对应时刻的信号的值是否正确。在信号较多时这种方式比较繁琐低效,因此查看波形的分析方式通常在小模块的仿真分析时使用。

显示信息和LOG文件的方式是指在设计Testbench时添加一些自检测的程序,将程序运行的状态信息显示到屏幕或LOG文件中,以便后续分析。

5.5 Testbench实例

5.5.1 2-4解码器

实现2-4解码器
根据输入A,B的变化,来改变输出Z的值
源码如下:
在这里插入图片描述
tb核心代码如下(省略例化和信号声明):

initial begin
en=0;
a=0;
b=0;
#10 en=1;
#10 b=1;
#10 a=1;
#10 b=0;
#10 a=0;
#10 $stop;
end
always @(en or a or b or z) begin
$display("At time%t,input is %b%b%b,output is %b",$time,a,b,en,z);
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

通过系统任务$display将程序执行相关信息输出到控制台
仿真结果分析
在这里插入图片描述
在这里插入图片描述
通过查看波形可以发现,z随着{a,b}产生对应的编码,因此2-4编码器满足设计要求

5.5.2 时序检测器

下面是一个时序检测器的原码,用于检测数据线上连续3个1的序列,在时钟的每个上升沿检查数据。
源代码如下图所示。
在这里插入图片描述

`timescale 1ns / 1ps
截取核心Testbench如下
    initial
    begin
        Data=0;
        #5 Data=1;
        #40 Data=0;
        #10 Data=1;
        #40 Data=0;
        #20 $stop;
    end

    initial 
        Out_File=$fopen("results.txt");
    always @(posedge Clock)
    begin
        if(Detect==1'b1)
            $fwrite(Out_File,"At time %t,Detect out is 1\n",$time);
            
    end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

在第一个initial语句中,设置激励
在第二个initial中,将程序执行的相关信息通过文件IO的系统任务$fopen和fwrite写入日志
仿真结果分析
在这里插入图片描述
时钟Clock一个周期为10ns,在Data连续三次上升沿为1后,Detect置为1。时序检测器满足设计要求。

5.6 常用的系统函数

1.$display$write
$display$write功能相同,都用于将仿真结果输出控制台,区别是$display输出后自动换行,$write不能。

2.$finish$stop
$finish$stop用于控制仿真过程,$finish表示结束仿真,$stop表示中断仿真。

3.$time$realtime
$time$realtime用于显示已仿真时间,$time返回整数,$realtime返回浮点数。

4.$monitor$strobe
$monitor$strobe也属于输出型函数,$monitor用于实时监控变量并输出,$strobe用于在仿真事件发生后输出。

5.$readmemb$readmemh
用于从外部文件读取数据并放入存储器中

6.$random
产生随机数的系统任务,返回32位随机整数
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/204912
推荐阅读
相关标签
  

闽ICP备14008679号