关键词:时延控制,事件触发,边沿触发,电平触发
Verilog 提供了 2 大类时序控制方法:时延控制和事件控制。事件控制主要分为边沿触发事件控制与电平敏感事件控制。
时延控制
基于时延的时序控制出现在表达式中,它指定了语句从开始执行到执行完毕之间的时间间隔。
时延可以是数字、标识符或者表达式。
根据在表达式中的位置差异,时延控制又可以分为常规时延与内嵌时延。
常规时延
遇到常规延时时,该语句需要等待一定时间,然后将计算结果赋值给目标信号。
格式为:#delay procedural_statement,例如:
reg value_test ;
reg value_general ;
#10 value_general = value_test ;
该时延方式的另一种写法是直接将井号 #
#10 ;
value_ single = value_test ;
内嵌时延
遇到内嵌延时时,该语句先将计算结果保存,然后等待一定的时间后赋值给目标信号。
内嵌时延控制加在赋值号之后。例如:
reg value_test ;
reg value_embed ;
value_embed = #10 value_test ;
需要说明的是,这 2 种时延控制方式的效果是有所不同的。
当延时语句的赋值符号右端是常量时,2 种时延控制都能达到相同的延时赋值效果。
当延时语句的赋值符号右端是变量时,2 种时延控制可能会产生不同的延时赋值效果。
例如下面仿真代码:
实例
`timescale 1ns/1ns
module test ;
reg value_test ;
reg value_general, value_embed, value_single ,cc;
//signal source
initial begin
value_test = 0 ;
#25 ; value_test = 1 ;
#35 ; value_test = 0 ; //absolute 60ns
#40 ; value_test = 1 ; //absolute 100ns
#10 ; value_test = 0 ; //absolute 110ns
end
//(1)general delay control
initial begin
value_general = 1;
#10 value_general = value_test ; //10ns, value_test=0
#45 value_general = value_test ; //55ns, value_test=1
#30 value_general = value_test ; //85ns, value_test=0
#20 value_general = value_test ; //105ns, value_test=1
end
initial begin
cc = 1;
#10 cc = 0 ;
#45 cc = 1 ;
#30 cc =0 ;
#20 cc = 1 ;
end
//(2)embedded delay control
initial begin
value_embed = 1;
value_embed = #10 value_test ; //0ns, value_test=0
value_embed = #45 value_test ; //10ns, value_test=0
value_embed = #30 value_test ; //55ns, value_test=1
value_embed = #20 value_test ; //85ns, value_test=0
end
//(3)single delay control
initial begin
value_single = 1;
#10 ;
value_single = value_test ; //10ns, value_test=0
#45 ;
value_single = value_test ; //55ns, value_test=1
#30 ;
value_single = value_test ; //85ns, value_test=0
#20 ;
value_single = value_test ; //105ns, value_test=1
end
always begin
#10;
if ($time >= 150) begin
$finish ;
end
end
endmodule
我用的是 vivado 21 秒学会 vivado 仿真
仿真结果如下,由图可知:
- (1)一般延时的两种表达方式执行的结果都是一致的。
- (2)一般时延赋值方式:遇到延迟语句后先延迟一定的时间,然后将当前操作数赋值给目标信号,并没有"惯性延迟"的特点,不会漏掉相对较窄的脉冲。
- (3)内嵌时延赋值方式:遇到延迟语句后,先计算出表达式右端的结果,然后再延迟一定的时间,赋值给目标信号。
下面分析下内嵌延时的赋值过程:
value_embed = #10 value_test ; //0ns, value_test=0
0ns 时,执行此延时语句。
先将 0 赋值给信号 value_embed, 延迟 10ns 输出为 0;
value_embed = #45 value_test ; //10ns, value_test=0
10ns 时,执行此延时语句。
由于此时 value_test 仍然为 0,所以 value_embed 值不变。
即到 55ns 时,value_embed 值仍然保持为 0。
value_embed = #30 value_test ; //55ns, value_test=1
同理,55ns 时,value_test 值为 1,将其赋值给 value_embed 并延迟 30ns 输出。
所以 85ns 时,value_embed 输出为 1。
value_embed = #20 value_test ; //85ns, value_test=0
同理,105ns 时,value_embed 输出为 0。
边沿触发事件控制
在 Verilog 中,事件是指某一个 reg 或 wire 型变量发生了值的变化。
基于事件触发的时序控制又主要分为以下几种。
一般事件控制
事件控制用符号 @
语句执行的条件是信号的值发生特定的变化。
关键字 posedge 指信号发生边沿正向跳变,negedge 指信号发生负向边沿跳变,未指明跳变方向时,则 2 种情况的边沿变化都会触发相关事件。例如:
实例
//信号clk只要发生变化,就执行q<=d,双边沿D触发器模型
always
@
(clk
) q
<= d
;
//在信号clk上升沿时刻,执行q<=d,正边沿D触发器模型
always
@
(
posedge clk
) q
<= d
;
//在信号clk下降沿时刻,执行q<=d,负边沿D触发器模型
always
@
(
negedge clk
) q
<= d
;
//立刻计算d的值,并在clk上升沿时刻赋值给q,不推荐这种写法
q
=
@
(
posedge clk
) d
;
命名事件控制
->
实例
event start_receiving
;
always
@
(
posedge clk_samp
)
begin
-> start_receiving
;
//采样时钟上升沿作为时间触发时刻
end
always
@
(start_receiving
)
begin
data_buf
=
{data_if
[
0
]
, data_if
[
1
]
}
;
//触发时刻,对多维数据整合
end
敏感列表
or 连接多个事件或信号。这些事件或信号组成的列表称为"敏感列表"。当然,or 也可以用逗号 ,
实例
//带有低有效复位端的D触发器模型
always
@
(
posedge clk
or
negedge rstn
)
begin
//always @(posedge clk , negedge rstn) begin
//也可以使用逗号陈列多个事件触发
if(
! rstn)
begin
q
<=
1'b
;
end
else
begin
q
<= d
;
end
end
@* 或 @(*),表示对语句块中的所有输入变量的变化都是敏感的。例如:
实例
always
@
(
*
)
begin
//always @(a, b, c, d, e, f, g, h, i, j, k, l, m) begin
//两种写法等价
assign s
= a
? b
+c
: d
? e
+f
: g
? h
+i
: j
? k
+l
: m
;
end
电平敏感事件控制
@+敏感列表
Verilog 中还支持使用电平作为敏感信号来控制时序,即后面语句的执行需要等待某个条件为真。Verilog 中使用关键字 wait 来表示这种电平敏感情况。例如:
实例
initial
begin
wait
(start_enable
)
;
//等待 start 信号
forever
begin
//start信号使能后,在clk_samp上升沿,对数据进行整合
@
(
posedge clk_samp
)
;
data_buf
=
{data_if
[
0
]
, data_if
[
1
]
}
;
end
end
源码下载
Download