2020年1月10日 星期五

Verilog HDL菜鸟学习笔记———三、Verilog常用语法之一



Verilog HDL菜鸟学习笔记———三、Verilog常用语法之一


1.一个完整版实例
上一次Verilog学习笔记中,我通过几个小例子,较为直观的对Verilog编程有了一些了解。这次笔记,我开始着重的系统学习Verilog编程语法基础。在我系统学习语法之前,我先用一个实例,引入这次笔记。


上图被测试器件被测试器件DUT 是一个二选一多路器。测试装置(test testbench or fixture)提供测试激励及验证机制。TestBench 使用行为级描述,DUT采用门级描述。下面将给出 采用门级描述。下面将给出TestBench的描述、DUT的描述及如何进行混合仿真。


1)书写Verilog程序
上图依旧是我们曾经提到的经典二选一电路,现在我对其进行Verilog编程(以后文章中程序均为笔者在Sublime Text3中书写):


2)书写该程序的TestBench
书写TestBench时候应注意,此验证性程序是没有端口的,这是因为TestBench是最顶层模块,不会被其他模块实例化,所以不需要端口。同时硬件描述程序中,即MUX2_1.V程序在TestBench中需要进行实例化,应注意模块名字要与引用模块(即MUX2_1)相同;实例的名字可以任意,但是要符合标记命名规则;端口列表与引用模块次序相同(即out ,a,b,sel)。
在书写TestBench时候常用initial和always这两个过程语句,二者区别为:initial:只能执行一次;always:循环执行。值得注意的是所有过程在时间0执行一次,过程之间是并行执行的(FPGA的特点)


激励描述:

(本图同数字电路中真值表)

• 例子中,a, b, sel说明为reg类数据。reg类数据是寄存器类数据信号,在重新赋值前一直保持当前数据。例子中,a, b, sel说明为reg类数据。reg类数据是寄存器类数据信号,在重新赋值前一直保持当前数据。
• #5 用于指示等待5个时间单位。
• $ stop是结束仿真的系统任务,也可以写成$finish。
3)TestBench 响应的产生
Verilog 提供了一些系统任务和系统函数,包括:
• $time 系统函数,给出当前仿真时间;
• $monitor 系统任务,若参数列表中的参数值发生变化,则在时间单位末显示参数值。
格式:$monitor ([“format_specifiers”,] <arguments>);
例如:$monitor($time, o, in1, in2);
$monitor($time, “%b %h%d %o”, sig1, sig2, sig3,sig4);
说明:
a)$time 是一个系统函数,返回当前返回仿真时间。时间用64位整数表示。
b)$monitor 在时间单位末,若参数列表中的参数值发生变化则显示所列参数的值。由$time引起的变化不会显示。
c)$monitor系统任务支持不同的数基。缺省数基是十进制。支持的数基还有二进制、八进制、十进制。
4)完整的TestBench


2.模块的结构
Verilog的基本设计单元是“模块”(block) 。一个模块是由两部分组成的,一部分描述接口,另一部分描述逻辑功能,即定义输入是如何影响输出的。


Verilog 模块的结构由在module和endmodule 关键词之间的四个主要部分组成:


1)模块端口的定义
模块的端口声明了模块的输入输出口。其格式如下:
module 模块名(口1,口2,口3,口4, ………);
模块名(标识符)的命名规则:
• 标识符是用户在描述时给 标识符是用户在描述时给Verilog对象起的名字;
• 标识符必须以字母 标识符必须以字母(a-z, A-Z) 或( _ ) 开头,后面可以是字母、数字、( $ )或( _ )。
• 最长可以是1023个字符个字符。
• 标识符区分大小写,标识符区分大小写,sel 和SEL是不同的标识符。
• 模块、端口和实例的名字都是标识符。
• 标识符要区别与关键字,这个命名规则和C语言大同小异。


2)编程命名规范
a)匈牙利命名:
开头字母用变量类型的缩写,其余部分用变量的英文或英文的缩写,要求单词第一个字母大写。
int iMyAge; “i”是int类型的缩写; char cMyName[10]; “c”是char类型的缩写; float fManHeight; “f”是float类型的缩写;
b)驼峰式命名法:
又叫小驼峰式命名法。第一个单词首字母小写,后面其他单词首字母大写。
int myAge;
char myName[10];
float manHeight;
c)帕斯卡命名法:
又叫大驼峰式命名法。每个单词的第一个字母都大写。
int MyAge;
char MyName[10];
float ManHeight;
个人比较喜欢驼峰命名法和下划线结合来用。
3)模块内容
模块的内容包括I/O说明、内部信号声明、功能定义。
I/O说明的格式:
输入口:input[信号位宽-1:0] 端口名1;
input[信号位宽-1:0] 端口名2;
input[信号位宽-1:0] 端口名i; //(共有i个输入口)
输出口:output[信号位宽-1:0] 端口名1;
output[信号位宽-1:0] 端口名2;
output[信号位宽-1:0] 端口名j; //(共有j个输出口)
输入/输出口:inout[信号位宽-1:0] 端口名1;
inout[信号位宽-1:0] 端口名2;
inout[信号位宽-1:0] 端口名k; //(共有k个双向总线端口)
注:I/O说明也可以写在端口声明语句里。其格式如下:
module module_name(input port1,input port2,… output port1,output port2… );
4)内部信号说明
在模块内用到的和与端口有关的wire 和 reg 变量的声明。
如: reg [width-1 :0] R变量1,R变量2 …;
wire [width-1 : 0] W变量1,W变量2 …;
5)功能定义
模块中最重要的部分是逻辑功能定义部分。有三种方法可在模块中产生逻辑。
a)用 assign 语句(数据流描述):assign a = b & c ;
b)用实例元件(结构化描述):and2 and_inst ( q, a, b);
c)用 “always” 块(行为描述):
always @ (posedge clk or
posedge clr)//敏感性列表
begin
if (clr) q<= 0;
else if (en) q<= d;
end //begin……end相当于C语言中的()
说明:上述三条语句是并行的,他们产生独立的逻辑电路;而在always块中:begin和end之间是顺序执行的。
3.数据类型及常量和变量
1)常量
在程序运行过程中,其值不能被改变的量称为常量。
I.数字:
(1)整数
在Verilog HDL中,整型常量即整常数有以下四种进制表示形式:
a)二进制整数(b或B)
b) 十进制整数(d或D)
c) 十六进制整数(h或H)
d) 八进制整数(o或O)
数字表达方式有以下三种:
a) <位宽><进制><数字>这是一种全面的描述方式。
b) <进制><数字>在这种描述方式中,数字的位宽采用缺省位宽(这由具体的机器系统决定,但至少32位)。
c) <数字>在这种描述方式中,采用缺省进制十进制。
(2)X,Z,0,1的含义


在数字电路中,x代表不定值,z代表高阻值。一个x可以用来定义十六进制数的4位二进制数的状态,八进制数的3位,二进制数的1位。z的表示方式同x类似。z还有一种表达方式是可以写作“?”。
(3)负数
一个数字可以被定义为负数,只需在位宽表达式前加一个减号,减号必须写在数字定义表达式的最前面。注意减号不可以放在位宽和进制之间也不可以放在进制和具体的数之间。
-8'd5 //这个表达式代表5的补数(用八位二进制数表示)
8‘d-5 //非法格式
(4)下画线(underscore_)
下划线可以用来分隔开数的表达以提高程序可读性。但不可以用在位宽和进制处,只能用在具体的数字之间。
16'b1010_1011_1111_1010 //合法格式
8'b_0011_1010 //非法格式
当常量不说明位数时,默认值是32位,每个字母用8位的ASCII值表示。
(5)基数符号及其合法的表示值


举例:


(6)实型数及其表示
Verilog中的实数可以用十进制与科学计数法两种格式来表示,如果采用十进制格式,小数点两边必须都有数字,否则为非法的表示形式。
实例:

排版太困难了,原谅我用图片了

II. 参数型(parameter)
a)用参数声明一个可变常量,常用于定义延时及宽度变量。
b)参数定义的语法:parameter <list_of_assignment>;
c)可一次定义多个参数,用逗号隔开。
d)在使用文字(literal)的地方都可以使用参数。
e)参数的定义是局部的,只在当前模块中有效。
f)参数定义可使用以前定义的整数和实数参数。
2)变量
变量即在程序运行过程中其值可以改变的量。常用的网络数据类型包括wire型和tri型。wire型变量通常是用来表示单个门驱动或连续赋值语句驱动的网络型数据,tri型变量则用来表示多驱动器驱动的网络型数据。
a)wire型
wire型数据常用来表示用于以assign关键字指定的组合逻辑信号。Verilog程序模块中输入输出信号类型缺省时自动定义为wire型。其格式如下:
wire [n-1:0] 数据名1,数据名2,……数据名i; //共有i条总线,每条总线内有n条线路,或
wire [n:1] 数据名1,数据名2,……数据名i;
b)reg型
寄存器是数据储存单元的抽象。寄存器数据类型的关键字是
reg。 reg类型数据的默认初始值为不定值x。reg型数据常用来表示用于“always”模块内的指定信号,常代表触发器。在“always”块内被赋值的每一个信号都必须定义成reg型。
reg型数据的格式如下:
reg [n-1:0] 数据名1,数据名2,… 数据名i;
reg [n:1] 数据名1,数据名2,… 数据名i;
c)memory型
Verilog HDL通过对reg型变量建立数组来对存储器建模,可以描述RAM型存储器,ROM存储器和reg文件。数组中的每一个单元通过一个数组索引进行寻址。在Verilog语言中没有多维数组存在。 memory型数据是通过扩展reg型数据的地址范围来生成的。其格式如下:
reg [n-1:0] 存储器名[m-1:0];
reg [n-1:0] 存储器名[m:1];
3)如何选择正确的数据类型


a)输入口(input)可以由寄存器或网络连接驱动,但它本身只能驱动网络连接。
b)输出口 (output)可以由寄存器或网络连接驱动,但它本身只能驱动网络连接。
c)输入/输出口(inout)只可以由网络连接驱动,它本身也只能驱动网络连接。
d)如果信号变量是在过程块 (initial块或always块)中被赋值的,必须把它声明为寄存器类型变量。


『文末说明』

沒有留言:

張貼留言

Messaging API作為替代方案

  LINE超好用功能要沒了!LINE Notify明年3月底終止服務,有什麼替代方案? LINE Notify將於2025年3月31日結束服務,官方建議改用Messaging API作為替代方案。 //CHANNEL_ACCESS_TOKEN = 'Messaging ...