always 블록의 대입 순서는 mux의 우선순위로 남는다
RTL의 always 블록은 코드처럼 위에서 아래로 읽힌다. 합성 결과에서는 그 블록이 사라지고 mux와 레지스터가 남는다. 그 사이에서 코드의 순서와 값의 유지 동작은 어디로 갔을까? RTL process lowering은 절차로 적은 동작을 신호 사이의 선택 관계와 저장 동작으로 바꾸는 과정이다. 블록이 지워지는 순간에도 원래 의미는 회로 안에 남아야 한다.
코드의 순서는 선택의 우선순위가 된다
하나의 8비트 레지스터 q에 두 입력 중 하나를 저장하는 가상 회로를 보자. 리셋은 동기식이다. force_b가 이름대로 b를 우선 저장하도록 코드를 적었다.
always @(posedge clk) begin
if (rst) begin
q <= 8'd0;
end else begin
if (en) q <= a;
if (force_b) q <= b;
end
end
두 if가 모두 참이면 같은 always 블록에서 나중에 실행한 q의 대입이 우선한다. 따라서 en=1, force_b=1인 에지에는 b를 저장한다. 두 if를 동시에 작동하는 두 개의 독립 저장 장치로 해석하면 이 의미를 잃는다. 하드웨어에는 한 q의 다음 값을 정하는 하나의 관계가 필요하다.
그 관계를 현재 상태 q와 다음 상태 q_next로 정리하면 다음과 같다.
q_next = rst ? 0 : (force_b ? b : (en ? a : q))
rst가 가장 바깥 조건이고, 리셋이 아니면 force_b가 en보다 우선한다. 마지막 q는 아무 대입도 실행되지 않았을 때 현재 값을 유지한다는 뜻이다. 동작 순서를 시간에 따라 두 번 저장하는 장치로 번역하는 대신, 같은 에지에 저장할 값의 선택 우선순위로 번역한 것이다.
mux와 플립플롭이 맡는 일을 나누기
Yosys의 공식 설명에서 Verilog frontend는 표현식의 논리와 제어·저장 요소의 process를 구성한다. 이후 proc 명령이 process를 mux와 레지스터 등의 RTL 셀로 바꾼다. proc는 하나의 거대한 변환이라기보다, 결정 트리를 mux로 바꾸는 proc_mux와 저장 요소를 추출하는 proc_dff 등을 순서대로 호출하는 흐름이다. Yosys의 process 변환 설명
위 예의 en과 force_b는 다음 값 계산의 제어 입력이다. 클록은 그 값을 언제 저장하는지 정한다. mux는 새로운 값을 선택하고 플립플롭은 이전 q를 보관한다. 값 유지가 필요한 경로는 q가 선택 입력으로 되돌아오거나, 이후 최적화에서 레지스터의 enable 동작으로 표현될 수 있다.
리셋도 종류에 따라 다른 저장 동작을 만든다. 이 코드의 rst는 상승 에지에서만 처리된다. 이를 에지 사이에도 q를 바꾸는 비동기 리셋으로 바꿔 놓으면 같은 회로가 아니다. proc 흐름에는 비동기 리셋을 인식하는 proc_arst와 래치를 추출하는 proc_dlatch도 포함되지만, 모든 always 블록이 그 모든 저장 요소를 만든다는 뜻은 아니다. 해당 process의 동작에 맞는 요소를 추출한다.
아직 이 단계의 셀이 최종 공정 라이브러리의 게이트와 일대일로 대응한다고 생각할 필요도 없다. 선택 관계를 중간 표현으로 만든 뒤 다른 최적화와 매핑이 이어진다. 물리적인 mux 개수나 최종 면적은 이 식만으로 확정할 수 없다. 여기서 확인하는 것은 구현에 반드시 남아야 하는 기능 관계다.
작은 표로 변환의 의미를 검산하기
q=9, a=20, b=30을 넣고 리셋이 아닐 때의 네 조합을 확인해 보자.
| en | force_b | 다음 q |
|---|---|---|
| 0 | 0 | 9 |
| 1 | 0 | 20 |
| 0 | 1 | 30 |
| 1 | 1 | 30 |
rst=1이면 위 제어값과 관계없이 다음 q는 0이다. 코드의 대입을 순서대로 계산한 결과와 q_next 식의 결과가 일치한다. 마지막 행을 20으로 바꾸는 변환은 우선순위를 뒤집은 것이며, 첫 행을 0으로 바꾸는 변환은 값 유지 조건을 지운 것이다.
코드만 보고 en을 enable 핀에 붙인 뒤 force_b를 다른 독립 경로로 처리하면 이 표를 놓칠 수 있다. 반대로 식을 제대로 얻었다면 여러 다른 셀 구조로 구현해도 같은 상태 전이를 보존할 수 있다. 이것이 process를 없애도 동작을 유지할 수 있는 근거다.
Yosys의 proc 구현은 기본 흐름에서 변환 뒤 opt_expr를 호출하고, proc -noopt로 이를 생략할 수 있게 한다. proc 전후를 살펴볼 때는 이미 간단한 표현식 정리가 함께 실행됐을 수 있음을 기억하면 좋다. 특정 subpass를 생략하는 옵션도 있으므로 “proc를 호출했다”와 “같은 전체 변환을 거쳤다”를 구분해야 한다. proc의 실제 호출 순서와 옵션
always 블록이 예상과 다른 회로가 됐다면, 먼저 한 에지의 다음 상태 식을 직접 적어 보면 된다. 대입의 우선순위, 미대입 때의 유지, 리셋이 작동하는 순간을 그 식에 넣고 작은 입력 표와 비교한다. 그 관계를 보존했다면 lowering은 코드의 의미를 회로로 옮긴 것이고, 관계가 달라졌다면 RTL 해석이나 변환 결과를 더 살펴볼 지점이 생긴다.