1. 테스트벤치의 구조
테스트벤치는 설계를 시험하려고 작성하는 포트 없는 모듈입니다. 검증 대상 설계인 DUT를 인스턴스로 만들고, 시간에 따라 입력을 바꾸며, 출력을 관찰하거나 기대값과 비교합니다. 클록으로 동작하는 설계는 같은 에지에서 여러 값이 동시에 바뀌므로, 언제 입력을 바꾸고 언제 출력을 읽는지 정확히 알아야 올바른 테스트벤치를 쓸 수 있습니다.
#5 같은 지연, $display 같은 시스템 태스크, 시험 순서를 적은 initial 블록은
시뮬레이션을 위한 구문입니다. 합성 도구는 이런 구문을 무시하거나 거부하며, 어디까지
받아들이는지는 도구마다 다릅니다. @(posedge clock)은 다릅니다. always 블록의 감지
목록으로 쓴 @(posedge clock)은 합성 가능한 레지스터를 기술하는 표준 형식이며, 이 과정의
DUT가 모두 그렇게 씁니다. 테스트벤치 안에서 순서 중간에 기다리는 용도로 쓸 때만 시뮬레이션
장치입니다. DUT는 레지스터와 조합 논리의 관용 형식으로 작성하고, 테스트벤치는 시뮬레이션
구문을 자유롭게 사용합니다. 이 장의 모든 예제는 Icarus 11로 시뮬레이션만 하며 합성은 하지
않습니다.
테스트벤치는 포트가 없는 최상위 모듈입니다. DUT의 입력에 연결할 신호는 테스트벤치가 값을
대입해야 하므로 reg로, 출력에 연결할 신호는 DUT가 구동하므로 wire로 선언합니다.
형식
module tb;
reg 입력_신호; // 테스트벤치가 대입
wire 출력_신호; // DUT가 구동
DUT_모듈 dut (.입력(입력_신호), .출력(출력_신호));
initial begin
// 입력을 바꾸고, 기다리고, 출력을 확인
$finish;
end
endmodulemodule tb;
reg a, b;
wire y;
and_gate dut (.a(a), .b(b), .y(y));
initial begin
a = 1; b = 0;
#1 $display("y=%b", y);
b = 1;
#1 $display("y=%b", y);
$finish;
end
endmodule| 구성 | 역할 |
|---|---|
module tb; | 포트 목록이 없습니다. 아무도 인스턴스로 만들지 않으므로 최상위가 됩니다 |
reg a, b; | DUT 입력을 구동하는 변수입니다 |
wire y; | DUT 출력이 구동하는 net입니다 |
and_gate dut (...) | 검증 대상 인스턴스입니다. 이름은 관례적으로 dut를 씁니다 |
initial | 입력을 바꾸고 출력을 확인하는 순서를 기술합니다 |
$finish | 시뮬레이션을 끝냅니다 |
2. 시간과 절차 블록
2.1 # 지연
# 뒤의 숫자만큼 시뮬레이션 시간이 흐른 뒤 다음 문장을 실행합니다. 여기서 다루는 형식은
절차 지연 제어입니다. Verilog는 연속 대입과 게이트에도 지연을 지정할 수 있지만, 이 장에서는
그 형식을 사용하지 않습니다.
형식
#지연 문장;
#지연;initial begin
a = 0; // 시간 0
#10 a = 1; // 시간 10
#5 a = 0; // 시간 15
end지연은 직전 문장이 실행된 시각부터 셉니다. 위 코드의 두 번째 대입은 시간 10, 세 번째
대입은 그로부터 5 뒤인 시간 15에 실행됩니다. 시간 단위의 물리적 의미는
레지스터 단계·관찰 시점·파형
장의 `timescale이 정합니다.
2.2 initial과 always
| 블록 | 시작 시점 | 실행 방식 |
|---|---|---|
initial | 시간 0 | 안의 문장을 한 번 차례로 실행하고 끝납니다 |
always | 시간 0 | 안의 문장을 끝까지 실행하면 처음부터 다시 실행합니다. 영원히 반복합니다 |
형식
initial 문장;
always 문장;always 블록은 반드시 시간이 흐르는 지점이나 이벤트를 기다리는 지점을 포함해야 합니다.
그런 지점이 없으면 시간 0에서 무한히 반복하게 되므로, Icarus는
always process does not have any delay. 오류로 컴파일을 중단합니다.
2.3 클록 생성
클록은 always 블록에서 일정한 간격으로 값을 반전해 만듭니다.
형식
reg clock = 0;
always #반주기 clock = ~clock;reg clock = 0;
always #5 clock = ~clock; // 주기 10. 상승 에지는 5, 15, 25, ...| 부분 | 의미 |
|---|---|
reg clock = 0; | 선언과 함께 초기값 0을 줍니다. 초기값이 없으면 ~x도 x이므로 클록이 시작되지 않습니다 |
#5 | 반 주기입니다. 5마다 반전하므로 한 주기는 10입니다 |
clock = ~clock | 현재 값을 반전합니다 |
참고. 같은 클록을 initial begin clock = 0; forever #5 clock = ~clock; end로도 쓸 수
있습니다. forever는 안의 문장을 끝없이 반복합니다. 두 방식 모두 시간 5에 첫 상승 에지,
시간 10에 첫 하강 에지가 생깁니다.
2.4 이벤트 제어와 대기
@(...)는 괄호 안의 이벤트가 일어날 때까지 블록의 실행을 멈춥니다.
형식
@(posedge 신호) 문장;
@(negedge 신호) 문장;
repeat (횟수) @(posedge 신호);
wait (조건) 문장;initial begin
@(posedge clock); // 다음 상승 에지까지 대기. 시간 5
@(negedge clock); // 다음 하강 에지까지 대기. 시간 10
repeat (3) @(posedge clock); // 상승 에지 세 번 대기. 시간 35
$display("t=%0t", $time);
end| 구문 | 동작 |
|---|---|
@(posedge clock) | clock이 0에서 1로 바뀔 때까지 기다립니다 |
@(negedge clock) | clock이 1에서 0으로 바뀔 때까지 기다립니다 |
repeat (n) 문장 | 문장을 n번 반복합니다. 이벤트 제어와 함께 쓰면 에지 n번을 기다립니다 |
wait (조건) | 조건이 참이 될 때까지 기다립니다. 이미 참이면 바로 진행합니다 |
$time | 현재 시뮬레이션 시간입니다. %0t로 앞쪽 공백 없이 출력합니다 |
테스트벤치가 레지스터를 에지에서, #1 뒤에, $strobe로 읽을 때 각각 어느 값을 보는지는
레지스터 단계·관찰 시점·파형
장의 관찰 시점 표가 정리합니다. 그 표 뒤의 스케줄링 영역과 입력을 하강 에지에서 바꾸는
까닭은 레지스터와 nonblocking 대입 장에
있습니다.
3. 태스크와 자가 검사
3.1 태스크
태스크는 여러 문장을 이름으로 묶어 반복해서 호출하는 단위입니다. 함수와 달리 지연과 이벤트 제어를 포함할 수 있으므로, 한 사이클 분량의 자극을 인가하는 코드를 묶는 데 적합합니다.
형식
task 태스크_이름(input [폭] 인자, input [폭] 인자);
begin
// 문장. 지연과 이벤트 제어를 쓸 수 있습니다
end
endtaskreg [7:0] a, b;
task show(input [7:0] x, input [7:0] y);
begin
a = x; b = y;
#1 $display("sum=%0d", a + b);
end
endtask
initial begin
show(8'd3, 8'd4); // sum=7
show(8'd10, 8'd20); // sum=30. 이때 시간은 2입니다
end태스크 안의 #1은 호출할 때마다 시간을 흐르게 합니다. 두 번 호출하면 시간은 2가 됩니다.
3.2 !==로 비교하기
| 연산자 | x나 z가 섞였을 때 | if에서의 결과 |
|---|---|---|
!= | 미지 비트 때문에 판정할 수 없으면 x입니다. 알려진 비트가 다르면 1입니다 | 조건이 x이면 거짓으로 처리되어 불일치를 놓칠 수 있습니다 |
!== | 네 상태를 비트마다 정확히 비교해 0 또는 1을 반환합니다 | 기대값이 확정되어 있으면 x 출력을 불일치로 판정합니다 |
reg [7:0] acc, model; // acc는 아직 대입하지 않아 x
initial begin
model = 8'd5;
if (acc != model) $display("mismatch"); // 실행되지 않습니다
if (acc !== model) $display("mismatch"); // 실행됩니다
end자가 검사 테스트벤치의 비교에는 항상 !==를 쓰세요.
3.3 $fatal과 watchdog
$fatal은 메시지를 출력하고 시뮬레이션을 즉시 끝내며, vvp를 0이 아닌 종료 코드로
종료시킵니다. 첫 인자는 종료 시 출력할 진단 정보의 양을 정하는 숫자이고, 이 과정에서는
1을 씁니다.
형식
$fatal(1, "형식", 값, ...);watchdog은 실행이 끝나지 않을 때를 대비한 독립된 initial 블록입니다.
initial begin
#1000 $fatal(1, "TIMEOUT");
end주 initial 블록이 정상적으로 $finish에 도달하면 watchdog의 $fatal은 실행되지 않습니다. 주
블록이 오지 않는 조건을 기다리며 멈추면 시간 1000에 watchdog이 실행을 끝냅니다.
참고. $fatal은 SystemVerilog(IEEE 1800)의 시스템 태스크이며 Verilog-2005에는 없습니다.
Icarus 11은 -g2001과 -g2005에서도 받아들이지만, 다른 Verilog-2005 도구는 거부할 수
있습니다. 이 장 테스트벤치의 나머지 구문은 모두 Verilog-2001입니다. 이 과정은 -g2012로
컴파일하므로 SystemVerilog 키워드도 예약됩니다. -g2005에서는 컴파일되던 before,
logic, bit라는 이름의 신호가 -g2012에서는 syntax error가 됩니다.
3.4 예제: 누산기 자가 검사
자가 검사 테스트벤치는 기대 결과를 직접 계산해 매 사이클 DUT와 비교합니다. 구성은 언제나 비슷합니다.
| 구성 | 역할 |
|---|---|
| 참조 모델 | 테스트벤치 안에 작성한, 의도한 동작의 단순한 기술 |
| 자극 태스크 | 안전한 시점에 한 사이클 분량의 입력을 인가하고 에지를 기다립니다 |
| 비교 | DUT 출력과 모델을 !==로 비교하고 불일치를 셉니다 |
| 요약 | 통과 또는 실패를 알리는 마지막 한 줄 |
| watchdog | 멈춘 실행을 끝내는 독립된 initial 블록 |
Accumulator.v는 add가 1인 에지마다 acc에 in을 더합니다. clear는 acc를 0으로
만들며, if/else if 연쇄가 clear를 먼저 검사하므로 두 입력이 모두 1이면 clear가
우선합니다. 리셋·enable·우선순위 장의
우선순위 규칙입니다.
// 8비트 누산기. clear가 add보다 우선한다.
module accumulator (
input clock,
input clear,
input add,
input [7:0] in,
output reg [7:0] acc
);
always @(posedge clock) begin
if (clear) acc <= 8'd0;
else if (add) acc <= acc + in;
end
endmoduleTb.v는 모델 레지스터 model을 유지합니다. 태스크 step은 다음 순서로 동작합니다.
- 샘플링 에지에서 반 주기 떨어진 하강 에지에서 입력을 설정합니다.
- 상승 에지를 기다립니다.
- DUT와 같은 우선순위 규칙으로
model을 갱신합니다. - DUT 갱신이 끝나도록 1만큼 기다린 뒤 비교합니다.
step 호출 일곱 번은 초기화, 덧셈, add가 0인 유지, clear와 add 동시 인가, 255를 넘는
덧셈을 포함합니다. 두 번째 initial 블록은 시간 1000에 실행을 끝내는 watchdog입니다.
module tb;
reg clock = 0;
reg clear = 0, add = 0;
reg [7:0] in = 0;
wire [7:0] acc;
reg [7:0] model;
integer checks = 0, errors = 0;
accumulator dut (.clock(clock), .clear(clear), .add(add), .in(in), .acc(acc));
always #5 clock = ~clock;
// 한 사이클 분량의 입력을 인가한 뒤 DUT와 모델을 비교한다.
task step(input c, input a, input [7:0] value);
begin
@(negedge clock);
clear = c; add = a; in = value;
@(posedge clock);
if (c) model = 8'd0;
else if (a) model = model + value;
#1;
checks = checks + 1;
if (acc !== model) begin
errors = errors + 1;
$display("MISMATCH check %0d: acc=%0d model=%0d", checks, acc, model);
end
end
endtask
// watchdog은 끝나지 않을 실행을 종료한다.
initial begin
#1000 $fatal(1, "TIMEOUT: no result after 1000 time units");
end
initial begin
step(1, 0, 0);
step(0, 1, 8'd10);
step(0, 1, 8'd20);
step(0, 0, 8'd99);
step(1, 1, 8'd5);
step(0, 1, 8'd200);
step(0, 1, 8'd100);
$display("SELF-CHECK checks=%0d errors=%0d acc=%0d", checks, errors, acc);
if (errors == 0) $display("RESULT PASS");
else $display("RESULT FAIL");
$finish;
end
endmodule이 개념 실습하기
다음 단계로 짧은 Verilog 문제에 이 개념을 적용해 보세요.
실행하기 전에 예측해 보세요.
- 일곱 번의
step이 끝난 뒤acc는 얼마일까요? - 올바른 DUT에서는 몇 줄이 출력될까요?
확인 문제
일곱 번째 step 호출에서 8비트 레지스터에 200 + 100을 더한 뒤 acc는 얼마입니까?
결과 확인.
| 확인 항목 | 내용 |
|---|---|
최종 acc | 10 + 20 = 30, 유지 단계에서 30, clear와 add를 함께 인가하면 0, 다음은 200, 마지막은 200 + 100 = 300이며 8비트에서 44입니다 |
SELF-CHECK 줄 | 검사 7번, 오류 0, 최종값 44를 보입니다 |
RESULT 줄 | RESULT PASS입니다 |
올바른 DUT에서는 이 두 줄 외에 아무것도 출력되지 않습니다. 통과하는 자가 검사 테스트벤치는 조용하고, 실패하는 테스트벤치는 어느 검사가 틀렸는지 알려 줍니다.
직접 바꿔 보세요.
| 바꿀 곳 | 예상 결과 |
|---|---|
Accumulator.v의 조건 순서를 add 먼저, clear 나중으로 | 두 입력을 함께 1로 인가하는 검사는 5번뿐이므로 MISMATCH check 5: acc=35 model=0이 먼저 출력됩니다. 오류가 이후 값에 전파되어 불일치가 두 번 더 나오고 errors=3, RESULT FAIL로 끝납니다. 종료 코드는 0입니다 |
결함을 유지한 채 불일치 분기의 $display를 같은 메시지의 $fatal(1, ...)로 | 첫 불일치에서 실행이 멈추고 파일, 줄, 시간, scope를 담은 FATAL: 줄이 출력됩니다. vvp는 종료 코드 1로 끝납니다 |
DUT를 원래대로 되돌리고 마지막 step 뒤에 wait (acc == 8'd255);를 추가 | acc는 255가 되지 않으므로 주 블록이 계속 기다립니다. 시간 1000에 watchdog이 TIMEOUT: no result after 1000 time units를 출력하고 종료 코드 1로 끝냅니다 |
참고. Icarus는 FATAL: 보고를 표준 오류가 아니라 표준 출력에 씁니다.
3.5 통과하지만 경우를 빠뜨린 테스트벤치
자기 검사 테스트벤치는 실제로 인가한 경우의 불일치만 알려 줍니다. WrapCounter.v는
리셋·enable·우선순위 장의
카운터 계약을 따르도록 만든 설계입니다. 리셋이 먼저, 그다음 enable일 때 5에서 0으로
되돌아감, 그다음 증가, 나머지는 유지입니다. 설계는 아직 열지 말고 Tb.v부터 읽으세요.
module tb;
reg clock = 0;
reg reset = 0, enable = 0;
wire [2:0] count;
reg [2:0] model;
integer checks = 0, errors = 0, edge_time, first_bad = -1;
wrap_counter dut (.clock(clock), .reset(reset), .enable(enable), .count(count));
always #5 clock = ~clock;
initial begin
$dumpfile("dump.vcd");
$dumpvars(0, tb);
end
// 한 사이클. 하강 에지에서 입력을 넣고, 상승 에지에서 모델을 갱신하고, 비교한다.
task step(input r, input e);
begin
@(negedge clock);
reset = r; enable = e;
@(posedge clock);
edge_time = $time;
if (r) model = 3'd0;
else if (e) model = (model == 3'd5) ? 3'd0 : model + 3'd1;
#1;
checks = checks + 1;
if (count !== model) begin
errors = errors + 1;
if (first_bad < 0) first_bad = edge_time;
$display("MISMATCH edge t=%0d reset=%b enable=%b count=%0d model=%0d",
edge_time, r, e, count, model);
end
end
endtask
initial begin
// 지금은 정상 경우만 검사한다.
step(1, 0);
step(0, 1); step(0, 1);
step(0, 0);
step(0, 1);
$display("SELF-CHECK checks=%0d errors=%0d", checks, errors);
if (errors == 0) $display("RESULT PASS");
else $display("RESULT FAIL first mismatch at t=%0d", first_bad);
$finish;
end
endmodulestep(r, e)는 하강 에지에서 reset = r, enable = e를 넣고, 상승 에지에서 계약대로
model을 갱신한 뒤, 1 단위 뒤에 !==로 비교합니다. 첫 불일치가 나면 그 상승 에지 시간을
first_bad에 저장하고, MISMATCH 줄에도 같은 시간이 나옵니다. 실행하면 dump.vcd도
기록합니다.
확인 문제
Tb.v는 step(1,0), step(0,1) 두 번, step(0,0), step(0,1)을 인가합니다. 계약의 조건 가운데 한 번도 인가하지 않는 것은 무엇입니까?
실행을 누르면 SELF-CHECK checks=5 errors=0과 RESULT PASS가 나옵니다. 이 통과는 검사한 다섯
경우가 올바르다는 뜻일 뿐, 인가하지 않은 두 조건에 대해서는 아무것도 말해 주지 않습니다.
빠진 경우를 더하세요. 정상 경우를 끝내는 step(0, 1); 줄 다음에 아래를 넣습니다.
step(0, 1); step(0, 1); step(0, 1);
step(1, 1);앞의 세 단계는 4, 5를 세고 0으로 되돌아가며, 마지막 단계는 enable이 1인 채로 reset을
올립니다. 첫 단계는 시간 10에 인가되어 15의 상승 에지에서 검사되고, 단계마다 한 주기인
10 단위가 걸립니다.
확인 문제
추가한 step(1, 1), 곧 enable이 1인 동안의 리셋은 어느 상승 에지 시간에 검사됩니까?
다시 실행합니다. MISMATCH 줄에는 실패한 단계의 에지 시간이 나오므로, 시간만 보고도 추가한
경우 가운데 어느 것이 실패했는지 알 수 있습니다.
| 줄 | 알려 주는 것 |
|---|---|
MISMATCH edge t=95 reset=1 enable=1 count=1 model=0 | 첫 실패 경우와 그 입력, 두 값 |
SELF-CHECK checks=9 errors=1 | 85의 되돌아감은 통과했고 검사 하나가 실패했습니다 |
RESULT FAIL first mismatch at t=95 | 파형에서 찾아볼 시간 |
파형을 열고 시간 95로 갑니다. reset은 90의 하강 에지에서 올라갔고 enable은 1로
남아 있습니다. 95의 상승 에지에서 count는 0에 머물러야 하는데 1로 올라갑니다. 95
이전은 모두 통과한 구간이므로, 첫 불일치 시간이 파형을 읽기 시작할 자리입니다. 이제 설계를
엽니다.
// 같은 카운터 계약이지만 enable을 리셋보다 먼저 검사한다.
module wrap_counter (
input clock,
input reset,
input enable,
output reg [2:0] count
);
always @(posedge clock) begin
if (enable) begin
if (count == 3'd5) count <= 3'd0;
else count <= count + 3'd1;
end
else if (reset)
count <= 3'd0;
end
endmodule이 설계는 reset보다 enable을 먼저 검사하므로, enable이 1인 사이클의 리셋을 무시합니다.
되돌림은 enable 분기 안에 둔 채 reset을 연쇄의 맨 앞으로 옮기고 다시 실행하면
checks=9 errors=0, RESULT PASS가 됩니다. 실습 문제 reset-priority는 enable이 0인 동안 리셋을
무시하는 반대 방향의 결함입니다. 두 결함을 모두 잡으려면 테스트벤치는 enable이 1일 때와
0일 때 모두 리셋을 인가해야 합니다.
4. 자주 하는 실수
4.1 지연 없는 always 블록
// 잘못된 코드: 시간이 흐르지 않아 무한 반복합니다
always clock = ~clock;
// 바른 코드
always #5 clock = ~clock;always 블록에 지연이나 이벤트 제어가 없으면 시간 0에서 영원히 반복합니다. Icarus는
always process does not have any delay.와 A runtime infinite loop will occur.를
출력하고 컴파일을 중단합니다.
4.2 $finish를 빠뜨림
always #5 clock = ~clock;
// 잘못된 코드: 클록이 계속 돌아 시뮬레이션이 끝나지 않습니다
initial begin
repeat (10) @(posedge clock);
$display("done");
end
// 바른 코드
initial begin
repeat (10) @(posedge clock);
$display("done");
$finish;
end클록을 만드는 always 블록은 끝나지 않으므로, initial 블록이 끝나도 시뮬레이션은
계속됩니다. 실습 환경에서는 실행 시간 제한에 도달할 때까지 끝나지 않습니다. 검사를 마친
지점에 $finish를 두세요.
4.3 !=로 비교해 x를 놓침
// 잘못된 코드: acc가 x이면 조건이 x가 되어 거짓으로 처리됩니다
if (acc != model) errors = errors + 1;
// 바른 코드
if (acc !== model) errors = errors + 1;리셋이 빠진 DUT처럼 출력이 x인 결함은 != 검사를 통과합니다. !==는 x를 불일치로
판정합니다.
4.4 실패를 출력만 하고 종료 코드는 0
// 잘못된 코드: 사람은 FAIL을 읽지만 스크립트는 성공으로 판단합니다
if (errors != 0) $display("RESULT FAIL");
$finish;
// 바른 코드: 실패하면 0이 아닌 종료 코드로 끝냅니다
if (errors != 0) $fatal(1, "RESULT FAIL errors=%0d", errors);
$finish;테스트를 실행하는 스크립트는 출력 문장이 아니라 종료 코드로 통과와 실패를 구분합니다.
자동으로 실행할 테스트벤치는 실패를 $fatal로 끝내세요.
샘플링 에지에서 입력을 바꾸는 실수, 곧 step이 하강 에지에서 입력을 바꿔 피하는 경쟁
조건은 레지스터와 nonblocking 대입 장에서
다룹니다.
5. 정리
| 주제 | 핵심 |
|---|---|
| 테스트벤치 구조 | 포트 없는 모듈입니다. DUT 입력은 reg, 출력은 wire로 연결합니다 |
| 시간 | #n은 n 단위 뒤에 실행합니다. always에는 반드시 지연이나 이벤트 제어가 있어야 합니다 |
| 클록 | reg clock = 0;과 always #5 clock = ~clock;로 주기 10의 클록을 만듭니다 |
| 이벤트 제어 | @(posedge ...), @(negedge ...), repeat (n), wait (조건)으로 기다립니다 |
| 자가 검사 | 모델과 !==로 비교하고, watchdog으로 멈춤을 막고, 실패는 $fatal로 끝냅니다 |
| 경우의 범위 | 통과는 인가한 경우만 보증합니다. 계약의 조건을 나열해 하나씩 인가하고, 첫 불일치 시간을 파형에서 찾습니다 |
이 장 아래의 실습 문제 reset-priority의 카운터는 enable이 0인 동안 리셋을 올려도 값이
그대로 남습니다. 에지마다 어느 분기가 실행되는지 추적해 리셋이 항상 이기도록 고치세요.
테스트벤치가 상승 에지에서 출력하므로, 각 줄을 에지 이전 값으로 읽으세요.
레지스터 단계·관찰 시점·파형
장 관찰 시점 표의 첫 행입니다.
6. 확인 문제
-
자가 검사 테스트벤치가
!=대신!==로 비교하는 이유는 무엇일까요?정답 보기
!=는 미지 비트 때문에 같고 다름을 정할 수 없으면x를 반환하고,if는 이를 거짓으로 처리합니다. 그래서 예제처럼 모든 비트가x인acc는 검사를 통과합니다. 다만 알려진 비트가 다르면!=도 1을 반환합니다.!==는 항상 0이나 1을 반환하며, 기대값이 확정되어 있을 때 실제 값의 미지 비트를 불일치로 판정합니다. -
불일치를 0이 아닌 종료 코드로 바꾼 변경은 무엇이며, 자동 채점에 종료 코드가 필요한 이유는 무엇일까요?
정답 보기
불일치 분기의
$display를$fatal(1, ...)로 바꾸었습니다. watchdog도 같은$fatal로 멈춘 실행을 종료 코드 1로 끝냅니다. 자동 채점 스크립트는 출력 문장의 뜻을 읽지 않고 종료 코드로 통과와 실패를 구분하므로, 실패가 종료 코드에 나타나야 합니다.