소프트웨어의 시간 vs 하드웨어의 시간
프로그램에서 시간은 명령어 단위로 흐릅니다. CPU가 1번 줄을 마치고 그다음에 2번 줄을 실행합니다. 두 가지를 동시에 하고 싶다면 thread, async, 혹은 다른 머신을 동원해야 합니다.
하드웨어에서는 모든 것이 동시에 일어납니다. 회로는 차례를 기다리지 않습니다. 가산기는 항상 더하고 있고, 멀티플렉서는 항상 선택하고 있으며, 플립플롭은 항상 클럭을 지켜보고 있습니다. program counter도 없고 "현재 줄"이라는 것도 없습니다.
Verilog은 이 세계를 텍스트로 기술해야 합니다. 그 방식이 이번 장의 모든 혼란의 원천입니다.
두 층의 concurrency
Verilog 모듈을 읽을 때, 여러분은 두 가지 다른 것을 동시에 보고 있는 셈입니다.
- 회로의 정적인 기술. wire, register, 게이트 인스턴스, 서브모듈 인스턴스, 연속 대입(continuous assignment). 이들은 모두 동시에 존재합니다. 파일 안의 순서는 의미가 없습니다.
- Procedural block -
initial과always- 은 시뮬레이터가 차례대로 실행하는 작은 프로그램처럼 보입니다. 그 블록 안에서는 문장이 어떤 의미로든 순서대로 실행됩니다. 하지만 여러always블록이 동시에 활성화될 수 있고, 각 블록은 자기만의 시뮬레이션 시간 스레드에서 돌아갑니다.
module example(input wire a, input wire b, output wire y, output wire z);
assign y = a & b; // 항상 존재
assign z = a | b; // 역시 항상 존재, 병렬
always @(posedge a) begin
// `a`의 rising edge마다 깨어나는
// 별도의 "항상 켜져 있는" 리액터
end
endmodule
두 assign 문은 시퀀스가 아닙니다. synthesis 도구가 나란히 배치할 수 있는 두 개의 조합 논리를 기술합니다. always 블록은 그와 병렬로 일어나는 세 번째 것입니다.
"Signal"이 의미하는 것
소프트웨어에서 변수는 여러분이 바꾸기 전까지 값을 보존합니다. Verilog에서는 signal 이 값을 continuously 보존하며, 모든 순간에 그것을 구동하는 무언가에 의존합니다.
wire는 외부에서 구동됩니다(assign, 서브모듈의 출력 포트, inout 연결). reg는 procedural block 내부에서 구동됩니다. 둘 다 항상 값을 가집니다. C가 말하는 "초기화되지 않은" 상태는 존재하지 않습니다 - signal은 정의된 값으로 구동되거나, 특수한 unknown 값인 x 또는 high-impedance인 z입니다. 뒤의 두 값은 X and Z Values에서 다룹니다.
Clock이 모든 것을 바꾼다
clock이 등장하는 순간 시간이 의미를 갖기 시작합니다. 플립플롭은 입력 값을 clock의 rising(또는 falling) edge에서 포착해 다음 edge까지 보존하는 작은 하드웨어입니다. 카운터, 상태 머신, 파이프라인 - 메모리 가 있는 모든 것 - 를 만들 수 있게 해주는 것이 clock입니다.
q = d가 아니라 q <= d인 점에 주목하세요. 이것이 non-blocking assignment - clocked logic의 핵심 도구입니다. 의미는 "다음 clock edge에 q가 현재의 d 값을 갖도록 스케줄한다"입니다. 자세한 규칙은 Blocking vs Non-blocking에서 다룹니다. 지금은 이 대입이 소프트웨어 문장인 척하지 않는다는 것만 알면 됩니다.
RTL: Register Transfer 사고방식
synthesis 가능한 Verilog 코드의 대부분은 Register Transfer Level(RTL)이라는 스타일로 작성됩니다. 아이디어는 단순합니다.
- 회로에 필요한 상태(register)를 결정합니다.
- 각 register에 대해 두 가지를 기술합니다: 무엇이 reset하는지, 그리고 어떤 조합 논리가 다음 값 을 계산하는지.
- 조합 논리의 출력을 register의 입력에 연결하면 회로가 완성됩니다.
always @(posedge clk) begin
if (reset) state <= IDLE;
else state <= next_state;
end
always @(*) begin
case (state)
IDLE: next_state = start ? RUNNING : IDLE;
RUNNING: next_state = done ? IDLE : RUNNING;
default: next_state = IDLE;
endcase
end
이건 두 상태짜리 상태 머신입니다. 첫 번째 always 블록은 clocked - 즉 플립플롭입니다. 두 번째는 순수 조합 - 그저 식 하나입니다. 여러분이 작성할 거의 모든 상태 머신, 카운터, 파이프라인이 이 형태를 따릅니다.
발목 잡는 소프트웨어 습관들
소프트웨어 출신이라면 다음 습관들을 내려놓아야 합니다.
- "대입하면 변수가 바로 갱신된다." - clock edge 위에서는 그렇지 않습니다. non-blocking assignment는 갱신을 time step 끝으로 스케줄합니다.
- "문장은 위에서 아래로 실행된다." - procedural block 밖에서는 아닙니다. clocked block 안이라면 일종의 순서가 있긴 합니다만, blocking 대 non-blocking이 "순서"의 의미를 바꾸어 놓습니다.
- "필요할 때 할당하면 된다." - 하드웨어는 동적으로 할당하지 않습니다. 모든 register와 게이트는 synthesis 시점에 존재해야 합니다. 모든 vector의 크기는 고정되어 있습니다.
- "이 루프는 한 연산이니까 빠를 거야." - synthesis 가능한 Verilog의
for루프는 병렬 하드웨어로 unroll 됩니다. 64번 반복하는 루프는 본문을 64번 복제한 하드웨어가 되지, 64번 도는 CPU 명령어가 되지 않습니다.
여러분은 프로그램을 작성하는 법을 배우는 게 아니라 회로를 기술하는 법을 배우고 있습니다. 위에서 아래로 읽는 본능이야말로 가장 잘못된 본능이며, 그 본능을 다시 훈련하는 데는 시간이 좀 걸립니다.
다음에 볼 내용
다음 문서들은 로컬 toolchain 설치(선택 사항 - 브라우저 에디터로도 충분합니다)와 첫 모듈을 처음부터 작성하는 과정을 다룹니다. 무언가가 이상해 보일 때마다 우리는 하드웨어 vs 소프트웨어의 대조로 되돌아올 것입니다. 그 "이상한" 부분 대부분의 뿌리가 같기 때문입니다 - 이것은 소프트웨어가 아닙니다.
자주 묻는 질문
Verilog에서 하드웨어와 소프트웨어의 차이는 무엇인가요?
소프트웨어는 CPU가 한 번에 하나씩 실행하는 명령어 시퀀스입니다. Verilog이 기술하는 하드웨어는 모두 동시에 신호를 전달하는 게이트와 wire의 네트워크입니다. Verilog 파일은 그 네트워크를 기술합니다. 시뮬레이터는 그 병렬적 동작을 흉내내고, synthesis 도구는 그것을 실제 실리콘으로 변환합니다.
Verilog 코드는 C처럼 위에서 아래로 실행되나요?
아닙니다 - 그렇게 다루는 것이 초보자가 가장 흔히 저지르는 실수입니다. 최상위 문장(assign, module instance, always 블록)은 모두 동시에 '존재'합니다. 순차 실행처럼 보이는 일이 일어나는 곳은 initial과 always 같은 procedural block 내부뿐이고, 그곳에서도 non-blocking assignment가 그 환상을 깨뜨립니다.
Verilog에서 'concurrent'란 무엇을 의미하나요?
회로의 여러 부분을 기술하는 문장들이 모두 동시에 동작한다는 뜻입니다. 같은 모듈 안의 두 assign 문은 '1번 줄 다음 2번 줄'이 아니라, 두 개의 하드웨어 조각이 병렬로 작동하며 입력에 계속 반응하는 것입니다.
RTL 설계란 무엇인가요?
RTL은 Register Transfer Level의 약자입니다. 회로를 register(플립플롭)와 그 다음 값을 계산하는 조합 논리의 조합으로 기술하는 Verilog 스타일입니다. 대부분의 synthesis 가능한 Verilog 코드가 RTL입니다. 그 위에는 behavioral 레벨이, 아래에는 gate 레벨이 있습니다.