0%

中科院实习—— Verilog语言重点内容解析

Verilog语言强化。包括可综合语句与不可综合语句、initial与always语句、组合语句与时序逻辑 、assign语句与always语句。

Verilog语言重点内容解析

可综合语句与不可综合语句

Verilog硬件描述语言有很完整的语法结构和系统,类似高级语言,这些语法结构的应用给我们的设计描述带来很多方便。但是,我们知道,Verilog是描述硬件电路的,它是建立在硬件电路的基础上的。有些语法结构是不能与实际硬件电路对应起来的,也就是说我们在把一个语言描述的程序映射成实际硬件电路中的结构时是不能实现的,这些语句称为不可综合语句,反之为可综合

initial与always语句

与C语言不通,verilog在本质上是并发而非顺序的。verilog中的各个执行流程(进程)并发执行,而不是顺序执行的。每个initial语句和always语句代表一个独立的执行过程,每个执行过程从仿真时间0开始执行并且两种语句不能嵌套使用。下面举例解释之:

所有的initial语句内的语句构成了一个initial块。initial块从仿真0时刻开始执行,在整个仿真过程中只执行一次。如果一个模块中包括了若干个initial块,则这些initial块从仿真0时刻开始并发执行,且每个块的执行是各自独立的。如果在块内包含了多条行为语句,那么需要将这些语句组成一组,一般式使用关键字begin和end将他们组合在一个块语句;如果块内只有一条语句,则不必使用begin和end.下面给出了initial语句的例子:

module stimulus
 
reg x,y, a,b, m
 
initial
    m = 1'b0;
    
initial
begin
    #5 a = 1'b1;
    #25 b = 1'b0;
end
 
initial
begin
    #10 x = 1'b0;
    #25 y = 1'b1;
end
 
initial
    #50 $finish;
    
endmodule

在上面例子中,三条initial语句在仿真0时刻开始并行执行。如果在某一条语句前面存在延迟#,那么这条initial语句的仿真将会停顿下来,在经过指定的延迟时间之后再继续执行。因此上面的initial语句执行顺序为:

时间				所执行的语句
0					m = 1'b0;
5					a = 1'b1;
10					x = 1'b0;
30					b = 1'b0;
35					y = 1'b1;
50					$finish;

initial块常用于测试文件和虚拟模块的编写,用来产生仿真测试信号和设置信号记录等仿真环境。它是一种不可综合的语句

always语句在仿真过程中是不断重复执行的。
image.png
其声明格式如下:

always <时序控制>  <语句>   

always语句由于其不断重复执行的特性,只有和一定的时序控制结合在一起才有用。如果一个always语句没有时序控制,则这个always语句将会发成一个仿真死锁。见下例:
[例1]:

always areg = ~areg;  

这个always语句将会生成一个0延迟的无限循环跳变过程,这时会发生仿真死锁。如果加上时序控制,则这个always语句将变为一条非常有用的描述语句。见下例:
[例2]:

always #10  areg = ~areg;  

这个例子生成了一个周期为20 的无限延续的信号波形,常用这种方法来描述时钟信号,作为激励信号来测试所设计的电路。

[例3]:

reg[7:0] counter;
reg tick;

always @(posedge areg) 
    begin
        tick = ~tick;
        counter = counter + 1;
    end 

这个例子中,每当areg信号的上升沿出现时把tick信号反相,并且把counter增加1。这种时间控制是always语句最常用的。

always 的时间控制可以是沿触发也可以是电平触发的可以单个信号也可以多个信号,中间需要用关键字 or 连接,如:

 always @(posedge clock or posedge reset) //由两个沿触发的always块
 begin
 ……
 end
 always @( a or b or c ) //由多个电平触发的always块
 begin
 ……
 end  

沿触发的always块常常描述时序逻辑,如果符合可综合风格要求可用综合工具自动转换为表示时序逻辑的寄存器组和门级逻辑,而电平触发的always块常常用来描述组合逻辑和带锁存器的组合逻辑,如果符合可综合风格要求可转换为表示组合逻辑的门级逻辑或带锁存器的组合逻辑。一个模块中可以有多个always块,它们都是并行运行的

always是一个极高频的语法,always@()用法总结如下
① always@(信号名)

• 信号名有变化就触发事件

例:

always@( clock) 
a=b;  

② always@( posedge信号名)

• 信号名有上升沿就触发事件

例:

always@( posedge clock) 
a=b; 

③ always@(negedge信号名)

• 信号名有下降沿就触发事件

例:

always@( negedge clock) 
a=b; 

④ always@(敏感事件1or敏感事件2or…)

• 敏感事件之一触发事件

• 没有其它组合触发

例:

always@(posedge reset or posedge clear) 
reg_out=0;  

⑤ always@(*)

• 无敏感列表,描述组合逻辑,和assign语句是有区别的

例:

always@(*) 
b= 1'b0;  

assign赋值语句和always@(*)语句。两者之间的差别有:

1.被assign赋值的信号定义为wire型,被always@( * )结构块下的信号定义为reg型,值得注意的是,这里的reg并不是一个真正的触发器,只有敏感列表为上升沿触发的写法才会综合为触发器,在仿真时才具有触发器的特性。

2.另外一个区别则是更细微的差别:举个例子,

wire a;
reg b;

assign a = 1'b0;

always@(*)
b= 1'b0;  

在这种情况下,做仿真时a将会正常为0,但是b却是不定态。这是为什么?verilog规定,always@( * )中的 * 是指该always块内的所有输入信号的变化为敏感列表,也就是仿真时只有当always@( * )块内的输入信号产生变化,该块内描述的信号才会产生变化,而像always@( * )b = 1’b0;这种写法由于1’b0一直没有变化,所以b的信号状态一直没有改变,由于b是组合逻辑输出,所以复位时没有明确的值(不定态),而又因为always@( * )块内没有敏感信号变化,因此b的信号状态一直保持为不定态。事实上该语句的综合结果有可能跟assign一样,但是在功能仿真时就差之千里了

组合逻辑的assign和always语句

组合语句与时序逻辑

组合逻辑电路

特点:是任意时刻的输出仅仅取决于当前时刻的输入,与电路之前的历史状态无关(即无记忆能力)

组合逻辑电路的设计通常包含以下几个步骤:

  • 进行逻辑抽象。分析事件的因果关系,确定输入变量和输出变量,列出输入变量和输出变量的逻辑真值表。
  • 写出逻辑函数。将真值表转换为对应的逻辑函数式,或者直接画出卡诺图,然后使用第三章中介绍的卡诺图将逻辑函数进行化简。
  • 根据化简后的逻辑函数,画出逻辑电路图。
    

时序逻辑电路

时序逻辑电路的输出不仅取决于当前的输入,还取决于电路的历史状态。 因此我们需要一种元件能保存电路的状态信息。如果一个元件带有内部存储功能,它就包含状态,也称之为状态单元(State Element)

  • 锁存器:锁存器在E的高(低)电平期间对信号敏感
  • 触发器:触发器在CP的上升沿(下降沿)对信号敏感

共同点:
具有0 和1两个稳定状态,一旦状态被确定,就能自行保持。一个锁存器或触发器能存储一位二进制码。

不同点:
锁存器—对脉冲电平敏感的存储电路,在特定输入脉冲电平作用下改变状态
触发器—对脉冲边沿敏感的存储电路,在时钟脉冲的上升沿或下降沿的变化瞬间改变状态

组合逻辑的assign和always语句

always 是“一直、总是”的意思, @后面跟着事件。整个 always 的意思是:当敏感事件的条件满足时,就执行一次“程序语句”。敏感事件每满足一次,就执行“程序语句”一次。 (敏感事件中的敏感条件出现变化时,执行always条件循环语句中的内容)
image.png
这段程序的意思是: 当信号 a 或者信号 b 或者信号 d 发生变化时,就执行一次下面语句。在执行该段语句时,首先判断信号 sel 是否为 0,如果为 0,则执行第 3 行代码。 如果 sel 不为 0,则执行第 5 行代码。需要强调的是, a、 b、 c 任意一个发生变化一次, 2 行至 5 行也只执行一次,不会执行第二次。

此处需要注意,仅仅 sel 这个信号发生变化是不会执行第 2 行到 5 行代码的, 通常这并不符合设计者的想法。例如,一般设计者的想法是: 当 sel 为 0 时 c 的结果是 a+b;当 sel 不为 0 时 c 的结果是 a+d。但如果触发条件没有发生改变, 虽然 sel 由 0 变 1, 但此时 c 的结果仍是 a+b。 因此, 这并不是一个规范的设计思维。

因此,按照设计者的想法重新对代码进行设计:当信号 a 或者信号 b 或者信号 d 或者信号 sel发生变化时,就执行 2 行至 5 行。这样就可以确保 sel 信号值为 0 时, c 的结果一定为 a+b, 当 sel 不为 0 时, c 的结果一定是 a+d。 因此要在敏感列表中加入 sel, 其代码如下所示。
image.png
当敏感信号非常多时很容易就会把敏感信号遗漏,为避免这种情况可以用“ * ”来代替。这个“ *”是指“程序语句”中所有的条件信号, 即 a、 b、 d、 sel(不包括 c) , 也推荐这种写法,其具体代码如下所示。
image.png
这种条件信号变化结果立即变化的 always 语句被称为“组合逻辑”。

电路延时

在实际电路中存在两种延迟,惯性延迟 (Inertial delay) 和传导延迟 (Transport delay)

惯性延迟
定义:若元件的输入信号的脉冲宽度小于一定值时,元件的输出没有响应,也就是说元件具有一定的惯性。
产生原因:当脉冲到达时,由于脉冲宽度小于元件本身的延迟,当脉冲结束时,元件的新输出还未建立起来。考虑了电路中存在的大量分布电容。

传导延迟
定义:输入信号变化到对应输出信号变化经过的时间,类似于物理传输线的延迟。
产生原因:载流子运动的速度有限,通过导线需要一定的时间。

Verilog 中的时序模型

在分析 Verilog HDL 的仿真行为前,我们需要了解 Verilog 中时序模型。
时序模型分为:门级时序模型、过程时序模型。

门级时序模型
适用范围:所有的连续赋值语句、过程连续赋值语句、门原语、用户自定义原语。
特点:
任意时刻输入发生变化,将重新计算输出。
当之前的的事件未执行完毕时又发生的新的变化,则会撤销之前的事件,开始新的事件。

过程时序模型

适用范围:过程语句。
特点:
当敏感列表发生变化时触发执行。
当之前的的事件未执行完毕时又发生的新的变化,则不撤销原有事件,同时开始新的事件,如果同时有几个更新事件,它们的执行顺序是不确定的。

Verilog 中的仿真延迟语句

Verilog 中的仿真延时语句为 #n,n 表示延时时间,将该语句加在语句中,延迟 n 个时间单位。
延时的添加方法有两种:正规延迟和内定延迟
正规延迟 (#在外面)
#5 C = A +B

在 T 时刻执行到该语句时,等待 5 个时间单位,然后计算等号右边的值赋给 C,此时使用的 A B 的值是 T+5 时刻的值。

内定延迟 (#在里面)

C = #5 A +B  

在 T 时刻执行到该语句时,先计算 A+B 的值,计算后等待 5 个时间单位将值赋给 C,使用的 A B 的值是 T 时刻的值。

阻塞赋值与非阻塞赋值

阻塞(blocking)赋值方式(如 b=a)
①赋值语句立即执行,执行完毕后才执行下一条语句(即为阻塞的含义,依次顺序执行);

②左侧值在赋值语句执行完后立即改变

非阻塞(Non_blocking)赋值方式(如 b<=a)

①语句执行到此时,先计算“<=”右侧值,但不立即赋值给左侧;
②always块结束后才完成此次赋值操作;
③这是时序逻辑模块最常用的赋值方法。

在 Verilog 建模中增加延时

// 1. 连续赋值+ 正规延迟
assign #5 C = A +B;

// 2. 连续赋值+ 内定延迟
assign C = #5 A +B;

// 3. 阻塞赋值 + 正规延迟
always @(*) begin
    #5 C = A +B;
end

// 4. 阻塞赋值 + 内定延迟
always @(*) begin
    C = #5 A +B;
end

// 5. 非阻塞赋值 + 正规延迟
always @(*) begin
    #5 C <= A +B;
end

// 6. 非阻塞赋值 + 内定延迟
always @(*) begin
    C <= #5 A +B;
end

下面对这六种方式逐一分析:

1.连续赋值+ 正规延迟
在 T 时刻执行到该语句时,等待 5 个时间单位,然后计算等号右边的值赋给 C1。
使用的 A B 的值是 T+5 时刻的值。
若在等待过程中 A B 的值发生变化再次触发 assign block,根据 assign block 的门级时序模型特点,仿真器会撤销先前的等待事件,然后重新执行语句。
当变化脉冲小于 5 个时间单位时,等待事件会被撤销,该脉冲将不起作用。
仿真结果
image.png
可以看出在 6ns、8ns、9ns、10ns 时刻 A 发生了变化,但皆因持续时间小于 5ns,所以都没有对 C1 产生影响,只有在 12ns 和 18ns 的变化持续时间超过 5ns,作用到 C1 上。
2.连续赋值+ 内定延迟
在 T 时刻执行到该语句时,计算等号右边的值,等待 5 个时间单位后赋给 C2。
使用的 A B 的值是 T 时刻的值。
若在等待过程中 A B 的值发生变化再次触发 assign block,根据 assign block 的门级时序模型特点,仿真器会撤销先前的等待事件,然后重新执行语句。
当变化脉冲小于 5 个时间单位时,等待事件会被撤销,该脉冲将不起作用。
该种方式有记忆属性,与连续赋值原则不符,为非法语句,编译不能通过
3.阻塞赋值 + 正规延迟
在 T 时刻执行到该语句时,等待 5 个时间单位,然后计算等号右边的值阻塞赋给 C3。
使用的 A B 的值是 T+5 时刻的值。
若在等待过程中 A B 的值发生变化再次触发 always block,根据 always block 的过程时序模型特点,此时还在等待过程,always 语句还未执行结束,不会开始新一轮的事件,仿真器不会对敏感列表反应。
仿真器忽略延迟时间段的数据变化。
仿真结果
image.png
在 6ns 时刻 A 的值发生变化,always block 开始执行,经过 5ns,用 11ns 时刻的 A B 的值计算出结果赋给 C3,而忽略了 8ns 和 10ns 时刻的变化。
4.阻塞赋值 + 内定延迟
在 T 时刻执行到该语句时,计算等号右边的值,等待 5 个时间单位后赋给 C4。
使用的 A B 的值是 T 时刻的值。
若在等待过程中 A B 的值发生变化再次触发 always block,根据 always block 的过程时序模型特点,由于赋值方式为阻塞赋值,此时 always 语句还未执行结束,不会开始新一轮的事件,仿真器不会对敏感列表反应。
仿真器忽略延迟时间段的数据变化。
仿真结果:
image.png
在 6ns 时刻 A 的值发生变化,always block 开始执行,经过 5ns,用 6ns 时刻的 A B 的变化后的值计算出结果赋给 C4,而忽略了 8ns 和 10ns 时刻的变化。
5.非阻塞赋值 + 正规延迟
在 T 时刻执行到该语句时,等待 5 个时间单位,然后计算等号右边的值非阻塞赋给 C5。
使用的 A B 的值是 T+5 时刻的值。
若在等待过程中 A B 的值发生变化再次触发 always block,根据 always block 的过程时序模型特点,此时还在等待过程,always 语句还未执行结束,不会开始新一轮的事件,仿真器不会对敏感列表反应。
仿真器忽略延迟时间段的数据变化。
仿真结果:
image.png
在 6ns 时刻 A 的值发生变化,always block 开始执行,经过 5ns,用 11ns 时刻的 A B 的值计算出结果赋给 C5,而忽略了 8ns 和 10ns 时刻的变化。
6.非阻塞赋值 + 内定延迟
在 T 时刻执行到该语句时,计算等号右边的值,等待 5 个时间单位后赋给 C6。
使用的 A B 的值是 T 时刻的值。
若在等待过程中 A B 的值发生变化再次触发 always block,根据 always block 的过程时序模型特点,由于赋值方式为非阻塞赋值,将赋值事件放进事件队列后,always 语句执行结束,等待下一次的触发,触发来到时开始新一轮的事件。
仿真器接受延迟时间段的数据变化,输入的变化延迟会全部反应在输出上。
仿真结果:
image.png
A B 的每一次变化都触发 always block 的执行,每一次变化都延时 5ns 后反应在 C6 上。

下图为 always block 中的四种延时方式的仿真流程:
image.png

-------------本文结束感谢您的阅读-------------