1. 멀티플렉서는 입력 가운데 하나를 고릅니다
멀티플렉서는 데이터 입력 여러 개, 선택 입력 하나, 출력 하나를 가집니다. 선택 입력이 어느 데이터 입력을 출력으로 보낼지 정하고, 나머지 입력은 무시됩니다. 저장되는 값은 없습니다. 선택 입력을 바꾸면 출력이 바로 바뀝니다. 아래 두 입력 실험을 규칙이 분명해질 때까지 조작해 보세요.
개념 실험 · mux2
컴파일한 설계가 아니라 브라우저 안의 회로 모델입니다. 실제 도구 체인은 예제에서 실행됩니다.
입력
출력
- y
- 0
| sel | a | b | y | 현재 입력 |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | ◂ 현재 입력 |
| 0 | 0 | 1 | 0 | |
| 0 | 1 | 0 | 1 | |
| 0 | 1 | 1 | 1 | |
| 1 | 0 | 0 | 0 | |
| 1 | 0 | 1 | 1 | |
| 1 | 1 | 0 | 0 | |
| 1 | 1 | 1 | 1 |
확인 문제
mux2 회로에서 sel이 1, a가 0, b가 1입니다. y는 무엇입니까?
이 장은 이 멀티플렉서를 Verilog의 assign과 절차 블록으로 쓰고, 세 가지 형식을 나란히
실행해 비교합니다. 형식마다 대입하는 신호의 종류가 다르므로, 신호의 종류부터 구분합니다.
2. 신호에는 net과 변수 두 종류가 있습니다
Verilog 신호는 net과 변수로 나뉩니다. 둘의 차이는 값을 어떻게 받는지, 그리고 값을 유지하는지에 있습니다.
| 구분 | 대표 선언 | 값을 넣는 방법 | 값을 유지하는가 |
|---|---|---|---|
| net | wire. 포트는 따로 적지 않으면 wire입니다 | assign 또는 다른 모듈의 출력 | 유지하지 않습니다. 언제나 구동하는 쪽의 값을 보입니다 |
| 변수 | reg, integer | initial, always 안의 대입문 | 유지합니다. 마지막으로 대입한 값을 기억합니다 |
net은 회로의 전선이고, 변수는 절차 대입 사이에서 시뮬레이션이 값을 보관하는 이름입니다.
하드웨어 저장소와는 다릅니다. always @*에서 대입하는 변수는 조합 논리를 기술하고, 클록
에지에서 대입하는 변수만 레지스터가 됩니다. 그 경계는 레지스터와
nonblocking 대입 장에서 확인합니다.
주의. reg라는 이름 때문에 하드웨어 레지스터를 떠올리기 쉽지만, reg로 선언했다고
플립플롭이 생기지는 않습니다. 실제로 만들어지는 회로는 선언 키워드가 아니라 값을 대입하는
방식이 결정합니다.
3. 연속 대입과 절차 대입
두 종류의 신호에 맞춰 대입문도 두 가지가 있습니다.
| 대입 | 형식 | 쓰는 위치 | 대상 |
|---|---|---|---|
| 연속 대입 | assign 대상 = 식; | 모듈 본문 | net |
| 절차 대입 | 대상 = 식; 또는 대상 <= 식; | initial, always 블록 안 | 변수 |
연속 대입은 시간과 상관없이 항상 성립하는 연결입니다. 절차 대입은 특정 시점에 실행되고, 대상은 다음 대입까지 그 값을 유지해야 합니다. net은 값을 유지할 수 없으므로 net에 절차 대입을 하면 컴파일 오류가 발생합니다.
반대로 변수에 assign을 쓰는 경우는 언어 버전에 따라 다릅니다.
언어 버전(iverilog 옵션) | reg에 assign |
|---|---|
Verilog-2005(-g2005) | 오류입니다. reg y; cannot be driven by primitives or continuous assignment. |
SystemVerilog(-g2012) | 다른 구동원이 없으면 허용됩니다 |
이 과정의 예제는 -g2012로 컴파일합니다.
참고. SystemVerilog의 logic은 네 상태를 나타내는 데이터 타입이며 net과 변수의 구분을
없애지 않습니다. logic y;는 변수, wire logic y;는 net 선언입니다. 이 과정은 둘의 차이를
분명히 보여 주려고 wire와 reg를 사용합니다.
4. 첫 번째 방법: assign과 ?:
조건 연산자 ?:는 멀티플렉서를 식으로 쓴 것입니다. 조건 ? x : y는 조건이 참이면 x,
아니면 y이며 전체가 배선입니다.
module mux2 (
input sel,
input [3:0] a,
input [3:0] b,
output [3:0] y
);
assign y = sel ? b : a;
endmodule두 분기는 리터럴·signed·식의
폭 장의 문맥 폭으로 계산되므로 a, b는 y와 같은 폭으로 선언합니다. 두 갈래 선택에는
이 형식이 가장 짧고 읽기 쉽습니다.
5. 두 번째 방법: always @*와 case
갈래가 여럿이면 중첩한 ?:보다 절차 블록 안의 case가 읽기 쉽습니다. always @*는 "이
블록이 읽는 신호가 하나라도 바뀌면 블록을 다시 실행하라"는 뜻이며, 조합 논리의 동작과
같습니다. 조합 논리를 절차 코드로 쓰는 방법이 이것입니다. always 안에서 대입하는 대상은
변수여야 하므로 출력을 reg로 선언합니다.
module mux4 (
input [1:0] sel,
input [3:0] a, b, c, d,
output reg [3:0] y
);
always @* begin
case (sel)
2'd0: y = a;
2'd1: y = b;
2'd2: y = c;
default: y = d;
endcase
end
endmodule여기서 reg는 저장소를 만들지 않습니다. 블록은 입력이 바뀔 때마다 현재 입력으로 y를 다시
계산하므로 y는 assign 판과 똑같은 조합 출력입니다. 변수를 레지스터로 만드는 것은 대입
방식이며, 그 내용은 다음 장에 있습니다.
참고. 예제는 iverilog -g2012로 컴파일합니다. always @*는 Verilog-2001 문법이고 이
과정은 이 형식을 씁니다. SystemVerilog는 같은 용도로 always_comb를 추가했지만 여기서는
쓰지 않습니다.
확인 문제
mux4에서 sel이 2'd2입니다. 어느 입력이 y에 도달합니까?
6. 세 가지로 쓴 멀티플렉서를 시뮬레이션으로 비교하기
세 번째 방법은 always @* 안에 if/else if 연쇄를 쓰는 것입니다. Mux4.v는 1비트
입력 네 개의 4:1 멀티플렉서를 세 가지로 씁니다. mux4_assign은 중첩한 ?:로,
mux4_if는 if/else 연쇄로, mux4_case는 default가 있는 case로 씁니다. 파일
어디에도 클록이 없는데, 세 출력 가운데 두 개는 output reg로 선언되어 있습니다.
// 같은 4:1 멀티플렉서를 문법상 올바른 세 가지 방법으로 쓴다.
module mux4_assign (
input [1:0] sel,
input a, b, c, d,
output y
);
assign y = (sel == 2'd0) ? a :
(sel == 2'd1) ? b :
(sel == 2'd2) ? c : d;
endmodule
module mux4_if (
input [1:0] sel,
input a, b, c, d,
output reg y
);
// 여기서 reg는 플립플롭이 아니라 변수다. 클록이 없다.
always @* begin
if (sel == 2'd0) y = a;
else if (sel == 2'd1) y = b;
else if (sel == 2'd2) y = c;
else y = d;
end
endmodule
module mux4_case (
input [1:0] sel,
input a, b, c, d,
output reg y
);
always @* begin
case (sel)
2'd0: y = a;
2'd1: y = b;
2'd2: y = c;
default: y = d;
endcase
end
endmodule확인 문제
Mux4.v의 mux4_if와 mux4_case는 y를 output reg로 선언합니다. 파일 전체가 기술하는 플립플롭은 몇 개입니까?
Tb.v는 세 모듈에 같은 입력을 연결합니다. 먼저 a=0 b=1 c=1 d=0을 고정한 채 sel을
0부터 3까지 바꾸며, 값마다 세 출력을 나란히 적은 SEL 줄을 출력합니다. 그다음 sel과
데이터 네 비트의 조합 64가지를 모두 넣어 mux4_if와 mux4_case를 mux4_assign과
!==로 비교하고, 결과가 다른 조합의 개수를 출력합니다.
module tb;
reg [1:0] sel;
reg a, b, c, d;
wire y_assign, y_if, y_case;
integer i, mismatches = 0;
mux4_assign u_assign (.sel(sel), .a(a), .b(b), .c(c), .d(d), .y(y_assign));
mux4_if u_if (.sel(sel), .a(a), .b(b), .c(c), .d(d), .y(y_if));
mux4_case u_case (.sel(sel), .a(a), .b(b), .c(c), .d(d), .y(y_case));
initial begin
// 데이터 패턴 하나(a=0 b=1 c=1 d=0)에 select 값을 모두 넣어 본다.
{a, b, c, d} = 4'b0110;
for (i = 0; i < 4; i = i + 1) begin
sel = i;
#1 $display("SEL %0d assign=%b if=%b case=%b", sel, y_assign, y_if, y_case);
end
// sel과 데이터의 모든 조합. 4 x 16 = 64가지다.
for (i = 0; i < 64; i = i + 1) begin
{sel, a, b, c, d} = i;
#1;
if (y_if !== y_assign || y_case !== y_assign) mismatches = mismatches + 1;
end
$display("COMPARED 64 combinations mismatches=%0d", mismatches);
$finish;
end
endmodule실행하기 전에 예측해 보세요. SEL 줄마다 어느 입력이 선택되고 그 값은 무엇일까요?
세 열이 다른 줄이 있을까요?
확인 문제
a=0 b=1 c=1 d=0일 때 SEL 3 줄에는 무엇이 출력됩니까?
결과 확인. SEL 줄은 차례로 0, 1, 1, 0으로 a, b, c, d의 값과 같고, 세 열은
모든 줄에서 일치합니다. 마지막 줄은 COMPARED 64 combinations mismatches=0입니다. 이진 입력
조합 64가지에서 세 기술의 결과가 일치한다는 뜻입니다. 선택값에 x나 z가 섞인 경우는
이 비교에 포함되지 않습니다.
하나만 바꿔 보세요. mux4_case에서 default: y = d; 줄을 지우고 다시 실행합니다. 그전에
SEL 3 줄부터 예측해 보세요.
확인 문제
default를 지우면 SEL 3 줄에는 무엇이 출력됩니까?
이제 실행은 mismatches=8로 끝납니다. sel = 3인 조합 16가지 동안 mux4_case는 마지막
sel = 2 조합에서 받은 1을 계속 갖고 있어서, d가 0인 조합에서만 틀립니다. 다음으로
넘어가기 전에 default를 되돌려 두세요. 값을 붙잡아 두는 것이 왜 문제인지는 다음 절에서
다룹니다.
7. 블록의 모든 경로가 출력에 대입해야 합니다
mux4의 case에 2'd0과 2'd1 분기만 있고 default가 없다고 가정합니다. sel이 2나 3이면
블록 안의 어떤 문장도 y에 대입하지 않습니다. 시뮬레이션에서 y는 직전 값을 그대로
유지합니다. 그러면 이 기술은 이전 값의 기억을 필요로 하는데, 조합 블록에는 기억이 있어서는
안 됩니다. 합성 도구도 같은 방식으로 읽어, 이전 값을 붙잡아 둘 저장 소자인 래치를 만들어야
합니다. 대부분의 도구가 경고를 출력하고, 회로는 의도한 멀티플렉서가 아닙니다. Icarus는
경고를 출력하지 않으므로, 단서는 출력에 남은 이전 값뿐입니다.
LatchMux.v는 case에 0, 1, 2 분기만 있고 3 분기와 default가 없는 4:1 멀티플렉서입니다.
Tb.v는 입력을 다섯 번 바꾸면서, 바꿀 때마다 이 멀티플렉서의 y와 빠진 분기가 없는
멀티플렉서가 낼 값 full을 나란히 출력합니다.
// 이 case에는 sel = 3 분기도 default도 없다.
module mux4_missing (
input [1:0] sel,
input a, b, c, d,
output reg y
);
always @* begin
case (sel)
2'd0: y = a;
2'd1: y = b;
2'd2: y = c;
endcase
end
endmodulemodule tb;
reg [1:0] sel;
reg a = 0, b = 0, c = 0, d = 0;
wire y;
// 비교용으로, 빠진 분기가 없는 멀티플렉서라면 낼 출력.
wire full = (sel == 2'd0) ? a : (sel == 2'd1) ? b : (sel == 2'd2) ? c : d;
mux4_missing dut (.sel(sel), .a(a), .b(b), .c(c), .d(d), .y(y));
task show(input integer n);
#1 $display("STEP %0d sel=%0d a=%b d=%b -> y=%b full=%b", n, sel, a, d, y, full);
endtask
initial begin
sel = 3; d = 1; show(1);
sel = 0; a = 1; show(2);
sel = 3; d = 0; show(3);
a = 0; show(4);
sel = 1; show(5);
$finish;
end
endmodule| 단계 | 바꾼 입력 | 완전한 멀티플렉서의 출력 |
|---|---|---|
| 1 | sel=3, d=1 | 1 |
| 2 | sel=0, a=1 | 1 |
| 3 | sel=3, d=0 | 0 |
| 4 | a=0, sel은 그대로 3 | 0 |
| 5 | sel=1, b=0 | 0 |
실행하기 전에 예측해 보세요. y 열을 직접 적어 보세요. 1단계에서는 y에 대입한 적이
한 번도 없습니다. 3단계와 4단계에서는 블록이 읽는 입력이 바뀌었으므로 블록이 다시 실행되지만,
맞는 분기가 없습니다.
확인 문제
STEP 3에서 sel은 다시 3이고 d는 0입니다. y는 무엇으로 출력됩니까?
결과 확인.
| 줄 | y | full | 확인할 내용 |
|---|---|---|---|
STEP 1 | x | 1 | y에 대입한 적이 없어 초기값 x가 그대로 있습니다 |
STEP 2 | 1 | 1 | 맞는 분기가 있어 y가 a를 따릅니다 |
STEP 3, STEP 4 | 1 | 0 | 유지된 값입니다. d와 a가 0인데도 y는 STEP 2의 값을 기억합니다 |
STEP 5 | 0 | 0 | 다시 맞는 분기가 있습니다 |
고쳐 보세요. 2'd2 분기 다음에 default: y = d;를 넣고 다시 실행합니다. 이제 STEP 1을
포함한 모든 줄에서 y와 full이 같습니다. y를 비워 두는 경로가 없으니 기억할 것도
없습니다.
규칙은 적용하기 쉽습니다. case에 default 분기를 두거나, case 앞에서 출력에 한 번
대입해 모든 경로가 값을 갖게 하세요. else 없는 if에도 같은 규칙이 적용됩니다.
확인 문제
조합 블록에서 래치를 만들어 내는 것은 어느 것입니까?
8. 조합 블록의 대입은 blocking =입니다
조합 블록 안에서는 blocking 대입 =을 씁니다. =은 대상을 즉시 바꾸므로 블록의 다음 문장이
이미 새 값을 읽습니다. 중간값을 계산한 뒤 그 값을 쓰는 조합 단계의 연쇄에 필요한 동작입니다.
always @* begin
tmp = a & b; // 이 줄부터 tmp는 새 값
y = tmp | c; // 새 tmp를 읽음
end다른 대입 <=은 갱신을 늦추며 클록 블록의 레지스터에 씁니다. 두 대입을 한 블록에 섞는 것이
초보자 버그의 가장 흔한 출처이고, 레지스터와
nonblocking 대입 장에서 그 경계를 긋습니다.
확인 문제
위 블록에서 a = 1, b = 1, c = 0입니다. 블록이 실행된 뒤 y는 무엇입니까?
9. 일부러 실패하는 블록을 실행해 보세요
다음 예제는 의도적으로 컴파일 오류를 발생시킵니다. Majority.v의 majority는 세 입력
가운데 둘 이상이 1이면 1을 출력하는 다수결 회로입니다. always @* 블록으로 썼는데, 출력 y를
output y로만 선언했으므로 y는 net(wire)입니다. 그런데 블록 안에서 y에 값을 대입합니다.
실행 전에 정하세요. 이 코드는 iverilog가 컴파일할 때 실패할까요, vvp가 시뮬레이션할 때
실패할까요?
module majority (
input a,
input b,
input c,
output y
);
// y는 net이므로 절차 블록에서 대입할 수 없다.
always @* begin
y = (a & b) | (a & c) | (b & c);
end
endmodule이 개념 실습하기
다음 단계로 짧은 Verilog 문제에 이 개념을 적용해 보세요.
확인 문제
Majority.v는 어느 단계에서 실패합니까?
결과 확인. 실행을 누르고 메시지 두 줄을 읽으세요.
| 확인 항목 | 내용 |
|---|---|
| 잘못된 코드 | net인 y에 always @* 블록 안에서 y = ...로 대입했습니다 |
| 실패 단계 | iverilog의 컴파일 단계입니다. vvp는 실행되지 않습니다 |
| 오류 메시지 | y is not a valid l-value in majority.와 y is declared here as wire. |
| 고치는 방법 | output reg y로 선언하거나, 블록 대신 assign y = ...;를 씁니다 |
오류 메시지의 l-value는 "대입문의 왼쪽에 올 수 있는 대상"이라는 뜻입니다. 두 번째 줄이 y를
wire로 선언한 위치를 알려 주므로, 선언부터 확인하면 됩니다.
그다음 하나만 바꿔 보세요. 선언을 output reg y로 바꾸고 다시 실행하면 컴파일이 됩니다.
테스트벤치가 없는 파일이므로 아무것도 출력되지 않고 종료 코드 0만 남습니다. 다른 수정 방법은
블록을 지우고 같은 식을 assign 뒤에 쓰는 것입니다. 두 방법은 모두 같은 다수결 회로를
만듭니다. 이 예제처럼 식 하나로 끝나는 논리는 assign이 읽기 쉽고, 조건 분기가 여러 개인
논리는 reg와 always @*로 쓰는 편이 읽기 쉽습니다.
10. 자주 하는 실수
10.1 net에 절차 대입하기
// 잘못된 코드: y는 wire
output y;
always @* y = a & b;
// 바른 코드 1: 변수로 선언
output reg y;
always @* y = a & b;
// 바른 코드 2: 연속 대입 사용
output y;
assign y = a & b;always나 initial 안에서 값을 넣는 대상은 변수여야 합니다. reg, integer, 그리고
-g2012에서는 logic이 변수입니다. 오류 메시지는 ... is not a valid l-value입니다.
10.2 대입하지 않은 변수 읽기
reg [7:0] total;
initial $display("%d", total); // 0이 아니라 x가 출력됩니다변수의 초기값은 0이 아니라 x입니다. 출력에 x가 보이면 그 변수가 사용 전에 대입되었는지,
계산에 쓰인 입력 가운데 x나 z가 있는지 확인하세요.
10.3 값을 바꾸자마자 출력 읽기
data = 8'hFF;
$display("%b", high); // 아직 이전 값이나 x가 보일 수 있습니다
#1;
$display("%b", high); // 새 값이 보입니다입력을 바꾼 바로 그 시점에는 assign이 아직 다시 계산되지 않았을 수 있습니다.
테스트벤치에서는 입력을 바꾼 뒤 #1처럼 한 시간 단위를 기다리고 출력을 읽으세요.
11. 정리
| 요소 | 뜻 |
|---|---|
net(wire)과 변수(reg, integer) | net은 assign으로 구동하고 값을 유지하지 않음. 변수는 절차 블록에서 대입하고 값을 유지 |
assign y = sel ? b : a; | 연속 대입. 배선으로 쓴 두 갈래 멀티플렉서 |
always @* … case | 절차 코드로 쓴 조합 논리. 대상은 reg |
?:, if/else, default 있는 case | 같은 멀티플렉서를 쓰는 세 가지 방법. 모든 입력을 비교하면 불일치 0 |
reg | 변수일 뿐 저장소가 아님. 대입 방식이 결정 |
| 모든 경로가 대입 | 아니면 이전 값을 유지해야 하고, 그것이 래치 |
always @* 안의 = | blocking. 다음 문장이 새 값을 봄 |
net에 y = … | 컴파일 오류. not a valid l-value |
12. 확인 문제
-
always블록 안의 대입 대상이 변수여야 하는 이유는 무엇일까요?정답 보기
절차 대입은 특정 시점에 한 번 실행되고, 대상은 다음 대입까지 그 값을 유지해야 합니다. net은 값을 유지하지 못하고 언제나 구동하는 쪽의 값을 보이므로 절차 대입의 대상이 될 수 없습니다.
-
output reg [3:0] y를always @*안에서 대입합니다.reg때문에y가 레지스터가 될까요?정답 보기
아닙니다. 블록은 입력이 바뀔 때마다 현재 입력으로
y를 다시 계산하므로y는 조합 출력입니다. 변수는 클록 에지에서 대입할 때만 레지스터가 되며, 키워드는 저장 여부와 관계가 없습니다.
실습 문제 select-op는 이 장을 그대로 적용합니다. 2비트 sel이 두 4비트 입력에 적용할 비트
연산 네 가지 가운데 하나를 골라 y를 구동하며, 클록은 없습니다. 시작 코드에는 default가 y를
0으로 두는 always @* 안의 case 뼈대가 있습니다. 모든 선택값에서, 곧 블록의 모든 경로에서
y에 대입하도록 네 연산을 채우세요.