1. 상태 기계는 레지스터 하나와 조합 블록 두 개입니다
지금까지 이 과정의 순차 회로는 모두 숫자를 상태로 가진 상태 기계였습니다. 카운터의 상태는 그 count입니다. 좁은 뜻의 상태 기계는 이름 붙은 상태 몇 개를 가지며, 이미 아는 세 부분으로 만듭니다.
| 부분 | 종류 | 계산하는 것 |
|---|---|---|
| 상태 레지스터 | 레지스터. <=을 쓰는 always @(posedge clock) | 현재 상태를 유지하고 에지에서 다음 상태를 받음 |
| 다음 상태 논리 | 조합. case를 쓰는 always @* | 현재 상태와 입력에서 다음 상태 |
| 출력 논리 | 조합. assign 또는 always @* | 현재 상태에서 출력. Mealy 기계는 입력도 읽음 |
출력이 상태에만 의존하면 Moore 기계, 입력도 직접 읽으면 Mealy 기계입니다. 가정은 이 과정의 다른 곳과 같습니다. 클록 하나, 상승 에지, 동기 리셋입니다.
2. 상태표가 코드보다 먼저 모든 것을 정합니다
레지스터
단계·관찰 시점·파형 장의 상승 검출기에서 기억에 이름을 붙입니다. 레지스터 prev
하나가 두 값을 가지므로 상태는 두 개입니다. 마지막 표본이 0이었다는 LOW와 1이었다는
HIGH입니다. 입력은 level이고, 출력 rise는 지금 상승이 보일 때 1입니다.
| 현재 상태 | level | 다음 상태 | rise |
|---|---|---|---|
LOW | 0 | LOW | 0 |
LOW | 1 | HIGH | 1 |
HIGH | 0 | LOW | 0 |
HIGH | 1 | HIGH | 0 |
각 행은 에지 하나입니다. 에지 이전의 상태와 입력, 에지 이후의 상태입니다. rise는 입력을
읽으므로 Mealy 출력입니다. 표는 상태 2개 × 입력 값 2개 = 4행이고, 진리표처럼 코드를 쓰기 전에
모든 조합의 행이 있어야 합니다.
확인 문제
기계가 HIGH 상태이고 level이 1입니다. 다음 상태와 rise는 무엇입니까?
3. 코드는 표를 부분별로 따릅니다
상태표는 RiseFsm.v의 블록 세 개가 됩니다. 표를 옆에 두고 한 부분씩 읽어 보세요.
// 상태표에서 만든 두 상태 상승 검출기.
module rise_fsm (
input clock,
input reset,
input level,
output rise,
output reg pulse,
output reg state
);
localparam LOW = 1'b0, HIGH = 1'b1;
reg next;
// 다음 상태 논리: 현재 상태마다 분기 하나를 두고, 모든 경로에서 값을 대입한다.
always @* begin
case (state)
LOW: next = level ? HIGH : LOW;
HIGH: next = level ? HIGH : LOW;
default: next = LOW;
endcase
end
// 상태 레지스터: state에 값을 쓰는 유일한 블록이다.
always @(posedge clock) begin
if (reset) state <= LOW;
else state <= next;
end
// Mealy 출력: 현재 상태와 이번 사이클의 입력을 함께 읽는다.
assign rise = (state == LOW) & level;
// rise를 레지스터에 담은 사본: 한 에지 늦게, 한 주기 전체 동안 유지된다.
always @(posedge clock) begin
if (reset) pulse <= 1'b0;
else pulse <= rise;
end
endmodule| 코드 | 부분 | 상태표와 맞춰 볼 것 |
|---|---|---|
localparam LOW = 1'b0, HIGH = 1'b1; | 상태 인코딩 | 상태마다 이름 하나. 상태 두 개는 1비트에 들어갑니다 |
always @* case (state) | 다음 상태 논리 | 상태마다 분기 하나, 분기마다 level의 두 값을 모두 처리, 그리고 default |
state를 쓰는 always @(posedge clock) | 상태 레지스터 | state를 쓰는 유일한 블록. 리셋을 먼저 처리 |
assign rise = (state == LOW) & level; | 출력 논리 | 표의 rise 열. LOW이고 level이 1인 행에서만 1 |
두 번째 always @(posedge clock) | 레지스터 출력 | pulse는 한 에지 늦은 rise |
localparam은 상태에 이름을 줍니다. 상태 두 개는 1비트에, 세 개나 네 개는 2비트에 들어갑니다.
case에는 상태마다 분기 하나와 default가 있으므로 net·변수·조합
always 장의 요구대로 모든 경로에서 next가 대입됩니다. 상태 레지스터는 state를
건드리는 유일한 클록 블록이고, 리셋이 기계를 알려진 상태에 놓습니다. 리셋이 없으면 state는
처음에 x이고, case (state)는 어느 이름 붙은 상태와도 일치하지 않아 default 분기에서
next를 LOW로 정합니다. 이때도 level이 0이면 x & 0은 0이므로 rise를 확정할 수
있습니다. level이 1이면 상태가 확정될 때까지 rise는 x입니다. 출력
rise는 상태와 입력에서 배선으로 만들고, pulse는 그것을 레지스터에 담습니다. 실습 문제
rise-pulse가 요구한 판이 이 pulse입니다. 여기서 state를 포트로 내보낸 것은 테스트벤치가
출력할 수 있게 하려는 것뿐이며, 보통 상태 기계는 상태를 안에 감춥니다.
확인 문제
어느 기계에 이름 붙은 상태가 다섯 개 있습니다. 상태 레지스터는 최소 몇 비트가 필요합니까?
4. 기계를 사이클 단위로 예측합니다
브라우저 모델 rise-detect는 prev를 상태로 둔 이 기계입니다. prev 0이 LOW, prev 1이
HIGH입니다. 모델의 rise는 사이클 시작 시점의 상태와 그 사이클에 넣은 입력으로 계산한,
사이클 동안의 조합 출력입니다. 먼저 몇 사이클 조작해 보세요.
개념 실험 · rise-detect
컴파일한 설계가 아니라 브라우저 안의 회로 모델입니다. 실제 도구 체인은 예제에서 실행됩니다.
입력
출력
- prev
- 0
- rise
- 0
아직 에지가 없습니다. 입력을 정한 뒤 클록 에지를 인가하세요.
이제 표를 채우세요. 각 사이클을 끝내는 에지 뒤의 prev와 사이클 동안의 rise입니다.
예측한 뒤 비교 · rise-detect
비교하기 전에 모든 칸을 채우세요. 빈칸도 불일치로 처리합니다.
| 사이클 | in에지에서 샘플링한 입력 | prev에지 직후 | rise출력 |
|---|---|---|---|
| 0 | 1 | ||
| 1 | 1 | ||
| 2 | 0 | ||
| 3 | 1 | ||
| 4 | 0 | ||
| 5 | 0 |
5. 기계를 실행해 상태표와 비교합니다
테스트벤치는 상승 에지 두 번에 걸친 리셋 뒤에 패턴 0111010011을 사이클마다 한 값씩 level에
넣습니다. level은 하강 에지에서 바꾸고 1 시간 단위 뒤에 출력하므로, 각 줄은 한 사이클에 대해
다음을 보여 줍니다.
| 열 | 출력 시점의 뜻 |
|---|---|
level | 이번 사이클에 넣은 입력. 다음 상승 에지가 이 값을 샘플링합니다 |
state | 사이클 시작 시점, 즉 그 에지 이전의 상태 |
rise | state와 level로 계산한 이번 사이클의 Mealy 출력 |
pulse | 직전 에지가 정한 레지스터 출력 |
실행하기 전에 상태표로 열 사이클의 state, rise, pulse 열을 적어 보세요. 패턴에는 1이 세 번
이어지는 구간이 있으므로, 계속 1로 머무는 level을 새 상승으로 세면 안 됩니다.
확인 문제
level = 0 1 1 1 0 1 0 0 1 1일 때, 열 사이클 가운데 rise가 1인 사이클은 몇 개입니까?
확인 문제
출력된 pulse가 처음 1이 되는 사이클은 몇 번입니까?
module tb;
reg clock = 0;
reg reset = 1;
reg level = 0;
wire rise, pulse, state;
reg [9:0] pattern = 10'b01110_10011;
integer i;
integer rises = 0;
rise_fsm dut (.clock(clock), .reset(reset), .level(level), .rise(rise), .pulse(pulse), .state(state));
always #5 clock = ~clock;
initial begin
$dumpfile("dump.vcd");
$dumpvars(0, tb);
// 상승 에지 두 번 동안 리셋을 유지하고, 하강 에지에서 해제한다.
@(negedge clock);
@(negedge clock) reset = 0;
for (i = 0; i < 10; i = i + 1) begin
// 입력은 하강 에지에서 바뀐다. 1 시간 단위 뒤의 출력 줄은 다음 상승 에지 직전의 상태와 이번 사이클의 출력을 보여 준다.
level = pattern[9 - i];
#1 $display("cycle %0d: level=%b state=%s rise=%b pulse=%b",
i, level, state ? "HIGH" : "LOW ", rise, pulse);
rises = rises + rise;
@(negedge clock);
end
$display("DONE rises=%0d", rises);
$finish;
end
endmodule이 개념 실습하기
다음 단계로 짧은 Verilog 문제에 이 개념을 적용해 보세요.
실행을 누르고 모든 줄을 예측한 열과 비교하세요. 마지막 줄 DONE rises=3은 테스트벤치가 열
사이클 동안의 rise를 더한 값입니다. 그다음 파형을 엽니다.
| 신호 | 확인할 것 |
|---|---|
tb.dut.state | 상승 에지에서만 바뀌고, 각 에지 뒤에는 그 에지가 샘플링한 level과 같습니다 |
tb.rise | level이 올라가는 하강 에지, 곧 사이클 중간에 1이 되고, 상태가 HIGH가 되는 다음 상승 에지에서 0으로 내려갑니다 |
tb.pulse | rise를 다음 상승 에지로 옮긴 모양이며 폭은 정확히 한 주기입니다 |
출력의 어느 줄이 상태표와 다르면, 먼저 파형에서 그 사이클을 찾고 상태, 다음 상태 논리, 출력 논리 가운데 어느 부분이 틀린 값을 만들었는지 따져 보세요.
6. 파형에는 세 종류의 신호가 서로 다르게 보여야 합니다
상태 기계를 파형에서 확인한다는 것은 각 신호가 어떻게 움직여도 되는지 미리 안다는 뜻입니다.
| 신호 | 종류 | 바뀌어도 되는 시점 |
|---|---|---|
state | 레지스터 | 상승 에지에서만 |
rise | Mealy 출력. 조합 | level이나 state가 바뀔 때마다. 에지 사이에서도 |
pulse | 레지스터 출력 | 상승 에지에서만. rise보다 한 에지 뒤 |
state가 상승 에지 사이에 바뀌면 감지 목록이 잘못되었거나 클록 블록 밖에서 대입하는 곳이
있는지 확인하세요. always @(posedge clock) 안의 blocking 대입 =는 경쟁 조건을 만들 수
있지만, 그것만으로 블록이 에지 사이에 실행되지는 않습니다. level이
흔들릴 때 rise가 에지 사이에 깜박이면 Mealy 출력으로서 올바른 동작이며, 출력이 한 사이클
내내 안정해야 할 때 레지스터에 담는 까닭이 그것입니다.
확인 문제
rise_fsm의 신호 가운데 두 클록 에지 사이에 바뀌어도 정당한 것은 어느 것입니까?
7. 전이 하나를 망가뜨리고 다시 검증합니다
상태 기계는 상태표의 모든 행이 맞아야 올바릅니다. 다음 수정을 하나씩 해 보고, 매번 먼저 예측한 뒤 실행하고, 다음 수정 전에 원래 코드로 되돌리세요.
| 수정 | 먼저 예측할 것 |
|---|---|
HIGH: next = level ? HIGH : LOW;를 HIGH: next = level ? HIGH : HIGH;로 | 출력이 보고하는 상승 횟수, 그리고 몇 번 사이클부터 state가 더는 바뀌지 않는지 |
pattern = 10'b01110_10011을 10'b11111_00000으로 | 리셋 직후 첫 사이클에 이미 1인 level을 상승으로 셀지 |
확인 문제
HIGH 분기를 level ? HIGH : HIGH로 바꾸면 마지막 줄에 무엇이 출력됩니까?
확인 문제
패턴을 1111100000으로 바꾸면 rise가 1인 사이클은 몇 번입니까?
첫 번째 수정은 아래 실습 문제의 시작 코드에 숨겨 둔 오류와 같습니다. 두 번째 수정은 상태의 리셋 값도 계약의 일부라는 것을 보여 줍니다. 리셋 직후 첫 사이클이 무엇과 비교할지를 그 값이 정하기 때문입니다.
8. 같은 출력 기록에서 보는 Moore와 Mealy
방금 실행한 출력에서 rise와 pulse는 같은 사건을 서로 다른 시점에 표시합니다.
| 출력 | 읽는 것 | level의 상승을 보여 주는 시점 | 종류 |
|---|---|---|---|
rise | 상태와 입력 | 0 다음 level이 1인 사이클 안, 에지 이전 | Mealy |
pulse | 레지스터만 | 그다음 사이클, 에지에서 에지까지 | Moore와 같은 시점 |
출력이 상태만 읽는 기계라면 "방금 상승을 샘플링했다"는 뜻의 상태가 필요합니다. 검출기에 상태
LOW, ROSE, HIGH 세 개를 둡니다. LOW에서 level이 1이면 ROSE로 가고, ROSE에서는 1이면
HIGH로, 0이면 LOW로 돌아가며, Moore 출력은 ROSE에서 1입니다. 이 출력은 에지에서만 바뀔 수
있으므로 Mealy rise보다 한 사이클 늦습니다.
확인 문제
세 상태 LOW/ROSE/HIGH 검출기의 Moore 출력은 출력 기록의 어느 열과 사이클마다 같습니까?
받는 쪽이 무엇을 필요로 하는지로 고릅니다. Mealy 출력은 같은 사이클에 답하지만 입력의 글리치도 그대로 따라갑니다. Moore 출력은 사이클 내내 안정하지만 한 사이클 지연이 생기고, 이 예에서는 상태도 하나 더 필요합니다.
9. 변형: 두 상태 Moore 기계인 토글
표를 바꾸면 같은 세 부분이 다른 기계를 만듭니다. 토글은 상태 OFF와 ON, 입력 t를
가집니다. t가 1이면 다음 상태는 다른 쪽이고, t가 0이면 같은 쪽입니다. 출력 q는 ON에서
1이며 상태만 읽으므로 Moore 기계입니다. 코드에서는 case의 분기와 assign만 바뀝니다. 각
에지 뒤의 q를 예측하세요.
예측한 뒤 비교 · toggle
비교하기 전에 모든 칸을 채우세요. 빈칸도 불일치로 처리합니다.
| 사이클 | t에지에서 샘플링한 입력 | q에지 직후 |
|---|---|---|
| 0 | 1 | |
| 1 | 1 | |
| 2 | 0 | |
| 3 | 1 | |
| 4 | 0 |
10. 적용: 기계를 고치고, 직접 설계하기
이 장은 실습 문제 두 개로 마무리합니다. 두 번째 문제는 도움이 더 적습니다.
| 문제 | 주어지는 것 | 할 일 |
|---|---|---|
| 상태 기계에서 잘못된 전이 찾기 | 이 장의 검출기, 다만 분기 하나가 틀림 | 출력에서 증상을 찾고, case를 상태표와 비교해 빠진 행을 찾아 고칩니다 |
| 계약에서 세 상태 검출기 설계하기 | 포트와 계약만. din의 마지막 두 샘플이 1이면 seen이 1인 Moore 출력 | 상태표를 먼저 쓰고, 상태 레지스터, 다음 상태 case, 출력 논리를 작성합니다 |
두 번째 문제의 채점은 계약을 지키는 인코딩이면 무엇이든 통과시키며, 실패하면 첫 번째로 틀린 사이클, 에지 전후 어느 시점의 검사인지, 기대값과 실제값을 알려 줍니다.
11. 정리
| 요소 | 뜻 |
|---|---|
| 상태표 | 상태와 입력마다 한 행. 다음 상태와 출력 |
localparam LOW = … | 이름 붙은 상태 인코딩. n비트는 최대 2ⁿ 상태 |
default가 있는 always @* case (state) | 다음 상태 논리. 모든 경로에서 대입 |
리셋이 있는 always @(posedge clock) | 상태 레지스터. state를 쓰는 유일한 곳 |
| Moore / Mealy | 출력이 상태만 읽어 에지에서만 바뀜 / 상태와 입력을 읽어 사이클 중간에도 바뀜 |
| 출력에서 처음 틀린 줄 | 증상입니다. 상태, 다음 상태 분기, 출력 논리 가운데 어디서 생겼는지 거슬러 찾습니다 |
이런 기계를 구동하는 테스트벤치 기법은 테스트벤치 기법 장에, 신호를 VCD로 기록하는 방법은 파형 기록 장에 있습니다.