更新于 

行为建模

在SystemVerilog HDL中,行为建模是指将数字逻辑电路的功能以较高的抽象形式描述出来,他通过输入和输出之间的因果关系直接建立电路模型,行为建模包括两种描述风格:

  • 基于持续赋值语句(assign)的建模
  • 基于过程块(always和initial)的建模

基于过程块语句的建模相比基于持续赋值语句的建模具有更高的抽象层次,编程也更加便捷。

基于持续赋值语句的建模

基于持续赋值语句的建模是指根据信号量之间的逻辑关系,采用持续赋值语句(assign)描述数字逻辑电路的方式,也称为数据流建模,其使用方法如下:

1
assign <#延迟量> 信号名=逻辑表达式//<#延迟量>可以缺省

比如:

1
2
3
4
logic [3:0]out1,out2,A,B;
assign out1=A+B;
//经过5个单位时间延迟后赋值给out2
assign #5 out2=~(A&B);

我们注意到行为建模语句主要是基于已经定义的变量,来定义输入变量和输出变量之间的某种关系,即为行为建模。基于持续赋值语句的建模的特点是只要“=”右侧表达式中的任意变量发生变化,那么这个表达式就会立即重新计算并赋值给左边的变量。如果定义了延迟量,那么赋值将在相应的单位时间内(默认为纳秒,ns)后再完成。

一定要注意延迟量主要用于仿真,是不可以综合的。

并且持续赋值语句左侧可以是变量类型(如logic)的信号,也可以是线网类型(如tri)信号,也可以是信号的拼接形式。对于持续赋值语句,任何输入的变化都会立即影响输出结果,体现了组合逻辑电路的特征,即变化瞬时性,因此,基于持续赋值语句的建模只能用来描绘组合逻辑电路。而且基于持续赋值语句的建模方式提供了使用逻辑表达式描述电路的一种方式,不必考虑电路的组成结构以及元组之间的连接。

刚刚上面我们已经给出了基于持续赋值语句的建模例子了,建模语句主要是用来描述信号之间的行为关系,下面我们给出基于持续赋值语句的建模模板:

1
2
3
4
5
6
7
8
9
10
module 模块名 (端口列表);
//中间变量声明
logic 信号1,信号2...

//逻辑功能定义
assign 赋值语句1;
assign 赋值语句2;
...
assign 赋值语句n;
endmodule

下面我们以一道例题来讲解如何进行基于赋值语句的建模,我们这里以译码器为例,我们前面学习过译码器是根据接收的信号所组成的编号,从而让特定的输出信号输出高电平真值。比如2线-4线译码器:

我们可以根据真值表列出不同信号取真值的表达式(简单的当然也可以使用卡诺图进行简化),然后定义译码器模块来进行建模:

1
2
3
4
5
6
7
8
9
module dec2to4(input EN,A,output Y);
logic EN;
logic [1:0] A;
logic [3:0] Y;
assign Y[0]=EN&~A[1]~A[0];
assign Y[1]=EN&~A[1]&A[0];
assign Y[2]=EN&A[1]&~A[0];
assign Y[3]=EN&A[1]&A[0];
endmodule

当然我们还可以通过这个方法来实现机组原理中讲到的一位全加器:

他的建模语言代码如下:

1
2
3
4
5
6
7
module fulladder(A,B,cin,sum,cout);
input logic A,B,cin;
output logic sum,cout;

assign sum=A^B^cin;
assign cout=(A^B)&cin;
endmodule

如果我们在基于持续赋值建模中的代码中使用了延迟量,那么虽然最后得到的电路完全相同,但是在仿真综合时是会出现不同的结果的,我们前面学习了最终仿真综合平台的结果会以脉冲的形式显示在图上,那么当增加了延迟后,脉冲出现的时间就会发生改变,如下:

未加延迟量的结果:

增加了延迟量的结果:

基于过程块的建模

前面我们介绍了基于持续赋值语句的建模方式,接下来我们来学习一下另一种建模方式–基于过程块的建模。基于过程块的建模关注数字逻辑电路输入输出的因果关系(行为特性),即在何种输入条件下,产生何种输出(即完成什么操作),并不关注电路的内部结构细节。这种建模适用于规模庞大、复杂的电路设计,配合EDA工具,构成了现代超大规模集成电路(VISI)的设计基础。

基于过程块的建模使用关键字initial和always定义,通过块标识符begin…end(相当于大括号)包围起来的过程块对电路进行描述。initial主要用于仿真验证,always则主要用于电路建模,也可以用于仿真。always过程块是一个无限循环,每一个always块描述了一个独立的电路功能。

always过程块分为三中类型:always_comb(描述组合逻辑),always_ff和always_latch(描述时序逻辑)。这里我们主要关注always_comb。他的代码模板如下:

1
2
3
4
5
6
7
8
9
10
module 模块名 (端口列表);
//中间变量声明(如果需要)
logic 信号1,信号2,...,信号n;

//逻辑功能定义(过程块)
always_comb begin
过程赋值语句
高级语言结构
end
endmodule

基于过程块的建模最重要的就是一定记住块标识符类型的声明以及begin…end包裹。这里我们给出二路选择器的建模板子:

我们发现上面的代码中是使用条件语句结构推动描述某几个信号之间的因果关系来进行行为建模的,但是仅仅使用条件语句明显是无法完成建模行为描述的,他也可以像基于持续赋值语句建模一样使用过程赋值语句即对某些变量信号进行赋值,但是他不需要使用assign声明,并且“=”左边的信号必须是变量类型(如logic类型),并且不能是线网类型,“=”右边的信号的类型无限制。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module adder(input a,b,cin,output[1:0] out);
logic half_sum,half_carry;
always_comb begin
//正确
half_sum=a^b^cin;
//正确
half_carry=a&b|a&~b&cin|~a&b&cin;
//错误,端口信号如果不显示声明为变量类型
//那么默认为wire类型,即线网类型
out={half_carry,half_sum};
//下面的是正确的语句
logic out;
out={half_carry,half_sum};
end
endmodule

阻塞赋值

在之前一讲的最后我们学习到了基于过程块的建模中的过程赋值语句,在SystemVerilog中,过程赋值语句可以分为两类:阻塞赋值(=)和非阻塞赋值(<=),其格式如下面所示。其中延迟量意义不变,也是不可以综合的。阻塞语句用来描述组合逻辑电路,非阻塞赋值用于描述时序逻辑电路。

1
2
#5 out =a&b;//阻塞赋值
out[3:0]<={b[2:0],1'b1};//非阻塞赋值

阻塞语句在该语句结束后就会立即完成赋值操作,如果在一个过程块有多条阻塞赋值语句,在前面的赋值语句没有完成之前,后面的赋值语句就不能执行,仿佛被阻塞了一般、由此可以,阻塞赋值中输入的变化会立即影响输出,故用于描述组合逻辑电路。

思考:阻塞语句貌似是串行的,这和SystemVerilog的并行性不冲突吗?

不冲突,两者强调的方面不一样。并行性是指语句的执行之前没有串行机制,因此假设

1
2
logic a=5;
logic b=7;

那么两个指令是并行执行的,没有变量的先后创建之分,这是并行性,他值得是语句的执行没有串行机制。但是对于一个变化来说,一定是前面的先变化,然后后面的赋值语句再接收到前面的变化后,在执行这个变化,因为对于后面的语句来说变化在未传达之前是不可预知的,因此必须先等待前面的赋值语句做出改变。

思考:阻塞语句和非阻塞语句的区别?

其实就是组合逻辑电路与时序逻辑电路的区别。我们前面学习了时序逻辑电路并不是接收到变化后立刻发生改变,而是需要等待所有的条件全部具备以后在发生变化,因此非阻塞语句就是等待全部变化赋值以后才可以执行。而阻塞语句就是接收到改变立刻修改赋值的语句,只能用于逻辑组合电路。

很明显上面额y想要变化,首先需要a和b更新赋值才可以。这就是逻辑上的串行,但是同时这几条指令是同时执行的,即a和b时同时发生变化的,因此是并行的,当y接收到a和b的变化后会立刻发生改变。

分支结构

在SystemVerilog中,分支结构有if…else语句和case语句。If…else语句是可综合的,主要用于生成多路选择器,其格式如下。if…else语句支持多层嵌套,可以使用begin…end增加可读性(类似于大括号)。

一定要注意在SystemVerilog中使用if…case语句尽量要考虑所有的条件(完整分支),即所有的情况都进行处理,这样才能产生组合逻辑电路,否则将综合出带有锁存器的时序电路。

也就是说即使某些情况我们不需要进行任何操作,最好也要加以讨论,如:

我们发现水位最高位14m,而我们使用的是4位表示,那么最高可以表示15,即使15的时候什么等也不亮,我们最好也加上一个灭灯的操作使得讨论完整。即总是要加上一个else总是好的。不要像写oj题一样只写if不写else。

同样的case语句也是一种分支结构语句,也是可以综合的,他主要用于生成多路选择器、译码器等。格式如下,同样的,对于case尽量也要考虑全面,这样才能产生组合逻辑电路,否则,将综合出带有锁存器的时序电路。也就是说,最后要加上default使得讨论完整。

并且某个分支项item_expr中的某位无关值,用?表示,那么该位的比较就不予考虑,即意味着比较结过永远为“真”。如下图:

下面我们来看一下如果未讨论完整所有的情况,那么综合时就会出现锁存器。

对于上面将2位的4中情况全部都讨论了,那么没事正常运行。如果出现下面这种少讨论的情况,那么就会出现锁存器,实际上锁存器很好理解。EN是使能端,只有EN为1时,输出端才会随着输入端立刻变化,即如果是a,b,c的某一种情况,那么Dout也会瞬间会根据不同的情况输出相对应的值,但是如果是未讨论的d情况出现了,那么此时EN会变为0,但是此时他和三态缓冲器不同,他的输出端并不是变为浮空,而是被锁住一直维持最后一次的输出状态,即如果在d情况之前Dout一直输出的是a的输出信号,那么锁住以后就一直维持输出a情况的输出信号直至d情况结束。如果d情况之前Dout一直输出的是c的输出信号,那么此时就会一直维持c情况的输出。

循环结构

在SystemVerilog中,循环结构主要包括for、repeat、while和forever。

  • for:满足条件表达式时执行(和C一样)
  • repeat:直接循环预先给定的次数
  • while():满足条件时执行
  • forever:一直循环下去

在这4种循环语句中,for语句是可综合的,可以用于数字电路的建模,其他三种语句多用于仿真当中,不一定能被综合工具支持。

注意这里的循环变量i等一定要设置为int或者其他的二态变量型,而不要使用logic向量型,否则会造成死循环。

行为建模总结

在行为建模中我们分别学习了基于持续赋值语句的建模和行为建模两个不同风格的建模。这里做一个总结:

  • 基于持续赋值语句的建模只能用于描述组合逻辑电路
  • 在过程块中,过程赋值“=”左边的信号必须是变量类型,不能是线网类型,而对右边的表达式则没有任何限制。
  • 基于过程块的组合逻辑电路建模可以通过always_comb和阻塞赋值语句完成
  • 基于过程块的组合逻辑电路,使用if…else和case…语句时要特别注意需要列出所有可能的条件,否则,综合得到的将不是组合逻辑电路。(对于case,必要时不忘记default语句)
  • 基于持续赋值语句的建模方式和基于过程块的建模方式在一个模块设计中可以混用,并且没有顺序关系。