打印

Verilog HDL三种建模方式

[复制链接]
3090|7
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
星星之火红|  楼主 | 2012-11-8 00:04 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
       如果总结使用verilog HDL进行建模的各种语法,可以将verilog HDL建模方式简单的归纳为三类:结构化描述方式、数据流描述方式、行为描述方式。一个模块中往往是将三种建模方式混合起来使用,来描述一个完整的功能。这三种建模方式是业界在使用verilog HDL的过程中不断归纳总结出来的,与IEEE 1364-2001的划分方式不太一样。这种简化后的归纳分类十分清晰,更利于掌握Verilog HDL的设计方法。学习笔记将会用四小节来分别介绍这三种建模方式的特点,以及如何使用三种方式混合建模。每小节都会介绍各种建模方式的基本概念,同时会给出详细的示例,便于读者对三种建模方式有一个概括性的了解。笔记中会以博主注的形式,说明一些需要注意的知识点并做一定延伸,并介绍三种建模方式的应用特点。

相关帖子

沙发
星星之火红|  楼主 | 2012-11-8 00:05 | 只看该作者
结构化的建模方式就是通过对电路的层次和组成结构进行描述来建模,即通过对器件的调用(HDL概念称为例化),并使用线网来连接各器件来描述一个模块的结构。这里的器件包括Verilog HDL的内置门如与门and,异或门xor等,也可以是用户自定义的一个模块,还可以是FPGA厂商的提供的一个基本逻辑单元或者宏。结构化的描述方式反映了一个设计的层次结构。

[例1]


                                                                         图1.一位全加器的结构图

代码:
module FA_struct (A, B, Cin, Sum, Cout);
input   A;
input   B;
input   Cin;
output  Sum;
output  Cout;
wire    S1, T1, T2, T3;

// -- statements -- //
xor     x1 (S1, A, B);
xor     x2 (Sum, S1, Cin);
and     A1 (T1, A, B );
and     A2 (T2, B, Cin);
and     A3 (T3, A, Cin);
or      O1 (Cout, T1, T2, T3 );

endmodule
      该实例显示了一个全加器由两个异或门、三个与门、一个或门构成。S1、T1、T2、T3则是门与门之间的连线。代码显示了用纯结构的建模方式,其中xor 、and、or 是Verilog HDL 内置的门器件。以 xor x1 (S1, A, B) 该例化语句为例:xor 表明调用一个内置的异或门,器件名称xor ,代码实例化名x1(类似原理图输入方式)。括号内的S1,A,B 表明该器件管脚的实际连接线(信号)的名称,其中 A、B是输入,S1是输出。其他同。

[例2]
                                                                    图2.两位全加器的结构图

代码:
module Four_bit_FA (FA, FB, FCin, FSum, FCout ) ;
parameter   SIZE = 2;
input   [SIZE-1:0]  FA;
input   [SIZE-1:0]  FB;
input               FCin;
output  [SIZE-1:0]  FSum;
output              FCout;
wire                FTemp;
FA_struct FA1(
.A      (FA[0]),
.B      (FB[0]),
.Cin    (FCin) ,
.Sum    (FSum[0]),
.Cout   (FTemp)
);
FA_struct FA2(
.A      (FA[1]),
.B      (FB[1]),
.Cin    (FTemp) ,
.Sum    (FSum[1]),
.Cout   (FCount )
);
endmodule
      该实例用结构化建模方式进行一个两位的全加器的设计,顶层模块Four_bit_FA 调用了两个一位的全加器 FA_struct 。在这里,以前的设计模块FA_struct 对顶层而言是一个现成的器件,顶层模块只要进行例化就可以了。注意这里的例化中,端口映射(管脚的连线)采用名字关联,如 .A (FA[2]) ,其中.A 表示调用器件的管脚A,括号中的信号表示接到该管脚A的电路中的具体信号。wire 保留字表明信号Ftemp 是属线网类型(下面有具体描述)。另外,在设计中,尽量考虑参数化的问题。器件的端口映射必须采用名字关联。

文中的代码可以直接用于综合,文中图片即为Synplify综合后的RTL View做过简单修改后的图像。为了获得和博文中一样的RTL View,必须将Synplify的Implementation Option->Disable I/O Insertion选中。

使用特权

评论回复
板凳
星星之火红|  楼主 | 2012-11-8 00:06 | 只看该作者
数据流的建模方式就是通过对数据流在设计中的具体行为的描述来建模。最基本的机制就是用连续赋值语句。在连续赋值语句中,某个值被赋给某个线网变量(信号),语法如下:
      assign [delay] net_name = expression;
      例如:
      assign #2 A = B;  


      在数据流描述方式中,还必须借助于HDL提供的一些运算符。如算术运算符:加(+)、减(-)等;关系运算符:大于(>),等于(==),不等于(!=)等等;按位逻辑运算符:逻辑与(&&),逻辑或(||)等;按位逻辑运算符:按位与(&)、按位或(|)等等;条件运算符:cond_expr ? expr1 : expr2;以及连接运算符:{expr1, expr2, . . .,exprN}。通过将这些运算符嵌入到连续赋值语句中,可以形成比较复杂的连续赋值语句,用来描述一些较复杂的线网变量的产生过程(即线网变量的行为)。
       继续以上一节中的一位全加器为例,介绍如何用数据流描述方式来来建模:
[例1]

                                                                            图1.一位全加器结构图
代码:
`timescale 1ns/100ps
module FA_flow(A,B,Cin,Sum,Cout)

input       A,B,Cin;
output    Sum, Cout;
wire       S1,T1,T2,T3;

assign #2 S1 = A ^ B;
assign #2 Sum = S1 ^ Cin;
assign #2 T1 = A & B;
assign #2 T2 = B & Cin;
assign #2 T3 = A & Cin ;
assign #2 Cout = T1|T2|T3;

endmodule


       注意,module内的各个assign 语句,是并行执行的,即各语句的执行与语句在module内出现的先后顺序无关。当assign语句右边表达式中的变量发生变化时,表达式的值会重新计算,并将结果赋值给左边的线网变量。如果赋值语句使用了时延,那么在等待时延结束后再将表达式的值赋给左边的线网变量。上例中每个assign语句都加了2个时间单位的时延,若右边表达式的值发生变化,assign语句左边的变量会在2个时间单位后获得右边表达式的新值。即,当信号A发生变化后,S1、T1、T3 也会跟着变化(A变化的2个时间单位后),S1的变化又会导致Sum的变化(A变化的4个时间单位后)。

数据流描述方式,相对行为描述方式而言,描述的都是比较简单的信号,不用类似于高级语言的高级语句(如if else,case等)就可以很容易的将信号的行为描述出来。而且通过数据流描述方式描述的电路,我们可以很容易的看出它的电路组成,如通过几个与门或者几个异或门,不需要经过很复杂的分析就可得知。对于行为描述方式,由于嵌入了大量的高级语句,我们很容易理解电路的行为,却不容易一下子看出电路的结构来。

使用特权

评论回复
地板
星星之火红|  楼主 | 2012-11-8 00:07 | 只看该作者
行为描述方式是指通过对信号的行为进行描述来建模。在表示方面,类似数据流建模方式,但一般是把用initial 块语句或always 块语句描述的归为行为建模方式。行为描述方式中,我们不关心电路使用到哪些基本逻辑单元(如逻辑门、厂商的基本逻辑单元LUT等),也不关心这些基本逻辑单元最终是怎么连起来的,只关心电路具有什么样的功能。为了达到这个目的,行为描述建模方式中使用了大量的类似C语言的高级语句,如if else、case、for、while等等,可以很方便的用简洁的代码描述复杂的电路。 task(任务)和function(函数)也属于行为描述方式建模。
      和数据流建模方式一样,行为建模方式也需要使用到各种运算符,如算术运算符:加(+)、减(-)等;关系运算符:大于(>),等于(==),不等于(!=)等等;按位逻辑运算符:逻辑与(&&),逻辑或(||)等;按位逻辑运算符:按位与(&)、按位或(|)等等;条件运算符:cond_expr ? expr1 : expr2;以及连接运算符:{expr1, expr2, . . .,exprN}。将各种运算符和高级语句配合起来使用,可以对复杂的电路进行抽象建模,只关心电路的功能,不关心电路的具体实现。电路的具体实现过程由软件自动完成。
      采用行为描述的建模方式,对电路的描述效率更高,可以用简洁的语句描述复杂的逻辑电路。下面还是以一位全加器为例,简单介绍行为建模的基本方式,让大家有一个概念。行为建模的具体细节以及一些高级语句的介绍放在后续专门的章节中介绍。

[例1]
                                                                            图1.一位全加器结构图(行为建模1)
代码:
module FA_behav1(A, B, Cin, Sum, Cout );
input       A,B,Cin;
output    Sum,Cout;
reg         Sum, Cout;
reg         T1,T2,T3;

always@ ( A or B or Cin )
begin
      Sum = (A ^ B) ^ Cin ;
      T1 = A & B ;
      T2 = B & Cin;
      T3 = A & Cin;
      Cout = (T1| T2) | T3;
end

endmodule
      上例中,出现了一个行为建模中的关键字:reg、always、begin end。对于行为建模方式,以下几点概念必须建立:
             1.只有寄存器类型的信号才可以在always和initial 语句中进行赋值,类型定义使用reg关键字声明。由于信号默认的类型
                是wire,所以reg型变量必须在module中声明。

             2.always 语句是一直重复执行的,由敏感表(always 语句括号内的变量)中的变量触发,每次敏感列表中的变量发生
                变化时,always语句内部的各个语句都要重新执行一次。

             3.always 语句从0 时刻开始。
             4.在begin 和end 之间的语句是顺序执行,属于串行语句。


[ 例2]
                                                                        图2.一位全加器结构图(行为建模2)

代码:
module FA_behav2(A, B, Cin, Sum, Cout );
input        A,B,Cin;
output     Sum,Cout;
reg          Sum, Cout;

always@ ( A or B or Cin )
begin
      {Cout ,Sum} = A + B + Cin ;
end

endmodule


        在例2中,采用更加高级(更趋于行为级)描述方式,即直接采用“+”来描述加法。{Cout,Sum}表示对位数的扩展,因为两个1bit 相加,和有两位,低位放在Sum 变量中,进位放在Cout 中。
行为描述方式可以使用一些类似于C语言的高级语句,例如if else和case等。一段使用case语句的进行行为描述方式建模的代码,如果改为用数据流描述方式来实现(假设都是组合逻辑,组合逻辑可用数据流描述方式和行为描述方式建模,时序逻辑只能用行为描述方式建模),代码量会剧增,可读性也会很差。行为描述方式体现了verilog HDL的精髓和强大的建模能力,同时代码具有很好的可读性和抽象性,体现了高级语言的优点。当然,结构建模方式和数据建模方式也是必不可少的,应该根据需要合理的搭配使用三种不同的方式进行建模。


使用特权

评论回复
5
星星之火红|  楼主 | 2012-11-8 00:07 | 只看该作者
    在前三节分别介绍了结构描述方式、数据流描述方式、行为描述方式这三种建模手段,在实际的设计中,往往是将这三种描述方式混合起来使用。一般而言,对顶层模块,会更多的采用结构描述方式,通过对低层模块的调用来描述顶层模块的功能,当然也会使用到数据流建模和行为建模来描述一些简单的功能,对于一些复杂的逻辑一般是放在低层模块中来实现。对于底层模块,更多的是采用数据流和行为描述方式来描述模块的功能,也会通过调用其它更低层模块来实现更细化的功能。
     下面举例说明使用三种方式混合建模:



[例1]

图1.两输入4bits数值比较器
代码:
`timescale 1 ns / 1 ps

//module function is:if data < datb,Value =0;else Value = 1.
module compare(Data,Datb,Value);

input   [3:0]   Data;
input   [3:0]   Datb;
output          Value;
reg               Value;
wire              Value0;
wire              Value1;
wire              Value2;
wire              Value3;

assign Value0 = (Data[0] ^ Datb[0]) ? Data[0] : 1'b1;
assign Value1 = (Data[1] ^ Datb[1]) ? Data[1] : 1'b1;
assign Value2 = (Data[2] ^ Datb[2]) ? Data[2] : 1'b1;
assign Value3 = (Data[3] ^ Datb[3]) ? Data[3] : 1'b1;

always@(Value0 or Value1 or Value2 or Value3)
begin
    if(Value0 == 1'b1)
        Value = 1'b1;
    else if(Value1 == 1'b1)
        Value = 1'b1;
    else if(Value2 == 1'b1)
        Value = 1'b1;
    else if(Value3 == 1'b1)
        Value = 1'b1;
    else
        Value = 1'b0;
end

endmodule



      上例中,通过数据流描述方式和行为描述方式,描述了一个二输入4bits数值比较器。二输入4bits数值比较器的功能为:当Data大于等于Datb时,Value为1;否则Value为0。作为一个底层module,compare模块实现的是一个具体的、较小的功能,不需要例化其他更低层模块。compare模块内部,简单的逻辑可以用数据流描述方式来实现,相对复杂一些的逻辑由行为描述方式来实现,相互搭配使用,使得代码不仅可读性好,而且简洁。



[例2]
                                                                       图2.三输入求最大最小值差额
代码:
//calculate the the result of the max data and min dat
module calc(Dat0,Dat1,Dat2,Result);

input   [3:0]   Dat0;
input   [3:0]   Dat1;
input   [3:0]   Dat2;
output  [3:0]  Result;
reg     [3:0]    MaxDat;
reg     [3:0]    MinDat;
wire               CmpResult0;
wire               CmpResult1;
wire               CmpResult2;

compare U_compare_0(
        .Data (Dat0),
        .Datb (Dat1),
        .Value(CmpResult0));

compare U_compare_1(
        .Data (Dat0),
        .Datb (Dat2),
        .Value(CmpResult1));

compare U_compare_2(
        .Data (Dat1),
        .Datb (Dat2),
        .Value(CmpResult2));

always@(CmpResult0 or CmpResult1 or CmpResult2 or Dat0 or Dat1 or Dat2)
begin
    case({CmpResult0,CmpResult1,CmpResult2})
        3'b110  : MaxDat = Dat0;
        3'b111  : MaxDat = Dat0;
        3'b001  : MaxDat = Dat1;
        3'b011  : MaxDat = Dat1;
        3'b000  : MaxDat = Dat2;
        3'b100  : MaxDat = Dat2;
        default  : MaxDat = Dat0;
    endcase
end

always@(CmpResult0 or CmpResult1 or CmpResult2 or Dat0 or Dat1 or Dat2)
begin
    case({CmpResult0,CmpResult1,CmpResult2})
        3'b000  : MinDat = Dat0;
        3'b001  : MinDat = Dat0;
        3'b100  : MinDat = Dat1;
        3'b110  : MinDat = Dat1;
        3'b011  : MinDat = Dat2;
        3'b111  : MinDat = Dat2;
        default  : MinDat = Dat2;
    endcase
end

assign Result = MaxDat - MinDat;
endmodule



         上例中实现的功能是三数据输入计算最大最小值的差值,模块内三种建模方式都使用到了。通过例化三个数值比较器(结构化建模),比较出三个数值的相对大小,然后通过分析数值比较的结果获得最大值和最小值(使用行为描述方式),最后使用数据流描述方式完成减法运算。通过上次可以看出,对于相对复杂的逻辑,用行为描述方式更为方便,而对于简单的逻辑,使用数据流描述方式就可以了。灵活的应用三种建模方式,就可以写出高效、易读、简洁的代码了。

使用特权

评论回复
6
hero2009nj| | 2013-10-10 15:24 | 只看该作者
谢谢楼主分享。

使用特权

评论回复
7
llf021421| | 2013-10-11 23:50 | 只看该作者
谢谢分享!

使用特权

评论回复
8
yghanwuji| | 2013-10-12 12:18 | 只看该作者
学习了!

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

101

主题

1782

帖子

22

粉丝