[Verilog] 문법 2 – 인스턴스(Instantiation), 플립플롭(Flip-Flop)과 래치(Latch)

지난 글에 이어, verilog 문법에 대해 설명하도록 하겠습니다.

인스턴스 (Instantiation)

인스턴스화는 이미 완성된 모듈을 가져오는 것을 말합니다. 어떤 경우에 인스턴스화를 할까요??

Testbench

이전 글에서 testbench는 시뮬레이션을 하기 위한 환경이라고 말씀을 드렸습니다. 시뮬레이션을 하는 이유는 DUT(Device Under Test)의 기능을 확인하거나 검증하기 위해서 합니다. 그러기 위해서는 testbench에 DUT를 가져와야겠죠?? 이럴 때 모듈을 인스턴스화합니다.

구조 설계

아래 그림은 UART라고 하는 통신 IP의 block diagram 예시입니다.

UART block diagram
UART block diagram

UART 모듈 내부에 기능별로 여러 component가 있죠?? 이러한 구조를 계층 구조(hierarchy)라고 합니다. 하나의 모듈 위의 component를 모두 구현할 수 있지만, 이럴 경우 코드가 너무 길어지고 재사용에도 용이하지 않습니다. 그래서 component마다 module을 만들어주고 uart_top에서 위의 모듈들을 인스턴스화하여 구조적 설계를 합니다.

그럼, testbench에서 인스턴스화 예시를 보겠습니다.

인스턴스화 예시

module top ();
  
  //자료형 선언, 초기화
  reg  [3:0] a; initial a = 4'h0;
  reg  [3:0] b; initial b = 4'h0;
  
  wire [7:0] c;
  
  //시뮬레이션 time
  initial begin
    #500
    $display("////////////////");
    $display("///sim_finish///");
    $display("////////////////");
    $finish;
  end
  
  //dump file 만들기
  initial begin
    $dumpfile("dump.vcd");
    $dumpvars;
  end

endmodule

여기서 $display는 $finish와 마찬가지로 베릴로그 시스템 task로, log에 괄호 안의 문자를 display 해달라는 의미입니다. 위의 코드를 실행하면 선언된 reg 들은 initial 값인 4’h0을, wire는 할당되지 않았기 때문에 z를 나타내고 500ns 뒤에 display 한 뒤 시뮬레이션이 종료됩니다.

이번에는 multiplier 모듈을 작성합시다.

module multiplier (x,y,out);
  input  [3:0] x, y;
  output [7:0] out;
  
  assign out = x * y;
  
endmodule

단순한 곱셈기로 4비트 x, y를 input으로 받아 둘을 곱한 뒤 8비트 output으로 출력하는 간단한 모듈입니다. input, output은 reg로 선언하지 않으면 wire로 선언됩니다.

port 선언을 아래와 같이 해줘도 됩니다, 회사에 있을 때는 항상 아래와 같이 했습니다.

module multiplier (
   input  wire [3:0] x
  ,input  wire [3:0] y
  ,output wire [7:0] out
);
  
  assign out = x * y;
  
endmodule

이제 이 모듈을 testbench의 DUT로 인스턴스화해봅시다. 이제 시뮬레이션 time과 dump 파일을 만드는 initial 구문은 생략하겠습니다. 여러분은 써주셔야 합니다.

module top ();
  
  //자료형 선언, 초기화
  reg  [3:0] a; initial a = 4'h0;
  reg  [3:0] b; initial b = 4'h0;
  
  wire [7:0] c;
  
  //dut 인스턴스화
  multiplier u_dut (
     .x   (a)
    ,.y   (b)
    ,.out (c)
  );
  
  //reg control
  initial begin
    #100
    a = 4'h3;
    b = 4'h5;
    #100
    a = 4'h7;
    b = 4'h6;
    #100
    a = 4'h0;
    b = 4'hf;
    #100
    a = 4'hd;
    b = 4'hc;
  end
  
endmodule

먼저 위 testbench 코드를 diagram으로 표현하고 분석해 보겠습니다.

Testbench block diagram
Testbench block diagram

Diagram을 보시면 이해가 쉬울 텐데요, testbench인 top 모듈에서 선언된 reg a, b가 u_dut라는 이름으로 인스턴스화된 multipler 모듈의 x, y port에 연결됐습니다. 그리고 wire c가 out port에 연결된 구조입니다.

그러면 시뮬레이션을 돌려서 a, b가 변함에 따라 c가 어떻게 변하는지 결과를 확인해 보겠습니다.

Simulation result
Simulation result

Waveform에서 x, y, out은 지웠습니다. 위의 결과를 검토해 볼까요?? 참고로 아래 수들은 모두 16진법의 수입니다.

#0 : 4’h0 x 4’h0 = 8’h0

#100: 4’h3 x 4’h5 = 8’hf

#200: 4’h7 x 4’h6 = 8’h2a

#300: 4’h0 x 4’hf = 8’h0

#400: 4’hd x 4’hc = 8’h9c

Clock과 Reset

Clock(클럭)과 reset(리셋)은 디지털회로에서 정말 매우 중요한 역할을 하는 1-bit signal입니다. verilog에서 설계된 모듈들은 clock에 ‘동기화’되어 동작하고 reset에 의해 초기 상태로 돌아가게 됩니다.

먼저, testbench에서 clock과 reset을 만들어봅시다. 다음의 코드를 실행해볼까요?

module top();
  reg clk;   initial clk   = 1'b0;
  reg reset; initial reset = 1'b1;
  
  localparam period_clk = 10;
  
  always #(0.5 * period_clk) clk =  ~clk;
  
  initial begin
    #period_clk
    reset = 1'b0;
    
    #100
    $finish;
  end
  
  initial begin
    $dumpfile("dump.vcd");
    $dumpvars;
  end

endmodule

그러면 아래와 같이 clock과 reset의 결과가 나타나게 됩니다.

clk, reset simulation result
clk, reset simulation result

clock의 주기는 10ns이고 처음에 reset이 걸려있다가(high) 10ns 뒤에 풀린 것을(low) 알 수 있습니다. 이처럼 대부분의 testbench에서는 reset을 걸었다가 일정 시간 뒤에 풀고 시뮬레이션이 진행되는 방식을 사용합니다.

그런데 이전 글에서는 resetn을 사용했는데요, 이는 reset의 기능인 초기화가 low일 때 작동한다는 뜻입니다. (Active-low) 실무에서는 active-high인 reset을 사용하지 않고 active-low인 resetn을 사용합니다. 그러면 testbench 코드를 아래와 같이 수정하면 됩니다.

module top();
  reg clk;    initial clk    = 1'b0;
  reg resetn; initial resetn = 1'b0;
  
  localparam period_clk = 10;
  
  always #(0.5 * period_clk) clk = ~clk;
  
  initial begin
    #period_clk
    resetn = 1'b1;
    
    #100
    $finish;
  end
  
  initial begin
    $dumpfile("dump.vcd");
    $dumpvars;
  end

endmodule

플립플롭(Flip-Flop)과 래치(Latch)

Filp-Flop과 Latch 모두 정보를 저장할 수 있는 회로입니다. 차이점은 Filp-Flop은 clock에 동기화되어 동작하고, Latch는 그렇지 않다는 것입니다. 다른 방식으로 설명하자면 Flip-Flop은 clock의 edge에 triggered 되고, Latch는 clock의 level에 triggered 되어 동작합니다.

아래 코드를 실행해볼까요?

module top();
  reg clk;    initial clk    = 1'b0;
  reg resetn; initial resetn = 1'b0;
  reg D;      initial D      = 1'b0;
  reg Q1;     initial Q1     = 1'b0;
  reg Q2;     initial Q2     = 1'b0;
  
  localparam period_clk = 10;
  
  always #(0.5 * period_clk) clk = ~clk;
  always #7 D = ~D;
  
  initial begin
    #period_clk
    resetn = 1'b1;
    
    #100
    $finish;
  end

  //Flip-Flop
  always @(posedge clk) begin
    Q1 <= D;
  end
  
  //Latch
  always @(clk) begin
    Q2 <= D;
  end
  
  initial begin
    $dumpfile("dump.vcd");
    $dumpvars;
  end
  
endmodule

Simulation 결과는 아래와 같습니다.

Flip-Flop, Latch simulation result
Simulation result

차이가 보이시나요?? 이렇게 Flip-Flop과 달리 Latch는 clock edge에 동기화되지 않습니다. 이는 디지털 설계 관점에서 매우 좋지 않기 때문에 실무에서는 사용하지 않습니다. 하지만 clock에 동기화시켜도 합성 시 latch가 생성될 수도 있는데요, 다음과 같은 경우에 latch가 생성됩니다.

  • if 문에서 else 조건을 설정하지 않았을 때
  • case 문에서 default 조건을 설정하지 않았을 때

Asynchronous Reset Flip-Flop

마지막으로 Asynchronous Reset Flip-Flop 예시를 보면서 마무리하도록 하겠습니다. Reset을 비동기로 사용하는 이유는 그만큼 reset 신호가 중요하기 때문입니다.

예를 들어, 컴퓨터가 작동하는데 리셋버튼을 누르면 컴퓨터 동작이 우선일까요, 아니면 리셋버튼을 누른 게 우선일까요?? 리셋버튼을 누르면 컴퓨터가 어떻게 동작하든 리셋이 됩니다.

Asynchronous Reset이란 말은, always 구문의 sesitive list에 reset이 포함된다는 의미입니다. 그러면 active-low인 resetn을 사용하는 flip-flop 예시를 보도록 합시다.

  //Asynchronous Reset Flip-Flop
  always @(posedge clk or negedge resetn) begin
    if (!resetn) Q1 <= 1'b0; //Q1 초기화
    else         Q1 <= D;    //리셋 풀린 뒤 동작 방식
  end

이렇게 reg Q1을 작동시키면 Q1의 초기값이 설정되어 있기 때문에, initial 문으로 Q1의 초기값을 설정하지 않아도 됩니다.

참고: Verilog standard

Similar Posts