본문으로 건너뛰기
code0.HDL

BSV 설계에 RTL 포함하기

타입이 있는 BSV와 외부 RTL을 잇는 import "BVI" 계약, enable·ready를 포함한 value method와 Action method 대응, default_clock과 default_reset, 모든 method 쌍의 schedule 관계, 정적 파라미터, 그리고 실패가 BSC 컴파일·Verilog elaboration·시뮬레이션 가운데 어디서 생겼는지 찾는 방법을 다룹니다.

이 장에서 배울 내용

  • value method나 Action method를 RTL 포트에 대응시키고, enable이나 ready 절을 생략하면 RTL에 대해 무엇을 약속하는지 말할 수 있다
  • 래퍼의 모든 method 쌍에 CF·SB·SBR·C를 선언하고, 사실과 다른 관계가 bsc에 무엇을 생성하게 하는지 예측할 수 있다
  • 정적인 BSV 인자를 RTL 파라미터로 전달하고, 레지스터 값이 오류 G0053을 내는 까닭을 설명할 수 있다
  • 대응이나 스케줄을 고치기 전에 래퍼의 실패를 그것을 보고한 단계로 분류할 수 있다
이 장에서 배우는 개념
  • BSV 설계에 RTL 포함하기
먼저 알아야 할 개념

import "BVI"는 벤더 기본 소자나 이미 검증된 블록 같은 기존 Verilog·VHDL 모듈을 타입이 있는 BSV 코드에서 인스턴스화하는 문법입니다. BVI 선언은 외부 모듈을 구현하지 않습니다. BSV의 값, method 호출, 클록, 리셋, 스케줄 관계가 외부 RTL의 어느 포트에 대응하는지 적은 계약입니다. bsc는 이 계약을 그대로 사용하지만 RTL 내부를 열어 계약이 맞는지 확인하지는 못합니다.

인터페이스, value method와 Action method, 어트리뷰트로 컴파일러 제어하기 장의 Ready와 Enable 신호, rule, 그리고 정적·동적 의미론 장의 같은 사이클 스케줄링 관계를 알고 있어야 합니다. 앞 장의 질문, 곧 컴파일러가 증명하는 사실과 그대로 믿는 사실을 구분하는 질문이 이 장에서도 이어집니다.

예제마다 BSV 파일과 Verilog 파일이 하나씩 있습니다. 실행을 누르면 bsc가 BSV를 컴파일하고, 생성된 Verilog를 외부 RTL과 연결한 뒤 Icarus Verilog로 시뮬레이션합니다.

1. BVI 선언

1.1 세 단계로 나뉘는 경계

BVI 래퍼는 타입이 있는 BSV 쪽과 타입이 없는 RTL 쪽을 잇는 선언형 어댑터입니다.

BSV caller -> typed interface -> BVI contract -> generated port wiring -> RTL

사실마다 확인되는 시점이 다르고, 어떤 사실은 끝까지 확인되지 않습니다.

단계확인되는 내용래퍼 작성자가 보장할 내용
BSC 컴파일과 elaborationmethod 타입과 폭, 정적 파라미터, 스케줄 선언의 일관성대응시킨 포트와 프로토콜이 실제 RTL과 같다는 것
Verilog elaboration모듈 이름과 포트 이름이 존재하는지모듈 이름, 포트 이름, 폭이 정확하다는 것
시뮬레이션과 하드웨어RTL 자체 외에는 없음RTL이 선언한 ready, enable, 클록, 리셋, 스케줄을 실제로 지킨다는 것

따라서 준비 상태, 리셋 극성, 허용되는 동시 실행을 사실과 다르게 선언한 래퍼도 컴파일과 연결을 통과할 수 있습니다.

1.2 선언의 형태

형식

import "BVI" RTL_모듈_이름 =
module mk래퍼_이름#(인자)(인터페이스_타입);
  // 필요하면 일반 모듈 문장
  // parameter, 클록, 리셋, port, method, schedule, path 문장
endmodule
interface AddIfc;
  method Bit#(8) add(Bit#(8) a, Bit#(8) b);
endinterface

import "BVI" ExternalAdder =
module mkExternalAdder(AddIfc);
  default_clock no_clock;
  default_reset no_reset;
  method SUM add(A, B);
  schedule (add) CF (add);
endmodule
부분의미
import "BVI" ExternalAdder =인스턴스화할 RTL 모듈입니다. 생략하면 BSV 모듈 이름을 씁니다
module mkExternalAdder(AddIfc)호출하는 쪽이 쓰는 일반 BSV 모듈 이름과 인터페이스입니다
default_clock, default_resetRTL이 쓰는 클록과 리셋입니다
method SUM add(A, B);method의 인자와 결과를 전달할 RTL 포트입니다
schedule (add) CF (add);한 사이클 안에서 method 호출을 어떻게 함께 쓸 수 있는지입니다

래퍼의 인자와 인터페이스 값에는 Bits 인스턴스가 있거나, Clock, Reset, Inout처럼 경계를 넘을 수 있는 타입이어야 합니다.

2. value method

2.1 value method 대응

value method는 결과 포트를 method 이름 앞에 쓰고, 인자 포트를 인자 순서대로 괄호 안에 씁니다.

형식

method 결과_포트 method_이름(인자_포트1, 인자_포트2, ...);
// BSV 인터페이스: method Bit#(8) add(Bit#(8) a, Bit#(8) b);
method SUM add(A, B);
BSV에서 일어나는 일BVI 대응RTL에서 일어나는 일
rule이 add(19, 23)을 평가합니다A = 19, B = 238비트 입력 포트 두 개가 구동됩니다
rule이 결과를 사용합니다SUM이 결과 포트입니다RTL이 8비트 결과를 구동합니다

세 포트의 폭은 인터페이스에 적은 method 타입에서 정해집니다.

2.2 클록이 없는 블록

순수한 조합 블록에는 클록도 리셋도 없습니다. 이 사실을 두 선언으로 명시합니다.

형식

default_clock no_clock;
default_reset no_reset;

ready 절이 없는 method 대응은 그 method가 항상 준비되어 있다고 약속합니다. 매 사이클 합을 계산할 수 있는 가산기에는 이 약속이 사실입니다.

2.3 예제: 외부 가산기

BVIAdder.bsv는 Verilog 모듈 ExternalAdder를 감쌉니다. AddIfc와 BVI 대응을 먼저 읽고, A, B, SUM을 ExternalAdder.v의 포트 목록과 비교하세요. show rule은 add를 한 번 호출하고 결과를 출력합니다.

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

  1. add(8'd19, 8'd23)의 10진수 합은 무엇일까요?
  2. 결과를 얻는 데 클록 에지가 필요할까요, 같은 사이클에 바로 쓸 수 있을까요?
  3. BSV 쪽에서 A, B, SUM의 8비트 폭은 어디에서 정해질까요?

확인 문제

show rule은 adder.add(8'd19, 8'd23)을 호출하고 같은 rule에서 결과를 출력합니다. 무엇이 출력되며, 결과를 얻는 데 클록 에지가 필요합니까?

답

BVIAdder.bsv
package BVIAdder;

interface AddIfc;
  method Bit#(8) add(Bit#(8) a, Bit#(8) b);
endinterface

import "BVI" ExternalAdder =
module mkExternalAdder(AddIfc);
  default_clock no_clock;
  default_reset no_reset;
  // BSV method의 인자와 결과를 조합 RTL 포트에 대응시킨다.
  method SUM add(A, B);
  schedule (add) CF (add);
endmodule

(* synthesize *)
module mkTop(Empty);
  AddIfc adder <- mkExternalAdder;

  rule show;
    $display("sum = %0d", adder.add(8'd19, 8'd23));
    $finish(0);
  endrule
endmodule

endpackage
ExternalAdder.v
module ExternalAdder (
  input  wire [7:0] A,
  input  wire [7:0] B,
  output wire [7:0] SUM
);
  assign SUM = A + B;
endmodule

결과 확인.

관찰 내용확인할 내용
sum = 42인자가 A와 B에 전달되었고, SUM이 method 결과로 반환되었습니다
한 rule이 호출과 출력을 모두 수행RTL을 지나는 경로가 조합 경로이며 상태가 필요 없습니다

패널에 -vsearch 디렉터리 중복에 관한 경고 S0073이 보일 수 있습니다. 실행 명령에서 생기는 경고이며 결과에는 영향이 없습니다.

직접 바꿔 보세요.

  1. 인자를 8'd200과 8'd100으로 바꾸세요. 출력은 300이 아니라 44입니다. RTL은 8비트 값 두 개를 더해 8비트 SUM에 담고, method 타입도 정확히 그 폭을 약속했습니다. 넘친 비트는 외부 논리에서 사라지며 bsc가 문제 삼을 것이 없습니다.
  2. 인자를 되돌리고 대응을 method S add(A, B);로 바꾸세요. bsc는 RTL 헤더를 읽지 않으므로 래퍼를 그대로 컴파일합니다. 오류는 다음 단계인 Verilog elaboration에서 S가 adder 인스턴스의 포트가 아니라는 메시지로 나타납니다. 포트 이름 불일치는 BSV 타입 오류가 아니라 연결 단계의 오류입니다.

3. Action method와 클록이 있는 RTL

3.1 Action method 대응

Action이나 ActionValue method는 데이터와 함께 호출 프로토콜도 대응시킵니다.

형식

method method_이름(인자_포트, ...) enable(Enable_포트) ready(Ready_포트);
// BSV 인터페이스: method Action write(Bit#(8) value);
method write(D_IN) enable(EN) ready(RDY);
절의미
(D_IN)인자가 D_IN을 구동합니다
enable(EN)method를 호출하는 사이클마다 EN이 활성화됩니다
ready(RDY)RDY가 method의 implicit condition이 됩니다. RDY가 1인 동안에만 호출하는 rule이 실행됩니다
생략한 절생략이 약속하는 내용
readymethod가 항상 준비되어 있습니다
Action method의 enableRTL이 호출마다 펄스를 요구하지 않습니다

주의. 절을 생략해도 빠진 프로토콜 논리가 생기지 않습니다. 생략 자체가 외부 RTL에 대한 약속입니다.

3.2 클록과 리셋

형식

default_clock 클록_이름(클록_포트);
default_reset 리셋_이름(리셋_포트);
default_clock clk(CLK);
default_reset rst(RST_N);

두 문장은 래퍼의 기본 클록을 RTL 포트 CLK에, 기본 리셋을 RST_N에 연결합니다. BVI 리셋 입력은 활성 로우로 가정하므로 흔히 쓰는 RST_N 관례와 맞습니다.

확인 문제

ExternalRegister를 고쳐 RST 포트가 하이일 때 리셋되게(if (RST) Q_OUT <= 0) 만들었습니다. 래퍼는 default_reset rst(RST);로 적고 테스트 rule은 그대로 둡니다. 어떻게 될까요?

답

3.3 예제: 외부 레지스터에 쓰고 읽기

ClockedRegister.bsv는 Enable이 있는 8비트 레지스터 ExternalRegister를 감쌉니다. write 대응에는 ready가 없으므로 이 레지스터는 언제나 쓸 수 있다고 약속한 셈입니다. 테스트 rule보다 BVI 선언을 먼저 읽고 다음 대응을 확인하세요.

BSV 선언 또는 호출RTL 포트 동작
default_clock clk(CLK)래퍼의 클록이 CLK를 구동합니다
default_reset rst(RST_N)BSV 리셋이 활성 로우 RST_N에 나타납니다
imported.write(8'h5a)D_IN이 값을 전달하고 EN이 호출을 표시합니다
imported.readQ_OUT이 결과를 제공합니다

사이클별 동작은 이렇게 진행됩니다.

  1. 리셋이 Q_OUT과 BSV step 레지스터를 0으로 만듭니다.
  2. step == 0인 사이클에 writeValue가 write를 호출합니다. D_IN에 5a가 실리고 EN이 1이므로 외부 레지스터는 클록 에지에서 5a를 저장합니다. step도 같은 에지에 1이 됩니다.
  3. 다음 사이클에는 showValue만 실행 가능합니다. read가 Q_OUT의 값을 반환하고 시뮬레이션이 끝납니다.

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

  1. showValue는 어떤 값을 출력할까요?
  2. 그 값이 리셋 값 00이 아닌 까닭은 무엇일까요?
  3. write의 호출 프로토콜을 전달하는 RTL 포트는 무엇일까요?

확인 문제

writeValue는 step == 0일 때 imported.write(8'h5a)를 호출하고, showValue는 step == 1일 때 imported.read를 읽습니다. showValue는 무엇을 출력합니까?

답

ClockedRegister.bsv
package ClockedRegister;

interface RegisterIfc;
  method Action write(Bit#(8) value);
  method Bit#(8) read;
endinterface

import "BVI" ExternalRegister =
module mkExternalRegister(RegisterIfc);
  // 가져온 순차 논리를 래퍼의 클록과 리셋에 연결한다.
  default_clock clk(CLK);
  default_reset rst(RST_N);
  method write(D_IN) enable(EN);
  method Q_OUT read;
  schedule (read) CF (read);
  schedule (read) SB (write);
  schedule (write) C (write);
endmodule

(* synthesize *)
module mkTop(Empty);
  RegisterIfc imported <- mkExternalRegister;
  Reg#(Bit#(2)) step <- mkReg(0);

  rule writeValue (step == 0);
    imported.write(8'h5a);
    step <= 1;
  endrule

  rule showValue (step == 1);
    $display("stored = %h", imported.read);
    $finish(0);
  endrule
endmodule

endpackage
ExternalRegister.v
module ExternalRegister (
  input  wire       CLK,
  input  wire       RST_N,
  input  wire [7:0] D_IN,
  input  wire       EN,
  output reg  [7:0] Q_OUT
);
  always @(posedge CLK) begin
    if (!RST_N)
      Q_OUT <= 8'h00;
    else if (EN)
      Q_OUT <= D_IN;
  end
endmodule

이 개념 실습하기

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

결과 확인.

관찰 내용확인할 내용
stored = 5aD_IN과 EN이 RTL에 전달되었고, 값이 에지에서 저장되었습니다
값이 00이 아님읽기가 쓰기의 클록 에지보다 한 사이클 뒤에 일어났습니다

직접 바꿔 보세요.

  1. showValue의 조건을 step == 0으로 바꾸어 읽기와 쓰기를 같은 사이클에 요청하세요. 스케줄이 read SB write이므로 bsc는 두 rule을 한 사이클에 실행하되 읽기를 논리적으로 먼저 둡니다. 읽기는 에지 이전의 Q_OUT을 보므로 00이 출력됩니다. 에지에서만 값이 바뀌는 RTL과 맞는 결과입니다. 래퍼가 write SB read라고 선언했다면 bsc는 읽기가 새 값을 본다고 가정했을 것이고, 그 선언은 하드웨어를 잘못 기술하게 됩니다.

처음 보는 모듈 대응시켜 보기. ExtBuffer는 항목 하나짜리 버퍼입니다. ENQ_RDY가 하이인 동안 데이터를 받고, DEQ_RDY가 하이인 동안 FIRST로 값을 보여 주며, DEQ_EN 펄스를 받으면 비웁니다.

module ExtBuffer (
  input  wire       CLK,
  input  wire       RST_N,
  input  wire [7:0] ENQ_DATA,
  input  wire       ENQ_EN,
  output wire       ENQ_RDY,   // 버퍼가 비어 있는 동안 하이
  input  wire       DEQ_EN,
  output wire       DEQ_RDY,   // 버퍼가 차 있는 동안 하이
  output wire [7:0] FIRST
);
interface BufIfc;
  method Action enq(Bit#(8) x);
  method Action deq;
  method Bit#(8) first;
endinterface

확인 문제

default_clock clk(CLK);와 default_reset rst(RST_N);을 쓴다고 할 때 ExtBuffer에 맞는 method 대응은 어느 것일까요?

답

확인 문제

바른 대응에서 enq의 ready(ENQ_RDY)만 빼 보세요. produce rule은 매 사이클 enq를 호출하고 consume rule은 first와 deq를 호출하며, 스케줄은 enq가 first, deq와 충돌(C)한다고 적혀 있습니다. 무엇이 보일까요?

답

4. schedule 선언

4.1 method 쌍의 관계 선언하기

bsc는 외부 method 사이의 관계를 추론할 수 없습니다. schedule 문은 왼쪽 목록의 모든 method와 오른쪽 목록의 모든 method 사이에 관계를 선언합니다. 관계의 종류는 정적·동적 의미론 장에서 라이브러리 기본 모듈이 선언하는 것과 같습니다.

형식

schedule (method1, method2, ...) 연산자 (method3, ...);
schedule (read) CF (read);
schedule (read) SB (write);
schedule (write) C (write);
연산자의미
CF충돌하지 않습니다. 순서와 관계없이 함께 호출할 수 있습니다
SB왼쪽이 오른쪽보다 앞섭니다. 한 rule 안에서 함께 쓸 수 있습니다
SBR왼쪽이 앞서지만 한 rule 안에서는 함께 쓸 수 없습니다
C충돌하므로 같은 사이클에 호출할 수 없습니다

4.2 레지스터의 스케줄 읽기

호출 조합허용 여부까닭
읽기 + 읽기허용, CF둘 다 Q_OUT을 관찰할 뿐입니다
읽기 + 쓰기읽기를 앞에 두고 허용, SB읽기는 이전 값을 보고 쓰기는 에지에서 반영됩니다
쓰기 + 쓰기불허, CD_IN과 EN이 하나뿐이고 중재 논리가 없습니다

주의. schedule은 최적화 힌트가 아니라 약속입니다. 중재되지 않은 자원을 공유하는 연산을 CF로 선언하면 bsc가 잘못된 동시 실행을 생성할 수 있습니다. 실제로 독립적인 연산을 C로 선언하면 동작은 옳지만 쓸모 있는 병렬 실행을 막습니다.

method 쌍에 관계가 하나도 없으면 bsc는 경고 P0200("No scheduling annotation given")을 내고 충돌로 가정합니다. 같은 쌍에 서로 다른 연산자를 두 번 선언하면 오류입니다. method가 자기 자신과 이루는 쌍까지 포함해 모든 쌍을 점검하세요.

확인 문제

ExtBuffer 래퍼가 schedule (first) CF (first); (first) SB (deq); (first, deq) C (enq); (enq) C (enq);만 선언합니다. bsc는 무엇을 보고할까요?

답

5. 정적 파라미터

5.1 파라미터 전달하기

BSV 생성자의 인자와 RTL 파라미터는 parameter 문이 연결하기 전까지 아무 관계가 없습니다.

형식

parameter RTL_파라미터 = 정적_식;
import "BVI" ExternalLimiter =
module mkExternalLimiter#(Integer limit)(LimiterIfc);
  parameter LIMIT = limit;
  ...
endmodule

오른쪽에는 컴파일 시점에 정해지는 String, Integer, Bit#(n) 상수만 올 수 있습니다. numeric type은 valueOf로 값으로 바꾸어 쓸 수 있습니다. 레지스터의 내용은 파라미터가 될 수 없으며, 레지스터 값을 전달하면 파라미터가 동적인 식으로 인스턴스화되었다는 오류 G0053이 발생합니다.

전달 수단정해지는 시점실행 중에 바꿀 수 있는가
parameterelaboration없습니다. 값이 다르면 다른 하드웨어입니다
method 인자호출할 때마다있습니다

5.2 예제: 상한이 고정된 리미터

ParameterizedLimiter.bsv는 BSV 인자 limit를 Verilog 파라미터 LIMIT로 전달합니다. mkTop의 생성자 호출, parameter 문, ExternalLimiter.v의 비교식 순서로 읽으세요.

정적인 흐름은 이렇게 이어집니다. BSV가 mkExternalLimiter(100)을 elaboration하면, 생성된 Verilog가 LIMIT = 100인 ExternalLimiter를 인스턴스화합니다. 실행 중에는 clamp가 이 고정된 인스턴스에 140을 전달합니다.

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

  1. LIMIT가 100일 때 clamp(8'd140)은 무엇을 반환할까요?
  2. 인스턴스가 만들어진 뒤에 rule이 LIMIT를 바꿀 수 있을까요?

확인 문제

mkTop은 mkExternalLimiter(100)을 인스턴스화하고 limiter.clamp(8'd140)을 호출합니다. 무엇이 출력됩니까?

답

ParameterizedLimiter.bsv
package ParameterizedLimiter;

interface LimiterIfc;
  method Bit#(8) clamp(Bit#(8) value);
endinterface

import "BVI" ExternalLimiter =
module mkExternalLimiter#(Integer limit)(LimiterIfc);
  // 컴파일 시점에 정해지는 BSV 인자를 RTL 파라미터로 전달한다.
  parameter LIMIT = limit;
  default_clock no_clock;
  default_reset no_reset;
  method OUT clamp(IN);
  schedule (clamp) CF (clamp);
endmodule

(* synthesize *)
module mkTop(Empty);
  LimiterIfc limiter <- mkExternalLimiter(100);

  rule show;
    $display("clamped = %0d", limiter.clamp(8'd140));
    $finish(0);
  endrule
endmodule

endpackage
ExternalLimiter.v
module ExternalLimiter #(
  parameter integer LIMIT = 255
) (
  input  wire [7:0] IN,
  output wire [7:0] OUT
);
  assign OUT = (IN > LIMIT) ? LIMIT[7:0] : IN;
endmodule

이 개념 실습하기

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

결과 확인.

관찰 내용확인할 내용
clamped = 100140이 상한을 넘었으므로 RTL이 LIMIT를 반환했습니다
상한이 mkExternalLimiter(100)에서 옴BSV 인자가 elaboration 중에 RTL 파라미터로 전달되었습니다

직접 바꿔 보세요.

  1. 생성자 인자를 150으로 바꾸세요. 생성된 인스턴스의 LIMIT가 150이 되어 140이 그대로 통과하므로 140이 출력됩니다. method 호출은 그대로이고, elaboration이 다른 하드웨어를 만들었습니다.

설계가 동작하는 중에 상한을 바꿔야 한다면 파라미터는 맞지 않는 수단입니다. 이때는 setLimit 같은 method가 필요하고, RTL에도 상한을 저장할 공간과 쓰기 프로토콜이 있어야 합니다.

6. 그 밖의 경계 문장

6.1 독립 포트, 클록, 리셋

port MODE = 2'b01;
input_clock fastClk(FAST_CLK) = clk2;
input_reset fastRst(FAST_RST_N) clocked_by(fastClk) = rst2;
문장용도
port 이름 = 식;모드 신호처럼 어느 method에도 속하지 않는 RTL 입력을 구동합니다
input_clockBSV Clock 인자를 RTL 클록 포트에 연결합니다
output_clockRTL이 생성한 클록을 내보냅니다
input_reset, output_reset리셋에 대해 같은 일을 합니다. BVI 리셋은 활성 로우로 가정합니다
ancestor, same_family클록 사이의 파생 관계나 위상 관계를 선언합니다
no_clock, no_reset기본 도메인을 명시적으로 없앱니다

기본 도메인에 속하지 않는 method나 포트에는 clocked_by와 reset_by를 붙이세요.

주의. 클록 관계 선언은 bsc가 클록 도메인 사이의 연결을 검사하게 할 뿐 동기화 회로를 만들지 않습니다.

6.2 조합 경로, 하위 인터페이스, inout

형식

path (입력_포트, 출력_포트);

path 문은 입력 포트가 같은 사이클에 출력 포트에 영향을 준다고 알립니다. bsc는 이 정보로 모듈 경계를 넘는 조합 루프를 검출합니다. 실제 경로를 빠뜨리면 루프가 숨고, 없는 경로를 적으면 올바른 설계가 거부됩니다.

중첩된 interface 문은 대응시킨 포트들을 BSV 하위 인터페이스로 묶습니다. inout은 RTL의 양방향 포트를 Inout#(t)로 가져오고, ifc_inout은 기존 BSV Inout 인자를 연결합니다. 이런 배선은 경계를 그대로 통과하므로 폭과 도메인을 명시하세요.

7. 래퍼를 고치기 전에 실패 단계 찾기

스케줄이나 배선을 고치기 전에 실패한 단계부터 분류하세요.

단계보이는 증상가능한 원인먼저 확인할 곳
BSC 컴파일타입, 정적 값, 스케줄에 관한 오류폭 불일치, 동적 파라미터(G0053), 중복 관계method 타입과 BVI 문장
Verilog elaboration모듈을 찾을 수 없다거나 포트가 아니라는 오류RTL 파일 누락, 모듈·포트 이름 오류생성된 인스턴스와 RTL 헤더
시뮬레이션이 진행하지 않음rule이 실행되지 않음잘못되었거나 계속 0인 ready, 잘못된 리셋 연결Ready와 리셋 파형
시뮬레이션 데이터 오류호출은 일어나지만 값이나 순서가 다름포트 뒤바뀜, 거짓 CF·SB, 폭 손실, 리셋 극성호출 하나를 method부터 포트까지 추적
하드웨어에서만 발생클록 도메인 사이에서 간헐적으로 값이 깨짐틀린 클록 관계 선언, 동기화 회로 누락실제 클록 도메인과 동기화 설계

포트 이름 오류는 스케줄을 완화해도 고쳐지지 않습니다. 사실과 다른 always-ready 약속은 컴파일과 연결을 깨끗하게 통과한 뒤 실제 프로토콜을 깨뜨릴 수 있습니다.

확인 문제

BSC 컴파일과 Verilog 엘라보레이션은 모두 깨끗한데, start를 호출하는 rule이 시뮬레이션에서 한 번도 실행되지 않습니다. 가장 먼저 무엇을 확인해야 할까요?

답

BVI 래퍼는 되도록 얇게 유지하세요. 파라미터 연결, method와 포트 대응, 클록과 리셋 선언, 빠짐없는 스케줄과 경로 선언만 담고, 버퍼링, 중재, 정책은 bsc가 검사할 수 있는 일반 BSV 모듈로 감싸서 구현합니다.

8. 자주 하는 실수

8.1 RTL에 없는 포트 이름 쓰기

// 잘못된 코드: RTL 출력 이름은 Q가 아니라 Q_OUT
method Q read;

// 바른 코드: RTL 포트 목록의 이름을 그대로 쓴다
method Q_OUT read;

bsc는 RTL을 읽지 않으므로 두 코드를 모두 컴파일합니다. 잘못된 코드는 Icarus Verilog의 elaboration 단계에서 Q가 인스턴스의 포트가 아니라는 오류로 중단됩니다. 대응에 쓴 이름을 RTL 헤더와 하나씩 대조하세요.

8.2 레지스터 값을 RTL 파라미터로 전달하기

// 잘못된 코드: G0053, 파라미터가 동적인 식으로 인스턴스화됨
Reg#(Bit#(8)) lim <- mkReg(100);
LimiterIfc limiter <- mkExternalLimiter(lim);

// 바른 코드: elaboration 중에 알 수 있는 값을 전달한다
LimiterIfc limiter <- mkExternalLimiter(100);

파라미터는 하드웨어를 생성할 때 고정됩니다. 실행 중에 바뀌는 값은 method로 전달해야 합니다.

8.3 RTL에 ready 출력이 있는데 ready 생략하기

// 잘못된 코드: RTL은 바쁠 때 RDY를 0으로 두지만 래퍼가 무시한다
method start(CMD) enable(GO);

// 바른 코드: RDY가 start의 implicit condition이 된다
method start(CMD) enable(GO) ready(RDY);

ready가 없으면 래퍼는 start가 항상 준비되어 있다고 약속하므로, RTL이 바쁜 동안에도 호출하는 rule이 실행됩니다. 오류는 보고되지 않고 명령만 사라집니다.

8.4 RTL 타이밍과 맞지 않는 스케줄

// 잘못된 코드: 같은 사이클의 읽기가 그 사이클에 쓴 값을 본다고 약속한다
schedule (write) SB (read);

// 바른 코드: 에지에서 갱신되는 레지스터는 이전 값을 보여 준다
schedule (read) SB (write);

bsc는 스케줄 선언을 믿고 설계를 구성합니다. RTL이 클록 에지에서만 값을 바꾼다면 읽기를 앞에 두어야 합니다.

8.5 관계를 선언하지 않은 method 쌍

// 불완전한 코드: write와 write의 관계가 없어 경고 P0200
schedule (read) CF (read);
schedule (read) SB (write);

// 완전한 코드: 모든 쌍을 선언한다
schedule (read) CF (read);
schedule (read) SB (write);
schedule (write) C (write);

빠진 관계는 충돌로 처리되며, 이 레지스터에서는 우연히 그것이 맞습니다. 그래도 관계를 적어 두어야 읽는 사람과 컴파일러가 같은 계약을 봅니다.

9. 정리

주제핵심
BVI 선언import "BVI" RTL_이름 = module ...은 bsc가 분석할 수 없는 RTL 경계를 기술합니다
value method 대응method 결과 이름(인자들);로 쓰며, 폭은 인터페이스 타입이 정합니다
Action method 대응enable은 호출을 표시하고 ready는 implicit condition이 됩니다. 생략도 약속입니다
클록과 리셋default_clock, default_reset을 쓰고, 조합 블록에는 no_clock, no_reset을 씁니다. 리셋은 활성 로우입니다
schedule모든 method 쌍에 CF, SB, SBR, C를 선언합니다. 빠진 쌍은 P0200 경고와 함께 충돌로 가정됩니다
parameter정적 값만 쓸 수 있고(아니면 G0053), 값이 다르면 다른 하드웨어입니다
실패 단계BSC 컴파일, Verilog elaboration, 시뮬레이션은 각각 다른 원인을 가리킵니다

10. 확인 문제

  1. BVI 계약 가운데 bsc가 확인하는 부분과 작성자가 직접 보장해야 하는 부분은 각각 무엇일까요?

    정답 보기

    bsc는 method 타입과 폭, 파라미터가 정적인지, 스케줄 선언이 일관된지를 확인합니다. 포트 이름, 준비 상태, Enable 동작, 리셋 극성, 선언한 스케줄이 실제 RTL과 맞는지는 작성자가 보장해야 합니다.

  2. ready를 생략하면 포트 목록만이 아니라 프로토콜 약속이 바뀌는 까닭은 무엇일까요?

    정답 보기

    ready가 없으면 method에 implicit condition이 없으므로 호출하는 rule이 매 사이클 실행될 수 있습니다. RTL이 실제로 바쁠 수 있다면 그 호출은 RTL이 받을 수 없는 때에 도착합니다.

  3. 레지스터 예제에서 같은 사이클에 읽으면 어떤 값이 반환되며, 그 결과를 정하는 선언은 무엇일까요?

    정답 보기

    이전 값, 곧 리셋 직후라면 00이 반환됩니다. schedule (read) SB (write)가 읽기를 쓰기 앞에 두며, 이는 에지에서 갱신되는 레지스터와 맞습니다.

  4. RTL 파라미터를 바꾸면 새 하드웨어가 필요하지만 method 인자를 바꾸는 데는 필요 없는 까닭은 무엇일까요?

    정답 보기

    파라미터는 elaboration 중에 고정되어 생성되는 인스턴스의 모양을 정합니다. method 인자는 입력 포트에 실리는 값이므로 호출할 때마다 달라질 수 있습니다.

  5. RTL 포트 이름의 오타는 어느 단계에서 드러나며, 사실과 다른 CF 선언은 어느 단계에서 드러날까요?

    정답 보기

    포트 이름 오타는 BSC 컴파일이 성공한 뒤 Verilog elaboration에서 중단됩니다. 사실과 다른 CF 선언은 컴파일과 elaboration을 모두 통과하고, 시뮬레이션이나 하드웨어에서 잘못된 값이나 순서로만 드러납니다.

아래 실습 문제는 이 장의 래퍼를 다룹니다. 고정된 import 레지스터 래퍼가 쓰기에 사용하는 값 대응을 완성하는 문제, 결과 대응이 가져온 RTL에 저장된 값을 가리는 원인을 찾는 문제, 고정된 BVI 파라미터 대응에 필요한 초기값을 전달하는 문제입니다.

확인 지점

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

참고 자료

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