Verilog 미니 프로젝트 5가지, 입문자의 단계별 코스

Verilog 미니 프로젝트 5가지, 입문자의 단계별 코스

개울을 건널 때 한 번에 건너편으로 뛰는 사람은 없습니다. 눈에 보이는 징검돌을 하나씩 밟으면서 발의 감각과 중심 잡는 법을 익히죠.

Verilog 미니 프로젝트도 징검돌과 비슷합니다. 문법을 전부 배운 뒤에 큰 프로젝트에 도전하기보다, 작은 회로를 하나씩 만들며 개념을 하나씩 쌓는 편이 훨씬 오래 남습니다.

이 글은 입문자가 난도 순서로 만들어 볼 다섯 가지 프로젝트와, 각각에서 배우는 개념을 설명합니다. 코드는 핵심만 짧게 보이고, 시뮬레이터와 보드는 제품명을 정하지 않습니다.

Verilog 미니 프로젝트를 고르는 기준

입문용 Verilog 미니 프로젝트는 세 가지 조건을 만족하면 좋습니다. 입력과 출력이 분명하고, 시뮬레이션으로 정답을 확인할 수 있으며, 한 번에 새로운 개념이 하나만 추가되어야 합니다.

새 개념이 한꺼번에 둘 이상 들어오면 오류가 났을 때 원인을 가리기 어렵습니다. 그래서 다섯 프로젝트는 조합 논리, 순차 논리, 입력 정리, 상태 머신, 통신 순서로 개념을 하나씩 더해 갑니다.

모든 프로젝트는 테스트벤치(Testbench)를 함께 만드는 것을 원칙으로 삼으세요. 보드가 없어도 시뮬레이션만으로 검증할 수 있고, 검증하는 습관은 설계만큼 중요합니다.

미니 프로젝트 다섯 가지, 난도 순서로 따라가기

프로젝트 1, 4비트 가산기로 배우는 조합 논리

두 개의 4비트 입력을 더해 5비트 결과를 내는 회로입니다. 입력이 바뀌면 출력이 곧바로 바뀌는 조합 논리(Combinational Logic)의 기본을 배웁니다.

결과를 5비트로 잡는 이유가 핵심 학습 포인트입니다. 4비트끼리 더하면 최대 15 + 15 = 30이어서 4비트(0부터 15)로는 담을 수 없고, 5비트(0부터 31)가 필요합니다.

4비트 가산기module adder4 (input [3:0] a, input [3:0] b, output [4:0] sum);
  assign sum = a + b;
endmodule

테스트벤치 예module tb_adder4;
  reg [3:0] a, b;
  wire [4:0] sum;
  adder4 dut (.a(a), .b(b), .sum(sum));
  initial begin
    a = 4’d9; b = 4’d8;
    #10 $display(“sum = %0d”, sum);
    $finish;
  end
endmodule

9 + 8 = 17이므로 시뮬레이션 출력에 17이 나오면 정상입니다. 확장으로는 입력 조합 256가지(2⁸)를 반복문으로 모두 검사해 보거나, 비트 폭을 매개변수로 바꿔 보는 연습이 좋습니다.

프로젝트 2, 카운터와 분주기로 배우는 순차 논리

클록이 들어올 때마다 값이 하나씩 올라가는 카운터입니다. 클록에 맞춰 값을 저장하는 순차 논리(Sequential Logic)와 리셋의 역할을 처음 만납니다.

보드의 클록이 50 MHz라고 가정하면 1초는 50,000,000 클록입니다. 이 수를 세려면 2²⁵ = 33,554,432는 부족하고 2²⁶ = 67,108,864면 충분하므로 26비트 카운터가 필요합니다.

주기 신호 만들기module tick_gen #(parameter N = 50_000_000) (input clk, input rst_n, output reg tick);
  reg [25:0] cnt;
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin cnt <= 0; tick <= 1’b0; end
    else if (cnt == N – 1) begin cnt <= 0; tick <= 1’b1; end
    else begin cnt <= cnt + 1; tick <= 1’b0; end
  end
endmodule

이 코드는 N 클록마다 한 클록 동안만 1이 되는 tick 신호를 만듭니다. tick을 다른 회로의 동작 허가 신호로 쓰면 1초에 한 번 움직이는 회로를 만들 수 있고, LED 점멸이 대표적인 예입니다.

클록을 직접 나눠 새 클록으로 쓰기보다 tick 신호를 쓰는 방식은 하나의 클록 안에서 타이밍을 관리할 수 있어 구조가 단순해집니다. 이 점은 나중에 큰 설계로 갈 때 중요해집니다.

프로젝트 3, 버튼 입력 정리로 배우는 디바운스와 에지 검출

사람이 누르는 버튼은 접점이 흔들려 짧은 시간 동안 0과 1을 여러 번 오갈 수 있습니다. 이 현상을 채터링이라 하고, 신호가 안정될 때까지 기다리는 회로를 디바운서(Debouncer)라고 합니다.

만드는 순서는 먼저 버튼 입력을 클록에 맞춰 레지스터 두 단으로 받아 동기화하고, 같은 값이 일정 시간 유지될 때만 출력을 바꾸게 하는 것입니다. 마지막으로 이전 값과 현재 값을 비교해 0에서 1로 바뀌는 순간 한 클록만 1이 되도록 에지 검출(Edge Detection)을 붙입니다.

이 프로젝트에서 배우는 것은 외부 신호가 내 클록과 무관하게 바뀐다는 사실과 그 신호를 안전하게 받는 습관입니다. 서로 다른 클록 사이로 신호를 건네는 문제로 이어지는 개념의 출발점이기도 합니다.

테스트벤치에서 짧은 펄스를 여러 번 만들어 흔들림을 흉내 내고, 출력이 한 번만 바뀌는지 파형으로 확인해 보세요.

프로젝트 4, 신호등 제어기로 배우는 상태 머신

초록, 노랑, 빨강이 차례로 바뀌는 신호등은 유한 상태 머신(FSM, Finite-State Machine)의 대표적인 예제입니다. 현재 상태와 입력으로 다음 상태가 정해진다는 개념을 코드로 옮기는 연습입니다.

신호등 상태 머신의 뼈대module light_fsm (input clk, input rst_n, input done, output reg [1:0] state);
  localparam GREEN = 2’d0, YELLOW = 2’d1, RED = 2’d2;
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) state <= GREEN;
    else if (done) begin
      case (state)
        GREEN: state <= YELLOW;
        YELLOW: state <= RED;
        RED: state <= GREEN;
        default: state <= GREEN;
      endcase
    end
  end
endmodule

상태가 바뀌는 조건인 done은 프로젝트 2의 tick과 카운터로 만들 수 있고, 상태마다 지속 시간이 다르면 그 시간을 상태별로 정해 주면 됩니다. 코드를 쓰기 전에 원과 화살표로 상태 그림을 먼저 그리면 실수가 크게 줄어듭니다.

case 문에 default를 두는 것은 정의되지 않은 상태 값에 들어가도 안전한 상태로 돌아오게 하려는 습관입니다. 상태가 늘어날 때 이 습관이 큰 도움이 됩니다.

프로젝트 5, UART 송신기로 배우는 통신 순서

UART(Universal Asynchronous Receiver/Transmitter)는 선 하나로 비트를 차례로 보내는 직렬 통신 방식입니다. 한 글자를 보낼 때 시작 비트, 데이터 비트, 정지 비트의 순서로 전송하며, 여기서는 데이터 8비트에 패리티가 없는 형식을 가정합니다.

송신기에는 앞 프로젝트의 부품이 모두 들어갑니다. 전송 속도에 맞춰 tick을 만드는 카운터, 비트 순서를 관리하는 상태 머신, 데이터를 한 비트씩 내보내는 시프트 동작이 모입니다.

가상의 예로 클록이 50 MHz이고 전송 속도가 9600 baud라면 한 비트의 시간은 50,000,000 ÷ 9600 ≈ 5208.33 클록입니다. 정수 5208 클록을 쓰면 오차는 약 0.006% 수준이어서 통신에 충분하고, 이 값을 담으려면 13비트(2¹³ = 8192)가 필요합니다.

한 글자를 보내는 데 필요한 비트는 시작 1개, 데이터 8개, 정지 1개로 총 10개이므로 5208 × 10 = 52,080 클록이 걸립니다. 송신 테스트벤치에서 이 간격이 파형으로 일정하게 나오는지 확인하면 검증이 됩니다.

입문자가 자주 막히는 지점

첫째는 순차 always 블록 안에서 =와 <=를 섞어 써도 결과는 같다는 생각입니다. 클록에 맞춰 값을 저장하는 블록에는 논블로킹 대입(<=)을, 조합 논리 블록에는 블로킹 대입(=)을 쓰는 것이 기본 규칙입니다.

Verilog 미니 프로젝트에서 둘째로 흔한 실수는 조합 논리의 if 문에 else를 빠뜨리는 것입니다. 모든 경우에 출력 값을 정해 주지 않으면 도구가 이전 값을 기억하는 래치를 만들 수 있으므로 기본값을 먼저 정해 두는 습관이 좋습니다.

셋째는 리셋은 없어도 알아서 0으로 시작한다는 생각입니다. 시뮬레이션에서는 값을 정해 주지 않으면 알 수 없는 값(x)으로 시작하므로 리셋으로 초기 상태를 분명히 만들어야 합니다.

넷째는 보드에 올려서 동작하면 검증은 끝난다는 생각입니다. 보드에서는 보이지 않는 경계 조건이 많아서 테스트벤치로 시나리오를 만들어 파형을 확인하는 단계가 꼭 필요합니다.

다섯 프로젝트 비교 표

구분핵심 개념검증 방법확장 아이디어
1. 4비트 가산기조합 논리, 비트 폭입력 256가지 전수 검사캐리 입력, 비트 폭 매개변수화
2. 카운터와 분주기순차 논리, 리셋, tick파형에서 주기 확인설정 가능한 주기, 일시 정지
3. 버튼 입력 정리동기화, 디바운스, 에지 검출흔들리는 입력을 흉내 낸 파형길게 누름 구분
4. 신호등 제어기상태 머신, 상태 전이상태 전이 순서 확인보행자 버튼 추가
5. UART 송신기전송 속도 생성, 상태 머신, 시프트비트 간격 5208 클록 확인수신기 추가

핵심 정리

Verilog 미니 프로젝트는 조합 논리, 순차 논리, 입력 정리, 상태 머신, 통신 순서의 다섯 징검돌로 이어집니다. 각 단계에서 새 개념을 하나만 더하고 테스트벤치로 검증하면, 오류가 났을 때 원인을 좁히기 쉽고 배운 것이 오래 남습니다.

개울을 건너는 사람이 돌 하나에서 다음 돌로 중심을 옮기듯, 한 프로젝트의 부품이 다음 프로젝트의 재료가 됩니다. 가산기와 테스트벤치부터 한 개를 끝까지 만들어 보면 다음 돌이 훨씬 가깝게 느껴질 것입니다.

면접 대비 질문

면접 대비 질문Verilog로 만들어 본 프로젝트 하나를 골라, 상태 설계와 검증 방법, 그리고 겪은 오류와 해결 과정을 설명해 주세요.

[답변 포인트] 프로젝트의 입력과 출력, 상태가 어떻게 나뉘는지를 상태 그림으로 먼저 설명합니다. 테스트벤치로 어떤 시나리오를 확인했는지, 파형에서 오류를 어떻게 찾았는지를 구체적으로 말합니다. 리셋 누락이나 대입 방식 혼용처럼 실제로 겪은 문제와 고친 방법을 덧붙이면 설계와 검증을 함께 이해하고 있다는 인상을 줍니다.