ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [digital] 구조적 모델링
    개인 공부/회로 2024. 12. 19. 22:30

     

     

     

     

    Verilog HDL을 이용한 여러 모델링과 문법

    두번째로 살펴볼 것은 바로 "구조적 모델링"이다.

     

    구조적 모델링
    • Verilog HDL에서 하드웨어를 설게하는 기본 단위는 모듈이다.
    • 구조적 모델링에서, 하나의 모듈은, 다른 모듈들을 이용하여 계층적으로 설계된다.
      ⇒ 상위 수준의 모듈은 하위 수준의 모듈을 인스턴스하고, 입력/출력/양방향 포트들을 통해 모듈들 간 신호를 전달해준다.
    • EX) PCB보드는 여러 IC칩들을 인스턴스하여 구성된다.
      또한, 각 IC 칩들은 플립플롭, MUX, ALU등과 같은 하위 모듈들을 인스터스하여 구성되고
      그러한 모듈들 또한 여러 하위 인스턴스들을 통해 계층적으로 구성된다.

     

     


     

    모듈

     

    ⇒ 머리부, 선언부, 몸체로 구성된다.

     

     

     

    module and_gate #(
        parameter N = 4
    )(
        input [N-1:0] a,  // 입력 A
        input [N-1:0] b,  // 입력 B
        input cin,         // 입력 Cin
        output [N-1:0] sum,  // 출력 sum
        output cout         // 출력 Cout
    );
    
        reg [N-1:0] a, b;  
        reg cin;         
        wire [N-1:0] sum;      
        wire cout;       
    
        // 논리 회로
        wire [N-1:0] axb_cin;
        wire [N-1:0] axb;
        wire [N-1:0] ab;
    
        and (axb_cin, a, b);
        and (axb, a, b);
        or (cout, axb, axb_cin);
        
        assign sum = a ^ b ^ cin;
    
    endmodule

     

    위 코드를 머리부/선언부/몸체부로 구분하여 살펴보자!

     

    머리부

    : 모듈명, 파라미터 및 포트 목록

    module and_gate #(
        parameter N = 4
    )(
        input [N-1:0] a,  // 입력 A
        input [N-1:0] b,  // 입력 B
        input cin,         // 입력 Cin
        output [N-1:0] sum,  // 출력 sum
        output cout         // 출력 Cout
    );
    
    
    ...​
    • module 키워드
    • 모듈 이름 (and_gate)
    • parameter 목록 (N)
    • 포트 목록 (port_list) 또는 포트 선언 ⇒ 위 예시는 선언이 사용됨

     

     

    선언부

    : port, parameter, net, var 선언

        reg [N-1:0] a, b;  
        reg cin;         
        wire [N-1:0] sum;      
        wire cout;       
    
        wire [N-1:0] axb_cin;
        wire [N-1:0] axb;
        wire [N-1:0] ab;
    • 머리부에 포트가 선언된 경우에는 재선언하지 ❌
    • 포트들의 선언 순서는 인스턴스화 될 때 포트 매핑에 영향을 미친다!

     

     

    몸체부

    : 회로의 동작, 기능, 구조를 모델링

        and (axb_cin, a, b);
        and (axb, a, b);
        or (cout, axb, axb_cin);
        
        assign sum = a ^ b ^ cin;

     

     

     


     

    인스턴스
    • 설계된 모듈을 다른 모듈 내에서 재사용하는 것
    • 모듈을 인스턴스화하면, 그 모듈이 내부에서 정의한 신호, 입력, 출력을 외부 설계와 연결할 수 있다.

     

    인스턴스 사용

    • 하위 모듈을 인스턴스로 사용할 때는 각각의 인스턴스 이름을 반드시 지정해주어야 한다. 
    • 인스턴스 이름은 해당 모듈의 복사본을 구별하는 데 사용된다.

    (📌단, 게이트 프리미티브의 인스턴스 이름은 생략하고 그냥 인스턴스종류만 작성해주어도 된다.)

     

     

    EX) full_adder 모듈을 인스턴스로 사용하여 4bit adder 구현

    module ripple_carry_adder (
      input [3:0] a,         // 4비트 입력 A
      input [3:0] b,         // 4비트 입력 B
      input cin,             // 초기 입력 캐리
      output [3:0] sum,      // 4비트 출력 합계
      output cout            // 최종 출력 캐리
    );
    
      wire c1, c2, c3;       // 내부 캐리 신호
    
      // Full Adder 인스턴스 (포트 순서: a, b, cin, sum, cout)
      full_adder fa0(a[0], b[0], cin, sum[0], c1); // 인스턴스 이름 : fa0
      full_adder fa1(a[1], b[1], c1, sum[1], c2); // 인스턴스 이름 : fa1
      full_adder fa2(a[2], b[2], c2, sum[2], c3);
      full_adder fa3(a[3], b[3], c3, sum[3], cout);
    
    endmodule

    ⇒ 여기서, full_adder 인스턴스를 fa0, fa1, fa2, fa3 과 같이 각각 이름을 지정하여 사용한 것을 알 수 있다.

     

     

     

    인스턴스 포트 매핑

    • 하위 모듈을 인스턴스로 사용할 때는 각각의 인스턴스 이름을 반드시 지정해주어야 한다. 
    • 인스턴스 이름은 해당 모듈의 복사본을 구별하는 데 사용된다.

     

    EX)

    module full_adder (
      input a,       // 입력 A (1비트)
      input b,       // 입력 B (1비트)
      input cin,     // 입력 캐리
      output sum,    // 출력 합계
      output cout    // 출력 캐리
    );
      assign sum = a ^ b ^ cin;               // XOR 연산으로 합계 계산
      assign cout = (a & b) | (cin & (a ^ b)); // 캐리 계산
    endmodule

     

    위와 같이 구현된 모듈을 인스턴스로 사용할 때,
    포트 매핑은 다음과 같이 이루어질 수 있다.

     

     

    1. 순서에 따른 매핑

    : 인스턴스 되는 모듈에 작성된 포트 목록 순서에 맞춰 포트매칭이 이루어 진다.

     full_adder fa0(sum[0], c1, a[0], b[0], cin);
     // full_adder 모듈에서 정의한 순서대로 a,b, cin, sum, cout으로 매핑된다.

     

     

    2. 이름에 따른 매핑

    : 인스턴스 되는 모듈에 작성한 이름을 명시적으로 지정함으로써 포트매칭이 이루어 진다.\

    full_adder fa0 (
        .a(a[2]), .sum(sum[2]), .cin(c2), .cout(c3), .b(b[2]),
      );

     

     

     

     

     

     


     

    포트의 선언
    • 한 번 포트선언에 사용된 이름은 다른 포트 선언/자료형 선언에서 다시 선언될 수 없다.
    • 명시적인 선언이 없는 net은 unsigned로 취급된다.

     

    EX)

    input wire signed [7:0] a,       // signed 8비트 입력 신호 a
      input wire [3:0] b,             // unsigned 4비트 입력 신호 b
      output reg signed [15:0] result, // signed 16비트 출력 레지스터 result
      inout wire [7:0] data_bus,      // unsigned 8비트 양방향 신호 data_bus
      output wire valid,              // unsigned 단일 비트 출력 신호 valid
      input tri0 [3:0] control        // tri0 기본값을 가지는 4비트 3상 네트워크

     

     

     


     

    parameter
    • parameter는 모듈 내부에서 상수처럼 사용되는 자료형이다.
    • 상수를 정의하거나, 모듈의 동작 및 크기(구조)를 매개변수화하는 데 사용된다.
    • 모듈 정의 시 초기값을 설정해야 하며, 이후에는 변경 불가능하다. (상수)
      • 📌 단, 모듈을 인스턴스화 하여 사용할 때는 명시적 재정의 문법 #(.PARAM_NAME(VALUE)) 을 통해 재정의 가능하다
    • 해당 모듈내에서만 사용하는 상수는 localparam 자료형으로 선언되며, 이는 내/외부에서 재정의 불가능하다.

     

    parameter 활용 예시

    예를 들어, 다음과 같이 하나의 adder 모듈을 만들었다고 가정하자.

    module adder #(
      parameter WIDTH = 8   // 기본 비트 폭 8
    )(
      input [WIDTH-1:0] a,  // 입력 A
      input [WIDTH-1:0] b,  // 입력 B
      output [WIDTH-1:0] sum // 출력 합계
    );
      assign sum = a + b;
    endmodule

     

    한 모듈에서는 4bit adder가 필요하고, 다른 모듈에서는 16bit adder가 필요하다면,
    adder_4, adder_8, adder_16를 각각 다르게 작성해주어야 할까?

     

    ⇒ NO!!

     

     

    기본 비트폭을 parameter로 설정하고,
    인스턴스로 사용 될 때 원하는 bit대로 parameter를 재정의 해주면 된다.

    다음은 위와 같이 구현된 모듈을 16bit adder로 사용하는 코드이다.

     

    module top;
      wire [15:0] sum;
      adder #(.WIDTH(16)) u1 (   // WIDTH를 16으로 재설정
        .a(16'hA5A5),
        .b(16'h5A5A),
        .sum(sum)
      );
    endmodule

    ⇒ adder 인스턴스 선언시, WIDTH 파라미터를 16으로 재설정 한 것을 볼 수 있다!

     

     

     

     


     

    [보충] generate-endgenerate 키워드 (동적 생성)
    • 코드가 컴파일 될 때,
    1. 동일한 하드웨어 블록반복적으로 생성하거나
    2. 조건에 따라 선택적으로 하드웨어 블록을 생성해주는 키워드
    • 설계의 유연성과 간결성을 크게 향상시킬 수 있다.

     

     

    특징

    • generate 블록은 컴파일 시간에 처리된다.
    • 반복 생성
      • genvar 변수를 이용해 generate 블록 내에서 반복을 제어할 수 있다.
    • 조건부 생성
      • if-else나 case를 사용하여 특정 조건에 따라 하드웨어를 생성할 수 있다.

     

     

    활용 예시

    예를 들어, 다음과 같이 크기가 명확하고 반복적인 조건이 없는 경우,
    컴파일러가 모든 할당을 정적
    으로 확인할 수 있으므로 generate를 사용하지 않아도 된다.

     

    EX) 고정된 4bit_and

    module and_gate ( 
      input [3:0] a, 
      input [3:0] b, 
      output [3:0] y 
    );
      assign y[0] = a[0] & b[0];
      assign y[1] = a[1] & b[1];
      assign y[2] = a[2] & b[2];
      assign y[3] = a[3] & b[3];
    endmodule

     

     

    하지만, 다음과 같이 크기나 동작이 변경 가능할 경우, 반복이나 조건부 생성을 위해 generate가 필요하다.

     

    EX) 변경가능한 비트수를 가지는 Nbit and_gate

    module and_gate #(
      parameter N = 8 // 비트 폭
    )(
      input [N-1:0] a,
      input [N-1:0] b,
      output [N-1:0] y
    );
      generate
        genvar i;
        for (i = 0; i < N; i = i + 1) begin
          assign y[i] = a[i] & b[i]; // 각 비트마다 AND 연산
        end
      endgenerate
    endmodule

     

    그렇다면,  for, while 과 같은 반복문이 있을 때는 무조건 generate 를 작성해주어야 할까?

     

    ⇒ NO!!

     

    generate 블록은 컴파일 시간에, 하드웨어 블록을 반복적/선택적으로 생성할 때 사용된다는 것에 주의해야 한다.

    📌하드웨어 블록?
    : Verilog 코드가 매핑되는 실제 논리 회로 구성 요소. (논리 게이트, 플립플롭, 레지스터 등등…)

     

     

     

    예를 들어, 다음과 같은 코드를 생각해보자

    odule decoder_while (
      input [2:0] in,        // 3비트 입력
      output reg [7:0] out   // 8비트 출력
    );
      integer i;             // 반복 변수
    
      always @(*) begin
        out = 8'b0;          // 출력 초기화
        i = 0;
        while (i < 8) begin
          if (i == in)       // 입력 값에 해당하는 출력 비트 활성화
            out[i] = 1;
          i = i + 1;
        end
      end
    endmodule

     

     

    ⇒ 주어진 코드에 대해 요구되는 하드웨어 블록은 위와 같이 고정적이다.
    즉, 반복문은 있으나, 고정된 3to8 decoder 하드웨어 구성 요소를 가지고 수행되므로 generate를 작성하지 않는다.

     

     

    하지만, 다음과 같이 코드를 작성한다면

    module decoder_generate #(
      parameter N = 3         // 입력 비트 수
    )(
      input [N-1:0] in,        // N비트 입력
      output [2**N-1:0] out    // 2^N 비트 출력
    );
      generate
        genvar i;
        for (i = 0; i < (1 << N); i = i + 1) begin
          assign out[i] = (in == i); // 입력과 일치하는 출력 비트 활성화
        end
      endgenerate
    endmodule

     

     

    ⇒ N 값 따라 입력 비트 수와 출력 비트수가 동적으로 결정되므로 요구되는 하드웨어 블록이 각각 다르다
    generate를 작성해주어야 한다!

    '개인 공부 > 회로' 카테고리의 다른 글

    [digital] 게이트수준 모델링  (1) 2024.12.19
    [digital] Verilog HDL 모델링  (4) 2024.12.02
    [digital] PLD 종류 (SPLD, CPLD)  (1) 2024.12.01
    [digital] PLD란?  (2) 2024.11.30
Designed by Tistory.
-->