본문 바로가기

실전! Verilog HDL RTL Design

[Verilog HDL] 7. 순차논리 (Sequential Logic) 회로 설계 (D F/F)

반응형

앞에서는 adder를 조합 회로로 구현하였다. 이번에는 clock에 동기 되는 순차 논리 회로로 adder를 설계해 보겠다. 앞장에서 설명하였듯이 순차 논리 회로는 현 상태를 저장할 수 있는 회로이다.

순차 논리 회로 adder를 만들기 전에 순차 논리 회로의 기본이 되는 D flip-flop에 대해 알아보자, 디지털 회로에서 1bit의 정보를 저장하는 기본 회로는 D flip-flop이다. D flip-flop은 clock edge에서 동작하고 입력 값을 clock 에 동기시켜 그대로 출력한다.

reset이 있는 flip-flop은 reset이 active인 구간에서 flip-flop의 출력을 0이나 1로 고정시킨다. 여기서 active라고 하는 것은 reset신호가 1일 때 유효한가 0일 때 유효한가를 따지는데 0일 때 유효한 것을 low active, 1일 때 유효한 것을 high active라고 한다.

예를 들어 low active reset을 사용하는 경우 reset신호가 0일 때 출력이 입력값에 상관없이 0으로 고정된다. 보통 low active reset인 경우 리셋 신호의 이름에 'n'을 접미사로 붙여 low active임을 나타내는 경우가 많다. 아래는 일반 적인 D flip-flop의 예를 나타낸다.

 

// D flip flop
module dff (
input clk,                                     
input resetn,
input d,
output reg q
);
always @(posedge clk or negedge resetn)
begin
    if (!resetn)
    	q <= 0;
    else
    	q <= d;
end
endmodule

 

클럭은 clk라는 입력 port로 정의하였고, 리셋 신호는 low active reset을 사용하기 때문에 resetn이라는 이름의 입력 port로 정의 하였다. 입력 데이터 신호는 d라는 이름의 입력 port로 정의 하였고 출력 port는 q라는 이름의 reg type으로 정의하였다. 여기서 q 출력 port를 reg type으로 선언한 것은 always 문의 begin/end 사이에서 q 값을 할당하기 때문이다.

 

always문에서 @(posedge clk or negedge resetn)은 flip-flop을 설계하기 위한 전형적인 감응 리스트 기술 방식이므로 반드시 외워야 하는 패턴이다. 즉 동기화 시키는 clock의 edge type과 이름을 앞에 기술하고 reset edge type과 이름을 or 키워드 다음에 기술한다. 'posedge clk'는 clk의 positive edge에서 입력을 동기시켜 출력한다는 의미이고, 'negedge resetn'은 resetn의 negative edge에서 flip-flop의 출력이 초기화 되고 resetn이 1이 될 때까지 그 값을 유지한다는 의미이다.

 

다음에 나오는 if/else 구문은 C언어에서의 조건문과 거의 동일하다. 

if (!resetn)에서 ! 는 로직게이트의 not을 의미한다. 즉 resetn이 0일 경우 다음 문장을 수행한다는 의미이다. sensitivity list에 negedge resetn에 의하여 resetn이 1에서 0으로 변할 때 또는 clk 신호가 0에서 1로 변할 때 always 문 안의 구문이 실행되며 첫번때 나오는 이 if문에서 resetn이 0일 때 'q <= 0;'문장이 실행된다.

 

'q <= 0;'문은 non-blocking assignment라고 하는 구문이다. 어떤 변수에 값을 할당하는 방법에는 blocking assignment와 non-blocking assignment 2가지가 있는데, 지금 처럼 '<='기호를 사용하는 것이 non-blocking assignment이고 이전 조합논리회로의 adder에서 처럼 '='기호를 사용하는 것이 blocking assignment이다. 2가지 할당 방식의 차이를 이해하는 것은 매우 중요하다.

 

두 가지 방법의 차이는 아래와 같다.

1) '=' blocking assignment

blocking assignment 구문을 만나면 그 이후 문장이 실행되지 않고 현재 대입문이 완료 될 때까지 blocking된다.

 

`timescale 1ns/1ns

module test;

reg [7:0] a;
reg [7:0] b;
reg [7:0] c;

initial
begin
   a = 0;
   b = 0;
   c = 10;
   #10;
   a = 1;
   b = 2;
   c = a+b;
   $display("c = %d", c);
end    

endmodule

 

이 경우 c의 값을 출력해보면 3이 되는 것을 알 수 있다.

 

2) '<=' non-blocking assignment

non-blocking assignment는 이후 문장의 실행을 blocking하지 않는다. 다음에 있는 문장은 동시에 실행된다.

위의 예제에서 'c = a+b'만 'c <= a+b'로 바꿔서 실행 시켜 보자. c 값은 어떻게 출력 될까?

답은 0이다. 왜냐 하면 초기에 a와 b는 0으로 초기화 되었고 10ns 이후에 a는 1, b는 2로 업데이트 된다. simulatior에서는 이를 변수 값을 evaluate한다고 한다. 10ns 에서 c는 non-blocking assignment로 대입되기 때문에 그 다음에 있는 $display문도 동시에 실행된다. 이 때 display문은 출력하는 변수의 현재 값을 보여 주기 때문에 아직 업데이트 되기 전의 값인 '10'을 출력한다.

그러면 이후에 10ns 딜레이를 주고 다시 non-blocking assignment로 c를 출력하면 어떻게 될까? 이 때는 a,b값이 업데이트 된 값이 사용되어 3이 출력됨을 알 수 있다. 아래는 이를 테스트 하는 코드이다.

첫번째 $display에서는 10이 출력되지만 두번째 $display문에서는 3이 출력된다.

 

`timescale 1ns/1ns

module test;

reg [7:0] a;
reg [7:0] b;
reg [7:0] c;

initial
begin
   a = 0;
   b = 0;
   c = 10;
   #10;
   a = 1;
   b = 2;
   c <= a+b;
   $display("c = %d", c);
   #10;
   $display("c = %d", c);
end    

endmodule

 

그러면 언제 blocking assignment를 사용하고 언제 non-blocking assignment를 사용하는가? 초보자가 제일 헷갈리기 쉬운 부분이다. 일단 결과적으로 알기 쉬운 적용 방법을 알려 준다면 조합회로를 만들기 위한 대입 문에서는 blocking assignment를 사용하고 flip-flop/register등의 순차회로를 만들기 위한 대입문에는 non-blocking assignment를 사용하는 것이다.  위의 D flip-flop예제의 always문에서는 순차 회로이기 때문에 '<='를 사용하였다. 

 

위의 D filp-flop 회로에서 사용한 always 문은 순차회로를 설계하는 가장 기본적인 패턴이므로 잘 외워두는 것이 좋다. 

 

위의 형태로 기술한 D flip-flop의 동작을 wave form으로 그려 보면 아래와 같다.

 

 

 

 [그림 1. dff wave form]

 

 

위 그림에서 resetn이 0인 구간에서는 입력 신호 d에 상관 없이 출력 신호 q가 0임을 알 수 있다. resetn이 1이 되면 그 다음 clk의 positive edge에서 입력 신호 d가 그대로 q로 출력 되는 것을 볼 수 있다. 위의 그림처럼 출력하기 위한 테스트벤치 코드는 아래와 같다.

 

`timescale 1ns/1ns

module test;

// delare variables
reg clk;
reg resetn;
reg d;
wire q;

// clock generation
always #10 clk = ~clk;


// DUT
dff u_dff (
    .clk(clk),
    .resetn(resetn),
    .d(d),
    .q(q)
);

initial
begin
    $dumpfile("dff.vcd");
    $dumpvars(0, test);
end

initial
begin
    clk     <= 0;
    resetn  <= 0;
    d       <= 0;
    @(posedge clk);
    @(posedge clk);
    @(posedge clk);
    @(posedge clk);
    d       <= 1;
    @(posedge clk);
    resetn  <= 1;
    @(posedge clk);
    @(posedge clk);
    @(posedge clk);
    d       <= 0;
    @(posedge clk);
    @(posedge clk);
    d       <= 1;
    @(posedge clk);
    @(posedge clk);
    $finish();
end

endmodule

 

 

우선 clock을 생성하는 부분을 보겠다.

 

always #10 clk = ~clk;

 

always문에서 문장을 하나만 사용하는 경우에는 begin/end를 생략할 수 있다. clk 변수는 block/end 안에서 사용하는 것과 마찬가지 이므로 reg 변수 타입을 사용한다. 그런데 이 always문에는 @로 시작하는 감응 리스트가 없다. 이런 경우 감응 리스트가 아니라 바로 다음에 나오는 #10시간 마다 always 문을 반복한다는 의미 이다. 다시 한번 설명하지만 always 문은 하드웨어의 병렬 실행의 기본 단위로써 감응 리스트나 특정 시간마다 반복적으로 실행되는 기능 블럭이다.

위의 always 문으로 10ns 마다 0->1, 1->0으로 toggle하는 clock 신호를 만들 수 있다.

 

dff을 instantiation하는 것은 전에서 배운 방법과 같다. u_dff라는 인스탄스를 만들고 각 port를 변수와 연결한다. 이 때 주의할 것은 입력 port에는 wire변수나 reg변수를 연결할 수 있지만 출력 port는 항상 wire변수로 연결하는 것이다. 

 

initial 문에서 clock과 resetn 그리고 d 값을 초기화 시킨다.

그리고 다음과 같이 clock의 positive edge event가 일어날 때까지 기다리는 문장이 있다.

@(posedge clk);

@('event')와 같은 형식으로 괄호안의 event가 발생할 때 까지 기다렸다가 다음 문장을 실행한다.

즉 @(posedge clk);는 다음 clk의 positive edge까지 기다린다는 말이다.

이렇게 위의 wave form에 나타낸 것처럼 resetn과 d 입력을 만들어 준다. 

initial 문안의 모든 대입 문에 '<='처럼 non-blocking assignment를 사용한 것을 유의하기 바란다.

이렇게 해야 clock의 positive edge에 동기 시켜 만들어준 d 입력이 다음 clock에서 사용될 수 있다.

non-blocking 대신 blocking assignment를 사용하면 결과가 달라질 것이다. 한 번 테스트 벤치를 수정하여 실행하고 waveform을 확인해 보기 바란다. (질문이 있으신 분은 언제든 답글 남겨 주시기 바랍니다.)

 

아래는 iverilog 로 컴파일 하고 실행 시킨 화면이다.

[그림 2. iverilog 실행]

 

gtkwave를 이용하여 아래와 같이 결과 파형을 확인 할 수 있다.

$gtkwave dff.vcd

[그림 3. vcd wave form]

 

위의 파형과 그림 1의 파형과 비교해 보면 동일한 것을 알 수 있다.

반응형