3. Behavior Modeling
Assignment
- continuous assignment
- blocking assignment
- nonblocking assignment 등등
Procedure Blocks
(1) initial block
reg를 초기화하고, wire에 값을 drive하기 위함
(2) always block
continuous한 동작을 하는 하드웨어를 만들기 위함
→ 각각의 always block는 하나의 logic을 의미함. (마치 useEffect)
- 두 block 모두 어떤 동작을 표현하며,
- simulation 0에 시작하고
- nested 될 수 없다.
Initial block
testbench에서만 쓰고, design-level에서는 사용하지 않는다.
- reg를 초기화하거나, port에 값을 넣어줄 때 쓴다. (net를 초기화하지 않는다!)
1
2
3
initial begin
statement;
end
- 전체 시뮬레이션에서 1번만 실행된다.
- 모든 Initial block은 t=0에서 동시에 시작한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
reg clock;
initial clock = 0;
// or
reg clock = 0; // 이것도 initial block에 해당함에 유의.
module inc(input [3:0] x, output reg[3:0] y = 4'b0000);
endmodule
module inc(nput[3:0]x, output reg [3:0] y);
y = 4'b0000;
endmodule
// 위의 경우 모두 가능하다.
위처럼 design module에서 initial block을 사용할 수는 있지만, 쓰지는 말자. FPGA는 가능하다!
아래는 initial block의 예시이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
module main;
reg [3:0] a = 2;
wire [3:0] b;
inc my_inc(.x(a), .y(b));
initial begin
$display("Hi a=%d b=%d", a, b);
$finish
end
endmodule
module inc(input[3:0] x, output reg[3:0] y);
y = 4'b0000;
endmodule
Synthesizability
- ASIC → not synthesizable!
- FPGA → synthesizabe
→ ASIC에서는 안돌아가니까, design module에서는 쓰지 말자.
대부분의 공정에서는
RTL → Simulation → FPGA(verify first, lookup table) → ASIC
과정을 거친다.
Always block
sensitivity list가 occur하면 실행되는 block
→ sequential block(latch, flip-flop) 구현에 필수적이며
→ 복잡한 combinational logic에도 사용할 수 있다.
1
2
3
4
5
module flip_flop(input [3:0]D, input CLK, output reg[3:0] Q);
always@(posedge CLK) begin
Q <= D;
end
endmodule
위와 같이 flip flop을 구현할 수 있다.
주의점
- LHS (여기서는 Q)는 항상 reg type이어야 함.
- assignment는 non-blocking assignment(< = )를 쓴다.
⇒ 위의 회로를 합성하면 4-bit register로 합성딘다.
How about Latch?
1
2
3
4
5
module latch(input CLK, input[3:0] D, output reg[3:0] Q);
always@(CLK, D) begin
if(CLK==1) Q <= D;
end
endmodule
→ CLK 이 high이면 assign. D-latch 4개 합성된다.
Inverter
combinational logic에도 alwyas를 쓸 수 있다.
1
2
3
4
5
6
module inverter(input [3:0] a, output reg[3:0] y);
// combinational logic이지만, always block 안이라서 reg로 선언해야한다.
always@(*) begin
y = ~a;
end
endmodule
always@(*)는 block 내에서 output을 변화시킬 수 있는 값들이 변화하면 바꾸라는, useEffect의 원리와 유사한 성질을 갖는 표시이다.
→ 여기서는 a가 바뀌면 다시 실행한다.
→ @(*) 를 이용하면 코드짜기가 쉽고, 버그도 적다.
위의 코드를 합성하면, 인버터가 각 비트마다 연결된다. 즉, 모든 reg가 register로 합성되지 않는다!
→ 이것은 베릴로그가 판단해서 해준다.
Encoder
encoder도 comb로직인데, 이렇게 짜면 어떨까?
1
2
3
4
5
6
7
8
module encoder4to2(input[3:0] y, output reg[1:0] x);
always@(*) begin
if(y == 4'b1000) x = 2'b00;
else if(y == 4'b0100) x = 2'b01;
else if(y == 4'b0010) x = 2'b10;
else if(y == 4'b0001) x = 2'b11;
end
endmodule
이것은 로직상으로는 combinational logic이지만, sequential로 합성된다. 왜일까?
→ Unwanted Latches!
1000, 0100 등 one-hot 신호가 아닌 경우 (ex. 0111)에는 따로 y가 처리되어있지 않다.
즉, 베릴로그는 이런 경우에 ‘기존 값을 유지한다’라는 것으로 해석한다.
그렇다면, 기존값을 유지하기 위해 latch를 쓸 수 밖에 없게 되는 것이다.
if 조건을 쓸 때에는 항상 else를 써서 모든 케이스를 다 처리할 수 있도록 하자.
Blocking and Nonblocking
Procedural assignment
assign이나 wire을 쓰지 않는, continuous하지 않은 assignment
- initial이나 always block 안에서 ‘만’ 써야한다.
- variable data type (reg, integer 등)의 값을 업데이트 한다.
예시)
1
2
reg a = #5 4'b1111; // evaluation 후 5ns delay -> assign
#5 reg a = 4'b1111; // 5ns delay 후 evaluation
Blocking assignment
1
2
3
4
5
6
7
8
module blocking;
reg x, y, z;
initial begin
x = #5 1'b0;
y = #3 1'b1;
z = #6 1'b0;
end
endmodule
오른쪽(RHS)에 있는 값들을 evaluate하고, assign하는데 작성된 순서대로 하나하나 실행함.
즉, 1’b0 ev → 5초 → x assign → 1’b1 ev → 1s → y assign → 1’b0 ev → 6s → z assign 순으로 진행되므로 z는 14초 시점에 assign된다.
1
2
3
4
5
6
7
8
module blocking;
reg x, y, z;
initial begin
#5 x = 1'b0;
#3 y = 1'b1;
#6 z = 1'b0;
end
endmodule
5초 후 evaluation → x assign → 3초 → evaluate → y assign → 6초 → evaluate → z assign
Nonblocking assignment
1
2
3
4
5
6
7
8
module nonblocking;
reg x, y, z;
initial begin
x <= #5 1'b0;
y <= #3 1'b1;
z <= #6 1'b0;
end
endmodule
모든 value들이 동시에 evaluate. 모든 값들은 block 마지막에 assign된다.
→ 3s에 y assign → 5초에 x assign → 6초에 z assign
1
2
3
4
5
6
7
8
module blocking;
reg x, y, z;
initial begin
#5 x <= 1'b0;
#3 y <= 1'b1;
#6 z <= 1'b0;
end
endmodule
3초에 1 evaluate 후 assign → 5초에 0 evaluate 후 assign → 6초에 0 evaluate 후 assign
Example
1
2
3
4
5
6
always @(*) begin
p = a ^ b;
g = a & b;
s = p ^ cin
cout = g | (p & cin);
end
- a, b, cin = 0 and a became 1!
- p = a ^ b, p = 1;
- g = a & b, q = 0;
- s = p ^ cin: s = 1 (because p has assigned to 1 in 1.)
cout = g (p & cin): cout = 0
1
2
3
4
5
6
always@(*) begin
p <= a ^ b;
g <= a & b;
s <= p ^ cin
cout <= g | (p & cin);
end
- a, b, cin = 0 and a became 1!
- Evaluate all RHS
- a^b = 1
- a&b = 0
- p ^ cin = 0
g (p& cin) = 0
- assign’em all!
- p가 바뀌었으니 다시 always block을 실행. (1,0,1,0)을 assign 함.
Combinational Logic with Nonblocking assignment
- 위의 경우, 같은 결과가 나오지만 nonblocking은 evaluate를 2번하기 때문에 느리다.
- 또, * 대신 일일히 값들을 써줬다면 wrong result를 낼 가능성이 높다.
- 어떤 합성 툴은 시뮬레이션 값이 틀린 값이 나와도 하드웨어를 적절히 합성해준다.
- mismatched result를 야기할 수 있다.
→ 그냥 combinational이면 Nonblocking 쓰지마!
Race Condition
example of swap
1
2
3
4
5
6
7
always@(posedge clk) begin
x = y;
end
always@(posedge clk) begin
y = x;
end
이 경우에는
둘 중 하나가 먼저 assign되고, 그 다음에 해당 값을 다른 값이 복사하여 swap되지 않는다.
각 timestep마다 evaluation된 값은 task queue로 들어갔다가 조건에 맞을 때 Javascript 엔진마냥 실행된다.
1
2
3
4
5
6
7
always@(posedge clk) begin
x <= y;
end
always@(posedge clk) begin
y <= x;
end
이 때는 x, y가 미리 한번에 evaluate되고 x, y에 assign되기 떄문에 swap이 가능하다.
이는 nonblocking assignment는 해당 timestamp에 발생하는 모든 RHS를 모아서 한번에 처리하기 때문이다.
shifter등을 구현할 때에도 유용하다.
1
2
3
4
5
6
7
8
9
10
module shifter(input clk, input sin, output reg[3:0] out);
always@(posedge clk) begin
qout[0] <= sin;
qout[1] <= qout[0];
qout[2] <= qout[1];
qout[3] <= qout[2];
end
endmodule
위에서 blocking을 썼다면 sin의 값이 qout에 복사되는 대참사가 벌어진다.
What happens in conditional?
1
2
3
4
5
6
7
8
always @(posedge clk) begin
count = count - 1;
if(count == 0) finish = 1;
end
always @(posedge clk) begin
count <= count -1;
if(count == 0) finish <= 1;
blocking에서는 count가 0으로 assign되고 if문을 실행하기 때문에 finish가 1이된다.
nonblocking에서는 count-1 = 0으로 evaluation이 되고, finish = 1은 if(count ==0)을 만족하지 않아 아예 evaluate되지 않았다.
따라서 finish는 0이다.
⇒ 이후 clock에서는 count = 0이기 떄문에 실행되며 이는 1 clk 차이를 야기한다.
총정리
- Sequential logic은 always@(clk) block + nonblocking
- combinational logic은 blocking (dataflow modeling)
- 복잡한 combinational logic은 always@(*) block + blocking
- 하나의 값을 서로 다른 always에서 바꾸지 말기
- 섞어 쓰지 말기