본문으로 건너뛰기
code0.HDL

어휘 요소

bsc가 소스를 토큰으로 나누는 규칙과 식별자, 키워드, 주석, 정수·Real·문자열 리터럴, 컴파일러 지시어의 정확한 표기법을 다룹니다.

이 장에서 배울 내용

  • 오류를 보고한 bsc 단계를 읽어 구문 오류와 타입 오류를 구분한다
  • 첫 글자의 대소문자 규칙과 키워드 목록을 지켜 올바른 식별자를 고른다
  • 크기 지정·미지정·채움·Real·문자열 리터럴을 폭 규칙과 이스케이프 규칙에 맞게 읽고 쓴다
  • define, ifdef 같은 지시어를 텍스트 치환으로 이해하고 식 매크로에 필요한 괄호를 붙인다
이 장에서 배우는 개념
  • 어휘 요소
먼저 알아야 할 개념

bsc는 소스를 읽을 때 가장 먼저 문자들을 토큰으로 나눕니다. 식별자, 키워드, 리터럴, 연산자, 구두점이 모두 토큰입니다. 토큰 경계가 정해진 다음에야 구문 분석, 타입 검사, elaboration이 진행되므로, 토큰 규칙을 모르면 오류 메시지가 어느 단계에서 나왔는지 판단하기 어렵습니다. 이 장은 BSV Language Reference Guide 제2장의 어휘 요소를 다룹니다. 모듈과 인터페이스 장과 레지스터와 rule 하나 장에서 본 module, rule, Reg, $display를 읽을 수 있으면 충분합니다. 이 장의 예제는 대부분 rule 하나에서 값을 출력하고 끝납니다. 어휘 요소 가운데 don't-care value ?는 실제로 쓰이는 case와 함께 선택·case·don't-care 값 장에서 다룹니다.

1. 식별자와 키워드

1.1 식별자 규칙

식별자는 변수, 모듈, 타입 같은 대상에 붙이는 이름입니다. 영문자, 숫자, _, $로 이루어지며 숫자로 시작할 수 없습니다. 대소문자를 구분하므로 count와 Count는 다른 이름입니다.

형식

첫_글자(영문자, _, $) + 나머지(영문자, 숫자, _, $)
Bit#(8) data_in;   // 올바른 식별자
Bit#(8) count2;    // 숫자는 첫 글자만 아니면 쓸 수 있습니다
Bit#(8) 2count;    // 오류: 숫자로 시작합니다

$로 시작하는 이름은 $display, $finish 같은 시스템 태스크와 시스템 함수에 예약되어 있습니다. 인스턴스 이름을 _로 시작하면 생성된 Verilog의 계층 이름에서 그 인스턴스 이름이 생략됩니다.

1.2 첫 글자의 대소문자

BSV는 첫 글자의 대소문자로 이름의 종류를 구분합니다. 이 규칙은 관례가 아니라 문법이므로 어기면 컴파일 오류가 발생합니다.

첫 글자쓰는 곳예
대문자패키지, 타입, 인터페이스 타입, enum label, tagged union 멤버, 타입클래스Tally, Bit, Reg, True
소문자변수, 모듈, 인터페이스 인스턴스, rule, method, 그 밖의 값count, mkTop, step
Bit#(4) Count = 4'd1;   // 오류: 변수 이름이 대문자로 시작합니다
Bit#(4) count = 4'd1;   // 바른 코드

첫 번째 줄은 P0005 오류를 발생시키며, 메시지에 소문자로 시작하는 식별자가 필요하다는 안내(identifier starting with lowercase)가 표시됩니다.

1.3 키워드

키워드는 언어가 미리 정해 둔 단어로, 식별자로 쓸 수 없습니다. module, rule, method, function, if, case 같은 단어가 키워드입니다. BSV 키워드에는 SystemVerilog 키워드가 모두 포함되며, 대부분 소문자이고 valueOf만 대문자를 포함합니다. 특수 타입 이름 Action과 ActionValue도 다시 정의할 수 없습니다.

Bit#(4) rule = 4'b1010;       // 오류: rule은 키워드입니다
Bit#(4) ruleValue = 4'b1010;  // 바른 코드

문맥이 어떻든 키워드는 식별자로 바뀌지 않습니다. 변수 선언 자리에 rule이 오면 bsc는 그것을 새 rule의 시작으로 읽으려다 실패합니다.

1.4 예제: 키워드를 변수 이름으로 쓰기

다음 예제는 일부러 컴파일 오류를 발생시킵니다. rule show 안에서 지역 변수 이름을 rule로 선언합니다. 이 소스는 bsc 컴파일 단계에서 멈추므로 시뮬레이션은 실행되지 않습니다.

실행하기 전에 예측해 보세요. 오류는 타입 검사에서 날까요, 구문 분석에서 날까요? $display 줄까지 처리될까요?

확인 문제

KeywordAsIdentifier.bsv는 어느 단계에서 멈출까요?

답

KeywordAsIdentifier.bsv
package KeywordAsIdentifier;

(* synthesize *)
module mkTop(Empty);

  rule show;
    // rule은 키워드이므로 변수 이름으로 사용할 수 없다.
    Bit#(4) rule = 4'b1010;
    $display("%b", rule);
    $finish(0);
  endrule

endmodule

endpackage

결과 확인.

확인 항목내용
잘못된 코드지역 변수 이름으로 키워드 rule을 썼습니다
실패 단계bsc의 구문 분석 단계입니다. 타입 검사와 시뮬레이션은 실행되지 않습니다
오류 메시지P0005와 Unexpected BSV 3.8 keyword `rule'
고치는 방법ruleValue처럼 키워드가 아닌 소문자 이름으로 바꿉니다

직접 바꿔 보세요. 선언과 $display의 rule을 모두 ruleValue로 바꾸면 컴파일이 통과하고 1010이 출력됩니다.

2. 공백과 주석

2.1 공백

공백, 탭, 줄바꿈, 폼 피드, 캐리지 리턴은 토큰 사이에 자유롭게 넣을 수 있습니다. 공백은 토큰을 나누는 역할만 하며, 한번 나뉜 토큰을 다시 이어 붙이지 않습니다.

2.2 줄 주석과 블록 주석

형식

// 줄 끝까지 주석
/* 여러 줄에 걸칠 수 있는 주석 */
Bit#(8) limit = 8'd5;  // 상한값
/* 아래 rule은
   limit까지만 셉니다 */
주석끝나는 위치안에 든 다른 주석 기호
//줄바꿈 직전/*, */도 일반 문자입니다
/* ... */처음 만나는 *///와 두 번째 /*도 일반 문자입니다

주석은 공백과 똑같이 취급됩니다. 즉 주석도 토큰을 나눌 뿐 이어 붙이지 않습니다.

주의. 블록 주석은 중첩되지 않습니다. /* a /* b */ c */에서는 처음 나오는 */가 주석을 닫으므로 c */가 소스 코드로 남아 P0005 구문 오류가 발생합니다. 닫는 */가 아예 없으면 P0003 오류가 발생합니다.

2.3 예제: 주석으로 나뉜 식별자

다음 예제는 일부러 컴파일 오류를 발생시킵니다. 변수 이름 value 한가운데에 블록 주석을 넣어 val/* gap */ue라고 썼습니다.

실행하기 전에 예측해 보세요. 주석이 사라진 뒤 bsc가 보는 토큰은 무엇일까요? value 하나일까요, 두 개의 식별자일까요?

확인 문제

val/* gap */ue에서 주석이 사라진 뒤 bsc가 보는 토큰은 무엇일까요?

답

TokenCommentSplit.bsv
package TokenCommentSplit;

(* synthesize *)
module mkTop(Empty);
  Bit#(4) val/* gap */ue = 4'b1010;
endmodule

endpackage

결과 확인.

확인 항목내용
잘못된 코드식별자 한가운데에 블록 주석을 넣었습니다
실패 단계bsc의 구문 분석 단계입니다
오류 메시지P0005와 Unexpected identifier `ue'
고치는 방법주석을 value /* gap */ = ...처럼 완성된 토큰 사이로 옮깁니다

주석이 공백으로 바뀌어 val과 ue 두 식별자가 남습니다. 선언에서 val 다음에는 식별자가 올 수 없으므로 ue에서 오류가 납니다. "토큰 안의 주석"이라는 별도 진단은 없으므로, P0005가 엉뚱한 이름을 가리키면 그 이름 바로 앞에 주석이나 공백이 끼어 있는지 확인하세요.

3. 정수 리터럴

3.1 크기 지정 리터럴

크기 지정 리터럴은 폭, 작은따옴표, 진법 문자, 숫자를 차례로 씁니다.

형식

<폭>'<진법><숫자>
진법 문자진법예값
d 또는 D10진수8'd4242
b 또는 B2진수8'b1010_1010170
o 또는 O8진수6'o5242
h 또는 H16진수16'hBEEF48879

숫자 사이의 밑줄 _은 읽기 쉽게 끊어 쓰는 표시이며 값에 영향을 주지 않습니다. 진법 문자 바로 뒤에도 쓸 수 있으므로 16'h_BEEF와 16'hBEEF는 같은 값입니다.

3.2 크기 미지정 리터럴

폭을 생략하면 크기 미지정 리터럴입니다. 125, -16, 'h48454a가 모두 크기 미지정 리터럴이며, 폭은 리터럴이 쓰인 문맥이 정합니다.

Bit#(8) a = 125;     // 문맥이 Bit#(8)이므로 8비트 값
Int#(4) b = -8;      // 부호가 리터럴의 일부: Int#(4)의 최솟값
Int#(4) c = 8;       // 오류: Int#(4)는 -8부터 7까지
구분부호최초 타입문맥 타입으로 바꾸는 함수
크기 미지정리터럴의 일부입니다IntegerLiteral 타입클래스의 fromInteger
크기 지정문법에 없습니다. -8'd1의 -는 단항 연산자입니다폭에 맞는 비트 벡터SizedLiteral 타입클래스의 fromSizedInteger

Int#(4) c = 8;은 T0051 오류 Literal 8 is not a valid Int#(4).를 발생시킵니다. 반면 -8은 부호까지 하나의 리터럴이므로 표현 범위 안에 들어갑니다.

3.3 채움 리터럴

'0과 '1은 채움 리터럴입니다. 문맥이 요구하는 폭의 모든 비트를 각각 0과 1로 채웁니다.

Bit#(12) zeros = '0;   // 12비트 모두 0
Bit#(5)  ones  = '1;   // 5비트 모두 1, 즉 5'b11111

채움 리터럴은 리터럴만 보고는 폭을 알 수 없습니다. 같은 '1도 선언된 타입에 따라 다른 비트 수가 됩니다.

3.4 폭보다 큰 값

리터럴 값이 폭 안에 들어가지 않으면 상위 비트를 버리지 않고 컴파일 오류를 발생시킵니다.

코드결과
Bit#(8) x = 8'd300;T0132 오류 The value 300 does not fit in 8 bits.
Bit#(8) y = 300;T0051 오류 Literal 300 is not a valid Bit#(8).

3.5 예제: 정수 리터럴 출력하기

IntegerLiterals.bsv는 여러 리터럴을 변수에 담아 출력합니다. 출력 줄마다 왼쪽에 원래 리터럴 표기가 있으므로, 표기와 출력 값을 나란히 비교할 수 있습니다. 코드를 읽을 때는 다음에 주목하세요.

  • decimal과 octal은 서로 다른 진법으로 쓴 리터럴입니다.
  • hex와 hexLeadingUnderscore는 밑줄 위치만 다릅니다.
  • zeros와 ones는 Bit#(12) 문맥의 채움 리터럴입니다.

실행하기 전에 예측해 보세요.

  1. 8'd42와 6'o52의 출력은 같을까요?
  2. 16'h_BEEF를 10진수로 출력하면 얼마일까요?
  3. '1 in Bit#(12) 줄은 16진수로 몇 자리가 출력될까요?

확인 문제

'1 in Bit#(12) 줄은 어떻게 출력될까요?

답

IntegerLiterals.bsv
package IntegerLiterals;

(* synthesize *)
module mkTop(Empty);

  // 크기 지정 리터럴은 폭과 진법을 리터럴 안에 표기한다.
  Bit#(8)  decimal = 8'd42;
  // 숫자 사이의 밑줄은 값을 바꾸지 않는다.
  Bit#(8)  binary  = 8'b1010_1010;
  Bit#(16) hex     = 16'hBEEF;
  // 진법 표시 바로 뒤의 밑줄도 값에 영향을 주지 않는다.
  Bit#(16) hexLeadingUnderscore = 16'h_BEEF;
  Bit#(6)  octal   = 6'o52;

  // 크기 미지정 리터럴은 먼저 Integer이고 요구되는 타입으로 변환된다.
  Bit#(8) unsized = 125;

  // 채움 리터럴은 문맥이 요구하는 폭의 모든 비트를 채운다.
  Bit#(12) zeros = '0;
  Bit#(12) ones  = '1;

  rule show;
    $display("8'd42          = %0d", decimal);
    $display("8'b1010_1010   = %b", binary);
    $display("16'hBEEF       = %h", hex);
    $display("16'h_BEEF      = %0d", hexLeadingUnderscore);
    $display("6'o52          = %0d", octal);
    $display("unsized 125    = %0d", unsized);
    $display("'0 in Bit#(12) = %h", zeros);
    $display("'1 in Bit#(12) = %h", ones);
    $finish(0);
  endrule

endmodule

endpackage

이 개념 실습하기

다음 단계로 짧은 BSV 문제에 이 개념을 적용해 보세요.

결과 확인.

출력 줄확인할 내용
8'd42, 6'o52두 줄 모두 42입니다. 8진수 52는 5×8 + 2 = 42입니다
8'b1010_1010밑줄 없이 10101010이 출력됩니다
16'hBEEF, 16'h_BEEF같은 값입니다. 두 번째 줄은 10진수 48879로 출력됩니다
unsized 125크기 미지정 리터럴이 Bit#(8) 문맥에서 125가 되었습니다
'0, '1 줄12비트이므로 16진수 세 자리 000과 fff입니다

직접 바꿔 보세요.

바꿀 곳예상 결과
Bit#(12) ones를 Bit#(5) ones로같은 '1이 5비트를 채우므로 1f가 출력됩니다
8'd42를 8'd300으로값이 8비트를 넘으므로 T0132 컴파일 오류가 발생합니다

4. Real 리터럴

4.1 표기법

Real 리터럴은 소수점 뒤에 숫자가 있거나 지수가 있어야 합니다.

형식

<정수부>.<소수부>
<수>e<지수>    // e 대신 E, 지수 앞에 + 또는 - 가능
Real a = 1.2;      // 소수점 표기
Real b = 5e-3;     // 지수 표기, 0.005
Real c = 1_000.5;  // 밑줄은 숫자 사이에 쓸 수 있습니다

맨 앞의 +나 -는 리터럴이 아니라 단항 연산자입니다. 리터럴을 밑줄로 시작할 수는 없습니다.

4.2 binary64 반올림

Real 리터럴은 IEEE 754 binary64(64비트 부동소수점) 값으로 반올림됩니다. 10진수로는 짧게 쓰이는 값도 2진수로는 정확히 표현되지 않을 수 있습니다.

값2진수 표현binary64 인코딩
3.125정확합니다(2 + 1 + 1/8)4009000000000000
0.005근삿값입니다3f747ae147ae147b

리터럴의 기본 타입은 Real입니다. RealLiteral 타입클래스의 fromReal을 제공하는 타입, 예를 들어 라이브러리의 FixedPoint도 Real 리터럴을 받을 수 있습니다.

주의. Real 저장소나 일반 부동소수점 연산 회로는 합성되지 않습니다. 컴파일 단계에 값이 정해지는 Real 상수식만 비트 값으로 계산되어 들어갑니다.

4.3 예제: 두 가지 표기 비교하기

RealLiterals.bsv는 같은 값을 소수점 표기와 지수 표기로 하나씩 써서 비교합니다. $realtobits는 Real 값의 binary64 인코딩을 Bit#(64)로 반환합니다.

실행하기 전에 예측해 보세요.

  1. 3.125 == 3125e-3의 결과는 참일까요?
  2. 0.005는 2진수로 정확히 표현되지 않습니다. 그래도 0.005 == 5e-3이 참일까요?

확인 문제

0.005는 binary64로 정확히 표현되지 않습니다. 비교 0.005 == 5e-3은 무엇을 출력할까요?

답

RealLiterals.bsv
package RealLiterals;

(* synthesize *)
module mkTop(Empty);

  // 컴파일 단계의 Real 상수는 IEEE 64비트 인코딩으로 계산될 수 있다. 일반 부동소수점 저장소나 데이터 경로가 합성되는 것은 아니다.
  Real decimalA  = 3.125;
  Real exponentA = 3125e-3;
  Real decimalB  = 0.005;
  Real exponentB = 5e-3;

  rule show;
    $display("3.125 == 3125e-3: %0d; bits = %h (expected 4009000000000000)",
             decimalA == exponentA, $realtobits(decimalA));
    $display("0.005 == 5e-3 after IEEE rounding: %0d; bits = %h (expected 3f747ae147ae147b)",
             decimalB == exponentB, $realtobits(decimalB));
    $finish(0);
  endrule

endmodule

endpackage

결과 확인.

출력 줄확인할 내용
첫 줄비교 결과 1과 인코딩 4009000000000000. 정확히 표현되는 값입니다
둘째 줄비교 결과 1과 인코딩 3f747ae147ae147b. 두 표기가 같은 근삿값으로 반올림되었습니다

두 줄의 인코딩은 컴파일 단계에 확정됩니다. 이 모듈에 부동소수점 연산 회로는 생기지 않습니다.

직접 바꿔 보세요. 둘째 쌍을 0.5와 5e-1로 바꾸면 비교 결과는 그대로 1이고 인코딩은 3fe0000000000000이 됩니다. 0.5는 2의 거듭제곱이므로 반올림 없이 정확히 표현됩니다.

5. 문자열 리터럴

5.1 표기법과 이스케이프

문자열 리터럴은 큰따옴표로 감싸며, 시작한 소스 줄에서 끝나야 합니다. 큰따옴표나 줄바꿈처럼 그대로 쓸 수 없는 문자는 이스케이프로 씁니다.

형식

"문자들"
$display("plain");
$display("say \"hi\"\n");   // 큰따옴표와 줄바꿈 이스케이프
이스케이프문자
\n줄바꿈
\t수평 탭
\\역슬래시
\"큰따옴표
\v, \f, \a수직 탭, 폼 피드, 벨
\OOO정확히 세 자리 8진수, 000부터 377까지
\xHH정확히 두 자리 16진수, 00부터 FF까지

표에 없는 이스케이프는 오류입니다. 다른 언어에서 흔히 쓰는 \r도 BSV에서는 지원되지 않으며 P0091 오류를 발생시킵니다.

문자열 리터럴의 기본 타입은 String입니다. fromString을 제공하는 타입도 문자열 리터럴을 받을 수 있습니다. String은 출력 메시지와 컴파일 단계의 처리에 쓰며, 합성되는 바이트 배열이 아닙니다.

5.2 예제: 이스케이프 해석하기

StringLiterals.bsv는 이스케이프를 종류별로 출력합니다. 소스의 글자 수가 아니라 해석된 뒤의 문자 수를 세면서 읽으세요.

실행하기 전에 예측해 보세요.

  1. "\102\123\126"과 "\x42\x53\x56"은 각각 어떤 글자가 될까요? (\102는 10진수 66입니다.)
  2. "\1024"는 몇 글자가 될까요?

확인 문제

octal then digit 줄의 문자열은 글자 B를 나타내는 8진 이스케이프 뒤에 숫자 4가 이어집니다. 해석하면 몇 글자가 될까요?

답

StringLiterals.bsv
package StringLiterals;

(* synthesize *)
module mkTop(Empty);

  rule show;
    // 문자열 리터럴은 시작한 소스 줄에서 끝나야 한다.
    $display("plain            = %s", "BSV");
    // 이스케이프로 큰따옴표나 역슬래시를 문자열에 넣는다.
    $display("quoted           = \"BSV\" \\");
    // 탭과 줄바꿈은 문자 자체가 아니라 이스케이프로 표기한다.
    $display("tabbed           = a\tb");
    // 팔진 이스케이프는 세 자리, 십육진 이스케이프는 두 자리를 쓴다.
    $display("octal escape     = %s", "\102\123\126");
    // 팔진 이스케이프는 정확히 세 자리만 소비하므로 뒤의 4는 일반 문자다.
    $display("octal then digit = %s", "\1024");
    $display("hex escape       = %s", "\x42\x53\x56");
    $finish(0);
  endrule

endmodule

endpackage

결과 확인.

출력 줄확인할 내용
quoted\"와 \\가 큰따옴표와 역슬래시로 출력됩니다
tabbeda와 b 사이에 탭이 들어갑니다
octal escape, hex escape두 줄 모두 BSV입니다
octal then digitB4입니다. \102만 이스케이프이고 4는 일반 문자입니다

8진 이스케이프는 정확히 세 자리만 소비합니다. 그래서 이스케이프 뒤에 숫자가 이어져도 어디까지가 이스케이프인지 모호하지 않습니다.

직접 바꿔 보세요. "\1024"를 "\1025"로 바꾸면 이스케이프가 만드는 B는 그대로이고 마지막 문자만 5로 바뀝니다.

5.3 예제: 짧은 8진 이스케이프

다음 예제는 일부러 컴파일 오류를 발생시킵니다. 8진 이스케이프에 두 자리 \10만 썼습니다.

실행하기 전에 예측해 보세요. bsc는 \10을 값 8인 문자로 읽을까요, 아니면 거부할까요?

확인 문제

ShortOctalEscape.bsv의 문자열에는 두 자리뿐인 8진 이스케이프가 있습니다. 어떻게 될까요?

답

ShortOctalEscape.bsv
package ShortOctalEscape;

(* synthesize *)
module mkTop(Empty);

  rule show;
    $display("%s", "\10");
    $finish(0);
  endrule

endmodule

endpackage

결과 확인.

확인 항목내용
잘못된 코드8진 이스케이프 \10이 두 자리뿐입니다
실패 단계bsc의 어휘 분석 단계입니다
오류 메시지P0091과 Bad string escape char: `1'
고치는 방법앞에 0을 붙여 \010처럼 세 자리로 씁니다. 값 8인 문자가 됩니다

5.4 예제: 짧은 16진 이스케이프

다음 예제도 일부러 컴파일 오류를 발생시킵니다. \x 뒤에 16진 숫자를 하나만 썼습니다.

ShortHexEscape.bsv
package ShortHexEscape;

(* synthesize *)
module mkTop(Empty);
  rule show;
    $display("\x4");
  endrule
endmodule

endpackage

결과 확인.

확인 항목내용
잘못된 코드16진 이스케이프 \x4가 한 자리뿐입니다
실패 단계bsc의 어휘 분석 단계입니다
오류 메시지P0091과 Bad string escape char: `x'
고치는 방법\x04처럼 두 자리로 씁니다

16진 숫자의 대소문자는 의미가 없으므로 \x4a와 \x4A는 같은 문자입니다.

6. 컴파일러 지시어

컴파일러 지시어는 백틱(`)으로 시작하며, 일반 구문 분석보다 먼저 소스 텍스트를 바꿉니다. 줄 첫 칸에서 시작하거나 한 줄을 혼자 차지할 필요는 없지만, 문자열과 주석 안에서는 인식되지 않습니다.

6.1 파일 포함과 소스 위치

지시어동작
`include "file"파일 내용을 현재 위치에 삽입합니다
`include <file>따옴표 형식과 같은 방식으로 삽입합니다
`line number "file" level이후 진단에 쓸 파일 이름과 줄 번호를 지정합니다

`include의 경로는 절대 경로나 현재 디렉터리 기준 상대 경로이며, 포함된 파일에도 지시어를 쓸 수 있습니다. `line의 level은 포함 파일 진입이면 1, 복귀이면 2, 그 밖이면 0입니다. `line은 주로 외부 전처리기가 원본 위치를 진단에 남길 때 씁니다.

6.2 매크로 정의와 해제

형식

`define 이름 치환_텍스트
`define 이름(인자1, 인자2) 치환_텍스트
`undef 이름
`define WIDTH 8
`define DOUBLE(v) ((v) + (v))

Bit#(`WIDTH) x = `DOUBLE(8'd6);   // Bit#(8) x = ((8'd6) + (8'd6));
요소의미
`define NAME text줄 끝까지의 텍스트를 NAME으로 저장합니다. 줄 끝의 역슬래시로 다음 줄까지 이어 쓸 수 있습니다
`NAME저장된 텍스트로 그대로 치환됩니다
형식 인자이름 바로 뒤 괄호에 씁니다. 호출할 때 실제 인자 텍스트로 바뀝니다
`undef NAME이후 소스에서 NAME 정의를 해제합니다
`resetall현재 정의된 매크로를 모두 해제합니다

매크로는 타입을 모르는 텍스트 치환입니다. 형식 인자를 본문에 두 번 쓰면 실제 인자 텍스트도 두 번 들어가고, 본문에서 쓴 이름이 호출 위치의 이름과 충돌할 수도 있습니다. 식 형태의 매크로는 전체 치환 텍스트와 각 형식 인자를 괄호로 감싸세요. 산술식 재사용에는 타입과 이름 범위가 있는 함수가 더 안전합니다.

참고. 매크로 본문의 `"는 큰따옴표, `\`"는 역슬래시와 큰따옴표를 만들고, ``는 양쪽 텍스트를 공백 없이 이어 붙입니다. `bluespec와 `BLUESPEC는 미리 정의되어 있습니다.

주의. BSV Language Reference Guide는 문자열, 주석, 괄호 안의 쉼표가 실제 인자를 나누지 않는다고 설명합니다. 그러나 bsc 2026.01에서는 문자열과 주석 안의 쉼표가 인자를 나누어 P0146 오류가 발생했고, 괄호 안의 쉼표만 한 인자 안에 남았습니다. 쉼표가 든 실제 인자는 괄호로 묶고, 사용하는 bsc 버전에서 동작을 확인하세요.

6.3 조건부 컴파일

형식

`ifdef 이름
  // 이름이 정의되어 있을 때 남는 코드
`elsif 다른_이름
  // 다른_이름이 정의되어 있을 때 남는 코드
`else
  // 어느 것도 정의되지 않았을 때 남는 코드
`endif

`ifndef는 이름이 정의되지 않았을 때 첫 분기를 남깁니다. `elsif는 여러 개, `else는 최대 하나 둘 수 있고, 그룹을 중첩할 수도 있습니다. 조건이 처음 맞는 분기만 구문 분석으로 넘어갑니다.

주의. 지시어는 컴파일 전에 텍스트를 고르는 도구입니다. 레지스터 값을 읽거나 사이클마다 분기를 바꾸는 실행 시점 제어문이 아닙니다.

6.4 예제: 매크로 치환과 조건부 컴파일

Directives.bsv는 형식 인자가 있는 매크로 DOUBLE과 기능 선택용 매크로 USE_FEATURE를 정의합니다. selected는 `ifdef 분기에 따라 두 가지 값 중 하나로 선언됩니다.

실행하기 전에 예측해 보세요.

  1. `DOUBLE(8'd6)은 어떤 텍스트로 치환될까요? 그 값은 얼마일까요?
  2. selected는 a5와 5a 중 어느 값일까요?

확인 문제

USE_FEATURE를 정의하는 줄이 있을 때 selected는 어떤 값이 될까요?

답

Directives.bsv
package Directives;

// 형식 인자가 있는 매크로다. 호출하면 실제 텍스트로 치환된다.
`define DOUBLE(value) ((value) + (value))
`define USE_FEATURE

(* synthesize *)
module mkTop(Empty);

  Bit#(8) doubled = `DOUBLE(8'd6);

  // 조건이 참인 분기만 컴파일러에 전달된다.
`ifdef USE_FEATURE
  Bit#(8) selected = 8'hA5;
`else
  Bit#(8) selected = 8'h5A;
`endif

  rule show;
    $display("DOUBLE(8'd6) = %0d", doubled);
    $display("selected     = %h", selected);
    $finish(0);
  endrule

endmodule

endpackage

결과 확인.

출력 줄확인할 내용
DOUBLE(8'd6) = 12((8'd6) + (8'd6))으로 치환되었습니다. 인자 텍스트가 두 번 들어갔습니다
selected = a5USE_FEATURE가 정의되어 있으므로 `ifdef 첫 분기만 남았습니다

직접 바꿔 보세요.

바꿀 곳예상 결과
`define USE_FEATURE 줄 삭제`else 분기가 남아 selected가 5a가 됩니다
DOUBLE을 function Bit#(8) double(Bit#(8) v) = v + v;로 바꾸고 호출 수정출력은 12로 같습니다. 이제 인자 타입이 검사되고 이름 범위가 생깁니다

7. 자주 하는 실수

7.1 대문자 변수 이름과 키워드 이름

// 잘못된 코드
Bit#(4) Count = 4'd1;   // 대문자로 시작하는 변수
Bit#(4) rule  = 4'd1;   // 키워드

// 바른 코드
Bit#(4) count     = 4'd1;
Bit#(4) ruleValue = 4'd1;

두 줄 모두 P0005 구문 오류입니다. 변수 이름은 소문자로 시작하고 키워드와 겹치지 않아야 합니다.

7.2 블록 주석 중첩

// 잘못된 코드: 첫 번째 */가 주석을 닫습니다
/* 바깥 /* 안쪽 */ 아직 바깥 */

// 바른 코드: 줄 주석으로 감싸거나 주석을 하나로 씁니다
// 바깥 /* 안쪽 */ 아직 바깥

블록 주석은 중첩되지 않으므로 남은 텍스트가 코드로 읽혀 P0005 오류가 발생합니다. 코드 덩어리를 잠시 끄려면 `ifdef로 감싸는 방법도 있습니다.

7.3 폭보다 큰 리터럴

// 잘못된 코드
Bit#(8) x = 8'd300;    // T0132

// 바른 코드: 폭을 넓히거나 값을 고칩니다
Bit#(9) x = 9'd300;

BSV는 리터럴의 상위 비트를 조용히 버리지 않습니다. 오류 메시지에 나온 값과 폭을 비교해 어느 쪽을 고칠지 정하세요.

7.4 괄호 없는 매크로

// 잘못된 코드: `TWICE(8'd3) * 2 가 8'd3 + 8'd3 * 2 로 치환되어 9
`define TWICE(v) v + v

// 바른 코드: 같은 식이 ((8'd3) + (8'd3)) * 2 로 치환되어 12
`define TWICE(v) ((v) + (v))

매크로는 연산자 우선순위를 모르는 텍스트 치환입니다. 치환 결과를 괄호로 감싸지 않으면 주변 연산자와 엉뚱하게 결합합니다.

8. 정리

어휘 요소핵심
식별자영문자, 숫자, _, $로 이루어지며 숫자로 시작하지 않습니다. 타입은 대문자, 값은 소문자로 시작합니다
키워드어떤 문맥에서도 식별자로 쓸 수 없습니다
주석공백처럼 토큰을 나누며 블록 주석은 중첩되지 않습니다
정수 리터럴<폭>'<진법><숫자>로 쓰고, 크기 미지정 리터럴과 '0, '1은 문맥이 폭을 정합니다. 값이 폭을 넘으면 컴파일 오류입니다
Real 리터럴binary64로 반올림되며 컴파일 단계 상수로만 하드웨어에 들어갑니다
문자열한 줄 안에서 끝나고, 8진 이스케이프는 세 자리, 16진 이스케이프는 두 자리입니다
?don't-care value이며 값이 관찰되지 않는 위치에만 씁니다. 선택·case·don't-care 값 장에서 다룹니다
지시어구문 분석 전의 텍스트 치환입니다. 식 매크로는 괄호로 감쌉니다

9. 확인 문제

  1. val/* gap */ue가 식별자 value가 되지 않는 이유는 무엇일까요?

    정답 보기

    주석은 공백처럼 취급되어 토큰을 나눕니다. 그래서 val과 ue 두 식별자가 남고, 선언에서 두 번째 식별자가 올 수 없는 위치이므로 P0005 오류가 발생합니다.

  2. 16'o42와 16'd42는 같은 값일까요?

    정답 보기

    다릅니다. 16'o42는 8진수이므로 4×8 + 2 = 34이고, 16'd42는 42입니다. 폭은 둘 다 16비트입니다.

  3. Int#(4) b = -8;은 컴파일되는데 Int#(4) c = 8;은 오류입니다. 이유는 무엇일까요?

    정답 보기

    Int#(4)의 범위는 -8부터 7까지입니다. 크기 미지정 리터럴에서는 부호가 리터럴의 일부이므로 -8은 범위 안의 값이고, 8은 범위를 벗어나 T0051 오류가 발생합니다.

  4. "\1024"가 B4로 출력되는 과정을 설명해 보세요.

    정답 보기

    8진 이스케이프는 정확히 세 자리를 소비하므로 \102가 10진수 66, 즉 B가 됩니다. 남은 4는 일반 문자입니다.

  5. `define TWICE(v) v + v로 정의한 매크로를 `TWICE(8'd3) * 2처럼 쓰면 결과가 9입니다. 12가 되게 하려면 어떻게 고쳐야 할까요?

    정답 보기

    치환 텍스트 전체와 각 형식 인자를 괄호로 감싸 `define TWICE(v) ((v) + (v))로 정의합니다. 그러면 ((8'd3) + (8'd3)) * 2로 치환되어 12가 됩니다. 타입 검사가 필요하면 함수로 바꾸는 편이 더 안전합니다.

이 장의 실습 문제는 정수 리터럴 예제를 이어 갑니다. 첫 문제에서는 네 개의 16비트 상수가 모두 틀린 값을 반환합니다. 진법, 자릿수, 채움 리터럴 가운데 무엇이 잘못되었는지 출력으로 진단해 보세요. 두 번째 문제에서는 하위 4비트만 남겨야 하는 마스크가 입력을 그대로 통과시킵니다.

확인 지점

예제를 실행해 예측과 비교합니다

참고 자료

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