본문으로 건너뛰기
code0.HDL

상태를 공유하는 rule: 원자성·덮어쓰기·충돌

같은 사이클에 rule 두 개를 놓습니다. 서로 다른 상태를 다루면 둘 다 실행되고, 한 레지스터에 쓴 두 번째 쓰기는 경고와 함께 첫 쓰기를 덮으며, 진짜 충돌에는 읽기/쓰기 순환이 필요합니다. 모든 실행은 하나의 원자적 전이입니다.

이 장에서 배울 내용

  • 준비된 여러 rule 가운데 어느 것이 한 사이클에 실행되는지를 읽고 쓰는 레지스터로 예측할 수 있습니다
  • 덮인 쓰기(둘 다 실행, 경고 G0036·G0117)와 충돌(하나가 억제, 경고 G0010·G0021)을 구분할 수 있습니다
  • rule 본문을 모든 읽기가 진입 상태를 보는 하나의 원자적 전이로 추적할 수 있습니다
  • 충돌 경고를 읽고 배타적 조건, 명시한 우선순위, phase 상태 가운데 맞는 해결책을 고를 수 있습니다
  • -show-schedule 보고서를 읽고 컴파일러가 정한 것(긴급도, 차단, 실행 순서)과 한 번의 시뮬레이션이 보여 준 것을 구분할 수 있습니다
이 장에서 배우는 개념
  • 독립 규칙과 공유 상태
먼저 알아야 할 개념

1. 조건이 겹쳐도 함께 실행될 수 있습니다

모듈에는 보통 rule이 여럿이고, 두 guard가 같은 사이클에 참이 되는 것을 막는 규칙은 없습니다. 각 rule은 사이클 시작 상태를 읽고 자기 쓰기를 예약합니다. 두 rule의 조건이 같은 사이클에 모두 참이어도 오류는 아닙니다. 함께 실행할 수 있는지는 두 rule이 사용하는 상태와 method의 관계가 정하며, 컴파일러 bsc는 이것을 컴파일 시점에 한 번 분석하고 호환되는 rule들이 사이클을 공유하게 하는 회로를 생성합니다.

일반 Reg 하나에 대해 두 rule이 호출할 수 있는 method 조합의 관계는 다음과 같습니다.

method 조합관계같은 사이클에서의 의미
읽기와 읽기CF서로 영향을 주지 않습니다
읽기와 쓰기SB읽기가 쓰기보다 앞선 것으로 처리됩니다. 그래서 읽기는 언제나 사이클 시작 값을 봅니다
쓰기와 쓰기SBR한 rule 안의 두 쓰기는 오류입니다(G0004, 7.1절). 서로 다른 두 rule은 같은 사이클에 같은 레지스터를 각각 쓸 수 있고, 스케줄 순서가 뒤인 쓰기가 앞의 쓰기를 덮습니다(bsc가 경고합니다)

이 관계는 일반 Reg에만 해당합니다. FIFO, wire 같은 다른 상태 요소는 각자의 scheduling 계약을 가지며 라이브러리 primitive 장에서 소개합니다. CF, SB, SBR이라는 이름 뒤의 관계 전체 표는 정적·동적 의미론 장에 있습니다.

2. 서로 다른 상태를 다루는 rule은 함께 실행됩니다

준비된 두 rule이 서로 다른 레지스터를 읽고 쓰면 그 사이클에 둘 다 실행되고 두 쓰기 집합이 모두 에지에서 반영됩니다. OverlapScheduling.bsv는 phase 값마다 한 상황씩, 두 상황을 보여 줍니다.

phase준비된 rule다루는 상태
0updateLeft, updateRight, advanceleft, right, phase. 서로 다른 레지스터 셋
1chooseOne, chooseTwo, advance두 선택 rule이 모두 winner를 읽고 씁니다
2report출력하고 멈춥니다

advance는 phase가 2보다 작은 동안 매 사이클 phase를 1 증가시키고, report는 phase가 2일 때 결과를 출력합니다.

OverlapScheduling.bsv
package OverlapScheduling;

(* synthesize *)
module mkTop(Empty);
  Reg#(Bit#(2)) phase <- mkReg(0);
  Reg#(Bit#(4)) left <- mkReg(0);
  Reg#(Bit#(4)) right <- mkReg(0);
  Reg#(Bit#(2)) winner <- mkReg(0);

  // rule guard가 명시 조건이다. 이 Reg method에는 상태에 따른 implicit condition이
  // 없다. phase 값이 0이면 두 rule이 준비되고 서로 다른 일반 Reg를 갱신한다.
  rule updateLeft (phase == 0);
    left <= 1;
  endrule

  rule updateRight (phase == 0);
    right <= 2;
  endrule

  // phase 값이 1이면 두 rule이 winner를 읽고 써서 읽기·쓰기 순서에 순환이 생긴다. 이 빌드에서 bsc의
  // 선택은 결정적이지만 소스 순서와 fairness는 언어가 보장하지 않는다.
  rule chooseOne (phase == 1 && winner == 0);
    winner <= 1;
  endrule

  rule chooseTwo (phase == 1 && winner == 0);
    winner <= 2;
  endrule

  // 일반 Reg 읽기는 쓰기보다 먼저 scheduling된다. 따라서 phase 값에 따라 준비된
  // rule은 advance와 합법적인 부분집합으로 함께 실행될 수 있다.
  rule advance (phase < 2);
    phase <= phase + 1;
  endrule

  rule report (phase == 2);
    $display("OVERLAP_SCHEDULING compatible=%0d,%0d winner=%0d",
             left, right, winner);
    $finish(0);
  endrule
endmodule

endpackage

이 개념 실습하기

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

실행하기 전에 예측해 보세요. phase가 0인 사이클에 실행되는 rule은 몇 개일까요? winner에 1과 2가 모두 쓰일 수 있을까요? 컴파일 단계에서 경고가 나올까요? 첫 질문은 아래 확인 문제이고, 둘째 질문은 4절의 확인 문제이며, 셋째 질문은 컴파일 출력이 답합니다.

확인 문제

phase가 0인 사이클에 실행되는 rule은 몇 개입니까?

3. 한 레지스터에 쓰는 두 쓰기는 그 자체로 충돌이 아닙니다

준비된 두 rule이 모두 winner에 쓰고 어느 쪽도 읽지 않는다고 가정합니다. bsc는 둘을 모두 실행시킵니다. 사이클 안에서 한 rule을 다른 rule 뒤에 놓고, 뒤에 놓인 rule의 쓰기가 저장됩니다. 앞의 쓰기는 가려집니다. 이것은 합법이고, 컴파일러는 rule 이름과 레지스터를 적은 경고(G0036, G0117, "shadows the effects of")로 알립니다. 가려지는 쓰기는 보통 설계의 실수이지만 충돌은 아니며, 두 rule은 모두 실행됩니다. 1절 표의 SBR 행이 바로 이 경우입니다.

4. 충돌에는 읽기/쓰기 순환이 필요합니다

phase 1은 다릅니다. chooseOne과 chooseTwo는 guard에 winner == 0이 있으므로 각각 winner를 읽고, 각각 winner에 씁니다. 둘이 한 사이클에 실행되려면 한쪽이 먼저 와야 합니다. 그러면 그 쪽의 쓰기가 다른 쪽의 같은 레지스터 읽기보다 앞서게 되는데, 레지스터 읽기는 사이클 시작 값을 보아야 하고 같은 사이클에 먼저 쓴 값을 볼 수 없습니다. 어느 순서도 성립하지 않습니다. 이것이 충돌입니다. 두 rule은 사이클을 전혀 공유할 수 없습니다.

bsc는 컴파일 시점에 한 rule을 다른 rule보다 긴급하게 두어 해결하고 경고합니다(G0010). 두 guard가 똑같으므로 밀린 rule은 영원히 실행될 수 없고, 두 번째 경고가 그 사실을 알립니다 (G0021). 어느 rule이 이기는지는 이 빌드의 선택이고, 소스 순서도 약속도 아닙니다.

확인 문제

실행 중에 winner가 1과 2를 모두 가질 수 있습니까?

답

실행을 누르세요. 패널에는 세 가지가 차례로 나타납니다. 컴파일 경고 두 개, 이 예제의 명령에 든 -show-schedule 플래그가 mkTop.sched에 기록한 schedule 보고서(명령이 cat으로 출력합니다), 마지막으로 시뮬레이션 출력 한 줄입니다. 이 순서대로 읽으세요.

결과 확인.

사이클 시작 상태함께 실행되는 rule경계 뒤 상태
phase가 0updateLeft, updateRight, advanceleft가 1, right가 2, phase가 1
phase가 1chooseOne과 chooseTwo 중 하나, advancewinner가 1 또는 2, phase가 2
phase가 2report결과를 출력하고 종료합니다
관찰의미
compatible=1,2서로 다른 레지스터에 쓰는 두 rule이 같은 사이클에 모두 실행되었습니다(2절)
winner= 뒤의 값 하나충돌하는 두 쓰기 가운데 하나만 반영되었습니다(이 절)
컴파일 경고 G0010, G0021충돌 때문에 한 rule을 다른 rule보다 우선했고, 억제된 rule은 실행될 수 없다는 경고입니다

4.1 경고와 schedule 보고서 읽기

경고와 보고서는 컴파일러가 무엇을 결정했는지 스스로 밝힌 기록입니다. 아래 각 줄을 패널에서 찾아보세요. 표의 문구는 이 소스에 대해 bsc 2026.01이 출력하는 그대로입니다.

위치이 빌드의 문구알려 주는 내용
G0010의 첫 줄Rule "chooseOne" was treated as more urgent than "chooseTwo"긴급도(urgency)입니다. 둘 다 활성화되면 chooseOne이 그 사이클을 가져갑니다
G0010 아래의 이유"chooseOne" cannot fire before "chooseTwo"와 그 반대 방향 줄, 각각 뒤에 calls to winner.write vs. winner.read이 절의 읽기/쓰기 순환이 양쪽 방향으로 있다는 뜻입니다
G0021According to the generated schedule, rule `chooseTwo' can never fire.guard가 같으므로 덜 긴급한 rule만 활성화되는 사이클이 없습니다
보고서의 Rule: chooseTwoBlocking rules: chooseOnechooseOne이 활성화된 사이클에는 언제나 chooseTwo가 억제됩니다
보고서의 나머지 ruleBlocking rules: (none)updateLeft, updateRight, advance를 막는 rule이 없습니다. 그래서 compatible=1,2가 나옵니다
Predicate:(phase == 2'd1) && (winner == 2'd0)컴파일된 형태의 rule 조건입니다
Logical execution order:updateLeft, updateRight, chooseOne, chooseTwo, advance, report한 사이클에 함께 실행되는 rule들이 따라야 하는 하나씩 실행 순서입니다. phase를 읽는 rule이 phase에 쓰는 advance보다 앞에 옵니다

긴급도와 논리적 실행 순서는 서로 다른 목록입니다. 긴급도는 충돌하는 두 rule이 함께 활성화됐을 때 어느 쪽이 실행될지를 정하고, 논리적 실행 순서는 실제로 함께 실행되는 rule들이 어떤 순서로 일어난 것으로 볼지를 정합니다. 5절의 세 번째 수정은 앞의 것만 바꾸고 뒤의 것은 그대로 둡니다.

같은 소스와 같은 컴파일러 버전에서는 선택 결과가 매번 같습니다. 그러나 소스 순서가 우선순위를 정한다는 규칙은 없으며, 소스나 컴파일러 버전이 바뀌면 선택이 달라질 수 있습니다. 우선순위가 필요하면 조건을 서로 배타적으로 만들거나, 어트리뷰트 장의 scheduling 어트리뷰트로 의도를 밝혀야 합니다.

주의. guard가 겹치는 것은 오류도 충돌도 아니고, 같은 레지스터에 쓰는 것만으로도 충돌이 생기지 않습니다. 충돌의 표지는 rule들이 읽고 쓰는 것 사이의 순환입니다. chooseOne과 chooseTwo처럼 각 rule이 상대가 쓰는 winner를 읽는 경우입니다. 충돌이 보고되면 고칠 곳은 설계입니다. guard를 서로 배타적으로 만들거나, phase 레지스터로 두 동작을 다른 사이클로 옮기세요(9절).

5. 한 곳만 바꿔 보세요

실행 패널에서 두 선택 guard의 && winner == 0을 지워 phase == 1만 남기고 다시 실행하세요. 두 rule은 이제 winner를 읽지 않고 씁니다.

확인 문제

guard에서 winner 읽기가 사라지면 phase 1 사이클에 어떤 일이 생깁니까?

답

원래 소스에서 한 곳을 더 바꿔 보세요. chooseTwo의 대입을 right <= 3;으로 바꿉니다. 이제 두 선택 rule은 서로 다른 레지스터에 씁니다. chooseTwo는 여전히 winner를 읽고 chooseOne은 winner에 쓰지만, 이것은 읽기 하나가 쓰기 하나보다 앞서는 관계이고 그런 순서는 존재합니다. 그래서 phase 1에 둘 다 실행되고 출력은 compatible=1,3 winner=1이 됩니다.

세 번째 수정도 원래 소스에서 시작합니다. rule chooseOne 바로 윗줄에 (* descending_urgency = "chooseTwo, chooseOne" *)을 한 줄로 넣으세요. 이 어트리뷰트는 어트리뷰트 장에서 배웁니다. 여기서는 두 rule 가운데 어느 쪽이 이기는지만 밝힙니다.

확인 문제

descending_urgency 어트리뷰트가 chooseTwo를 chooseOne보다 앞에 두면, 어떤 경고가 남고 실행 결과는 무엇을 출력합니까?

답

5.1 실행 결과가 보여 주는 것과 컴파일러가 보고하는 것

세 버전을 나란히 놓아 보겠습니다. 모든 행은 bsc 2026.01로 컴파일하고 실행한 결과입니다.

소스경고mkTop.sched의 Blocking rules:phase 1 사이클에 실행되는 rule실행 출력
원래 소스G0010, G0021(chooseTwo는 실행되지 않음)chooseTwo 아래 chooseOnechooseOne, advancewinner=1
winner 읽기 제거G0036, G0117(가림)둘 다 (none)chooseOne, chooseTwo, advancewinner=2
descending_urgency = "chooseTwo, chooseOne"G0021(chooseOne은 실행되지 않음)chooseOne 아래 chooseTwochooseTwo, advancewinner=2

아래 두 행은 출력이 같지만 이유는 정반대입니다. 하나는 두 rule이 모두 실행되고, 논리적 실행 순서에서 chooseTwo가 뒤에 있어 chooseOne의 쓰기가 가려진 경우입니다. 다른 하나는 chooseOne이 아예 실행되지 않은 경우입니다. 시뮬레이션은 한 시작 상태에서 얻은 추적 하나입니다. 일어난 일을 보여 줄 뿐이고, 실행되지 않은 rule은 출력에 아무 흔적도 남기지 않습니다. 어떤 rule이 실행될 수 있는지, 충돌에서 누가 이기는지, 한 사이클을 나누는 rule들이 어떤 순서로 반영되는지는 모든 사이클에 대한 사실이고, 그것을 밝히는 것은 경고와 schedule 보고서뿐입니다.

확인 문제

두 빌드가 모두 winner=2를 출력합니다. phase 1 사이클에 chooseOne이 실행된 쪽은 어느 빌드입니까?

답

6. 한 번의 실행은 하나의 원자적 전이입니다

rule 하나의 동작은 원자적으로 일어납니다. rule의 상태 변경이 모두 반영되거나 하나도 반영되지 않습니다. 다른 rule이 반쯤 실행된 상태를 관찰하는 일은 없습니다.

주의. 원자성은 "반쯤 반영된 상태가 보이지 않는다"는 안전 속성입니다. 준비된 rule이 언젠가 반드시 실행된다는 liveness나, 준비된 rule을 번갈아 공평하게 선택한다는 fairness는 포함하지 않습니다. 4절에서는 준비되었지만 영원히 실행되지 않는 rule을 보았고, guard 장에서는 어떤 rule도 실행되지 않는 설계를 보았습니다.

6.1 <=는 다음 상태를 예약합니다

레지스터에 대한 <=는 값을 즉시 바꾸지 않고 다음 클록 에지에 저장할 값을 예약합니다. 같은 rule 안에서 그 레지스터를 다시 읽으면 여전히 사이클 시작 값이 보입니다.

형식

레지스터 <= 식;
rule step;
  count <= count + 1;
  $display("%0d", count);   // 갱신 전 값이 출력됩니다
endrule
시점count가 보이는 값
rule 안의 모든 읽기사이클 시작 값
다음 사이클count + 1

6.2 순차 참조 의미론

BSV의 기준 의미론은 순차 참조 의미론입니다. 활성화된 rule 하나를 골라 본문 전체를 수행하고, 다시 하나를 골라 수행하는 과정을 반복한다고 보는 모델입니다. Term Rewriting Systems에서 유래해 TRS 의미론이라고도 부릅니다.

이 모델의 한 단계는 실제 클록 사이클 하나와 같지 않습니다. 각 rule을 나눌 수 없는 상태 전이로 다루기 위한 추론 도구입니다. 이 모델에서 보장되는 성질은 하나입니다. 어떤 관찰에서도 rule의 갱신 중 일부만 반영된 상태는 보이지 않습니다. rule 하나 안에서 모든 읽기는 사이클 시작 상태를 보고 모든 쓰기는 에지에서 함께 반영되므로, rule의 본문은 단계의 나열이 아니라 한 상태에서 다음 상태로 가는 함수입니다.

6.3 예제: 한 rule에서 두 레지스터 교환하기

AtomicUpdate.bsv의 swap은 left <= right;와 right <= left;를 연달아 적습니다. 소프트웨어였다면 두 번째 줄이 이미 바뀐 left를 읽어 두 값이 같아집니다. 여기서는 두 오른쪽 식이 같은 진입 상태를 읽습니다. 코드를 읽을 때는 다음을 확인하세요.

  • 초깃값: left = 1, right = 9, swapped = False
  • swap의 조건 !swapped와 본문의 세 대입
  • show의 조건 swapped

실행하기 전에 예측해 보세요. right <= left가 읽는 left는 1일까요, 9일까요? show가 출력하는 두 값은 무엇일까요?

확인 문제

left는 1, right는 9에서 시작합니다. right <= left는 left를 어떤 값으로 읽습니까?

답

AtomicUpdate.bsv
package AtomicUpdate;

(* synthesize *)
module mkTop(Empty);
  Reg#(Bit#(4)) left <- mkReg(1);
  Reg#(Bit#(4)) right <- mkReg(9);
  Reg#(Bool) swapped <- mkReg(False);

  rule swap (!swapped);
    // 두 우변은 어느 레지스터도 바뀌기 전의 같은 rule 진입 상태를 읽는다.
    left <= right;
    right <= left;
    // 두 쓰기는 하나의 원자적 상태 전이로 함께 반영된다.
    swapped <= True;
  endrule

  rule show (swapped);
    $display("left = %0d, right = %0d", left, right);
    $finish(0);
  endrule
endmodule

endpackage

이 개념 실습하기

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

결과 확인.

단계상태
사이클 0 시작left = 1, right = 9, swapped = False
swap이 계산하는 다음 값left' = 9, right' = 1, swapped' = True. 세 우변 모두 사이클 시작 상태를 읽습니다
사이클 1 시작세 값이 함께 반영되어 swapped가 참이 되고 show가 활성화됩니다

출력 줄이 left = 9, right = 1이면 두 값이 교환되었다는 뜻입니다. 같은 값이 두 번 나오거나 한쪽만 바뀐 상태는 보이지 않습니다. 원자성은 정확히 그것을 약속합니다. 어떤 관찰자도 절반만 실행된 상태를 보지 않습니다.

직접 바꿔 보세요.

  1. swap 안의 두 대입 순서를 바꿔 right <= left;를 먼저 적어 보세요. 출력은 그대로 left = 9, right = 1입니다. 대입의 순서는 읽는 값을 바꾸지 않습니다.
  2. 초깃값을 mkReg(3)과 mkReg(12)로 바꾸면 출력은 left = 12, right = 3이 됩니다.
  3. 두 대입을 서로 다른 두 rule로 나누는 것은 동등한 변경이 아닙니다. 중간 상태가 관찰될 수 있고, 하나의 rule에는 없던 순서 문제가 새로 생깁니다.

7. 활성화와 실행은 다릅니다

생성된 하드웨어는 한 사이클에 여러 rule을 실행할 수 있습니다. 단, 함께 실행하는 rule 집합은 다음 조건을 만족해야 합니다.

  • 선택한 rule을 순차 참조 의미론의 단계들로 일렬로 배열할 수 있어야 합니다.
  • 병렬 회로가 만드는 최종 상태가 그 순차 실행의 결과와 같아야 합니다.

조건이 참인 rule은 활성화된 것이고, scheduler가 선택한 rule만 실행됩니다. 활성화된 두 rule이 함께 실행될 수 없는 자원을 쓰면 scheduler가 하나를 억제합니다. 하나의 rule은 한 사이클에 최대 한 번 실행됩니다. 함께 실행된 rule들의 결과는 그 rule들을 어떤 합법적인 순서로 하나씩 실행한 결과와 같아야 하며, 이 조건 덕분에 각 rule을 따로 떼어 추론할 수 있습니다.

7.1 한 rule 안에서 같은 레지스터를 두 번 쓰면

한 rule 안의 모든 문장은 병렬로 실행되므로, 같은 레지스터에 두 값을 동시에 쓰는 것은 의미가 없습니다.

rule bad;
  value <= 1;
  value <= 2;   // 컴파일 오류
endrule

이 코드는 컴파일 오류 G0004를 발생시킵니다. 메시지는 이 rule이 병렬로 충돌하는 method value.write를 두 번 사용한다고 보고합니다. 조건에 따라 하나만 쓰려면 if/else로 두 쓰기를 배타적인 분기에 둡니다. 두 rule 사이의 충돌(경고, 한 rule 억제)과 한 rule 안의 충돌(오류)은 다르게 처리됩니다.

8. 예제: 같은 레지스터를 읽고 쓰는 두 rule

RuleConflict.bsv의 chooseOne과 chooseTwo는 같은 조건 phase == 0을 검사하고 같은 두 레지스터 phase와 value에 값을 씁니다. 각 rule은 조건에서 phase를 읽고 본문에서 phase에 쓰므로 4절의 읽기/쓰기 순환이 생깁니다. value에 대한 두 쓰기만 있었다면 가림에 그쳤을 것입니다. 이 예제는 컴파일과 실행이 모두 성공하지만 컴파일 단계에서 경고가 출력됩니다. 실행하기 전에 경고와 실행 결과를 따로 예측해 보세요.

실행하기 전에 예측해 보세요. 컴파일 단계에서 경고가 몇 개 나올까요? 각각 무엇을 알려 줄까요? show가 출력하는 value는 1일까요, 2일까요?

확인 문제

chooseOne fired와 chooseTwo fired가 둘 다 출력됩니까?

답

RuleConflict.bsv
package RuleConflict;

(* synthesize *)
module mkTop(Empty);
  Reg#(Bit#(2)) phase <- mkReg(0);
  Reg#(Bit#(4)) value <- mkReg(0);

  // 두 rule이 phase를 읽고 쓰므로 어느 쪽도 다른 쪽보다 먼저 실행될 수 없다. 컴파일러는 하나를 선택하고
  // 충돌을 보고해야 한다.
  rule chooseOne (phase == 0);
    value <= 1;
    phase <= 1;
    $display("chooseOne fired");
  endrule

  rule chooseTwo (phase == 0);
    value <= 2;
    phase <= 1;
    $display("chooseTwo fired");
  endrule

  // 올바른 동작은 임의적인 우선순위 선택에서 어떤 rule이 이기는지에 의존하면 안 된다.
  rule show (phase == 1);
    $display("value = %0d", value);
    $finish(0);
  endrule
endmodule

endpackage

이 개념 실습하기

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

결과 확인. 출력 패널의 경고와 출력 줄은 다음 사실을 보여 줍니다.

출력확인할 내용
경고 G0010bsc가 한 rule을 다른 rule보다 긴급하다고 임의로 정했다는 뜻입니다. 두 rule이 서로 상대보다 먼저 실행될 수 없는 이유로 phase.write와 phase.read의 충돌을 나열합니다
경고 G0021생성된 schedule에 따르면 지고 있는 rule은 절대 실행될 수 없다는 뜻입니다
... fired 줄한 줄만 출력됩니다. 선택된 rule만 실행되었습니다
value = ...선택된 rule이 쓴 값입니다. 이 값은 설계 계약이 아니라 이번 컴파일이 정한 우선순위의 결과입니다

충돌은 다음 순서로 일어납니다.

순서분석 또는 전이
1사이클 0에서 phase == 0이므로 두 조건이 모두 참입니다
2두 rule이 각각 phase와 value의 다음 값을 요청합니다
3어느 쪽이 앞서든 그쪽의 phase 쓰기가 상대의 phase 읽기보다 앞서야 하는데, 읽기는 쓰기보다 앞서야 하므로 합법적인 순서가 없습니다. scheduler가 하나만 허가합니다
4선택된 rule의 두 쓰기가 에지에서 함께 반영되어 phase가 1이 됩니다
5사이클 1에서는 두 rule 모두 비활성이고 show가 실행됩니다

선택 결과는 한 번의 컴파일 안에서는 결정적입니다. 그러나 관련 없는 소스 변경이나 컴파일러 변경 뒤에 우선순위가 달라질 수 있습니다. 출력에 보이는 승자는 현재 schedule을 보여 줄 뿐입니다.

직접 바꿔 보세요.

  1. chooseTwo의 조건을 phase == 2로 바꿔 보세요. 두 조건이 동시에 참일 수 없으므로 경고 두 개가 모두 사라지고 chooseOne fired와 value = 1이 출력됩니다.
  2. chooseOne에 value <= 2;를 한 줄 더 추가해 보세요. 이번에는 경고가 아니라 7.1절의 G0004 컴파일 오류가 발생합니다. 두 rule 사이의 충돌과 한 rule 안의 충돌은 다르게 처리됩니다.

9. 충돌 경고를 해결하는 방법

"한 rule이 실행되지 않는다"는 증상이고, "동시에 활성화될 수 있는 두 rule이 서로 상대가 쓰는 것을 읽는다"는 원인입니다. 원인에 맞춰 고칩니다.

상황해결 방법
상태가 이미 어떤 작업인지 구분함두 rule에 서로 배타적인 조건을 줍니다
우선순위가 명세의 일부임scheduling 어트리뷰트로 우선순위를 명시합니다. 낮은 우선순위 rule이 계속 실행되지 못하는 기아 상태가 생길 수 있으므로 그 영향도 함께 검토합니다
두 작업이 모두 정해진 순서로 일어나야 함순서를 phase 상태로 표현합니다. 현재 단계를 기록하는 레지스터를 두고 단계마다 정확히 한 rule만 활성화되게 합니다

scheduling 어트리뷰트는 어트리뷰트 장에서 다루고, phase 레지스터 형식은 상태 기계 장의 주제입니다.

10. 적용: rule을 분리하고, 교환하세요

실습 문제가 세 개 이어집니다. 첫 문제에서 fill과 drain은 같은 guard를 갖고 둘 다 accumulator에 쓰며, fill은 drain이 쓰는 상태를 읽기도 합니다. bsc는 충돌 경고 쌍을 보고하고 drain은 실행되지 않습니다. 수집이 멈춘 뒤이면서 한 번의 전달이 아직 일어나지 않았을 때만 참인 guard를 drain에 주어 두 guard가 함께 참이 되지 않게 하세요. 두 번째 문제에서는 같은 상태를 두고 다투는 두 rule 때문에 scheduling 경고가 나고 한 rule이 실행되지 않습니다. 고치기 전에 경고 문구에서 어느 레지스터가 읽기/쓰기 순환을 이루는지 먼저 찾으세요. 채점 문제는 교환입니다. 자기 자신을 대입하는 두 줄을 진입 상태 값을 쓰는 교환으로 바꾸되, 임시 레지스터도 두 번째 rule도 두지 마세요.

11. 자주 하는 실수

11.1 <= 뒤에서 새 값을 읽을 수 있다고 생각하기

// 잘못된 기대: 갱신된 값이 출력될 것이라고 생각합니다
rule step;
  count <= count + 1;
  $display("now %0d", count);   // 실제로는 갱신 전 값
endrule

// 바른 코드: 새 값이 필요하면 식으로 계산해 씁니다
rule step;
  let next = count + 1;
  count <= next;
  $display("now %0d", next);
endrule

<=는 다음 에지에 저장할 값을 예약할 뿐입니다. 같은 rule 안에서 새 값이 필요하면 지역 변수에 계산해 두고 그 변수를 사용하세요.

11.2 한 rule에서 같은 레지스터를 두 번 쓰기

// 잘못된 코드: G0004 컴파일 오류
value <= 1;
value <= 2;

// 바른 코드: 배타적인 분기에 둡니다
if (select) value <= 1;
else        value <= 2;

한 rule의 문장은 병렬로 실행됩니다. 마지막 대입이 이긴다는 소프트웨어의 규칙은 적용되지 않습니다.

11.3 소스 순서로 우선순위를 정하려 하기

// 잘못된 기대: 먼저 적은 chooseOne이 항상 이긴다고 생각함
rule chooseOne (go);  winner <= 1;  endrule
rule chooseTwo (go);  winner <= 2;  endrule

// 바른 코드: 조건을 서로 배타적으로 만듭니다
rule chooseOne (go && preferOne);   winner <= 1;  endrule
rule chooseTwo (go && !preferOne);  winner <= 2;  endrule

서로 상대가 쓰는 값을 읽어 충돌하는 두 rule이 함께 준비되면 하나만 실행됩니다. 어느 쪽이 선택되는지는 언어가 소스 순서로 보장하지 않습니다. 같은 레지스터에 쓰기만 하는 두 rule은 함께 실행될 수 있으며, 스케줄 순서가 뒤인 rule의 쓰기가 남고 bsc가 덮인 쓰기를 경고합니다.

11.4 충돌 경고를 무시하기

// 잘못된 코드: 두 rule이 동시에 활성화되고 각자 phase를 읽고 씁니다
rule chooseOne (phase == 0); value <= 1; phase <= 1; endrule
rule chooseTwo (phase == 0); value <= 2; phase <= 1; endrule

// 바른 코드: 상태로 두 작업을 구분합니다
rule chooseOne (phase == 0 && mode == 0); value <= 1; phase <= 1; endrule
rule chooseTwo (phase == 0 && mode == 1); value <= 2; phase <= 1; endrule

시뮬레이션이 성공해도 G0010과 G0021 경고는 한 rule이 결코 실행되지 않는다는 사실을 알려 줍니다. 경고를 남겨 두면 설계의 동작이 bsc가 임의로 정한 우선순위에 의존하게 됩니다.

11.5 우선순위로 순서를 해결하려 하기

두 연산이 모두, 정해진 순서로 일어나야 할 때 충돌하는 두 rule에 우선순위를 주면 매 사이클 한쪽만 선택됩니다. 다른 쪽은 계속 실행되지 못할 수도 있습니다. 순서가 필요하면 상태 기계 장처럼 phase 상태로 표현하세요.

12. 정리

상황결과
준비된 두 rule, 서로 다른 레지스터둘 다 실행. 쓰기는 함께 반영
준비된 두 rule이 한 레지스터에 쓰고 어느 쪽도 읽지 않음둘 다 실행. 뒤의 쓰기가 앞을 가림. 경고 G0036, G0117
각자 상대가 쓰는 것을 읽음충돌. 하나가 억제됨. 경고 G0010, G0021
한 rule 안에서 한 레지스터에 두 번 쓰기컴파일 오류 G0004
한 번의 실행 안읽기는 진입 상태를 보고 쓰기는 함께 반영. 본문은 전이 하나
원자성절반만 반영된 실행은 관찰되지 않음. 실행(liveness)이나 교대(fairness)의 약속은 아님
활성화와 실행활성화된 rule 가운데 합법적인 순차 배열이 있는 부분집합만 실행
Reg의 관계읽기/읽기 CF, 읽기/쓰기 SB, 쓰기/쓰기 SBR
충돌 경고원인을 고침. 배타적 조건, 명시한 우선순위, 또는 phase 상태
-show-schedule 보고서Blocking rules:는 이 rule을 억제하는 rule, Logical execution order:는 한 사이클을 나누는 rule의 순차 순서. 긴급도와 순서는 별개
실행 결과와 보고서실행은 추적 하나. 어떤 rule이 실행될 수 있는지는 경고와 보고서만 밝힘

13. 확인 문제

  1. phase == 0에서 updateLeft, updateRight, advance가 같은 사이클에 실행될 수 있는 이유는 무엇일까요?

    정답 보기

    세 rule은 각각 left, right, phase에 씁니다. 같은 레지스터에 쓰는 쌍이 없고, 두 갱신 rule의 phase 읽기는 advance의 쓰기보다 앞선 것으로 처리할 수 있으므로 합법적인 순서가 존재합니다.

  2. left <= right; right <= left;가 두 값을 교환하는 이유는 무엇일까요?

    정답 보기

    두 우변이 모두 사이클 시작 상태를 읽고, 두 쓰기는 에지에서 함께 반영되기 때문입니다. 첫 번째 쓰기가 두 번째 줄의 읽기에 영향을 주지 않습니다.

  3. 시뮬레이션 결과가 기대와 같아도 G0021 경고를 고쳐야 하는 이유는 무엇일까요?

    정답 보기

    G0021은 어떤 rule이 생성된 schedule에서 결코 실행될 수 없다는 뜻입니다. 지금의 결과는 임의로 정해진 우선순위에 의존하고 있고, 소스나 컴파일러가 바뀌면 다른 rule이 선택될 수 있습니다.

  4. 두 rule이 모두 count에 쓰고 어느 쪽도 읽지 않습니다. 충돌일까요?

    정답 보기

    아닙니다. 순서가 존재하므로 둘 다 실행되고, 스케줄 순서가 뒤인 rule의 쓰기가 저장됩니다. bsc는 다른 쓰기가 가려진다고 경고합니다(G0036, G0117). 충돌에는 두 rule 사이의 읽기/쓰기 순환이 필요합니다.

다음 두 문제는 읽고 쓰는 레지스터만 보고 판단하세요. 두 경우 모두 bsc 2026.01로 컴파일하고 실행해 답을 확인했습니다.

확인 문제

rule a는 x를 읽고 y <= x + 10을 씁니다. rule b는 y를 읽고 z <= y를 씁니다. x = 1, y = 2, z = 3에서 두 rule이 같은 사이클에 활성화됩니다. 어떻게 됩니까?

답

확인 문제

rule a는 x를 읽고 y <= x를 씁니다. rule b는 y를 읽고 x <= y를 씁니다. 두 rule이 같은 사이클에 활성화됩니다. bsc는 무엇을 보고합니까?

답

다음 장에서는 다른 모듈의 method를 호출하는 rule을 다룹니다. 호출한 method의 준비 조건도 rule의 활성 여부를 결정합니다.

확인 지점

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

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

참고 자료

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