본문으로 건너뛰기
code0.HDL

BSV 설계에 C 포함하기

import BDPI로 BSV에서 C 함수를 호출합니다. 선언, 양쪽이 보는 전달 타입, packed 값과 넓은 값, 다형적 import, 반환 형태, 경계에서 생기는 오류의 모습을 다룹니다.

이 장에서 배울 내용

  • import한 C 함수를 선언하고 BSV 타입을 C 원형과 맞출 수 있습니다
  • packed BSV 값이 C 쪽에서 어떻게 보이는지 넓은 값까지 포함해 예측할 수 있습니다
  • 링크나 실행 시점의 증상으로 경계 오류를 진단할 수 있습니다
  • C 코드가 하는 일에 맞춰 값·Action·ActionValue import 가운데 하나를 고를 수 있습니다
이 장에서 배우는 개념
  • BSV 설계에 C 포함하기
먼저 알아야 할 개념

import "BDPI"는 C로 구현한 함수를 BSV 시뮬레이션 코드에서 타입이 있는 함수로 호출하게 하는 문법입니다. 참조 모델, 테스트벤치 유틸리티, 파일을 다루는 서비스, 기존 소프트웨어 알고리즘을 BSV 시뮬레이션에 연결할 때 씁니다. 어떤 호출이 타입 검사를 통과하는지는 BSV 선언이 정하고, C 함수 원형은 bsc가 고른 전달 타입과 맞아야 합니다. 두 쪽이 맞는지 대조해 주는 단계는 없습니다. 이 장은 BSV Language Reference Guide 제16장을 따라 선언과 빌드 단계, pack 폭에 따라 정해지는 C 전달 타입, 비트 패턴으로 건너가는 Bits 타입, 포인터로 전달되는 넓은 값과 다형적 값, 값·Action·ActionValue 반환 형태를 다룹니다.

먼저 알아 두면 좋은 것. 이 장은 BSV 설계에 RTL 포함하기 장 다음에 이어집니다. 타입클래스와 proviso 장의 Bits와 pack, Add 같은 proviso, valueOf, 그리고 method: value·Action·ActionValue 장의 Action과 ActionValue 형태를 알고 있어야 합니다. BSV가 검사하는 사실과 외부 경계 너머에서 믿어야 하는 사실을 구분하는 앞 장의 질문이 이 장에서도 이어집니다.

주의. BDPI는 시뮬레이션 경계입니다. C 함수를 호출하는 설계는 일반적인 합성 가능 하드웨어가 아닙니다. 외부 함수 호출은 합성 대상 데이터 경로 밖에 두고, 하드웨어 동작은 BSV나 RTL로 작성하세요.

1. BDPI 선언

1.1 가져온 함수 선언하기

BDPI 선언은 본문이 없는 함수 원형처럼 생겼습니다. 연결 이름을 쓰면 BSV 코드가 호출하는 이름과 C가 내보내는 심볼 이름을 다르게 둘 수 있습니다.

형식

import "BDPI" function 결과_타입 이름(인자_타입 인자, ...);
import "BDPI" C_심볼 = function 결과_타입 이름(인자_타입 인자, ...);
import "BDPI" function Bit#(32) checksum(Bit#(32) value);

import "BDPI" c_checksum =
function Bit#(32) checksum(Bit#(32) value);
부분의미
import "BDPI"함수의 구현이 C에 있습니다
c_checksum =시뮬레이터가 찾아야 할 C 심볼입니다. 생략하면 BSV 이름을 씁니다
function Bit#(32) checksum(...)BSV 호출 코드가 보고 컴파일러가 검사하는 타입입니다

연결 이름에는 BSV 식별자의 대소문자 규칙이 적용되지 않으므로 c_checksum 같은 C 이름을 그대로 쓸 수 있습니다.

1.2 빌드 단계

BDPI 설계는 네 단계로 빌드되며, 단계마다 확인하는 내용이 다릅니다.

단계예제의 명령생성하거나 확인하는 것
1. BSV 컴파일bsc -verilog -u -g mkTop ...mkTop.v, 가져온 함수마다 .ba 파일 하나, VPI 래퍼 소스
2. C 컴파일gcc -std=c11 -Wall -Wextra -Werror -c ...C 오브젝트 파일
3. 시뮬레이터 연결bsc -verilog -vsim iverilog -e mkTop ... 이름.ba 파일.oC 코드를 적재 가능한 라이브러리로 담은 시뮬레이터 바이너리
4. 시뮬레이션vvp sim.out라이브러리를 적재하고 C 심볼을 찾은 뒤 실행

3단계에는 가져온 함수마다 .ba 파일이 하나씩 필요합니다. logValue와 nextCount를 가져온 설계라면 logValue.ba와 nextCount.ba를 모두 전달합니다.

참고. 1단계는 가져온 함수마다 vpi_wrapper_<이름>.c를 생성합니다. 이 파일 앞부분의 "the type of the wrapped function" 주석 아래에 bsc가 기대하는 C 원형이 있습니다. 직접 쓴 C 원형을 이 줄과 대조해 보세요.

1.3 예제: 이름이 다른 C 함수 호출하기

ScalarBridge.bsv는 addBias를 가져오며, 그 구현은 scalar_bridge.c의 C 함수 c_add_bias입니다. BDPI 선언과 C 함수 원형을 나란히 놓고 읽으세요.

호출 시점에 37은 8비트 전달 타입으로, 5는 32비트 전달 타입으로 넘어갑니다. C 함수는 첫 값을 넓혀 두 값을 더하고 32비트 결과를 반환합니다.

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

  1. Bit#(8) 인자와 Bit#(32) 인자는 각각 어떤 C 타입으로 받을까요?
  2. 시뮬레이터가 C 오브젝트에서 찾아야 할 심볼은 무엇일까요?
  3. 출력되는 값은 무엇일까요?
scalar_bridge.c
#include <stdint.h>

// Bit#(32)는 32비트 부호 없는 타입으로 BDPI 경계를 넘는다.
uint32_t c_add_bias(uint8_t value, uint32_t bias) {
  return (uint32_t)value + bias;
}

이 개념 실습하기

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

결과 확인.

관찰 내용확인할 내용
sum = 42C 함수가 실행되었고 그 결과가 BSV 값으로 반환되었습니다
Foreign import file created: c_add_bias.ba.ba 파일 이름은 BSV 이름이 아니라 연결 이름을 따릅니다
Icarus Verilog의 VPI 지원이 불완전하다는 경고이 시뮬레이터에서는 항상 나타나며 예제 결과에는 영향이 없습니다

직접 바꿔 보세요.

  1. BSV 선언은 그대로 두고 C 함수 이름을 c_add_bias2로 바꾸세요. BSC 컴파일, gcc 컴파일, 시뮬레이터 바이너리 생성이 모두 성공합니다. 시뮬레이션 전에는 심볼이 존재하는지 확인하는 단계가 없기 때문입니다. 오류는 vvp가 라이브러리를 적재할 때 정의되지 않은 심볼 c_add_bias로 보고되고, 종료 코드는 127입니다. 연결 이름의 오타는 BSV 타입 오류도 C 컴파일 오류도 아닌 적재 시점의 오류입니다.

2. C 전달 타입

2.1 폭이 전달 타입을 정합니다

BDPI는 각 인자와 결과의 pack 폭에 따라 C 타입을 고릅니다.

BSV 인자 또는 결과C 전달 타입
Bit#(0)–Bit#(8)unsigned char
Bit#(9)–Bit#(32)unsigned int
Bit#(33)–Bit#(64)unsigned long long
Bit#(65) 이상unsigned int *
polymorphic type인 Bit#(n)unsigned int *
입력 Stringchar *
import "BDPI" function Bit#(17) scale(Bit#(5) shift, Bit#(40) value);
unsigned int scale(unsigned char shift, unsigned long long value);
BSV 타입폭 구간전달 타입
Bit#(5) shift0–8unsigned char
Bit#(40) value33–64unsigned long long
Bit#(17) 결과9–32unsigned int

Bit#(17)도 unsigned int 하나를 통째로 차지하며, BSV 데이터는 하위 17비트에만 들어 있습니다. 예제는 실습 환경에서 위 전달 타입과 폭이 정확히 같은 uint8_t와 uint32_t를 씁니다.

참고. 이 bsc 버전이 생성하는 래퍼는 8비트 전달 타입을 그냥 char로 적습니다. 크기는 unsigned char와 같습니다. C 쪽에서 부호 없는 8비트 타입을 쓰면 비트 패턴을 해석할 때 혼동이 없습니다.

처음 보는 선언에 위 표를 적용해 보세요.

import "BDPI" function Bit#(40) f(Bit#(9) a, Bit#(64) b, Bit#(65) c);

확인 문제

import BDPI function Bit#(40) f(Bit#(9) a, Bit#(64) b, Bit#(65) c); 에 맞는 C 원형은 어느 것일까요?

답

2.2 String은 입력으로만 씁니다

String은 char *로 C에 전달할 수 있지만 반환할 수는 없습니다. 결과 타입을 String으로 선언하면 외부 함수의 반환 타입이 String일 수 없다는 오류 T0087이 발생합니다.

3. Bits 타입은 비트로 전달됩니다

3.1 비트 표현은 Bits 인스턴스가 정합니다

Int, UInt, Bool, Maybe, 구조체, enum, tagged union은 Bits 인스턴스가 있으면 BDPI 경계를 넘을 수 있습니다. 호출 전에 pack되고, 반환된 비트는 호출 뒤에 unpack됩니다.

import "BDPI" function Bool isZero(Int#(32) value);
uint8_t isZero(uint32_t value);   // Bool은 8비트 전달 타입으로 반환된다

pack은 BSV 타입의 계약을 지킬 뿐 C의 부호 있는 타입을 고르지 않습니다. Int#(32)와 UInt#(32)는 모두 32비트 부호 없는 전달 타입으로 넘어갑니다. 그 비트를 부호 있는 수로 볼지 부호 없는 수로 볼지는 C 코드가 정합니다.

BSV 값C에 도착하는 비트uint32_t로 읽은 값
Int#(32) -132비트가 모두 1가장 큰 부호 없는 값인 0xffffffff
Int#(32) -33의 2의 보수0xfffffffd
C가 반환한 Bool True최하위 비트가 1True로 unpack됩니다

구조체, enum, tagged union에도 같은 규칙이 적용됩니다. C 코드는 BSV Bits 인스턴스가 만든 비트 배치를 정확히 알아야 합니다.

확인 문제

import BDPI function Bool inRange(Int#(16) v, UInt#(8) lo); 에 대해 bsc가 기대하는 C 원형은 무엇일까요?

답

3.2 예제: C에서 하는 부호 있는 비교

PackedSigned.bsv는 Int#(32) 두 개를 받는 signedGreater를 가져옵니다. packed_signed.c는 두 값을 uint32_t로 받은 뒤 signed_bits에서 부호 있는 수로 되돌려 비교합니다. BSV 선언과 signed_bits를 함께 읽으세요. 경계에서는 부호 변환이 일어나지 않습니다.

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

  1. -1은 어떤 비트 패턴으로 도착할까요?
  2. signedGreater(-1, 1)은 무엇을 출력할까요?
  3. signedGreater(7, -3)은 무엇을 출력할까요?
PackedSigned.bsv
package PackedSigned;

// BDPI는 pack된 비트를 전달하고, 부호 의미는 Int#(32) 타입이 유지한다.
import "BDPI" c_signed_greater =
function Bool signedGreater(Int#(32) left, Int#(32) right);

(* synthesize *)
module mkTop(Empty);
  rule show;
    $display("negative greater = %0d", pack(signedGreater(-1, 1)));
    $display("positive greater = %0d", pack(signedGreater(7, -3)));
    $finish(0);
  endrule
endmodule

endpackage
packed_signed.c
#include <stdint.h>

static int32_t signed_bits(uint32_t bits) {
  if ((bits & UINT32_C(0x80000000)) == 0) return (int32_t)bits;
  return -1 - (int32_t)(~bits);
}

// Bool은 8비트 부호 없는 타입으로 반환된다.
uint8_t c_signed_greater(uint32_t left, uint32_t right) {
  return (uint8_t)(signed_bits(left) > signed_bits(right));
}

이 개념 실습하기

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

결과 확인.

출력 줄값확인할 내용
negative greater0-1 > 1이 거짓이므로 C가 부호 있는 수로 비교했습니다
positive greater17 > -3이 참입니다. -3이 2의 보수 패턴 그대로 경계를 넘었습니다

BSV 선언이 Int#(32)만 받으므로 부호 없는 값을 실수로 전달할 수 없습니다. 올바른 결과는 C 쪽에서 부호 의미를 되살리는 signed_bits 덕분입니다.

직접 바꿔 보세요.

  1. C에서 전달된 값을 그대로 left > right로 비교하고, 쓰이지 않게 된 signed_bits를 지우세요. C 컴파일에 -Werror가 붙어 있어서 쓰이지 않는 정적 함수를 남기면 빌드가 중단됩니다. -1이 가장 큰 부호 없는 값 0xffffffff로 도착하므로 두 결과가 1과 0으로 뒤집힙니다. 경계를 넘은 비트는 그대로이고 C 쪽 해석만 바뀌었습니다.
  2. C 파일을 되돌리고 BSV 인자 타입을 UInt#(32)로 바꾸세요. 이번에는 C가 관여하기 전에 BSC가 오류 T0051("Literal -1 is not a valid UInt#(32)")로 중단합니다. 부호 의미를 강제하는 곳은 BSV 선언입니다.

4. 넓은 값

4.1 32비트 워드 배열

65비트 이상의 벡터는 32비트 워드 배열을 가리키는 포인터로 전달됩니다.

BSV 비트배열 위치
bit 0워드 0의 bit 0
bit 31워드 0의 bit 31
bit 32워드 1의 bit 0
그보다 높은 비트다음 워드들이 차례로 상위 비트를 담습니다

BDPI는 포인터만 전달하고 폭은 전달하지 않습니다. C 함수가 폭을 미리 알거나 다른 인자로 받아야 합니다.

확인 문제

Bit#(96) 인자가 unsigned int *a로 도착합니다. BSV의 비트 70은 어디에 있을까요?

답

4.2 넓은 결과

넓은 결과는 방향이 반대입니다. bsc가 결과 저장소를 마련하고 그 포인터를 C 함수의 첫 인자로 전달합니다. C 함수는 void를 반환하고 받은 워드를 채우며, 버퍼를 할당하거나 해제하지 않습니다.

import "BDPI" function Bit#(96) mix96(Bit#(96) a);
void mix96(unsigned int *result, unsigned int *a);   // 각각 워드 3개

5. polymorphic type 가져오기

5.1 다형 폭은 항상 포인터를 씁니다

가져온 Bit#(n)은 polymorphic type이므로, 실제 호출이 모두 작은 폭을 쓰더라도 C 인자는 unsigned int *입니다.

import "BDPI" function Bit#(32) popcountPoly(Bit#(n) value);
unsigned int popcountPoly(unsigned int *value);   // 폭은 전달되지 않는다

확인 문제

설계의 모든 popcountPoly 호출이 Bit#(8)을 넘깁니다. C 매개변수는 어떤 모양일까요?

답

더 단순한 방법은 C 경계를 구체 타입으로 두고 BSV 래퍼에서 다형성을 되살리는 것입니다.

  1. Bit#(32)를 받는 C 함수를 가져옵니다.
  2. BSV 값을 32비트로 확장합니다.
  3. valueOf(n)을 폭 인자로 명시해 전달합니다.
  4. 반환값을 Bit#(n)으로 절단합니다.

형식

function Bit#(n) 래퍼(Bit#(32) value)
  provisos (Add#(n, spare, 32));
  return truncate(가져온_함수(value, fromInteger(valueOf(n))));
endfunction
부분역할
Add#(n, spare, 32)n이 32비트 전달 타입 안에 들어간다는 것을 보장합니다
valueOf(n)타입 수준의 폭을 값으로 바꿉니다
fromInteger(...)그 값을 C 인자의 타입으로 변환합니다
truncate(...)호출한 쪽에 정확히 n비트를 돌려줍니다

5.2 예제: 폭을 명시하는 마스크

WidthWrapper.bsv가 가져오는 maskToWidthRaw는 C 쪽에서 32비트 값의 하위 width비트만 남깁니다. BSV 래퍼 maskToWidth는 그 폭을 자기 결과 타입에서 얻어 전달합니다. maskToWidth와 width_wrapper.c를 읽으세요.

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

  1. 폭 5와 폭 12의 마스크는 각각 무엇일까요?
  2. Bit#(5) five = maskToWidth(32'h3f)는 무엇을 출력할까요?
  3. Bit#(12) twelve = maskToWidth(32'habcd)는 무엇을 출력할까요?
WidthWrapper.bsv
package WidthWrapper;

import "BDPI" c_mask_to_width =
function Bit#(32) maskToWidthRaw(Bit#(32) value, Bit#(8) width);

// C 경계는 구체 타입으로 두고 다형성은 BSV에서 되살린다.
function Bit#(n) maskToWidth(Bit#(32) value)
  provisos (Add#(n, spare, 32));
  return truncate(maskToWidthRaw(value, fromInteger(valueOf(n))));
endfunction

(* synthesize *)
module mkTop(Empty);
  rule show;
    Bit#(5) five = maskToWidth(32'h3f);
    Bit#(12) twelve = maskToWidth(32'habcd);
    $display("five bits = %0h", five);
    $display("twelve bits = %0h", twelve);
    $finish(0);
  endrule
endmodule

endpackage
width_wrapper.c
#include <stdint.h>

// 드러나지 않는 벡터 폭을 BSV 래퍼가 명시적으로 전달한다.
uint32_t c_mask_to_width(uint32_t value, uint8_t width) {
  if (width == 0) return 0;
  if (width >= 32) return value;
  return value & ((UINT32_C(1) << width) - UINT32_C(1));
}

이 개념 실습하기

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

결과 확인.

출력 줄값확인할 내용
five bits1fvalueOf(n)이 5였고, C가 0x3f의 하위 5비트를 남겼습니다
twelve bitsbcdvalueOf(n)이 12였고, C가 0xabcd의 하위 16진수 세 자리를 남겼습니다

C 함수는 polymorphic type 벡터를 한 번도 보지 않습니다. 각 호출의 결과 타입이 폭을 정했고, 래퍼가 그 폭을 명시적으로 전달했습니다.

직접 바꿔 보세요.

  1. twelve의 타입을 Bit#(16)으로 바꾸세요. valueOf(n)이 16이 되어 C가 네 자리를 남기므로 abcd가 출력됩니다. C 코드는 바뀌지 않았습니다.
  2. Bit#(33)으로 바꾸세요. Add#(33, spare, 32)가 성립하지 않으므로 C 코드가 실행되기 전에 BSC가 오류 T0031로 중단합니다. 구체 타입 32비트 경계를 지키는 것은 proviso입니다.

6. 반환 형태

6.1 값, Action, ActionValue

가져온 함수는 값, Action, ActionValue#(t) 가운데 하나를 반환할 수 있습니다. C 코드가 실제로 하는 일에 맞추어 고르세요.

형식

import "BDPI" function 결과_타입 순수_함수(...);                // 값
import "BDPI" function Action 효과_함수(...);                   // 효과만 있음
import "BDPI" function ActionValue#(결과_타입) 둘_다(...);      // 효과와 결과
import "BDPI" function Action logValue(Bit#(32) v);
import "BDPI" function ActionValue#(Bit#(32)) nextCount();

rule step1;
  logValue(32'd7);
endrule

rule step2;
  let x <- nextCount();
  $display("x=%0d", x);
endrule
void logValue(uint32_t v);      // Action: 반환값 없음
uint32_t nextCount(void);       // ActionValue: 결과를 반환
형태쓰는 경우스케줄상의 의미
값부수 효과가 없고 몇 번 호출해도 같은 결과를 내는 계산식이 평가될 때마다 평가될 수 있습니다
ActionC 쪽 상태를 바꾸거나 입출력을 수행하는 호출rule이 실행될 때 정확히 호출됩니다
ActionValue순서가 정해진 한 번의 호출이 효과와 결과를 함께 만드는 경우rule이 실행될 때 호출되며, 결과는 <-로 받습니다

호출 횟수나 순서가 중요한데 순수한 값 함수로 선언하면 컴파일러에 거짓 약속을 하게 됩니다.

확인 문제

C 도우미 세 개의 import 형태를 고르세요. (1) 상태 없이 인자의 crc32를 계산합니다. (2) 추적 파일에 한 줄을 덧붙입니다. (3) C 쪽 큐에서 다음 자극 워드를 꺼냅니다.

답

6.2 C 자원 공유

가져온 함수 여러 개가 파일 핸들이나 메모리 객체 같은 C 자원을 공유할 수 있습니다. 핸들은 C 포인터를 BSV에 그대로 노출하지 말고 pack된 비트 벡터로 호출 사이에 전달하세요. 자원의 수명은 C 쪽이 관리합니다.

7. 실패 단계로 원인 좁히기

어느 단계에서 실패했는지가 원인을 좁혀 줍니다.

실패의미
BSV 타입 오류C가 관여하기 전에 선언이나 래퍼가 서로 맞지 않습니다
vvp 시작 시 정의되지 않은 심볼, 종료 코드 127연결 이름과 C 심볼이 다르거나 오브젝트가 빠졌습니다. 빌드 자체는 성공했습니다
C 호출 중 잘못된 값이나 비정상 종료전달 타입의 폭과 포인터 형태가 C 원형과 맞는지 확인합니다. 불일치하면 결과를 보장할 수 없습니다
비트는 맞지만 순서가 어긋나거나 효과가 반복됨상태를 바꾸는 C 호출을 순수한 값 함수로 선언했습니다

확인 문제

bsc, gcc, 시뮬레이터 링크가 모두 성공했는데 vvp가 정의되지 않은 심볼 c_mix를 보고하고 종료 코드 127로 끝났습니다. 무엇부터 확인해야 할까요?

답

믿을 수 있는 BDPI 연결에는 다섯 가지가 기록되어 있습니다. BSV 함수 타입, 정확한 C 심볼, 각 값의 pack 폭과 전달 타입, 따로 전달하는 폭이나 크기, 호출에 관찰 가능한 효과가 있는지 여부입니다. 변환 정책은 작은 BSV 래퍼에, ABI 세부 사항은 작은 C 함수에 두세요.

8. 자주 하는 실수

// 잘못된 코드: C는 c_add_bias2를 정의하므로 vvp가 정의되지 않은 심볼로 127 종료
import "BDPI" c_add_bias =
function Bit#(32) addBias(Bit#(8) value, Bit#(32) bias);

// 바른 코드: 연결 이름이 C 함수 이름과 정확히 같다
import "BDPI" c_add_bias2 =
function Bit#(32) addBias(Bit#(8) value, Bit#(32) bias);

심볼을 찾아보는 컴파일 단계는 없습니다. 이름은 C 소스에서 그대로 옮기고, 연결 단계에 전달하는 .ba 파일 이름도 이 이름을 따른다는 점을 기억하세요.

8.2 polymorphic type 가져오기에 스칼라 C 인자 쓰기

import "BDPI" function Bit#(32) popcountPoly(Bit#(n) value);
// 잘못된 코드: 인자는 수가 아니라 포인터다
unsigned int popcountPoly(unsigned int value);

// 바른 코드: 다형 인자는 워드 배열로 도착한다
unsigned int popcountPoly(unsigned int *value);

실제 폭과 관계없이 polymorphic type이면 포인터 ABI가 쓰입니다. 스칼라 전달 타입을 원한다면 width-wrapper 예제처럼 구체 타입 Bit#(32) 함수를 가져와 감싸세요.

8.3 String 반환하기

// 잘못된 코드: T0087, 외부 함수의 반환 타입은 String일 수 없다
import "BDPI" function String greet(Bit#(8) id);

// 바른 코드: pack된 데이터를 반환하고 출력은 BSV 쪽에서 한다
import "BDPI" function Bit#(32) greetCode(Bit#(8) id);

C에서 돌려받을 수 있는 것은 비트 벡터로 pack할 수 있는 타입뿐입니다.

8.4 부호 있는 의도에 부호 없는 타입 쓰기

// 잘못된 코드: T0051, "Literal -1 is not a valid UInt#(32)"
import "BDPI" c_signed_greater =
function Bool signedGreater(UInt#(32) left, UInt#(32) right);

// 바른 코드: BSV 타입이 부호 의미를 나타낸다
import "BDPI" c_signed_greater =
function Bool signedGreater(Int#(32) left, Int#(32) right);

두 선언은 같은 C 전달 타입을 쓰므로 C 코드는 둘을 구별할 수 없습니다. 부호 있는 값이 필요한 곳에 부호 없는 값이 전달되는 것을 막을 수 있는 곳은 BSV 선언뿐입니다.

8.5 상태를 바꾸는 C 함수를 값 함수로 선언하기

// 잘못된 코드: 호출할 때마다 바뀌는 카운터를 순수 함수로 선언
import "BDPI" function Bit#(32) nextCount();

// 바른 코드: 효과와 결과를 순서가 정해진 한 번의 호출로 묶는다
import "BDPI" function ActionValue#(Bit#(32)) nextCount();

값 함수는 다시 호출해도 같은 결과가 나온다고 약속합니다. 카운터는 이 약속을 깨며, 컴파일러는 식이 나타나는 만큼 호출을 평가해도 됩니다.

9. 정리

주제핵심
선언import "BDPI" [C_심볼 =] function .... 연결 이름이 C 심볼이며 .ba 파일 이름도 정합니다
빌드BSV 컴파일, C 컴파일, 모든 .ba와 오브젝트를 넣은 연결, vvp 순서입니다. 심볼이 없으면 적재할 때에야 실패합니다
전달 타입폭 0–8은 unsigned char, 9–32는 unsigned int, 33–64는 unsigned long long, 그보다 넓거나 다형이면 unsigned int *입니다
Bits 타입비트 그대로 전달되며, 부호 의미는 BSV 타입이 지키고 C가 되살립니다
넓은 값32비트 워드 배열이며 bit 0은 워드 0에 있습니다. 넓은 결과는 첫 포인터 인자로 받습니다
다형성다형 Bit#(n)은 포인터를 씁니다. 구체 타입 가져오기와 BSV 래퍼로 스칼라 전달 타입을 유지할 수 있습니다
반환 형태순수 계산은 값, 효과는 Action, 둘 다면 ActionValue입니다

10. 확인 문제

  1. Int#(32)와 UInt#(32)가 같은 C 전달 타입을 쓰는 이유는 무엇일까요?

    정답 보기

    BDPI는 pack 폭만 보고 전달 타입을 고릅니다. 두 타입 모두 32비트로 pack되므로 32비트 부호 없는 값으로 전달됩니다. 부호 의미는 BSV 타입에 있으며, C 코드가 되살려야 합니다.

  2. proviso가 n을 제한하는데도 polymorphic type인 Bit#(n)이 포인터를 쓰는 이유는 무엇일까요?

    정답 보기

    C 함수는 한 번만 컴파일되고 BSV 타입이 허용하는 모든 폭에 쓰여야 합니다. 폭을 모를 때 쓸 수 있는 것은 포인터 ABI뿐이며, proviso는 가져온 함수의 타입을 바꾸지 않습니다.

  3. 연결 이름에 오타가 있습니다. 어느 단계가 실패하며, 그 전까지 빌드가 성공하는 이유는 무엇일까요?

    정답 보기

    시뮬레이션 단계가 실패합니다. vvp가 C 라이브러리를 적재할 때 정의되지 않은 심볼을 보고하고 종료 코드 127로 끝납니다. bsc, gcc, 연결 단계 모두 이름이 가리키는 심볼이 있는지 확인하지 않습니다.

  4. import "BDPI" function Bit#(96) mix96(Bit#(96) a);의 C 원형은 무엇일까요?

    정답 보기

    void mix96(unsigned int *result, unsigned int *a);입니다. 96비트 결과는 첫 인자로 받은 포인터를 통해 기록하고, 96비트 인자는 32비트 워드 3개를 가리키는 포인터로 도착합니다.

  5. 로그를 기록하고 상태 코드를 반환하는 C 도우미는 어떤 반환 형태로 가져와야 할까요?

    정답 보기

    ActionValue#(t)입니다. rule이 실행될 때 정확히 일어나야 하는 효과가 있고 데이터도 반환합니다. 값 함수로 선언하면 효과가 없다는 거짓 약속을 하게 됩니다.

확인 지점

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

참고 자료

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