Menu

Verilog 유한 상태 머신: 표준 FSM 패턴

프로의 방식으로 Verilog FSM을 작성하는 법 - clocked 상태 register, 조합 next-state 블록, 그리고 읽고 synthesize하기 쉬운 깔끔한 분리.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

FSM이란

유한 상태 머신은 다음 일을 하는 컨트롤러입니다.

  • 어느 시점이든 고정된 이름 있는 상태 집합 중 하나를 유지.
  • 입력(과 가능하면 현재 상태)에 따라 상태 간 전이.
  • 현재 상태(와 가능하면 현재 입력)에 의존하는 출력 생산.

이것이 놀랍게도 많은 디지털 설계를 다룹니다: 신호등 컨트롤러, UART 송신기, 메모리 컨트롤러, 네트워크 프로토콜 핸들러, 명령어 디코더, 별개의 동작 모드를 가진 모든 것.

FSM이 datapath 로직과 다르게 느껴지는 점은 이 아니라 상태 라는 점입니다. 카운터는 1, 2, 3, 4로 흘러갑니다. FSM은 IDLE, FETCHING, BUSY, DONE으로 흘러갑니다 - 같은 모양이지만 상태에 이름이 있고 전이에 의미가 있습니다.

2-프로세스 패턴

표준 Verilog FSM은 always 블록 을 사용합니다.

  1. clocked 블록 이 매 클럭 edge에서 상태 register를 포착합니다. non-blocking assignment를 사용합니다. 작음 - 보통 세 줄.
  2. 조합 블록 이 현재 상태에 대한 case로 다음 상태와 출력을 계산합니다. blocking assignment를 사용합니다. "흥미로운" 코드가 여기 있습니다.

이 분리에는 세 가지 이점이 있습니다: 상태를 명시적으로 생각하게 강제하고, "register 하나와 조합 블록 하나"로 깔끔히 매핑되는 하드웨어를 만들고, 표준이라 누가 봐도 즉시 알아봅니다.

동작 예시: sequence detector

고전적 교육용 FSM: 직렬 입력에서 비트 시퀀스 1011을 검출. 시퀀스가 완료되면 1 사이클짜리 pulse를 출력.

2-블록 구조가 위의 글머리표대로입니다. 다듬는 세부 사항도 짚어 둘 가치가 있습니다.

  • 조합 블록 맨 위의 기본값. next_state = state(그대로 있기)와 detected = 1'b0(pulse 없음)이 "아무 일 안 함" 대입입니다. 각 case 분기는 차이 나는 것만 설정합니다. 이 덕분에 latch inference가 불가능합니다.
  • 상태 이름에 localparam. 모듈을 읽는 누구든 S0, S1, S2, S3로 생각하지 3'd0, 3'd1로 생각하지 않습니다. synthesizer가 치환합니다.
  • clocked 블록에서의 출력 없음. 모든 "이 상태가 무엇을 하는가" 로직은 조합 블록에 살죠. clocked 블록은 현재 상태를 들고 있는 것 외에는 책임이 없습니다.

Moore vs Mealy

Moore: 출력은 오직 현재 상태에만 의존. Mealy: 출력은 현재 상태 현재 입력에 의존.

위 예제에서 detectedS3 분기 안에서 in이 예상되는 시퀀스 완료 패턴 중 하나와 매칭될 때만 설정됩니다. 그게 Mealy 출력입니다 - state에 더해 in에 의존. Moore 버전은 "방금 검출됨"이라는 별도 상태를 두고 그 상태가 현재일 때마다 detected = 1을 설정합니다. 출력이 한 사이클 더 늦게 pulse하지만 in의 글리치에 sensitive하지 않습니다.

두 스타일 모두 유효합니다. Moore가 교과서에서 기본인 이유는 출력이 사이클 중간 입력 변경 시 글리치하지 않음이 보장되기 때문입니다. Mealy가 더 빠르고(입력 구동 출력에 register latency 없음) 많은 경우 더 작은 하드웨어를 만듭니다. 구현하는 프로토콜에 따라 고르세요.

상태 인코딩: binary, one-hot, gray

각 상태에 할당하는 비트 패턴은 면적과 속도에 의미가 있습니다.

  • Binary(S0 = 3'd0, S1 = 3'd1, ...): 가장 작은 상태 register - NN 상태에 log2N\lceil \log_2 N \rceil 비트. 디코드 로직 최대.
  • One-hot(S0 = 4'b0001, S1 = 4'b0010, ...): N 상태에 N비트. 디코드 로직이 사소(각 상태가 하나의 wire)하고 전이가 빠름. FPGA가 종종 이걸 기본으로 함.
  • Gray code: 연속 상태가 1비트만 다름. 상태 비트가 클럭 도메인을 가로지를 때 유용.

대부분의 현대 synthesis 도구가 인코딩을 골라 줍니다(Vivado, Quartus, Design Compiler 모두 각각을 시도해 최선을 고르는 자동 모드 보유). 거의 지정할 필요가 없습니다. 지정한다면 대부분 도구가 attribute annotation이나 (* fsm_encoding = "one_hot" *) pragma를 받습니다.

3-블록 변형

가끔 FSM을 갈래로 분할한 것을 봅니다: 상태용 clocked 블록 하나, next-state용 조합 블록 하나, 출력용 조합 블록 하나. 2-블록 패턴에서 출력 계산을 자기 블록으로 분리한 것입니다.

// 상태 register
always @(posedge clk) ...

// next-state 로직
always @(*) ...

// 출력 로직
always @(*) begin
    case (state)
        ...
    endcase
end

분리된 출력 스타일은 출력이 크고 next-state 로직이 같은 블록에 들어가면 어수선해질 때 유용합니다. 작은 FSM에는 과합니다.

FSM에서 default가 하는 일

모든 FSM의 case 문은 default 분기를 가져야 합니다. 두 가지 이유.

  1. 안전: 상태 register가 어쩌다 잘못된 값을 가지면(corruption, 버그, 부분 reset) default가 알려진 상태로 되돌립니다.
  2. Synthesis 힌트: 명시적 case가 exhaustive할 때(예: 2비트 상태에서 4개 값을 모두 처리) default: next_state = 'x;는 synthesizer에게 "default가 도달 불가임을 약속하니 자유롭게 최적화하라"고 알립니다. 도달 불가 경로가 시뮬레이션에서 실제로 부딪치면 결과 x가 전파되어 버그가 즉시 드러납니다.
default: begin
    next_state = S0;   // 안전 복구
    // 또는
    next_state = 'x;   // 도달 불가, 자유롭게 최적화
end

default가 정말 도달 불가임을 증명했는지에 따라 고르세요.

주의할 점

조합 블록 맨 위의 기본값 잊기. next_state = state와 출력 기본값 없이는 모든 것을 대입하지 않는 분기가 latch를 새 나가게 합니다.

clocked 블록에 출력 두기. detected <= 1always @(posedge clk) 블록 안에 살면 출력이 register됩니다 - 한 사이클 늦게 나타납니다. 의도일 수 있지만("registered Mealy" 출력), 사양이 즉시 pulse를 요구할 때 흔한 사고입니다.

blocking과 non-blocking 섞기. clocked 블록: <=. 조합 블록: =. 한 블록에서 둘을 섞으면 race condition.

next_state를 참조하고 state에 대입하는 조합 always 블록. 시뮬레이터가 해결할 수 없는 피드백 루프를 만듭니다. clocked 블록이 state를 소유하고, 조합 블록이 next_state를 소유하며, 어느 쪽도 다른 쪽의 변수를 건드리지 마세요.

다음에 볼 내용

이제 기술할 수 있는 어떤 컨트롤러든 만들 수 있습니다. 다음 장은 synthesizable 설계에서 한 발 물러나 그것을 검증하는 testbench를 다룹니다 - 자극을 구동하고, 출력을 관찰하고, 파형을 dump하고, 모듈이 실제로 의도대로 동작하는지 검증하는 방법.

자주 묻는 질문

Verilog의 유한 상태 머신이란?

FSM은 작은 이름 있는 상태 집합 중 하나를 유지하면서 입력에 따라 그것들 사이를 전이하는 컨트롤러입니다. Verilog에서 표준 구현은 두 블록을 가집니다: 매 클럭 edge에서 상태 register를 갱신하는 clocked always, 그리고 현재 상태와 입력에 기반해 다음 상태와 출력을 계산하는 조합 always.

Verilog의 표준 FSM 패턴은?

2-프로세스 FSM: clocked always @(posedge clk) 블록 하나가 상태 register를 들고 non-blocking assignment를 쓰며, 조합 always @(*) 블록 하나가 현재 상태에 대한 case로 다음 상태와 출력을 계산합니다. 이 분리가 코드를 읽기 쉽고, lint하기 쉽고, 깔끔히 synthesize되게 만듭니다.

Mealy와 Moore FSM의 차이는?

Moore FSM에서 출력은 오직 현재 상태에만 의존합니다. Mealy FSM에서 출력은 현재 상태와 현재 입력 모두에 의존합니다. Mealy 머신이 한 사이클 더 빠르게 반응합니다(입력 의존 출력에 register latency 없음) - 하지만 입력이 사이클 중간에 변하면 glitch를 만들 수 있습니다. Moore 머신이 한 사이클 더 느리지만 더 예측 가능합니다 - 속도가 필요하지 않다면 기본 선택입니다.

Verilog에서 상태는 어떻게 인코딩하나요?

모듈 안에 localparam 상수를 사용합니다: localparam IDLE = 3'd0; 등. 세 가지 흔한 인코딩: binary(상태 0, 1, 2, ... - 가장 작은 상태 register), one-hot(상태당 1비트, 전이마다 로직 단계 더 적음), gray code(연속 상태가 1비트만 다름 - glitch 최소화). synthesis 도구가 보통 인코딩을 자동으로 골라 줍니다. 명시적으로 지정하는 일은 드뭅니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기