OK,我们到目前为止已经了解了芯片的架构、资源和使用这款芯片的开发流程,从今天开始就可以进入实践开发阶段了,本节我会先对近日所学的Verilog HDL语言知识进行总结。
目录
1.Verilog HDL基础
1.1 Verilog HDL基本结构
对于Verilog HDL的结构要求,我列出了以下几点供大家参考:
- Verilog HDL程序是由模块构成的。每个模块嵌套在module和 endmodule声明语句中。
- 每个Verilog HDL源文件只有一个顶层模块,其他为子模块。可以每个模块写一个文件。
- 每个模块要进行端口定义,并说明输入输出的端口,然后对模块的功能进行行为逻辑描述。
- 模块中的时序逻辑部分要在always块的内部,在always块中只能对寄存器变量进行赋值。
- 模块中对端口或其他wire型变量的赋值,必须在always块的外部用assign语句进行赋值,通常是将always内部的寄存器的值送出。
- 代码书写格式自由,一行可以写几个语句,一条语句也可分多行写。
- 除了endmodule语句、begin_end语句和fork_join语句之外,每个语句和数据定义的最后必须有分号。
- 代码注释可以采用//...或者/* ... */。
下图为一个简单例子。
图1.1
1.2 数据类型及变量、常量
1.2.1 数据类型
Verilog中有两种常用的数据类型,分别是线网类型和变量类型,这两种类型都属于四态数据类型,有着4种可能的逻辑值(0、1、X、Z)。相信学过数字逻辑的同学对这四种状态肯定都比较熟悉。
图1.2.1
对于线网类型,重点是常用的wire型变量;对于变量类型,重点是reg型变量。
我们也可以说成是wire型信号和reg型信号。(我理解是,他们是在电路之间传递的,就像传递信号一样。在VHDL中似乎也有这种说法,但是VHDL的语法更加复杂。)
对于wire型信号和reg型信号的定义如下:
图1.2.2
图1.2.3
ok我们现在对verilog中的数据类型和变量都有了基本的认识,那么对于常量呢?
Verilog HDL中符号常量的定义如下:
如果用关键词parameter来定义一个标识符,代表一个常量,这个常量就被称为符号常量。
值得注意的是,对于已经定义好的符号常量,我们可以在后续调用模块时再对其进行重定义。
下面给出了一个符号常量定义及其重定义的例子。
图1.2.4
重定义的例子如下:
图1.2.5
我们再adder中将常量time_delay的值定义为5,time_count为10,可以看到,后续调用模块时采用了#(4,8)把常量的值重新定义。
存储器型变量,实际上是一个寄存器数组,是由reg型衍生而来的,定义方式如下:
图1.2.6
1.2.2 数的表达
在verilog中数的表达,大致有以下3种方式,我觉得从编程者角度来说,应当常采用完整的表达方式,这样也可以提高程序的可读性。
图1.2.6
1.3 运算符
(1.)算术运算符
图1.3.1
需要注意的是,算术运算时,如果某一个操作数为X,则结果也是X。
(2.)逻辑运算符
图1.3.2
常用于条件语句,同样的,若有一个输入为X,则结果也为X。
(3.)按位运算符
图1.3.3
这里注意以下按位同或,之前也没用过。
(4.)关系运算符
图1.3.4
常用于条件判断,同样的,若某一操作数为X,则返回值也是X。
(5.)等式运算符
图1.3.5
这里需要注意下 === 和 !== 的用法,常用于case表达式的判别。(具体怎么用,我还没遇到过。)
(6.)缩减运算符
图1.3.6
缩减运算是对单个操作数进行的运算,结果是1位的二进制数。
查阅了一些资料,对于缩减运算符的运算过程大致如下:
第一步先将操作数的第1位和第2位进行与、或、非运算,第二步将运算结果与第3位进行与、或、非运算,以此类推直至最后一位。
(7.)移位运算符
图1.3.7
(8.)条件运算符
图1.3.8
这里需要注意的是,条件运算符?不能对寄存器变量(reg)进行赋值。
(9.)拼接运算符
图1.3.9
图1.3.10
(10.)运算符优先级
图1.3.11
1.4 语句
1.4.1 赋值语句
赋值语句分为两类连续赋值语句和过程赋值语句,介绍如下:
图1.4.1
这里需要注意的是,对wire型变量赋值要使用连续赋值语句assign;而对reg型变量赋值就直接使用过程赋值语句 = 和 <= 即可。(过程块:由always或者initial引导的语句块)
1.4.2 结构说明语句
(1.)always语句
图 1.4.2
值得说明的是,always也可以引导组合逻辑,不一定都是时序逻辑。
这里还需要注意的是,在always块中被赋值的只能是reg型变量。
注意格式:always@(敏感信号表达式)begin(语句块)end
(2.)initial语句
用于对寄存器变量赋予初值,可在其中使用条件语句进行赋值。
格式:initial begin(语句块)end
图1.4.3
1.4.3 阻塞赋值与非阻塞赋值
图1.4.4
这是一个比较重要的概念,我的理解是这样,阻塞就是相当于时钟信号到达之后,赋值顺序进行,有先后次序;而非阻塞就是时钟信号到达之后,并行执行所有赋值语句,同一时间完成赋值。
1.4.4 条件语句
常用的条件语句分为两大类:if-else语句和case语句。
需要注意的是,条件语句用于always或initial过程块的内部。
(1)if-else语句
Verilog HDL中的if-else语句和C语言中的基本是一样的,都是判定所给条件是否满足,根据判定的结果(真或假)决定执行给出的两种操作之一。
if-else语句有3种形式:
图1.4.5
注:
- 如果语句有多条组成,必须包含再begin和end之内;
- 3种形式的if语句后面都有表达式,一般为逻辑表达式或者关系表达式。当表达式的逻辑值为1,按真处理,若为0、X、Z其他三种逻辑值,则都按照假处理;
- else语句不能单独使用,它是if语句的一部分。
(2)case 语句
case语句是一种多分支选择语句,if只有两个分支可以选择,但是case可以直接处理多分支语句,这样可以使得程序看起来更加简洁。
在verilog中有三种类型的分支语句,分别是case,casez,casex。
图1.4.6
图1.4.7
图1.4.8 casex的例子
这里需要注意的是, casex和casez是不同于C语言中的用法,我们两者选一就行,就比如casex,如果分支表达式某些位的值为X或者Z,那么比较时则可以不考虑对这些位的比较,比如上面这个例子。
1.4.5 循环语句
在Verilog中存在着多种循环语句,用来控制执行语句的执行次数。
但在FPGA设计中,循环语句有时不一定能被综合,就比如循环次数未知的循环语句。
循环语句多用于在仿真代码生成仿真激励信号。
(1.)forever语句:连续执行的语句
格式:forever begin(语句块)end
常用于仿真代码中。
(2.)repeat语句:连续执行N次的语句
格式:repeat(表达式)begin (语句块)end
其中,表达式用于指定循环次数,可以是一个整数、变量或者数值表达式。如果是变量或者表达式,其数值只会在第一次循环时得到计算,从而得到确切的循环次数。
也常用于仿真中。
(3.)while语句:执行语句,直至条件不满足时跳出循环
格式:while (表达式) begin语句块end
表达式通常是一个逻辑表达式或者关系表达式。
在每次执行循环体之前,都需要对这个表达式判断是否成立。
(4.)for语句
有时我们在使用while语句时,循环次数并不是确定的,而for语句则可以很轻松的确定出循环次数。如果要让系统能够综合,那么循环的次数一定是固定的。
格式:for(循环变量赋初值;循环执行条件;循环变量增值)begin (语句块)end
例如:for(i=1;i<=8;i=i+1)begin...end
。
。
。