assign은 영구적 진리를 기술한다
wire 선언은 신호를 만듭니다. assign은 그것을 무엇이 구동하는지 를 기술합니다. 관계는 연속적입니다: 우변이 무엇이든 좌변은 모든 시뮬레이션 시점에 그것과 같습니다.
wire y;
assign y = a & b;
이 두 줄이 함의하는 것.
- 회로에
y라는 wire가 존재합니다. y는 모든 시점에a와b의 비트와이즈 AND입니다. 어느 쪽이든 바뀌면y가 따라갑니다.
clock도 이벤트도 없습니다. 시뮬레이터는 a 또는 b가 변하는 것을 보고 y를 dirty로 표시하고 식을 다시 평가합니다. 하드웨어에서는 AND 게이트 몇 개로 매핑됩니다: 조합, stateless, 즉각적.
암시적(implicit) 형태
선언과 대입을 한 줄로 합칠 수 있습니다.
wire y = a & b;
위의 두 줄과 동일합니다. 이 스코프 바깥에서 신경 쓰는 사람이 없는 인라인 wire에 유용합니다. 많은 스타일 가이드가 선언과 식을 바로 옆에 두기 때문에 implicit 형태를 선호합니다.
assign이 구동할 수 있는 것
assign의 타겟은 net 타입 이어야 합니다 - 일반 Verilog에서는 wire(또는 더 드문 사촌인 tri, wand, wor)입니다. reg는 될 수 없습니다. 실수로 타겟을 reg로 선언하면
reg y;
assign y = a & b; // 오류: assign으로 reg를 구동할 수 없음
컴파일러가 알려 줍니다. 타겟을 wire로 바꾸거나, 로직을 reg가 합법인 always @(*) 블록으로 옮기세요.
우변은 값으로 평가되는 어떤 것이든 될 수 있습니다: 리터럴, 신호, parameter, 연산자 식, 함수 호출. 여러 입력 너비를 섞을 수 있고 표준 너비 확장 규칙이 적용됩니다.
assign과 always 중 무엇을 쓸까
둘 다 조합 논리를 만들 수 있습니다. 선택은 주로 코드 가독성의 문제입니다.
assign은 관계가 단일 식일 때 좋습니다. 가산기,?:로 만든 단순 mux, mask, parity bit 등 한 줄로 쓸 수 있는 모든 것.always @(*)는 procedural 문이 필요할 때 좋습니다. 다중 분기case, 중첩if/else if, 이름 있는 중간reg로 도움받는 모든 것. 이건 Always Block에서 다룹니다.
같은 4-to-1 mux를 양쪽 방식으로 작성한 예시입니다.
두 모듈 모두 본질적으로 같은 멀티플렉서로 synthesize됩니다. assign 버전은 한 줄이고, always 버전은 여섯 줄입니다. 분기가 네 개라면 차이가 크지 않지만 열여섯 개라면 case 블록이 분명히 더 읽기 쉽습니다.
흔한 패턴
평범한 조합 논리
assign sum = a + b;
assign carry = a[7] & b[7];
assign equal = (data == 8'hFF);
식 하나, wire 하나. assign의 빵과 버터입니다.
2-to-1 Mux
assign out = sel ? a : b;
조건식 하나 - synthesizer가 그것을 2-to-1 mux 하나로 바꿉니다. "a와 b 중 선택"의 가장 깔끔한 표기입니다.
비트 패킹
assign status = {error, overflow, ready, busy, 4'b0};
assign의 우변에서 concatenation을 쓰면 flag를 상태 바이트로 묶을 수 있습니다. 결과는 연속적으로 계산되고 구동됩니다.
Tri-State 출력
assign data_pin = output_enable ? data_out : 1'bz;
output_enable이 high이면 핀을 구동하고, low이면 high impedance로 풉니다. 여러 드라이버가 wire를 공유할 수 있는 칩 핀에서 정석 패턴입니다.
동시성: 여러 assign은 순차가 아니다
계속 강조해도 부족한 사실: 같은 모듈 안의 여러 assign 문은 모두 병렬로 실행됩니다. 시퀀스가 아닙니다.
assign y = a & b; // 모든 시점에 존재
assign z = a | b; // 역시 모든 시점에 존재, 독립적
파일 안의 순서는 무관합니다. 두 식이 동시에 참입니다. synthesizer는 AND 게이트를 OR 게이트의 왼쪽에 둘 수도 오른쪽에 둘 수도 있지만 중요하지 않습니다 - 두 게이트가 연속적으로 동작합니다.
순차적으로 보이는 동작을 원한다면 always 블록(아마도 clock과 함께)을 찾게 됩니다. 그건 다른 장의 이야기입니다.
다중 드라이버: 버스 패턴
wire는 여러 assign이 타겟할 수 있지만, tri-state bus가 아니라면 거의 원하지 않을 일입니다. 두 드라이버가 wire를 두고 다투면 정의되지 않은 동작이 나옵니다.
assign y = a;
assign y = b; // BAD - 드라이버 둘, 시뮬레이터가 하나를 고르거나 X로 만듦
정당한 패턴: 각 드라이버가 비활성일 때 z로 풀고, 동시에 활성인 드라이버는 최대 하나입니다.
assign bus = device_a_active ? data_from_a : 1'bz;
assign bus = device_b_active ? data_from_b : 1'bz;
이게 작동하는 이유는 어떤 시점에든 두 ternary 중 최대 하나만 z가 아닌 값을 만들기 때문입니다. wire의 실제 값은 풀어 주지 않은 드라이버의 값입니다.
내부 로직(칩 핀이나 공유 on-chip bus가 아닌 곳)에서는 wire 하나당 드라이버 하나. 다중 드라이버 버그는 디버그하기 끔찍합니다.
assign이 못 하는 것
assign이 잘못된 도구인 경우.
- 저장.
assign은 조합 관계를 기술합니다 - 플립플롭을 만들지 못합니다. clock 사이클을 가로질러 값을 기억해야 한다면 그건always @(posedge clk)블록입니다. - 다단계 procedural 로직.
assign안에서는if/else나case를 쓸 수 없습니다. 가장 가까운 게?:체인인데, 분기가 셋을 넘으면 보기 흉해집니다. - procedural 블록 내부에서 register를 구동.
reg타겟에는 procedural assignment가 필요하지assign은 아닙니다.
이 한계를 알면 always로 언제 전환할지 결정할 수 있습니다.
다음에 볼 내용
이제 Verilog의 구조적 측면 전체를 봤습니다: 모듈 선언, 인스턴스화, 그리고 assign으로 조합 논리 배선. 다음 장은 procedural block으로 들어갑니다 - 시간과 순서가 의미를 갖기 시작하는 initial과 always 구성요소들.
자주 묻는 질문
Verilog의 continuous assignment는 무엇인가요?
assign target = expression;은 영구적이고 연속적인 관계를 선언합니다: target은 항상 expression과 같습니다. 식의 어떤 신호든 변하면 시뮬레이터는 우변을 다시 평가해 target을 갱신합니다. clock도 이벤트도 없습니다 - 관계는 모든 시점에 참입니다.
Verilog에서 assign으로 무엇을 구동할 수 있나요?
assign은 wire를 구동할 수 있지만 reg는 절대 안 됩니다. 타겟은 net 타입이어야 합니다. 대신 always 블록 내부에서 대입하려면 reg로 선언해야 합니다. 컴파일러는 x가 reg인데 assign x = ...을 쓰면 거부하고, x가 wire인데 always 안에서 x = ...를 쓰면 거부합니다.
assign과 always 블록 중 무엇을 써야 하나요?
단순한 조합 논리에는 assign을 사용하세요 - 입력 하나, 출력 하나, if/else가 필요 없는 경우. always @(*)는 procedural 문(case, if/else if 체인, for 루프)이 필요할 때 사용합니다. 둘 다 조합 하드웨어를 만듭니다 - 선택은 가독성에 관한 것입니다.
Verilog에서 같은 wire에 여러 assign을 할 수 있나요?
각 드라이버가 비활성일 때 wire를 z로 풀어 주는 tri-state bus를 모델링할 때만 가능합니다. 동시에 wire를 명확한 값으로 구동하려는 두 assign은 contention을 일으킵니다 - 시뮬레이터가 하나를 선택할 수도 있고 신호를 X로 만들 수도 있고, 도구에 따라 다릅니다. 일반적인 조합 논리에서는 wire 하나당 드라이버 하나입니다.