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 컴파일과 elaboration | method 타입과 폭, 정적 파라미터, 스케줄 선언의 일관성 | 대응시킨 포트와 프로토콜이 실제 RTL과 같다는 것 |
Verilog elaboration | 모듈 이름과 포트 이름이 존재하는지 | 모듈 이름, 포트 이름, 폭이 정확하다는 것 |
| 시뮬레이션과 하드웨어 | RTL 자체 외에는 없음 | RTL이 선언한 ready, enable, 클록, 리셋, 스케줄을 실제로 지킨다는 것 |
따라서 준비 상태, 리셋 극성, 허용되는 동시 실행을 사실과 다르게 선언한 래퍼도 컴파일과 연결을 통과할 수 있습니다.
1.2 선언의 형태
형식
import "BVI" RTL_모듈_이름 =
module mk래퍼_이름#(인자)(인터페이스_타입);
// 필요하면 일반 모듈 문장
// parameter, 클록, 리셋, port, method, schedule, path 문장
endmoduleinterface 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_reset | RTL이 쓰는 클록과 리셋입니다 |
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 = 23 | 8비트 입력 포트 두 개가 구동됩니다 |
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를 한 번 호출하고 결과를 출력합니다.
실행하기 전에 예측해 보세요.
add(8'd19, 8'd23)의 10진수 합은 무엇일까요?- 결과를 얻는 데 클록 에지가 필요할까요, 같은 사이클에 바로 쓸 수 있을까요?
- BSV 쪽에서
A,B,SUM의 8비트 폭은 어디에서 정해질까요?
확인 문제
show rule은 adder.add(8'd19, 8'd23)을 호출하고 같은 rule에서 결과를 출력합니다. 무엇이 출력되며, 결과를 얻는 데 클록 에지가 필요합니까?
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
endpackagemodule 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이 보일 수 있습니다. 실행 명령에서
생기는 경고이며 결과에는 영향이 없습니다.
직접 바꿔 보세요.
- 인자를
8'd200과8'd100으로 바꾸세요. 출력은 300이 아니라 44입니다. RTL은 8비트 값 두 개를 더해 8비트SUM에 담고, method 타입도 정확히 그 폭을 약속했습니다. 넘친 비트는 외부 논리에서 사라지며bsc가 문제 삼을 것이 없습니다. - 인자를 되돌리고 대응을
method S add(A, B);로 바꾸세요.bsc는 RTL 헤더를 읽지 않으므로 래퍼를 그대로 컴파일합니다. 오류는 다음 단계인 Verilogelaboration에서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이 실행됩니다 |
| 생략한 절 | 생략이 약속하는 내용 |
|---|---|
ready | method가 항상 준비되어 있습니다 |
Action method의 enable | RTL이 호출마다 펄스를 요구하지 않습니다 |
주의. 절을 생략해도 빠진 프로토콜 논리가 생기지 않습니다. 생략 자체가 외부 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.read | Q_OUT이 결과를 제공합니다 |
사이클별 동작은 이렇게 진행됩니다.
- 리셋이
Q_OUT과 BSVstep레지스터를 0으로 만듭니다. step == 0인 사이클에writeValue가write를 호출합니다.D_IN에5a가 실리고EN이 1이므로 외부 레지스터는 클록 에지에서5a를 저장합니다.step도 같은 에지에 1이 됩니다.- 다음 사이클에는
showValue만 실행 가능합니다.read가Q_OUT의 값을 반환하고 시뮬레이션이 끝납니다.
실행하기 전에 예측해 보세요.
showValue는 어떤 값을 출력할까요?- 그 값이 리셋 값
00이 아닌 까닭은 무엇일까요? write의 호출 프로토콜을 전달하는 RTL 포트는 무엇일까요?
확인 문제
writeValue는 step == 0일 때 imported.write(8'h5a)를 호출하고, showValue는 step == 1일 때 imported.read를 읽습니다. showValue는 무엇을 출력합니까?
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
endpackagemodule 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 = 5a | D_IN과 EN이 RTL에 전달되었고, 값이 에지에서 저장되었습니다 |
값이 00이 아님 | 읽기가 쓰기의 클록 에지보다 한 사이클 뒤에 일어났습니다 |
직접 바꿔 보세요.
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 | 읽기는 이전 값을 보고 쓰기는 에지에서 반영됩니다 |
| 쓰기 + 쓰기 | 불허, C | D_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이 발생합니다.
| 전달 수단 | 정해지는 시점 | 실행 중에 바꿀 수 있는가 |
|---|---|---|
parameter | elaboration | 없습니다. 값이 다르면 다른 하드웨어입니다 |
| method 인자 | 호출할 때마다 | 있습니다 |
5.2 예제: 상한이 고정된 리미터
ParameterizedLimiter.bsv는 BSV 인자 limit를 Verilog 파라미터 LIMIT로
전달합니다. mkTop의 생성자 호출, parameter 문, ExternalLimiter.v의 비교식
순서로 읽으세요.
정적인 흐름은 이렇게 이어집니다. BSV가 mkExternalLimiter(100)을 elaboration하면,
생성된 Verilog가 LIMIT = 100인 ExternalLimiter를 인스턴스화합니다. 실행 중에는
clamp가 이 고정된 인스턴스에 140을 전달합니다.
실행하기 전에 예측해 보세요.
LIMIT가 100일 때clamp(8'd140)은 무엇을 반환할까요?- 인스턴스가 만들어진 뒤에
rule이LIMIT를 바꿀 수 있을까요?
확인 문제
mkTop은 mkExternalLimiter(100)을 인스턴스화하고 limiter.clamp(8'd140)을 호출합니다. 무엇이 출력됩니까?
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
endpackagemodule 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 = 100 | 140이 상한을 넘었으므로 RTL이 LIMIT를 반환했습니다 |
상한이 mkExternalLimiter(100)에서 옴 | BSV 인자가 elaboration 중에 RTL 파라미터로 전달되었습니다 |
직접 바꿔 보세요.
- 생성자 인자를 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_clock | BSV Clock 인자를 RTL 클록 포트에 연결합니다 |
output_clock | RTL이 생성한 클록을 내보냅니다 |
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. 확인 문제
-
BVI 계약 가운데
bsc가 확인하는 부분과 작성자가 직접 보장해야 하는 부분은 각각 무엇일까요?정답 보기
bsc는 method 타입과 폭, 파라미터가 정적인지, 스케줄 선언이 일관된지를 확인합니다. 포트 이름, 준비 상태, Enable 동작, 리셋 극성, 선언한 스케줄이 실제 RTL과 맞는지는 작성자가 보장해야 합니다. -
ready를 생략하면 포트 목록만이 아니라 프로토콜 약속이 바뀌는 까닭은 무엇일까요?정답 보기
ready가 없으면 method에implicit condition이 없으므로 호출하는rule이 매 사이클 실행될 수 있습니다. RTL이 실제로 바쁠 수 있다면 그 호출은 RTL이 받을 수 없는 때에 도착합니다. -
레지스터 예제에서 같은 사이클에 읽으면 어떤 값이 반환되며, 그 결과를 정하는 선언은 무엇일까요?
정답 보기
이전 값, 곧 리셋 직후라면
00이 반환됩니다.schedule (read) SB (write)가 읽기를 쓰기 앞에 두며, 이는 에지에서 갱신되는 레지스터와 맞습니다. -
RTL 파라미터를 바꾸면 새 하드웨어가 필요하지만 method 인자를 바꾸는 데는 필요 없는 까닭은 무엇일까요?
정답 보기
파라미터는
elaboration중에 고정되어 생성되는 인스턴스의 모양을 정합니다. method 인자는 입력 포트에 실리는 값이므로 호출할 때마다 달라질 수 있습니다. -
RTL 포트 이름의 오타는 어느 단계에서 드러나며, 사실과 다른
CF선언은 어느 단계에서 드러날까요?정답 보기
포트 이름 오타는 BSC 컴파일이 성공한 뒤 Verilog
elaboration에서 중단됩니다. 사실과 다른CF선언은 컴파일과elaboration을 모두 통과하고, 시뮬레이션이나 하드웨어에서 잘못된 값이나 순서로만 드러납니다.
아래 실습 문제는 이 장의 래퍼를 다룹니다. 고정된 import 레지스터 래퍼가 쓰기에 사용하는 값 대응을 완성하는 문제, 결과 대응이 가져온 RTL에 저장된 값을 가리는 원인을 찾는 문제, 고정된 BVI 파라미터 대응에 필요한 초기값을 전달하는 문제입니다.