添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

这篇博文是写给要入门Verilog HDL及其初学者的,也算是我对Verilog  HDL学习的一个总结,主要是Verilog HDL的程序结构及其描述,如果有错,欢迎评论指出。

一、 Verilog HDL的程序结构

首先我们不开始讲Verilog HDL的语法,我们从Verilog HDL的程序结构出发。相信大家都看过芯片吧,它有个名字,有个外壳,外壳向外伸出有引脚(BGA封装的那种请不要乱搅和...),然后芯片它可以实现一定的功能。

Ok,知道这些之后,我们就来看看Verilog HDL的描述数字电路的程序结构吧。

在解释结构的时候,我拿芯片设计这个例子来打一个不恰当的比方。

VerilogHDL程序的大致结构就是这么一个形式,每一个模块的结构都是一致的只不过语句之间存在一些差别,每一部分的具体描述将在后面进行。

二、 verilog程序结构的简单描述

OK,现在让我们现在开始了解一下一些语法和注意事项(注意,我这里不会把语法讲得很细很细,我主要是描述一些(我,或者初学者)易忘的,关键的语法和知识点),描述的顺序不一定按照上面的各个部分哟,我们先描述一些VerilogHDL程序必须的。

首先是 模块说明

module  和 endmodule ,这两个关键词成对出现,一般的内容都会囊括在这两个两个关键词之间。一个verilog(.v)文件可以有多个module ...endmodule,但是为了方便管理,建议只实现一个。

此外无论是能够综合成电路的verilog程序还是只是仿真的程序,都需要以模块的形式给出。

①模块名的定义要符合标识符的定义,至于什么是标识符,以后会说的。此外也要注意书写的规范性。

端口说明:

①端口可以比方成芯片的输入输入管脚,它只有三种类型:输入,输出,双向;

②端口有一些附加属性,如数据类型、符号特性、位宽等;input端口只能是线网类型,output可以是寄存器类型也可以是线网类型,inout也只能是线网类型。至于线网类型和寄存器类型,在以后的数据类型中会介绍。

③当端口是总线类型时,可以简单地理解为端口有多位时,对应于同时描述芯片的多个管脚时,可以给端口加上位宽。即input wire  [M:N]  a;如果M>N,则为降序,a[M]为最高位,位宽为M-N+1位;如果M<N,则最高位位a[N]位,位宽位N-M+1位;

④端口的属性声明顺序可以是:

端口的输入输出   数据类型   有无符号   位宽   端口名称 ;

如:output  reg  signed  [7:0] a;

注:数据类型没有写则默认为wire 型,有无符号没有写默认为无符号型;位宽没有写默认为1bit 。

⑤当书写完端口声明是,括号后面的‘;’千万不要忘记。

程序主体:

①前面我们提及到verilog的描述层次中有一个结构化描述,结构化的描述就是一点一点地例化(子模块、单元门)来实现系统/设计的功能。程序的主体可以是只例化一些门单元,但是verilog的这种纯结构化方式的显然是无法满足现代设计要求的,因此纯结构化方式已经被遗弃。

②程序的主体可以是行为描述,也就是通过一些verilog的行为语句来实现设计的要求/功能。行为描述主要有: 控制流描述,过程描述,块语句,时序控制 等。对于行为描述,有可综合的,有不可综合的,可综合的行为描述语句/语法就可以认为是RTL级描述时用到的,而不可综合的行为描述语句/语法可以认为是仿真/建模时用到的。

③下面大概说一下行为描述情况,也就是行为描述中无论是可综合还是不可综合都可以使用的情况,具体的可综合和不可综合的行为级描述语法在后面会描述。

控制流描述 :以assign关键字开头的语句描述称为控制流描述,主要用来实现一些简单的组合逻辑。

块语句 :包含在关键字begin...end 、fork ...join之间的语句称为块语句。

过程描述 :由initial模块、always模块、function模块、task模块这四个模块实现的过程。

时序控制 :延时控制、敏感信号控制语句等

④块语句可以有一个名字,写在begin/fork后面(如beign:adder_disc ),给它一个名字有什么好处呢?好处有两个,一个是可以在块内部定义寄存器变量(注意哦,只能寄存器变量,这个寄存器变量在块内部使用),另外一个就是可以用disable这个关键字来中断语句块的执行(具体怎么弄,请参看其他书籍,这里不详述)

⑤begin...end内的语句是串行的,这是从语法的结果上讲的,但是它是可以综合的,综合出来的实际电路是并行的,也就是实际电路中,各条语句之间并不全是串行的,这里需要建立一个概念,方便讲解以后的阻塞赋值和非阻塞赋值。而fork...join 内的语句是同时进行的,然而这是不可综合的。

⑥一个程序的主体中可以有多个initial模块、always模块、function模块、task模块。Function模块和task模块以后介绍。多个Initial模块、always模块之间是并行的,但是initial只执行一次,而always是反复执行。

initial一般是不可综合的,用在仿真当中;在进行仿真时,通常被用来描述测试模块的初始化、监视、波形生成等功能。

always用在可综合的描述当中,一般情况下由敏感列表触发(至于什么是敏感列表,敏感列表怎么用,请参考其他书籍,我后面也会做一些记载),也就是用在描述RTL级的描述当中,通常被用来对硬件的功能进行描述,可以实现组合逻辑和实现逻辑的功能。

上面没有给出initial格式和always格式只是提了一下简单应用。

⑦程序的主体大多可以由结构描述和行为描述构成

模块的例化:

①模块的例化主要是为实现总体的功能,比如说我要设计一块芯片,这个芯片的某个功能可以有某个芯片来完成,那我就可以调用这个芯片,例化就是调用这么一个概念。

②例化可以使用位置映射,也可使用名称映射,由于位置映射可读性太差,容易出错,所有建议使用名称映射。下面是名称映射的格式:

被调用(例化)的模块名字     自定义的例化模块名(
.被调用(例化)模块的端口名     (本模块中的线网变量名),
...

.被调用(例化)模块的端口名     (本模块中的线网变量名)
);

③悬空处理:

在模块例化时,如果被例化的模块输入管脚悬空,则该输入为高阻Z,相当于与外界隔绝,但是实际电路中...

如果将输出管脚悬空,则该输出管脚将被废弃掉。

④不同端口位宽处理

当模块例化端口和被例化的端口位宽不同时,端口通过无符号数的右对齐截断方式进行匹配。举个例子说:

参数定义与映射:

①参数定义 就是parameter 那一部分了,也就是可以用一个标识符表示一个固定的参数,当然`define这种宏定义也可以实现这种功能,这样子提高程序的可维护性和可读性。

使用`define时语法为:`define  A  2'b10 或者`define A 2.
一般把`define宏定义语句放在模块最前面,并且要注意,无论是在子模块还是顶层模块中,A 的值都代表2(调用时为 `A)(全局参数)。
使用parameter定义的语法为 parameter  A = 2或者parameter A= 2’d2;
一般放在模块名字和模块端口列表之间:模块名 #(parameter A = XX,.....)(端口列表);使用parameter定义的参数只在当前模块中有用(局部参数)

localparam定义时,上层模块不能调用传递参数。

②参数传递:

参数传递就是在编译或者仿真的时候,进行维护时,对参数空闲重新复制而更改其值。传递的参数是子模块中定义的parameter,传递的方法有两种:

(1)使用’#’符号 :在同一模块中使用’#’符号,参数赋值顺序必须与原始模块定义的顺序相同,并不是一定要给所以的参数赋值,但是不允许跳过任何一个参数,即使是保持不变的值也要写在相应的位置。说了这么一大坨,还是举个例子吧:

这样子,A_WITH就是x1,B_WITH不改变,但是要写在相应的位置。

(2)使用defparam关键字

直接上格式: