Verilog 지연 제어 이해: 타이밍 기법과 모범 사례
Verilog에는 두 가지 타이밍 제어 유형이 있습니다: 지연(delay)과 이벤트(event) 표현식. 지연 제어는 시뮬레이터가 문을 만나고 실제로 실행하는 사이에 지연을 추가하는 방법일 뿐입니다. 이벤트 표현식은 넷이나 변수의 값 변경(암시적 이벤트)이나 다른 프로시저에서 트리거되는 명시적 명명 이벤트 같은 시뮬레이션 이벤트 발생까지 문을 지연시킵니다.
시뮬레이션 시간은 다음 방법들로 진행될 수 있습니다.
내부 지연을 가지도록 모델링된 게이트와 넷도 시뮬레이션 시간을 진행시킵니다.
지연 제어 (Delay Control)
지연 표현식이 알 수 없는 값이나 하이 임피던스 값으로 평가되면 0 지연으로 해석됩니다. 음수 값으로 평가되면 time 변수와 같은 크기의 2의 보수 부호 없는 정수로 해석됩니다.
`timescale 1ns/1ps
module tb;
reg [3:0] a, b;
initial begin
{a, b} <= 0;
$display ("T=%0t a=%0d b=%0d", $realtime, a, b);
#10; a <= $random; $display ("T=%0t a=%0d b=%0d", $realtime, a, b);
#10 b <= $random; $display ("T=%0t a=%0d b=%0d", $realtime, a, b);
#(a) $display ("T=%0t After a delay of a=%0d units", $realtime, a);
#(a+b) $display ("T=%0t After a delay of a=%0d + b=%0d = %0d units", $realtime, a, b, a+b);
#((a+b)*10ps) $display ("T=%0t After a delay of %0d * 10ps", $realtime, a+b);
#(b-a) $display ("T=%0t Expr evaluates to a negative delay", $realtime);
#('h10) $display ("T=%0t Delay in hex", $realtime);
a = 'hX; #(a) $display ("T=%0t Delay is unknown, taken as zero a=%h", $realtime, a);
a = 'hZ; #(a) $display ("T=%0t Delay is in high impedance, taken as zero a=%h", $realtime, a);
#1ps $display ("T=%0t Delay of 10ps", $realtime);
end
endmodule
timescale 정밀도가 1ps이므로, 지연 표현식 (a+b)*10ps 문장에 대해 정밀도 값을 표시하려면 $realtime이 필요합니다.
시뮬레이션 로그
xcelium> run
T=0 a=x b=x
T=10000 a=0 b=0
T=20000 a=4 b=0
T=24000 After a delay of a=4 units
T=29000 After a delay of a=4 + b=1 = 5 units
T=29050 After a delay of 5 * 10ps
T=42050 Expr evaluates to a negative delay
T=58050 Delay in hex
T=58050 Delay is unknown, taken as zero a=x
T=58050 Delay is in high impedance, taken as zero a=z
T=58051 Delay of 10ps
xmsim: *W,RNQUIE: Simulation is complete.
이벤트 제어 (Event Control)
넷과 변수의 값 변경은 다른 절차적 문장의 실행을 트리거하는 동기화 이벤트로 사용될 수 있으며 이는 암시적 이벤트입니다. 이벤트는 변경 방향에 기반할 수도 있는데, 0 방향 변경은 negedge, 1 방향 변경은 posedge가 됩니다.
negedge: 1에서 X, Z, 0으로의 전이, X 또는 Z에서 0으로의 전이
posedge: 0에서 X, Z, 1으로의 전이, X 또는 Z에서 1으로의 전이
같은 상태에서 같은 상태로의 전이는 에지로 간주되지 않습니다. posedge나 negedge 같은 에지 이벤트는 벡터 신호나 변수의 LSB에서만 감지할 수 있습니다. 표현식이 같은 결과로 평가되면 이벤트로 간주될 수 없습니다.
module tb;
reg a, b;
initial begin
a <= 0;
#10 a <= 1;
#10 b <= 1;
#10 a <= 0;
#15 a <= 1;
end
// 위 절차적 블록에서 만든 신호 업데이트를 기다리는 다른 절차적 블록
initial begin
@(posedge a); $display ("T=%0t Posedge of a detected for 0->1", $time);
@(posedge b); $display ("T=%0t Posedge of b detected for X->1", $time);
end
initial begin
@(posedge (a + b)) $display ("T=%0t Posedge of a+b", $time);
@(a) $display ("T=%0t Change in a found", $time);
end
endmodule
시뮬레이션 로그
ncsim> run
T=10 Posedge of a detected for 0->1
T=20 Posedge of b detected for X->1
T=30 Posedge of a+b
T=45 Change in a found
ncsim: *W,RNQUIE: Simulation is complete.
명명된 이벤트 (Named Events)
event 키워드로 명명된 이벤트를 선언할 수 있으며, 이는 명시적으로 트리거될 수 있습니다. event는 데이터를 담지 않으며, 시간 지속 기간이 없고, 특정 시간에 발생하도록 만들 수 있습니다. 명명된 이벤트는 -> 연산자로 명명된 이벤트 핸들 앞에 붙여서 트리거합니다. 명명된 이벤트는 위에서 설명한 @ 연산자로 기다릴 수 있습니다.
module tb;
event a_event;
event b_event[5];
initial begin
#20 -> a_event;
#30; ->a_event;
#50 ->a_event;
#10 ->b_event[3];
end
always @ (a_event) $display ("T=%0t [always] a_event is triggered", $time);
initial begin
#25; @(a_event) $display ("T=%0t [initial] a_event is triggered", $time);
#10 @(b_event[3]) $display ("T=%0t [initial] b_event is triggered", $time);
end
endmodule
명명된 이벤트는 동시에 실행되는 두 개 이상의 프로세스를 동기화하는 데 사용할 수 있습니다. 예를 들어, always 블록과 두 번째 initial 블록이 a_event로 동기화됩니다. 이벤트는 배열로 선언할 수 있으며, b_event가 크기 5의 배열이고 인덱스 3이 트리거와 대기 목적으로 사용됩니다.
시뮬레이션 로그
ncsim> run
T=20 [always] a_event is triggered
T=50 [always] a_event is triggered
T=50 [initial] a_event is triggered
T=100 [always] a_event is triggered
T=110 [initial] b_event is triggered
ncsim: *W,RNQUIE: Simulation is complete.
이벤트 or 연산자
or 연산자는 나열된 이벤트 중 하나가 트리거될 때까지 기다리는 데 사용됩니다. 쉼표 ,도 or 연산자 대신 사용할 수 있습니다.
module tb;
reg a, b;
initial begin
$monitor ("T=%0t a=%0d b=%0d", $time, a, b);
{a, b} <= 0;
#10 a <= 1;
#5 b <= 1;
#5 b <= 0;
end
// 이벤트 사이에 "or" 사용
always @ (posedge a or posedge b) $display ("T=%0t posedge of a or b found", $time);
// 쉼표 사용
always @ (posedge a, negedge b) $display ("T=%0t posedge of a or negedge of b found", $time);
always @ (a, b) $display ("T=%0t Any change on a or b", $time);
endmodule
시뮬레이션 로그
ncsim> run
T=0 posedge of a or negedge of b found
T=0 Any change on a or b
T=0 a=0 b=0
T=10 posedge of a or b found
T=10 posedge of a or negedge of b found
T=10 Any change on a or b
T=10 a=1 b=0
T=15 posedge of a or b found
T=15 Any change on a or b
T=15 a=1 b=1
T=20 posedge of a or negedge of b found
T=20 Any change on a or b
T=20 a=1 b=0
ncsim: *W,RNQUIE: Simulation is complete.
암시적 이벤트 표현식 리스트
민감도 리스트 또는 이벤트 표현식 리스트는 RTL에서 많은 기능적 오류의 흔한 원인입니다. 사용자가 절차적 블록에 새 신호를 도입한 후 민감도 리스트를 업데이트하는 것을 잊을 수 있기 때문입니다.
module tb;
reg a, b, c, d;
reg x, y;
// 이벤트 표현식/민감도 리스트는 @ 연산자 뒤 () 안의
// 모든 신호로 형성되며, 이 경우 a, b, c, d
always @ (a, b, c, d) begin
x = a | b;
y = c ^ d;
end
initial begin
$monitor ("T=%0t a=%0b b=%0b c=%0b d=%0b x=%0b y=%0b", $time, a, b, c, d, x, y);
{a, b, c, d} <= 0;
#10 {a, b, c, d} <= $random;
#10 {a, b, c, d} <= $random;
#10 {a, b, c, d} <= $random;
end
endmodule
시뮬레이션 로그
ncsim> run
T=0 a=0 b=0 c=0 d=0 x=0 y=0
T=10 a=0 b=1 c=0 d=0 x=1 y=0
T=20 a=0 b=0 c=0 d=1 x=0 y=1
T=30 a=1 b=0 c=0 d=1 x=1 y=1
ncsim: *W,RNQUIE: Simulation is complete.
사용자가 새 신호 e를 추가하고 그 역을 z로 캡처하기로 결정하면, e도 민감도 리스트에 추가해야 합니다.
module tb;
reg a, b, c, d, e;
reg x, y, z;
// 민감도 리스트에 "e"도 추가
always @ (a, b, c, d, e) begin
x = a | b;
y = c ^ d;
z = ~e;
end
initial begin
$monitor ("T=%0t a=%0b b=%0b c=%0b d=%0b e=%0b x=%0b y=%0b z=%0b", $time, a, b, c, d, e, x, y, z);
{a, b, c, d, e} <= 0;
#10 {a, b, c, d, e} <= $random;
#10 {a, b, c, d, e} <= $random;
#10 {a, b, c, d, e} <= $random;
end
endmodule
시뮬레이션 로그
ncsim> run
T=0 a=0 b=0 c=0 d=0 e=0 x=0 y=0 z=1
T=10 a=0 b=0 c=1 d=0 e=0 x=0 y=1 z=1
T=20 a=0 b=0 c=0 d=0 e=1 x=0 y=0 z=0
T=30 a=0 b=1 c=0 d=0 e=1 x=1 y=0 z=0
ncsim: *W,RNQUIE: Simulation is complete.
이제 Verilog는 민감도 리스트를 *로 대체할 수 있게 하여, 문장이 읽는 모든 넷과 변수를 추가함으로써 이러한 문제를 편리하게 해결합니다.
module tb;
reg a, b, c, d, e;
reg x, y, z;
// @* 또는 @(*) 사용
always @ * begin
x = a | b;
y = c ^ d;
z = ~e;
end
initial begin
$monitor ("T=%0t a=%0b b=%0b c=%0b d=%0b e=%0b x=%0b y=%0b z=%0b", $time, a, b, c, d, e, x, y, z);
{a, b, c, d, e} <= 0;
#10 {a, b, c, d, e} <= $random;
#10 {a, b, c, d, e} <= $random;
#10 {a, b, c, d, e} <= $random;
end
endmodule
시뮬레이션 로그
ncsim> run
T=0 a=0 b=0 c=0 d=0 e=0 x=0 y=0 z=1
T=10 a=0 b=0 c=1 d=0 e=0 x=0 y=1 z=1
T=20 a=0 b=0 c=0 d=0 e=1 x=0 y=0 z=0
T=30 a=0 b=1 c=0 d=0 e=1 x=1 y=0 z=0
ncsim: *W,RNQUIE: Simulation is complete.
레벨 민감 이벤트 제어 (Level Sensitive Event Control)
절차적 문장의 실행은 조건이 참이 될 때까지 지연될 수 있으며, 이는 wait 키워드로 달성되며 레벨 민감 제어입니다.
wait 문은 조건을 평가하고, 거짓이면 조건이 참이 될 때까지 이후 절차적 문장들이 차단된 상태로 유지됩니다.
module tb;
reg [3:0] ctr;
reg clk;
initial begin
{ctr, clk} <= 0;
wait (ctr);
$display ("T=%0t Counter reached non-zero value 0x%0h", $time, ctr);
wait (ctr == 4) $display ("T=%0t Counter reached 0x%0h", $time, ctr);
$finish;
end
always #10 clk = ~clk;
always @ (posedge clk) ctr <= ctr + 1;
endmodule
시뮬레이션 로그
ncsim> run
T=10 Counter reached non-zero value 0x1
T=70 Counter reached 0x4
T=90 Counter reached 0x5
T=170 Counter reached 0x9
Simulation complete via $finish(1) at time 170 NS + 1