目录
- 0 前言
- 1 输出端口的设计
- 1.1 门级描述和数据流描述
- 1.2 行为级描述
- 2 三种描述方式的整体架构
- 2.1 门级描述
- 2.2 数据流描述
- 2.3 行为级描述
- 2.4 补充:独立的语句
- 2.5 小结
- 3 理解三种描述方式的本质
- 3.1 门级描述
- 3.2 数据流描述
- 3.3 行为级描述
- 4 理解不同抽象层级描述方式与功能设计之间的联系
- 4.1 需求分析 & 行为级描述
- 4.2 求逻辑表达式 & 数据流描述
- 4.3 画逻辑电路图 & 门级描述
- 4.4 小结
- 5 激励块的特殊设置
- 6 善用科技黑箱:利用行为级描述和集成器件快速完成设计
0 前言
本文从整体上带你完成Verilog HDL语言的三种不同描述方式,让你从宏观上有所把握。
最核心的原则:一切设计实际需求而定,需要存储变量就用reg
,需要有符号数就用integer/real/reg signed
……
1 输出端口的设计
端口的设计,区别主要在于输出端口是默认的wire
还是自定义的reg
,本篇将以1位四选一数据选择器为例进行说明。
1.1 门级描述和数据流描述
这两种描述的时候,使用默认的wire
即可。
这两种描述方式,本质上都是直接使用逻辑门
- 门级描述是显式地使用了门级原语
- 数据流描述其实是隐式地使用门级原语,因为他是直接描述数据在寄存器直接的流动关系,本质上,还是在阐述逻辑门的使用
门级描述与数据流描述,就好比结绳记事和使用符号记事的区别,用一连串的符号标志,代替了绳子,减少了许多麻烦。
- 门级描述是以门级原语为基石的描述方式,必须使用线网类型
- 数据流描述是以连续赋值语句为基石的描述方式,其左值必须是线网类型,右值无要求。
以下是四选一数据选择器的端口声明,关注output out
语句
module choose_4to1(input d0,d1,d2,d3,input add1,add0,output out // 注意输出端口的设定);endmodule
你需要记住Verilog描述形式
需要记住,门级描述的输出和数据流描述的连续赋值语句的左值,必须是线网类型,所以必须使用默认的输出端口
1.2 行为级描述
行为级描述,输出端口类型应该使用output reg OUT
,使用reg
类型。
因为过程赋值语句的左值必须是寄存器类型
ANSI C风格的描述如下
module choose_4to1(input d0,d1,d2,d3,input add1,add0,output reg out // 注意输出端口的设定);endmodule
你也可以将输出端口初始化output reg out = 0
另外一种端口风格,但是不推荐
module choose_4to1(d0,d1,d2,d3,add1,add0,out);input d0,d1,d2,d3;input add1,add0;// 以下两条语句才能将out声明为reg类型的输出端口output out;reg out;endmodule
2 三种描述方式的整体架构
就像盖房子那样,同样是楼房,使用不同的材料,建造的方式不同,速度也不同。
下面我对这几种描述进行一个近似比喻:
- 门级描述:手里只有基本材料,需要先烧制砖头再盖房子
- 数据流描述:已经有了现成的砖头,只需要将其以合理的方式组合起来
- 行为级描述:已经有了集成的房子,只需要拼接起来,就像火神山医院那样
2.1 门级描述
门级原语:and
、or
……
门级描述与门级原语为基本单元
2.2 数据流描述
连续赋值语句:assign
数据流描述以连续赋值语句为基本单元
2.3 行为级描述
结构化过程语句:initial
和always
行为级描述以结构化过程语句为基本单元
2.4 补充:独立的语句
独立的语句指的是
- 输入输出端口的声明,特别的,reg类型输出端口可以定义的时候初始化,但是输入端口不允许
module Example (input a,b,output reg OUT = 0 //【这里是关键点!】);<其他内容>
endmodule
- 内部线网的设定,可以在定义的时候初始化:
wire a = 1;
- 内部变量的声明,可以在定义的时候初始化:
reg b = 0;
2.5 小结
- 门级描述:输出部分必须是net类型,门级原语本质是模块实例调用,符合端口连接规则
- 数据流描述:左值必须是net类型,右值无要求
- 行为级描述:左值必须是reg类型,右值无要求,这里的重点是过程赋值语句的要求,因为它是行为描述的基本单元,就像C语言的变量那样。
3 理解三种描述方式的本质
3.1 门级描述
门级描述,使用门级原语对硬件设计进行描述,它直接反应了逻辑门直接的关系,更加接近底层,接近硬件。
3.2 数据流描述
数据流描述,描述了输出数据与输入数据之间的逻辑关系,通过逻辑表达式来建立输入输出数据的联系。
逻辑表达式可以理解为对硬件设计功能的数学表达形式。
3.3 行为级描述
行为级描述,直接描述硬件设计所能实现的功能,相当于:设计者告诉软件需要实现怎样的功能,由软件自动生成其门机描述。当然,没有那么智能。
4 理解不同抽象层级描述方式与功能设计之间的联系
此处,我将会以1位四选一数据选择器的设计为例
4.1 需求分析 & 行为级描述
- 输入四个数据,从四个里面选择一个:d0,d1,d2,d3
- 通过地址控制选择哪个:s1,s0
其行为描述是:
- 对于输入的数据
- 如果地址是
00
,则输出d0
- 否则,如果地址是
01
,则输出d1
- 否则,如果地址是
10
,则输出d2
- 否则,如果地址是
11
,则输出d3
- 否则,输出
x
设计块如下:
if语句版本的设计块
module mux_4to1 ( input d0,d1,d2,d3,input s1,s0,output reg out = 0);always @(*)beginif ({s1,s0} == 2'b_00)out = d0;else if ({s1,s0} == 2'b_01)out = d1;else if ({s1,s0} == 2'b_10)out = d2;else if ({s1,s0} == 2'b_11)out = d3;elseout = 1'bx;endendmodule
case语句版本的设计块
module mux_4to1 (input d0,d1,d2,d3,input s1,s0,output reg out = 0);always @(*)begincase({s1,s0})2'b00: out = d0; // 也可写成【2'd0】2'b01: out = d1; // 【2'd1】2'b10: out = d2; // 甚至于你可以直接写【2】2'b11: out = d3; // 【3】default: $display("错误!\n"); // 千万别忘记这个endcaseendendmodule
激励块如下:
module test4;reg d0 = 0,d1 = 1,d2 = 0,d3 = 1;reg s1,s0;wire out;mux_4to1 MT0 (d0,d1,d2,d3,s1,s0,out);initial$monitor("s1 = %b, s0 = %b, out = %b\n",s1,s0,out);initialbegin#1 s1 <= 0; s0 <= 0;#1 s1 <= 0; s0 <= 1;#1 s1 <= 1; s0 <= 0;#1 s1 <= 1; s0 <= 1;endendmodule
输出结果为:
事实上,行为级描述,不仅仅可以适用于1位位宽,更可以直接设置为32位位宽,这是其他描述方式做不到的,他们需要将1位的模块组合成32位的。
4.2 求逻辑表达式 & 数据流描述
- 列出真值表
- 求逻辑表达式:
out = (~s1 & ~s0 & d0) | (~s1 & s0 & d1) | (s1 & ~s0 & d2) | (s1 & s0 & d3)
逻辑表达式,表示了输出与输入直接的逻辑关系,可以直接使用数据流描述。
事实上,只有你写得出逻辑表达式,就能使用数据流描述,但是,对于复杂问题往往很难将其逻辑表达式写清楚,并且当今时代有很多集成的模块,完全可以直接调用他们,而没有必要再自己设计,这一点我在后面再进行阐述。
设计块:
逻辑表达式版本的设计块
module mux_4to1(input d0,d1,d2,d3,input s1,s0,output out);assign out = (~s1 & ~s0 & d0) | (~s1 & s0 & d1) | (s1 & ~s0 & d2) | (s1 & s0 & d3);endmodule
条件操作符版本的设计块,这个其实已经和行为级描述类似了。
module mux_4to1 (input d0,d1,d2,d3,input s1,s0,output out);assign out = s1? (s0? d3:d2):(s0? d1:d0);endmodule
激励块与仿真结果和行为级一样,不再赘述。
4.3 画逻辑电路图 & 门级描述
- 选择器件
- 根据逻辑表达式画出逻辑电路图
此处选用基本的逻辑门作为器件。
相比之下,门级描述显得非常复杂,这里不再赘述,请读者自行查阅资料。
当今时代也很少有人再使用门级描述。
4.4 小结
当今时代人们会使用数据流描述和行为级描述,对于某些必要的部分使用门级描述,但是这种情况非常少。
通常我们使用的是RTL级描述,也就是数据流和行为级描述的混合描述方式。
我们来观察两条线对比以下
结果显而易见,行为级描述更加简单,提高了效率,但是,由于行为级描述目前没有足够智能,有些事情不能完成,因此我们依然需要数据流描述,但是门级描述几乎已经不需要了。
5 激励块的特殊设置
首先,采用分治思想,将激励块和设计块分开看,激励块的输出显示结果,是由激励信号的类型决定的,在符合端口对接规则的前提下,需要对激励信号的数据类型加以修饰,以达到验证输出结果的目的。
目前我们的激励块是这样是:
reg d0 = 0,d1 = 1,d2 = 0,d3 = 1;
reg s1,s0;
wire out;
如果,我们需要输入的是有符号数,则可以改为reg signed d0;
或者integer d0;
或者real d0;
,请记住,输入端口的reg类型,代表的是一组寄存器类型,而不单单是reg。
如果我们需要输出的结果显示为十进制的负数,则需要设置为wire signed out;
,代表其是有符号数。
这也充分体现了开篇所说的:一切设计由需求决定。
6 善用科技黑箱:利用行为级描述和集成器件快速完成设计
科技黑箱就是其他设计者已经开发好的功能,你可以直接拿来使用,以提高开发效率。它也可以是C++中的STL库,Python的库等等。
同时,我想你也已经感受到三种描述方式在开发效率方面的差别,多多使用RTL级描述,会大大提高设计者的开发效率。
简而言之,就是把别人做好的东西直接拿来用,帮助你快速完成你设计的东西。