본문으로 건너뛰기
code0.HDL

guard: 유지와 정지

rule의 명시 조건으로 레지스터를 유지하고, 카운터를 한계값에서 정확히 멈추며, 한 rule이 남긴 상태가 다음 rule의 조건을 만족하지 못할 때 생기는 조용한 빈 구간을 찾습니다.

이 장에서 배울 내용

  • guard를 rule이 실행될 수 있는 사이클 시작 값의 집합으로 읽고, 스스로 멈추는 카운터가 어디서 멈추는지 예측합니다
  • 유지를 쓰기가 없는 상태, 즉 BSV에서 enable이 0인 경우로 설명합니다
  • 명시 조건과 호출한 method의 implicit condition을 rule의 결합 조건으로 묶습니다
  • 한 rule이 멈추는 값과 다음 rule이 기다리는 조건 사이의 빈 구간을 찾습니다
이 장에서 배우는 개념
  • guard·유지·정지 조건
먼저 알아야 할 개념

1. guard는 rule이 실행될 수 있는 사이클을 고릅니다

1.1 명시 조건

rule 이름 뒤 괄호 안의 Bool 식이 명시 조건이며 보통 guard라고 부릅니다. 명시 조건은 사이클 시작 상태를 입력으로 하는 조합 논리가 되고 매 사이클 평가됩니다. 참이면 3절에서 다룰 method의 준비 조건도 만족해야 rule이 준비됩니다. 거짓이면 이 rule은 아무 일도 하지 않으며, 다른 rule이 쓰지 않는 레지스터는 값을 유지합니다.

형식

rule 이름 (명시_조건);
  동작;
endrule
rule tick (running && count < 10);
  count <= count + 1;
endrule

rule always_on;          // 조건이 없으면 (True)와 같습니다
  $display("tick");
endrule
규칙내용
guard가 읽을 수 있는 것범위 안의 모든 상태와 값. 레지스터, 다른 값, method 결과
평가 시점매 사이클, 그 사이클 시작에 저장된 값으로
생략하면명시 조건은 True이지만 호출한 method의 준비 조건이 rule을 막을 수 있습니다
정하는 것이 사이클에 rule이 실행될 수 있는지. 쓰는 값은 정하지 않습니다

guard는 일반 Bool 식이므로 Bool·Bit와 순수 함수 장의 연산자로 조건을 조합합니다. running && count < 10은 두 부분이 모두 참인 사이클에만 준비됩니다.

1.2 유지는 별도의 명령이 아닙니다

레지스터는 실행된 어떤 rule이 쓸 때만 바뀝니다. "유지"라는 문장은 따로 없습니다. 한 사이클에 어떤 rule도 쓰지 않은 레지스터는 에지를 지나도 값을 그대로 가집니다. 따라서 거짓인 guard는 공통 기초의 enable(enable은 갱신과 유지 가운데 하나를 고릅니다)과 같습니다. 레지스터로 들어가는 신호가 아니라 갱신이 일어나는 조건으로 적힌 것입니다.

공통 기초BSV
enable = 1: 레지스터가 D를 저장guard가 참이고 rule이 실행됨. <= 쓰기가 저장됩니다
enable = 0: 레지스터가 Q를 유지guard가 거짓. 쓰기가 없고 레지스터는 값을 유지합니다
리셋mkReg에 준 초깃값. 사이클 0 전에 적용됩니다

주의. 거짓인 guard는 아무것도 리셋하지 않습니다. 멈춘 rule이 마지막으로 5를 쓴 레지스터는 여러 사이클 뒤에도 5를 가집니다. 값을 바꾸는 것은 다른 rule의 쓰기나 리셋뿐입니다.

1.3 준비와 실행

이 과정은 두 단어를 구분합니다. 명시 조건과 implicit condition이 모두 참이면 rule이 준비된 것이고, 그 사이클에 실제로 실행되면 실행된 것입니다. rule이 하나인 설계나 guard가 함께 참이 되는 일이 없는 설계에서는 둘이 일치합니다. 준비는 현재 상태에 대한 사실이고, 실행은 스케줄러가 준비된 rule 가운데에서 내리는 결정입니다. 준비되었지만 실행되지 않는 경우는 상태를 공유하는 rule 장에서 다룹니다.

2. guard가 검사하는 값을 쓰는 rule은 스스로 멈춥니다

rule step (count < 5);
  count <= count + 1;
endrule

step은 count를 검사하고 count를 씁니다. 그래서 실행마다 상태는 guard가 거짓이 되는 지점으로 움직입니다. 마지막 실행은 count가 4인 사이클입니다. 4를 읽고 5를 저장하며, 다음 사이클부터 count < 5는 거짓입니다. 카운터는 5에서 멈춥니다. 5는 rule이 한 번도 실행되지 않는 첫 값입니다.

guardrule이 실행되는 값rule이 읽는 마지막 값레지스터가 멈추는 값
count < 50, 1, 2, 3, 445
count <= 50, 1, 2, 3, 4, 556

<와 <=의 선택이 한계값에서 멈추는지 한 단계 지나치는지를 결정합니다. guard를 읽을 때는 어떤 값에서 본문이 실행되는지 먼저 확인하고, 그 가운데 마지막 값에서 본문이 무엇을 저장하는지 보세요. 공통 기초의 정상·경계·리셋 사례가 바로 점검할 사이클입니다. 한계값 바로 아래 값, 한계값 자체, 리셋 값을 확인합니다.

3. 호출한 method도 조건을 더합니다

지금까지 guard는 모두 직접 적은 조건이었습니다. rule이 호출하는 method도 자기 준비 조건을 가질 수 있으며, 이것을 implicit condition이라고 합니다. FIFO의 deq와 first는 FIFO가 비어 있는 동안 준비되지 않고, enq는 FIFO가 가득 찬 동안 준비되지 않습니다.

rule move;              // 명시 조건 없음
  outq.enq(inq.first);  // inq가 비어 있으면 first가 준비되지 않습니다
  inq.deq;
endrule

rule이 실제로 활성화되려면 명시 조건과 호출한 모든 method의 implicit condition이 함께 참이어야 합니다. 다른 method 안에서 호출한 method의 준비 조건도 포함됩니다. 이 과정은 이 논리곱을 결합 조건이라고 부릅니다. move에는 조건이 없지만 inq가 비어 있거나 outq가 가득 찬 동안에는 실행될 수 없습니다. 준비되지 않은 method 하나가 rule 전체를 막으며, BSV는 그 호출만 건너뛰고 본문의 나머지를 실행하지 않습니다.

직접 정의한 method도 같습니다. 아래에서 method 헤더 뒤의 if (count < 3)이 bump의 준비 조건입니다. 준비 조건을 쓰는 방법은 준비 조건·모듈 인자·합성 경계 장에서 배웁니다.

// bump는 count < 3일 때만 준비됩니다
method Action bump if (count < 3);
  count <= count + 1;
endmethod

rule drive (cycles < 5);
  c.bump;
endrule
// drive의 결합 조건: cycles < 5 && count < 3

count = 0에서 시작해 사이클마다 추적하면 다음과 같습니다.

사이클 시작 상태명시 조건bump 준비drive에지 뒤 count
cycles = 0, count = 0참참실행 가능1
cycles = 1, count = 1참참실행 가능2
cycles = 2, count = 2참참실행 가능3
cycles = 3, count = 3참거짓비활성3
cycles = 4, count = 3참거짓비활성3

마지막 두 사이클에도 cycles를 증가시키는 다른 rule은 계속 실행됩니다. method가 준비되지 않으면 그 method를 호출하는 rule이 비활성화될 뿐, 모듈 전체가 멈추지는 않습니다.

참고. 일반 Reg의 읽기와 쓰기 method에는 값에 따라 달라지는 implicit condition이 없습니다. 이 장의 모든 예제처럼 레지스터만으로 이루어진 설계에서는 명시 조건만으로 준비 여부가 정해집니다. 명시 조건이 참인데 rule이 실행되지 않으면 호출 경로에 있는 method의 준비 조건부터 확인하세요.

4. 두 조건 사이의 빈 구간은 모든 것을 조용히 멈춥니다

4.1 두 조건의 경계 맞추기

한 rule이 멈춘 상태에서 다른 rule이 이어받아야 한다면, 앞 rule의 마지막 실행이 남긴 상태가 뒤 rule의 guard를 참으로 만들어야 합니다. 멈추는 rule의 guard를 count < S, 기다리는 rule의 guard를 count == F라고 적습니다.

관계결과
S == F앞 rule이 F를 저장한 다음 사이클에 뒤 rule이 준비됩니다
S < Fcount가 S에서 멈추고 두 guard가 모두 거짓이 되어 어떤 rule도 다시 실행되지 않습니다
S > Fcount == F인 사이클에 두 guard가 모두 참입니다

S < F인 경우에는 어떤 메시지도 없습니다. 컴파일은 성공하고 시뮬레이션은 실행되지만 설계는 아무 일도 일어나지 않는 상태에 머물고, 시뮬레이터의 시간 제한이 끝낼 때까지 그대로입니다.

4.2 예제: 도달할 수 없는 조건

GuardGap.bsv는 S를 3, F를 5로 의도적으로 둔 설계입니다. finish가 영원히 준비되지 않으므로 파일에는 별도의 tick 카운터와 watchdog rule이 있습니다. cycle이 8이 되면 watchdog이 count를 출력하고 실행을 끝냅니다. 읽기만 하는 monitor rule은 cycle < 8인 동안 사이클마다 cycle과 count를 한 줄씩 출력하므로, 손으로 추적한 표의 행마다 비교할 출력이 생깁니다. 세 레지스터는 mkReg 초깃값에서 시작합니다. count와 cycle은 0, done은 False입니다.

GuardGap.bsv
package GuardGap;

(* synthesize *)
module mkTop(Empty);
  Reg#(Bit#(4)) count <- mkReg(0);
  Reg#(Bool) done <- mkReg(False);
  Reg#(Bit#(4)) cycle <- mkReg(0);

  // finish guard가 준비되기 전에 작업 rule이 멈춘다.
  rule step (count < 3);
    count <= count + 1;
  endrule

  rule finish (count == 5 && !done);
    done <= True;
    $display("FINISH: count=%0d", count);
    $finish(0);
  endrule

  // 독립적인 watchdog은 잘못된 인계가 있는 시뮬레이션을 유한 시간 안에 종료한다.
  rule tick (cycle < 8);
    cycle <= cycle + 1;
  endrule

  // 사이클마다 한 번, 사이클 시작 상태를 출력한다.
  rule monitor (cycle < 8);
    $display("cycle=%0d count=%0d", cycle, count);
  endrule

  rule watchdog (cycle == 8);
    $display("GUARD_GAP: count=%0d", count);
    $finish(0);
  endrule
endmodule

endpackage

이 개념 실습하기

다음 단계로 짧은 BSV 문제에 이 개념을 적용해 보세요.

실행하기 전에 예측해 보세요.

확인 문제

count는 어디까지 올라간 뒤 더 바뀌지 않습니까?

확인 문제

시뮬레이션을 끝내는 rule은 무엇입니까?

답

확인 문제

monitor는 사이클마다 한 줄을 출력합니다. cycle=6 줄에 찍히는 count는 얼마입니까?

실행을 누른 뒤 출력의 모든 줄을 다음 추적표와 맞춰 보세요. 한 행이 한 사이클이고, guard 열은 사이클 시작 값으로 계산합니다.

cycle시작 시 countstep guard count < 3finish guard count == 5 && !done실행되는 rule출력 줄
00참거짓step, tick, monitorcycle=0 count=0
11참거짓step, tick, monitorcycle=1 count=1
22참거짓step, tick, monitorcycle=2 count=2
33거짓거짓tick, monitorcycle=3 count=3
4–73거짓거짓tick, monitor모든 줄이 count=3
83거짓거짓watchdogGUARD_GAP: count=3
출력확인할 내용
cycle=3부터 이어지는 count=3step의 마지막 실행이 2를 읽고 3을 저장했습니다. guard가 거짓이 된 뒤로 count는 값을 유지합니다
GUARD_GAP: count=3이 있고 FINISH 줄은 없음watchdog이 시뮬레이션을 끝냈습니다. 3에서 5로 가는 전이가 없어 finish는 한 번도 실행되지 않았습니다

원인은 rule 순서도 레지스터 지연도 아닙니다. 모든 실행은 올바랐고, 3에서 5로 가는 전이가 없을 뿐입니다. 원자성은 각 실행이 완전하다는 보장이고, 설계가 계속 진행된다는 보장이 아닙니다.

5. 한 곳만 바꿔 보세요

finish의 guard를 count == 3 && !done으로 바꿔 다시 실행하세요. 그 다음 count == 16 && !done도 시험하세요.

확인 문제

guard가 count == 3 && !done이면 출력에서 무엇이 바뀝니까?

답

바꿀 곳예상 결과
finish의 조건을 count == 3 && !done으로cycle=0부터 cycle=2까지 세 줄 뒤에 FINISH: count=3이 나오고 GUARD_GAP 줄은 없습니다. cycle=3 줄도 없습니다. 그 사이클에는 finish와 monitor가 함께 활성화되는데, 생성된 코드가 finish를 먼저 실행하고 $finish가 monitor의 출력 전에 시뮬레이터를 멈추기 때문입니다
finish의 조건을 count == 16 && !done으로Bit#(4)는 0부터 15까지만 표현하므로 컴파일 단계에서 T0051 오류 Literal 16 is not a valid Bit#(4).가 발생합니다

두 번째 수정은 실행조차 되지 않습니다. 타입·폭·명시적 변환 장의 리터럴 규칙은 guard 안의 리터럴에도 그대로 적용됩니다. 타입 때문에 참이 될 수 없는 guard는 컴파일 시점에 검출되고, 다른 rule 때문에 참이 될 수 없는 guard는 검출되지 않습니다.

참고. Bit#(4) 산술은 모듈로 16으로 계산됩니다. 실행 중에 15에 1을 더해 같은 폭에 저장하면 0이 됩니다. 반면 리터럴 16은 Bit#(4) 값이 될 수 없으므로 실행 전에 컴파일 오류가 발생합니다.

6. 적용: 한계값에서 정확히 멈추세요

아래 채점 문제는 limit 레지스터와 guard count <= limit을 가진 카운터입니다. 여기서 쓰는 한계값 5와 2에서는 한 단계를 지나치므로 atLimit은 한 사이클만 참이 되고, 카운터를 리셋할 때까지 거짓입니다. 4비트 최댓값 15를 한계값으로 쓰면 0으로 되돌아가 계속 셉니다. guard의 비교만 바꾸세요. 실행 전에 2절의 표를 써서 기본 한계값 5와 테스트벤치가 나중에 설정하는 한계값 2 각각에 대해 rule이 읽는 마지막 값과 레지스터가 머무는 값을 적어 두세요.

확인 문제

guard가 count <= limit이고 limit = 5이면 count는 어떤 값에 머뭅니까?

7. 자주 하는 실수

7.1 한계값을 한 단계 지나치기

// 잘못된 코드: count가 5일 때도 실행되어 6을 저장합니다
rule step (count <= 5);  count <= count + 1;  endrule

// 바른 코드: 마지막 실행이 4를 읽고 레지스터는 5에서 멈춥니다
rule step (count < 5);   count <= count + 1;  endrule

guard는 본문이 실행되는 값을 정하고, 레지스터는 그 가운데 마지막 값에서 본문이 저장한 값에 머뭅니다. 중간 값만이 아니라 한계값 자체에서 guard를 확인하세요.

7.2 이어지는 두 조건 사이에 빈 구간 만들기

// 잘못된 코드: count가 3에서 멈추므로 finish는 준비되지 않습니다
rule step (count < 3);  count <= count + 1;  endrule
rule finish (count == 5);  $finish(0);  endrule

// 바른 코드: 앞 rule이 멈추는 값과 뒤 rule이 기다리는 값이 같습니다
rule step (count < 5);  count <= count + 1;  endrule
rule finish (count == 5);  $finish(0);  endrule

이 실수는 오류 메시지 없이 설계가 멈추는 증상으로 나타납니다. 앞 rule의 마지막 전이가 남기는 값을 뒤 rule의 조건과 대조하세요.

7.3 아무것도 실행되지 않을 때 오류를 기다리기

// 잘못된 기대: 멈춘 설계가 스스로 보고합니다
rule finish (count == 5);  $finish(0);  endrule

// 바른 코드: 독립된 watchdog이 테스트를 유한하게 만들고 멈춤을 드러냅니다
rule tick (cycle < 8);  cycle <= cycle + 1;  endrule
rule watchdog (cycle == 8);
  $display("stalled at count=%0d", count);
  $finish(0);
endrule

어떤 rule도 실행되지 않는 상태는 오류가 아니라 합법적인 상태이므로 bsc도 시뮬레이터도 보고하지 않습니다. 조건을 기다리는 테스트벤치는 기다리는 시간의 상한을 직접 정해야 합니다.

8. 정리

개념규칙
guard사이클 시작 상태 위의 조합 조건. rule이 실행될 수 있는 사이클을 고릅니다. 생략하면 True
유지실행된 어떤 rule도 쓰지 않은 레지스터는 값을 유지합니다. 거짓인 guard는 enable이 0인 경우입니다
준비와 실행명시 조건과 implicit condition이 모두 참이면 rule이 준비됩니다. 실행은 스케줄러의 별도 결정입니다
정지guard가 검사하는 값을 쓰는 rule은 guard를 거짓으로 만드는 첫 값에서 멈춥니다
하나 차이<는 한계값에서, <=는 한 단계 지나서 멈춥니다
결합 조건명시 조건과 호출한 모든 method의 implicit condition. 준비되지 않은 method는 rule 전체를 막습니다
빈 구간멈춘 값이 다음 guard를 만족하지 못하면 아무것도 실행되지 않고 아무것도 보고되지 않습니다
불가능한 리터럴타입 범위를 벗어난 guard 리터럴은 컴파일 시점의 T0051입니다

9. 확인 문제

  1. 인계에 count < S와 count == F를 쓰고 S < F라면 무엇이 관찰됩니까?

    답 보기

    count가 S에 도달하면 두 조건이 모두 거짓이 되어 어떤 rule도 실행되지 않습니다. 컴파일 오류도 실행 오류도 없고, 설계가 더 진행하지 않을 뿐입니다.

  2. 명시 조건 없이 inq.first와 inq.deq를 호출하는 rule이 있습니다. inq가 비어 있는 동안 어떤 일이 일어납니까?

    답 보기

    first와 deq의 implicit condition이 거짓이므로 rule 전체가 준비되지 않고 실행되지 않습니다. BSV는 준비되지 않은 호출만 건너뛰지 않습니다.

  3. step (count < 5)가 열 사이클 전에 5에서 멈췄고 다른 rule은 count를 쓰지 않습니다. 지금 count의 값은 무엇입니까?

    답 보기

    여전히 5입니다. 거짓인 guard는 쓰기가 없다는 뜻이고, 쓰이지 않은 레지스터는 값을 유지합니다. guard는 상태를 리셋하지 않습니다.

다음 장은 rule 두 개를 같은 사이클에 놓고, 둘이 함께 준비되면 어떤 일이 생기는지 다룹니다.

확인 지점

실습 문제를 제출해 채점을 받습니다

채점은 서버의 비공개 테스트로 진행되며, 결과는 이 브라우저에만 기록됩니다.

참고 자료

참고한 자료의 라이선스와 고지