Verilog HDL语法(上)

1 什么是Verilog HDL

        Verilog HDL(Hardware Description Language)是在C语言的基础上发展起来的一种硬件描述语言,可以用文本形式来描述数字系统硬件的结构和行为,像是逻辑电路图、逻辑表达式等。


1.1发展历程

        Verilog 语言最初是于 1983 年由 Gateway Design Automation 公司为其模拟器产品开发的硬件建模语言。由于他们的模拟仿真器产品的广泛使用,Verilog HDL 作为一种便于使用且实用的语言逐渐为众多设计者所接受。

       Verilog 语言于 1995 年成为 IEEE 标准,称为Verilog-95。设计人员在使用 Verilog-95 的过程中发现了一些可改进之处。为了解决用户在使用此版本 Verilog 过程中反映的问题,Verilog 进行了修正和扩展,这个扩展后的版本也成为了IEEE 标准,称为Verilog-2001。Verilog-2001 是对 Verilog-95 的一个重大改进版本,它具备一些新的实用功能,例如敏感列表、多维数组、生成语句块、命名端口连接等。

        目前,Verilog-2001 是 Verilog 的最主流版本,被大多数商业电子设计自动化软件支持。


1.2为啥需要它

        在 FPGA 设计里面,我们有多种设计方式,如原理图设计方式编写描述语言(代码)等方式。

        一开始很多工程师对原理图设计方式很钟爱,这种输入方式能够很直观的看到电路结构并快速理解,但是随着电路设计规模的不断增加,逻辑电路设计也越来越复杂,这种设计方式已经越来越不满足实际的项目需求了。

想像一下大规模集成电路,他们要咋画?难度太大了。要是画错了就更加麻烦了

        这个时候 Verilog 语言就取而代之了,目前 Verilog 已经在 FPGA 发/IC 设计领域占据绝对的领导地位。


1.3它的作用

        数字电路设计者利用这种语言,可以从顶层到底层逐层描述自己的设计思想,用一系列分层次的模块来表示极其复杂的数字系统。然后利用电子设计自动化(EDA)工具,逐层进行仿真验证,再把其中需要变为实际电路的模块组合,经过自动综合工具转换到门级电路网表。接下来再用专用集成电路 ASIC 或FPGA 自动布局布线工具,把网表转换为要实现的具体电路结构。


1.4Verilog 与 VHDL 

        这两种语言都是用于数字电路系统设计的硬件描述语言,而且都已经是 IEEE 的标准。

VHDL Verilog
成为IEEE标准的时间 1987 1995
来源 美国军方组织开发的 由一个公司的私有财产转化而来
难易程度

相对要难一点

这个是因为 VHDL 不是很直观,一般认为至少要半年以上的专业培训才能掌握。

容易掌握

只要有 C 语言的编程基础,通过比较短的时间,经过一些实际的操作,可以在 1 个月左右掌握。


1.5Verilog 和 C 

        Verilog 是硬件描述语言,在编译下载到 FPGA 之后,FPGA 会生成电路,所以 Verilog 全部是并行处理与运行的;

        C 语言是软件语言,编译下载到单片机/CPU 之后,还是软件指令,而不会根据你的代码生成相应的硬件电路,而单片机/CPU 处理软件指令需要取址、译码、执行,是串行执行的。

      Verilog 和 C 的区别也是 FPGA 和单片机/CPU 的区别,由于 FPGA 全部并行处理,所以处理速度非常快,这个是 FPGA 的最大优势,这一点是单片机/CPU 替代不了的。


2 语法入门:

2.1逻辑值

        逻辑电路中有四种值,即四种状态:

  • 逻辑 0:表示低电平,也就是对应我们电路的 GND;
  • 逻辑 1:表示高电平,也就是对应我们电路的 VCC;
  • 逻辑 X:表示未知,有可能是高电平,也有可能是低电平;
  • 逻辑 Z:表示高阻态,外部没有激励信号是一个悬空状态。

        高阻,或者说“阻高”,是一种电路分析时的特殊情况。简单来说,它可以被看作是具有极高的输入(输出)电阻,在极限状态下就成为了断路。

        严谨地说,高阻态(High_impedance state)、高输出态(High output state)和低输出态(Low output state)共同组成了三态逻辑。

        这里要说明一下:真实电路中电平信号是连续变化的,我们定义 VCC 代表电路电压(也就是电压上限值),而数字电路则对 0~VCC 的范围进行了区间划分。例如:0 ~ 0.3VCC 为低电平(0),0.7VCC ~ VCC 为高电平(1),0.3VCC ~ 0.7VCC 之间的则不属于工作电压,而高阻态往往被认为是理想的 0 电压。 因此,低输出态不等于没有输出;高阻则在行为上更接近于没有输出。

        因此,如果两个输出端口被导线直接相连,且二者一个处于高电平状态,另一个处于低电平状态,此时相连的导线上电平就会出现混乱,即 1+0=?。但处于高阻态的端口不会影响另一个端口的输出结果,即 z+0=0,z+1=1。这一特性使得三态门被广泛应用与总线(Bus)结构之中。

2c0b13afcf284eb294e179b75be085d3.png


2.2标识符与命名规范

        标识符(identifier)用于定义模块名、端口名和信号名等。

        命名规则:只能是下列4种的组合。标识符的第一个字符必须是字母或者下划线

  1. 字母
  2. 数字
  3. $
  4. _(下划线)

标识符大小写敏感,是区分大小写的,毕竟它们的ASCII码不同。

      标识符的几个例子:
        Count和COUNT       //两者不同
        R56_68 

        WIRE  //区别于关键字wire

        FIVE$

        4bit   //错误命名   

不建议大小写混合使用,普通内部信号建议全部小写,参数定义建议大写。

 规范建议:
1、用有意义的有效的名字如 sum、cpu_addr 等;(见名知意
2、用下划线区分词语组合,如 cpu_addr;(而不是用大小写混合的方式CpuAddr;
3、采用一些前缀或后缀,比如:时钟采用 clk 前缀:clk_50m,clk_cpu;低电平采用_n 后缀:
enable_n;(这几乎已经成为了行业标准
4、统一缩写,如全局复位信号 rst。
5、同一个信号在不同层次需保持一致性,如同一时钟信号必须在各模块保持一致。
6、自定义的标识符不能与保留字(关键词)同名。(例如module、begin、wire等)
7、参数统一采用大写,如定义参数使用 SIZE。


2.3数字与进制

        Verilog 数字进制格式包括二进制、八进制、十进制和十六进制。

  1. b/B二进制:4’b0101 表示 4 位二进制数字 0101,值为5;
  2. o/O八进制:'o617 表示 32 位八进制数字 618,值为399;
  3. d/D十进制:5201314 表示 32 位宽十进制数字 5201314
  4. h/H十六进制:4'Ha表示 4位宽十六进制数字 a,值为10。

        十六进制中的 a∼f 字符是不区分大小写的,例如 4'ha=4'hA

        声明时可以不用指明位宽,位宽和编译器有关,一般为32 bit。   

        当代码中没有指定数字的位宽与进制时,默认为 32 位的十进制。比如直接写数字100,实际上表示的值为32’D100,即32位宽的10进制数100。

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

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

2) <进制><数字>在这种描述方式中,数字的位宽采用缺省位宽(32位)

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


        一个4位二进制数的数字其位宽为4,一个4位十六进制数的数字其位宽为16(因为每单个十六进制数就要用4位二进制数来表示)。见下例:

        8'b1100_1100       //位宽为8的二进制数字,值为204

        8'ha2                    //位宽为8的2位十六进制数字。

        注意:整数表示中,如果填写的二进制位宽小于实际位宽,则会根据填写的位宽对实际数据进行截断。在编译器中,这种操作极有可能会触发 Warning。例如:3'hfff 填写的二进制位宽为 3bit,最终会被截断为 3'b111


文章推荐

FPGA 21 ,深入理解 Verilog 中的基数,以及二进制数与十进制数之间的关系( Verilog中的基数 )-CSDN博客

verilog 常见位宽问题集合_verilog位宽不一致赋值-CSDN博客


2.4数据类型

        主要有三大类数据类型,即寄存器类型、线网类型和参数类型。

        从名称中我们也可以看出,真正在数字电路中起作用的数据类型应该是:寄存器类型和线网类型,其余类型可以理解为这两种数据类型的扩展或辅助。

        如果省略 width 不写,则默认变量的位宽为 1(等价于 [0:0])。无论是 wire 型变量还是 reg 型变量,Verilog 统一将位宽为 1 的变量称作标量(Scalar),位宽大于 1 的变量称作向量(Vector)。

        在表达式中,我们可任意选择向量中的一位或相邻几位,分别称为位选择(bit-select)和域选择(part-select)。如果选中的位宽超出了实际范围,部分编译器会报错,部分编译器则会自动将信号接地(接 0)。例如:

wire [4:0] bit_test = 5'b01011

bit_test[0]     //位选择:表示最低位,值为 1'b1
bit_test[3:2]   //域选择:表示第4位和第3位,值为 2'b10
bit_test[4]     //表示第5位,值为 1'b0

 1、线网类型: wire

        线网类型表示元件之间的物理连线/导线。

        线网类型不能保存状态,必须由其他逻辑驱动。它的值 由 驱动它的原件的输出值 决定。

        如果没有驱动原件连接到线网,即线网缺省时,它的值为Z高阻态。因为啥也没接到线上,悬空!

        wire 是 Verilog 的默认数据类型。(也就是说,对于没有显式声明类型的信号,Verilog 一律将其默认为 wire 类型)

        任何已经声明为wire 型变量的连续赋值语句都是以assign开头。

//wire define
wire data_en;         //没指定位宽默认是1bit。数据使能信号
wire a,b,c;           //连续定义3个位宽为1的信号
wire [7:0] data;     //1个字节长度/位宽的数据。当位宽大于 1 时,声明为向量的形式
wire gnd = 1'b0;      //定义时也可以赋值,但是不推荐这样的代码风格
wire [8:2] addr ;     //声明7bit位宽的线型变量addr,位宽有效范围为8:2
assign a = b + c;

2、 寄存器类型:reg、integer

        寄存器类型表示数据存储单元。

        它只能在always和initial中被赋值,被赋值后它会保持数据原有的值,直到再次被改写。

        寄存器类型的缺省值是x。就像现实的电路那样,上电前不知道是0还是1。  

//reg define
reg clk_temp;        //缺省位宽说明默认为1个bit长度
reg flag1, flag2;    //声明两个1位宽的寄存器型变量
reg [0:31] data;    //声明32bit位宽的寄存器变量data, 最高有效位为0,最低位为31
reg [7:0] counter;    //声明1字节长度的reg型
  • 如果赋值过程的语句描述的是时序逻辑,即always语句带有时钟信号,则该寄存器变量对应为寄存器/边沿触发器时钟信号为变量的状态更新提供了一个明确的时间参考点,使得变量的值在每个时钟周期都会根据输入条件进行更新,所以即使有输入信号也不会立马改变输出信号。
  • 如果该过程语句描述的是组合逻辑,即always 语句不带有时钟信号,则该寄存器变量对应为硬件连线wire型变量。因为没有时钟,所以会立马做出数据的更改,这种赋值是基于输入信号的即时变化,它更接近于组合逻辑的行为。硬件综合工具通常会将这些变量实现为硬件连线(wire)类型的逻辑网络。

        reg 作为 Verilog 的类型关键字实际上是具有误导性的。与 wire 类型代表线网不同,一个 reg 类型的变量不一定对应一个寄存器(Register)。

        reg 关键字只是声明了一个在 always 语句中进行赋值的信号。如果 always 描述的是组合逻辑,那么 reg 就会综合成一根线,如果 always 描述的是时序逻辑,那么 reg 才会综合成一个寄存器。        


        可以这样想:在Verilog中,reg类型并不直接对应于硬件中的寄存器;它更多地是一个表示该变量将在always/initial块中被赋值的声明。

        当always块描述组合逻辑时,尽管变量被声明为reg类型,但硬件实现上可能并不包含钟控寄存器。相反,它可能是一个由逻辑门组成的组合逻辑网络。

//时序逻辑电路中的reg代码例子:
always @(posedge clk) begin
    reg_var <= some_expression;
end
//在这个例子中,reg_var是一个reg类型的变量,它在每个时钟上升沿都会根据some_expression的值进行更新。在硬件实现中,这通常对应于一个寄存器或边沿触发器。


//组合逻辑电路中的reg代码例子
always @(*) begin    //是一种组合逻辑块,表示只要输入信号有变化都会执行语句块
                     //()括号里被称为“敏感列表”,*星号表示它对所有输入信号都敏感。
    result = a + b;    //输入信号a,b发生变化时, 结果result也会随之立马变化
end
//例子说明了组合逻辑的特点:它直接反映了当前所有相关输出端口上的最新数据,不存在延迟机制去等待时钟的到来。

2、 参数类型:parameter

        参数其实就是一个常量。

        作用:常被用于定义状态机的状态、数据位宽和延迟大小等,由于它可以在编译时修改参数的值,因此它又常被用于一些参数可调的模块中,使用户在实例化模块时,可以根据需要配置参数。

        由于是常量,所以变量只能被赋值一次,赋值后的参数值不再改变。

        需要注意的是参数的定义是局部的,只在当前模块中有效。

声明位置有以下2种:

1 模块头部声明(推荐方式):

module module_name #(
    parameter WIDTH = 8,
    parameter DEPTH = 16
)
(
    input [WIDTH - 1 : 0] data_in,
    input [DEPTH - 1 : 0] data_out
);

2 模块内部声明

module MyModule (
    input clk, 
    ...
);

parameter DATA_WIDTH = 8;      //32'D8
parameter i = 1, j = 2, k = 3; //32'D1、32'D2、32'D3

//还记得标识符里讲的命名规则吗?参数统一采用大写字母表示,用下划线区分词语。
//哪小写字母用于什么信号的命名呢?

2.5运算符

        Verilog 中的运算符按照功能可以分为下述7种类型:

        1、算术运算符

        2、关系运算符

        3、逻辑运算符

        4、条件运算符

        5、位运算符

        6、移位运算符

        7、拼接运算符 复制

        8、归约运算符

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值