포스트

임베디드 스터디 - Verilog 설계

임베디드 스터디 - Verilog 설계

조합논리회로 설계

assign vs always @(*)

  • assign : 단순 논리 연산, 입출력 배선에 사용
  • always : if-else, case 조건 분기에 사용

반가산기 설계

논리연산 문법 : AND(&), OR(), NOT(~), XOR(^)
1
2
3
4
5
6
7
module HA(
    input wire a, b,
    output wire sum, carry
);
    assign sum = a ^ b;
    assign carry = a & b;
endmodule

다중비트 데이터

  • Verilog는 다중비트 입출력을 [MSB:LSB]의 형태로 데이터를 선언한다.
1
2
input wire [1:0] in
output reg [7:0] out

숫자

  • Verilog는 다음과 같이 숫자를 선언할 수 있다.
    1
    2
    3
    4
    
    10 // Decimal
    3'b000 // 3비트 이진수 000
    8'd10 // 8비트 십진수 10
    6'hc // 6비트 16진수 C(12)
    

MUX 설계

  • 4X1 MUX는 Verilog의 case 문을 사용하여 설계할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module mux4to1 (
    input  wire [1:0] sel,
    input  wire d0, d1, d2, d3,
    output reg  y
);
    always @(*) begin
        case (sel)
            2'b00: y = d0;
            2'b01: y = d1;
            2'b10: y = d2;
            2'b11: y = d3;
            default: y = d0;
        endcase
    end
endmodule
  • Verilog는 0, 1외에 X(Unknown), Z(High Impedance)라는 값이 존재한다. 이에 위의 MUX 설계에서 sel 신호에 해당하는 비트를 모두 case 문에 넣어도, X, Z 데이터가 생기면 y는 미정의 상태가 된다.
    • 이를 해결하기 위해 상항 default를 넣는 습관을 들이자.

디코더 설계

  • 3X8 디코더는 Verilog에서 두 가지 방법으로 설계할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module decoder3to8 (
    input wire [2:0] in,
    output reg [7:0] out
    );
    // case 문 사용
    always@(*) begin
        case (in)
            3'b000 : out = 8'd1;    //8'b0000_0001
            3'b001 : out = 8'd2;    //8'b0000_0010
            3'b010 : out = 8'd4;    //8'b0000_0100
            3'b011 : out = 8'd8;    //8'b0000_1000
            3'b100 : out = 8'd16;   //8'b0001_0000
            3'b101 : out = 8'd32;   //8'b0010_0000
            3'b110 : out = 8'd64;   //8'b0100_0000
            3'b111 : out = 8'd128;  //8'b1000_0000
            default : out = 8'd1;   //8'b0000_0001
        endcase
    end 

    // bit shift 문법 사용
    assign out = 8'd1 << in;
endmodule

순서논리회로 설계

변수 할당

  • Verilog는 변수 할당에 =, <= 두 가지 방식이 존재한다.
    • = (Blocking assignment) : 선언 순서대로 즉시 대입
    • <= (Non-blocking assignment) : always 블록 실행이 끝날 때 우변을 일괄 대입. 순서논리회로에서 사용

D-FF 설계

  • Verilog로 심플하게 설계할 수 있다.
1
2
3
4
5
6
7
8
module DFF(
    input  wire clk, d,
    output reg  q
);
    always @(posedge clk) begin
        q <= d;
    end
endmodule

리셋

  • Verilog를 통해 비동기 리셋, 동기 리셋을 구현할 수 있다.
    • 동기 리셋 : reset 신호를 입력받아 조건문으로 변수 초기화. 클럭에 종속되어 데이터가 변한다.
    • 비동기 리셋 : reset 신호를 sensitivity list에 추가. 이후 조건문으로 변수 초기화. reset 자체가 sensitivity list에 들어가있으므로 모듈이 reset의 변화에 반응한다.

트리거

  • Verilog는 크게 3가지 형태의 트리거를 지원한다.
    • posedge : 상승 엣지 트리거
    • negedge : 하강 엣지 트리거
    • 일반 변수 : 레벨 트리거
  • Verilog는 양방향 트리거를 기본적으로 지원하지 않는다. 또, 표준 디지털 로직에 양방향 트리거 FF 프리미티브 자체가 존재하지 않아, FPGA 상에서 실제 하드웨어로 구현되기 어렵다.

4비트 동기 카운터 설계

  • Verilog로 4비트 동기 카운터를 설계한다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    module counter(
      input wire clk, rst,
      output reg [3:0] count
      );
        
      always@(posedge clk, posedge rst) begin
          if (rst == 1)
              count <= 4'b0000;
          else
              count <= count + 4'b0001;
      end 
    endmodule
    

    파라미터 설정

  • Verilog는 파라미터를 정의하는 방법이 두 가지 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 방법 1: `define (C의 #define과 동일)
`define S0 2'b00
`define S1 2'b01
`define S2 2'b10

// 사용 시
state <= `S0;  // 백틱(`) 필요

// 방법 2: parameter (Verilog 권장 방식)
parameter S0 = 2'b00;
parameter S1 = 2'b01;
parameter S2 = 2'b10;

// 사용 시
state <= S0;   // 백틱 불필요

FSM 설계

  • Verilog를 통해 2비트 시퀀스 검출기를 설계한다.
  • FSM을 Verilog로 구현할 때, 보통 2-블록 구조를 자주 사용한다.
    • 한 블록에서는 상태의 업데이트를, 다른 블록에서는 상태에 따른 동작을 설계한다. ```verilog module state_machine( input wire rst, in, clk, output reg out ); reg [1:0] state, next_state; parameter S0 = 2’b00; parameter S1 = 2’b01; parameter S2 = 2’b10;

    always@(posedge clk, posedge rst) begin if (rst == 1) state <= S0; else state <= next_state; end

    always@(*) begin case (state) S0 : begin if (in == 1) begin next_state = S1; end else begin next_state = S0; end out = 0; end S1 : begin if (in == 1) begin next_state = S2; end else begin next_state = S0; end out = 0; end S2 : begin out = 1; next_state = S0; end default : begin out = 0; next_state = S0; end
    endcase end endmodule

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
## 계층설계 (Hierarchical Design)

* 인스턴스화 : 하나의 모듈에서 다른 모듈을 불러와 설계함. 
    * 소프트웨어의 **함수 호출**과 다른 점 : Verilog는 모듈의 배치 후 계속 하드웨어에서 실행, 소프트웨어는 함수를 호출할 때만 실행된다.

```verilog
module top (
    input  wire a, b,
    output wire sum, carry
);
    // HA 모듈을 인스턴스화
    half_adder u0 (
        .a(a),
        .b(b),
        .sum(sum),
        .carry(carry)
    );
endmodule

전가산기 설계

  • 전가산기는 반가산기 2개를 사용하여 회로를 구성할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
module half_adder(
    input wire a, b,
    output wire sum, carry
    );
    
    assign sum = a ^ b;
    assign carry = a & b;
endmodule

module full_adder(
    input wire a, b, carry_in,
    output wire sum_out, carry_out
    );
    wire sum0, carry0, carry1;
    
    half_adder u0 (
        .a(a),
        .b(b),
        .sum(sum0),
        .carry(carry0)
    );
   
   half_adder u1 (
        .a(sum0),
        .b(carry_in),
        .sum(sum_out),
        .carry(carry1)
    );
    
    assign carry_out = carry0 | carry1;
endmodule

재사용성 중심 모듈 설계

  • parameter 변수를 사용해 동일 기능의 모듈을 다양한 데이터 크기로 만들 수 있다.
1
2
3
4
5
6
7
8
9
module counter #(parameter WIDTH = 4) (
    input  wire clk, rst,
    output reg [WIDTH-1:0] count
);
    always @(posedge clk, posedge rst) begin
        if (rst) count <= 0;
        else     count <= count + 1;
    end
endmodule

반복회로 생성

  • generate 블록을 사용해 동일한 모듈의 인스턴스를 여러 개 생성할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
module ripple_adder #(parameter N = 4) (
    input  wire [N-1:0] a, b,
    input  wire cin,
    output wire [N-1:0] sum,
    output wire cout
);
    wire [N:0] carry;
    assign carry[0] = cin;

    genvar i;
    generate
        for (i = 0; i < N; i = i+1) begin : FA
            full_adder u (
                .a(a[i]), .b(b[i]),
                .carry_in(carry[i]),
                .sum_out(sum[i]),
                .carry_out(carry[i+1])
            );
        end
    endgenerate

    assign cout = carry[N];
endmodule
  • 여기서 generate 블록의 이름의 FA이고, FA 안의 full_adderu라는 이름으로 생성된다.
  • 즉, 각 모듈은 아래와 같은 형태로 접근할 수 있다.
    • 시뮬레이션 및 테스트 검증 시 각 신호를 확인해볼 수 있다.
1
2
ripple_adder.FA[0].u.sum_out  // 0번째 FA 인스턴스의 sum_out 신호
ripple_adder.FA[1].u.sum_out  // 1번째 FA 인스턴스의 sum_out 신호
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.