【Verilog HDL】门级描述 / 数据流描述 / 行为级描述——通过四选一多路选择器,实现对于不同层级描述方式的整体性认知

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 门级描述

门级原语:andor……

门级描述与门级原语为基本单元

2.2 数据流描述

连续赋值语句:assign

数据流描述以连续赋值语句为基本单元

2.3 行为级描述

结构化过程语句:initialalways

行为级描述以结构化过程语句为基本单元

2.4 补充:独立的语句

独立的语句指的是

  • 输入输出端口的声明,特别的,reg类型输出端口可以定义的时候初始化,但是输入端口不允许
module Example (
	input a,b,
	output reg OUT = 0	//【这里是关键点!】
	);
	
	<其他内容>
endmodule
  • 内部线网的设定,可以在定义的时候初始化:wire a = 1;
  • 内部变量的声明,可以在定义的时候初始化:reg b = 0;

2.5 小结

  1. 门级描述:输出部分必须是net类型,门级原语本质是模块实例调用,符合端口连接规则
  2. 数据流描述:左值必须是net类型,右值无要求
  3. 行为级描述:左值必须是reg类型,右值无要求,这里的重点是过程赋值语句的要求,因为它是行为描述的基本单元,就像C语言的变量那样。

3 理解三种描述方式的本质

3.1 门级描述

门级描述,使用门级原语对硬件设计进行描述,它直接反应了逻辑门直接的关系,更加接近底层,接近硬件

3.2 数据流描述

数据流描述,描述了输出数据与输入数据之间的逻辑关系,通过逻辑表达式来建立输入输出数据的联系。

逻辑表达式可以理解为对硬件设计功能的数学表达形式

3.3 行为级描述

行为级描述,直接描述硬件设计所能实现的功能,相当于:设计者告诉软件需要实现怎样的功能,由软件自动生成其门机描述。当然,没有那么智能。

4 理解不同抽象层级描述方式与功能设计之间的联系

此处,我将会以1位四选一数据选择器的设计为例

4.1 需求分析 & 行为级描述

  1. 输入四个数据,从四个里面选择一个:d0,d1,d2,d3
  2. 通过地址控制选择哪个:s1,s0

其行为描述是

  1. 对于输入的数据
  2. 如果地址是00,则输出d0
  3. 否则,如果地址是01,则输出d1
  4. 否则,如果地址是10,则输出d2
  5. 否则,如果地址是11,则输出d3
  6. 否则,输出x

设计块如下:
if语句版本的设计块

module mux_4to1 ( 
    input d0,d1,d2,d3,
    input s1,s0,
    output reg out = 0
    );
    
    always @(*)
    begin
        if ({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;
        else
            out = 1'bx;
    end
    
endmodule

case语句版本的设计块

module mux_4to1 (
    input d0,d1,d2,d3,
    input s1,s0,
    output reg out = 0
    );
    
    always @(*)
    begin
        case({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"); // 千万别忘记这个
        endcase
    end

endmodule

激励块如下:

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);
    
    initial
    begin
        #1 s1 <= 0; s0 <= 0;
        
        #1 s1 <= 0; s0 <= 1;
        
        #1 s1 <= 1; s0 <= 0;
        
        #1 s1 <= 1; s0 <= 1;
    end
    
endmodule

输出结果为:
结果
事实上,行为级描述,不仅仅可以适用于1位位宽,更可以直接设置为32位位宽,这是其他描述方式做不到的,他们需要将1位的模块组合成32位的。

4.2 求逻辑表达式 & 数据流描述

  1. 列出真值表
  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 画逻辑电路图 & 门级描述

  1. 选择器件
  2. 根据逻辑表达式画出逻辑电路图

此处选用基本的逻辑门作为器件。

相比之下,门级描述显得非常复杂,这里不再赘述,请读者自行查阅资料。

当今时代也很少有人再使用门级描述。

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级描述,会大大提高设计者的开发效率。

简而言之,就是把别人做好的东西直接拿来用,帮助你快速完成你设计的东西。

发布了21 篇原创文章 · 获赞 6 · 访问量 3015

猜你喜欢

转载自blog.csdn.net/weixin_42929607/article/details/104848995