본문으로 건너뛰기
code0.HDL

net·변수·조합 always

net과 변수를 구분하고, 멀티플렉서를 assign의 ?:와 always @*의 case로 두 번 쓰고, 모든 경로가 출력에 대입해야 하는 이유를 확인하고, 조합 블록에서는 blocking 대입 =을 씁니다.

이 장에서 배울 내용

  • 신호가 net인지 변수인지, 어느 대입이 그 신호를 대상으로 삼을 수 있는지, reg가 뜻하는 것과 뜻하지 않는 것을 말할 수 있습니다
  • 멀티플렉서를 ?:를 쓴 assign, if/else를 쓴 always @*, case를 쓴 always @*로 쓰고, 모든 입력을 비교해 세 가지가 같은 회로임을 확인할 수 있습니다
  • 모든 경로가 출력에 대입하지 않는 조합 블록을 찾아내고, 그 블록이 출력할 유지된 값을 예측하고, 래치가 생기는 이유를 설명할 수 있습니다
  • net에 절차 대입했을 때 Icarus가 출력하는 l-value 오류를 읽고 두 수정 방법 가운데 하나를 적용할 수 있습니다
이 장에서 배우는 개념
  • MUX·조합 always·모든 분기 대입
먼저 알아야 할 개념

1. 멀티플렉서는 입력 가운데 하나를 고릅니다

멀티플렉서는 데이터 입력 여러 개, 선택 입력 하나, 출력 하나를 가집니다. 선택 입력이 어느 데이터 입력을 출력으로 보낼지 정하고, 나머지 입력은 무시됩니다. 저장되는 값은 없습니다. 선택 입력을 바꾸면 출력이 바로 바뀝니다. 아래 두 입력 실험을 규칙이 분명해질 때까지 조작해 보세요.

개념 실험 · mux2

컴파일한 설계가 아니라 브라우저 안의 회로 모델입니다. 실제 도구 체인은 예제에서 실행됩니다.

입력

출력

y
0
진리표
selaby현재 입력
0000◂ 현재 입력
0010
0101
0111
1000
1011
1100
1111

확인 문제

mux2 회로에서 sel이 1, a가 0, b가 1입니다. y는 무엇입니까?

이 장은 이 멀티플렉서를 Verilog의 assign과 절차 블록으로 쓰고, 세 가지 형식을 나란히 실행해 비교합니다. 형식마다 대입하는 신호의 종류가 다르므로, 신호의 종류부터 구분합니다.

2. 신호에는 net과 변수 두 종류가 있습니다

Verilog 신호는 net과 변수로 나뉩니다. 둘의 차이는 값을 어떻게 받는지, 그리고 값을 유지하는지에 있습니다.

구분대표 선언값을 넣는 방법값을 유지하는가
netwire. 포트는 따로 적지 않으면 wire입니다assign 또는 다른 모듈의 출력유지하지 않습니다. 언제나 구동하는 쪽의 값을 보입니다
변수reg, integerinitial, 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로 선언되어 있습니다.

Mux4.v
// 같은 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과 !==로 비교하고, 결과가 다른 조합의 개수를 출력합니다.

Tb.v
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을 나란히 출력합니다.

LatchMux.v
// 이 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
endmodule
Tb.v
module 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
단계바꾼 입력완전한 멀티플렉서의 출력
1sel=3, d=11
2sel=0, a=11
3sel=3, d=00
4a=0, sel은 그대로 30
5sel=1, b=00

실행하기 전에 예측해 보세요. y 열을 직접 적어 보세요. 1단계에서는 y에 대입한 적이 한 번도 없습니다. 3단계와 4단계에서는 블록이 읽는 입력이 바뀌었으므로 블록이 다시 실행되지만, 맞는 분기가 없습니다.

확인 문제

STEP 3에서 sel은 다시 3이고 d는 0입니다. y는 무엇으로 출력됩니까?

답

결과 확인.

줄yfull확인할 내용
STEP 1x1y에 대입한 적이 없어 초기값 x가 그대로 있습니다
STEP 211맞는 분기가 있어 y가 a를 따릅니다
STEP 3, STEP 410유지된 값입니다. d와 a가 0인데도 y는 STEP 2의 값을 기억합니다
STEP 500다시 맞는 분기가 있습니다

고쳐 보세요. 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가 시뮬레이션할 때 실패할까요?

Majority.v
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. 확인 문제

  1. always 블록 안의 대입 대상이 변수여야 하는 이유는 무엇일까요?

    정답 보기

    절차 대입은 특정 시점에 한 번 실행되고, 대상은 다음 대입까지 그 값을 유지해야 합니다. net은 값을 유지하지 못하고 언제나 구동하는 쪽의 값을 보이므로 절차 대입의 대상이 될 수 없습니다.

  2. output reg [3:0] y를 always @* 안에서 대입합니다. reg 때문에 y가 레지스터가 될까요?

    정답 보기

    아닙니다. 블록은 입력이 바뀔 때마다 현재 입력으로 y를 다시 계산하므로 y는 조합 출력입니다. 변수는 클록 에지에서 대입할 때만 레지스터가 되며, 키워드는 저장 여부와 관계가 없습니다.

실습 문제 select-op는 이 장을 그대로 적용합니다. 2비트 sel이 두 4비트 입력에 적용할 비트 연산 네 가지 가운데 하나를 골라 y를 구동하며, 클록은 없습니다. 시작 코드에는 default가 y를 0으로 두는 always @* 안의 case 뼈대가 있습니다. 모든 선택값에서, 곧 블록의 모든 경로에서 y에 대입하도록 네 연산을 채우세요.

확인 지점

이 장의 확인 문제에 답하세요

참고 자료

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