KITAQ SERIES • 참고 판본
KITAQFC
패미컴 C 프로그래밍
소스 스냅샷: 2026년 9월 14일 · 한국어판: 2026년 9월 14일
Rust 네이티브 컴파일러와 보조 도구
Rust 1.85 이상으로 Windows, Linux, macOS ARM 및 macOS Intel용 컴파일러와 모든 보조 도구를 빌드할 수 있습니다. 네이티브 실행 파일에는 .NET이 필요하지 않으며, 리소스 처리 도구에도 Python이나 Pillow가 필요하지 않습니다.
Windows 실행 파일은 저장소 최상위 폴더에, Linux와 macOS 실행 파일은 아래 표의 bin/ 폴더에 있습니다. lib/와 라이선스 고지를 함께 보관하세요. Linux/macOS에서는 chmod +x로 실행 권한을 주고 해당 폴더를 PATH에 추가하거나 전체 경로로 실행하세요.
| OS | kitaqfc |
|---|---|
| Windows x64 | kitaqfc.exe |
| Linux x64 | bin/Linux-X64/kitaqfc |
| macOS ARM64 | bin/macOS-ARM64/kitaqfc |
| macOS Intel | bin/macOS-X64/kitaqfc |
kitaqfc · Rust 네이티브 컴파일러와 보조 도구 · GitHub Actions
Windows, Linux, macOS ARM 및 macOS Intel에서 네이티브 빌드와 실행 검증이 성공했습니다. KITAQGB는 환경별 테스트 48개와 보조 도구 검사 395개, KITAQFC는 55개와 401개를 통과했습니다. Rust 1.85도 검증했습니다. PUBLIC_DISTRIBUTION.json은 배치한 바이너리의 해시와 검증 출처를 기록합니다. 공개 GitHub Actions는 이 소스를 독립적으로 빌드하고 검증합니다.
저장된 참조 출력으로 ROM 바이트, 진단 및 보조 도구 형식을 검사합니다. 이전 C# 에뮬레이터 검증은 원래 소스 지문에 해당하는 과거 기록입니다. 모든 Rust API, 실제 하드웨어 또는 FDS BIOS를 통한 완전한 게임 시작을 입증하지는 않습니다. 원본 PNG 변환 스크립트는 없어 사양으로 재구현했으며 원본과의 바이트 일치는 확인할 수 없습니다.
--attach는 호환성을 위해 허용되지만 네이티브 실행 파일에 관리형 디버거를 연결하지 않습니다. PC 프로그램 디버깅에는 네이티브 디버거를 사용하세요.
KITAQGB · SHA-256 · KITAQFC · SHA-256
운영체제별 샘플 빌드
PowerShell 샘플 빌드는 최상위 Windows 실행 파일을 사용합니다. Linux/macOS에서는 같은 C 입력과 옵션을 네이티브 컴파일러에 전달하거나 PowerShell 7을 사용하세요. CHR 그래픽과 C 소스는 별도 입력이며 font.chr은 원래 샘플 글꼴을 유지합니다.
kitaqfc game.c -I lib -o game.nes --mapper=nrom완성된 예제 프로그램
각 프로그램과 공통 헤더, 제공된 ascii.c, 변환 리소스를 함께 실었습니다. m_으로 시작하는 이름은 설명서용 도우미입니다. 빌드 명령은 기대 결과와 검증 기록을 함께 읽으세요. 명령은 저장소들이 나란히 놓인 상위 폴더에서 실행합니다.
제공된 문자 집합 표시 fc_font.c
목적: 제공된 독자 제작 글꼴에 어떤 문자·숫자·문장부호가 있는지, 그리고 어떻게 그리는지 알아봅니다.
동작 원리: m_init이 표시와 글꼴을 준비합니다. m_text가 각 행을 그리고, m_put은 문자 코드 34로 큰따옴표를 그립니다.
조작 방법과 기대 결과: 대문자 26개, 소문자 26개, 숫자 10개, 문장부호 30개가 있는지 확인하세요. 모든 글자가 읽기 쉬운지 살펴보세요. 입력 조작은 필요하지 않습니다.

.\kitaqfc\kitaqfc.exe kitaq-docs/samples/fc_font.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_font.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Display the letters, digits and punctuation from the supplied ascii.c font.
// Expected: Uppercase, lowercase, numbers and all 30 supplied punctuation glyphs
#include "fc_common.h"
// Lay out every supplied character category, yielding between rows. The final
// m_put uses character code 34 to display the double quote separately.
void main(void) {
m_init();
m_text(2,3,"FONT");
m_text(2,5,"ABCDEFGHIJKLMNOP"); m_wait();
m_text(2,6,"QRSTUVWXYZ"); m_wait();
m_text(2,8,"0123456789"); m_wait();
m_text(2,10,"abcdefghijklmnop"); m_wait();
m_text(2,11,"qrstuvwxyz"); m_wait();
m_text(2,13,"!#$%&'()*+,-./"); m_wait();
m_text(2,14,":;<=>?@[]^_`{}~"); m_wait();
m_put(16,13,34); m_wait();
while (1) { m_wait(); }
}첫 텍스트 표시 fc_hello.c
목적: ROM을 시작하고 배경에 문자와 숫자를 그리는 기본 순서를 알아봅니다.
동작 원리: m_init 후 m_text가 인사말을 그리고 m_number가 42를 표시 형식으로 바꿉니다. 마지막 루프는 화면을 유지하며 프레임을 기다립니다.
조작 방법과 기대 결과: 화면에 HELLO WORLD와 042가 표시됩니다. 앞의 0은 십진수를 세 자리로 표시하기 위한 것입니다.

.\kitaqfc\kitaqfc.exe kitaq-docs/samples/fc_hello.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_hello.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Display a first message on screen.
// Expected: HELLO WORLD and 042
#include "fc_common.h"
// Initialize the display/font, write HELLO WORLD and a three-digit 042,
// then wait for frames so the result remains visible.
void main(void) {
m_init();
m_text(2,3,"HELLO");
m_text(2,5,"HELLO WORLD");
m_number(42);
while (1) { m_wait(); }
}더 넓은 타입의 중간값으로 연산 fc_arithmetic.c
목적: 8비트로 표현할 수 없는 중간 결과를 보관하는 방법을 알아봅니다.
동작 원리: (u16)200 + 100의 결과는 300입니다. 프로그램은 이를 10으로 나눈 뒤 u8로 변환해 표시합니다. 덧셈 전에 더 넓은 타입으로 캐스팅한다는 점에 주목하세요.
조작 방법과 기대 결과: 030은 계산 결과인 30을 나타냅니다.

.\kitaqfc\kitaqfc.exe kitaq-docs/samples/fc_arithmetic.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_arithmetic.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Variables, arithmetic and type conversion.
// Expected: 030
#include "fc_common.h"
// Widen before adding 200 + 100, divide the 16-bit sum, and display 30.
// The cast demonstrates avoiding an 8-bit intermediate result.
void main(void) {
m_init();
m_text(2,3,"ARITHMETIC");
u16 sum;
sum = (u16)200 + 100;
m_number((u8)(sum / 10));
while (1) { m_wait(); }
}분기와 반복 fc_control.c
목적: 간단한 계산에서 for, continue, while, if/else를 사용해 봅니다.
동작 원리: for 루프는 0부터 7까지 더하면서 3을 건너뛰어 25를 만듭니다. while 루프는 32까지 증가시키고, 이어지는 if는 이 값이 일치하면 42를 대입합니다.
조작 방법과 기대 결과: 042가 최종 결과입니다. 이 프로그램은 if/else 분기를 사용합니다.

.\kitaqfc\kitaqfc.exe kitaq-docs/samples/fc_control.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_control.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Conditional branches and loops.
// Expected: 042
#include "fc_common.h"
// Exercise loop control and a final branch, then display the selected value 42.
void main(void) {
m_init();
m_text(2,3,"CONTROL");
u8 i; u8 sum; sum=0;
for (i=0;i<8;i++) { if (i==3) continue; sum=(u8)(sum+i); }
while (sum<32) { sum++; }
if (sum==32) sum=42; else sum=0;
m_number(sum);
while (1) { m_wait(); }
}함수, 배열, 구조체, 포인터 fc_aggregate.c
목적: 값을 묶고 함수와 포인터로 접근하는 방법을 알아봅니다.
동작 원리: 프로그램은 (10,20)을 담은 Point를 복사한 뒤 포인터로 배열에 1부터 4까지 씁니다. 함수 결과 30에 배열 값 1, 2, 4와 상수 5를 더합니다.
조작 방법과 기대 결과: 합계가 042로 표시됩니다. 구조체 복사, ptr[i]에 쓰는 과정과 함수 반환값을 따라가 보세요.
![함수, 배열, 구조체, 포인터: 합계가 042로 표시됩니다. 구조체 복사, ptr[i]에 쓰는 과정과 함수 반환값을 따라가 보세요.](../verification/fc_aggregate.png)
.\kitaqfc\kitaqfc.exe kitaq-docs/samples/fc_aggregate.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_aggregate.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Functions, arrays, structures and pointers.
// Expected: 042
#include "fc_common.h"
typedef struct { u8 x; u8 y; } Point;
// Add two byte values and explicitly narrow the returned result to u8.
u8 add(u8 a,u8 b) { return (u8)(a+b); }
// Copy a structure, fill an array through a pointer, and combine selected values
// through add() to produce 42. Keep the final screen visible in the idle loop.
void main(void) {
m_init();
m_text(2,3,"AGGREGATE");
Point a; Point b; u8 i; u8 data[4]; u8* ptr;
a.x=10; a.y=20; b=a; ptr=data;
for(i=0;i<4;i++) ptr[i]=(u8)(i+1);
m_number((u8)(add(b.x,b.y)+ptr[0]+ptr[1]+ptr[3]+5));
while (1) { m_wait(); }
}비트 플래그와 버퍼 복사 fc_bits_memory.c
목적: 플래그를 한 바이트에 담고 버퍼를 별도 저장 공간에 복사합니다.
동작 원리: __memset으로 4바이트를 지웁니다. 비트 3을 설정하고 비트 1을 반전한 뒤 __memcpy로 4바이트 모두를 복사합니다.
조작 방법과 기대 결과: 복사 대상의 첫 바이트가 010으로 표시됩니다. 비트 3의 값 8과 비트 1의 값 2를 더한 결과입니다.

.\kitaqfc\kitaqfc.exe kitaq-docs/samples/fc_bits_memory.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_bits_memory.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Bit operations and memory copying.
// Expected: 010
#include "fc_common.h"
// Clear the source bytes, set bits 3 and 1, copy the buffer, and display 8 + 2 = 10.
void main(void) {
m_init();
m_text(2,3,"BITS MEMORY");
u8 data[4]; u8 copy[4];
__memset(data,0,4); __bit_set(data,3); __bit_toggle(data,1);
__memcpy(copy,data,4); m_number(copy[0]);
while (1) { m_wait(); }
}새로 누른 버튼 횟수 세기 fc_input.c
목적: 버튼을 새로 누른 경우와 계속 누르고 있는 경우를 구분합니다.
동작 원리: 매 프레임 input_update를 호출합니다. input_pressed(BTN_A)가 참일 때만 카운터가 증가합니다.
조작 방법과 기대 결과: 초기값은 000입니다. A를 누르면 001이 되며, 누르고 있어도 증가하지 않습니다. 버튼을 놓았다가 다시 누르면 002가 됩니다. 바이트 카운터는 255 다음에 000으로 돌아갑니다.

.\kitaqfc\kitaqfc.exe kitaqfc/lib/input.c kitaq-docs/samples/fc_input.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_input.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Distinguish a new button press from a held button.
// Expected: 000 initially; A increments once per press
#include "fc_common.h"
#include "input.h"
u8 manual_count;
// Sample buttons once per frame and increment only on a new A press.
// Holding A does not repeatedly increment; the byte-sized counter wraps after 255.
void main(void) {
m_init();
m_text(2,3,"INPUT");
input_init();
m_number(0);
while (1) { m_wait(); input_update(); if(input_pressed(BTN_A)) { manual_count++; m_number(manual_count); } }
}Q8.8 고정소수점 값 곱하기 fc_fixed.c
목적: 정수를 Q8.8로 변환하고 곱한 다음 다시 정수로 바꾸는 방법을 알아봅니다.
동작 원리: fix_from_int가 3과 4를 Q8.8로 변환합니다. fix_mul로 곱하고 fix_to_int로 결과를 다시 정수로 바꿉니다.
조작 방법과 기대 결과: 012는 3 × 4 = 12를 뜻합니다. 256배로 저장된 내부값이 아니라 변환한 정수입니다.

.\kitaqfc\kitaqfc.exe kitaqfc/lib/fixed.c kitaq-docs/samples/fc_fixed.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_fixed.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Calculate with signed Q8.8 fixed-point values.
// Expected: 012
#include "fc_common.h"
#include "fixed.h"
// Convert 3 and 4 to Q8.8, multiply them, convert the result back to an
// integer, and display 12.
void main(void) {
m_init();
m_text(2,3,"FIXED");
fix8 a; fix8 b; a=fix_from_int(3); b=fix_from_int(4);
m_number((u8)fix_to_int(fix_mul(a,b)));
while (1) { m_wait(); }
}고정 오브젝트 풀에서 할당 fc_entity.c
목적: 동적 할당 없이 고정 슬롯 집합에서 오브젝트를 생성합니다.
동작 원리: entity_init이 풀을 초기화합니다. entity_create가 (20,30)에 타입 1을 만들고, 프로그램은 실패 ID 0xFF가 아닌지 확인한 뒤 vx를 2로 설정합니다.
조작 방법과 기대 결과: 001은 활성 오브젝트 수이며 ID나 좌표가 아닙니다. 이 예제는 오브젝트를 갱신하거나 그리지 않으므로 움직이는 캐릭터는 나타나지 않습니다.

.\kitaqfc\kitaqfc.exe kitaqfc/lib/entity.c kitaq-docs/samples/fc_entity.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_entity.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Allocate an entity from a fixed-capacity object pool.
// Expected: 001
#include "fc_common.h"
#include "entity.h"
// Create one entity and check the 0xFF allocation-failure sentinel before
// accessing its velocity. Display the pool occupancy, not the entity ID.
void main(void) {
m_init();
m_text(2,3,"ENTITY");
u8 id; entity_init(); id=entity_create(1,20,30);
if(id!=0xFF) entity_get(id)->vx=2; m_number(entity_count_active());
while (1) { m_wait(); }
}RAM에 진단 값 기록 fc_debug.c
목적: 화면 표시와 별개로 이름이 붙은 게임 값을 진단 버퍼에 보관합니다.
동작 원리: 디버깅을 초기화하고 프레임 태그를 1로 설정한 뒤 HP라는 이름으로 값 42를 기록합니다.
조작 방법과 기대 결과: 001은 추적 항목 수입니다. HP=42는 진단 버퍼에 저장되며, 화면만으로는 그 내용을 보거나 검증할 수 없습니다.

.\kitaqfc\kitaqfc.exe kitaqfc/lib/debug.c kitaq-docs/samples/fc_debug.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_debug.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Record diagnostic values in the debug trace.
// Expected: 001; trace log contains HP=42
#include "fc_common.h"
#include "debug.h"
// Record HP = 42 with frame tag 1 and display the number of trace entries.
// The trace buffer contains the value; the screen displays the count, not HP.
void main(void) {
m_init();
m_text(2,3,"DEBUG");
debug_init(); debug_set_frame(1); debug_trace_u8("HP",42);
m_number(debug_get_trace_count());
while (1) { m_wait(); }
}위치 이력 저장 fc_chain.c
목적: 위치 이력을 링 버퍼에 저장하는 방법을 알아봅니다.
동작 원리: chain_init은 점 4개를 담을 수 있는 배열을 연결합니다. 그다음 chain_push_head가 (20,30)을 기록합니다.
조작 방법과 기대 결과: 042는 고정된 완료 표시이며 좌표나 개수가 아닙니다. 저장된 이력은 trail과 points를 살펴보세요. 이 프로그램은 따라오는 몸체를 그리지 않습니다.

.\kitaqfc\kitaqfc.exe kitaqfc/lib/chain.c kitaq-docs/samples/fc_chain.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_chain.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Store a position history in a ring buffer.
// Expected: 042; history contains (20,30)
#include "fc_common.h"
#include "chain.h"
Chain trail; ChainPoint points[4];
// Attach four history slots and record one point. The displayed 42 is a fixed
// completion marker; inspect the chain to verify the stored (20,30) point.
void main(void) {
m_init();
m_text(2,3,"CHAIN");
chain_init(&trail,points,4); chain_push_head(&trail,20,30);
m_number(42);
while (1) { m_wait(); }
}라이브러리 프레임 카운터 읽기 fc_system.c
목적: 프레임 단위 대기가 라이브러리 프레임 카운터에 미치는 영향을 확인합니다.
동작 원리: system_init 후 프로그램은 system_wait_vblank를 두 번 호출하고 system_get_frame8을 표시합니다.
조작 방법과 기대 결과: 결과는 002입니다. 대기 루프는 매뉴얼 보조 함수 m_wait를 사용하므로 표시 값이 계속 증가하지 않습니다.

.\kitaqfc\kitaqfc.exe kitaqfc/lib/system.c kitaq-docs/samples/fc_system.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_system.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Count completed frame waits.
// Expected: 002
#include "fc_common.h"
#include "system.h"
// Reset the library frame count, perform two counted waits, and display 2.
// Subsequent m_wait calls leave that library counter unchanged.
void main(void) {
m_init();
m_text(2,3,"SYSTEM");
system_init(); system_wait_vblank(); system_wait_vblank();
m_number(system_get_frame8());
while (1) { m_wait(); }
}현재 장면 ID 선택 fc_scene.c
목적: 타이틀 화면과 플레이 화면 등을 구분하는 ID를 선택하는 방법을 알아봅니다.
동작 원리: 0으로 초기화한 SceneDef 항목 두 개를 등록하고 scene_change로 ID 1을 선택합니다.
조작 방법과 기대 결과: 001은 scene_get_current가 반환한 현재 ID입니다. 이 예제에는 장면 콜백이나 전환 그리기가 등록되어 있지 않습니다.

.\kitaqfc\kitaqfc.exe kitaqfc/lib/runtime.c kitaqfc/lib/scene.c kitaq-docs/samples/fc_scene.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_scene.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// The scene streaming module shares runtime.c queue symbols, even though this demo only changes IDs.
#define MANUAL_FC_RUNTIME 1
// Switch the current scene ID.
// Expected: 001; no callbacks registered
#include "fc_common.h"
#include "scene.h"
SceneDef manual_scenes[2];
// Register two zero-initialized scene entries, select ID 1 and display it.
// No callbacks are registered in this example.
void main(void) {
m_init();
m_text(2,3,"SCENE");
scene_init(manual_scenes,2); scene_change(1);
m_number(scene_get_current());
while (1) { m_wait(); }
}Q5.3 소수 단위 이동 누적 fc_subpixel.c
목적: 서브픽셀 이동량은 나머지로 보관하고, 정수 픽셀만 위치에 더합니다.
동작 원리: X=40에서 시작해 루프가 2/8픽셀을 여덟 번 더합니다. 오른쪽 시프트로 정수 픽셀을 추출하고 마스크로 나머지를 유지합니다.
조작 방법과 기대 결과: 총 이동량은 2픽셀이므로 화면에 X=42가 042로 표시됩니다. 이 직접 적분 예제는 양의 방향 이동을 처리합니다. 음의 방향에는 알맞은 빌림 처리 경로가 필요합니다.

.\kitaqfc\kitaqfc.exe kitaq-docs/samples/fc_subpixel.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_subpixel.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Accumulate Q5.3 eighth-pixel motion into an integer coordinate.
// Expected: 042; explicit game-side integration using header constants
#include "fc_common.h"
#include "physics2d.h"
// Integrate eight increments of 2/8 pixel. Carry whole pixels into X and
// retain the fractional remainder with the supplied mask, moving X from 40 to 42.
void main(void) {
m_init();
m_text(2,3,"SUBPIXEL");
KQ2DPosition x; KQ2DFraction frac; KQ2DSpeed speed; u8 i;
// This example demonstrates positive motion only; signed negative motion needs a matching carry/borrow path.
x=40; frac=0; speed=2;
for(i=0;i<8;i++) {
frac=(u8)(frac+speed);
x=(u8)(x+(frac>>KQ2D_SUBPIXEL_BITS));
frac=(u8)(frac & KQ2D_SUBPIXEL_MASK);
}
m_number(x);
while (1) { m_wait(); }
}NES 배경 스크롤 fc_scroll.c
목적: PPU 스크롤 위치를 갱신해 배경을 이동합니다.
동작 원리: __scroll_set(0,0)으로 시작한 다음 매 프레임 phase를 한 번 증가시켜 가로 위치로 사용합니다.
조작 방법과 기대 결과: SCROLL이라는 글자의 가로 위치가 바뀝니다. phase는 255에서 0으로 돌아갑니다. 여러 네임테이블에 걸친 넓은 맵의 예제는 아닙니다.

.\kitaqfc\kitaqfc.exe kitaq-docs/samples/fc_scroll.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_scroll.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Scroll the NES background horizontally.
// Expected: title scrolls horizontally
#include "fc_common.h"
u8 phase;
// Advance an 8-bit horizontal scroll position once per frame, wrapping naturally
// after 255. Background motion demonstrates the changing scroll register.
void main(void) {
m_init();
m_text(2,3,"SCROLL");
__scroll_set(0,0);
while (1) { m_wait(); phase++; __scroll_set(phase,0); }
}NMI를 통한 타일 갱신 적용 fc_vram_queue.c
목적: 메인 프로그램에서 PPU 갱신을 제출하고 NMI 처리 경로에서 적용합니다.
동작 원리: __vramq_put이 네임테이블 주소 0x2103에 타일 52를 예약하고 __vramq_commit이 제출합니다. 이 주소는 타일 좌표 (3,8)에 해당합니다.
조작 방법과 기대 결과: 배경 좌표 (3,8)에 숫자 4 하나가 나타납니다. 제공된 글꼴에서 타일 52는 숫자 4입니다.

.\kitaqfc\kitaqfc.exe kitaq-docs/samples/fc_vram_queue.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_vram_queue.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Submit a VRAM update for the NMI handler.
// Expected: 4 at (3,8)
#include "fc_common.h"
// Queue tile 52 at nametable address 0x2103, corresponding to (3,8), and
// commit the queue so the NMI update path displays the digit 4.
void main(void) {
m_init();
m_text(2,3,"VRAM QUEUE");
__vramq_put(0x2103,52); __vramq_commit();
while (1) { m_wait(); }
}NES OAM 스프라이트 표시 fc_sprite.c
목적: 배경과 독립적으로 이미지를 그릴 수 있도록 스프라이트 색상과 OAM 데이터를 준비합니다.
동작 원리: 스프라이트 팔레트를 불러오고 슬롯 0에 위치 (70,80), 타일 65, 속성 0을 설정합니다. 렌더링을 켠 뒤 OAM DMA를 반복합니다.
조작 방법과 기대 결과: 정지된 문자 A가 스프라이트로 나타납니다. OAM 설정과 전송을 보여 주는 예제이며 애니메이션은 아닙니다.

.\kitaqfc\kitaqfc.exe kitaq-docs/samples/fc_sprite.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_sprite.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Display a sprite using NES OAM.
// Expected: A sprite on screen
#include "fc_common.h"
// Load the sprite palette, assign font tile 65 to OAM slot zero, enable
// background/sprite display, and transfer OAM after each frame wait.
void main(void) {
m_init();
m_text(2,3,"SPRITE");
__palette_sp_load(manual_pal); __sprite_set(0,70,80,65,0);
__ppu_mask_set(0x1E);
while (1) { m_wait(); __oam_dma(); }
}APU 펄스음 재생 fc_sound.c
목적: NES 내장 펄스 채널의 제어·타이머·길이 값을 설정합니다.
동작 원리: nes_apu_init 후 프로그램은 nes_sfx_square1(0xBF,400,20)을 호출합니다. 제어값 0xBF는 고정 음량을 선택하고 길이 카운터를 멈추므로 음이 지속됩니다. 20은 20프레임이 아니라 길이 테이블 인덱스입니다.
조작 방법과 기대 결과: 042가 표시되는 동안 펄스음이 지속됩니다. 멈추려면 nes_apu_silence_all을 호출하세요. 화면의 표시만으로는 소리 출력을 검증할 수 없습니다.

.\kitaqfc\kitaqfc.exe kitaqfc/lib/audio.c kitaq-docs/samples/fc_sound.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_sound.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Play a pulse-channel effect on the built-in NES APU.
// Expected: 042 and a pulse tone
#include "fc_common.h"
#include "audio.h"
// Initialize the APU, trigger pulse channel 1 with the chosen timer/length
// values, and leave the screen displaying 42 while the hardware plays the tone.
void main(void) {
m_init();
m_text(2,3,"SOUND");
// Control BF selects constant volume and halts the length counter, so this tone sustains.
// Call nes_apu_silence_all when the game should stop it; 20 is a length-table index, not frames.
nes_apu_init(); nes_sfx_square1(0xBF,400,20);
m_number(42);
while (1) { m_wait(); }
}컴파일러 내장 함수 사전
총 161개 항목입니다. 항목을 열면 문법, 인수, 소스 주석, 사용 예를 볼 수 있습니다. 호출 조각은 주변 코드의 초기화가 필요하며 독립적인 ROM이 아닙니다. 인수 전달 예제는 준비한 값을 API에 넘기는 방법을 보여 줄 뿐, 장치에서의 동작 검증을 뜻하지 않습니다.
audio_vblank.h — VBlank IRQ를 통한 음악 재생
4채널 음악은 NMI에서 진행됩니다. 초기화한 뒤 곡을 연결하고 큐를 미리 채우세요. 재생을 위해 NMI를 활성화하고 메인 코드에서 계속 보충하세요. NMI는 RAM 큐를 읽으므로, 보충이 제때 이루어지면 적은 RAM으로 긴 스트림을 재생할 수 있습니다. 각 채널에는 독립적인 HOLD/STOP 명령이 있습니다.
레코드는 delay, CH1, CH2, CH3, CH4의 5바이트로 구성됩니다. 지연값 1~255는 새 상태가 지속되는 NMI 틱 수입니다. 0은 재생을 종료하며 나머지 4바이트는 무시합니다. 음정값 0~71은 C2~B7에 해당합니다(24=C4). 노이즈값 0~15는 긴 모드의 주기를, 16~31은 짧은 모드를 선택합니다. 모든 채널에서 255=NES_AUDIO_HOLD는 상태를 유지하고 254=NES_AUDIO_STOP은 소리를 끕니다.
이 4채널 드라이버는 NTSC에 맞춰져 있습니다. CH1=펄스 1, CH2=펄스 2, CH3=삼각파, CH4=노이즈이며 인덱스는 0~3입니다. DMC나 확장 음원은 재생하지 않습니다. audio.c의 효과음처럼 APU에 직접 쓰는 코드와 동시에 실행하지 마세요. PAL용 음정표나 템포 보정은 없습니다.
지연이 끝났는데 다음 BGM 레코드가 없으면 BGM 음을 끄고, 데이터가 도착한 뒤 NMI에서 재개합니다. 별도로 재생 중인 SFX는 계속됩니다. 부족 횟수는 비어 있는 매 프레임이 아니라 데이터 부족 상태에 진입한 횟수입니다. HOLD 값만 삽입해도 이전 BGM 음이 복원되지는 않습니다. 평소에는 매 프레임 보충하고, 오래 걸리는 작업 전에는 충분한 재생 시간을 큐에 확보하세요.
fds.h — FDS 디스크 조작
__fds_available
컴파일 대상이 FDS인지 알려 줍니다. 예를 들어 공통 게임 코드에서 대상별 정보를 표시할 때 사용합니다.
사용 형식
u8 __fds_available(void);반환값
FDS 빌드이면 1, 아니면 0입니다. 런타임 주변기기 탐지가 아닙니다.
사용 조건과 제한
NROM 같은 카트리지 대상에서도 이 조회는 유효합니다. FDS 전용 API를 if (__fds_available()) 안에 넣어도 대상 검사가 면제되지는 않습니다. 대상별 소스 구성을 따로 마련하세요.
캡처는 KUROSAKI가 디스크 부팅 파일을 RAM에 직접 읽어 오는 방식을 사용합니다. 예제는 BIOS 루틴을 호출하지 않으며 파일 전송, 디스크 교체, 실기 부팅을 검증하지 않습니다. 빈 테스트 펌웨어는 대체 BIOS가 아닙니다. 예제는 화면 갱신에 NMI를 사용하며, 이 조회 함수에는 필요 없는 마스커블 IRQ를 비활성화합니다.
빌드 예제는 추가 승인 화면용 부팅 파일 없이 네이티브 배치의 .fds를 생성합니다. 이 설정은 KUROSAKI의 직접 부팅을 확인하기 위한 것입니다. 실기 디스크의 부팅 절차는 따로 검증해야 합니다.
사용 예
void main(void) {
// These BIOS-free queries need NMI for display, but no maskable IRQ.
__irq_disable();
fds_result[0] = __fds_available();
m_init();
m_text(2,2,"FDS BUILD SELECTION"); m_wait();
m_text(2,5,"FDS=1 / CARTRIDGE=0"); m_wait();
m_number(fds_result[0]); m_wait();
while (1) { m_wait(); }
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
FDS 빌드는 001, NROM 빌드는 000을 표시합니다. 같은 C 소스라도 컴파일 대상에 따라 다른 값을 반환하며 디스크 삽입 여부와는 관계없습니다.


빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_available.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_available.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
# Cartridge comparison
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_available.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_available.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/fds.h:7
Return the compile-time FDS mapper selection as 0 or 1; this is not a hardware probe.
kitaqfc/kitaqfc/CodeGenerator.cs:5146
if (funcName == "__fds_available")
{
if (!Need(0)) return true;
EmitAsm("LDA", Imm(isFds ? 1 : 0));
return true;
}__fds_disk_ready
디스크 유무를 즉시 확인하므로 삽입 안내 화면에서 프레임마다 한 번씩 막힘 없이 조회할 수 있습니다.
사용 형식
u8 __fds_disk_ready(void);반환값
$4032 비트 0이 0이면 1, 1이면 0을 반환합니다.
사용 조건과 제한
--mapper=fds로 빌드하세요. 카트리지 매퍼용으로 __fds_available 이외의 FDS 전용 함수를 호출하면 KQFC2420 오류로 거부됩니다.
검사는 디스크 유무를 나타내는 $4032 비트 0을 사용합니다. 비트 1의 드라이브 준비 상태, 파일 존재 여부, 로드 완료는 확인하지 않습니다. 여기서 “ready”라는 말이 전송을 시작해도 된다는 뜻은 아닙니다.
KUROSAKI는 FDS 컨트롤러를 부분적으로 모델링합니다. 이 예제에서 관측한 값은 디스크 유무 1, 보호 마스크 0, 상태 0입니다. 이 캡처로 디스크 꺼내기, 쓰기 보호된 매체, CRC 오류의 동작을 입증할 수는 없습니다.
캡처는 KUROSAKI가 디스크 부팅 파일을 RAM에 직접 읽어 오는 방식을 사용합니다. 예제는 BIOS 루틴을 호출하지 않으며 파일 전송, 디스크 교체, 실기 부팅을 검증하지 않습니다. 빈 테스트 펌웨어는 대체 BIOS가 아닙니다. 예제는 화면 갱신에 NMI를 사용하며, 이 조회 함수에는 필요 없는 마스커블 IRQ를 비활성화합니다.
빌드 예제는 추가 승인 화면용 부팅 파일 없이 네이티브 배치의 .fds를 생성합니다. 이 설정은 KUROSAKI의 직접 부팅을 확인하기 위한 것입니다. 실기 디스크의 부팅 절차는 따로 검증해야 합니다.
사용 예
void main(void) {
// These BIOS-free queries need NMI for display, but no maskable IRQ.
__irq_disable();
fds_result[0] = __fds_disk_ready();
fds_result[1] = __fds_side();
fds_result[2] = __fds_error();
fds_result[3] = 0;
// Never enter an unbounded wait without first checking disk presence.
if (fds_result[0]) {
__fds_wait_ready(); fds_result[3]++;
__fds_wait_insert(); fds_result[3]++;
}
m_init();
m_text(2,2,"FDS REGISTER STATUS"); m_wait();
m_text(2,5,"PRESENT / PROTECT / STATUS"); m_wait();
m_put(2,7,'0'+fds_result[0]);
m_put(12,7,'0'+fds_result[1]);
m_put(22,7,'0'+fds_result[2]); m_wait();
m_text(2,10,"COMPLETED PRESENCE WAITS"); m_wait();
m_put(2,12,'0'+fds_result[3]); m_wait();
m_text(2,15,"NO FILE TRANSFER TESTED"); m_wait();
while (1) { m_wait(); }
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
PRESENT / PROTECT / STATUS 아래에는 1 / 0 / 0이 표시되고 COMPLETED PRESENCE WAITS 아래에는 2가 표시됩니다. 디스크 유무를 기다리는 두 호출이 반환되었다는 뜻이며 파일 전송 성공 표시는 아닙니다. 아래쪽에는 NO FILE TRANSFER TESTED가 표시됩니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_status.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_status.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/fds.h:9
Return the inverted disk-absent bit from 4032 as 0 or 1; this checks presence, not transfer completion.
kitaqfc/kitaqfc/CodeGenerator.cs:5160
case "__fds_disk_ready":
if (!Need(0)) return true;
EmitAsm("LDA", Mem(0x4032));
EmitAsm("AND", Imm(0x01));
EmitAsm("EOR", Imm(0x01));
return true;
// Expose status bit 2 as the raw mask value 0 or 4, rather than a normalized Boolean.__fds_error
$4030의 FDS 하드웨어 상태 레지스터를 읽습니다. 마지막 디스크 작업에서 저장해 둔 오류값이 아닙니다.
사용 형식
u8 __fds_error(void);반환값
$4030의 8비트 값 전체를 반환합니다. BIOS 오류 코드를 처리할 때는 __fds_load_file() 또는 __fds_save_file()의 반환값을 보존하세요.
사용 조건과 제한
--mapper=fds로 빌드하세요. 카트리지 매퍼용으로 __fds_available 이외의 FDS 전용 함수를 호출하면 KQFC2420 오류로 거부됩니다.
KUROSAKI는 FDS 컨트롤러를 부분적으로 모델링합니다. 이 예제에서 관측한 값은 디스크 유무 1, 보호 마스크 0, 상태 0입니다. 이 캡처로 디스크 꺼내기, 쓰기 보호된 매체, CRC 오류의 동작을 입증할 수는 없습니다.
상태 읽기는 인터럽트 요청 확인 처리도 수행합니다. 다른 핸들러가 같은 상태에 의존한다면 무분별하게 조회하지 마세요. KUROSAKI는 이 읽기에서 대기 중인 FDS IRQ 플래그를 지웁니다.
캡처는 KUROSAKI가 디스크 부팅 파일을 RAM에 직접 읽어 오는 방식을 사용합니다. 예제는 BIOS 루틴을 호출하지 않으며 파일 전송, 디스크 교체, 실기 부팅을 검증하지 않습니다. 빈 테스트 펌웨어는 대체 BIOS가 아닙니다. 예제는 화면 갱신에 NMI를 사용하며, 이 조회 함수에는 필요 없는 마스커블 IRQ를 비활성화합니다.
빌드 예제는 추가 승인 화면용 부팅 파일 없이 네이티브 배치의 .fds를 생성합니다. 이 설정은 KUROSAKI의 직접 부팅을 확인하기 위한 것입니다. 실기 디스크의 부팅 절차는 따로 검증해야 합니다.
사용 예
void main(void) {
// These BIOS-free queries need NMI for display, but no maskable IRQ.
__irq_disable();
fds_result[0] = __fds_disk_ready();
fds_result[1] = __fds_side();
fds_result[2] = __fds_error();
fds_result[3] = 0;
// Never enter an unbounded wait without first checking disk presence.
if (fds_result[0]) {
__fds_wait_ready(); fds_result[3]++;
__fds_wait_insert(); fds_result[3]++;
}
m_init();
m_text(2,2,"FDS REGISTER STATUS"); m_wait();
m_text(2,5,"PRESENT / PROTECT / STATUS"); m_wait();
m_put(2,7,'0'+fds_result[0]);
m_put(12,7,'0'+fds_result[1]);
m_put(22,7,'0'+fds_result[2]); m_wait();
m_text(2,10,"COMPLETED PRESENCE WAITS"); m_wait();
m_put(2,12,'0'+fds_result[3]); m_wait();
m_text(2,15,"NO FILE TRANSFER TESTED"); m_wait();
while (1) { m_wait(); }
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
PRESENT / PROTECT / STATUS 아래에는 1 / 0 / 0이 표시되고 COMPLETED PRESENCE WAITS 아래에는 2가 표시됩니다. 디스크 유무를 기다리는 두 호출이 반환되었다는 뜻이며 파일 전송 성공 표시는 아닙니다. 아래쪽에는 NO FILE TRANSFER TESTED가 표시됩니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_status.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_status.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/fds.h:13
Read the entire 4030 status register; this is not a stored BIOS error code.
kitaqfc/kitaqfc/CodeGenerator.cs:5173
case "__fds_error":
if (!Need(0)) return true;
EmitAsm("LDA", Mem(0x4030));
return true;__fds_side
$4032에서 마스크로 추출한 쓰기 보호 비트를 반환합니다. 디스크 A면과 B면을 구분하는 함수가 아닙니다.
사용 형식
u8 __fds_side(void);반환값
$4032 & 0x04, 즉 0 또는 4를 반환합니다. 정규화된 불리언이 아닙니다. 하드웨어 비트 2는 쓰기 보호 또는 디스크가 꺼내진 상태를 나타냅니다.
사용 조건과 제한
--mapper=fds로 빌드하세요. 카트리지 매퍼용으로 __fds_available 이외의 FDS 전용 함수를 호출하면 KQFC2420 오류로 거부됩니다.
KUROSAKI는 FDS 컨트롤러를 부분적으로 모델링합니다. 이 예제에서 관측한 값은 디스크 유무 1, 보호 마스크 0, 상태 0입니다. 이 캡처로 디스크 꺼내기, 쓰기 보호된 매체, CRC 오류의 동작을 입증할 수는 없습니다.
쓰기 보호 검사에는 (__fds_side() != 0)을 사용하세요. 저장 성공 여부를 판단하려면 디스크 유무와 BIOS 작업의 반환값도 필요합니다.
캡처는 KUROSAKI가 디스크 부팅 파일을 RAM에 직접 읽어 오는 방식을 사용합니다. 예제는 BIOS 루틴을 호출하지 않으며 파일 전송, 디스크 교체, 실기 부팅을 검증하지 않습니다. 빈 테스트 펌웨어는 대체 BIOS가 아닙니다. 예제는 화면 갱신에 NMI를 사용하며, 이 조회 함수에는 필요 없는 마스커블 IRQ를 비활성화합니다.
빌드 예제는 추가 승인 화면용 부팅 파일 없이 네이티브 배치의 .fds를 생성합니다. 이 설정은 KUROSAKI의 직접 부팅을 확인하기 위한 것입니다. 실기 디스크의 부팅 절차는 따로 검증해야 합니다.
사용 예
void main(void) {
// These BIOS-free queries need NMI for display, but no maskable IRQ.
__irq_disable();
fds_result[0] = __fds_disk_ready();
fds_result[1] = __fds_side();
fds_result[2] = __fds_error();
fds_result[3] = 0;
// Never enter an unbounded wait without first checking disk presence.
if (fds_result[0]) {
__fds_wait_ready(); fds_result[3]++;
__fds_wait_insert(); fds_result[3]++;
}
m_init();
m_text(2,2,"FDS REGISTER STATUS"); m_wait();
m_text(2,5,"PRESENT / PROTECT / STATUS"); m_wait();
m_put(2,7,'0'+fds_result[0]);
m_put(12,7,'0'+fds_result[1]);
m_put(22,7,'0'+fds_result[2]); m_wait();
m_text(2,10,"COMPLETED PRESENCE WAITS"); m_wait();
m_put(2,12,'0'+fds_result[3]); m_wait();
m_text(2,15,"NO FILE TRANSFER TESTED"); m_wait();
while (1) { m_wait(); }
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
PRESENT / PROTECT / STATUS 아래에는 1 / 0 / 0이 표시되고 COMPLETED PRESENCE WAITS 아래에는 2가 표시됩니다. 디스크 유무를 기다리는 두 호출이 반환되었다는 뜻이며 파일 전송 성공 표시는 아닙니다. 아래쪽에는 NO FILE TRANSFER TESTED가 표시됩니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_status.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_status.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/fds.h:11
Return raw status mask 4032 & 04, either 0 or 4. This is not a numbered disk-side identifier.
kitaqfc/kitaqfc/CodeGenerator.cs:5167
case "__fds_side":
if (!Need(0)) return true;
EmitAsm("LDA", Mem(0x4032));
EmitAsm("AND", Imm(0x04));
return true;
// Read status register 4030 directly; the caller receives its complete byte value.__fds_wait_insert
디스크 삽입 대기를 나타내는 호출입니다. __fds_wait_ready()와 같은 디스크 유무 폴링 루프를 호출합니다.
사용 형식
void __fds_wait_insert(void);반환값
반환값은 없습니다.
사용 조건과 제한
--mapper=fds로 빌드하세요. 카트리지 매퍼용으로 __fds_available 이외의 FDS 전용 함수를 호출하면 KQFC2420 오류로 거부됩니다.
검사는 디스크 유무를 나타내는 $4032 비트 0을 사용합니다. 비트 1의 드라이브 준비 상태, 파일 존재 여부, 로드 완료는 확인하지 않습니다. 여기서 “ready”라는 말이 전송을 시작해도 된다는 뜻은 아닙니다.
타임아웃이 없습니다. 디스크가 없으면 호출이 계속 대기합니다. 게임이 화면을 갱신하거나 입력을 받아야 한다면 __fds_disk_ready()를 프레임마다 한 번 조회하세요. 예제는 두 대기 함수에 들어가기 전에 디스크 유무를 확인합니다. 이미 삽입된 디스크가 있으면 새로 꺼냈다 넣을 필요 없이 대기가 끝납니다.
KUROSAKI는 FDS 컨트롤러를 부분적으로 모델링합니다. 이 예제에서 관측한 값은 디스크 유무 1, 보호 마스크 0, 상태 0입니다. 이 캡처로 디스크 꺼내기, 쓰기 보호된 매체, CRC 오류의 동작을 입증할 수는 없습니다.
캡처는 KUROSAKI가 디스크 부팅 파일을 RAM에 직접 읽어 오는 방식을 사용합니다. 예제는 BIOS 루틴을 호출하지 않으며 파일 전송, 디스크 교체, 실기 부팅을 검증하지 않습니다. 빈 테스트 펌웨어는 대체 BIOS가 아닙니다. 예제는 화면 갱신에 NMI를 사용하며, 이 조회 함수에는 필요 없는 마스커블 IRQ를 비활성화합니다.
빌드 예제는 추가 승인 화면용 부팅 파일 없이 네이티브 배치의 .fds를 생성합니다. 이 설정은 KUROSAKI의 직접 부팅을 확인하기 위한 것입니다. 실기 디스크의 부팅 절차는 따로 검증해야 합니다.
사용 예
void main(void) {
// These BIOS-free queries need NMI for display, but no maskable IRQ.
__irq_disable();
fds_result[0] = __fds_disk_ready();
fds_result[1] = __fds_side();
fds_result[2] = __fds_error();
fds_result[3] = 0;
// Never enter an unbounded wait without first checking disk presence.
if (fds_result[0]) {
__fds_wait_ready(); fds_result[3]++;
__fds_wait_insert(); fds_result[3]++;
}
m_init();
m_text(2,2,"FDS REGISTER STATUS"); m_wait();
m_text(2,5,"PRESENT / PROTECT / STATUS"); m_wait();
m_put(2,7,'0'+fds_result[0]);
m_put(12,7,'0'+fds_result[1]);
m_put(22,7,'0'+fds_result[2]); m_wait();
m_text(2,10,"COMPLETED PRESENCE WAITS"); m_wait();
m_put(2,12,'0'+fds_result[3]); m_wait();
m_text(2,15,"NO FILE TRANSFER TESTED"); m_wait();
while (1) { m_wait(); }
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
PRESENT / PROTECT / STATUS 아래에는 1 / 0 / 0이 표시되고 COMPLETED PRESENCE WAITS 아래에는 2가 표시됩니다. 디스크 유무를 기다리는 두 호출이 반환되었다는 뜻이며 파일 전송 성공 표시는 아닙니다. 아래쪽에는 NO FILE TRANSFER TESTED가 표시됩니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_status.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_status.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/fds.h:17
Alias the same disk-presence wait as __fds_wait_ready; the call can block indefinitely.
kitaqfc/kitaqfc/CodeGenerator.cs:9148
EmitHelperStart("__fds_wait_ready");
string loop = NewGeneratedLabel("fds_wait_ready_loop");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LDA", Mem(0x4032));
EmitAsm("AND", Imm(0x01));
EmitAsm("BNE", Rel(loop));
EmitAsm("RTS");__fds_wait_ready
디스크가 있을 때까지 CPU 폴링 루프에서 기다립니다.
사용 형식
void __fds_wait_ready(void);반환값
반환값은 없습니다.
사용 조건과 제한
--mapper=fds로 빌드하세요. 카트리지 매퍼용으로 __fds_available 이외의 FDS 전용 함수를 호출하면 KQFC2420 오류로 거부됩니다.
검사는 디스크 유무를 나타내는 $4032 비트 0을 사용합니다. 비트 1의 드라이브 준비 상태, 파일 존재 여부, 로드 완료는 확인하지 않습니다. 여기서 “ready”라는 말이 전송을 시작해도 된다는 뜻은 아닙니다.
타임아웃이 없습니다. 디스크가 없으면 호출이 계속 대기합니다. 게임이 화면을 갱신하거나 입력을 받아야 한다면 __fds_disk_ready()를 프레임마다 한 번 조회하세요. 예제는 두 대기 함수에 들어가기 전에 디스크 유무를 확인합니다. 이미 삽입된 디스크가 있으면 새로 꺼냈다 넣을 필요 없이 대기가 끝납니다.
KUROSAKI는 FDS 컨트롤러를 부분적으로 모델링합니다. 이 예제에서 관측한 값은 디스크 유무 1, 보호 마스크 0, 상태 0입니다. 이 캡처로 디스크 꺼내기, 쓰기 보호된 매체, CRC 오류의 동작을 입증할 수는 없습니다.
캡처는 KUROSAKI가 디스크 부팅 파일을 RAM에 직접 읽어 오는 방식을 사용합니다. 예제는 BIOS 루틴을 호출하지 않으며 파일 전송, 디스크 교체, 실기 부팅을 검증하지 않습니다. 빈 테스트 펌웨어는 대체 BIOS가 아닙니다. 예제는 화면 갱신에 NMI를 사용하며, 이 조회 함수에는 필요 없는 마스커블 IRQ를 비활성화합니다.
빌드 예제는 추가 승인 화면용 부팅 파일 없이 네이티브 배치의 .fds를 생성합니다. 이 설정은 KUROSAKI의 직접 부팅을 확인하기 위한 것입니다. 실기 디스크의 부팅 절차는 따로 검증해야 합니다.
사용 예
void main(void) {
// These BIOS-free queries need NMI for display, but no maskable IRQ.
__irq_disable();
fds_result[0] = __fds_disk_ready();
fds_result[1] = __fds_side();
fds_result[2] = __fds_error();
fds_result[3] = 0;
// Never enter an unbounded wait without first checking disk presence.
if (fds_result[0]) {
__fds_wait_ready(); fds_result[3]++;
__fds_wait_insert(); fds_result[3]++;
}
m_init();
m_text(2,2,"FDS REGISTER STATUS"); m_wait();
m_text(2,5,"PRESENT / PROTECT / STATUS"); m_wait();
m_put(2,7,'0'+fds_result[0]);
m_put(12,7,'0'+fds_result[1]);
m_put(22,7,'0'+fds_result[2]); m_wait();
m_text(2,10,"COMPLETED PRESENCE WAITS"); m_wait();
m_put(2,12,'0'+fds_result[3]); m_wait();
m_text(2,15,"NO FILE TRANSFER TESTED"); m_wait();
while (1) { m_wait(); }
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
PRESENT / PROTECT / STATUS 아래에는 1 / 0 / 0이 표시되고 COMPLETED PRESENCE WAITS 아래에는 2가 표시됩니다. 디스크 유무를 기다리는 두 호출이 반환되었다는 뜻이며 파일 전송 성공 표시는 아닙니다. 아래쪽에는 NO FILE TRANSFER TESTED가 표시됩니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_status.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_status.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/fds.h:15
Poll disk presence until the absent bit clears. There is no timeout or cancellation path.
kitaqfc/kitaqfc/CodeGenerator.cs:9148
EmitHelperStart("__fds_wait_ready");
string loop = NewGeneratedLabel("fds_wait_ready_loop");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LDA", Mem(0x4032));
EmitAsm("AND", Imm(0x01));
EmitAsm("BNE", Rel(loop));
EmitAsm("RTS");fds_file.h — FDS 파일 로딩
__fds_file_exists
컴파일된 파일 메타데이터에 요청한 ID가 있는지 확인합니다. 크기가 0인 레코드도 존재하는 것으로 봅니다.
사용 형식
u8 __fds_file_exists(u8 id);매개변수
id조회할 파일 ID입니다. 메타데이터는 ID 0~254를 사용하며 255는 종료값으로 예약되어 있습니다. ID는 파일 번호, 파일 이름, 대상 주소와는 다릅니다.
반환값
컴파일된 테이블에 ID가 있으면 1, 없으면 0입니다. 삽입된 디스크에 해당 파일이 있다는 보장은 아닙니다.
사용 조건과 제한
--mapper=fds로 빌드하세요. 카트리지 매퍼용으로 __fds_available 이외의 FDS 전용 함수를 호출하면 KQFC2420 오류로 거부됩니다.
--fds-meta=... 또는 --fds-manifest=...에서 컴파일한 테이블과 자동 생성된 오버레이 레코드를 검색합니다. 디스크 디렉터리를 읽는 것은 아닙니다. 디스크에 파일이 들어 있어도 테이블 레코드가 없으면 찾지 못합니다. 수동 및 자동 생성 레코드를 합쳐 최대 42개입니다.
크기는 메타데이터에 선언된 값입니다. 원본 파일에서 추론한 길이, 잘라낸 오버레이의 실제 데이터 길이, 교체한 디스크의 내용과 같다는 보장은 없습니다. 이 조회값으로 데이터 접근 범위를 제한하려면 정확한 크기를 선언하세요.
fds_metadata.json과 두 .bin 파일을 C 소스와 같은 위치에 두세요. ID 16에는 0~255를 두 번 반복하고 마지막에 0을 붙인 독자적인 513바이트 수열이 들어 있습니다. ID 17은 빈 데이터입니다. 둘 다 부팅용이 아닌 PRG 파일(형식 0)이며 이 예제에서는 로드하지 않습니다. 상대 소스 경로는 매니페스트 디렉터리를 기준으로 해석됩니다.
캡처는 KUROSAKI가 디스크 부팅 파일을 RAM에 직접 읽어 오는 방식을 사용합니다. 예제는 BIOS 루틴을 호출하지 않으며 파일 전송, 디스크 교체, 실기 부팅을 검증하지 않습니다. 빈 테스트 펌웨어는 대체 BIOS가 아닙니다. 예제는 화면 갱신에 NMI를 사용하며, 이 조회 함수에는 필요 없는 마스커블 IRQ를 비활성화합니다.
빌드 예제는 추가 승인 화면용 부팅 파일 없이 네이티브 배치의 .fds를 생성합니다. 이 설정은 KUROSAKI의 직접 부팅을 확인하기 위한 것입니다. 실기 디스크의 부팅 절차는 따로 검증해야 합니다.
사용 예
void main(void) {
// These BIOS-free queries need NMI for display, but no maskable IRQ.
__irq_disable();
fds_result[0] = __fds_file_exists(16);
fds_sizes[0] = __fds_file_size(16);
fds_result[1] = __fds_file_exists(17);
fds_sizes[1] = __fds_file_size(17);
fds_result[2] = __fds_file_exists(18);
fds_sizes[2] = __fds_file_size(18);
m_init();
m_text(2,2,"FDS COMPILED FILE TABLE"); m_wait();
m_text(2,5,"ID EXISTS BYTES"); m_wait();
m_text(2,7,"16 1 513"); m_wait();
m_text(2,9,"17 1 0"); m_wait();
m_text(2,11,"18 0 0"); m_wait();
if (fds_result[0]==1 && fds_sizes[0]==513 &&
fds_result[1]==1 && fds_sizes[1]==0 &&
fds_result[2]==0 && fds_sizes[2]==0) { m_text(2,14,"TABLE VALUES MATCH: PASS"); }
else { m_text(2,14,"TABLE VALUES MATCH: FAIL"); }
m_wait();
m_text(2,17,"NO DISK DIRECTORY SCAN"); m_wait();
while (1) { m_wait(); }
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
표는 ID 16이 존재하며 513바이트, ID 17이 존재하며 0바이트, ID 18이 없으며 0바이트임을 보여 줍니다. TABLE VALUES MATCH: PASS는 조회값을 이 조합과 비교한 결과입니다. 크기 0만으로는 빈 레코드와 없는 ID를 구분할 수 없습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_metadata.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_metadata.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm --fds-meta=.\kitaq-docs\samples\api-examples\fc\fds_metadata.json구현과 소스
kitaqfc/lib/fds_file.h:18
Query the compiled --fds-meta/--fds-manifest table, not the inserted disk; return 0 or 1.
kitaqfc/kitaqfc/CodeGenerator.cs:9481
EmitHelperStart("__fds_file_exists");
EmitFdsMetadataSearch(foundReturnsSize: false);
// Return the compiled metadata size, using zero for an unmatched ID.__fds_file_size
컴파일된 메타데이터에서 해당 ID에 선언된 바이트 크기를 반환합니다. 예를 들어 에셋의 예정 로드 크기를 확인할 때 사용합니다.
사용 형식
u16 __fds_file_size(u8 id);매개변수
id조회할 파일 ID입니다. 메타데이터는 ID 0~254를 사용하며 255는 종료값으로 예약되어 있습니다. ID는 파일 번호, 파일 이름, 대상 주소와는 다릅니다.
반환값
선언된 부호 없는 16비트 크기(0~65535)입니다. 없는 ID도 0을 반환하므로 구분이 필요하면 __fds_file_exists()를 사용하세요.
사용 조건과 제한
--mapper=fds로 빌드하세요. 카트리지 매퍼용으로 __fds_available 이외의 FDS 전용 함수를 호출하면 KQFC2420 오류로 거부됩니다.
--fds-meta=... 또는 --fds-manifest=...에서 컴파일한 테이블과 자동 생성된 오버레이 레코드를 검색합니다. 디스크 디렉터리를 읽는 것은 아닙니다. 디스크에 파일이 들어 있어도 테이블 레코드가 없으면 찾지 못합니다. 수동 및 자동 생성 레코드를 합쳐 최대 42개입니다.
크기는 메타데이터에 선언된 값입니다. 원본 파일에서 추론한 길이, 잘라낸 오버레이의 실제 데이터 길이, 교체한 디스크의 내용과 같다는 보장은 없습니다. 이 조회값으로 데이터 접근 범위를 제한하려면 정확한 크기를 선언하세요.
fds_metadata.json과 두 .bin 파일을 C 소스와 같은 위치에 두세요. ID 16에는 0~255를 두 번 반복하고 마지막에 0을 붙인 독자적인 513바이트 수열이 들어 있습니다. ID 17은 빈 데이터입니다. 둘 다 부팅용이 아닌 PRG 파일(형식 0)이며 이 예제에서는 로드하지 않습니다. 상대 소스 경로는 매니페스트 디렉터리를 기준으로 해석됩니다.
캡처는 KUROSAKI가 디스크 부팅 파일을 RAM에 직접 읽어 오는 방식을 사용합니다. 예제는 BIOS 루틴을 호출하지 않으며 파일 전송, 디스크 교체, 실기 부팅을 검증하지 않습니다. 빈 테스트 펌웨어는 대체 BIOS가 아닙니다. 예제는 화면 갱신에 NMI를 사용하며, 이 조회 함수에는 필요 없는 마스커블 IRQ를 비활성화합니다.
빌드 예제는 추가 승인 화면용 부팅 파일 없이 네이티브 배치의 .fds를 생성합니다. 이 설정은 KUROSAKI의 직접 부팅을 확인하기 위한 것입니다. 실기 디스크의 부팅 절차는 따로 검증해야 합니다.
사용 예
void main(void) {
// These BIOS-free queries need NMI for display, but no maskable IRQ.
__irq_disable();
fds_result[0] = __fds_file_exists(16);
fds_sizes[0] = __fds_file_size(16);
fds_result[1] = __fds_file_exists(17);
fds_sizes[1] = __fds_file_size(17);
fds_result[2] = __fds_file_exists(18);
fds_sizes[2] = __fds_file_size(18);
m_init();
m_text(2,2,"FDS COMPILED FILE TABLE"); m_wait();
m_text(2,5,"ID EXISTS BYTES"); m_wait();
m_text(2,7,"16 1 513"); m_wait();
m_text(2,9,"17 1 0"); m_wait();
m_text(2,11,"18 0 0"); m_wait();
if (fds_result[0]==1 && fds_sizes[0]==513 &&
fds_result[1]==1 && fds_sizes[1]==0 &&
fds_result[2]==0 && fds_sizes[2]==0) { m_text(2,14,"TABLE VALUES MATCH: PASS"); }
else { m_text(2,14,"TABLE VALUES MATCH: FAIL"); }
m_wait();
m_text(2,17,"NO DISK DIRECTORY SCAN"); m_wait();
while (1) { m_wait(); }
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
표는 ID 16이 존재하며 513바이트, ID 17이 존재하며 0바이트, ID 18이 없으며 0바이트임을 보여 줍니다. TABLE VALUES MATCH: PASS는 조회값을 이 조합과 비교한 결과입니다. 크기 0만으로는 빈 레코드와 없는 ID를 구분할 수 없습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_metadata.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_metadata.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm --fds-meta=.\kitaq-docs\samples\api-examples\fc\fds_metadata.json구현과 소스
kitaqfc/lib/fds_file.h:20
Return the compiled metadata size, or zero if id is absent. No disk directory is read.
kitaqfc/kitaqfc/CodeGenerator.cs:9485
EmitHelperStart("__fds_file_size");
EmitFdsMetadataSearch(foundReturnsSize: true);__fds_load_file
BIOS를 통해 파일을 읽고 PRG 데이터를 CPU 버퍼에 복사합니다. 저장 데이터나 레벨 에셋을 게임이 예약한 메모리로 가져올 때 사용하세요.
사용 형식
u8 __fds_load_file(u8 id, u8* dst);매개변수
id생성된 디스크에 포함된 부팅용이 아닌 파일의 ID(0~254)이며 예제는 73을 사용합니다. 파일이 물리적으로 놓인 위치와는 다릅니다. ID 255, 존재하지 않는 ID, 부팅 파일은 거부됩니다.
dstPRG 파일의 대상 주소이며 패키징된 파일 전체 길이를 담을 공간이 필요합니다. 0을 전달하면 복사하지 않고 헤더에 지정된 대상에 데이터를 남깁니다. CHR·네임테이블 PPU 파일은 0만 허용하며 BIOS가 원래의 PPU 대상에 전송합니다.
반환값
성공하면 0입니다. 0이 아닌 BIOS 오류는 그대로 반환합니다. BIOS가 성공을 보고했더라도 정확히 한 파일을 읽지 않았다면 64($40)를 반환합니다. 잘못된 ID, 버퍼, 파일 형식과 대상의 조합은 255($FF)를 반환합니다.
사용 조건과 제한
--mapper=fds --fds-layout=fds32와 함께 사용하세요. --fds-meta 매니페스트가 파일 ID, 로드 주소, 형식, 부팅 여부, 순서를 정의합니다. 전송 주소와 크기는 빌드로 생성한 디스크 이미지에서 가져옵니다. 이 API는 임의로 삽입한 디스크를 분석하지 않습니다.
BIOS는 먼저 디스크 헤더에 저장된 주소로 읽고, 그 뒤 도우미가 dst로 복사합니다. 겹치는 데이터가 보존되도록 복사 방향을 선택하지만 준비 영역도 따로 예약해야 합니다. 전송에 실패하면 dst로 복사하지 않습니다. BIOS 호출 후에는 현재 뱅크 기록을 255(알 수 없음)로 표시합니다.
각 CPU 버퍼는 $0200–$07FF 또는 $6000–$DFFF 중 한 범위 안에 완전히 들어가야 합니다. 주소가 순환하는 범위와 I/O 레지스터 대상은 거부됩니다. 이 조건 외에도 호출자는 버퍼가 코드, OAM, 다른 변수, 컴파일러 임시 영역과 겹치지 않도록 해야 합니다. 예제는 공통 코드에 뱅크 0, 준비 영역에 $8000, 4바이트 버퍼에 $8500과 $8600을 사용합니다.
동기식 BIOS 호출입니다. 전송이 실행 중인 코드나 복귀 주소를 덮어쓰지 않도록 공통 코드에서 호출하고 인터럽트 핸들러에서는 호출하지 마세요. 디스크 I/O 중에는 렌더링과 NMI를 멈추고 PPU 주소 증가량을 1로 설정하며 디스크 이외의 IRQ 발생원을 비활성화하는 등 BIOS의 전제 조건을 지키세요. 전송 중 게임이나 음악이 계속된다는 보장은 없습니다.
캡처에서는 독자적으로 만든 BIOS 호출 규약 검증 코드를 사용하여 컴파일된 CPU 코드, 버퍼 내용, 저장 후 다시 읽는 동작, 표시된 모든 픽셀을 확인합니다. 실제 BIOS, 물리 디스크 쓰기, CRC, 전원 차단 후 복구를 검증하는 것은 아닙니다. KUROSAKI에서 일반 FDS 프로그램을 실행하는 것만으로 동일한 저장 동작을 입증할 수는 없습니다.
같은 폴더의 fds_file_io_manifest.json과 fds_file_io_payload.bin을 예제와 함께 사용하세요. 아래 검사 도구는 독자적으로 작성한 소스에서 임시 검증 코드를 생성하고 끝나면 제거합니다. 상용 BIOS는 포함하지 않으며 이 검증 코드는 일반 게임용 BIOS가 아닙니다.
사용 예
#pragma fixed_bank 0
#include "fc_fds_scene.h"
void main(void) {
u8 i;
u8* data=(u8*)0x8500;
u8* again=(u8*)0x8600;
__irq_disable();__ppu_ctrl_set(0);__ppu_mask_set(0);
// Keep rendering/NMI and non-disk IRQ sources disabled during disk I/O.
*((u8*)0xFF)=0;*((u8*)0x4010)=0;*((u8*)0x4017)=0x40;*((u8*)0x4022)=0;
fds_result[0]=__fds_load_file(73,data);
fds_result[1]=data[0];fds_result[2]=data[3];
for(i=0;i<4;i=i+1){data[i]=(i+1)*7;}
fds_result[3]=__fds_save_file(73,data,4);
fds_result[4]=__fds_load_file(73,again);
fds_result[5]=again[0];fds_result[6]=again[3];
// Check every returned byte, not only the displayed endpoints.
for(i=0;i<4;i=i+1){if(again[i]!=(i+1)*7){fds_result[7]=1;}}
fds_result[15]=165;
m_init();m_text(2,2,"FDS LOAD / SAVE BUFFER");m_wait();
m_text(2,5,"LOAD TO 8500 STATUS");fds_number(25,5,fds_result[0]);m_wait();
m_text(2,7,"INITIAL FIRST/LAST");fds_number(21,7,fds_result[1]);fds_number(25,7,fds_result[2]);m_wait();
m_text(2,9,"SAVE FILE 73 STATUS");fds_number(25,9,fds_result[3]);m_wait();
m_text(2,11,"RELOAD TO 8600 STATUS");fds_number(25,11,fds_result[4]);m_wait();
m_text(2,13,"SAVED FIRST / LAST");fds_number(21,13,fds_result[5]);fds_number(25,13,fds_result[6]);m_wait();
m_text(2,15,"BYTE CHECK ERRORS");fds_number(25,15,fds_result[7]);m_wait();
m_text(2,18,"STAGING 8000 / SIZE 4");m_wait();
while(1){m_wait();}
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
LOAD, SAVE, RELOAD STATUS와 BYTE CHECK ERRORS는 모두 000입니다. INITIAL FIRST/LAST의 011과 044는 $8500으로 읽은 4바이트의 첫 값과 끝 값입니다. 예제는 이를 7, 14, 21, 28로 바꿔 저장한 뒤 $8600으로 다시 읽으므로 SAVED FIRST / LAST는 007과 028입니다. STAGING 8000 / SIZE 4는 디스크에 기록된 로드 주소와 파일 길이를 나타냅니다. 별도의 검증에서는 ID 73이 실제 네 번째 파일, 즉 0부터 세는 위치 3에 저장되는지도 확인합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_file_io.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_file_io.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --fds-meta=.\kitaq-docs\samples\api-examples\fc\fds_file_io_manifest.json --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
python .\kitaq-docs\tools\check_fds_file_examples.py구현과 소스
kitaqfc/lib/fds_file.h:11
Native FDS32: load a non-boot file at its disk-header address, then memmove PRG bytes to dst. Null dst keeps the native destination (also for PPU files). Both CPU spans must fit $0200-$07FF or $6000-$DFFF and be owned by the caller. Return 0, a BIOS error, $40 for a missing transfer, or $FF for invalid input. Call from common code with disk-I/O interrupt/display prerequisites satisfied.
kitaqfc/kitaqfc/CodeGenerator.cs:9223
EmitHelperStart("__fds_load_file");
// Load at the on-disk staging address, then memmove PRG data to non-null dst.
EmitAsm("JSR", Abs("__kq_fds_find_io"));
EmitAsm("CMP", Imm(0));
EmitAsm("BNE", Rel("__kq_fio_load_invalid"));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 19));
EmitAsm("BMI", Rel("__kq_fio_load_invalid"));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 14));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 15));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 23));
EmitAsm("STA", Mem(CallArgBase + 10));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 24));
EmitAsm("STA", Mem(CallArgBase + 11));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 20));
EmitAsm("BNE", Rel("__kq_fio_load_ppu"));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 21));
EmitAsm("STA", Mem(CallArgBase + 8));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 22));
EmitAsm("STA", Mem(CallArgBase + 9));
EmitAsm("JSR", Abs("__kq_fds_check_span"));
EmitAsm("CMP", Imm(0));
EmitAsm("BNE", Rel("__kq_fio_load_invalid"));
EmitAsm("JMP", Abs("__kq_fio_load_dst"));
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_load_ppu"));
// CHR/nametable files have no CPU buffer; null dst selects their native PPU destination.
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 14));
EmitAsm("ORA", Mem(_runtimeFdsFileHeaderAddress + 15));
EmitAsm("BNE", Rel("__kq_fio_load_invalid"));
EmitAsm("JMP", Abs("__kq_fio_load_bios"));
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_load_dst"));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 14));
EmitAsm("ORA", Mem(_runtimeFdsFileHeaderAddress + 15));
EmitAsm("BEQ", Rel("__kq_fio_load_bios"));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 14));
EmitAsm("STA", Mem(CallArgBase + 8));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 15));
EmitAsm("STA", Mem(CallArgBase + 9));
EmitAsm("JSR", Abs("__kq_fds_check_span"));
EmitAsm("CMP", Imm(0));
EmitAsm("BNE", Rel("__kq_fio_load_invalid"));
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_load_bios"));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 17));
EmitAsm("STA", Mem(_runtimeFdsLoadListAddress));
EmitAsm("LDA", Imm(0xFF));
EmitAsm("STA", Mem(_runtimeFdsLoadListAddress + 1));
EmitAsm("JSR", Mem(0xE1F8));
_assembly.Add(Expr.Make(Tag.Word, string.Format("${0:X4}", _runtimeFdsFileHeaderAddress + 33)));
_assembly.Add(Expr.Make(Tag.Word, string.Format("${0:X4}", _runtimeFdsLoadListAddress)));
EmitAsm("TAX");
EmitAsm("LDA", Imm(0xFF));
EmitAsm("STA", Mem(_runtimeFdsResidentBankAddress));
EmitAsm("STA", Mem(_runtimeCurrentBankAddress));
EmitAsm("TXA");
EmitAsm("BNE", Rel("__kq_fio_load_return"));
EmitAsm("CPY", Imm(1));
EmitAsm("BEQ", Rel("__kq_fio_load_count_ok"));
EmitAsm("LDA", Imm(0x40));
EmitAsm("RTS");
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_load_count_ok"));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 14));
EmitAsm("ORA", Mem(_runtimeFdsFileHeaderAddress + 15));
EmitAsm("BEQ", Rel("__kq_fio_load_ok"));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 21));
EmitAsm("STA", Mem(CallArgBase + 0));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 14));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 23));
EmitAsm("STA", Mem(CallArgBase + 4));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 22));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 15));
EmitAsm("STA", Mem(CallArgBase + 3));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 24));
EmitAsm("STA", Mem(CallArgBase + 5));
EmitAsm("LDA", Mem(CallArgBase + 4));
EmitAsm("ORA", Mem(CallArgBase + 5));
EmitAsm("BEQ", Rel("__kq_fio_load_ok"));
// Copy backwards when destination starts above source, including overlapping buffers.
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("CMP", Mem(CallArgBase + 1));
EmitAsm("BCC", Rel("__kq_fio_forward"));
EmitAsm("BNE", Rel("__kq_fio_back_setup"));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("CMP", Mem(CallArgBase + 0));
EmitAsm("BCC", Rel("__kq_fio_forward"));
EmitAsm("BEQ", Rel("__kq_fio_load_ok"));
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_back_setup"));
EmitAsm("CLC");
EmitAsm("LDA", Mem(CallArgBase + 0));
EmitAsm("ADC", Mem(CallArgBase + 4));
EmitAsm("STA", Mem(CallArgBase + 0));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("ADC", Mem(CallArgBase + 5));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("CLC");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("ADC", Mem(CallArgBase + 4));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("ADC", Mem(CallArgBase + 5));
EmitAsm("STA", Mem(CallArgBase + 3));
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_backward"));
EmitAsm("LDA", Mem(CallArgBase + 0));
EmitAsm("BNE", Rel("__kq_fio_back_dec0"));
EmitAsm("DEC", Mem(CallArgBase + 1));
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_back_dec0"));
EmitAsm("DEC", Mem(CallArgBase + 0));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("BNE", Rel("__kq_fio_back_dec2"));
EmitAsm("DEC", Mem(CallArgBase + 3));
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_back_dec2"));
EmitAsm("DEC", Mem(CallArgBase + 2));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", IndY(CallArgBase));
EmitAsm("STA", IndY(CallArgBase + 2));
EmitAsm("JSR", Abs("__kq_fds_copy_count"));
EmitAsm("BNE", Rel("__kq_fio_backward"));
EmitAsm("JMP", Abs("__kq_fio_load_ok"));
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_forward"));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", IndY(CallArgBase));
EmitAsm("STA", IndY(CallArgBase + 2));
EmitAsm("INC", Mem(CallArgBase + 0));
EmitAsm("BNE", Rel("__kq_fio_forward_inc0"));
EmitAsm("INC", Mem(CallArgBase + 1));
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_forward_inc0"));
EmitAsm("INC", Mem(CallArgBase + 2));
EmitAsm("BNE", Rel("__kq_fio_forward_inc2"));
EmitAsm("INC", Mem(CallArgBase + 3));
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_forward_inc2"));
EmitAsm("JSR", Abs("__kq_fds_copy_count"));
EmitAsm("BNE", Rel("__kq_fio_forward"));
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_load_ok"));
EmitAsm("LDA", Imm(0));
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_load_return"));
EmitAsm("RTS");
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_load_invalid"));
EmitAsm("LDA", Imm(0xFF));
EmitAsm("RTS");__fds_save_file
디스크 면 끝에 예약된 고정 크기의 PRG 저장 슬롯을 CPU 버퍼의 바이트로 덮어씁니다. 파일 ID, 이름, 이후의 로드 주소는 유지됩니다.
사용 형식
u8 __fds_save_file(u8 id, const u8* src, u16 len);매개변수
id생성된 디스크에 포함된 부팅용이 아닌 파일의 ID(0~254)이며 예제는 73을 사용합니다. 파일이 물리적으로 놓인 위치와는 다릅니다. ID 255, 존재하지 않는 ID, 부팅 파일은 거부됩니다.
srcCPU RAM에 있는 데이터의 시작 주소이며 len바이트를 읽을 수 있어야 합니다. 이 API는 VRAM을 직접 저장하지 않습니다.
len저장할 바이트 수이며 패키징된 파일 길이와 같아야 합니다. 게임 데이터가 더 적다면 호출 전에 저장 버퍼의 미사용 부분도 채우세요.
반환값
성공하면 0, 그 외에는 쓰기 방지나 쓰기 검증 실패 등의 BIOS 오류를 반환합니다. 잘못된 ID, 부팅 파일 또는 PRG가 아닌 파일, 해당 면의 마지막이 아닌 파일, 길이 불일치, 잘못된 CPU 주소 범위이면 BIOS를 호출하지 않고 255($FF)를 반환합니다.
사용 조건과 제한
--mapper=fds --fds-layout=fds32와 함께 사용하세요. --fds-meta 매니페스트가 파일 ID, 로드 주소, 형식, 부팅 여부, 순서를 정의합니다. 전송 주소와 크기는 빌드로 생성한 디스크 이미지에서 가져옵니다. 이 API는 임의로 삽입한 디스크를 분석하지 않습니다.
BIOS WriteFile은 쓰기 위치 뒤의 파일을 논리적으로 숨깁니다. 따라서 이 API는 각 면의 마지막 파일인, 부팅용이 아닌 PRG 파일만 허용합니다. 새 파일을 추가하거나 슬롯 크기를 바꾸지 않습니다. 생성된 부팅 도우미와 자동 오버레이까지 포함한 최종 배치에서도 저장 슬롯이 마지막이어야 합니다.
컴파일러는 파일 ID, 헤더 번호, 물리적 순서를 구분합니다. 예제의 ID는 73, 매니페스트 번호는 200이지만 BIOS에는 위치 3을 전달합니다. 디스크 번호와 면 번호도 확정된 배치에서 가져옵니다. 매니페스트와 별도로 배치를 변경한 디스크에는 사용하지 마세요.
각 CPU 버퍼는 $0200–$07FF 또는 $6000–$DFFF 중 한 범위 안에 완전히 들어가야 합니다. 주소가 순환하는 범위와 I/O 레지스터 대상은 거부됩니다. 이 조건 외에도 호출자는 버퍼가 코드, OAM, 다른 변수, 컴파일러 임시 영역과 겹치지 않도록 해야 합니다. 예제는 공통 코드에 뱅크 0, 준비 영역에 $8000, 4바이트 버퍼에 $8500과 $8600을 사용합니다.
동기식 BIOS 호출입니다. 전송이 실행 중인 코드나 복귀 주소를 덮어쓰지 않도록 공통 코드에서 호출하고 인터럽트 핸들러에서는 호출하지 마세요. 디스크 I/O 중에는 렌더링과 NMI를 멈추고 PPU 주소 증가량을 1로 설정하며 디스크 이외의 IRQ 발생원을 비활성화하는 등 BIOS의 전제 조건을 지키세요. 전송 중 게임이나 음악이 계속된다는 보장은 없습니다.
캡처에서는 독자적으로 만든 BIOS 호출 규약 검증 코드를 사용하여 컴파일된 CPU 코드, 버퍼 내용, 저장 후 다시 읽는 동작, 표시된 모든 픽셀을 확인합니다. 실제 BIOS, 물리 디스크 쓰기, CRC, 전원 차단 후 복구를 검증하는 것은 아닙니다. KUROSAKI에서 일반 FDS 프로그램을 실행하는 것만으로 동일한 저장 동작을 입증할 수는 없습니다.
같은 폴더의 fds_file_io_manifest.json과 fds_file_io_payload.bin을 예제와 함께 사용하세요. 아래 검사 도구는 독자적으로 작성한 소스에서 임시 검증 코드를 생성하고 끝나면 제거합니다. 상용 BIOS는 포함하지 않으며 이 검증 코드는 일반 게임용 BIOS가 아닙니다.
사용 예
#pragma fixed_bank 0
#include "fc_fds_scene.h"
void main(void) {
u8 i;
u8* data=(u8*)0x8500;
u8* again=(u8*)0x8600;
__irq_disable();__ppu_ctrl_set(0);__ppu_mask_set(0);
// Keep rendering/NMI and non-disk IRQ sources disabled during disk I/O.
*((u8*)0xFF)=0;*((u8*)0x4010)=0;*((u8*)0x4017)=0x40;*((u8*)0x4022)=0;
fds_result[0]=__fds_load_file(73,data);
fds_result[1]=data[0];fds_result[2]=data[3];
for(i=0;i<4;i=i+1){data[i]=(i+1)*7;}
fds_result[3]=__fds_save_file(73,data,4);
fds_result[4]=__fds_load_file(73,again);
fds_result[5]=again[0];fds_result[6]=again[3];
// Check every returned byte, not only the displayed endpoints.
for(i=0;i<4;i=i+1){if(again[i]!=(i+1)*7){fds_result[7]=1;}}
fds_result[15]=165;
m_init();m_text(2,2,"FDS LOAD / SAVE BUFFER");m_wait();
m_text(2,5,"LOAD TO 8500 STATUS");fds_number(25,5,fds_result[0]);m_wait();
m_text(2,7,"INITIAL FIRST/LAST");fds_number(21,7,fds_result[1]);fds_number(25,7,fds_result[2]);m_wait();
m_text(2,9,"SAVE FILE 73 STATUS");fds_number(25,9,fds_result[3]);m_wait();
m_text(2,11,"RELOAD TO 8600 STATUS");fds_number(25,11,fds_result[4]);m_wait();
m_text(2,13,"SAVED FIRST / LAST");fds_number(21,13,fds_result[5]);fds_number(25,13,fds_result[6]);m_wait();
m_text(2,15,"BYTE CHECK ERRORS");fds_number(25,15,fds_result[7]);m_wait();
m_text(2,18,"STAGING 8000 / SIZE 4");m_wait();
while(1){m_wait();}
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
LOAD, SAVE, RELOAD STATUS와 BYTE CHECK ERRORS는 모두 000입니다. INITIAL FIRST/LAST의 011과 044는 $8500으로 읽은 4바이트의 첫 값과 끝 값입니다. 예제는 이를 7, 14, 21, 28로 바꿔 저장한 뒤 $8600으로 다시 읽으므로 SAVED FIRST / LAST는 007과 028입니다. STAGING 8000 / SIZE 4는 디스크에 기록된 로드 주소와 파일 길이를 나타냅니다. 별도의 검증에서는 ID 73이 실제 네 번째 파일, 즉 0부터 세는 위치 3에 저장되는지도 확인합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_file_io.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_file_io.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --fds-meta=.\kitaq-docs\samples\api-examples\fc\fds_file_io_manifest.json --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
python .\kitaq-docs\tools\check_fds_file_examples.py구현과 소스
kitaqfc/lib/fds_file.h:16
Overwrite the final non-boot PRG slot on its side, preserving its ID/name/load address. len must equal its packaged size; src must be a valid CPU RAM span. The resolved physical ordinal selects the slot, independently of file ID. Return 0, a BIOS error, or $FF before any write for an invalid slot/span/size.
kitaqfc/kitaqfc/CodeGenerator.cs:9488
EmitHelperStart("__fds_save_file");
// Only a non-boot, final, fixed-size PRG slot can be overwritten without hiding later files.
EmitAsm("JSR", Abs("__kq_fds_find_io"));
EmitAsm("CMP", Imm(0));
EmitAsm("BNE", Rel("__kq_fio_save_invalid"));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 19));
EmitAsm("AND", Imm(0xC0));
EmitAsm("CMP", Imm(0x40));
EmitAsm("BNE", Rel("__kq_fio_save_invalid"));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 20));
EmitAsm("BNE", Rel("__kq_fio_save_invalid"));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("CMP", Mem(_runtimeFdsFileHeaderAddress + 23));
EmitAsm("BNE", Rel("__kq_fio_save_invalid"));
EmitAsm("STA", Mem(CallArgBase + 10));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(CallArgBase + 8));
EmitAsm("LDA", Mem(CallArgBase + 4));
EmitAsm("CMP", Mem(_runtimeFdsFileHeaderAddress + 24));
EmitAsm("BNE", Rel("__kq_fio_save_invalid"));
EmitAsm("STA", Mem(CallArgBase + 11));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", Mem(CallArgBase + 9));
EmitAsm("JSR", Abs("__kq_fds_check_span"));
EmitAsm("CMP", Imm(0));
EmitAsm("BNE", Rel("__kq_fio_save_invalid"));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 17));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 0));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 25));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 1));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 26));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 2));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 27));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 3));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 28));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 4));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 29));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 5));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 30));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 6));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 31));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 7));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 32));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 8));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 21));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 9));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 11));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 14));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 22));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 10));
EmitAsm("LDA", Mem(CallArgBase + 4));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 12));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 15));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 13));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 16));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 18));
EmitAsm("JSR", Mem(0xE239));
_assembly.Add(Expr.Make(Tag.Word, string.Format("${0:X4}", _runtimeFdsFileHeaderAddress + 33)));
_assembly.Add(Expr.Make(Tag.Word, string.Format("${0:X4}", _runtimeFdsFileHeaderAddress)));
EmitAsm("RTS");
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_save_invalid"));
EmitAsm("LDA", Imm(0xFF));
EmitAsm("RTS");
// Enable wave-RAM writes, copy 64 source bytes, then clear the wave-write control register.fds_overlay.h — FDS 오버레이 코드
__fds_current_bank
디스크를 읽지 않고 상주 상태로 기록된 논리 뱅크를 반환합니다.
사용 형식
u8 __fds_current_bank(void);반환값
8비트 소프트웨어 상주 기록입니다. 표준 시작 직후에는 1, 알 수 없을 때는 255입니다.
사용 조건과 제한
--mapper=fds로 빌드하세요. 카트리지 매퍼용으로 __fds_available 이외의 FDS 전용 함수를 호출하면 KQFC2420 오류로 거부됩니다.
표준 FDS 시작 코드는 상주 기록을 1로 초기화합니다. __fds_load_bank()는 로드 성공 후 이를 갱신합니다. __fds_load_file() 또는 __fds_load_overlay()를 통한 직접 로드와 실패한 뱅크 전송은 기록을 255(알 수 없음)로 설정합니다. 메모리 내용을 검사하지 않으므로 창을 직접 수정한 뒤에는 이 기록에 의존하지 마세요. 사용자 정의 리셋 루틴은 초기화를 직접 관리해야 합니다.
캡처는 KUROSAKI가 디스크 부팅 파일을 RAM에 직접 읽어 오는 방식을 사용합니다. 예제는 BIOS 루틴을 호출하지 않으며 파일 전송, 디스크 교체, 실기 부팅을 검증하지 않습니다. 빈 테스트 펌웨어는 대체 BIOS가 아닙니다. 예제는 화면 갱신에 NMI를 사용하며, 이 조회 함수에는 필요 없는 마스커블 IRQ를 비활성화합니다.
빌드 예제는 추가 승인 화면용 부팅 파일 없이 네이티브 배치의 .fds를 생성합니다. 이 설정은 KUROSAKI의 직접 부팅을 확인하기 위한 것입니다. 실기 디스크의 부팅 절차는 따로 검증해야 합니다.
사용 예
void main(void) {
// These BIOS-free queries need NMI for display, but no maskable IRQ.
__irq_disable();
request_overlay=0;
fds_result[0] = __fds_current_bank();
fds_result[1] = __fds_is_bank_resident(1);
fds_result[2] = __fds_is_bank_resident(2);
fds_result[3] = __fds_overlay_function_count();
if (request_overlay) {
fds_result[4]=lesson_overlay_a(); fds_result[5]=lesson_overlay_b();
}
m_init();
m_text(2,2,"FDS OVERLAY RECORDS"); m_wait();
m_text(2,5,"CURRENT BANK"); m_put(25,5,'0'+fds_result[0]); m_wait();
m_text(2,7,"BANK 1 RESIDENT"); m_put(25,7,'0'+fds_result[1]); m_wait();
m_text(2,9,"BANK 2 RESIDENT"); m_put(25,9,'0'+fds_result[2]); m_wait();
m_text(2,11,"OVERLAY FUNCTIONS"); m_put(25,11,'0'+fds_result[3]); m_wait();
m_text(2,14,"NO OVERLAY LOAD TESTED"); m_wait();
while (1) { m_wait(); }
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
CURRENT BANK는 1, BANK 1 RESIDENT는 1, BANK 2 RESIDENT는 0, OVERLAY FUNCTIONS는 2입니다. 뱅크 2에 함수를 두 개 패키징했다고 시작부터 그 뱅크가 상주하는 것은 아닙니다. --fds-no-overlay-table을 사용하는 검증 빌드에서는 마지막 값만 0으로 바뀝니다.


빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_residency.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_residency.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/fds_overlay.h:16
Return the software resident-bank byte, or FF after a failed bank transfer. This is not a checksum of overlay memory.
kitaqfc/kitaqfc/CodeGenerator.cs:5222
case "__fds_current_bank":
if (!Need(0)) return true;
EmitAsm("LDA", Mem(_runtimeFdsResidentBankAddress));
return true;__fds_farcall
FDS 전용 표기로 무인수 원거리 호출을 수행합니다. 로드와 복원 동작은 __fds_overlay_farcall()과 완전히 같습니다.
사용 형식
u8 __fds_farcall(u8 bank, u16 func);매개변수
bank뱅크를 나타내는 식입니다. 호출 전에 한 번 평가되지만 실제로 로드하는 뱅크는 링크된 func의 배치로 결정됩니다. 런타임에 임의로 선택한 뱅크의 주소를 호출하는 기능이 아닙니다.
__bankof(func)처럼 일치하는 값을 전달하세요.func선언된 무인수 함수의 이름입니다. 예제는
u8 overlay_answer(void)를 전달합니다. 숫자 주소, 런타임 함수 포인터, 인수가 필요한 함수는 거부됩니다. 인수가 필요하면 일반적인target(a, b)호출을 사용하세요.
반환값
대상 함수의 8비트 결과입니다. 로드에 실패했으나 이전 뱅크를 복원할 수 있으면 대상은 호출하지 않고 대신 로드 오류 바이트를 반환합니다. 정상 결과와 오류를 구분할 수 있도록 반환값 규약을 설계하세요.
사용 조건과 제한
--mapper=fds --fds-layout=fds32와 함께 사용하세요. 뱅크 0은 $A000–$DFFF의 공통 코드입니다. 시작 시 뱅크 1이 $6000–$9FFF를 차지하고, 뱅크 2 이상은 같은 16KiB 창에 읽어 오는 오버레이입니다. #pragma fixed_bank 2 같은 지시문으로 오버레이 코드나 데이터를 배치하세요.
뱅크 1은 $6000을 로드 주소로 하는 별도의 16,384바이트 부팅 PRG 파일에서 복원됩니다. 매니페스트가 없으면 기본 파일 ID는 뱅크 1이 0, 공통 코드가 1, CHR이 2입니다. 사용자 정의 매니페스트는 부팅 PRG를 $6000과 $A000의 16,384바이트 파일로 나눠야 합니다. 뱅크 2 이상의 파일 ID는 --fds-overlay-start-id(기본값 32) + 뱅크 번호 − 2입니다.
다른 뱅크를 호출할 때는 이전 뱅크를 저장하고 반환 전에 복원합니다. 중첩 호출도 같은 규칙을 따릅니다. 전송이 일부 진행된 후 실패해도 복원을 시도합니다. 복원 자체가 실패하면 덮어쓴 코드로 돌아가지 않고 공통 코드에서 실행을 멈춥니다. 공통 뱅크의 대상이나 같은 뱅크를 호출할 때는 불필요한 전송을 하지 않습니다.
BIOS 호출은 동기식이므로 이 API는 로드 중 게임 루프를 프레임마다 계속 진행시키지 않습니다. 인터럽트 핸들러가 아닌 공통 뱅크의 일반 제어 코드에서 사용하세요. 화면·음악의 연속성과 실제 BIOS와의 공존은 별도로 검증해야 합니다.
이 캡처는 독자적으로 만든 LoadFiles 호출 규약 검증 코드를 사용하여 컴파일된 CPU 코드, 메모리 전송, 표시된 모든 픽셀을 확인합니다. 실제 BIOS, 디스크 전송 타이밍, CRC, 실기 부팅을 검증하는 것은 아닙니다. KUROSAKI의 FDS 컨트롤러 모델은 부분 구현입니다. 이 검증 코드 없이 일반 실행한 결과만으로 같은 전송 결과를 입증할 수는 없습니다.
아래 첫 컴파일러 명령으로 다운로드 가능한 예제를 빌드합니다. Python 검사 도구는 5개 학습 예제를 최적화 사용 및 미사용으로 모두 빌드한 뒤 KUROSAKI에서 확인합니다. scripts/fds_abi_fixture.py에서 임시 검증 코드를 생성하고 제거하므로 이 검증에는 외부 BIOS가 필요 없습니다. 이 코드는 일반 게임용 BIOS가 아닙니다.
사용 예
#pragma fixed_bank 0
#include "fc_fds_scene.h"
u8 choose_overlay(void){fds_result[2]++;return 2;}
#pragma fixed_bank 2
u8 overlay_answer(void){fds_result[1]++;fds_result[3]=__fds_current_bank();return 42;}
#pragma fixed_bank 0
void main(void) {
__irq_disable();fds_result[1]=0;fds_result[2]=0;
fds_result[0]=__fds_farcall(choose_overlay(),overlay_answer);
fds_result[4]=__fds_current_bank();fds_result[15]=165;
fds_call_screen();while(1){m_wait();}
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
독자적으로 만든 LoadFiles ABI 검증 코드에서 RETURN VALUE는 042, CALLBACK COUNT와 BANK EXPR COUNT는 각각 001, BANK IN CALLBACK은 002, BANK AFTER RETURN은 001입니다. 함수와 뱅크 식이 각각 한 번 실행되었으며 뱅크 2를 호출한 뒤 뱅크 1이 복원되었습니다. 실제 디스크 전송 테스트를 캡처한 것은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_farcall.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_farcall.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --fds-overlay-trim --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
python .\kitaq-docs\tools\check_fds_load_examples.py구현과 소스
kitaqfc/lib/fds_overlay.h:25
Same named, zero-argument dispatch as __fds_overlay_farcall; evaluate bank once.
kitaqfc/kitaqfc/CodeGenerator.cs:4074
if (string.Equals(funcName, "__fds_overlay_farcall", StringComparison.Ordinal) ||
string.Equals(funcName, "__fds_farcall", StringComparison.Ordinal))
{
// FDS overlay dispatch requires the configured FDS RAM layout and a named function target.
Expr[] farArgs = args ?? Array.Empty<Expr>();
if (farArgs.Length != 2)
{
Program.Error("error KQFC2512: {0}(bank, func) expects exactly 2 arguments.", funcName);
return;
}
string targetName;
if (!farArgs[1].Match(Tag.Name, out targetName))
{
Program.Error("error KQFC2513: {0}(bank, func) requires the 2nd argument to be a function name.", funcName);
return;
}
if (!Program.NesMapperProfile.HasFds || !Program.FdsPrgRamLayoutEnabled)
{
Program.Error("error KQFC2514: {0} requires --mapper=fds with --fds-layout=fds32.", funcName);
return;
}
// Like ordinary __farcall, this entry supplies no callback arguments.
// Validate before emitting code so a data symbol cannot become a JSR target.
if (!_functionParameters.TryGetValue(targetName, out FieldInfo[] fdsParameters))
{
Program.Error("error KQFC2610: {0} requires a declared function name; use an ordinary typed call for a function pointer.", funcName);
return;
}
if (fdsParameters.Length != 0)
{
Program.Error("error KQFC2610: {0} requires a callback with no parameters.", funcName);
return;
}
// Evaluate the bank expression exactly once, even for a safe direct call.
// The named target's linked placement determines the dispatched bank.
EmitLoadValue(farArgs[0], ctx, 1);
int callerBank = ctx == null ? 0 : ctx.Bank;
int calleeBank = GetFunctionBank(targetName);
if (ShouldUseFdsOverlayThunk(callerBank, calleeBank))
{
string directFdsThunkName = EnsureFdsOverlayThunk(targetName, calleeBank);
EmitAsm("JSR", Abs(directFdsThunkName));
RecordCallEdge(targetName, calleeBank, "fds_overlay_thunk", viaThunk: true, viaFarcall: true);
return;
}
if (calleeBank == 0 || calleeBank == callerBank)
{
EmitAsm("JSR", Abs(targetName));
RecordCallEdge(targetName, calleeBank, "direct", viaThunk: false, viaFarcall: true);
return;
}
string fdsThunkName = EnsureFdsOverlayThunk(targetName, calleeBank);
EmitAsm("JSR", Abs(fdsThunkName));
RecordCallEdge(targetName, calleeBank, "fds_overlay_thunk", viaThunk: true, viaFarcall: true);
return;
}__fds_is_bank_resident
요청한 뱅크를 상주 기록과 비교합니다. 로드가 필요한지 판단하는 데 도움이 되지만 실제 로드는 하지 않습니다.
사용 형식
u8 __fds_is_bank_resident(u8 bank);매개변수
bank기록과 비교할 논리 뱅크 번호입니다. 표준 시작 기록은 1이며 생성된 오버레이는 2 이상을 사용합니다. 뱅크 0은 공통 코드를 담고 있으며 이 상주 기록의 시작값이 아닙니다.
반환값
유효한 뱅크가 기록과 일치하면 1, 아니면 0입니다. 뱅크 255(알 수 없음)는 기록이 255여도 항상 0을 반환합니다.
사용 조건과 제한
--mapper=fds로 빌드하세요. 카트리지 매퍼용으로 __fds_available 이외의 FDS 전용 함수를 호출하면 KQFC2420 오류로 거부됩니다.
표준 FDS 시작 코드는 상주 기록을 1로 초기화합니다. __fds_load_bank()는 로드 성공 후 이를 갱신합니다. __fds_load_file() 또는 __fds_load_overlay()를 통한 직접 로드와 실패한 뱅크 전송은 기록을 255(알 수 없음)로 설정합니다. 메모리 내용을 검사하지 않으므로 창을 직접 수정한 뒤에는 이 기록에 의존하지 마세요. 사용자 정의 리셋 루틴은 초기화를 직접 관리해야 합니다.
캡처는 KUROSAKI가 디스크 부팅 파일을 RAM에 직접 읽어 오는 방식을 사용합니다. 예제는 BIOS 루틴을 호출하지 않으며 파일 전송, 디스크 교체, 실기 부팅을 검증하지 않습니다. 빈 테스트 펌웨어는 대체 BIOS가 아닙니다. 예제는 화면 갱신에 NMI를 사용하며, 이 조회 함수에는 필요 없는 마스커블 IRQ를 비활성화합니다.
빌드 예제는 추가 승인 화면용 부팅 파일 없이 네이티브 배치의 .fds를 생성합니다. 이 설정은 KUROSAKI의 직접 부팅을 확인하기 위한 것입니다. 실기 디스크의 부팅 절차는 따로 검증해야 합니다.
사용 예
void main(void) {
// These BIOS-free queries need NMI for display, but no maskable IRQ.
__irq_disable();
request_overlay=0;
fds_result[0] = __fds_current_bank();
fds_result[1] = __fds_is_bank_resident(1);
fds_result[2] = __fds_is_bank_resident(2);
fds_result[3] = __fds_overlay_function_count();
if (request_overlay) {
fds_result[4]=lesson_overlay_a(); fds_result[5]=lesson_overlay_b();
}
m_init();
m_text(2,2,"FDS OVERLAY RECORDS"); m_wait();
m_text(2,5,"CURRENT BANK"); m_put(25,5,'0'+fds_result[0]); m_wait();
m_text(2,7,"BANK 1 RESIDENT"); m_put(25,7,'0'+fds_result[1]); m_wait();
m_text(2,9,"BANK 2 RESIDENT"); m_put(25,9,'0'+fds_result[2]); m_wait();
m_text(2,11,"OVERLAY FUNCTIONS"); m_put(25,11,'0'+fds_result[3]); m_wait();
m_text(2,14,"NO OVERLAY LOAD TESTED"); m_wait();
while (1) { m_wait(); }
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
CURRENT BANK는 1, BANK 1 RESIDENT는 1, BANK 2 RESIDENT는 0, OVERLAY FUNCTIONS는 2입니다. 뱅크 2에 함수를 두 개 패키징했다고 시작부터 그 뱅크가 상주하는 것은 아닙니다. --fds-no-overlay-table을 사용하는 검증 빌드에서는 마지막 값만 0으로 바뀝니다.


빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_residency.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_residency.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/fds_overlay.h:18
Compare a valid bank with the software resident-bank byte; FF (unknown) never matches.
kitaqfc/kitaqfc/CodeGenerator.cs:9465
EmitHelperStart("__fds_is_bank_resident");
string fdsResidentYes = NewGeneratedLabel("fds_resident_yes");
string fdsResidentNo = NewGeneratedLabel("fds_resident_no");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("CMP", Imm(0xFF));
EmitAsm("BEQ", Rel(fdsResidentNo));
EmitAsm("CMP", Mem(_runtimeFdsResidentBankAddress));
EmitAsm("BEQ", Rel(fdsResidentYes));
_assembly.Add(Expr.Make(Tag.Label, fdsResidentNo));
EmitAsm("LDA", Imm(0));
EmitAsm("RTS");
_assembly.Add(Expr.Make(Tag.Label, fdsResidentYes));
EmitAsm("LDA", Imm(1));
EmitAsm("RTS");
// Query file existence in the compiled metadata table.__fds_load_bank
논리 뱅크를 파일 ID로 변환하여 $6000–$9FFF 오버레이 창을 로드한 뒤 해당 뱅크가 상주한다고 기록합니다.
사용 형식
u8 __fds_load_bank(u8 bank);매개변수
bank로드할 논리 뱅크 번호(1~254)입니다. 뱅크 0은 공통 코드이며 로드 가능한 창이 아닙니다. 255는 현재 뱅크를 알 수 없음을 나타내기 위해 예약되어 있습니다. 뱅크 2 이상에서는 계산된 파일 ID도 0~254 범위여야 합니다.
반환값
성공하면 0입니다. 뱅크가 0 또는 255이거나 파일 ID가 넘치거나 뱅크 1 복원 파일이 없으면 255를 반환합니다. BIOS 전송에 실패하면 해당 오류 바이트를 반환합니다. 전송 전 인수 오류는 현재 뱅크 기록을 유지하지만 BIOS 전송 실패는 255(알 수 없음)로 표시합니다.
사용 조건과 제한
--mapper=fds --fds-layout=fds32와 함께 사용하세요. 뱅크 0은 $A000–$DFFF의 공통 코드입니다. 시작 시 뱅크 1이 $6000–$9FFF를 차지하고, 뱅크 2 이상은 같은 16KiB 창에 읽어 오는 오버레이입니다. #pragma fixed_bank 2 같은 지시문으로 오버레이 코드나 데이터를 배치하세요.
뱅크 1은 $6000을 로드 주소로 하는 별도의 16,384바이트 부팅 PRG 파일에서 복원됩니다. 매니페스트가 없으면 기본 파일 ID는 뱅크 1이 0, 공통 코드가 1, CHR이 2입니다. 사용자 정의 매니페스트는 부팅 PRG를 $6000과 $A000의 16,384바이트 파일로 나눠야 합니다. 뱅크 2 이상의 파일 ID는 --fds-overlay-start-id(기본값 32) + 뱅크 번호 − 2입니다.
기본 상주 뱅크 검사에서는 이미 로드되었다고 기록된 뱅크이면 디스크를 읽지 않고 0을 반환합니다. --fds-no-overlay-guard는 __fds_load_bank()의 이 생략 기능을 끕니다. 로드에 성공하면 요청한 뱅크를 기록합니다. 이 기록은 메모리 내용의 유효성을 검증한 결과가 아닙니다.
$6000–$9FFF 창에서 실행 중인 코드가 직접 다른 뱅크를 로드하면 실행 중인 함수나 복귀 주소를 덮어쓸 수 있습니다. 이 예제는 호출자와 표시 코드를 #pragma fixed_bank 0에 배치합니다. 다른 뱅크의 함수를 호출하고 돌아오려면 __fds_overlay_farcall()이나 일반적인 타입 지정 함수 호출을 사용하세요.
BIOS 호출은 동기식이므로 이 API는 로드 중 게임 루프를 프레임마다 계속 진행시키지 않습니다. 인터럽트 핸들러가 아닌 공통 뱅크의 일반 제어 코드에서 사용하세요. 화면·음악의 연속성과 실제 BIOS와의 공존은 별도로 검증해야 합니다.
이 캡처는 독자적으로 만든 LoadFiles 호출 규약 검증 코드를 사용하여 컴파일된 CPU 코드, 메모리 전송, 표시된 모든 픽셀을 확인합니다. 실제 BIOS, 디스크 전송 타이밍, CRC, 실기 부팅을 검증하는 것은 아닙니다. KUROSAKI의 FDS 컨트롤러 모델은 부분 구현입니다. 이 검증 코드 없이 일반 실행한 결과만으로 같은 전송 결과를 입증할 수는 없습니다.
아래 첫 컴파일러 명령으로 다운로드 가능한 예제를 빌드합니다. Python 검사 도구는 5개 학습 예제를 최적화 사용 및 미사용으로 모두 빌드한 뒤 KUROSAKI에서 확인합니다. scripts/fds_abi_fixture.py에서 임시 검증 코드를 생성하고 제거하므로 이 검증에는 외부 BIOS가 필요 없습니다. 이 코드는 일반 게임용 BIOS가 아닙니다.
사용 예
#pragma fixed_bank 0
#include "fc_fds_scene.h"
#pragma fixed_bank 2
__prg_rom u8 bank_payload[4]={11,22,33,44};
#pragma fixed_bank 0
void main(void) {
__irq_disable();
fds_result[0]=__fds_load_bank(2);
fds_result[1]=__fds_current_bank();
fds_result[2]=__fds_require_bank(2);
fds_result[3]=bank_payload[0];fds_result[4]=bank_payload[3];
fds_result[5]=nes_fds_load_bank(1);
fds_result[6]=__fds_current_bank();
fds_result[7]=__fds_require_bank(1);
fds_result[8]=__fds_load_bank(0);
fds_result[9]=__fds_load_bank(255);
fds_result[15]=165;
m_init();m_text(2,2,"FDS DATA WINDOW");m_wait();
m_text(2,5,"LOAD BANK 2 STATUS");fds_number(25,5,fds_result[0]);m_wait();
m_text(2,7,"FIRST / LAST BYTE");fds_number(21,7,fds_result[3]);fds_number(25,7,fds_result[4]);m_wait();
m_text(2,9,"REQUIRE SAME BANK");fds_number(25,9,fds_result[2]);m_wait();
m_text(2,11,"RESTORE BANK 1");fds_number(25,11,fds_result[5]);m_wait();
m_text(2,13,"RESIDENT BANK");fds_number(25,13,fds_result[6]);m_wait();
m_text(2,16,"INVALID BANKS 0 / 255");m_wait();
fds_number(2,18,fds_result[8]);fds_number(8,18,fds_result[9]);m_wait();
while(1){m_wait();}
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
독자적으로 만든 LoadFiles ABI 검증 코드에서 LOAD BANK 2 STATUS, REQUIRE SAME BANK, RESTORE BANK 1은 모두 000입니다. FIRST / LAST BYTE는 전송된 4바이트 데이터의 011과 044를 표시하며, 복원 후 RESIDENT BANK는 001입니다. 잘못된 뱅크 0과 255는 각각 255를 반환합니다. 같은 뱅크를 요구해도 추가 로드는 없으며 확인된 파일 요청은 32, 그다음 0입니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_bank_load.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_bank_load.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --fds-overlay-trim --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
python .\kitaq-docs\tools\check_fds_load_examples.py구현과 소스
kitaqfc/lib/fds_overlay.h:11
Load logical bank 1 from its separate boot file, or bank 2+ from overlay-start-id+(bank-2). Return zero on success, FF for invalid/unavailable mappings, or the BIOS error. A failed BIOS transfer marks residency FF (unknown); a matching valid guard can skip loading.
kitaqfc/kitaqfc/CodeGenerator.cs:9394
EmitHelperStart("__fds_load_bank");
string fdsLoadBankLoad = NewGeneratedLabel("fds_load_bank_load");
string fdsLoadBankOverlay = NewGeneratedLabel("fds_load_bank_overlay");
string fdsLoadBankCall = NewGeneratedLabel("fds_load_bank_call");
string fdsLoadBankDone = NewGeneratedLabel("fds_load_bank_done");
string fdsLoadBankPopInvalid = NewGeneratedLabel("fds_load_bank_pop_invalid");
string fdsLoadBankInvalid = NewGeneratedLabel("fds_load_bank_invalid");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("CMP", Imm(1));
EmitAsm("BCC", Rel(fdsLoadBankInvalid));
EmitAsm("CMP", Imm(0xFF));
EmitAsm("BEQ", Rel(fdsLoadBankInvalid));
if (Program.FdsOverlayResidencyGuardEnabled)
{
EmitAsm("CMP", Mem(_runtimeFdsResidentBankAddress));
EmitAsm("BNE", Rel(fdsLoadBankLoad));
EmitAsm("LDA", Imm(0));
EmitAsm("RTS");
_assembly.Add(Expr.Make(Tag.Label, fdsLoadBankLoad));
}
EmitAsm("PHA");
EmitAsm("CMP", Imm(1));
EmitAsm("BNE", Rel(fdsLoadBankOverlay));
EmitAsm("LDA", Abs("__kq_fds_boot_bank_file_id"));
EmitAsm("CMP", Imm(0xFF));
EmitAsm("BEQ", Rel(fdsLoadBankPopInvalid));
EmitAsm("JMP", Abs(fdsLoadBankCall));
_assembly.Add(Expr.Make(Tag.Label, fdsLoadBankOverlay));
EmitAsm("SEC");
EmitAsm("SBC", Imm(2));
EmitAsm("CLC");
EmitAsm("ADC", Imm(Program.FdsOverlayStartId & 0xFF));
EmitAsm("BCS", Rel(fdsLoadBankPopInvalid));
EmitAsm("CMP", Imm(0xFF));
EmitAsm("BEQ", Rel(fdsLoadBankPopInvalid));
_assembly.Add(Expr.Make(Tag.Label, fdsLoadBankCall));
EmitAsm("STA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__fds_load_overlay"));
EmitAsm("TAX");
EmitAsm("PLA");
EmitAsm("CPX", Imm(0));
EmitAsm("BEQ", Rel(fdsLoadBankDone));
// A failed BIOS transfer may have overwritten part of the window.
// Mark it unknown so a subsequent restore cannot be skipped by the guard.
EmitAsm("LDA", Imm(0xFF));
_assembly.Add(Expr.Make(Tag.Label, fdsLoadBankDone));
EmitAsm("STA", Mem(_runtimeFdsResidentBankAddress));
EmitAsm("STA", Mem(_runtimeCurrentBankAddress));
EmitAsm("TXA");
EmitAsm("RTS");
_assembly.Add(Expr.Make(Tag.Label, fdsLoadBankPopInvalid));
EmitAsm("PLA");
_assembly.Add(Expr.Make(Tag.Label, fdsLoadBankInvalid));
EmitAsm("LDA", Imm(0xFF));
EmitAsm("RTS");
// Return success for the recorded resident bank, otherwise dispatch a bank load.__fds_load_overlay
논리 뱅크 번호 대신 디스크 파일 ID로 BIOS LoadFiles를 호출합니다. 대상 주소는 파일 헤더로 결정됩니다.
사용 형식
u8 __fds_load_overlay(u8 id);매개변수
id디스크 파일 ID(0~254)입니다. 255는 BIOS 파일 목록의 종료값이므로 전달하지 마세요. 기본 배치에서는 ID 32에 뱅크 2가 들어 있습니다.
반환값
BIOS 오류 바이트이며 0은 성공입니다. 성공 여부에 관계없이 상주 기록은 255(알 수 없음)가 됩니다. 파일 ID만으로 논리 뱅크의 매핑이 확정되지는 않습니다.
사용 조건과 제한
--mapper=fds --fds-layout=fds32와 함께 사용하세요. 뱅크 0은 $A000–$DFFF의 공통 코드입니다. 시작 시 뱅크 1이 $6000–$9FFF를 차지하고, 뱅크 2 이상은 같은 16KiB 창에 읽어 오는 오버레이입니다. #pragma fixed_bank 2 같은 지시문으로 오버레이 코드나 데이터를 배치하세요.
요청한 ID와 종료값 255가 포함된 목록을 만듭니다. 파일이 공통 코드나 실행 중인 코드를 덮어쓰지 않도록 매니페스트의 대상 주소를 확인하세요. 논리 오버레이에는 상주 상태를 관리하는 __fds_load_bank()를 우선 사용하세요.
$6000–$9FFF 창에서 실행 중인 코드가 직접 다른 뱅크를 로드하면 실행 중인 함수나 복귀 주소를 덮어쓸 수 있습니다. 이 예제는 호출자와 표시 코드를 #pragma fixed_bank 0에 배치합니다. 다른 뱅크의 함수를 호출하고 돌아오려면 __fds_overlay_farcall()이나 일반적인 타입 지정 함수 호출을 사용하세요.
BIOS 호출은 동기식이므로 이 API는 로드 중 게임 루프를 프레임마다 계속 진행시키지 않습니다. 인터럽트 핸들러가 아닌 공통 뱅크의 일반 제어 코드에서 사용하세요. 화면·음악의 연속성과 실제 BIOS와의 공존은 별도로 검증해야 합니다.
이 캡처는 독자적으로 만든 LoadFiles 호출 규약 검증 코드를 사용하여 컴파일된 CPU 코드, 메모리 전송, 표시된 모든 픽셀을 확인합니다. 실제 BIOS, 디스크 전송 타이밍, CRC, 실기 부팅을 검증하는 것은 아닙니다. KUROSAKI의 FDS 컨트롤러 모델은 부분 구현입니다. 이 검증 코드 없이 일반 실행한 결과만으로 같은 전송 결과를 입증할 수는 없습니다.
아래 첫 컴파일러 명령으로 다운로드 가능한 예제를 빌드합니다. Python 검사 도구는 5개 학습 예제를 최적화 사용 및 미사용으로 모두 빌드한 뒤 KUROSAKI에서 확인합니다. scripts/fds_abi_fixture.py에서 임시 검증 코드를 생성하고 제거하므로 이 검증에는 외부 BIOS가 필요 없습니다. 이 코드는 일반 게임용 BIOS가 아닙니다.
사용 예
#pragma fixed_bank 0
#include "fc_fds_scene.h"
#pragma fixed_bank 2
__prg_rom u8 raw_payload[4]={11,22,33,44};
#pragma fixed_bank 0
void main(void) {
__irq_disable();
fds_result[0]=__fds_load_overlay(32);
fds_result[1]=__fds_current_bank();
fds_result[2]=__fds_is_bank_resident(1);
fds_result[3]=raw_payload[0];fds_result[4]=raw_payload[3];
fds_result[5]=__fds_require_bank(1);
fds_result[6]=__fds_current_bank();
fds_result[15]=165;
m_init();m_text(2,2,"FDS RAW FILE-ID LOAD");m_wait();
m_text(2,5,"FILE 32 LOAD STATUS");fds_number(25,5,fds_result[0]);m_wait();
m_text(2,7,"FIRST / LAST BYTE");fds_number(21,7,fds_result[3]);fds_number(25,7,fds_result[4]);m_wait();
m_text(2,9,"UNKNOWN BANK RECORD");fds_number(25,9,fds_result[1]);m_wait();
m_text(2,11,"IS BANK 1 RESIDENT");fds_number(25,11,fds_result[2]);m_wait();
m_text(2,13,"REQUIRE BANK 1");fds_number(25,13,fds_result[5]);m_wait();
m_text(2,15,"RESIDENT AFTER RELOAD");fds_number(25,15,fds_result[6]);m_wait();
while(1){m_wait();}
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
독자적으로 만든 LoadFiles ABI 검증 코드에서 FILE 32 LOAD STATUS는 000이고 전송 데이터의 첫 바이트와 끝 바이트는 011과 044입니다. 직접 로드한 뒤 UNKNOWN BANK RECORD는 255, IS BANK 1 RESIDENT는 000입니다. REQUIRE BANK 1이 000으로 성공하면 RESIDENT AFTER RELOAD는 001이 됩니다. 파일 ID로 로드하는 것과 논리 뱅크를 관리하는 것의 차이를 보여 줍니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_raw_overlay.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_raw_overlay.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --fds-overlay-trim --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
python .\kitaq-docs\tools\check_fds_load_examples.py구현과 소스
kitaqfc/lib/fds_overlay.h:7
Load disk file id through BIOS, return its error byte and mark bank residency FF (unknown).
kitaqfc/kitaqfc/CodeGenerator.cs:9375
EmitHelperStart("__fds_load_overlay");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeFdsLoadListAddress));
EmitAsm("LDA", Imm(0xFF));
EmitAsm("STA", Mem(_runtimeFdsLoadListAddress + 1));
EmitAsm("JSR", Mem(0xE1F8));
_assembly.Add(Expr.Make(Tag.Word, "__kq_fds_disk_id_wildcard"));
_assembly.Add(Expr.Make(Tag.Word, string.Format("${0:X4}", _runtimeFdsLoadListAddress)));
// A raw file load can replace any part of the switchable window.
// Only the bank-aware wrapper may establish a known resident bank.
EmitAsm("TAX");
EmitAsm("LDA", Imm(0xFF));
EmitAsm("STA", Mem(_runtimeFdsResidentBankAddress));
EmitAsm("STA", Mem(_runtimeCurrentBankAddress));
EmitAsm("TXA");
EmitAsm("RTS");
// Restore bank 1 from its separate boot file; map banks 2+ to overlay
// IDs. Keep the requested bank on the CPU stack across the BIOS call.__fds_overlay_farcall
오버레이의 무인수 함수를 호출하고 반환 전에 이전 뱅크를 복원합니다.
사용 형식
u8 __fds_overlay_farcall(u8 bank, u16 func);매개변수
bank뱅크를 나타내는 식입니다. 호출 전에 한 번 평가되지만 실제로 로드하는 뱅크는 링크된 func의 배치로 결정됩니다. 런타임에 임의로 선택한 뱅크의 주소를 호출하는 기능이 아닙니다.
__bankof(func)처럼 일치하는 값을 전달하세요.func선언된 무인수 함수의 이름입니다. 예제는
u8 overlay_answer(void)를 전달합니다. 숫자 주소, 런타임 함수 포인터, 인수가 필요한 함수는 거부됩니다. 인수가 필요하면 일반적인target(a, b)호출을 사용하세요.
반환값
대상 함수의 8비트 결과입니다. 로드에 실패했으나 이전 뱅크를 복원할 수 있으면 대상은 호출하지 않고 대신 로드 오류 바이트를 반환합니다. 정상 결과와 오류를 구분할 수 있도록 반환값 규약을 설계하세요.
사용 조건과 제한
--mapper=fds --fds-layout=fds32와 함께 사용하세요. 뱅크 0은 $A000–$DFFF의 공통 코드입니다. 시작 시 뱅크 1이 $6000–$9FFF를 차지하고, 뱅크 2 이상은 같은 16KiB 창에 읽어 오는 오버레이입니다. #pragma fixed_bank 2 같은 지시문으로 오버레이 코드나 데이터를 배치하세요.
뱅크 1은 $6000을 로드 주소로 하는 별도의 16,384바이트 부팅 PRG 파일에서 복원됩니다. 매니페스트가 없으면 기본 파일 ID는 뱅크 1이 0, 공통 코드가 1, CHR이 2입니다. 사용자 정의 매니페스트는 부팅 PRG를 $6000과 $A000의 16,384바이트 파일로 나눠야 합니다. 뱅크 2 이상의 파일 ID는 --fds-overlay-start-id(기본값 32) + 뱅크 번호 − 2입니다.
다른 뱅크를 호출할 때는 이전 뱅크를 저장하고 반환 전에 복원합니다. 중첩 호출도 같은 규칙을 따릅니다. 전송이 일부 진행된 후 실패해도 복원을 시도합니다. 복원 자체가 실패하면 덮어쓴 코드로 돌아가지 않고 공통 코드에서 실행을 멈춥니다. 공통 뱅크의 대상이나 같은 뱅크를 호출할 때는 불필요한 전송을 하지 않습니다.
BIOS 호출은 동기식이므로 이 API는 로드 중 게임 루프를 프레임마다 계속 진행시키지 않습니다. 인터럽트 핸들러가 아닌 공통 뱅크의 일반 제어 코드에서 사용하세요. 화면·음악의 연속성과 실제 BIOS와의 공존은 별도로 검증해야 합니다.
이 캡처는 독자적으로 만든 LoadFiles 호출 규약 검증 코드를 사용하여 컴파일된 CPU 코드, 메모리 전송, 표시된 모든 픽셀을 확인합니다. 실제 BIOS, 디스크 전송 타이밍, CRC, 실기 부팅을 검증하는 것은 아닙니다. KUROSAKI의 FDS 컨트롤러 모델은 부분 구현입니다. 이 검증 코드 없이 일반 실행한 결과만으로 같은 전송 결과를 입증할 수는 없습니다.
아래 첫 컴파일러 명령으로 다운로드 가능한 예제를 빌드합니다. Python 검사 도구는 5개 학습 예제를 최적화 사용 및 미사용으로 모두 빌드한 뒤 KUROSAKI에서 확인합니다. scripts/fds_abi_fixture.py에서 임시 검증 코드를 생성하고 제거하므로 이 검증에는 외부 BIOS가 필요 없습니다. 이 코드는 일반 게임용 BIOS가 아닙니다.
사용 예
#pragma fixed_bank 0
#include "fc_fds_scene.h"
u8 choose_overlay(void){fds_result[2]++;return 2;}
#pragma fixed_bank 2
u8 overlay_answer(void){fds_result[1]++;fds_result[3]=__fds_current_bank();return 42;}
#pragma fixed_bank 0
void main(void) {
__irq_disable();fds_result[1]=0;fds_result[2]=0;
fds_result[0]=__fds_overlay_farcall(choose_overlay(),overlay_answer);
fds_result[4]=__fds_current_bank();fds_result[15]=165;
fds_call_screen();while(1){m_wait();}
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
독자적으로 만든 LoadFiles ABI 검증 코드에서 RETURN VALUE는 042, CALLBACK COUNT와 BANK EXPR COUNT는 각각 001, BANK IN CALLBACK은 002, BANK AFTER RETURN은 001입니다. 함수와 뱅크 식이 각각 한 번 실행되었으며 뱅크 2를 호출한 뒤 뱅크 1이 복원되었습니다. 실제 디스크 전송 테스트를 캡처한 것은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_overlay_call.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_overlay_call.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --fds-overlay-trim --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
python .\kitaq-docs\tools\check_fds_load_examples.py구현과 소스
kitaqfc/lib/fds_overlay.h:23
Evaluate bank once and call the named zero-argument function using its linked bank. Restore the caller window before returning; restoration failure stops in common code.
kitaqfc/kitaqfc/CodeGenerator.cs:4074
if (string.Equals(funcName, "__fds_overlay_farcall", StringComparison.Ordinal) ||
string.Equals(funcName, "__fds_farcall", StringComparison.Ordinal))
{
// FDS overlay dispatch requires the configured FDS RAM layout and a named function target.
Expr[] farArgs = args ?? Array.Empty<Expr>();
if (farArgs.Length != 2)
{
Program.Error("error KQFC2512: {0}(bank, func) expects exactly 2 arguments.", funcName);
return;
}
string targetName;
if (!farArgs[1].Match(Tag.Name, out targetName))
{
Program.Error("error KQFC2513: {0}(bank, func) requires the 2nd argument to be a function name.", funcName);
return;
}
if (!Program.NesMapperProfile.HasFds || !Program.FdsPrgRamLayoutEnabled)
{
Program.Error("error KQFC2514: {0} requires --mapper=fds with --fds-layout=fds32.", funcName);
return;
}
// Like ordinary __farcall, this entry supplies no callback arguments.
// Validate before emitting code so a data symbol cannot become a JSR target.
if (!_functionParameters.TryGetValue(targetName, out FieldInfo[] fdsParameters))
{
Program.Error("error KQFC2610: {0} requires a declared function name; use an ordinary typed call for a function pointer.", funcName);
return;
}
if (fdsParameters.Length != 0)
{
Program.Error("error KQFC2610: {0} requires a callback with no parameters.", funcName);
return;
}
// Evaluate the bank expression exactly once, even for a safe direct call.
// The named target's linked placement determines the dispatched bank.
EmitLoadValue(farArgs[0], ctx, 1);
int callerBank = ctx == null ? 0 : ctx.Bank;
int calleeBank = GetFunctionBank(targetName);
if (ShouldUseFdsOverlayThunk(callerBank, calleeBank))
{
string directFdsThunkName = EnsureFdsOverlayThunk(targetName, calleeBank);
EmitAsm("JSR", Abs(directFdsThunkName));
RecordCallEdge(targetName, calleeBank, "fds_overlay_thunk", viaThunk: true, viaFarcall: true);
return;
}
if (calleeBank == 0 || calleeBank == callerBank)
{
EmitAsm("JSR", Abs(targetName));
RecordCallEdge(targetName, calleeBank, "direct", viaThunk: false, viaFarcall: true);
return;
}
string fdsThunkName = EnsureFdsOverlayThunk(targetName, calleeBank);
EmitAsm("JSR", Abs(fdsThunkName));
RecordCallEdge(targetName, calleeBank, "fds_overlay_thunk", viaThunk: true, viaFarcall: true);
return;
}__fds_overlay_function_count
링커가 오버레이 뱅크 2 이상에 배치한 함수 수를 반환합니다. 상주 함수 수나 파일 수가 아닙니다.
사용 형식
u8 __fds_overlay_function_count(void);반환값
테이블 첫머리의 개수 바이트(0~255)입니다. 오버레이 함수가 없거나 --fds-no-overlay-table을 사용하면 0입니다. 항목이 255개를 넘으면 KQFC2511로 거부됩니다.
사용 조건과 제한
--mapper=fds로 빌드하세요. 카트리지 매퍼용으로 __fds_available 이외의 FDS 전용 함수를 호출하면 KQFC2420 오류로 거부됩니다.
코드 생성 시에는 값 0의 임시 자리를 만들고 네이티브 FDS 링크에서 뱅크·주소 배치 후 테이블로 대체합니다. 예제는 뱅크 2에 함수 두 개를 패키징하지만 호출하지 않습니다. --fds-layout=legacy의 초기 데이터는 오버레이 함수의 실제 개수를 집계한 값이 아닙니다.
캡처는 KUROSAKI가 디스크 부팅 파일을 RAM에 직접 읽어 오는 방식을 사용합니다. 예제는 BIOS 루틴을 호출하지 않으며 파일 전송, 디스크 교체, 실기 부팅을 검증하지 않습니다. 빈 테스트 펌웨어는 대체 BIOS가 아닙니다. 예제는 화면 갱신에 NMI를 사용하며, 이 조회 함수에는 필요 없는 마스커블 IRQ를 비활성화합니다.
빌드 예제는 추가 승인 화면용 부팅 파일 없이 네이티브 배치의 .fds를 생성합니다. 이 설정은 KUROSAKI의 직접 부팅을 확인하기 위한 것입니다. 실기 디스크의 부팅 절차는 따로 검증해야 합니다.
사용 예
void main(void) {
// These BIOS-free queries need NMI for display, but no maskable IRQ.
__irq_disable();
request_overlay=0;
fds_result[0] = __fds_current_bank();
fds_result[1] = __fds_is_bank_resident(1);
fds_result[2] = __fds_is_bank_resident(2);
fds_result[3] = __fds_overlay_function_count();
if (request_overlay) {
fds_result[4]=lesson_overlay_a(); fds_result[5]=lesson_overlay_b();
}
m_init();
m_text(2,2,"FDS OVERLAY RECORDS"); m_wait();
m_text(2,5,"CURRENT BANK"); m_put(25,5,'0'+fds_result[0]); m_wait();
m_text(2,7,"BANK 1 RESIDENT"); m_put(25,7,'0'+fds_result[1]); m_wait();
m_text(2,9,"BANK 2 RESIDENT"); m_put(25,9,'0'+fds_result[2]); m_wait();
m_text(2,11,"OVERLAY FUNCTIONS"); m_put(25,11,'0'+fds_result[3]); m_wait();
m_text(2,14,"NO OVERLAY LOAD TESTED"); m_wait();
while (1) { m_wait(); }
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
CURRENT BANK는 1, BANK 1 RESIDENT는 1, BANK 2 RESIDENT는 0, OVERLAY FUNCTIONS는 2입니다. 뱅크 2에 함수를 두 개 패키징했다고 시작부터 그 뱅크가 상주하는 것은 아닙니다. --fds-no-overlay-table을 사용하는 검증 빌드에서는 마지막 값만 0으로 바뀝니다.


빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_residency.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_residency.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/fds_overlay.h:20
Return the linker-generated overlay-function count; zero if the table is disabled.
kitaqfc/kitaqfc/CodeGenerator.cs:5229
case "__fds_overlay_function_count":
if (!Need(0)) return true;
EmitAsm("LDA", Abs("__kq_fds_overlay_function_table"));
return true;__fds_require_bank
이미 상주한다고 기록되지 않은 경우에만 뱅크를 로드합니다. 에셋에 접근하기 전에 호출하면 이미 준비된 경우의 불필요한 전송을 피할 수 있습니다.
사용 형식
u8 __fds_require_bank(u8 bank);매개변수
bank로드할 논리 뱅크 번호(1~254)입니다. 뱅크 0은 공통 코드이며 로드 가능한 창이 아닙니다. 255는 현재 뱅크를 알 수 없음을 나타내기 위해 예약되어 있습니다. 뱅크 2 이상에서는 계산된 파일 ID도 0~254 범위여야 합니다.
반환값
성공하면 0입니다. 뱅크가 0 또는 255이거나 파일 ID가 넘치거나 뱅크 1 복원 파일이 없으면 255를 반환합니다. BIOS 전송에 실패하면 해당 오류 바이트를 반환합니다. 전송 전 인수 오류는 현재 뱅크 기록을 유지하지만 BIOS 전송 실패는 255(알 수 없음)로 표시합니다.
사용 조건과 제한
--mapper=fds --fds-layout=fds32와 함께 사용하세요. 뱅크 0은 $A000–$DFFF의 공통 코드입니다. 시작 시 뱅크 1이 $6000–$9FFF를 차지하고, 뱅크 2 이상은 같은 16KiB 창에 읽어 오는 오버레이입니다. #pragma fixed_bank 2 같은 지시문으로 오버레이 코드나 데이터를 배치하세요.
유효한 뱅크가 기록과 일치하면 0을 반환하고, 아니면 __fds_load_bank()를 호출합니다. --fds-no-overlay-guard를 사용해도 이 최초 비교는 수행합니다. 기록이 255여도 뱅크 255를 상주 상태로 보지는 않습니다.
뱅크 1은 $6000을 로드 주소로 하는 별도의 16,384바이트 부팅 PRG 파일에서 복원됩니다. 매니페스트가 없으면 기본 파일 ID는 뱅크 1이 0, 공통 코드가 1, CHR이 2입니다. 사용자 정의 매니페스트는 부팅 PRG를 $6000과 $A000의 16,384바이트 파일로 나눠야 합니다. 뱅크 2 이상의 파일 ID는 --fds-overlay-start-id(기본값 32) + 뱅크 번호 − 2입니다.
$6000–$9FFF 창에서 실행 중인 코드가 직접 다른 뱅크를 로드하면 실행 중인 함수나 복귀 주소를 덮어쓸 수 있습니다. 이 예제는 호출자와 표시 코드를 #pragma fixed_bank 0에 배치합니다. 다른 뱅크의 함수를 호출하고 돌아오려면 __fds_overlay_farcall()이나 일반적인 타입 지정 함수 호출을 사용하세요.
BIOS 호출은 동기식이므로 이 API는 로드 중 게임 루프를 프레임마다 계속 진행시키지 않습니다. 인터럽트 핸들러가 아닌 공통 뱅크의 일반 제어 코드에서 사용하세요. 화면·음악의 연속성과 실제 BIOS와의 공존은 별도로 검증해야 합니다.
이 캡처는 독자적으로 만든 LoadFiles 호출 규약 검증 코드를 사용하여 컴파일된 CPU 코드, 메모리 전송, 표시된 모든 픽셀을 확인합니다. 실제 BIOS, 디스크 전송 타이밍, CRC, 실기 부팅을 검증하는 것은 아닙니다. KUROSAKI의 FDS 컨트롤러 모델은 부분 구현입니다. 이 검증 코드 없이 일반 실행한 결과만으로 같은 전송 결과를 입증할 수는 없습니다.
아래 첫 컴파일러 명령으로 다운로드 가능한 예제를 빌드합니다. Python 검사 도구는 5개 학습 예제를 최적화 사용 및 미사용으로 모두 빌드한 뒤 KUROSAKI에서 확인합니다. scripts/fds_abi_fixture.py에서 임시 검증 코드를 생성하고 제거하므로 이 검증에는 외부 BIOS가 필요 없습니다. 이 코드는 일반 게임용 BIOS가 아닙니다.
사용 예
#pragma fixed_bank 0
#include "fc_fds_scene.h"
#pragma fixed_bank 2
__prg_rom u8 bank_payload[4]={11,22,33,44};
#pragma fixed_bank 0
void main(void) {
__irq_disable();
fds_result[0]=__fds_load_bank(2);
fds_result[1]=__fds_current_bank();
fds_result[2]=__fds_require_bank(2);
fds_result[3]=bank_payload[0];fds_result[4]=bank_payload[3];
fds_result[5]=nes_fds_load_bank(1);
fds_result[6]=__fds_current_bank();
fds_result[7]=__fds_require_bank(1);
fds_result[8]=__fds_load_bank(0);
fds_result[9]=__fds_load_bank(255);
fds_result[15]=165;
m_init();m_text(2,2,"FDS DATA WINDOW");m_wait();
m_text(2,5,"LOAD BANK 2 STATUS");fds_number(25,5,fds_result[0]);m_wait();
m_text(2,7,"FIRST / LAST BYTE");fds_number(21,7,fds_result[3]);fds_number(25,7,fds_result[4]);m_wait();
m_text(2,9,"REQUIRE SAME BANK");fds_number(25,9,fds_result[2]);m_wait();
m_text(2,11,"RESTORE BANK 1");fds_number(25,11,fds_result[5]);m_wait();
m_text(2,13,"RESIDENT BANK");fds_number(25,13,fds_result[6]);m_wait();
m_text(2,16,"INVALID BANKS 0 / 255");m_wait();
fds_number(2,18,fds_result[8]);fds_number(8,18,fds_result[9]);m_wait();
while(1){m_wait();}
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
독자적으로 만든 LoadFiles ABI 검증 코드에서 LOAD BANK 2 STATUS, REQUIRE SAME BANK, RESTORE BANK 1은 모두 000입니다. FIRST / LAST BYTE는 전송된 4바이트 데이터의 011과 044를 표시하며, 복원 후 RESIDENT BANK는 001입니다. 잘못된 뱅크 0과 255는 각각 255를 반환합니다. 같은 뱅크를 요구해도 추가 로드는 없으며 확인된 파일 요청은 32, 그다음 0입니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_bank_load.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_bank_load.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --fds-overlay-trim --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
python .\kitaq-docs\tools\check_fds_load_examples.py구현과 소스
kitaqfc/lib/fds_overlay.h:13
Return zero when the software residency record matches, otherwise call __fds_load_bank.
kitaqfc/kitaqfc/CodeGenerator.cs:9451
EmitHelperStart("__fds_require_bank");
string fdsRequireLoad = NewGeneratedLabel("fds_require_load");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("CMP", Imm(0xFF));
EmitAsm("BEQ", Rel(fdsRequireLoad));
EmitAsm("CMP", Mem(_runtimeFdsResidentBankAddress));
EmitAsm("BNE", Rel(fdsRequireLoad));
EmitAsm("LDA", Imm(0));
EmitAsm("RTS");
_assembly.Add(Expr.Make(Tag.Label, fdsRequireLoad));
EmitAsm("JSR", Abs("__fds_load_bank"));
EmitAsm("RTS");
// Compare the requested bank with the software residency record and return zero or one.fds_sound.h — FDS 웨이브테이블 음원
64단계 파형, 변조 테이블, 음높이, 음량, 엔벌로프용 컴파일러 내장 함수를 선언합니다. 구현은 KITAQFC가 생성합니다.
--mapper=fds로 .fds 이미지를 빌드하세요. 이 예제는 디스크 입출력 없이 사운드를 다룹니다. 녹음은 KUROSAKI의 RAM 직접 부팅과 오디오 모델을 사용합니다. 빈 테스트 펌웨어는 BIOS 루틴을 구현하지 않으며 배포하지 않습니다. 일반 디스크 부팅과 실기에는 적절한 부팅 환경이 필요합니다.
__fds_env_set
세 원시 인수를 $4080, $4084, $408A 순서로 씁니다.
사용 형식
void __fds_env_set(u8 speed, u8 gain, u8 mode);매개변수
speed매개변수 이름과 달리 원시 $4080 값입니다. 비트 7은 직접 음량, 비트 6은 증가 엔벌로프입니다.
gain$4084에 쓰는 원시 변조 엔벌로프 값입니다.
mode$408A에 쓰는 엔벌로프 마스터 속도입니다.
반환값
반환값은 없습니다. 사운드 레지스터나 드라이버 상태를 변경합니다.
사용 조건과 제한
--mapper=fds로 .fds 이미지를 빌드하세요. 이 예제는 디스크 입출력 없이 사운드를 다룹니다. 녹음은 KUROSAKI의 RAM 직접 부팅과 오디오 모델을 사용합니다. 빈 테스트 펌웨어는 BIOS 루틴을 구현하지 않으며 배포하지 않습니다. 일반 디스크 부팅과 실기에는 적절한 부팅 환경이 필요합니다.
사용 예
// Mapper 20 sound lesson: original 64-step triangle and two sustained pitches.
#include "fc_sound_example.h"
#include "fds_sound.h"
#include "nes_game.h"
__location(0x4087) u8 mod_control;
__location(0x4085) u8 mod_counter;
u8 triangle[64]; u8 modulation[32];
void main(void) {
u8 i;
sound_begin("FDS WAVE AND VOLUME");
for(i=0;i<32;i++) { triangle[i]=(u8)(i*2); triangle[63-i]=(u8)(i*2); modulation[i]=0; }
__fds_sound_enable();
// Halt/reset modulation before writing the 32 values (each writes two slots).
mod_control=0x80; mod_counter=0;
__fds_mod_load(modulation);
__fds_wave_load(triangle);
sound_result[0]=*((u8*)0x4040); sound_result[1]=*((u8*)0x405F);
__fds_freq_set(1031);
__fds_volume_set(24);
sound_wait(60);
__fds_volume_set(0); sound_wait(30);
// This macro forwards the same 64-byte load. Use raw envelope registers.
nes_fds_wave_load(triangle);
__fds_env_set(0x98,0x80,0);
__fds_freq_set(1545);
sound_wait(60);
__fds_volume_set(0); sound_wait(30);
sound_end();
}예상 결과
독자적인 64단계 삼각파가 약 440 Hz로 재생되고 쉬었다가 약 660 Hz로 재생된 뒤 멈춥니다. 두 번째 음은 nes_fds_wave_load와 원시 엔벌로프 설정을 사용합니다. 이는 오디오 모델과 직접 부팅의 검증이며 BIOS 기반 디스크 시작 검증은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_fds.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-fds.fds --mapper=fds --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/fds_sound.h:18
Write the three raw arguments to 4080, 4084 and 408A in that order. The legacy parameter names do not describe three independently packed envelope fields.
kitaqfc/kitaqfc/CodeGenerator.cs:5207
case "__fds_env_set":
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1}, "__fds_env_set");
// File helpers use byte identifiers and word memory addresses/lengths.
EmitHelperStart("__fds_env_set");
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("STA", Mem(0x4080));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("STA", Mem(0x4084));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("STA", Mem(0x408A));
EmitAsm("RTS");__fds_freq_set
12비트 주파수를 $4082/$4083에 쓰고 $4083의 상위 제어 비트를 지웁니다.
사용 형식
void __fds_freq_set(u16 freq);매개변수
freqFDS 주파수 레지스터값 0..4095입니다. 상위 제어 비트는 지웁니다.
반환값
반환값은 없습니다. 사운드 레지스터나 드라이버 상태를 변경합니다.
사용 조건과 제한
--mapper=fds로 .fds 이미지를 빌드하세요. 이 예제는 디스크 입출력 없이 사운드를 다룹니다. 녹음은 KUROSAKI의 RAM 직접 부팅과 오디오 모델을 사용합니다. 빈 테스트 펌웨어는 BIOS 루틴을 구현하지 않으며 배포하지 않습니다. 일반 디스크 부팅과 실기에는 적절한 부팅 환경이 필요합니다.
사용 예
// Mapper 20 sound lesson: original 64-step triangle and two sustained pitches.
#include "fc_sound_example.h"
#include "fds_sound.h"
#include "nes_game.h"
__location(0x4087) u8 mod_control;
__location(0x4085) u8 mod_counter;
u8 triangle[64]; u8 modulation[32];
void main(void) {
u8 i;
sound_begin("FDS WAVE AND VOLUME");
for(i=0;i<32;i++) { triangle[i]=(u8)(i*2); triangle[63-i]=(u8)(i*2); modulation[i]=0; }
__fds_sound_enable();
// Halt/reset modulation before writing the 32 values (each writes two slots).
mod_control=0x80; mod_counter=0;
__fds_mod_load(modulation);
__fds_wave_load(triangle);
sound_result[0]=*((u8*)0x4040); sound_result[1]=*((u8*)0x405F);
__fds_freq_set(1031);
__fds_volume_set(24);
sound_wait(60);
__fds_volume_set(0); sound_wait(30);
// This macro forwards the same 64-byte load. Use raw envelope registers.
nes_fds_wave_load(triangle);
__fds_env_set(0x98,0x80,0);
__fds_freq_set(1545);
sound_wait(60);
__fds_volume_set(0); sound_wait(30);
sound_end();
}예상 결과
독자적인 64단계 삼각파가 약 440 Hz로 재생되고 쉬었다가 약 660 Hz로 재생된 뒤 멈춥니다. 두 번째 음은 nes_fds_wave_load와 원시 엔벌로프 설정을 사용합니다. 이는 오디오 모델과 직접 부팅의 검증이며 BIOS 기반 디스크 시작 검증은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_fds.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-fds.fds --mapper=fds --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/fds_sound.h:13
Write a 12-bit frequency to 4082/4083; upper control bits in 4083 are cleared.
kitaqfc/kitaqfc/CodeGenerator.cs:5197
case "__fds_freq_set":
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2}, "__fds_freq_set");
// Clamp the direct volume field to six bits and set the fixed-volume control bit.
EmitHelperStart("__fds_freq_set");
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("STA", Mem(0x4082));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("AND", Imm(0x0F)); EmitAsm("STA", Mem(0x4083));
EmitAsm("RTS");__fds_mod_load
변조 테이블 쓰기 포트 $4088에 32바이트를 순서대로 보냅니다.
사용 형식
void __fds_mod_load(const u8* mod32);매개변수
mod32FDS 변조 테이블용 32바이트이며 각 값은 제어 코드 0..7입니다. 로드 전에 변조를 멈추세요.
반환값
반환값은 없습니다. 사운드 레지스터나 드라이버 상태를 변경합니다.
사용 조건과 제한
--mapper=fds로 .fds 이미지를 빌드하세요. 이 예제는 디스크 입출력 없이 사운드를 다룹니다. 녹음은 KUROSAKI의 RAM 직접 부팅과 오디오 모델을 사용합니다. 빈 테스트 펌웨어는 BIOS 루틴을 구현하지 않으며 배포하지 않습니다. 일반 디스크 부팅과 실기에는 적절한 부팅 환경이 필요합니다.
사용 예
// Mapper 20 sound lesson: original 64-step triangle and two sustained pitches.
#include "fc_sound_example.h"
#include "fds_sound.h"
#include "nes_game.h"
__location(0x4087) u8 mod_control;
__location(0x4085) u8 mod_counter;
u8 triangle[64]; u8 modulation[32];
void main(void) {
u8 i;
sound_begin("FDS WAVE AND VOLUME");
for(i=0;i<32;i++) { triangle[i]=(u8)(i*2); triangle[63-i]=(u8)(i*2); modulation[i]=0; }
__fds_sound_enable();
// Halt/reset modulation before writing the 32 values (each writes two slots).
mod_control=0x80; mod_counter=0;
__fds_mod_load(modulation);
__fds_wave_load(triangle);
sound_result[0]=*((u8*)0x4040); sound_result[1]=*((u8*)0x405F);
__fds_freq_set(1031);
__fds_volume_set(24);
sound_wait(60);
__fds_volume_set(0); sound_wait(30);
// This macro forwards the same 64-byte load. Use raw envelope registers.
nes_fds_wave_load(triangle);
__fds_env_set(0x98,0x80,0);
__fds_freq_set(1545);
sound_wait(60);
__fds_volume_set(0); sound_wait(30);
sound_end();
}예상 결과
독자적인 64단계 삼각파가 약 440 Hz로 재생되고 쉬었다가 약 660 Hz로 재생된 뒤 멈춥니다. 두 번째 음은 nes_fds_wave_load와 원시 엔벌로프 설정을 사용합니다. 이는 오디오 모델과 직접 부팅의 검증이며 BIOS 기반 디스크 시작 검증은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_fds.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-fds.fds --mapper=fds --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/fds_sound.h:11
Stream 32 readable bytes to 4088; the caller prepares modulation state and write conditions.
kitaqfc/kitaqfc/CodeGenerator.cs:5195
case "__fds_mod_load":
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2}, "__fds_mod_load");
EmitHelperStart("__fds_mod_load");
EmitAsm("LDY", Imm(0));
string modLoop = NewGeneratedLabel("fds_mod_loop");
_assembly.Add(Expr.Make(Tag.Label, modLoop));
EmitAsm("LDA", IndY(CallArgBase));
EmitAsm("STA", Mem(0x4088));
EmitAsm("INY"); EmitAsm("CPY", Imm(32)); EmitAsm("BNE", Rel(modLoop));
EmitAsm("RTS");__fds_sound_enable
디스크/사운드 게이트 $4023에 0x83을 쓰고 파형 제어 $4089와 속도 $408A를 0으로 만듭니다.
사용 형식
void __fds_sound_enable(void);반환값
반환값은 없습니다. 사운드 레지스터나 드라이버 상태를 변경합니다.
사용 조건과 제한
--mapper=fds로 .fds 이미지를 빌드하세요. 이 예제는 디스크 입출력 없이 사운드를 다룹니다. 녹음은 KUROSAKI의 RAM 직접 부팅과 오디오 모델을 사용합니다. 빈 테스트 펌웨어는 BIOS 루틴을 구현하지 않으며 배포하지 않습니다. 일반 디스크 부팅과 실기에는 적절한 부팅 환경이 필요합니다.
사용 예
// Mapper 20 sound lesson: original 64-step triangle and two sustained pitches.
#include "fc_sound_example.h"
#include "fds_sound.h"
#include "nes_game.h"
__location(0x4087) u8 mod_control;
__location(0x4085) u8 mod_counter;
u8 triangle[64]; u8 modulation[32];
void main(void) {
u8 i;
sound_begin("FDS WAVE AND VOLUME");
for(i=0;i<32;i++) { triangle[i]=(u8)(i*2); triangle[63-i]=(u8)(i*2); modulation[i]=0; }
__fds_sound_enable();
// Halt/reset modulation before writing the 32 values (each writes two slots).
mod_control=0x80; mod_counter=0;
__fds_mod_load(modulation);
__fds_wave_load(triangle);
sound_result[0]=*((u8*)0x4040); sound_result[1]=*((u8*)0x405F);
__fds_freq_set(1031);
__fds_volume_set(24);
sound_wait(60);
__fds_volume_set(0); sound_wait(30);
// This macro forwards the same 64-byte load. Use raw envelope registers.
nes_fds_wave_load(triangle);
__fds_env_set(0x98,0x80,0);
__fds_freq_set(1545);
sound_wait(60);
__fds_volume_set(0); sound_wait(30);
sound_end();
}예상 결과
독자적인 64단계 삼각파가 약 440 Hz로 재생되고 쉬었다가 약 660 Hz로 재생된 뒤 멈춥니다. 두 번째 음은 nes_fds_wave_load와 원시 엔벌로프 설정을 사용합니다. 이는 오디오 모델과 직접 부팅의 검증이며 BIOS 기반 디스크 시작 검증은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_fds.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-fds.fds --mapper=fds --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/fds_sound.h:7
Write 83 to 4023 and zero to 4089/408A to initialize disk/sound register gates.
kitaqfc/kitaqfc/CodeGenerator.cs:5183
case "__fds_sound_enable":
if (!Need(0)) return true;
EmitAsm("LDA", Imm(0x83));
EmitAsm("STA", Mem(0x4023));
EmitAsm("LDA", Imm(0x00));
EmitAsm("STA", Mem(0x4089));
EmitAsm("LDA", Imm(0x00));
EmitAsm("STA", Mem(0x408A));
return true;
// Wave, modulation and frequency operations pass word arguments to their dedicated helpers.__fds_volume_set
직접 음량 제어를 위해 하위 6비트 음량에 0x80을 더해 $4080에 씁니다.
사용 형식
void __fds_volume_set(u8 vol);매개변수
volFDS 직접 음량 0..63입니다. 6비트 마스크를 적용하므로 64는 0으로 순환합니다.
반환값
반환값은 없습니다. 사운드 레지스터나 드라이버 상태를 변경합니다.
사용 조건과 제한
--mapper=fds로 .fds 이미지를 빌드하세요. 이 예제는 디스크 입출력 없이 사운드를 다룹니다. 녹음은 KUROSAKI의 RAM 직접 부팅과 오디오 모델을 사용합니다. 빈 테스트 펌웨어는 BIOS 루틴을 구현하지 않으며 배포하지 않습니다. 일반 디스크 부팅과 실기에는 적절한 부팅 환경이 필요합니다.
사용 예
// Mapper 20 sound lesson: original 64-step triangle and two sustained pitches.
#include "fc_sound_example.h"
#include "fds_sound.h"
#include "nes_game.h"
__location(0x4087) u8 mod_control;
__location(0x4085) u8 mod_counter;
u8 triangle[64]; u8 modulation[32];
void main(void) {
u8 i;
sound_begin("FDS WAVE AND VOLUME");
for(i=0;i<32;i++) { triangle[i]=(u8)(i*2); triangle[63-i]=(u8)(i*2); modulation[i]=0; }
__fds_sound_enable();
// Halt/reset modulation before writing the 32 values (each writes two slots).
mod_control=0x80; mod_counter=0;
__fds_mod_load(modulation);
__fds_wave_load(triangle);
sound_result[0]=*((u8*)0x4040); sound_result[1]=*((u8*)0x405F);
__fds_freq_set(1031);
__fds_volume_set(24);
sound_wait(60);
__fds_volume_set(0); sound_wait(30);
// This macro forwards the same 64-byte load. Use raw envelope registers.
nes_fds_wave_load(triangle);
__fds_env_set(0x98,0x80,0);
__fds_freq_set(1545);
sound_wait(60);
__fds_volume_set(0); sound_wait(30);
sound_end();
}예상 결과
독자적인 64단계 삼각파가 약 440 Hz로 재생되고 쉬었다가 약 660 Hz로 재생된 뒤 멈춥니다. 두 번째 음은 nes_fds_wave_load와 원시 엔벌로프 설정을 사용합니다. 이는 오디오 모델과 직접 부팅의 검증이며 BIOS 기반 디스크 시작 검증은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_fds.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-fds.fds --mapper=fds --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/fds_sound.h:15
Write (volume & 3F) | 80 to 4080 for direct volume control; masking wraps, rather than clamps.
kitaqfc/kitaqfc/CodeGenerator.cs:5200
case "__fds_volume_set":
if (!Need(1)) return true;
Load1(args[0]);
EmitAsm("AND", Imm(0x3F));
EmitAsm("ORA", Imm(0x80));
EmitAsm("STA", Mem(0x4080));
return true;__fds_wave_load
파형 쓰기를 활성화하고 64바이트를 $4040..$407F에 복사한 뒤 $4089를 0으로 초기화합니다.
사용 형식
void __fds_wave_load(const u8* wave64);매개변수
wave64FDS 파형 64바이트이며 각 값은 6비트 진폭 0..63입니다. NULL은 검사하지 않습니다.
반환값
반환값은 없습니다. 사운드 레지스터나 드라이버 상태를 변경합니다.
사용 조건과 제한
--mapper=fds로 .fds 이미지를 빌드하세요. 이 예제는 디스크 입출력 없이 사운드를 다룹니다. 녹음은 KUROSAKI의 RAM 직접 부팅과 오디오 모델을 사용합니다. 빈 테스트 펌웨어는 BIOS 루틴을 구현하지 않으며 배포하지 않습니다. 일반 디스크 부팅과 실기에는 적절한 부팅 환경이 필요합니다.
사용 예
// Mapper 20 sound lesson: original 64-step triangle and two sustained pitches.
#include "fc_sound_example.h"
#include "fds_sound.h"
#include "nes_game.h"
__location(0x4087) u8 mod_control;
__location(0x4085) u8 mod_counter;
u8 triangle[64]; u8 modulation[32];
void main(void) {
u8 i;
sound_begin("FDS WAVE AND VOLUME");
for(i=0;i<32;i++) { triangle[i]=(u8)(i*2); triangle[63-i]=(u8)(i*2); modulation[i]=0; }
__fds_sound_enable();
// Halt/reset modulation before writing the 32 values (each writes two slots).
mod_control=0x80; mod_counter=0;
__fds_mod_load(modulation);
__fds_wave_load(triangle);
sound_result[0]=*((u8*)0x4040); sound_result[1]=*((u8*)0x405F);
__fds_freq_set(1031);
__fds_volume_set(24);
sound_wait(60);
__fds_volume_set(0); sound_wait(30);
// This macro forwards the same 64-byte load. Use raw envelope registers.
nes_fds_wave_load(triangle);
__fds_env_set(0x98,0x80,0);
__fds_freq_set(1545);
sound_wait(60);
__fds_volume_set(0); sound_wait(30);
sound_end();
}예상 결과
독자적인 64단계 삼각파가 약 440 Hz로 재생되고 쉬었다가 약 660 Hz로 재생된 뒤 멈춥니다. 두 번째 음은 nes_fds_wave_load와 원시 엔벌로프 설정을 사용합니다. 이는 오디오 모델과 직접 부팅의 검증이며 BIOS 기반 디스크 시작 검증은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_fds.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-fds.fds --mapper=fds --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/fds_sound.h:9
Copy exactly 64 readable bytes to wave RAM with wave writes enabled, then set 4089 to zero.
kitaqfc/kitaqfc/CodeGenerator.cs:5193
case "__fds_wave_load":
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2}, "__fds_wave_load");
EmitHelperStart("__fds_wave_load");
EmitAsm("LDA", Imm(0x80)); EmitAsm("STA", Mem(0x4089));
EmitAsm("LDY", Imm(0));
string waveLoop = NewGeneratedLabel("fds_wave_loop");
_assembly.Add(Expr.Make(Tag.Label, waveLoop));
EmitAsm("LDA", IndY(CallArgBase));
EmitAsm("STA", new AsmOperand(0x4040, AddressMode.AbsoluteY));
EmitAsm("INY"); EmitAsm("CPY", Imm(64)); EmitAsm("BNE", Rel(waveLoop));
EmitAsm("LDA", Imm(0x00)); EmitAsm("STA", Mem(0x4089));
EmitAsm("RTS");intrinsics.h — 컴파일러 내장 함수
__nes_audio_vblank_tick
BGM과 SFX의 지연을 진행시키고 현재 채널 사용 주체에 따라 APU를 갱신하는 NMI 훅입니다.
사용 형식
void __nes_audio_vblank_tick(void);반환값
반환값은 없습니다.
사용 조건과 제한
audio_vblank.h를 포함하고 audio_vblank.c를 빌드에 정확히 한 번 추가하세요. 컴파일러는 기본 NMI 핸들러든 사용자 정의 NMI 핸들러든 훅을 한 번 호출하도록 합니다. PPU의 NMI를 활성화하세요. 메인 루프에서는 음악 갱신 루틴을 호출하지 않습니다.
이 4채널 드라이버는 NTSC에 맞춰져 있습니다. CH1=펄스 1, CH2=펄스 2, CH3=삼각파, CH4=노이즈이며 인덱스는 0~3입니다. DMC나 확장 음원은 재생하지 않습니다. audio.c의 효과음처럼 APU에 직접 쓰는 코드와 동시에 실행하지 마세요. PAL용 음정표나 템포 보정은 없습니다.
애플리케이션 코드에서 직접 호출하지 마세요. 음악 라이브러리를 링크하면 컴파일러가 NMI에 연결합니다. 훅은 A/X/Y를 저장하며 공유 수식 임시 영역을 사용하지 않습니다. 훅과 음정표는 고정 ROM에 있고 NMI 안에서 ROM 뱅크를 전환하지 않습니다. 이 상태 보존은 음악 훅 자체에만 적용됩니다. 사용자 정의 NMI 핸들러의 다른 작업에 필요한 상태는 해당 핸들러가 보존해야 합니다.
레코드는 delay, CH1, CH2, CH3, CH4의 5바이트로 구성됩니다. 지연값 1~255는 새 상태가 지속되는 NMI 틱 수입니다. 0은 재생을 종료하며 나머지 4바이트는 무시합니다. 음정값 0~71은 C2~B7에 해당합니다(24=C4). 노이즈값 0~15는 긴 모드의 주기를, 16~31은 짧은 모드를 선택합니다. 모든 채널에서 255=NES_AUDIO_HOLD는 상태를 유지하고 254=NES_AUDIO_STOP은 소리를 끕니다.
사용 예
// The linked audio_vblank.c supplies __nes_audio_vblank_tick().
// The compiler calls that hook once per NMI. Do not call it in this loop.
__nmi_wait();호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
예제는 독자적으로 만든 4채널 악구를 한 번 재생합니다. 메인 코드가 90프레임 동안 큐를 보충하지 않고 정수 연산을 수행해도 음악은 계속됩니다. “PHRASE COMPLETE / SILENT”와 “UNDERRUNS 0 / MATH OK”는 재생이 끝났고 큐 데이터 부족이 없었으며 연산 결과가 일치했음을 뜻합니다. 녹음된 오디오에서 펄스파 멜로디와 반주, 삼각파 베이스, 노이즈를 들어 보세요. 끝부분은 무음입니다. 이미지는 제어 결과를 보여 주며 소리는 오디오로 확인해야 합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaqfc\lib\audio_vblank.c .\kitaq-docs\samples\api-examples\fc\audio_vblank.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr -o .\out\audio_vblank.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/audio_vblank.h:70
NMI hook: invoke exactly once per NMI. With a custom handler or a compiler without automatic audio-hook linking, call it there. Never call from main or call it again if the compiler already inserts the hook. Saves A/X/Y, uses no compiler scratch bytes and reads only RAM/fixed ROM.
kitaqfc/lib/audio_vblank.c:150
void __nes_audio_vblank_tick(void){
__asm {
PHA
TXA
PHA
TYA
PHA
LDA nav_paused
BEQ nav_tick_begin
JMP nav_tick_exit
nav_tick_begin:
LDA #0
STA nav_dirty
LDA nav_refresh
BEQ nav_tick_music
LDA #0
STA nav_refresh
LDA #15
STA nav_dirty
// Advance BGM even when an effect temporarily owns its output channel.
nav_tick_music:
LDA nav_active
BNE nav_tick_active
JMP nav_tick_sfx
nav_tick_active:
LDA nav_delay
BEQ nav_tick_next
DEC nav_delay
BEQ nav_tick_next
JMP nav_tick_sfx
nav_tick_next:
LDX nav_tail
CPX nav_head
BNE nav_tick_record
LDA nav_starved
BNE nav_tick_empty_done
INC nav_starved
LDA nav_underruns
CMP #255
BEQ nav_tick_empty_done
INC nav_underruns
nav_tick_empty_done:
JSR nav_silence_music
JMP nav_tick_sfx
nav_tick_record:
LDA #0
STA nav_starved
LDA nav_queue,X
BNE nav_tick_timed
STA nav_active
JSR nav_silence_music
JMP nav_tick_release
nav_tick_timed:
STA nav_delay
LDY #0
// Remember the new BGM notes but do not retrigger channels owned by SFX.
nav_tick_copy:
INX
LDA nav_queue,X
CMP #255
BEQ nav_tick_copy_next
STA nav_notes,Y
LDA nav_bits,Y
AND nav_previous_mask
BNE nav_tick_copy_next
LDA nav_bits,Y
ORA nav_dirty
STA nav_dirty
nav_tick_copy_next:
INY
CPY #4
BNE nav_tick_copy
nav_tick_release:
LDA nav_tail
CLC
ADC #5
CMP #40
BNE nav_tick_publish
LDA #0
nav_tick_publish:
STA nav_tail
// The SFX has its own delay; completion releases its mask automatically.
nav_tick_sfx:
LDA nav_sfx_active
BEQ nav_tick_select
LDA nav_sfx_delay
BEQ nav_sfx_next
DEC nav_sfx_delay
BNE nav_tick_select
nav_sfx_next:
LDX nav_sfx_cursor
CPX nav_sfx_length
BCC nav_sfx_record
LDA #0
STA nav_sfx_active
JMP nav_tick_select
nav_sfx_record:
LDA nav_sfx_queue,X
STA nav_sfx_delay
LDY #0
nav_sfx_copy:
INX
LDA nav_sfx_queue,X
CMP #255
BEQ nav_sfx_copy_next
STA nav_sfx_notes,Y
LDA nav_bits,Y
AND nav_sfx_mask
ORA nav_dirty
STA nav_dirty
nav_sfx_copy_next:
INY
CPY #4
BNE nav_sfx_copy
INX
STX nav_sfx_cursor
nav_tick_select:
LDA #0
STA nav_effective_mask
LDA nav_sfx_active
BEQ nav_tick_restore
LDA nav_sfx_mask
STA nav_effective_mask
// A changed ownership bit forces the newly selected note onto the APU.
nav_tick_restore:
LDA nav_effective_mask
EOR nav_previous_mask
ORA nav_dirty
STA nav_dirty
LDA nav_effective_mask
STA nav_previous_mask
LDA nav_dirty
AND #1
BNE nav_apply_0
JMP nav_done_0
nav_apply_0:
LDA nav_effective_mask
AND #1
BEQ nav_bg_0
LDA nav_sfx_notes+0
JMP nav_value_0
nav_bg_0:
LDA nav_notes+0
nav_value_0:
CMP #254
BNE nav_note_0
LDA nav_enabled
AND #254
STA nav_enabled
STA $4015
JMP nav_done_0
nav_note_0:
TAY
LDA nav_enabled
ORA #1
STA nav_enabled
STA $4015
LDA nav_timbre+0
STA $4000
LDA nav_pulse_lo,Y
STA $4002
LDA nav_pulse_hi,Y
STA $4003
nav_done_0:
LDA nav_dirty
AND #2
BNE nav_apply_1
JMP nav_done_1
nav_apply_1:
LDA nav_effective_mask
AND #2
BEQ nav_bg_1
LDA nav_sfx_notes+1
JMP nav_value_1
nav_bg_1:
LDA nav_notes+1
nav_value_1:
CMP #254
BNE nav_note_1
LDA nav_enabled
AND #253
STA nav_enabled
STA $4015
JMP nav_done_1
nav_note_1:
TAY
LDA nav_enabled
ORA #2
STA nav_enabled
STA $4015
LDA nav_timbre+1
STA $4004
LDA nav_pulse_lo,Y
STA $4006
LDA nav_pulse_hi,Y
STA $4007
nav_done_1:
LDA nav_dirty
AND #4
BNE nav_apply_2
JMP nav_done_2
nav_apply_2:
LDA nav_effective_mask
AND #4
BEQ nav_bg_2
LDA nav_sfx_notes+2
JMP nav_value_2
nav_bg_2:
LDA nav_notes+2
nav_value_2:
CMP #254
BNE nav_note_2
LDA nav_enabled
AND #251
STA nav_enabled
STA $4015
JMP nav_done_2
nav_note_2:
TAY
LDA nav_enabled
ORA #4
STA nav_enabled
STA $4015
LDA nav_timbre+2
STA $4008
LDA nav_triangle_lo,Y
STA $400A
LDA nav_triangle_hi,Y
STA $400B
nav_done_2:
LDA nav_dirty
AND #8
BNE nav_apply_3
JMP nav_done_3
nav_apply_3:
LDA nav_effective_mask
AND #8
BEQ nav_bg_3
LDA nav_sfx_notes+3
JMP nav_value_3
nav_bg_3:
LDA nav_notes+3
nav_value_3:
CMP #254
BNE nav_note_3
LDA nav_enabled
AND #247
STA nav_enabled
STA $4015
JMP nav_done_3
nav_note_3:
TAY
LDA nav_enabled
ORA #8
STA nav_enabled
STA $4015
LDA nav_timbre+3
STA $400C
TYA
AND #16
BEQ nav_noise_long
TYA
AND #15
ORA #128
JMP nav_noise_period
nav_noise_long:
TYA
nav_noise_period:
STA $400E
// A one-shot envelope must outlive its slowest 16-step decay.
// Length index 1 gives 254 half-frame ticks; index 0 cuts it at 10.
LDA nav_timbre+3
AND #16
BNE nav_noise_constant_length
LDA #8
JMP nav_noise_trigger
nav_noise_constant_length:
LDA #0
nav_noise_trigger:
STA $400F
nav_done_3:
nav_tick_exit:
PLA
TAY
PLA
TAX
PLA
RTS
// Internal subroutine: stop BGM channels without disturbing an active effect.
// End/underrun forgets the BGM notes without cutting an independent SFX.
nav_silence_music:
LDA #254
STA nav_notes+0
STA nav_notes+1
STA nav_notes+2
STA nav_notes+3
LDA nav_previous_mask
EOR #15
ORA nav_dirty
STA nav_dirty
RTS
}
}__attr_set
네임테이블 0의 0x23C0 + (y>>2)*8 + (x>>2)에 압축된 속성 바이트 전체를 씁니다. X/Y는 4×4타일 속성 영역을 가리키는 타일 좌표입니다. 2비트 팔레트 필드 네 개를 모두 바꾸며 선택한 사분면 하나만 병합하지 않습니다.
사용 형식
void __attr_set(u8 x, u8 y, u8 attr);매개변수
x / y픽셀이 아닌 타일 좌표입니다. 화면의 32×30타일 영역에서는
x를0..31,y를0..29로 유지하세요. 한 타일은 8×8픽셀입니다. 바이트 인수를 제한하지 않으므로 범위 밖 좌표는 의도한 타일 영역 밖에 쓸 수 있습니다.attr압축된 속성 바이트입니다. 비트 0–1은 왼쪽 위 2×2타일, 2–3은 오른쪽 위, 4–5는 왼쪽 아래, 6–7은 오른쪽 아래를 지정하며 각 필드는 팔레트 0–3을 선택합니다.
0x55,0xAA,0xFF는 네 사분면 모두를 각각 팔레트 1, 2, 3으로 설정합니다.
반환값
없음(void).
사용 조건과 제한
직접 PPU 접근은 렌더링을 끄거나 전체 작업이 안전한 VBlank 구간 안에 끝나는 조건에서 수행하세요. 이 함수들은 대기, 큐 등록, NMI 차단, 스크롤 복원을 하지 않습니다. 호출 중 인터럽트가 공유 래치나 주소를 바꾸지 않도록 하세요. PPUCTRL 비트 2에 따라 대상 주소는 바이트마다 1 또는 32씩 증가합니다. 미러링과 주소 순환은 PPU 동작을 따르며 CHR-ROM에는 쓸 수 없습니다. 접근 후 렌더링 전에 원하는 스크롤 상태를 설정하세요.
사용 예
for(i=2;i<12;i=(u8)(i+4)) {
__attr_set(i,6,0xFF);__attr_set(i,10,0xFF); // All quadrants select green.
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 프로그램에서는 레지스터 설정, 타일·속성 직접 쓰기, 스크롤, 배경·스프라이트 팔레트의 개별 전송을 배웁니다. (0,16)의 파란 띠는 256×16픽셀입니다. 첫 행은 꽉 찬 8×8타일, 왼쪽 4×8막대, 8행 삼각형을 반복하고 둘째 행은 왼쪽 막대만 나열합니다. (16,48)에는 녹색으로 채운 64×32 사각형, (144,48)에는 삼각형 타일을 가로 8개·세로 4개 배치한 빨간 64×32 영역이 표시됩니다.
y=96에는 x=16, 24, 32에 각각 파란 정사각형·왼쪽 막대·삼각형, x=80에 빨간 왼쪽 막대, x=144에 녹색 삼각형이 표시됩니다. 이들은 배경 타일입니다. 별도 팔레트의 스프라이트는 y=144에 있으며 x=16은 빨간 8×8 정사각형, x=80은 파란 왼쪽 4×8막대, x=144는 녹색 8행 삼각형입니다. 최종 스크롤은 (0,0)입니다.
이미지는 스프라이트 색상을 포함해 16,384픽셀의 모양과 색상을 대조합니다. 별도 31개 상태 검사는 레지스터 교체, 상태·래치 부수 효과, 저장된 스크롤, 0/255바이트 전송, 증가량 1/32, 맵 경계, 크기 0 사각형, 테이블 선택, 전체 속성 바이트와 두 팔레트 대상을 다룹니다. Y=255는 레지스터 쓰기만 확인하며 올바른 스크롤 이미지를 검증한 것은 아닙니다. KUROSAKI의 NROM에서 검증했으며 실기는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:134
Write the entire packed attribute byte at 23C0+(y>>2)*8+(x>>2). Use tile coordinates below 32; this does not merge a single palette quadrant.
kitaqfc/kitaqfc/CodeGenerator.cs:4824
case "__attr_set": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1}, funcName);
void EmitHelperAttrSet()
{
EmitHelperStart("__attr_set");
// args: x,y,attr. Attribute address = $23C0 + (y>>2)*8 + (x>>2), nametable 0.
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("LSR"); EmitAsm("LSR"); EmitAsm("ASL"); EmitAsm("ASL"); EmitAsm("ASL");
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("LSR"); EmitAsm("LSR"); EmitAsm("CLC"); EmitAsm("ADC", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(0x2002)); EmitAsm("LDA", Imm(0x23)); EmitAsm("STA", Mem(0x2006));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("CLC"); EmitAsm("ADC", Imm(0xC0)); EmitAsm("STA", Mem(0x2006));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("STA", Mem(0x2007));
EmitAsm("RTS");
}__attr_set_nt
논리 네임테이블 nt & 3을 선택하고 PPU 래치를 초기화한 뒤 0x23C0 + (nt & 3)*0x400 + (y>>2)*8 + (x>>2)에 속성 바이트 전체를 씁니다. 해당 바이트의 모든 팔레트 사분면을 덮어씁니다. 테이블 선택은 카트리지의 현재 미러링을 따릅니다.
사용 형식
void __attr_set_nt(u8 nt, u8 x, u8 y, u8 attr);매개변수
nt논리 네임테이블 선택값입니다. 하위 두 비트만 사용하므로(
nt & 3) 4는 테이블 0, 7은 테이블 3을 선택합니다. 독립 저장 공간을 확보하거나 매퍼 미러링을 바꾸지 않습니다.x / y픽셀이 아닌 타일 좌표입니다. 화면의 32×30타일 영역에서는
x를0..31,y를0..29로 유지하세요. 한 타일은 8×8픽셀입니다. 바이트 인수를 제한하지 않으므로 범위 밖 좌표는 의도한 타일 영역 밖에 쓸 수 있습니다.attr압축된 속성 바이트입니다. 비트 0–1은 왼쪽 위 2×2타일, 2–3은 오른쪽 위, 4–5는 왼쪽 아래, 6–7은 오른쪽 아래를 지정하며 각 필드는 팔레트 0–3을 선택합니다.
0x55,0xAA,0xFF는 네 사분면 모두를 각각 팔레트 1, 2, 3으로 설정합니다.
반환값
없음(void).
사용 조건과 제한
직접 PPU 접근은 렌더링을 끄거나 전체 작업이 안전한 VBlank 구간 안에 끝나는 조건에서 수행하세요. 이 함수들은 대기, 큐 등록, NMI 차단, 스크롤 복원을 하지 않습니다. 호출 중 인터럽트가 공유 래치나 주소를 바꾸지 않도록 하세요. PPUCTRL 비트 2에 따라 대상 주소는 바이트마다 1 또는 32씩 증가합니다. 미러링과 주소 순환은 PPU 동작을 따르며 CHR-ROM에는 쓸 수 없습니다. 접근 후 렌더링 전에 원하는 스크롤 상태를 설정하세요.
사용 예
for(i=18;i<28;i=(u8)(i+4)) {
__attr_set_nt(4,i,6,0x55);__attr_set_nt(4,i,10,0x55); // nt&3=0; red.
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 프로그램에서는 레지스터 설정, 타일·속성 직접 쓰기, 스크롤, 배경·스프라이트 팔레트의 개별 전송을 배웁니다. (0,16)의 파란 띠는 256×16픽셀입니다. 첫 행은 꽉 찬 8×8타일, 왼쪽 4×8막대, 8행 삼각형을 반복하고 둘째 행은 왼쪽 막대만 나열합니다. (16,48)에는 녹색으로 채운 64×32 사각형, (144,48)에는 삼각형 타일을 가로 8개·세로 4개 배치한 빨간 64×32 영역이 표시됩니다.
y=96에는 x=16, 24, 32에 각각 파란 정사각형·왼쪽 막대·삼각형, x=80에 빨간 왼쪽 막대, x=144에 녹색 삼각형이 표시됩니다. 이들은 배경 타일입니다. 별도 팔레트의 스프라이트는 y=144에 있으며 x=16은 빨간 8×8 정사각형, x=80은 파란 왼쪽 4×8막대, x=144는 녹색 8행 삼각형입니다. 최종 스크롤은 (0,0)입니다.
이미지는 스프라이트 색상을 포함해 16,384픽셀의 모양과 색상을 대조합니다. 별도 31개 상태 검사는 레지스터 교체, 상태·래치 부수 효과, 저장된 스크롤, 0/255바이트 전송, 증가량 1/32, 맵 경계, 크기 0 사각형, 테이블 선택, 전체 속성 바이트와 두 팔레트 대상을 다룹니다. Y=255는 레지스터 쓰기만 확인하며 올바른 스크롤 이미지를 검증한 것은 아닙니다. KUROSAKI의 NROM에서 검증했으며 실기는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:136
Write a whole packed attribute byte in nametable nt&3; other quadrant fields are overwritten.
kitaqfc/kitaqfc/CodeGenerator.cs:4825
case "__attr_set_nt": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1,1}, funcName);
// Palette loaders receive a word pointer; their fixed data layout is handled by the runtime helper.
void EmitHelperAttrSetNt()
{
EmitHelperStart("__attr_set_nt");
// args: nt,x,y,attr. Attribute address = $23C0 + nt*$400 + (y>>2)*8 + (x>>2).
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("AND", Imm(3));
EmitAsm("ASL");
EmitAsm("ASL");
EmitAsm("CLC");
EmitAsm("ADC", Imm(0x23));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("LSR"); EmitAsm("LSR"); EmitAsm("ASL"); EmitAsm("ASL"); EmitAsm("ASL");
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("LSR"); EmitAsm("LSR"); EmitAsm("CLC"); EmitAsm("ADC", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(0x2002)); EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("STA", Mem(0x2006));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("CLC"); EmitAsm("ADC", Imm(0xC0)); EmitAsm("STA", Mem(0x2006));
EmitAsm("LDA", Mem(CallArgBase + 3)); EmitAsm("STA", Mem(0x2007));
EmitAsm("RTS");
}__bankof
이름으로 지정한 데이터나 함수에 배정된 논리 ROM 뱅크 번호를 구합니다. 배치 정보를 확인하는 기능이며, 현재 선택된 뱅크를 읽거나 포인터를 역참조하지 않습니다.
사용 형식
u8 bank = __bankof(symbol);매개변수
symbol컴파일러나 링커가 배치를 아는 ROM 데이터 또는 함수의 이름입니다. 포인터 변수 이름은 그 변수가 가리키는 대상을 뜻하지 않습니다. 실행 시점의 식을 심벌 이름으로 전달할 수 없습니다.
반환값
u8 뱅크 번호입니다. 0은 공통 ROM을 나타낼 수 있지만 모든 대상에서 유효한 전환 번호는 아닙니다.
사용 예
bank_expect(__bankof(payload)==3,3);
bank_expect(__bankof(callback)==3,4);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
예제는 payload와 callback이 모두 뱅크 3에 배치되었는지 확인합니다. 이는 배치 정보 검사이며, 실행 시 뱅크 동작은 별도의 읽기와 콜백 검사로 확인합니다.
완성 프로그램은 뱅크 번호와 근거리 주소로 데이터를 지정하는 방법, 뱅크를 넘는 콜백, 라이브러리 기록과 실제 매핑의 구별을 보여 줍니다. 모든 표시 숫자는 16진수입니다. BANK IN과 BANK OUT은 02, BYTE HEX는 D3, WORD HEX는 5C7A, FAILED CHECKS와 FIRST FAILURE는 모두 00이어야 합니다. CGB와 FC는 파란 글자, DMG는 검은 글자를 표시합니다.
CALL COUNT는 02입니다. far_call 매크로와 __farcall이 각각 콜백을 한 번 호출합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\banked_data_calls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\banked_data_calls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:77
PRG bank Resolve a symbol or function placement to its byte bank number at compilation time.
kitaqfc/kitaqfc/CodeGenerator.cs:6906
if (expr.MatchAny(Tag.Call, out funcExpr, out args) &&
funcExpr != null &&
funcExpr.Match(Tag.Name, out name) &&
string.Equals(name, "__bankof", StringComparison.Ordinal) &&
args != null &&
args.Length == 1 &&
args[0] != null &&
args[0].Match(Tag.Name, out string bankedSymbol))
{
// Resolve __bankof from placement metadata and keep the byte-sized bank value.
value = ResolveSymbolBank(bankedSymbol) & 0xFF;
return true;
}__bankswitch
컴파일러의 매퍼별 처리로 논리 PRG 뱅크를 선택하고 내부 뱅크 상태를 갱신합니다. 매핑 변경은 호출 후에도 유지되며, bank 라이브러리의 별도 기록은 갱신하지 않습니다.
사용 형식
void __bankswitch(u8 bank);매개변수
bank부호 없는 8비트 ROM 뱅크 번호입니다. 호출 시에는 함수 배치와 일치해야 하고, 읽기 시에는 원본 매핑을 지정합니다. 대상 카트리지에서 유효한 범위를 사용하세요.
반환값
없음(void).
사용 조건과 제한
지속되는 뱅크 변경으로 실행 중인 코드나 반환 주소가 사라져서는 안 됩니다. 예제처럼 호출 위치, 사용하는 bank_switch 본체, 이후 실행할 코드를 공통·고정 뱅크 0에 두세요.
라이브러리 기록은 0으로 시작하며 카트리지의 시작 매핑을 알아내지 않습니다. bank_switch로 초기화하세요. 내장 함수나 매퍼에 대한 직접 쓰기는 이 별도 기록을 갱신하지 않으므로 bank_get_current로 실제 하드웨어 매핑을 검증할 수는 없습니다.
FC 전환 번호는 컴파일러의 논리 뱅크 번호입니다. 0은 공통 ROM이며 전환 대상이 아닙니다. 보조 코드는 0을 뱅크 1로 처리합니다. SUROM512에서는 1~30만 유효합니다. 전환 내장 함수에 범위를 벗어난 상수를 직접 전달하면 빌드 오류가 나며, 잘못된 실행 시점 값은 1로 대체되고 매퍼 오류 상태가 설정됩니다. 공통 ROM은 뱅크 0 전환을 요청하지 말고 직접 읽으세요.
사용 예
__bankswitch(1);
bank_expect(home1[0]==0xA1,19);
bank_expect(bank_get_current()==2,20); // Intrinsics bypass the library record.
__bankswitch(2);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
처음 bank_switch(2)는 뱅크 2를 기록하고 선택합니다. 이후 직접 전환으로 뱅크 1을 선택해 home1에서 A1을 읽어도 bank_get_current는 여전히 2입니다. 복귀하면 home2에서 다시 B2를 읽습니다.
완성 프로그램은 뱅크 번호와 근거리 주소로 데이터를 지정하는 방법, 뱅크를 넘는 콜백, 라이브러리 기록과 실제 매핑의 구별을 보여 줍니다. 모든 표시 숫자는 16진수입니다. BANK IN과 BANK OUT은 02, BYTE HEX는 D3, WORD HEX는 5C7A, FAILED CHECKS와 FIRST FAILURE는 모두 00이어야 합니다. CGB와 FC는 파란 글자, DMG는 검은 글자를 표시합니다.
CALL COUNT는 02입니다. far_call 매크로와 __farcall이 각각 콜백을 한 번 호출합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\banked_data_calls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\banked_data_calls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:79
Select the mapper-specific PRG bank; the common bank and legal bank range depend on the target board.
kitaqfc/kitaqfc/CodeGenerator.cs:4746
case "__prg_bank_set":
case "__bankswitch":
if (!Need(1)) return true;
if (Program.NesMapperProfile.IsSurom512 && TryEvaluateConstant(args[0], out int constantBank) && (constantBank < 1 || constantBank > 30))
{
Program.Error("error KQFC2603 KQFC-SUROM-BANK-RANGE: {0} constant bank {1} is outside 1..30; common bank 0 is not a switch target.", funcName, constantBank);
return true;
}
_mapperWritingFunctions.Add(_currentFunctionName);
Load1(args[0]); EmitAsm("JSR", Abs("__kq_prg_set_bank_a")); return true;__bit_clear
같은 바이트의 나머지는 지우지 않고 선택한 플래그만 0으로 지웁니다. 일시적 상태 플래그처럼 저장된 조건 하나를 해제할 때 사용하세요. 같은 비트를 다시 지워도 0을 유지합니다.
사용 형식
void __bit_clear(u8* base, u16 bit);매개변수
baseCPU에서 접근할 수 있는 메모리에 있는 플래그 배열의 첫 바이트 포인터입니다. 이 호출은 배열을 할당하거나 초기화하지 않습니다. 설정·해제·반전에는 쓰기 가능한 RAM을 사용하세요. 검사는 읽기만 합니다.
bit바이트 오프셋이나 비트 마스크가 아닌, 0부터 시작하는
u16비트 인덱스(0~65535)입니다. 인덱스 0은 바이트 0의 마스크0x01, 인덱스 7은 바이트 0의0x80, 인덱스 8은 바이트 1의0x01을 선택합니다. 일반적으로 바이트 오프셋은index / 8, 마스크는1 << (index % 8)입니다. N바이트 배열에서는index < 8*N이어야 하고 CPU 주소 범위도 유효해야 합니다.
반환값
없음(void).
사용 조건과 제한
충분한 저장 공간은 호출자가 제공해야 합니다. 배열 길이 인수, 범위 클리핑, 뱅크 전환, 자동 그래픽 전송은 없습니다. 큰 비트 번호는 할당한 배열보다 훨씬 뒤의 바이트를 선택할 수 있습니다. 메모리 매핑을 유지하고, 읽기나 쓰기에 부작용이 있는 하드웨어 레지스터에 이 RAM 플래그 도우미를 대신 사용하지 마세요.
설정·해제·반전은 바이트를 읽고 선택한 비트를 바꾼 뒤 다시 씁니다. 이 과정은 원자적 갱신이 아닙니다. 같은 바이트의 다른 비트에 대한 중간 갱신이 사라지지 않도록 메인 루프와 인터럽트 처리기의 쓰기를 조정하세요.
FC 코드는 상수 인덱스를 인라인 처리하거나 생성된 도우미를 호출할 수 있습니다. 두 방식은 같은 비트 번호와 검사 결과 마스크를 사용합니다. 생성된 도우미는 인수와 임시 저장 공간을 공유하므로 다른 도우미가 실행 중일 때 NMI/IRQ에서 재진입하지 마세요.
사용 예
__bit_clear(bit_flags,2);
bit_clear_value=bit_flags[0]; // A6 & FB = A2, decimal 162.
__bit_clear(bit_flags,2); // Clearing it twice leaves it clear.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
전체 프로그램이 이 단계에 도달하면 바이트 0은 0xA6입니다. 비트 2를 지우면 0xA2(2진수 10100010, 10진수 162)가 되므로 CLEAR BIT2 행에는 162가 표시되어야 합니다. 비트 2를 다시 지워도 결과는 같으며, 다른 저장 플래그는 두 호출 모두에서 유지됩니다.
FC에서 기대하는 10진수 표시값은 TEST OFF 000, TEST ON 128, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000입니다. NEXT BYTE는 두 번째 바이트의 인덱스 9를 설정하여 0x80을 0x82로 바꾼 결과이며, 바이트 0은 변경하지 않습니다. 캡처한 FC 화면은 검은 바탕에 파란 글씨입니다.
프로그램은 화면에 표시하는 모든 상태 변화를 검사하고 실패 횟수를 보여 줍니다. 설정·해제·반전 자체는 반환값이 없으며, 해당 행은 변경 후 RAM 바이트를 표시합니다. 별도의 에뮬레이터 검사는 플랫폼마다 136가지 경우에서 보호 영역이 있는 버퍼 전체 520바이트를 비교합니다. 여기에는 바이트와 256바이트 경계를 넘는 상수·런타임 인덱스, 0이 아닌 비트 검사 마스크 8가지가 모두 포함됩니다. 이는 그리기 시연이 아니라 RAM 상태 검사입니다. 실기와 인터럽트의 동시 쓰기는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\bit_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\bit_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:45
Clear the indexed bit in caller storage; no bounds check or interrupt exclusion is inserted.
kitaqfc/kitaqfc/CodeGenerator.cs:4640
case "__bit_clear":
if (TryEmitBitIntrinsicInline(funcName, args, ctx)) return true;
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2}, "__bit_clear");
bool TryEmitBitIntrinsicInline(string funcName, Expr[] args, FunctionContext ctx)
{
args = args ?? Array.Empty<Expr>();
if (args.Length != 2) return false;
if (!TryEvaluateConstant(args[1], out int bit) || bit < 0) return false;
// Split the bit number into a byte displacement and an in-byte mask.
int byteOffset = bit >> 3;
if (byteOffset > 255) return false;
int mask = 1 << (bit & 7);
int[] ptr = AcquireTemps(2);
try
{
EmitLoadValue(args[0], ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
EmitAsm("LDY", Imm(byteOffset));
EmitAsm("LDA", IndY(ptr[0]));
switch (funcName)
{
case "__bit_test":
EmitAsm("AND", Imm(mask));
break;
case "__bit_set":
EmitAsm("ORA", Imm(mask));
EmitAsm("STA", IndY(ptr[0]));
break;
case "__bit_clear":
EmitAsm("AND", Imm((~mask) & 0xFF));
EmitAsm("STA", IndY(ptr[0]));
break;
case "__bit_toggle":
EmitAsm("EOR", Imm(mask));
EmitAsm("STA", IndY(ptr[0]));
break;
default:
return false;
}
}
finally { ReleaseTemps(ptr); }
return true;
}
void EmitBitHelper(string name, string op)
{
EmitHelperStart(name);
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(7));
EmitAsm("TAX");
string maskDone = NewGeneratedLabel("bit_mask_done");
string maskLoop = NewGeneratedLabel("bit_mask_loop");
EmitAsm("BEQ", Rel(maskDone));
_assembly.Add(Expr.Make(Tag.Label, maskLoop));
EmitAsm("ASL", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("DEX");
EmitAsm("BNE", Rel(maskLoop));
_assembly.Add(Expr.Make(Tag.Label, maskDone));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
for (int i = 0; i < 3; i++) { EmitAsm("LSR", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("ROR", Mem(_runtimeIntrinsicTmp1Address)); }
// Fold the high displacement into the pointer; indirect-Y adds the low byte and its carry.
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("CLC");
EmitAsm("ADC", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDY", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", IndY(CallArgBase));
switch (op)
{
case "test":
EmitAsm("AND", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
break;
case "set":
EmitAsm("ORA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("RTS");
break;
case "clear":
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("EOR", Imm(0xFF));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", IndY(CallArgBase));
EmitAsm("AND", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("RTS");
break;
case "toggle":
EmitAsm("EOR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("RTS");
break;
}
}__bit_set
같은 바이트의 나머지 7비트를 보존하며 선택한 플래그를 1로 설정합니다. 예를 들어 퀘스트 완료나 아이템 획득을 기록할 수 있습니다. 이미 1인 비트를 다시 설정해도 그대로이며, 토글 동작이 아닙니다.
사용 형식
void __bit_set(u8* base, u16 bit);매개변수
baseCPU에서 접근할 수 있는 메모리에 있는 플래그 배열의 첫 바이트 포인터입니다. 이 호출은 배열을 할당하거나 초기화하지 않습니다. 설정·해제·반전에는 쓰기 가능한 RAM을 사용하세요. 검사는 읽기만 합니다.
bit바이트 오프셋이나 비트 마스크가 아닌, 0부터 시작하는
u16비트 인덱스(0~65535)입니다. 인덱스 0은 바이트 0의 마스크0x01, 인덱스 7은 바이트 0의0x80, 인덱스 8은 바이트 1의0x01을 선택합니다. 일반적으로 바이트 오프셋은index / 8, 마스크는1 << (index % 8)입니다. N바이트 배열에서는index < 8*N이어야 하고 CPU 주소 범위도 유효해야 합니다.
반환값
없음(void).
사용 조건과 제한
충분한 저장 공간은 호출자가 제공해야 합니다. 배열 길이 인수, 범위 클리핑, 뱅크 전환, 자동 그래픽 전송은 없습니다. 큰 비트 번호는 할당한 배열보다 훨씬 뒤의 바이트를 선택할 수 있습니다. 메모리 매핑을 유지하고, 읽기나 쓰기에 부작용이 있는 하드웨어 레지스터에 이 RAM 플래그 도우미를 대신 사용하지 마세요.
설정·해제·반전은 바이트를 읽고 선택한 비트를 바꾼 뒤 다시 씁니다. 이 과정은 원자적 갱신이 아닙니다. 같은 바이트의 다른 비트에 대한 중간 갱신이 사라지지 않도록 메인 루프와 인터럽트 처리기의 쓰기를 조정하세요.
FC 코드는 상수 인덱스를 인라인 처리하거나 생성된 도우미를 호출할 수 있습니다. 두 방식은 같은 비트 번호와 검사 결과 마스크를 사용합니다. 생성된 도우미는 인수와 임시 저장 공간을 공유하므로 다른 도우미가 실행 중일 때 NMI/IRQ에서 재진입하지 마세요.
사용 예
__bit_set(bit_flags,1);
bit_set_value=bit_flags[0]; // A4 | 02 = A6, decimal 166.
__bit_set(bit_flags,1); // Setting it twice leaves it set.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
0xA4의 비트 1을 설정하면 10100100 | 00000010은 10100110, 즉 0xA6(10진수 166)이 됩니다. SET BIT1 행에는 166이 표시되어야 합니다. 샘플은 같은 비트를 다시 설정한 뒤에도 0xA6인지 확인하여 반복 설정이 플래그를 반전하지 않음을 보여 줍니다.
FC에서 기대하는 10진수 표시값은 TEST OFF 000, TEST ON 128, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000입니다. NEXT BYTE는 두 번째 바이트의 인덱스 9를 설정하여 0x80을 0x82로 바꾼 결과이며, 바이트 0은 변경하지 않습니다. 캡처한 FC 화면은 검은 바탕에 파란 글씨입니다.
프로그램은 화면에 표시하는 모든 상태 변화를 검사하고 실패 횟수를 보여 줍니다. 설정·해제·반전 자체는 반환값이 없으며, 해당 행은 변경 후 RAM 바이트를 표시합니다. 별도의 에뮬레이터 검사는 플랫폼마다 136가지 경우에서 보호 영역이 있는 버퍼 전체 520바이트를 비교합니다. 여기에는 바이트와 256바이트 경계를 넘는 상수·런타임 인덱스, 0이 아닌 비트 검사 마스크 8가지가 모두 포함됩니다. 이는 그리기 시연이 아니라 RAM 상태 검사입니다. 실기와 인터럽트의 동시 쓰기는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\bit_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\bit_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:43
Set the indexed bit in caller storage without changing the other seven bits in that byte.
kitaqfc/kitaqfc/CodeGenerator.cs:4637
case "__bit_set":
if (TryEmitBitIntrinsicInline(funcName, args, ctx)) return true;
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2}, "__bit_set");
bool TryEmitBitIntrinsicInline(string funcName, Expr[] args, FunctionContext ctx)
{
args = args ?? Array.Empty<Expr>();
if (args.Length != 2) return false;
if (!TryEvaluateConstant(args[1], out int bit) || bit < 0) return false;
// Split the bit number into a byte displacement and an in-byte mask.
int byteOffset = bit >> 3;
if (byteOffset > 255) return false;
int mask = 1 << (bit & 7);
int[] ptr = AcquireTemps(2);
try
{
EmitLoadValue(args[0], ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
EmitAsm("LDY", Imm(byteOffset));
EmitAsm("LDA", IndY(ptr[0]));
switch (funcName)
{
case "__bit_test":
EmitAsm("AND", Imm(mask));
break;
case "__bit_set":
EmitAsm("ORA", Imm(mask));
EmitAsm("STA", IndY(ptr[0]));
break;
case "__bit_clear":
EmitAsm("AND", Imm((~mask) & 0xFF));
EmitAsm("STA", IndY(ptr[0]));
break;
case "__bit_toggle":
EmitAsm("EOR", Imm(mask));
EmitAsm("STA", IndY(ptr[0]));
break;
default:
return false;
}
}
finally { ReleaseTemps(ptr); }
return true;
}
void EmitBitHelper(string name, string op)
{
EmitHelperStart(name);
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(7));
EmitAsm("TAX");
string maskDone = NewGeneratedLabel("bit_mask_done");
string maskLoop = NewGeneratedLabel("bit_mask_loop");
EmitAsm("BEQ", Rel(maskDone));
_assembly.Add(Expr.Make(Tag.Label, maskLoop));
EmitAsm("ASL", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("DEX");
EmitAsm("BNE", Rel(maskLoop));
_assembly.Add(Expr.Make(Tag.Label, maskDone));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
for (int i = 0; i < 3; i++) { EmitAsm("LSR", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("ROR", Mem(_runtimeIntrinsicTmp1Address)); }
// Fold the high displacement into the pointer; indirect-Y adds the low byte and its carry.
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("CLC");
EmitAsm("ADC", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDY", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", IndY(CallArgBase));
switch (op)
{
case "test":
EmitAsm("AND", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
break;
case "set":
EmitAsm("ORA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("RTS");
break;
case "clear":
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("EOR", Imm(0xFF));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", IndY(CallArgBase));
EmitAsm("AND", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("RTS");
break;
case "toggle":
EmitAsm("EOR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("RTS");
break;
}
}__bit_test
비트 배열을 변경하지 않고 플래그 하나를 읽습니다. RAM 1바이트에 독립된 켜짐·꺼짐 플래그 8개를 저장할 수 있습니다. 결과는 선택한 비트가 꺼져 있는지 켜져 있는지를 나타내며, 숫자 표현은 대상 기종에 따라 다릅니다.
사용 형식
u8 __bit_test(u8* base, u16 bit);매개변수
baseCPU에서 접근할 수 있는 메모리에 있는 플래그 배열의 첫 바이트 포인터입니다. 이 호출은 배열을 할당하거나 초기화하지 않습니다. 설정·해제·반전에는 쓰기 가능한 RAM을 사용하세요. 검사는 읽기만 합니다.
bit바이트 오프셋이나 비트 마스크가 아닌, 0부터 시작하는
u16비트 인덱스(0~65535)입니다. 인덱스 0은 바이트 0의 마스크0x01, 인덱스 7은 바이트 0의0x80, 인덱스 8은 바이트 1의0x01을 선택합니다. 일반적으로 바이트 오프셋은index / 8, 마스크는1 << (index % 8)입니다. N바이트 배열에서는index < 8*N이어야 하고 CPU 주소 범위도 유효해야 합니다.
반환값
u8: 비트가 꺼져 있으면 0, 켜져 있으면 그 마스크인 1, 2, 4, 8, 16, 32, 64, 128 중 하나입니다. 결과는 base[bit >> 3] & (1 << (bit & 7))입니다. 예를 들어 설정된 비트 7을 검사하면 128을 반환합니다. 플래그가 켜졌는지는 result != 0으로 검사하세요. 1과만 비교하면 다른 위치의 플래그를 놓칩니다.
사용 조건과 제한
충분한 저장 공간은 호출자가 제공해야 합니다. 배열 길이 인수, 범위 클리핑, 뱅크 전환, 자동 그래픽 전송은 없습니다. 큰 비트 번호는 할당한 배열보다 훨씬 뒤의 바이트를 선택할 수 있습니다. 메모리 매핑을 유지하고, 읽기나 쓰기에 부작용이 있는 하드웨어 레지스터에 이 RAM 플래그 도우미를 대신 사용하지 마세요.
FC 코드는 상수 인덱스를 인라인 처리하거나 생성된 도우미를 호출할 수 있습니다. 두 방식은 같은 비트 번호와 검사 결과 마스크를 사용합니다. 생성된 도우미는 인수와 임시 저장 공간을 공유하므로 다른 도우미가 실행 중일 때 NMI/IRQ에서 재진입하지 마세요.
사용 예
bit_off=__bit_test(bit_flags,1); // Bit 1 of A4 is clear: zero.
bit_on=__bit_test(bit_flags,7); // Bit 7 is set: GB returns 1; FC returns 128.
bit_is_on=0;
if(bit_on!=0)bit_is_on=1; // The same nonzero test works on both targets.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
바이트 0을 0xA4(2진수 10100100)로 시작합니다. 비트 1 검사 결과는 0입니다. 비트 7은 GB에서 1, FC에서 128을 반환합니다. 샘플은 이 0이 아닌 결과를 공통 켜짐·꺼짐 플래그로 바꾸고 원래 바이트가 유지되는지도 확인합니다. 아래 화면의 TEST OFF와 TEST ON을 보세요.
FC에서 기대하는 10진수 표시값은 TEST OFF 000, TEST ON 128, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000입니다. NEXT BYTE는 두 번째 바이트의 인덱스 9를 설정하여 0x80을 0x82로 바꾼 결과이며, 바이트 0은 변경하지 않습니다. 캡처한 FC 화면은 검은 바탕에 파란 글씨입니다.
프로그램은 화면에 표시하는 모든 상태 변화를 검사하고 실패 횟수를 보여 줍니다. 설정·해제·반전 자체는 반환값이 없으며, 해당 행은 변경 후 RAM 바이트를 표시합니다. 별도의 에뮬레이터 검사는 플랫폼마다 136가지 경우에서 보호 영역이 있는 버퍼 전체 520바이트를 비교합니다. 여기에는 바이트와 256바이트 경계를 넘는 상수·런타임 인덱스, 0이 아닌 비트 검사 마스크 8가지가 모두 포함됩니다. 이는 그리기 시연이 아니라 RAM 상태 검사입니다. 실기와 인터럽트의 동시 쓰기는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\bit_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\bit_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:41
Return base[bit>>3] & (1<<(bit&7)): zero or the bit mask, not a normalized Boolean.
kitaqfc/kitaqfc/CodeGenerator.cs:4634
case "__bit_test":
if (TryEmitBitIntrinsicInline(funcName, args, ctx)) return true;
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2}, "__bit_test");
bool TryEmitBitIntrinsicInline(string funcName, Expr[] args, FunctionContext ctx)
{
args = args ?? Array.Empty<Expr>();
if (args.Length != 2) return false;
if (!TryEvaluateConstant(args[1], out int bit) || bit < 0) return false;
// Split the bit number into a byte displacement and an in-byte mask.
int byteOffset = bit >> 3;
if (byteOffset > 255) return false;
int mask = 1 << (bit & 7);
int[] ptr = AcquireTemps(2);
try
{
EmitLoadValue(args[0], ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
EmitAsm("LDY", Imm(byteOffset));
EmitAsm("LDA", IndY(ptr[0]));
switch (funcName)
{
case "__bit_test":
EmitAsm("AND", Imm(mask));
break;
case "__bit_set":
EmitAsm("ORA", Imm(mask));
EmitAsm("STA", IndY(ptr[0]));
break;
case "__bit_clear":
EmitAsm("AND", Imm((~mask) & 0xFF));
EmitAsm("STA", IndY(ptr[0]));
break;
case "__bit_toggle":
EmitAsm("EOR", Imm(mask));
EmitAsm("STA", IndY(ptr[0]));
break;
default:
return false;
}
}
finally { ReleaseTemps(ptr); }
return true;
}
void EmitBitHelper(string name, string op)
{
EmitHelperStart(name);
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(7));
EmitAsm("TAX");
string maskDone = NewGeneratedLabel("bit_mask_done");
string maskLoop = NewGeneratedLabel("bit_mask_loop");
EmitAsm("BEQ", Rel(maskDone));
_assembly.Add(Expr.Make(Tag.Label, maskLoop));
EmitAsm("ASL", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("DEX");
EmitAsm("BNE", Rel(maskLoop));
_assembly.Add(Expr.Make(Tag.Label, maskDone));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
for (int i = 0; i < 3; i++) { EmitAsm("LSR", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("ROR", Mem(_runtimeIntrinsicTmp1Address)); }
// Fold the high displacement into the pointer; indirect-Y adds the low byte and its carry.
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("CLC");
EmitAsm("ADC", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDY", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", IndY(CallArgBase));
switch (op)
{
case "test":
EmitAsm("AND", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
break;
case "set":
EmitAsm("ORA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("RTS");
break;
case "clear":
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("EOR", Imm(0xFF));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", IndY(CallArgBase));
EmitAsm("AND", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("RTS");
break;
case "toggle":
EmitAsm("EOR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("RTS");
break;
}
}__bit_toggle
선택한 플래그를 반전하여 0은 1, 1은 0으로 만듭니다. 나머지 비트는 유지합니다. 두 호출 사이에 다른 코드가 그 비트를 쓰지 않는다면, 같은 비트에 두 번 호출하여 원래 상태로 되돌릴 수 있습니다.
사용 형식
void __bit_toggle(u8* base, u16 bit);매개변수
baseCPU에서 접근할 수 있는 메모리에 있는 플래그 배열의 첫 바이트 포인터입니다. 이 호출은 배열을 할당하거나 초기화하지 않습니다. 설정·해제·반전에는 쓰기 가능한 RAM을 사용하세요. 검사는 읽기만 합니다.
bit바이트 오프셋이나 비트 마스크가 아닌, 0부터 시작하는
u16비트 인덱스(0~65535)입니다. 인덱스 0은 바이트 0의 마스크0x01, 인덱스 7은 바이트 0의0x80, 인덱스 8은 바이트 1의0x01을 선택합니다. 일반적으로 바이트 오프셋은index / 8, 마스크는1 << (index % 8)입니다. N바이트 배열에서는index < 8*N이어야 하고 CPU 주소 범위도 유효해야 합니다.
반환값
없음(void).
사용 조건과 제한
충분한 저장 공간은 호출자가 제공해야 합니다. 배열 길이 인수, 범위 클리핑, 뱅크 전환, 자동 그래픽 전송은 없습니다. 큰 비트 번호는 할당한 배열보다 훨씬 뒤의 바이트를 선택할 수 있습니다. 메모리 매핑을 유지하고, 읽기나 쓰기에 부작용이 있는 하드웨어 레지스터에 이 RAM 플래그 도우미를 대신 사용하지 마세요.
설정·해제·반전은 바이트를 읽고 선택한 비트를 바꾼 뒤 다시 씁니다. 이 과정은 원자적 갱신이 아닙니다. 같은 바이트의 다른 비트에 대한 중간 갱신이 사라지지 않도록 메인 루프와 인터럽트 처리기의 쓰기를 조정하세요.
FC 코드는 상수 인덱스를 인라인 처리하거나 생성된 도우미를 호출할 수 있습니다. 두 방식은 같은 비트 번호와 검사 결과 마스크를 사용합니다. 생성된 도우미는 인수와 임시 저장 공간을 공유하므로 다른 도우미가 실행 중일 때 NMI/IRQ에서 재진입하지 마세요.
사용 예
__bit_toggle(bit_flags,7);
bit_toggle_value=bit_flags[0]; // A2 ^ 80 = 22, decimal 34.
__bit_toggle(bit_flags,7);
bit_restore_value=bit_flags[0]; // Two toggles restore A2.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
전체 프로그램은 0xA2로 이 단계에 진입합니다. 비트 7을 반전하면 0x22(10진수 34)가 되어 TOGGLE BIT7에 034가 표시됩니다. 다시 반전하면 0xA2로 돌아가며 RESTORE는 162를 표시합니다. 두 결과는 다른 비트를 유지하면서 양방향으로 반전되는 것을 보여 줍니다.
FC에서 기대하는 10진수 표시값은 TEST OFF 000, TEST ON 128, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000입니다. NEXT BYTE는 두 번째 바이트의 인덱스 9를 설정하여 0x80을 0x82로 바꾼 결과이며, 바이트 0은 변경하지 않습니다. 캡처한 FC 화면은 검은 바탕에 파란 글씨입니다.
프로그램은 화면에 표시하는 모든 상태 변화를 검사하고 실패 횟수를 보여 줍니다. 설정·해제·반전 자체는 반환값이 없으며, 해당 행은 변경 후 RAM 바이트를 표시합니다. 별도의 에뮬레이터 검사는 플랫폼마다 136가지 경우에서 보호 영역이 있는 버퍼 전체 520바이트를 비교합니다. 여기에는 바이트와 256바이트 경계를 넘는 상수·런타임 인덱스, 0이 아닌 비트 검사 마스크 8가지가 모두 포함됩니다. 이는 그리기 시연이 아니라 RAM 상태 검사입니다. 실기와 인터럽트의 동시 쓰기는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\bit_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\bit_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:47
XOR the indexed bit in caller storage; coordinate concurrent writers to avoid lost updates.
kitaqfc/kitaqfc/CodeGenerator.cs:4643
case "__bit_toggle":
if (TryEmitBitIntrinsicInline(funcName, args, ctx)) return true;
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2}, "__bit_toggle");
// Arithmetic variants share argument packing here; signed/fixed-point arithmetic is implemented by their named helpers.
bool TryEmitBitIntrinsicInline(string funcName, Expr[] args, FunctionContext ctx)
{
args = args ?? Array.Empty<Expr>();
if (args.Length != 2) return false;
if (!TryEvaluateConstant(args[1], out int bit) || bit < 0) return false;
// Split the bit number into a byte displacement and an in-byte mask.
int byteOffset = bit >> 3;
if (byteOffset > 255) return false;
int mask = 1 << (bit & 7);
int[] ptr = AcquireTemps(2);
try
{
EmitLoadValue(args[0], ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
EmitAsm("LDY", Imm(byteOffset));
EmitAsm("LDA", IndY(ptr[0]));
switch (funcName)
{
case "__bit_test":
EmitAsm("AND", Imm(mask));
break;
case "__bit_set":
EmitAsm("ORA", Imm(mask));
EmitAsm("STA", IndY(ptr[0]));
break;
case "__bit_clear":
EmitAsm("AND", Imm((~mask) & 0xFF));
EmitAsm("STA", IndY(ptr[0]));
break;
case "__bit_toggle":
EmitAsm("EOR", Imm(mask));
EmitAsm("STA", IndY(ptr[0]));
break;
default:
return false;
}
}
finally { ReleaseTemps(ptr); }
return true;
}
void EmitBitHelper(string name, string op)
{
EmitHelperStart(name);
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(7));
EmitAsm("TAX");
string maskDone = NewGeneratedLabel("bit_mask_done");
string maskLoop = NewGeneratedLabel("bit_mask_loop");
EmitAsm("BEQ", Rel(maskDone));
_assembly.Add(Expr.Make(Tag.Label, maskLoop));
EmitAsm("ASL", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("DEX");
EmitAsm("BNE", Rel(maskLoop));
_assembly.Add(Expr.Make(Tag.Label, maskDone));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
for (int i = 0; i < 3; i++) { EmitAsm("LSR", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("ROR", Mem(_runtimeIntrinsicTmp1Address)); }
// Fold the high displacement into the pointer; indirect-Y adds the low byte and its carry.
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("CLC");
EmitAsm("ADC", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDY", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", IndY(CallArgBase));
switch (op)
{
case "test":
EmitAsm("AND", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
break;
case "set":
EmitAsm("ORA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("RTS");
break;
case "clear":
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("EOR", Imm(0xFF));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", IndY(CallArgBase));
EmitAsm("AND", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("RTS");
break;
case "toggle":
EmitAsm("EOR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("RTS");
break;
}
}__chr_bank_set
CHR 뱅크를 선택합니다. MMC3에서는 R0을 선택하여 PPU $0000–$07FF의 2KiB 창을 바꿉니다. CNROM에서는 $0000–$1FFF의 8KiB 영역 전체를 바꿉니다.
사용 형식
void __chr_bank_set(u8 bank);매개변수
bankCNROM에서는 8KiB CHR 뱅크 번호입니다. MMC3에서는 2KiB 창을 선택하는 1KiB 뱅크 번호입니다. 최하위 비트를 무시하므로 2와 3은 모두 1KiB 뱅크 2·3의 쌍을 선택합니다. 사용 가능한 뱅크는 CHR ROM 용량에 따라 달라지며 API는 범위를 검사하지 않습니다.
반환값
반환값은 없습니다.
사용 조건과 제한
내장 함수 선언은 intrinsics.h에 있습니다. nes_mapper_irq_* 매크로를 쓰려면 nes_game.h를 포함하세요. 빌드 시 --mapper로 보드를 선택합니다.
MMC3 뱅크 선택 레지스터에 0 또는 1을 쓰면 PRG 모드 비트 6과 CHR 반전 비트 7도 지워집니다. 두 비트가 계속 설정되어 있어야 하는 PRG/CHR 배치와 함께 쓰지 마세요. 렌더링 중 바꾸면 이후 패턴 읽기에 영향을 줍니다. 예제는 렌더링을 끈 채 확인하고 결과를 표시하기 전에 폰트 뱅크를 복원합니다.
이 내장 함수 3개는 CNROM과 MMC3를 지원합니다. 다른 보드는 컴파일 시 거부합니다. SUROM은 외부 PRG 영역 선택에도 CHR 제어를 사용하므로 이 CHR 연산을 허용하지 않습니다.
사용 예
__chr_bank_set(2);
mapper_result[1] = mapper_read(0x03FF);
mapper_result[2] = mapper_read(0x07FF);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
MMC3 예제는 CHR 표식 19/20, 21/22, 23/24를 표시합니다. CIRAM 읽기 결과는 수직 미러링에서 A B A B, 수평 미러링에서 A A B B입니다. IRQ 수는 01→02→03→03입니다. 처음 세 번의 명시적 활성화는 각각 인터럽트 한 번을 허용하고 비활성화 후에는 수가 변하지 않습니다. 각 단계는 6프레임을 기다립니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_controls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_controls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\mapper_banks.chr --no-cache --no-disasm사용 예
__chr_bank_set(2); chr_result[0]=chr_marker();
__chr_bank_set0(1); chr_result[1]=chr_marker();
__chr_bank_set1(3); chr_result[2]=chr_marker();호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
CNROM 예제는 SET 2, SET0 1, SET1 3 이후 표식 33, 25, 41을 보여 줍니다. PPU를 통해 독자적으로 만든 표식 바이트를 읽어 세 이름 모두 8KiB 패턴 영역 전체를 전환함을 확인합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_chr_cnrom.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_chr_cnrom.nes --mapper=cnrom --nes-chr=.\kitaq-docs\samples\mapper_banks.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:316
Select a CNROM bank or MMC3 register 0 bank; unsupported mapper profiles produce a compile error.
__chr_bank_set0
MMC3 R0을 선택하여 PPU $0000–$07FF의 2KiB 창을 바꿉니다. __chr_bank_set과 같습니다. CNROM에서는 8KiB 영역 전체를 바꿉니다.
사용 형식
void __chr_bank_set0(u8 bank);매개변수
bankCNROM에서는 8KiB CHR 뱅크 번호입니다. MMC3에서는 2KiB 창을 선택하는 1KiB 뱅크 번호입니다. 최하위 비트를 무시하므로 2와 3은 모두 1KiB 뱅크 2·3의 쌍을 선택합니다. 사용 가능한 뱅크는 CHR ROM 용량에 따라 달라지며 API는 범위를 검사하지 않습니다.
반환값
반환값은 없습니다.
사용 조건과 제한
내장 함수 선언은 intrinsics.h에 있습니다. nes_mapper_irq_* 매크로를 쓰려면 nes_game.h를 포함하세요. 빌드 시 --mapper로 보드를 선택합니다.
MMC3 뱅크 선택 레지스터에 0 또는 1을 쓰면 PRG 모드 비트 6과 CHR 반전 비트 7도 지워집니다. 두 비트가 계속 설정되어 있어야 하는 PRG/CHR 배치와 함께 쓰지 마세요. 렌더링 중 바꾸면 이후 패턴 읽기에 영향을 줍니다. 예제는 렌더링을 끈 채 확인하고 결과를 표시하기 전에 폰트 뱅크를 복원합니다.
이 내장 함수 3개는 CNROM과 MMC3를 지원합니다. 다른 보드는 컴파일 시 거부합니다. SUROM은 외부 PRG 영역 선택에도 CHR 제어를 사용하므로 이 CHR 연산을 허용하지 않습니다.
사용 예
__chr_bank_set0(4);
mapper_result[3] = mapper_read(0x03FF);
mapper_result[4] = mapper_read(0x07FF);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
MMC3 예제는 CHR 표식 19/20, 21/22, 23/24를 표시합니다. CIRAM 읽기 결과는 수직 미러링에서 A B A B, 수평 미러링에서 A A B B입니다. IRQ 수는 01→02→03→03입니다. 처음 세 번의 명시적 활성화는 각각 인터럽트 한 번을 허용하고 비활성화 후에는 수가 변하지 않습니다. 각 단계는 6프레임을 기다립니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_controls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_controls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\mapper_banks.chr --no-cache --no-disasm사용 예
__chr_bank_set(2); chr_result[0]=chr_marker();
__chr_bank_set0(1); chr_result[1]=chr_marker();
__chr_bank_set1(3); chr_result[2]=chr_marker();호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
CNROM 예제는 SET 2, SET0 1, SET1 3 이후 표식 33, 25, 41을 보여 줍니다. PPU를 통해 독자적으로 만든 표식 바이트를 읽어 세 이름 모두 8KiB 패턴 영역 전체를 전환함을 확인합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_chr_cnrom.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_chr_cnrom.nes --mapper=cnrom --nes-chr=.\kitaq-docs\samples\mapper_banks.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:318
Use the same CNROM/MMC3 register 0 selection as __chr_bank_set.
__chr_bank_set1
MMC3 R1을 선택하여 PPU $0800–$0FFF의 2KiB 창을 바꾸고 R0은 유지합니다. CNROM에는 독립적인 두 번째 창이 없으므로 8KiB 영역 전체를 바꿉니다.
사용 형식
void __chr_bank_set1(u8 bank);매개변수
bankCNROM에서는 8KiB CHR 뱅크 번호입니다. MMC3에서는 2KiB 창을 선택하는 1KiB 뱅크 번호입니다. 최하위 비트를 무시하므로 2와 3은 모두 1KiB 뱅크 2·3의 쌍을 선택합니다. 사용 가능한 뱅크는 CHR ROM 용량에 따라 달라지며 API는 범위를 검사하지 않습니다.
반환값
반환값은 없습니다.
사용 조건과 제한
내장 함수 선언은 intrinsics.h에 있습니다. nes_mapper_irq_* 매크로를 쓰려면 nes_game.h를 포함하세요. 빌드 시 --mapper로 보드를 선택합니다.
MMC3 뱅크 선택 레지스터에 0 또는 1을 쓰면 PRG 모드 비트 6과 CHR 반전 비트 7도 지워집니다. 두 비트가 계속 설정되어 있어야 하는 PRG/CHR 배치와 함께 쓰지 마세요. 렌더링 중 바꾸면 이후 패턴 읽기에 영향을 줍니다. 예제는 렌더링을 끈 채 확인하고 결과를 표시하기 전에 폰트 뱅크를 복원합니다.
이 내장 함수 3개는 CNROM과 MMC3를 지원합니다. 다른 보드는 컴파일 시 거부합니다. SUROM은 외부 PRG 영역 선택에도 CHR 제어를 사용하므로 이 CHR 연산을 허용하지 않습니다.
사용 예
__chr_bank_set1(6);
mapper_result[5] = mapper_read(0x0BFF);
mapper_result[6] = mapper_read(0x0FFF);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
MMC3 예제는 CHR 표식 19/20, 21/22, 23/24를 표시합니다. CIRAM 읽기 결과는 수직 미러링에서 A B A B, 수평 미러링에서 A A B B입니다. IRQ 수는 01→02→03→03입니다. 처음 세 번의 명시적 활성화는 각각 인터럽트 한 번을 허용하고 비활성화 후에는 수가 변하지 않습니다. 각 단계는 6프레임을 기다립니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_controls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_controls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\mapper_banks.chr --no-cache --no-disasm사용 예
__chr_bank_set(2); chr_result[0]=chr_marker();
__chr_bank_set0(1); chr_result[1]=chr_marker();
__chr_bank_set1(3); chr_result[2]=chr_marker();호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
CNROM 예제는 SET 2, SET0 1, SET1 3 이후 표식 33, 25, 41을 보여 줍니다. PPU를 통해 독자적으로 만든 표식 바이트를 읽어 세 이름 모두 8KiB 패턴 영역 전체를 전환함을 확인합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_chr_cnrom.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_chr_cnrom.nes --mapper=cnrom --nes-chr=.\kitaq-docs\samples\mapper_banks.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:320
Select MMC3 register 1, or the same full CNROM bank register; this is mapper-specific banking.
__copy16
길이 인수 없이 src에서 dst로 정확히 16바이트를 복사합니다. CPU가 접근할 수 있는 메모리에 있는 16바이트 레코드나 GB·NES 타일 한 개를 복사할 때 쓸 수 있습니다. 16은 비트 수가 아니라 바이트 수이며, 화면에 그래픽을 전송하는 함수는 아닙니다.
사용 형식
void __copy16(u8* dst, const u8* src);매개변수
dst쓰기 대상의 첫 바이트를 가리키는 포인터입니다. 전체 작업에 필요한 연속 공간을 미리 확보하세요. 버퍼를 자동으로 할당하지는 않습니다.
src읽기 원본의 첫 바이트를 가리키는 포인터입니다. 복사하는 동안 전체 범위를 읽을 수 있어야 하며 내용이 바뀌면 안 됩니다.
반환값
없음(void).
사용 조건과 제한
원본과 대상에는 서로 겹치지 않는 영역을 사용하세요. 낮은 주소부터 복사하며, 겹침을 안전하게 처리하는 memmove 기능은 제공하지 않습니다. 아직 읽지 않은 원본 위치에 쓰면 뒤쪽 데이터가 손상될 수 있습니다.
작업을 완료한 뒤 호출한 곳으로 돌아갑니다. ROM·RAM 뱅크를 전환하거나 버퍼 경계에 맞춰 범위를 잘라 주지 않습니다. 매핑을 유지하고 유효한 범위를 지정하며, 같은 버퍼를 쓰는 인터럽트 처리와 접근을 조정하세요.
FC에서 dst는 PPU 주소가 아닌 CPU 주소입니다. CPU RAM에 복사해도 CHR·네임테이블·팔레트는 갱신되지 않으므로 전송에는 PPU·VRAM API를 사용하세요. 생성된 보조 루틴은 인수·작업 공간을 공유하므로 실행 중에 NMI·IRQ에서 다시 진입하면 안 됩니다.
사용 예
memory_reset();
__copy16(memory_destination+1,memory_source+1);
memory_copy_16=memory_check(16,0); // Exactly 16 bytes, not 16 bits.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 예제는 __copy16로 일정한 패턴의 16바이트를 오프셋 1부터 복사합니다. memory_check가 원본과 대상 배열 전체를 비교하며, COPY16 행에는 016가 표시되어야 합니다. 버퍼 첫 바이트는 보호 영역으로 남기고, 복사 범위 뒤쪽 바이트도 바뀌지 않았는지 확인합니다.
전체 프로그램은 작업마다 264바이트 버퍼 두 개를 초기화한 뒤 원본·대상의 모든 바이트와 변경되면 안 되는 앞뒤 보호 영역을 검사합니다. 정상 표시는 COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002, FAILED 000입니다. WORD·BYTE는 복사할 원소의 크기가 아니라 길이의 자료형을 뜻합니다. 숫자는 예제의 검사 결과이며, 반환값이 없는 이 함수들이 반환한 값이 아닙니다.
캡처 화면은 DMG가 흰 바탕에 검은 글자, CGB가 흰 바탕에 파란 글자, FC가 검은 바탕에 파란 글자입니다. RAM 동작을 확인하는 예제이며 글자는 검사 결과를 보여 주기 위한 것입니다. 도형 그리기 함수의 검증은 아닙니다. 별도 에뮬레이터 검사에서는 플랫폼마다 88개 사례를 확인했습니다. 상수·실행 시 길이 0, 1, 16, 17, 32, 33, 64, 65, 255바이트와 16비트 길이의 256·257바이트를 포함합니다. 실제 기기에서는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\memory_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\memory_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:27
Copy exactly 16 bytes forward; both ranges must be valid and suitably non-overlapping.
kitaqfc/kitaqfc/CodeGenerator.cs:4620
case "__copy16":
if (!Need(2)) return true;
EmitFixedMemcpy(args[0], args[1], 16, ctx); return true;
void EmitFixedMemcpy(Expr dstExpr, Expr srcExpr, int len, FunctionContext ctx)
{
int[] ptr = AcquireTemps(4);
try
{
EmitLoadValue(dstExpr, ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
EmitLoadValue(srcExpr, ctx, 2);
EmitAsm("STA", Mem(ptr[2]));
EmitAsm("STX", Mem(ptr[3]));
for (int i = 0; i < len; i++)
{
EmitAsm("LDY", Imm(i));
EmitAsm("LDA", IndY(ptr[2]));
EmitAsm("STA", IndY(ptr[0]));
}
}
finally { ReleaseTemps(ptr); }
}
// kitaqfc/kitaqfc/Lowerer.cs:2128
if (expr.MatchAny(Tag.Call, out callFunc, out callArgs))
{
string knownCallName;
if (callFunc != null && callFunc.Match(Tag.Name, out knownCallName) &&
knownCallName == "__cgb_is_cgb" &&
callArgs != null && callArgs.Length == 0 &&
// A resolved target configuration can replace the zero-argument CGB query with a literal.
Program.TryGetKnownCgbRuntimeValue(out int knownCgbValue))
{
return Expr.Make(Tag.Integer, knownCgbValue).WithSource(expr.Source);
}
Expr newFunc = LowerExpr(ctx, callFunc, false, prefix);
// Optimization (post-A3): when we must spill a non-atomic argument into a temp,
// prefer a u8 temp if the *parameter type* is u8-like.
// This preserves semantics (implicit truncation to u8) while avoiding needless
// u16 temporaries created by integer promotions.
CType[] paramTypes = null;
string __fname = null;
if (newFunc.Match(Tag.Name, out __fname))
{
paramTypes = ctx.FindFuncParams(__fname);
if (paramTypes == null)
{
CType fnTypeFromName;
if (TryGetCallableFunctionType(ctx.FindTypeOfName(__fname), out fnTypeFromName))
paramTypes = fnTypeFromName.ParamTypes;
}
}
else
{
CType fnTypeFromExpr;
if (TryGetCallableFunctionType(InferType(ctx, newFunc), out fnTypeFromExpr))
paramTypes = fnTypeFromExpr.ParamTypes;
}
Expr[] newArgs = new Expr[callArgs.Length];
for (int i = 0; i < callArgs.Length; i++)
{
Expr a = LowerExpr(ctx, callArgs[i], allowExtract, prefix);
bool preserveConstantArgIntrinsic = false;
if (a.MatchAny(Tag.Call, out Expr argFunc, out Expr[] _argCallArgs) &&
argFunc.Match(Tag.Name, out string argFuncName))
{
preserveConstantArgIntrinsic = (argFuncName == "__bankof" || argFuncName == "__cgb_is_cgb");
}
// Keep evaluable constants and special intrinsic/bank operands visible to code generation instead of spilling them.
bool preserveCompileTimeConstantArg = ctx.Global.TryEvalConstInt(a, out _);
bool preserveFarcallBankArg = (__fname == "__farcall" && i == 0);
if (allowExtract && !preserveFarcallBankArg && !preserveConstantArgIntrinsic && !preserveCompileTimeConstantArg && !IsAtom(a))
{
// If we know the callee's parameter type, use it to choose a smaller temp.
// Example: (u8)(row*32) passed to a u8 param should spill as u8, not u16.
CType spillAs = null;
if (paramTypes != null && i < paramTypes.Length)
{
CType pt = paramTypes[i];
if (IsU8Like(pt)) spillAs = CType.UInt8;
}
if (spillAs == null) spillAs = InferType(ctx, a);
if (!IsAggregateType(spillAs) && SizeOfType(spillAs) <= 2)
a = SpillToTemp(ctx, a, spillAs, prefix);
}
newArgs[i] = a;
}
// Lint: const discard for argument passing
// Warn when passing a const-qualified pointer (or const array decay) to a non-const pointer parameter.
// Suppress when the argument has an explicit cast, or inside __unsafe.
if (!ctx.InUnsafe && paramTypes != null)
{
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
CType pt = paramTypes[i];
if (pt == null) continue;
if (!IsPointerToNonConst(pt)) continue;
if (HasTopLevelCast(newArgs[i])) continue;
CType at = InferType(ctx, newArgs[i]);
if (at != null && (IsPointerToConst(at) || IsArrayOfConst(at)))
{
string fn = __fname ?? "<call>";
WarnConstDiscard(expr, $"argument {i} to '{fn}'");
break; // keep it quiet: one warning per call is enough
}
}
}
if (paramTypes != null)
{
string fn = __fname ?? "<call>";
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
FilePosition argPos = newArgs[i].Source;
if (string.IsNullOrEmpty(argPos.Filename) || argPos.Filename == "<unknown>")
argPos = expr.Source;
WarnImplicitNarrowingIfNeeded(ctx, newArgs[i], InferType(ctx, newArgs[i]), paramTypes[i], argPos,
"argument " + i + " to '" + fn + "'");
}
}
// Lint: __enum_strict argument passing.
if (paramTypes != null)
{
string fn = __fname ?? "<call>";
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
CType pt = paramTypes[i];
if (pt == null) continue;
CType at = InferType(ctx, newArgs[i]);
if (at == null) continue;
if (HasTopLevelCast(newArgs[i])) continue;
if (IsStrictEnum(pt))
{
if (!IsSameEnum(pt, at))
{
Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
"argument {0} to '{1}' expects __enum_strict {2}, got {3}; cast explicitly to silence",
i, fn, pt.Show(), at.Show());
}
}
else if (IsPlainInteger(pt) && IsStrictEnum(at))
{
Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
"argument {0} to '{1}' passes __enum_strict {2} to integer parameter {3}; cast explicitly to silence",
i, fn, at.Show(), pt.Show());
}
}
}
if (!ctx.InUnsafe)
RestrictAliasLint(ctx, newFunc, newArgs, expr.Source);
// __slice(ptr,len) helper: represent a "slice" (ptr+len) in the IR.
// Semantics: evaluate both arguments, result type is the pointer/array (same as arg0).
// Bounds checks are injected only with -Zcheck (see Program.CheckSliceBounds).
// Note: keep the intrinsic name conservative to avoid collisions ("slice" is allowed only
// when there is no declared function named "slice").
if (newFunc.Match(Tag.Name, out string __sfname))
{
bool isSliceIntrinsic = (__sfname == "__slice") || (__sfname == "slice" && ctx.FindFuncParams("slice") == null);
if (isSliceIntrinsic)
{
if (newArgs.Length != 2)
{
Program.Error(expr.Source, ErrorCode.ParseError, "{0}: expected 2 arguments: __slice(ptr,len)", __sfname);
}
else
{
CType pty = InferType(ctx, newArgs[0]);
if (pty == null || !(pty.IsPointer || pty.IsArray))
Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: first argument must be a pointer or array, got: {1}", __sfname, (pty == null ? "<unknown>" : pty.Show()));
CType lty = InferType(ctx, newArgs[1]);
if (lty == null || !lty.IsInteger)
Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: second argument must be an integer length, got: {1}", __sfname, (lty == null ? "<unknown>" : lty.Show()));
}
return Expr.Make(Tag.Slice, newArgs[0], newArgs[1]).WithSource(expr.Source);
}
}
return MakeCall(newFunc, newArgs).WithSource(expr.Source);
}__copy32
길이 인수 없이 src에서 dst로 정확히 32바이트를 복사합니다. CPU가 접근할 수 있는 메모리의 고정 길이 32바이트 레코드나 연속된 16바이트 타일 패턴 두 개에 사용할 수 있습니다. 낮은 주소부터 바이트 단위로 복사하며, 32비트 값을 한 번에 읽거나 뱅크를 전환하는 기능은 아닙니다.
사용 형식
void __copy32(u8* dst, const u8* src);매개변수
dst쓰기 대상의 첫 바이트를 가리키는 포인터입니다. 전체 작업에 필요한 연속 공간을 미리 확보하세요. 버퍼를 자동으로 할당하지는 않습니다.
src읽기 원본의 첫 바이트를 가리키는 포인터입니다. 복사하는 동안 전체 범위를 읽을 수 있어야 하며 내용이 바뀌면 안 됩니다.
반환값
없음(void).
사용 조건과 제한
원본과 대상에는 서로 겹치지 않는 영역을 사용하세요. 낮은 주소부터 복사하며, 겹침을 안전하게 처리하는 memmove 기능은 제공하지 않습니다. 아직 읽지 않은 원본 위치에 쓰면 뒤쪽 데이터가 손상될 수 있습니다.
작업을 완료한 뒤 호출한 곳으로 돌아갑니다. ROM·RAM 뱅크를 전환하거나 버퍼 경계에 맞춰 범위를 잘라 주지 않습니다. 매핑을 유지하고 유효한 범위를 지정하며, 같은 버퍼를 쓰는 인터럽트 처리와 접근을 조정하세요.
FC에서 dst는 PPU 주소가 아닌 CPU 주소입니다. CPU RAM에 복사해도 CHR·네임테이블·팔레트는 갱신되지 않으므로 전송에는 PPU·VRAM API를 사용하세요. 생성된 보조 루틴은 인수·작업 공간을 공유하므로 실행 중에 NMI·IRQ에서 다시 진입하면 안 됩니다.
사용 예
memory_reset();
__copy32(memory_destination+1,memory_source+1);
memory_copy_32=memory_check(32,0);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 예제는 __copy32로 일정한 패턴의 32바이트를 오프셋 1부터 복사합니다. memory_check가 원본과 대상 배열 전체를 비교하며, COPY32 행에는 032가 표시되어야 합니다. 버퍼 첫 바이트는 보호 영역으로 남기고, 복사 범위 뒤쪽 바이트도 바뀌지 않았는지 확인합니다.
전체 프로그램은 작업마다 264바이트 버퍼 두 개를 초기화한 뒤 원본·대상의 모든 바이트와 변경되면 안 되는 앞뒤 보호 영역을 검사합니다. 정상 표시는 COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002, FAILED 000입니다. WORD·BYTE는 복사할 원소의 크기가 아니라 길이의 자료형을 뜻합니다. 숫자는 예제의 검사 결과이며, 반환값이 없는 이 함수들이 반환한 값이 아닙니다.
캡처 화면은 DMG가 흰 바탕에 검은 글자, CGB가 흰 바탕에 파란 글자, FC가 검은 바탕에 파란 글자입니다. RAM 동작을 확인하는 예제이며 글자는 검사 결과를 보여 주기 위한 것입니다. 도형 그리기 함수의 검증은 아닙니다. 별도 에뮬레이터 검사에서는 플랫폼마다 88개 사례를 확인했습니다. 상수·실행 시 길이 0, 1, 16, 17, 32, 33, 64, 65, 255바이트와 16비트 길이의 256·257바이트를 포함합니다. 실제 기기에서는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\memory_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\memory_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:29
Copy exactly 32 bytes forward; bank selection and source lifetime belong to the caller.
kitaqfc/kitaqfc/CodeGenerator.cs:4623
case "__copy32":
if (!Need(2)) return true;
EmitFixedMemcpy(args[0], args[1], 32, ctx); return true;
// Geometry helpers receive byte coordinates; the size array describes ABI packing, not result width.
void EmitFixedMemcpy(Expr dstExpr, Expr srcExpr, int len, FunctionContext ctx)
{
int[] ptr = AcquireTemps(4);
try
{
EmitLoadValue(dstExpr, ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
EmitLoadValue(srcExpr, ctx, 2);
EmitAsm("STA", Mem(ptr[2]));
EmitAsm("STX", Mem(ptr[3]));
for (int i = 0; i < len; i++)
{
EmitAsm("LDY", Imm(i));
EmitAsm("LDA", IndY(ptr[2]));
EmitAsm("STA", IndY(ptr[0]));
}
}
finally { ReleaseTemps(ptr); }
}
// kitaqfc/kitaqfc/Lowerer.cs:2128
if (expr.MatchAny(Tag.Call, out callFunc, out callArgs))
{
string knownCallName;
if (callFunc != null && callFunc.Match(Tag.Name, out knownCallName) &&
knownCallName == "__cgb_is_cgb" &&
callArgs != null && callArgs.Length == 0 &&
// A resolved target configuration can replace the zero-argument CGB query with a literal.
Program.TryGetKnownCgbRuntimeValue(out int knownCgbValue))
{
return Expr.Make(Tag.Integer, knownCgbValue).WithSource(expr.Source);
}
Expr newFunc = LowerExpr(ctx, callFunc, false, prefix);
// Optimization (post-A3): when we must spill a non-atomic argument into a temp,
// prefer a u8 temp if the *parameter type* is u8-like.
// This preserves semantics (implicit truncation to u8) while avoiding needless
// u16 temporaries created by integer promotions.
CType[] paramTypes = null;
string __fname = null;
if (newFunc.Match(Tag.Name, out __fname))
{
paramTypes = ctx.FindFuncParams(__fname);
if (paramTypes == null)
{
CType fnTypeFromName;
if (TryGetCallableFunctionType(ctx.FindTypeOfName(__fname), out fnTypeFromName))
paramTypes = fnTypeFromName.ParamTypes;
}
}
else
{
CType fnTypeFromExpr;
if (TryGetCallableFunctionType(InferType(ctx, newFunc), out fnTypeFromExpr))
paramTypes = fnTypeFromExpr.ParamTypes;
}
Expr[] newArgs = new Expr[callArgs.Length];
for (int i = 0; i < callArgs.Length; i++)
{
Expr a = LowerExpr(ctx, callArgs[i], allowExtract, prefix);
bool preserveConstantArgIntrinsic = false;
if (a.MatchAny(Tag.Call, out Expr argFunc, out Expr[] _argCallArgs) &&
argFunc.Match(Tag.Name, out string argFuncName))
{
preserveConstantArgIntrinsic = (argFuncName == "__bankof" || argFuncName == "__cgb_is_cgb");
}
// Keep evaluable constants and special intrinsic/bank operands visible to code generation instead of spilling them.
bool preserveCompileTimeConstantArg = ctx.Global.TryEvalConstInt(a, out _);
bool preserveFarcallBankArg = (__fname == "__farcall" && i == 0);
if (allowExtract && !preserveFarcallBankArg && !preserveConstantArgIntrinsic && !preserveCompileTimeConstantArg && !IsAtom(a))
{
// If we know the callee's parameter type, use it to choose a smaller temp.
// Example: (u8)(row*32) passed to a u8 param should spill as u8, not u16.
CType spillAs = null;
if (paramTypes != null && i < paramTypes.Length)
{
CType pt = paramTypes[i];
if (IsU8Like(pt)) spillAs = CType.UInt8;
}
if (spillAs == null) spillAs = InferType(ctx, a);
if (!IsAggregateType(spillAs) && SizeOfType(spillAs) <= 2)
a = SpillToTemp(ctx, a, spillAs, prefix);
}
newArgs[i] = a;
}
// Lint: const discard for argument passing
// Warn when passing a const-qualified pointer (or const array decay) to a non-const pointer parameter.
// Suppress when the argument has an explicit cast, or inside __unsafe.
if (!ctx.InUnsafe && paramTypes != null)
{
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
CType pt = paramTypes[i];
if (pt == null) continue;
if (!IsPointerToNonConst(pt)) continue;
if (HasTopLevelCast(newArgs[i])) continue;
CType at = InferType(ctx, newArgs[i]);
if (at != null && (IsPointerToConst(at) || IsArrayOfConst(at)))
{
string fn = __fname ?? "<call>";
WarnConstDiscard(expr, $"argument {i} to '{fn}'");
break; // keep it quiet: one warning per call is enough
}
}
}
if (paramTypes != null)
{
string fn = __fname ?? "<call>";
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
FilePosition argPos = newArgs[i].Source;
if (string.IsNullOrEmpty(argPos.Filename) || argPos.Filename == "<unknown>")
argPos = expr.Source;
WarnImplicitNarrowingIfNeeded(ctx, newArgs[i], InferType(ctx, newArgs[i]), paramTypes[i], argPos,
"argument " + i + " to '" + fn + "'");
}
}
// Lint: __enum_strict argument passing.
if (paramTypes != null)
{
string fn = __fname ?? "<call>";
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
CType pt = paramTypes[i];
if (pt == null) continue;
CType at = InferType(ctx, newArgs[i]);
if (at == null) continue;
if (HasTopLevelCast(newArgs[i])) continue;
if (IsStrictEnum(pt))
{
if (!IsSameEnum(pt, at))
{
Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
"argument {0} to '{1}' expects __enum_strict {2}, got {3}; cast explicitly to silence",
i, fn, pt.Show(), at.Show());
}
}
else if (IsPlainInteger(pt) && IsStrictEnum(at))
{
Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
"argument {0} to '{1}' passes __enum_strict {2} to integer parameter {3}; cast explicitly to silence",
i, fn, at.Show(), pt.Show());
}
}
}
if (!ctx.InUnsafe)
RestrictAliasLint(ctx, newFunc, newArgs, expr.Source);
// __slice(ptr,len) helper: represent a "slice" (ptr+len) in the IR.
// Semantics: evaluate both arguments, result type is the pointer/array (same as arg0).
// Bounds checks are injected only with -Zcheck (see Program.CheckSliceBounds).
// Note: keep the intrinsic name conservative to avoid collisions ("slice" is allowed only
// when there is no declared function named "slice").
if (newFunc.Match(Tag.Name, out string __sfname))
{
bool isSliceIntrinsic = (__sfname == "__slice") || (__sfname == "slice" && ctx.FindFuncParams("slice") == null);
if (isSliceIntrinsic)
{
if (newArgs.Length != 2)
{
Program.Error(expr.Source, ErrorCode.ParseError, "{0}: expected 2 arguments: __slice(ptr,len)", __sfname);
}
else
{
CType pty = InferType(ctx, newArgs[0]);
if (pty == null || !(pty.IsPointer || pty.IsArray))
Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: first argument must be a pointer or array, got: {1}", __sfname, (pty == null ? "<unknown>" : pty.Show()));
CType lty = InferType(ctx, newArgs[1]);
if (lty == null || !lty.IsInteger)
Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: second argument must be an integer length, got: {1}", __sfname, (lty == null ? "<unknown>" : lty.Show()));
}
return Expr.Make(Tag.Slice, newArgs[0], newArgs[1]).WithSource(expr.Source);
}
}
return MakeCall(newFunc, newArgs).WithSource(expr.Source);
}__dot2_q8_8
부호 없는 두 항의 내적을 계산합니다.
사용 형식
u16 __dot2_q8_8(u16 a0, u16 a1, u8 b0, u8 b1);매개변수
a0부호 없는 워드 0~65535입니다.
a1부호 없는 워드 0~65535입니다.
b0부호 없는 계수 0~255입니다.
b1부호 없는 계수 0~255입니다.
반환값
a0*b0+a1*b1의 하위 16비트를 반환합니다.
사용 조건과 제한
FC에서 q8_8/q1_7 이름의 함수는 고정소수점용 오른쪽 시프트 없이 원래 정수 곱을 사용합니다. Q1.7 함수는 대응하는 부호 있는 연산의 별칭입니다. 단위는 호출자가 처리하며, 부호 있는 결과는 u16 비트 패턴으로 반환됩니다.
사용 예
// Compare literal coefficients with values read from RAM.
result[22]=__dot2_q8_8(513,769,64,127);
v0=513;v1=769;v2=64;v3=127;
result[23]=__dot2_q8_8(v0,v1,v2,v3);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
리터럴 입력과 RAM에 저장된 값을 비교합니다. 대표 결과는 부호 없는 16비트 값으로 표시하며 음수는 2의 보수 비트 패턴을 사용합니다. GB는 고정소수점 곱의 크기를 조정하지만 FC는 정수 곱을 반환하므로 같은 입력이라도 결과가 같지 않을 수 있습니다.
이 발췌 코드는 다음 16비트 값을 저장합니다: result[22]=64959, result[23]=64959. 리터럴 입력으로 계산한 각 결과는 대응하는 RAM 입력 결과와 같습니다. 화면에는 대표값을 표시하고, result 배열에는 모든 결과를 보관합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr구현과 소스
kitaqfc/lib/intrinsics.h:63
Return a0*b0+a1*b1 modulo 65536. The FC backend inserts no Q8.8 rescaling or rounding.
kitaqfc/kitaqfc/CodeGenerator.cs:4654
case "__dot2_q8_8": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2,1,1}, funcName);
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__dot3_q8_8
부호 없는 세 항의 내적을 계산합니다.
사용 형식
u16 __dot3_q8_8(u16 a0, u16 a1, u16 a2, u8 b0, u8 b1, u8 b2);매개변수
a0부호 없는 워드 0~65535입니다.
a1부호 없는 워드 0~65535입니다.
a2부호 없는 워드 0~65535입니다.
b0부호 없는 계수 0~255입니다.
b1부호 없는 계수 0~255입니다.
b2부호 없는 계수 0~255입니다.
반환값
a0*b0+a1*b1+a2*b2의 하위 16비트를 반환합니다.
사용 조건과 제한
FC에서 q8_8/q1_7 이름의 함수는 고정소수점용 오른쪽 시프트 없이 원래 정수 곱을 사용합니다. Q1.7 함수는 대응하는 부호 있는 연산의 별칭입니다. 단위는 호출자가 처리하며, 부호 있는 결과는 u16 비트 패턴으로 반환됩니다.
사용 예
// Compare literal coefficients with values read from RAM.
result[24]=__dot3_q8_8(513,769,1025,64,127,3);
v0=513;v1=769;v2=1025;v3=64;v4=127;v5=3;
result[25]=__dot3_q8_8(v0,v1,v2,v3,v4,v5);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
리터럴 입력과 RAM에 저장된 값을 비교합니다. 대표 결과는 부호 없는 16비트 값으로 표시하며 음수는 2의 보수 비트 패턴을 사용합니다. GB는 고정소수점 곱의 크기를 조정하지만 FC는 정수 곱을 반환하므로 같은 입력이라도 결과가 같지 않을 수 있습니다.
이 발췌 코드는 다음 16비트 값을 저장합니다: result[24]=2498, result[25]=2498. 리터럴 입력으로 계산한 각 결과는 대응하는 RAM 입력 결과와 같습니다. 화면에는 대표값을 표시하고, result 배열에는 모든 결과를 보관합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr구현과 소스
kitaqfc/lib/intrinsics.h:65
Sum three unsigned word-by-byte products modulo 65536 without a final scaling shift.
kitaqfc/kitaqfc/CodeGenerator.cs:4655
case "__dot3_q8_8": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2,2,1,1,1}, funcName);
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__exp_pad_read1
__exp_pad_read1는 __pad_read1_d1과 같은 컴파일러 헬퍼를 호출합니다. $4016의 확장 D1 신호를 같은 래치 순서로 읽고 동일한 8비트 결과를 반환하며 부수 효과도 같습니다. 확장 입력 용도임을 나타내는 이름으로, 별도 장치 모드를 켜거나 D0와 D1을 합치지는 않습니다.
사용 형식
u8 __exp_pad_read1(void);반환값
NES 원시 비트 순서의 8비트 눌림 마스크입니다. 설정된 비트가 눌린 버튼을 나타내며 0은 없음을 뜻합니다. 0/1로 정규화한 불리언 값이 아닙니다.
사용 조건과 제한
NES 원시 형식은 A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80입니다. 1인 비트가 눌림을 뜻합니다. nes_pad_*와 원시 __pad_* 결과에는 이 값을 사용하세요. input.h의 공통 BTN_* 마스크와 혼동하지 마세요.
선택한 D1 선에 호환되는 8비트 직렬 패드 신호를 보내는 주변기기와 함께 사용합니다. 연결 장치를 식별하거나 모든 확장 장치 프로토콜을 지원하지는 않습니다. 호출할 때마다 두 컨트롤러 스트림을 다시 래치하므로 한 읽기를 마친 뒤 다음 읽기를 시작하세요. 결과는 새로 읽은 상태이며 input_* 또는 nes_pad1_*의 저장 상태를 갱신하지 않습니다.
공개된 KUROSAKI 컨트롤러 모델은 D0와 고정된 비트 6만 반환합니다. 따라서 일반 버튼을 누르고 있어도 이 에뮬레이터의 D1과 마이크 비트는 항상 0입니다. 제공된 실행 결과는 빌드, 일반 버튼 입력과 이 0 신호 표시를 확인한 것입니다. 주변기기의 0이 아닌 신호, 전기적 타이밍과 실기 호환성은 검증하지 않았습니다. 여기서 0이 나온다고 장치가 정상 작동한다거나 실기에서도 읽을 수 없다는 뜻은 아닙니다.
사용 예
expansion_first=__exp_pad_read1(); // Same helper as __pad_read1_d1.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
일반 D0 입력, 확장 D1 입력과 마이크 디지털 레벨을 비교하는 프로그램입니다. 상태가 캡처될 때까지 컨트롤러 1의 A와 컨트롤러 2의 B를 누릅니다. NORMAL P1과 NORMAL P2는 001과 002입니다. 제공된 KUROSAKI 실행에서는 해당 신호선을 에뮬레이션하지 않으므로 D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL, MIC DIRECT가 모두 000입니다. 캡처 후 표시값은 고정됩니다. 적합한 주변기기와 신호선 구현이 있는 환경에서는 실제 입력에 따라 값이 달라지므로, 이 0을 기대하지 말고 위의 비트 배치에 따라 해석하세요.
공개된 KUROSAKI 컨트롤러 모델은 D0와 고정된 비트 6만 반환합니다. 따라서 일반 버튼을 누르고 있어도 이 에뮬레이터의 D1과 마이크 비트는 항상 0입니다. 제공된 실행 결과는 빌드, 일반 버튼 입력과 이 0 신호 표시를 확인한 것입니다. 주변기기의 0이 아닌 신호, 전기적 타이밍과 실기 호환성은 검증하지 않았습니다. 여기서 0이 나온다고 장치가 정상 작동한다거나 실기에서도 읽을 수 없다는 뜻은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\expansion_input.c -I .\kitaq-docs\samples -I .\kitaqfc\lib -o .\out\expansion_input.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:227
Alias the port-1 D1 controller reader; the attached device must use the expected serial protocol.
__exp_pad_read2
__exp_pad_read2는 __pad_read2_d1과 같은 컴파일러 헬퍼를 호출합니다. $4017의 확장 D1 신호를 같은 래치 순서로 읽고 동일한 8비트 결과를 반환하며 부수 효과도 같습니다. 확장 입력 용도임을 나타내는 이름으로, 별도 장치 모드를 켜거나 D0와 D1을 합치지는 않습니다.
사용 형식
u8 __exp_pad_read2(void);반환값
NES 원시 비트 순서의 8비트 눌림 마스크입니다. 설정된 비트가 눌린 버튼을 나타내며 0은 없음을 뜻합니다. 0/1로 정규화한 불리언 값이 아닙니다.
사용 조건과 제한
NES 원시 형식은 A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80입니다. 1인 비트가 눌림을 뜻합니다. nes_pad_*와 원시 __pad_* 결과에는 이 값을 사용하세요. input.h의 공통 BTN_* 마스크와 혼동하지 마세요.
선택한 D1 선에 호환되는 8비트 직렬 패드 신호를 보내는 주변기기와 함께 사용합니다. 연결 장치를 식별하거나 모든 확장 장치 프로토콜을 지원하지는 않습니다. 호출할 때마다 두 컨트롤러 스트림을 다시 래치하므로 한 읽기를 마친 뒤 다음 읽기를 시작하세요. 결과는 새로 읽은 상태이며 input_* 또는 nes_pad1_*의 저장 상태를 갱신하지 않습니다.
공개된 KUROSAKI 컨트롤러 모델은 D0와 고정된 비트 6만 반환합니다. 따라서 일반 버튼을 누르고 있어도 이 에뮬레이터의 D1과 마이크 비트는 항상 0입니다. 제공된 실행 결과는 빌드, 일반 버튼 입력과 이 0 신호 표시를 확인한 것입니다. 주변기기의 0이 아닌 신호, 전기적 타이밍과 실기 호환성은 검증하지 않았습니다. 여기서 0이 나온다고 장치가 정상 작동한다거나 실기에서도 읽을 수 없다는 뜻은 아닙니다.
사용 예
expansion_second=__exp_pad_read2(); // Same helper as __pad_read2_d1.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
일반 D0 입력, 확장 D1 입력과 마이크 디지털 레벨을 비교하는 프로그램입니다. 상태가 캡처될 때까지 컨트롤러 1의 A와 컨트롤러 2의 B를 누릅니다. NORMAL P1과 NORMAL P2는 001과 002입니다. 제공된 KUROSAKI 실행에서는 해당 신호선을 에뮬레이션하지 않으므로 D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL, MIC DIRECT가 모두 000입니다. 캡처 후 표시값은 고정됩니다. 적합한 주변기기와 신호선 구현이 있는 환경에서는 실제 입력에 따라 값이 달라지므로, 이 0을 기대하지 말고 위의 비트 배치에 따라 해석하세요.
공개된 KUROSAKI 컨트롤러 모델은 D0와 고정된 비트 6만 반환합니다. 따라서 일반 버튼을 누르고 있어도 이 에뮬레이터의 D1과 마이크 비트는 항상 0입니다. 제공된 실행 결과는 빌드, 일반 버튼 입력과 이 0 신호 표시를 확인한 것입니다. 주변기기의 0이 아닌 신호, 전기적 타이밍과 실기 호환성은 검증하지 않았습니다. 여기서 0이 나온다고 장치가 정상 작동한다거나 실기에서도 읽을 수 없다는 뜻은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\expansion_input.c -I .\kitaq-docs\samples -I .\kitaqfc\lib -o .\out\expansion_input.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:229
Alias the port-2 D1 controller reader; this is not an expansion-device discovery operation.
__far_memcpy
ROM 뱅크를 잠시 전환하고 src에서 dst로 주소가 증가하는 순서대로 len바이트를 복사한 뒤, 컴파일러가 저장한 ROM 뱅크로 돌아갑니다. 이 기종에서 __farmemcpy와 __far_memcpy는 같은 구현을 사용합니다.
사용 형식
void __far_memcpy(u8* dst, u8 bank, u16 src, u16 len);매개변수
dst쓰기 대상 메모리입니다. 실제 복사량을 모두 담을 공간을 확보하세요. 0바이트 전송을 제외하면 널 포인터나 잘못된 대상 주소를 검사하지 않습니다.
bank원본 ROM 매핑을 선택하는 8비트 뱅크 값입니다. 카트리지와 컴파일러의 뱅크 배치에서 지원하는 번호를 쓰며, 원본 주소와 별도로 지정합니다.
src선택한 ROM 매핑에서 원본의 16비트 CPU 주소입니다. FC 선언은
u16을 받으므로 소재 포인터는(u16)pointer로 변환합니다.len바이트 수이며 범위는 0~65535입니다. 0이면 메모리를 복사하지 않지만 뱅크 선택과 복구 과정은 실행합니다.
반환값
없음(void).
사용 조건과 제한
원본 전체가 의도한 ROM 매핑에서 읽히고 대상 전체가 쓰기 가능한 메모리 안에 있어야 합니다. 뱅크 창 경계에서 다음 뱅크로 넘어가거나, memmove처럼 겹치는 범위를 처리하거나, VRAM 쓰기 시점을 제어하지 않습니다. VRAM에는 그래픽 전송 API를 사용하세요.
예제의 호출 코드는 고정 뱅크 0에 두어 원본 뱅크를 바꿔도 실행 중인 코드가 사라지지 않도록 합니다. 전송은 동기식입니다. 전송 도중 인터럽트에서 다른 뱅크 전환 복사를 실행하지 마세요. 이 함수만으로 프로그램 전체의 인터럽트 안전성이 확보되지는 않습니다.
사용 예
__far_memcpy(buffer,payload_bank,(u16)payload_pointer,32);
expect(*asset_bank_marker==0xA1 && bank_get_current()==1,27);
for(i=0;i<32;i++) expect(buffer[(__safe_index u8)i]==expected_shape_byte(i),28);
buffer[32]=0x55;
__far_memcpy(buffer+32,2,(u16)asset_shapes,0);
expect(buffer[32]==0x55 && *asset_bank_marker==0xA1,30);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
뱅크 1이 선택된 상태에서 소재 뱅크 2의 패턴 두 개, 32바이트를 복사합니다. 모든 복사 바이트와 뱅크 1 복구를 검사하고, 길이 0 복사에서 확인값 0x55가 그대로인지 검증합니다.
예상 화면의 빨간 줄은 픽셀 좌표 (16,32)에서 시작하며 72×8픽셀입니다. 8×8 삼각형, 테두리 두께 1픽셀인 8×8 사각형, 왼쪽 네 열을 채운 타일을 세 번 반복합니다. 삼각형은 왼쪽 위의 1픽셀에서 맨 아래 줄의 8픽셀까지 넓어집니다.
FC에서 (16,64)의 파란 삼각형과 테두리는 __far_memcpy 결과이며, (16,96)의 초록색 쌍은 asset_load_raw 후 CHR로 전송한 결과입니다. 검은 바탕에서 각 쌍은 16×8픽셀입니다. 숫자 상태값뿐 아니라 복사된 이미지 바이트를 직접 확인할 수 있습니다.
예상 상태 표시는 FAILED CHECKS 000과 FIRST FAILURE 00입니다. 검증은 300프레임 동안 실행한 뒤 글자, 도형 마스크, 색을 예상값과 비교합니다. 에뮬레이터 검증이며 실제 하드웨어 시험은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\asset_banks.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\asset_banks.nes --mapper=mmc1 --board=surom512 --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:86
Switch to the source bank, copy len bytes forward and restore the previous bank. Keep destination RAM and helper code accessible throughout; overlapping copies are not memmove.
__farcall
인수가 없는 이름 있는 함수를 호출합니다. FC 컴파일러는 함수가 배치된 뱅크에 따라 직접 호출하거나 뱅크 복원용 보조 코드를 사용합니다. bank 인수는 평가하지만 실제 선택은 함수 배치에 따라 결정됩니다.
사용 형식
__farcall(bank, function_name);매개변수
bank부호 없는 8비트 ROM 뱅크 번호입니다. 호출 시에는 함수 배치와 일치해야 하고, 읽기 시에는 원본 매핑을 지정합니다. 대상 카트리지에서 유효한 범위를 사용하세요.
func선언된 매개변수 없는 함수의 이름입니다. 이 이름 기반 호출 내장 함수에는 함수 주소를 담은 변수를 전달할 수 없습니다. bank 인수는 콜백 매개변수로 전달되지 않습니다.
반환값
반환형은 호출한 함수에 따릅니다. void 콜백은 값을 반환하지 않고 u8·u16 콜백은 해당 값을 반환합니다. 화면 예제는 void 콜백을 사용합니다.
사용 조건과 제한
이 호출은 사용자 인수를 전달하거나 콜백의 인터럽트 재진입성을 보장하지 않습니다. 콜백 자체의 데이터, 스택, 인터럽트 사용을 올바르게 관리하세요. 뱅크 복귀는 중첩 및 반복 호출로 검사했지만 실제 하드웨어와 임의의 인터럽트 타이밍은 이 예제의 검증 범위가 아닙니다.
FC 전환 번호는 컴파일러의 논리 뱅크 번호입니다. 0은 공통 ROM이며 전환 대상이 아닙니다. 보조 코드는 0을 뱅크 1로 처리합니다. SUROM512에서는 1~30만 유효합니다. 전환 내장 함수에 범위를 벗어난 상수를 직접 전달하면 빌드 오류가 나며, 잘못된 실행 시점 값은 1로 대체되고 매퍼 오류 상태가 설정됩니다. 공통 ROM은 뱅크 0 전환을 요청하지 말고 직접 읽으세요.
사용 예
__farcall(3,callback);
bank_expect(callback_count==2 && callback_byte==0xD3,16);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
완성 프로그램의 이 지점에서 __farcall는 callback_count를 2으로 만듭니다. 콜백은 뱅크 3에서 D3을 읽습니다. 이후 home2를 근거리로 읽어 B2인지 검사함으로써 뱅크 2가 매핑된 상태로 복귀했는지 확인합니다.
완성 프로그램은 뱅크 번호와 근거리 주소로 데이터를 지정하는 방법, 뱅크를 넘는 콜백, 라이브러리 기록과 실제 매핑의 구별을 보여 줍니다. 모든 표시 숫자는 16진수입니다. BANK IN과 BANK OUT은 02, BYTE HEX는 D3, WORD HEX는 5C7A, FAILED CHECKS와 FIRST FAILURE는 모두 00이어야 합니다. CGB와 FC는 파란 글자, DMG는 검은 글자를 표시합니다.
CALL COUNT는 02입니다. far_call 매크로와 __farcall이 각각 콜백을 한 번 호출합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\banked_data_calls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\banked_data_calls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:83
Call a declared zero-argument function; placement selects its bank and its declared type determines the result.
kitaqfc/kitaqfc/CodeGenerator.cs:4131
if (string.Equals(funcName, "__farcall", StringComparison.Ordinal))
{
Expr[] farArgs = args ?? Array.Empty<Expr>();
if (farArgs.Length != 2)
{
Program.Error("error KQ0000: __farcall(bank, func) expects exactly 2 arguments.");
return;
}
string targetName;
if (!farArgs[1].Match(Tag.Name, out targetName))
{
Program.Error("error KQ0000: __farcall(bank, func) requires the 2nd argument to be a function name.");
return;
}
// This intrinsic supplies no callback arguments. A variable name
// is not a static function target, even when its value is a pointer.
if (!_functionParameters.TryGetValue(targetName, out FieldInfo[] farParameters))
{
Program.Error("error KQFC2610: __farcall requires a declared function name; use an ordinary typed call for a function pointer.");
return;
}
if (farParameters.Length != 0)
{
Program.Error("error KQFC2610: __farcall requires a callback with no parameters.");
return;
}
if (Program.NesMapperProfile.IsSurom512 &&
TryEvaluateConstant(farArgs[0], out int constantFarBank) &&
(constantFarBank < 1 || constantFarBank > 30))
{
Program.Error("error KQFC2603 KQFC-SUROM-BANK-RANGE: __farcall constant bank {0} is outside 1..30; common bank 0 is not a switch target.", constantFarBank);
return;
}
// Evaluate the requested far-call bank byte; the following dispatch uses the target function bank metadata.
EmitLoadValue(farArgs[0], ctx, 1);
int callerBank = ctx == null ? 0 : ctx.Bank;
int calleeBank = GetFunctionBank(targetName);
if (ShouldUseFdsOverlayThunk(callerBank, calleeBank))
{
string fdsThunkName = EnsureFdsOverlayThunk(targetName, calleeBank);
EmitAsm("JSR", Abs(fdsThunkName));
RecordCallEdge(targetName, calleeBank, "fds_overlay_thunk", viaThunk: true, viaFarcall: true);
return;
}
bool bankedPrg = Program.NesMapperProfile.SupportsPrgBanking || Program.NesMapperProfile.UsesDuplicatedCommonBank;
bool directSafe = !bankedPrg || calleeBank == 0 || calleeBank == callerBank;
if (directSafe)
{
EmitAsm("JSR", Abs(targetName));
RecordCallEdge(targetName, calleeBank, "direct", viaThunk: false, viaFarcall: true);
return;
}
string thunkName = EnsureBankThunk(targetName, calleeBank);
EmitAsm("JSR", Abs(thunkName));
RecordCallEdge(targetName, calleeBank, "bank_thunk", viaThunk: true, viaFarcall: true);
return;
}__farmemcpy
__farmemcpy는 FC 내장 함수 __far_memcpy의 다른 표기입니다. 인수와 복사·뱅크 복원 루틴이 같으므로 이 표기를 선택해도 전송 방식이나 속도는 달라지지 않습니다. 예제에서는 ROM의 타일 패턴을 RAM으로 읽어 오는 데 사용합니다.
ROM 뱅크를 잠시 전환하고 src에서 dst로 주소가 증가하는 순서대로 len바이트를 복사한 뒤, 컴파일러가 저장한 ROM 뱅크로 돌아갑니다. 이 기종에서 __farmemcpy와 __far_memcpy는 같은 구현을 사용합니다.
사용 형식
void __farmemcpy(u8* dst, u8 bank, u16 src, u16 len);매개변수
dst쓰기 대상 메모리입니다. 실제 복사량을 모두 담을 공간을 확보하세요. 0바이트 전송을 제외하면 널 포인터나 잘못된 대상 주소를 검사하지 않습니다.
bank원본 ROM 매핑을 선택하는 8비트 뱅크 값입니다. 카트리지와 컴파일러의 뱅크 배치에서 지원하는 번호를 쓰며, 원본 주소와 별도로 지정합니다.
src선택한 ROM 매핑에서 원본의 16비트 CPU 주소입니다. FC 선언은
u16을 받으므로 소재 포인터는(u16)pointer로 변환합니다.len바이트 수이며 범위는 0~65535입니다. 0이면 메모리를 복사하지 않지만 뱅크 선택과 복구 과정은 실행합니다.
반환값
없음(void).
사용 조건과 제한
컴파일러가 이 표기를 직접 등록하므로 intrinsics.h에 별도 선언이 없어도 호출할 수 있습니다. 인수는 순서대로 RAM 포인터, 8비트 뱅크 번호, 16비트 CPU 주소, 16비트 바이트 수입니다. src는 ROM 파일 시작점을 기준으로 한 오프셋이 아닙니다.
원본 전체가 의도한 ROM 매핑에서 읽히고 대상 전체가 쓰기 가능한 메모리 안에 있어야 합니다. 뱅크 창 경계에서 다음 뱅크로 넘어가거나, memmove처럼 겹치는 범위를 처리하거나, VRAM 쓰기 시점을 제어하지 않습니다. VRAM에는 그래픽 전송 API를 사용하세요.
예제의 호출 코드는 고정 뱅크 0에 두어 원본 뱅크를 바꿔도 실행 중인 코드가 사라지지 않도록 합니다. 전송은 동기식입니다. 전송 도중 인터럽트에서 다른 뱅크 전환 복사를 실행하지 마세요. 이 함수만으로 프로그램 전체의 인터럽트 안전성이 확보되지는 않습니다.
len이 0이면 대상 바이트는 바뀌지 않지만 뱅크 선택과 복원은 수행됩니다. 반환값은 없습니다.
사용 예
__farmemcpy(buffer,payload_bank,(u16)payload_pointer,32);
expect(*asset_bank_marker==0xA1 && bank_get_current()==1,27);
for(i=0;i<32;i++) expect(buffer[(__safe_index u8)i]==expected_shape_byte(i),28);
buffer[32]=0x55;
__farmemcpy(buffer+32,2,(u16)asset_shapes,0);
expect(buffer[32]==0x55 && *asset_bank_marker==0xA1,30);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
뱅크 1이 선택된 상태에서 소재 뱅크 2의 패턴 두 개, 32바이트를 복사합니다. 모든 복사 바이트와 뱅크 1 복구를 검사하고, 길이 0 복사에서 확인값 0x55가 그대로인지 검증합니다.
예상 화면의 빨간 줄은 픽셀 좌표 (16,32)에서 시작하며 72×8픽셀입니다. 8×8 삼각형, 테두리 두께 1픽셀인 8×8 사각형, 왼쪽 네 열을 채운 타일을 세 번 반복합니다. 삼각형은 왼쪽 위의 1픽셀에서 맨 아래 줄의 8픽셀까지 넓어집니다.
픽셀 좌표 (16,64)의 파란색 삼각형과 사각형 윤곽은 __farmemcpy로 복사한 바이트를 표시합니다. 아래쪽 (16,96)의 초록색 도형 쌍은 asset_load_raw로 불러온 결과입니다. 각 쌍은 검은 배경에서 16×8픽셀을 차지하며, 복사된 바이트를 CHR RAM에 전송하여 표시합니다.
예상 상태 표시는 FAILED CHECKS 000과 FIRST FAILURE 00입니다. 검증은 300프레임 동안 실행한 뒤 글자, 도형 마스크, 색을 예상값과 비교합니다. 에뮬레이터 검증이며 실제 하드웨어 시험은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\far_copy_alias.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\far_copy_alias.nes --mapper=mmc1 --board=surom512 --no-cache --no-disasm구현과 소스
kitaqfc/kitaqfc/CodeGenerator.cs:264
kitaqfc/kitaqfc/CodeGenerator.cs:4668
case "__far_memcpy":
case "__farmemcpy":
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,1,2,2}, "__far_memcpy");__farpeek16
뱅크를 잠시 선택해 addr와 addr+1에서 리틀 엔디언 16비트 값을 읽고 이전 뱅크로 복귀합니다. far_data_read16이 사용하는 내장 함수입니다.
사용 형식
u16 __farpeek16(u8 bank, u16 addr);매개변수
bank부호 없는 8비트 ROM 뱅크 번호입니다. 호출 시에는 함수 배치와 일치해야 하고, 읽기 시에는 원본 매핑을 지정합니다. 대상 카트리지에서 유효한 범위를 사용하세요.
addr선택한 ROM 매핑에서 원본의 16비트 CPU 주소입니다. FC 선언은
u16을 받으므로 소재 포인터는(u16)pointer로 변환합니다.
반환값
읽은 리틀 엔디언 워드를 u16(0~65535)으로 반환합니다.
사용 조건과 제한
선택한 CPU 매핑에서 바이트 또는 워드 전체를 읽을 수 있어야 합니다. 포인터 검사, 주소 잘라내기, 창 경계에서 다음 ROM 뱅크로의 자동 전환은 수행하지 않습니다. 워드 읽기에는 연속된 유효한 두 바이트가 필요합니다.
원거리 읽기와 복사는 동기식이며 컴파일러의 공유 작업 공간을 사용합니다. 처리 중 인터럽트에서 다른 뱅크 전송을 실행해 같은 공간을 덮어쓰지 마세요. GB 복사 보조 코드는 복귀 뱅크에도 공유 HRAM 슬롯 하나를 씁니다. 예제는 경쟁하는 전송 인터럽트 없이 고정 코드에서 실행합니다.
FC 전환 번호는 컴파일러의 논리 뱅크 번호입니다. 0은 공통 ROM이며 전환 대상이 아닙니다. 보조 코드는 0을 뱅크 1로 처리합니다. SUROM512에서는 1~30만 유효합니다. 전환 내장 함수에 범위를 벗어난 상수를 직접 전달하면 빌드 오류가 나며, 잘못된 실행 시점 값은 1로 대체되고 매퍼 오류 상태가 설정됩니다. 공통 ROM은 뱅크 0 전환을 요청하지 말고 직접 읽으세요.
사용 예
bank_expect(__farpeek16(3,(u16)(payload+1))==0x5C7A,14);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 코드 조각은 __farpeek16로 뱅크 3의 payload+1를 읽고 5C7A를 확인합니다. 완성 프로그램은 home2의 근거리 읽기로 뱅크 2 매핑이 복원되었는지도 확인합니다.
완성 프로그램은 뱅크 번호와 근거리 주소로 데이터를 지정하는 방법, 뱅크를 넘는 콜백, 라이브러리 기록과 실제 매핑의 구별을 보여 줍니다. 모든 표시 숫자는 16진수입니다. BANK IN과 BANK OUT은 02, BYTE HEX는 D3, WORD HEX는 5C7A, FAILED CHECKS와 FIRST FAILURE는 모두 00이어야 합니다. CGB와 FC는 파란 글자, DMG는 검은 글자를 표시합니다.
CALL COUNT는 02입니다. far_call 매크로와 __farcall이 각각 콜백을 한 번 호출합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\banked_data_calls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\banked_data_calls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:90
Temporarily select bank and read a little-endian word, then restore the previous mapping.
kitaqfc/kitaqfc/CodeGenerator.cs:8698
EmitHelperStart("__farpeek16");
// args: bank, addr16. Return A=lo, X=hi.
EmitAsm("LDA", Mem(_runtimeCurrentBankAddress));
EmitAsm("PHA");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_prg_set_bank_a"));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", IndY(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("INY");
EmitAsm("LDA", IndY(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("PLA");
EmitAsm("JSR", Abs("__kq_prg_set_bank_a"));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Consume the word count while advancing source/destination pointers; zero count still restores the saved bank.__farpeek8
지정한 뱅크를 잠시 선택해 CPU 주소에서 한 바이트를 읽고 이전 뱅크를 복원합니다. far_data_read8이 사용하는 내장 함수입니다.
사용 형식
u8 __farpeek8(u8 bank, u16 addr);매개변수
bank부호 없는 8비트 ROM 뱅크 번호입니다. 호출 시에는 함수 배치와 일치해야 하고, 읽기 시에는 원본 매핑을 지정합니다. 대상 카트리지에서 유효한 범위를 사용하세요.
addr선택한 ROM 매핑에서 원본의 16비트 CPU 주소입니다. FC 선언은
u16을 받으므로 소재 포인터는(u16)pointer로 변환합니다.
반환값
읽은 바이트를 u8(0~255)로 반환합니다.
사용 조건과 제한
선택한 CPU 매핑에서 바이트 또는 워드 전체를 읽을 수 있어야 합니다. 포인터 검사, 주소 잘라내기, 창 경계에서 다음 ROM 뱅크로의 자동 전환은 수행하지 않습니다. 워드 읽기에는 연속된 유효한 두 바이트가 필요합니다.
원거리 읽기와 복사는 동기식이며 컴파일러의 공유 작업 공간을 사용합니다. 처리 중 인터럽트에서 다른 뱅크 전송을 실행해 같은 공간을 덮어쓰지 마세요. GB 복사 보조 코드는 복귀 뱅크에도 공유 HRAM 슬롯 하나를 씁니다. 예제는 경쟁하는 전송 인터럽트 없이 고정 코드에서 실행합니다.
FC 전환 번호는 컴파일러의 논리 뱅크 번호입니다. 0은 공통 ROM이며 전환 대상이 아닙니다. 보조 코드는 0을 뱅크 1로 처리합니다. SUROM512에서는 1~30만 유효합니다. 전환 내장 함수에 범위를 벗어난 상수를 직접 전달하면 빌드 오류가 나며, 잘못된 실행 시점 값은 1로 대체되고 매퍼 오류 상태가 설정됩니다. 공통 ROM은 뱅크 0 전환을 요청하지 말고 직접 읽으세요.
사용 예
bank_expect(__farpeek8(3,(u16)payload)==0xD3,13);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 코드 조각은 __farpeek8로 뱅크 3의 payload를 읽고 D3를 확인합니다. 완성 프로그램은 home2의 근거리 읽기로 뱅크 2 매핑이 복원되었는지도 확인합니다.
완성 프로그램은 뱅크 번호와 근거리 주소로 데이터를 지정하는 방법, 뱅크를 넘는 콜백, 라이브러리 기록과 실제 매핑의 구별을 보여 줍니다. 모든 표시 숫자는 16진수입니다. BANK IN과 BANK OUT은 02, BYTE HEX는 D3, WORD HEX는 5C7A, FAILED CHECKS와 FIRST FAILURE는 모두 00이어야 합니다. CGB와 FC는 파란 글자, DMG는 검은 글자를 표시합니다.
CALL COUNT는 02입니다. far_call 매크로와 __farcall이 각각 콜백을 한 번 호출합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\banked_data_calls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\banked_data_calls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:88
Temporarily select bank, read one byte at addr and restore the prior bank mapping.
kitaqfc/kitaqfc/CodeGenerator.cs:8683
EmitHelperStart("__farpeek8");
// args: bank, addr16. Return A.
EmitAsm("LDA", Mem(_runtimeCurrentBankAddress));
EmitAsm("PHA");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_prg_set_bank_a"));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", IndY(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("PLA");
EmitAsm("JSR", Abs("__kq_prg_set_bank_a"));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Retain both fetched bytes across bank restoration and return the word in A/X.__fkb_detect
키 입력을 해석하기 전에 키보드 감지용 신호 패턴을 확인합니다.
사용 형식
u8 __fkb_detect(void);반환값
9행 0열을 읽은 값이 0x1E이고 비활성화한 매트릭스를 읽은 값이 0이면 1, 그렇지 않으면 0을 반환합니다. 장치 식별 코드를 읽는 것이 아니라 신호 패턴으로 추정하는 방식입니다.
사용 조건과 제한
이 API는 $4016/$4017에 직접 접근합니다. 같은 포트에서 인터럽트 기반 게임패드·키보드·시리얼 연산을 끼워 넣지 마세요. 컨트롤러의 시프트 위치도 영향을 받을 수 있습니다. 예제는 입력을 캡처하는 동안 NMI를 끄고 화면 초기화 때 다시 켭니다.
$4016에 5를 써서 행 0으로 리셋한 뒤 열 선택 비트를 높은 값에서 낮은 값으로 바꾸며 행을 진행합니다. 생성된 코드는 주변 명령 외에도 리셋 후 NOP 12 CPU 사이클, 열 선택 후 50사이클을 포함합니다. 종료 시 0을 써서 매트릭스를 끕니다.
별도의 회귀 검증은 실행된 CPU 버스 추적으로 행 0~9, 두 열, 9행 스캔, 안정화 대기, 최종 포트 상태를 확인합니다. 실제 키 신호를 주입하는 검증은 아닙니다.
9행에는 키가 없습니다. 조건이 맞지 않으면 즉시 0을 반환합니다. KUROSAKI는 첫 번째 읽기에서 0을 반환하므로 이 환경에서는 감지가 실패합니다.
이 화면은 다운로드 가능한 ROM을 수정 없이 KUROSAKI에서 실행한 것입니다. 현재 입력 버스는 게임패드만 모델링하며 키보드·광선총·시리얼 어댑터 신호는 모델링하지 않습니다. 외부 입력 비트가 0으로 읽히므로 이 캡처로 해당 장치를 검증할 수 없습니다. 실제 키 입력, 광 감지, 통신은 미검증입니다.
사용 예
#include "fc_ports_scene.h"
void main(void){
u8 row;
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
port_result[0]=__fkb_detect();
__fkb_scan(key_matrix);
port_result[1]=__fkb_read_row_col(6,0);
port_result[2]=0;
if(port_result[0] && !(port_result[1]&16))port_result[2]=1;
port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"KEYBOARD STARTUP SNAPSHOT");m_wait();
m_text(2,4,"DEVICE DETECT");port_number(25,4,port_result[0]);m_wait();
m_text(2,6,"ROW 6 COL 0");port_number(25,6,port_result[1]);m_wait();
m_text(2,8,"A KEY - GUARDED");port_number(25,8,port_result[2]);m_wait();
m_text(2,10,"ROW COL0 COL1 (HEX)");m_wait();
for(row=0;row<9;row++){
m_put(3,12+row,'0'+row);port_hex(9,12+row,key_matrix[row*2]);port_hex(15,12+row,key_matrix[row*2+1]);m_wait();
}
m_text(2,23,"RAW MATRIX - NOT ASCII");m_wait();
while(1){m_wait();}
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 KUROSAKI 실행에서 DEVICE DETECT, ROW 6 COL 0, A KEY - GUARDED는 모두 000이고 9개의 COL0/COL1 쌍은 모두 00입니다. A 키는 행 6, 열 0, 비트 4지만 장치 감지에 실패하므로 원시값 0을 키 눌림으로 보고하지 않습니다. 표는 시작 시 한 번 캡처한 값입니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_keyboard.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_keyboard.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_keyboard.nes --frames 120 --png .\out\peripheral_keyboard.png구현과 소스
kitaqfc/lib/intrinsics.h:259
Family BASIC Keyboard. __fkb_scan() writes 18 bytes: 9 rows x 2 columns, each byte containing raw $4017 & 0x1E for that row/column. Probe selected-row and disabled-matrix bit patterns and return 0 or 1; this is a heuristic detection.
kitaqfc/kitaqfc/CodeGenerator.cs:7842
EmitHelperStart("__fkb_detect");
EmitAsm("LDA", Imm(0x09));
EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("JSR", Abs("__fkb_read_row_col"));
EmitAsm("CMP", Imm(0x1E));
string no = NewGeneratedLabel("fkb_no");
EmitAsm("BNE", Rel(no));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x1E));
EmitAsm("BNE", Rel(no));
EmitAsm("LDA", Imm(1));
EmitAsm("RTS");
_assembly.Add(Expr.Make(Tag.Label, no));
EmitAsm("LDA", Imm(0));
EmitAsm("RTS");__fkb_read_row_col
특정 그룹만 필요할 때 18바이트 버퍼 없이 선택한 행과 열 그룹 하나를 읽습니다.
사용 형식
u8 __fkb_read_row_col(u8 row, u8 col);매개변수
row키가 있는 행에는 0..8, 키가 없는 감지용 행에는 9를 지정합니다. 런타임 범위 검사 코드는 생성되지 않습니다.
col0 또는 1입니다. 최하위 비트만 사용하므로 짝수는 그룹 0, 홀수는 그룹 1을 선택합니다.
반환값
비트를 하위 자리로 옮기지 않고 $4017 & 0x1E를 그대로 반환합니다.
사용 조건과 제한
이 API는 $4016/$4017에 직접 접근합니다. 같은 포트에서 인터럽트 기반 게임패드·키보드·시리얼 연산을 끼워 넣지 마세요. 컨트롤러의 시프트 위치도 영향을 받을 수 있습니다. 예제는 입력을 캡처하는 동안 NMI를 끄고 화면 초기화 때 다시 켭니다.
키 그룹은 원시 비트 1~4입니다. 키를 누르면 해당 비트가 0이 되며 아무 키도 누르지 않은 그룹은 0x1E입니다. ASCII 텍스트도 아니고 1이 눌림을 뜻하는 비트맵도 아닙니다. 장치가 없어도 0으로 읽힐 수 있으므로 연결을 확인하지 않고 모든 키가 눌렸다고 해석하지 마세요.
$4016에 5를 써서 행 0으로 리셋한 뒤 열 선택 비트를 높은 값에서 낮은 값으로 바꾸며 행을 진행합니다. 생성된 코드는 주변 명령 외에도 리셋 후 NOP 12 CPU 사이클, 열 선택 후 50사이클을 포함합니다. 종료 시 0을 써서 매트릭스를 끕니다.
별도의 회귀 검증은 실행된 CPU 버스 추적으로 행 0~9, 두 열, 9행 스캔, 안정화 대기, 최종 포트 상태를 확인합니다. 실제 키 신호를 주입하는 검증은 아닙니다.
호출할 때마다 0행으로 되돌린 뒤 원하는 행까지 다시 이동합니다. 이전 호출의 행 위치를 재사용하지 않습니다.
이 화면은 다운로드 가능한 ROM을 수정 없이 KUROSAKI에서 실행한 것입니다. 현재 입력 버스는 게임패드만 모델링하며 키보드·광선총·시리얼 어댑터 신호는 모델링하지 않습니다. 외부 입력 비트가 0으로 읽히므로 이 캡처로 해당 장치를 검증할 수 없습니다. 실제 키 입력, 광 감지, 통신은 미검증입니다.
사용 예
#include "fc_ports_scene.h"
void main(void){
u8 row;
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
port_result[0]=__fkb_detect();
__fkb_scan(key_matrix);
port_result[1]=__fkb_read_row_col(6,0);
port_result[2]=0;
if(port_result[0] && !(port_result[1]&16))port_result[2]=1;
port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"KEYBOARD STARTUP SNAPSHOT");m_wait();
m_text(2,4,"DEVICE DETECT");port_number(25,4,port_result[0]);m_wait();
m_text(2,6,"ROW 6 COL 0");port_number(25,6,port_result[1]);m_wait();
m_text(2,8,"A KEY - GUARDED");port_number(25,8,port_result[2]);m_wait();
m_text(2,10,"ROW COL0 COL1 (HEX)");m_wait();
for(row=0;row<9;row++){
m_put(3,12+row,'0'+row);port_hex(9,12+row,key_matrix[row*2]);port_hex(15,12+row,key_matrix[row*2+1]);m_wait();
}
m_text(2,23,"RAW MATRIX - NOT ASCII");m_wait();
while(1){m_wait();}
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 KUROSAKI 실행에서 DEVICE DETECT, ROW 6 COL 0, A KEY - GUARDED는 모두 000이고 9개의 COL0/COL1 쌍은 모두 00입니다. A 키는 행 6, 열 0, 비트 4지만 장치 감지에 실패하므로 원시값 0을 키 눌림으로 보고하지 않습니다. 표는 시작 시 한 번 캡처한 값입니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_keyboard.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_keyboard.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_keyboard.nes --frames 120 --png .\out\peripheral_keyboard.png구현과 소스
kitaqfc/lib/intrinsics.h:264
Select a matrix row and the low bit of col, then return raw 4017 & 1E. Use rows 0..8 for keys; row 9 is used by detection. No row-range validation is emitted.
kitaqfc/kitaqfc/CodeGenerator.cs:7802
EmitHelperStart("__fkb_read_row_col");
EmitAsm("LDA", Imm(0x05));
EmitAsm("STA", Mem(0x4016));
EmitKeyboardDelayShort();
// Establish row zero, then generate one high-to-low column edge
// per requested advance. Repeated writes of 04 do not clock the counter.
EmitAsm("LDA", Imm(0x04));
EmitAsm("STA", Mem(0x4016));
EmitKeyboardDelayLong();
EmitAsm("LDX", Mem(CallArgBase));
string selectedRow = NewGeneratedLabel("fkb_selected_row");
EmitAsm("BEQ", Rel(selectedRow));
string adv = NewGeneratedLabel("fkb_adv_row");
_assembly.Add(Expr.Make(Tag.Label, adv));
EmitAsm("LDA", Imm(0x06));
EmitAsm("STA", Mem(0x4016));
EmitKeyboardDelayLong();
EmitAsm("LDA", Imm(0x04));
EmitAsm("STA", Mem(0x4016));
EmitKeyboardDelayLong();
EmitAsm("DEX");
EmitAsm("BNE", Rel(adv));
_assembly.Add(Expr.Make(Tag.Label, selectedRow));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(1));
string readNow = NewGeneratedLabel("fkb_read_now");
EmitAsm("BEQ", Rel(readNow));
EmitAsm("LDA", Imm(0x06));
EmitAsm("STA", Mem(0x4016));
EmitKeyboardDelayLong();
_assembly.Add(Expr.Make(Tag.Label, readNow));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x1E));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");__fkb_scan
9개 행과 2개 열 그룹 전체를 스캔해 18바이트에 저장합니다. 한 번의 매트릭스 스캔 결과로 여러 키를 확인할 수 있습니다.
사용 형식
void __fkb_scan(u8* dst18);매개변수
dst18최소 18바이트의 쓰기 가능한 저장 공간입니다.
row*2+col원소에 해당 그룹의 원시 값이 저장됩니다. 널 포인터나 길이는 검사하지 않습니다.
반환값
반환값은 없습니다.
사용 조건과 제한
이 API는 $4016/$4017에 직접 접근합니다. 같은 포트에서 인터럽트 기반 게임패드·키보드·시리얼 연산을 끼워 넣지 마세요. 컨트롤러의 시프트 위치도 영향을 받을 수 있습니다. 예제는 입력을 캡처하는 동안 NMI를 끄고 화면 초기화 때 다시 켭니다.
키 그룹은 원시 비트 1~4입니다. 키를 누르면 해당 비트가 0이 되며 아무 키도 누르지 않은 그룹은 0x1E입니다. ASCII 텍스트도 아니고 1이 눌림을 뜻하는 비트맵도 아닙니다. 장치가 없어도 0으로 읽힐 수 있으므로 연결을 확인하지 않고 모든 키가 눌렸다고 해석하지 마세요.
$4016에 5를 써서 행 0으로 리셋한 뒤 열 선택 비트를 높은 값에서 낮은 값으로 바꾸며 행을 진행합니다. 생성된 코드는 주변 명령 외에도 리셋 후 NOP 12 CPU 사이클, 열 선택 후 50사이클을 포함합니다. 종료 시 0을 써서 매트릭스를 끕니다.
별도의 회귀 검증은 실행된 CPU 버스 추적으로 행 0~9, 두 열, 9행 스캔, 안정화 대기, 최종 포트 상태를 확인합니다. 실제 키 신호를 주입하는 검증은 아닙니다.
각 행에서 0열, 1열 순서로 읽습니다. 모든 키를 동시에 캡처하는 것이 아니라 순차적으로 스캔합니다. 그룹 값이 0이어도 스캔을 조기에 끝내지 않습니다.
이 화면은 다운로드 가능한 ROM을 수정 없이 KUROSAKI에서 실행한 것입니다. 현재 입력 버스는 게임패드만 모델링하며 키보드·광선총·시리얼 어댑터 신호는 모델링하지 않습니다. 외부 입력 비트가 0으로 읽히므로 이 캡처로 해당 장치를 검증할 수 없습니다. 실제 키 입력, 광 감지, 통신은 미검증입니다.
사용 예
#include "fc_ports_scene.h"
void main(void){
u8 row;
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
port_result[0]=__fkb_detect();
__fkb_scan(key_matrix);
port_result[1]=__fkb_read_row_col(6,0);
port_result[2]=0;
if(port_result[0] && !(port_result[1]&16))port_result[2]=1;
port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"KEYBOARD STARTUP SNAPSHOT");m_wait();
m_text(2,4,"DEVICE DETECT");port_number(25,4,port_result[0]);m_wait();
m_text(2,6,"ROW 6 COL 0");port_number(25,6,port_result[1]);m_wait();
m_text(2,8,"A KEY - GUARDED");port_number(25,8,port_result[2]);m_wait();
m_text(2,10,"ROW COL0 COL1 (HEX)");m_wait();
for(row=0;row<9;row++){
m_put(3,12+row,'0'+row);port_hex(9,12+row,key_matrix[row*2]);port_hex(15,12+row,key_matrix[row*2+1]);m_wait();
}
m_text(2,23,"RAW MATRIX - NOT ASCII");m_wait();
while(1){m_wait();}
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 KUROSAKI 실행에서 DEVICE DETECT, ROW 6 COL 0, A KEY - GUARDED는 모두 000이고 9개의 COL0/COL1 쌍은 모두 00입니다. A 키는 행 6, 열 0, 비트 4지만 장치 감지에 실패하므로 원시값 0을 키 눌림으로 보고하지 않습니다. 표는 시작 시 한 번 캡처한 값입니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_keyboard.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_keyboard.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_keyboard.nes --frames 120 --png .\out\peripheral_keyboard.png구현과 소스
kitaqfc/lib/intrinsics.h:261
Write 18 raw masked samples (4017 & 1E), two per row, to caller storage; reset 4016 afterward.
kitaqfc/kitaqfc/CodeGenerator.cs:7773
EmitHelperStart("__fkb_scan");
EmitAsm("LDA", Imm(0x05));
EmitAsm("STA", Mem(0x4016));
EmitKeyboardDelayShort();
EmitAsm("LDY", Imm(0));
EmitAsm("LDX", Imm(9));
string rowLoop = NewGeneratedLabel("fkb_scan_row");
_assembly.Add(Expr.Make(Tag.Label, rowLoop));
EmitAsm("LDA", Imm(0x04));
EmitAsm("STA", Mem(0x4016));
EmitKeyboardDelayLong();
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x1E));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("INY");
EmitAsm("LDA", Imm(0x06));
EmitAsm("STA", Mem(0x4016));
EmitKeyboardDelayLong();
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x1E));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("INY");
EmitAsm("DEX");
EmitAsm("BNE", Rel(rowLoop));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
EmitAsm("RTS");__irq_disable
SEI 명령으로 CPU 상태 레지스터의 I 비트를 설정하여 일반 IRQ 요청을 막습니다. IRQ로 중단되면 안 되는 구간에 사용합니다. NMI 차단, APU 음소거, IRQ 발생 원인의 해제 또는 이전 마스크 저장은 하지 않습니다.
사용 형식
void __irq_disable(void);반환값
없음(void).
사용 조건과 제한
I 비트는 일반 IRQ만 제어하므로 이 구간에서도 NMI가 발생할 수 있습니다. IRQ 차단은 장치의 대기 요청을 해제하거나 NMI와 공유하는 데이터를 보호하지 않습니다. 발생원과 핸들러는 별도로 준비하세요. 이 샘플에서는 IRQ를 발생시키는 주변 기기를 시험하지 않습니다.
사용 예
__irq_disable();
disabled=(__irq_save()&4)>>2; // The CPU I bit must be 1.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
CPU의 IRQ 마스크와 PPU의 NMI 제어가 어떻게 다른지 확인하는 프로그램입니다. 검은 배경의 파란 글자로 IRQ DISABLED I는 001, IRQ ENABLED I는 000, SAVED SECTION I는 001, RESTORED I는 000이어야 합니다. 상태 바이트 전체가 아닌 I 비트만 표시합니다. FAILED CHECKS는 000이어야 합니다.
256번 기다리는 실험이 끝나도록 에뮬레이터를 360프레임 이상 실행하세요. IRQ가 차단되어 있어도 NMI DELTA 3 WAITS는 003이어야 합니다. 1바이트가 순환하므로 NMI DELTA 256 WAITS는 000이며 NMI OFF DELTA도 000입니다. runtime.c를 링크하지 않고 기본 핸들러를 사용합니다. 별도 상태 검증에서는 상태 비트 복원, NMI 비활성화 또는 빈 사용자 핸들러에서의 제한된 실행 관찰 등 21개 사례를 확인합니다. KUROSAKI/NROM에서 검증했으며 실제 하드웨어는 미검증입니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\interrupt_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\font.chr -o .\out\interrupt_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:176
Emit SEI to mask CPU IRQs; NMI is unaffected.
kitaqfc/kitaqfc/CodeGenerator.cs:4733
case "__irq_disable": if (!Need(0)) return true; EmitAsm("SEI"); return true;__irq_enable
CLI 명령으로 CPU 상태 레지스터의 I 비트를 지워 일반 IRQ 요청을 처리할 수 있게 합니다. 매퍼나 APU의 인터럽트 발생원을 설정하지는 않습니다. 해당 발생원을 활성화하기 전에 핸들러와 요청 해제 처리를 준비하세요.
사용 형식
void __irq_enable(void);반환값
없음(void).
사용 조건과 제한
I 비트는 일반 IRQ만 제어하므로 이 구간에서도 NMI가 발생할 수 있습니다. IRQ 차단은 장치의 대기 요청을 해제하거나 NMI와 공유하는 데이터를 보호하지 않습니다. 발생원과 핸들러는 별도로 준비하세요. 이 샘플에서는 IRQ를 발생시키는 주변 기기를 시험하지 않습니다.
사용 예
__irq_enable();
enabled=(__irq_save()&4)>>2; // Save returns the old I bit: 0, then masks IRQ.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
CPU의 IRQ 마스크와 PPU의 NMI 제어가 어떻게 다른지 확인하는 프로그램입니다. 검은 배경의 파란 글자로 IRQ DISABLED I는 001, IRQ ENABLED I는 000, SAVED SECTION I는 001, RESTORED I는 000이어야 합니다. 상태 바이트 전체가 아닌 I 비트만 표시합니다. FAILED CHECKS는 000이어야 합니다.
256번 기다리는 실험이 끝나도록 에뮬레이터를 360프레임 이상 실행하세요. IRQ가 차단되어 있어도 NMI DELTA 3 WAITS는 003이어야 합니다. 1바이트가 순환하므로 NMI DELTA 256 WAITS는 000이며 NMI OFF DELTA도 000입니다. runtime.c를 링크하지 않고 기본 핸들러를 사용합니다. 별도 상태 검증에서는 상태 비트 복원, NMI 비활성화 또는 빈 사용자 핸들러에서의 제한된 실행 관찰 등 21개 사례를 확인합니다. KUROSAKI/NROM에서 검증했으며 실제 하드웨어는 미검증입니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\interrupt_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\font.chr -o .\out\interrupt_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:178
Emit CLI to unmask CPU IRQs; the caller must have a valid interrupt handler.
kitaqfc/kitaqfc/CodeGenerator.cs:4734
case "__irq_enable": if (!Need(0)) return true; EmitAsm("CLI"); return true;
// Return the saved processor status in A after masking IRQs; restore consumes a status byte, not a Boolean.__irq_restore
인수의 상태 바이트를 평가한 뒤 PHA로 스택에 넣고 PLP로 CPU 상태를 복원합니다. 무조건 IRQ를 허용하는 것이 아니라 저장된 IRQ 마스크와 연산 플래그를 복원합니다. 이후 실행되는 일반 C 식은 연산 플래그를 다시 바꿀 수 있습니다.
사용 형식
void __irq_restore(u8 state);매개변수
state__irq_save가 저장한 8비트 CPU 상태값 전체입니다. __irq_restore에 전달하도록 모든 비트를 유지하고 참·거짓으로 바꾸지 마세요. 비트 2가 I이며 1은 IRQ 차단, 0은 허용입니다. PHP가 스택에 넣는 값에는 비트 4와 5도 설정됩니다.
반환값
없음(void).
사용 조건과 제한
I 비트는 일반 IRQ만 제어하므로 이 구간에서도 NMI가 발생할 수 있습니다. IRQ 차단은 장치의 대기 요청을 해제하거나 NMI와 공유하는 데이터를 보호하지 않습니다. 발생원과 핸들러는 별도로 준비하세요. 이 샘플에서는 IRQ를 발생시키는 주변 기기를 시험하지 않습니다.
사용 예
__irq_restore(saved_state); // Restore the caller's original IRQ mask.
restored=(__irq_save()&4)>>2; // Original I was 0; this probe masks it again.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
CPU의 IRQ 마스크와 PPU의 NMI 제어가 어떻게 다른지 확인하는 프로그램입니다. 검은 배경의 파란 글자로 IRQ DISABLED I는 001, IRQ ENABLED I는 000, SAVED SECTION I는 001, RESTORED I는 000이어야 합니다. 상태 바이트 전체가 아닌 I 비트만 표시합니다. FAILED CHECKS는 000이어야 합니다.
256번 기다리는 실험이 끝나도록 에뮬레이터를 360프레임 이상 실행하세요. IRQ가 차단되어 있어도 NMI DELTA 3 WAITS는 003이어야 합니다. 1바이트가 순환하므로 NMI DELTA 256 WAITS는 000이며 NMI OFF DELTA도 000입니다. runtime.c를 링크하지 않고 기본 핸들러를 사용합니다. 별도 상태 검증에서는 상태 비트 복원, NMI 비활성화 또는 빈 사용자 핸들러에서의 제한된 실행 관찰 등 21개 사례를 확인합니다. KUROSAKI/NROM에서 검증했으며 실제 하드웨어는 미검증입니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\interrupt_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\font.chr -o .\out\interrupt_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:182
Restore processor status with PLP, including arithmetic flags and the saved IRQ mask.
kitaqfc/kitaqfc/CodeGenerator.cs:4737
case "__irq_restore": if (!Need(1)) return true; Load1(args[0]); EmitAsm("PHA"); EmitAsm("PLP"); return true;__irq_save
PHP로 CPU 상태 바이트를 저장한 다음 SEI로 IRQ를 막고 PLA로 저장된 바이트를 꺼내 반환합니다. 저장값의 I 비트는 마스크하기 전 상태입니다. __irq_restore와 함께 사용하면 임계 구역이 중첩되어도 원래 IRQ가 차단된 상태를 유지할 수 있습니다.
사용 형식
u8 __irq_save(void);반환값
__irq_save가 저장한 8비트 CPU 상태값 전체입니다. __irq_restore에 전달하도록 모든 비트를 유지하고 참·거짓으로 바꾸지 마세요. 비트 2가 I이며 1은 IRQ 차단, 0은 허용입니다. PHP가 스택에 넣는 값에는 비트 4와 5도 설정됩니다.
사용 조건과 제한
I 비트는 일반 IRQ만 제어하므로 이 구간에서도 NMI가 발생할 수 있습니다. IRQ 차단은 장치의 대기 요청을 해제하거나 NMI와 공유하는 데이터를 보호하지 않습니다. 발생원과 핸들러는 별도로 준비하세요. 이 샘플에서는 IRQ를 발생시키는 주변 기기를 시험하지 않습니다.
사용 예
saved_state=__irq_save(); // Save all processor-status bits, then set I.
inside=(__irq_save()&4)>>2; // IRQ remains masked inside this section.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
CPU의 IRQ 마스크와 PPU의 NMI 제어가 어떻게 다른지 확인하는 프로그램입니다. 검은 배경의 파란 글자로 IRQ DISABLED I는 001, IRQ ENABLED I는 000, SAVED SECTION I는 001, RESTORED I는 000이어야 합니다. 상태 바이트 전체가 아닌 I 비트만 표시합니다. FAILED CHECKS는 000이어야 합니다.
256번 기다리는 실험이 끝나도록 에뮬레이터를 360프레임 이상 실행하세요. IRQ가 차단되어 있어도 NMI DELTA 3 WAITS는 003이어야 합니다. 1바이트가 순환하므로 NMI DELTA 256 WAITS는 000이며 NMI OFF DELTA도 000입니다. runtime.c를 링크하지 않고 기본 핸들러를 사용합니다. 별도 상태 검증에서는 상태 비트 복원, NMI 비활성화 또는 빈 사용자 핸들러에서의 제한된 실행 관찰 등 21개 사례를 확인합니다. KUROSAKI/NROM에서 검증했으며 실제 하드웨어는 미검증입니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\interrupt_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\font.chr -o .\out\interrupt_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:180
Return the processor-status byte captured before SEI. Pass that byte to __irq_restore.
kitaqfc/kitaqfc/CodeGenerator.cs:4736
case "__irq_save": if (!Need(0)) return true; EmitAsm("PHP"); EmitAsm("SEI"); EmitAsm("PLA"); return true;__irq_scanline_set
__mapper_irq_set의 MMC3 래치 설정·재로드 연산 별칭입니다. 이름에 scanline이 있어도 절대 화면 행을 보장하지는 않습니다.
사용 형식
void __irq_scanline_set(u8 line);매개변수
line8비트 카운터 래치값 0~255이며 화면의 절대 Y 좌표가 아닙니다. 재로드 시점과 조건을 만족하는 PPU A12 전이가 발동 시점을 결정합니다. 0도 유효하며 “인터럽트 끄기”를 뜻하지 않습니다.
반환값
반환값은 없습니다.
사용 조건과 제한
내장 함수 선언은 intrinsics.h에 있습니다. nes_mapper_irq_* 매크로를 쓰려면 nes_game.h를 포함하세요. 빌드 시 --mapper로 보드를 선택합니다.
MMC3 전용입니다. __nes_irq 핸들러, 조건을 만족하는 PPU A12 전이, 매퍼 IRQ 활성화, CPU __irq_enable()가 필요합니다. 예제는 배경 패턴 $0000, 스프라이트 패턴 $1000으로 렌더링을 켭니다. 이 예제는 실기에서 정확한 한 스캔라인에 동기화되는지 검증하지 않습니다.
MMC3의 확인 처리와 비활성화는 모두 $E000에 씁니다. 현재 요청을 지우고 이후 요청도 끕니다. 다음 인터럽트를 준비하려면 원하는 시점에 __mapper_irq_enable()를 다시 호출하세요. 두 연산 모두 CPU 인터럽트 마스크나 NMI 활성화 상태는 바꾸지 않습니다.
컴파일러는 사용자 정의 인터럽트 핸들러에 RTI를 생성하지만 핸들러가 바꾸는 A/X/Y 레지스터와 공유 임시 공간은 핸들러가 보존해야 합니다. 예제는 A/X/Y를 저장하고 바이트 증가와 인수 없는 확인 처리만 사용합니다. C 함수 호출을 추가하면 해당 함수가 쓰는 공유 저장 공간도 확인하세요.
사용 예
irq_phase=2;
__irq_scanline_set(7); __mapper_irq_enable();
for (i=0;i<6;i++) { m_wait(); }
mapper_result[17]=irq_count;호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
MMC3 예제는 CHR 표식 19/20, 21/22, 23/24를 표시합니다. CIRAM 읽기 결과는 수직 미러링에서 A B A B, 수평 미러링에서 A A B B입니다. IRQ 수는 01→02→03→03입니다. 처음 세 번의 명시적 활성화는 각각 인터럽트 한 번을 허용하고 비활성화 후에는 수가 변하지 않습니다. 각 단계는 6프레임을 기다립니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_controls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_controls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\mapper_banks.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:324
Write the MMC3 IRQ latch and reload registers. This does not enable IRQ delivery.
__joypad2p_voice
__joypad2p_voice는 __mic_read2p 헬퍼를 호출하는 다른 이름입니다. 둘 다 같은 디지털 마이크 비트를 읽습니다. 이름의 “voice”는 오디오 처리를 뜻하지 않습니다. 별도 호출은 각각 새 샘플을 읽으므로 그 사이에 신호가 바뀌면 값이 달라질 수 있습니다.
패미컴 2번 컨트롤러의 마이크에 해당하는 디지털 신호를 $4016의 비트 2에서 읽습니다. 레지스터를 한 번 읽고 0x04로 마스킹한 뒤 반전 없이 0 또는 1로 정규화합니다. 컨트롤러를 래치하거나 오디오를 녹음하고 음량을 측정하거나 음성을 인식하지 않습니다.
사용 형식
u8 __joypad2p_voice(void);반환값
$4016 & 0x04가 0이 아니면 정확히 1, 그 외에는 0입니다. 읽어 온 디지털 레벨이며 버튼 마스크, 진폭, 주파수 또는 말한 단어 수가 아닙니다.
사용 조건과 제한
마이크는 2번 컨트롤러에 있지만 이 헬퍼는 $4017이 아니라 $4016에 접근합니다. 스트로브가 낮을 때 레지스터를 읽으면 컨트롤러 직렬 스트림도 진행될 수 있으므로 다른 컨트롤러의 8비트 읽기 루프 중간에 마이크 읽기를 끼워 넣지 마세요. 입력 이력 캐시는 갱신하지 않습니다.
공개된 KUROSAKI 컨트롤러 모델은 D0와 고정된 비트 6만 반환합니다. 따라서 일반 버튼을 누르고 있어도 이 에뮬레이터의 D1과 마이크 비트는 항상 0입니다. 제공된 실행 결과는 빌드, 일반 버튼 입력과 이 0 신호 표시를 확인한 것입니다. 주변기기의 0이 아닌 신호, 전기적 타이밍과 실기 호환성은 검증하지 않았습니다. 여기서 0이 나온다고 장치가 정상 작동한다거나 실기에서도 읽을 수 없다는 뜻은 아닙니다.
사용 예
voice=__joypad2p_voice();호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
일반 D0 입력, 확장 D1 입력과 마이크 디지털 레벨을 비교하는 프로그램입니다. 상태가 캡처될 때까지 컨트롤러 1의 A와 컨트롤러 2의 B를 누릅니다. NORMAL P1과 NORMAL P2는 001과 002입니다. 제공된 KUROSAKI 실행에서는 해당 신호선을 에뮬레이션하지 않으므로 D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL, MIC DIRECT가 모두 000입니다. 캡처 후 표시값은 고정됩니다. 적합한 주변기기와 신호선 구현이 있는 환경에서는 실제 입력에 따라 값이 달라지므로, 이 0을 기대하지 말고 위의 비트 배치에 따라 해석하세요.
공개된 KUROSAKI 컨트롤러 모델은 D0와 고정된 비트 6만 반환합니다. 따라서 일반 버튼을 누르고 있어도 이 에뮬레이터의 D1과 마이크 비트는 항상 0입니다. 제공된 실행 결과는 빌드, 일반 버튼 입력과 이 0 신호 표시를 확인한 것입니다. 주변기기의 0이 아닌 신호, 전기적 타이밍과 실기 호환성은 검증하지 않았습니다. 여기서 0이 나온다고 장치가 정상 작동한다거나 실기에서도 읽을 수 없다는 뜻은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\expansion_input.c -I .\kitaq-docs\samples -I .\kitaqfc\lib -o .\out\expansion_input.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:231
Return controller-port 4016 bit 2 normalized to 0 or 1; this is a microphone level bit, not PCM audio.
__mac16
부호 없는 곱셈 누산을 수행합니다.
사용 형식
u16 __mac16(u16 acc, u16 a, u8 b);매개변수
acc더할 16비트 누산값입니다.
a부호 없는 워드 0~65535입니다.
b부호 없는 계수 0~255입니다.
반환값
acc+a*b의 하위 16비트를 반환합니다.
사용 조건과 제한
FC에서 q8_8/q1_7 이름의 함수는 고정소수점용 오른쪽 시프트 없이 원래 정수 곱을 사용합니다. Q1.7 함수는 대응하는 부호 있는 연산의 별칭입니다. 단위는 호출자가 처리하며, 부호 있는 결과는 u16 비트 패턴으로 반환됩니다.
사용 예
// Compare literal coefficients with values read from RAM.
result[8]=__mac16(65500,513,127);
v0=65500;v1=513;v2=127;
result[9]=__mac16(v0,v1,v2);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
리터럴 입력과 RAM에 저장된 값을 비교합니다. 대표 결과는 부호 없는 16비트 값으로 표시하며 음수는 2의 보수 비트 패턴을 사용합니다. GB는 고정소수점 곱의 크기를 조정하지만 FC는 정수 곱을 반환하므로 같은 입력이라도 결과가 같지 않을 수 있습니다.
이 발췌 코드는 다음 16비트 값을 저장합니다: result[8]=65115, result[9]=65115. 리터럴 입력으로 계산한 각 결과는 대응하는 RAM 입력 결과와 같습니다. 화면에는 대표값을 표시하고, result 배열에는 모든 결과를 보관합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr구현과 소스
kitaqfc/lib/intrinsics.h:55
Return acc+a*b modulo 65536 using unsigned multiplication.
kitaqfc/kitaqfc/CodeGenerator.cs:4650
case "__mac16": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2,1}, funcName);
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__manhattan
두 점 사이의 X 좌표 차이와 Y 좌표 차이의 절댓값을 더합니다.
사용 형식
u8 __manhattan(u8 x1, u8 y1, u8 x2, u8 y2);매개변수
x1첫 번째 점의 X 좌표 0~255입니다.
y1첫 번째 점의 Y 좌표 0~255입니다.
x2두 번째 점의 X 좌표 0~255입니다.
y2두 번째 점의 Y 좌표 0~255입니다.
반환값
|x1−x2|+|y1−y2|의 하위 바이트를 반환합니다. 255를 넘는 거리는 래핑됩니다.
사용 예
result[5]=__manhattan(2,3,9,11);result[6]=__manhattan(0,0,255,255);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
내부, 경계, 외부 점, 너비 0을 검사합니다. INSIDE=1, RIGHT EDGE=0, LEFT EDGE=0, HIGH X=1입니다. 거리 15, 래핑된 거리 254, 배열 인덱스 64/65280도 확인합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_coordinates.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_coordinates.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr구현과 소스
kitaqfc/lib/intrinsics.h:33
Return abs(x1-x2)+abs(y1-y2) modulo 256; large distances are not saturated.
kitaqfc/kitaqfc/CodeGenerator.cs:4629
case "__manhattan":
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1,1}, "__manhattan");
void EmitHelperGeometryAndRng()
{
EmitHelperStart("__xy_in_rect");
string xyFalse = NewGeneratedLabel("xy_in_rect_false");
string xyDone = NewGeneratedLabel("xy_in_rect_done");
// return (x-rx < rw) && (y-ry < rh), using unsigned subtract to avoid rx+rw overflow.
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("SEC");
EmitAsm("SBC", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(xyFalse));
EmitAsm("CMP", Mem(CallArgBase + 4));
EmitAsm("BCS", Rel(xyFalse));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("SEC");
EmitAsm("SBC", Mem(CallArgBase + 3));
EmitAsm("BCC", Rel(xyFalse));
EmitAsm("CMP", Mem(CallArgBase + 5));
EmitAsm("BCS", Rel(xyFalse));
EmitAsm("LDA", Imm(1));
EmitAsm("JMP", Abs(xyDone));
_assembly.Add(Expr.Make(Tag.Label, xyFalse));
EmitAsm("LDA", Imm(0));
_assembly.Add(Expr.Make(Tag.Label, xyDone));
EmitAsm("RTS");
EmitHelperStart("__manhattan");
// dx = abs(x1-x2)
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("SEC");
EmitAsm("SBC", Mem(CallArgBase + 2));
string mdxOk = NewGeneratedLabel("manhattan_dx_ok");
EmitAsm("BCS", Rel(mdxOk));
EmitAsm("EOR", Imm(0xFF));
EmitAsm("CLC");
EmitAsm("ADC", Imm(1));
_assembly.Add(Expr.Make(Tag.Label, mdxOk));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
// dy = abs(y1-y2), then add dx.
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("SEC");
EmitAsm("SBC", Mem(CallArgBase + 3));
string mdyOk = NewGeneratedLabel("manhattan_dy_ok");
EmitAsm("BCS", Rel(mdyOk));
EmitAsm("EOR", Imm(0xFF));
EmitAsm("CLC");
EmitAsm("ADC", Imm(1));
_assembly.Add(Expr.Make(Tag.Label, mdyOk));
EmitAsm("CLC");
EmitAsm("ADC", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Save X-coordinate separately while multiplying width by Y, then add X with carry into the high byte.
EmitHelperStart("__map_index");
// args: x, y, width. Return y * width + x as u16.
EmitAsm("LDA", Mem(CallArgBase)); // x
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Mem(CallArgBase + 1)); // y
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", Mem(CallArgBase + 2)); // width as u16 multiplicand
EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("CLC");
EmitAsm("ADC", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("TXA");
EmitAsm("ADC", Imm(0));
EmitAsm("TAX");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Replace both bytes of the persistent generator state with the supplied seed.
EmitHelperStart("__rng_seed");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeRngLoAddress));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeRngHiAddress));
EmitAsm("RTS");
EmitHelperStart("__rng8");
// 16-bit Galois LFSR: if low bit set, shift right then xor $B400.
EmitAsm("LDA", Mem(_runtimeRngLoAddress));
EmitAsm("ORA", Mem(_runtimeRngHiAddress));
// Replace the all-zero LFSR state with a deterministic nonzero seed before advancing.
string seeded = NewGeneratedLabel("rng_seeded");
EmitAsm("BNE", Rel(seeded));
EmitAsm("LDA", Imm(0x5A));
EmitAsm("STA", Mem(_runtimeRngLoAddress));
EmitAsm("LDA", Imm(0xA5));
EmitAsm("STA", Mem(_runtimeRngHiAddress));
_assembly.Add(Expr.Make(Tag.Label, seeded));
EmitAsm("LDA", Mem(_runtimeRngLoAddress));
EmitAsm("AND", Imm(1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LSR", Mem(_runtimeRngHiAddress));
EmitAsm("ROR", Mem(_runtimeRngLoAddress));
string rngNoXor = NewGeneratedLabel("rng_no_xor");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("BEQ", Rel(rngNoXor));
EmitAsm("LDA", Mem(_runtimeRngHiAddress));
EmitAsm("EOR", Imm(0xB4));
EmitAsm("STA", Mem(_runtimeRngHiAddress));
_assembly.Add(Expr.Make(Tag.Label, rngNoXor));
EmitAsm("LDA", Mem(_runtimeRngLoAddress));
EmitAsm("EOR", Mem(_runtimeRngHiAddress));
EmitAsm("RTS");
}__map_index
셀 좌표를 행 우선 배열 인덱스로 변환합니다.
사용 형식
u16 __map_index(u8 x, u8 y, u8 width);매개변수
x열 0~255이며, 보통 width보다 작아야 합니다.
y행 0~255입니다.
width한 행의 셀 수 0~255입니다.
반환값
y*width+x를 u16으로 반환합니다. 좌표는 검사하지 않습니다.
사용 예
result[7]=__map_index(4,3,20);result[8]=__map_index(255,255,255);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
내부, 경계, 외부 점, 너비 0을 검사합니다. INSIDE=1, RIGHT EDGE=0, LEFT EDGE=0, HIGH X=1입니다. 거리 15, 래핑된 거리 254, 배열 인덱스 64/65280도 확인합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_coordinates.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_coordinates.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr구현과 소스
kitaqfc/lib/intrinsics.h:35
Return y*width+x as a word without checking coordinates against map dimensions.
kitaqfc/kitaqfc/CodeGenerator.cs:4631
case "__map_index":
if (TryEmitMapIndexInline(args, ctx)) return true;
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1}, "__map_index");
void EmitHelperGeometryAndRng()
{
EmitHelperStart("__xy_in_rect");
string xyFalse = NewGeneratedLabel("xy_in_rect_false");
string xyDone = NewGeneratedLabel("xy_in_rect_done");
// return (x-rx < rw) && (y-ry < rh), using unsigned subtract to avoid rx+rw overflow.
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("SEC");
EmitAsm("SBC", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(xyFalse));
EmitAsm("CMP", Mem(CallArgBase + 4));
EmitAsm("BCS", Rel(xyFalse));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("SEC");
EmitAsm("SBC", Mem(CallArgBase + 3));
EmitAsm("BCC", Rel(xyFalse));
EmitAsm("CMP", Mem(CallArgBase + 5));
EmitAsm("BCS", Rel(xyFalse));
EmitAsm("LDA", Imm(1));
EmitAsm("JMP", Abs(xyDone));
_assembly.Add(Expr.Make(Tag.Label, xyFalse));
EmitAsm("LDA", Imm(0));
_assembly.Add(Expr.Make(Tag.Label, xyDone));
EmitAsm("RTS");
EmitHelperStart("__manhattan");
// dx = abs(x1-x2)
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("SEC");
EmitAsm("SBC", Mem(CallArgBase + 2));
string mdxOk = NewGeneratedLabel("manhattan_dx_ok");
EmitAsm("BCS", Rel(mdxOk));
EmitAsm("EOR", Imm(0xFF));
EmitAsm("CLC");
EmitAsm("ADC", Imm(1));
_assembly.Add(Expr.Make(Tag.Label, mdxOk));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
// dy = abs(y1-y2), then add dx.
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("SEC");
EmitAsm("SBC", Mem(CallArgBase + 3));
string mdyOk = NewGeneratedLabel("manhattan_dy_ok");
EmitAsm("BCS", Rel(mdyOk));
EmitAsm("EOR", Imm(0xFF));
EmitAsm("CLC");
EmitAsm("ADC", Imm(1));
_assembly.Add(Expr.Make(Tag.Label, mdyOk));
EmitAsm("CLC");
EmitAsm("ADC", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Save X-coordinate separately while multiplying width by Y, then add X with carry into the high byte.
EmitHelperStart("__map_index");
// args: x, y, width. Return y * width + x as u16.
EmitAsm("LDA", Mem(CallArgBase)); // x
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Mem(CallArgBase + 1)); // y
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", Mem(CallArgBase + 2)); // width as u16 multiplicand
EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("CLC");
EmitAsm("ADC", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("TXA");
EmitAsm("ADC", Imm(0));
EmitAsm("TAX");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Replace both bytes of the persistent generator state with the supplied seed.
EmitHelperStart("__rng_seed");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeRngLoAddress));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeRngHiAddress));
EmitAsm("RTS");
EmitHelperStart("__rng8");
// 16-bit Galois LFSR: if low bit set, shift right then xor $B400.
EmitAsm("LDA", Mem(_runtimeRngLoAddress));
EmitAsm("ORA", Mem(_runtimeRngHiAddress));
// Replace the all-zero LFSR state with a deterministic nonzero seed before advancing.
string seeded = NewGeneratedLabel("rng_seeded");
EmitAsm("BNE", Rel(seeded));
EmitAsm("LDA", Imm(0x5A));
EmitAsm("STA", Mem(_runtimeRngLoAddress));
EmitAsm("LDA", Imm(0xA5));
EmitAsm("STA", Mem(_runtimeRngHiAddress));
_assembly.Add(Expr.Make(Tag.Label, seeded));
EmitAsm("LDA", Mem(_runtimeRngLoAddress));
EmitAsm("AND", Imm(1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LSR", Mem(_runtimeRngHiAddress));
EmitAsm("ROR", Mem(_runtimeRngLoAddress));
string rngNoXor = NewGeneratedLabel("rng_no_xor");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("BEQ", Rel(rngNoXor));
EmitAsm("LDA", Mem(_runtimeRngHiAddress));
EmitAsm("EOR", Imm(0xB4));
EmitAsm("STA", Mem(_runtimeRngHiAddress));
_assembly.Add(Expr.Make(Tag.Label, rngNoXor));
EmitAsm("LDA", Mem(_runtimeRngLoAddress));
EmitAsm("EOR", Mem(_runtimeRngHiAddress));
EmitAsm("RTS");
}__mapper_id
빌드 시 선택한 매퍼 번호를 반환합니다. 보드별 초기화나 표시에 사용할 수 있습니다.
사용 형식
u8 __mapper_id(void);반환값
8비트 매퍼 번호입니다. NROM=0, MMC1=1, UxROM=2, CNROM=3, MMC3=4, MMC5=5, AxROM=7, VRC6=24, FME7=69, VRC7=85입니다. 컴파일러가 상수를 생성하며 실행 중 하드웨어를 조사하지 않습니다.
사용 조건과 제한
내장 함수 선언은 intrinsics.h에 있습니다. nes_mapper_irq_* 매크로를 쓰려면 nes_game.h를 포함하세요. 빌드 시 --mapper로 보드를 선택합니다.
사용 예
mapper_result[0] = __mapper_id();호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
MMC3 예제는 CHR 표식 19/20, 21/22, 23/24를 표시합니다. CIRAM 읽기 결과는 수직 미러링에서 A B A B, 수평 미러링에서 A A B B입니다. IRQ 수는 01→02→03→03입니다. 처음 세 번의 명시적 활성화는 각각 인터럽트 한 번을 허용하고 비활성화 후에는 수가 변하지 않습니다. 각 단계는 6프레임을 기다립니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_controls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_controls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\mapper_banks.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:314
Mapper Return the low byte of the mapper number selected for this build; no cartridge probe is performed.
__mapper_irq_ack
IRQ 핸들러에서 MMC3 요청을 확인 처리합니다. $E000에 쓰므로 이후 IRQ 요청도 비활성화합니다.
사용 형식
void __mapper_irq_ack(void);반환값
반환값은 없습니다.
사용 조건과 제한
내장 함수 선언은 intrinsics.h에 있습니다. nes_mapper_irq_* 매크로를 쓰려면 nes_game.h를 포함하세요. 빌드 시 --mapper로 보드를 선택합니다.
MMC3 전용입니다. __nes_irq 핸들러, 조건을 만족하는 PPU A12 전이, 매퍼 IRQ 활성화, CPU __irq_enable()가 필요합니다. 예제는 배경 패턴 $0000, 스프라이트 패턴 $1000으로 렌더링을 켭니다. 이 예제는 실기에서 정확한 한 스캔라인에 동기화되는지 검증하지 않습니다.
MMC3의 확인 처리와 비활성화는 모두 $E000에 씁니다. 현재 요청을 지우고 이후 요청도 끕니다. 다음 인터럽트를 준비하려면 원하는 시점에 __mapper_irq_enable()를 다시 호출하세요. 두 연산 모두 CPU 인터럽트 마스크나 NMI 활성화 상태는 바꾸지 않습니다.
컴파일러는 사용자 정의 인터럽트 핸들러에 RTI를 생성하지만 핸들러가 바꾸는 A/X/Y 레지스터와 공유 임시 공간은 핸들러가 보존해야 합니다. 예제는 A/X/Y를 저장하고 바이트 증가와 인수 없는 확인 처리만 사용합니다. C 함수 호출을 추가하면 해당 함수가 쓰는 공유 저장 공간도 확인하세요.
사용 예
void __nes_irq(void) {
__asm {
PHA
TXA
PHA
TYA
PHA
}
irq_count++;
if (irq_phase == 1) { nes_mapper_irq_ack(); }
else { __mapper_irq_ack(); }
__asm {
PLA
TAY
PLA
TAX
PLA
}
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
MMC3 예제는 CHR 표식 19/20, 21/22, 23/24를 표시합니다. CIRAM 읽기 결과는 수직 미러링에서 A B A B, 수평 미러링에서 A A B B입니다. IRQ 수는 01→02→03→03입니다. 처음 세 번의 명시적 활성화는 각각 인터럽트 한 번을 허용하고 비활성화 후에는 수가 변하지 않습니다. 각 단계는 6프레임을 기다립니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_controls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_controls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\mapper_banks.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:332
Use the same E000 write as disable; explicitly re-enable when another interrupt is needed.
__mapper_irq_disable
$E000에 써서 MMC3 IRQ 요청을 지우고 이후 요청을 비활성화합니다.
사용 형식
void __mapper_irq_disable(void);반환값
반환값은 없습니다.
사용 조건과 제한
내장 함수 선언은 intrinsics.h에 있습니다. nes_mapper_irq_* 매크로를 쓰려면 nes_game.h를 포함하세요. 빌드 시 --mapper로 보드를 선택합니다.
MMC3 전용입니다. __nes_irq 핸들러, 조건을 만족하는 PPU A12 전이, 매퍼 IRQ 활성화, CPU __irq_enable()가 필요합니다. 예제는 배경 패턴 $0000, 스프라이트 패턴 $1000으로 렌더링을 켭니다. 이 예제는 실기에서 정확한 한 스캔라인에 동기화되는지 검증하지 않습니다.
MMC3의 확인 처리와 비활성화는 모두 $E000에 씁니다. 현재 요청을 지우고 이후 요청도 끕니다. 다음 인터럽트를 준비하려면 원하는 시점에 __mapper_irq_enable()를 다시 호출하세요. 두 연산 모두 CPU 인터럽트 마스크나 NMI 활성화 상태는 바꾸지 않습니다.
컴파일러는 사용자 정의 인터럽트 핸들러에 RTI를 생성하지만 핸들러가 바꾸는 A/X/Y 레지스터와 공유 임시 공간은 핸들러가 보존해야 합니다. 예제는 A/X/Y를 저장하고 바이트 증가와 인수 없는 확인 처리만 사용합니다. C 함수 호출을 추가하면 해당 함수가 쓰는 공유 저장 공간도 확인하세요.
사용 예
__irq_disable(); __mapper_irq_disable();호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
MMC3 예제는 CHR 표식 19/20, 21/22, 23/24를 표시합니다. CIRAM 읽기 결과는 수직 미러링에서 A B A B, 수평 미러링에서 A A B B입니다. IRQ 수는 01→02→03→03입니다. 처음 세 번의 명시적 활성화는 각각 인터럽트 한 번을 허용하고 비활성화 후에는 수가 변하지 않습니다. 각 단계는 6프레임을 기다립니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_controls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_controls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\mapper_banks.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:330
Write MMC3 E000 to disable and acknowledge mapper IRQs.
__mapper_irq_enable
$E001에 써서 MMC3가 카운터 조건을 만족할 때 IRQ를 발생시킬 수 있게 합니다. 래치 설정이나 재로드 요청은 하지 않습니다.
사용 형식
void __mapper_irq_enable(void);반환값
반환값은 없습니다.
사용 조건과 제한
내장 함수 선언은 intrinsics.h에 있습니다. nes_mapper_irq_* 매크로를 쓰려면 nes_game.h를 포함하세요. 빌드 시 --mapper로 보드를 선택합니다.
MMC3 전용입니다. __nes_irq 핸들러, 조건을 만족하는 PPU A12 전이, 매퍼 IRQ 활성화, CPU __irq_enable()가 필요합니다. 예제는 배경 패턴 $0000, 스프라이트 패턴 $1000으로 렌더링을 켭니다. 이 예제는 실기에서 정확한 한 스캔라인에 동기화되는지 검증하지 않습니다.
MMC3의 확인 처리와 비활성화는 모두 $E000에 씁니다. 현재 요청을 지우고 이후 요청도 끕니다. 다음 인터럽트를 준비하려면 원하는 시점에 __mapper_irq_enable()를 다시 호출하세요. 두 연산 모두 CPU 인터럽트 마스크나 NMI 활성화 상태는 바꾸지 않습니다.
컴파일러는 사용자 정의 인터럽트 핸들러에 RTI를 생성하지만 핸들러가 바꾸는 A/X/Y 레지스터와 공유 임시 공간은 핸들러가 보존해야 합니다. 예제는 A/X/Y를 저장하고 바이트 증가와 인수 없는 확인 처리만 사용합니다. C 함수 호출을 추가하면 해당 함수가 쓰는 공유 저장 공간도 확인하세요.
사용 예
__mapper_irq_set(37); __mapper_irq_enable(); __irq_enable();
for (i=0;i<6;i++) { m_wait(); }
mapper_result[15]=irq_count;호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
MMC3 예제는 CHR 표식 19/20, 21/22, 23/24를 표시합니다. CIRAM 읽기 결과는 수직 미러링에서 A B A B, 수평 미러링에서 A A B B입니다. IRQ 수는 01→02→03→03입니다. 처음 세 번의 명시적 활성화는 각각 인터럽트 한 번을 허용하고 비활성화 후에는 수가 변하지 않습니다. 각 단계는 6프레임을 기다립니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_controls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_controls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\mapper_banks.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:328
Write MMC3 E001. The CPU IRQ mask and a valid handler are separate caller responsibilities.
__mapper_irq_set
$C000의 MMC3 IRQ 래치에 쓰고 $C001에서 다음 재로드를 요청합니다. 인터럽트 활성화는 별도 작업입니다.
사용 형식
void __mapper_irq_set(u8 scanline);매개변수
scanline8비트 카운터 래치값 0~255이며 화면의 절대 Y 좌표가 아닙니다. 재로드 시점과 조건을 만족하는 PPU A12 전이가 발동 시점을 결정합니다. 0도 유효하며 “인터럽트 끄기”를 뜻하지 않습니다.
반환값
반환값은 없습니다.
사용 조건과 제한
내장 함수 선언은 intrinsics.h에 있습니다. nes_mapper_irq_* 매크로를 쓰려면 nes_game.h를 포함하세요. 빌드 시 --mapper로 보드를 선택합니다.
MMC3 전용입니다. __nes_irq 핸들러, 조건을 만족하는 PPU A12 전이, 매퍼 IRQ 활성화, CPU __irq_enable()가 필요합니다. 예제는 배경 패턴 $0000, 스프라이트 패턴 $1000으로 렌더링을 켭니다. 이 예제는 실기에서 정확한 한 스캔라인에 동기화되는지 검증하지 않습니다.
MMC3의 확인 처리와 비활성화는 모두 $E000에 씁니다. 현재 요청을 지우고 이후 요청도 끕니다. 다음 인터럽트를 준비하려면 원하는 시점에 __mapper_irq_enable()를 다시 호출하세요. 두 연산 모두 CPU 인터럽트 마스크나 NMI 활성화 상태는 바꾸지 않습니다.
컴파일러는 사용자 정의 인터럽트 핸들러에 RTI를 생성하지만 핸들러가 바꾸는 A/X/Y 레지스터와 공유 임시 공간은 핸들러가 보존해야 합니다. 예제는 A/X/Y를 저장하고 바이트 증가와 인수 없는 확인 처리만 사용합니다. C 함수 호출을 추가하면 해당 함수가 쓰는 공유 저장 공간도 확인하세요.
사용 예
__mapper_irq_set(37); __mapper_irq_enable(); __irq_enable();
for (i=0;i<6;i++) { m_wait(); }
mapper_result[15]=irq_count;호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
MMC3 예제는 CHR 표식 19/20, 21/22, 23/24를 표시합니다. CIRAM 읽기 결과는 수직 미러링에서 A B A B, 수평 미러링에서 A A B B입니다. IRQ 수는 01→02→03→03입니다. 처음 세 번의 명시적 활성화는 각각 인터럽트 한 번을 허용하고 비활성화 후에는 수가 변하지 않습니다. 각 단계는 6프레임을 기다립니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_controls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_controls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\mapper_banks.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:326
Alias the MMC3 IRQ latch/reload operation; other mapper profiles are rejected.
__memcpy
src에서 dst로 지정한 바이트 수만큼 복사합니다. 길이가 16비트이므로 RAM의 테이블 전체처럼 255바이트를 넘는 데이터도 복사할 수 있습니다. 낮은 주소부터 차례로 읽고 씁니다.
사용 형식
void __memcpy(u8* dst, const u8* src, u16 len);매개변수
dst쓰기 대상의 첫 바이트를 가리키는 포인터입니다. 전체 작업에 필요한 연속 공간을 미리 확보하세요. 버퍼를 자동으로 할당하지는 않습니다.
src읽기 원본의 첫 바이트를 가리키는 포인터입니다. 복사하는 동안 전체 범위를 읽을 수 있어야 하며 내용이 바뀌면 안 됩니다.
lenu16으로 지정하는 바이트 수(0~65535)이며 원소 개수가 아닙니다. 자료형의 최댓값만큼 큰 버퍼를 실제 기기에서 확보할 수 있다는 뜻은 아닙니다. 전체 범위가 현재 메모리 매핑 안에 들어가야 합니다.
반환값
없음(void).
사용 조건과 제한
길이가 0이면 바이트를 전송하지 않습니다. 단, 인수 식의 부수 효과는 실행됩니다. 예제에서는 포인터를 반환하는 함수 두 개를 인수로 넘겨, 복사 길이가 0이어도 두 함수가 모두 호출됨을 확인합니다.
원본과 대상에는 서로 겹치지 않는 영역을 사용하세요. 낮은 주소부터 복사하며, 겹침을 안전하게 처리하는 memmove 기능은 제공하지 않습니다. 아직 읽지 않은 원본 위치에 쓰면 뒤쪽 데이터가 손상될 수 있습니다.
작업을 완료한 뒤 호출한 곳으로 돌아갑니다. ROM·RAM 뱅크를 전환하거나 버퍼 경계에 맞춰 범위를 잘라 주지 않습니다. 매핑을 유지하고 유효한 범위를 지정하며, 같은 버퍼를 쓰는 인터럽트 처리와 접근을 조정하세요.
FC에서 dst는 PPU 주소가 아닌 CPU 주소입니다. CPU RAM에 복사해도 CHR·네임테이블·팔레트는 갱신되지 않으므로 전송에는 PPU·VRAM API를 사용하세요. 생성된 보조 루틴은 인수·작업 공간을 공유하므로 실행 중에 NMI·IRQ에서 다시 진입하면 안 됩니다.
사용 예
memory_reset();
__memcpy(memory_destination+1,memory_source+1,257);
memory_copy_word=memory_check(257,0); // 257 copied bytes; guards unchanged.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 예제는 __memcpy로 일정한 패턴의 257바이트를 오프셋 1부터 복사합니다. memory_check가 원본과 대상 배열 전체를 비교하며, COPY WORD 행에는 257가 표시되어야 합니다. 버퍼 첫 바이트는 보호 영역으로 남기고, 복사 범위 뒤쪽 바이트도 바뀌지 않았는지 확인합니다.
전체 프로그램은 작업마다 264바이트 버퍼 두 개를 초기화한 뒤 원본·대상의 모든 바이트와 변경되면 안 되는 앞뒤 보호 영역을 검사합니다. 정상 표시는 COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002, FAILED 000입니다. WORD·BYTE는 복사할 원소의 크기가 아니라 길이의 자료형을 뜻합니다. 숫자는 예제의 검사 결과이며, 반환값이 없는 이 함수들이 반환한 값이 아닙니다.
캡처 화면은 DMG가 흰 바탕에 검은 글자, CGB가 흰 바탕에 파란 글자, FC가 검은 바탕에 파란 글자입니다. RAM 동작을 확인하는 예제이며 글자는 검사 결과를 보여 주기 위한 것입니다. 도형 그리기 함수의 검증은 아닙니다. 별도 에뮬레이터 검사에서는 플랫폼마다 88개 사례를 확인했습니다. 상수·실행 시 길이 0, 1, 16, 17, 32, 33, 64, 65, 255바이트와 16비트 길이의 256·257바이트를 포함합니다. 실제 기기에서는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\memory_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\memory_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:19
memory / bit / math Copy len bytes forward between readable/writable CPU ranges; overlapping copies are not memmove. Zero length performs no memory transfer; argument side effects are evaluated before expansion.
kitaqfc/kitaqfc/CodeGenerator.cs:4607
case "__memcpy":
if (TryEmitMemcpyOrMemsetInline(funcName, args, ctx, isSet: false, lenIndex: 2, lenSize: 2)) return true;
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2,2}, "__memcpy");
bool TryEmitMemcpyOrMemsetInline(string funcName, Expr[] args, FunctionContext ctx, bool isSet, int lenIndex, int lenSize)
{
args = args ?? Array.Empty<Expr>();
int expected = isSet ? 3 : 3;
if (args.Length != expected) return false;
if (!TryEvaluateConstant(args[lenIndex], out int len)) return false;
if (len < 0 || len > 32) return false;
int valueConst = 0;
if (isSet && !TryEvaluateConstant(args[1], out valueConst)) return false;
// A zero-length expansion emits no address or fill evaluation in this helper.
if (len == 0) return true;
if (isSet)
{
int[] ptr = AcquireTemps(2);
try
{
EmitLoadValue(args[0], ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
for (int i = 0; i < len; i++)
{
EmitAsm("LDY", Imm(i));
EmitAsm("LDA", Imm(valueConst));
EmitAsm("STA", IndY(ptr[0]));
}
}
finally { ReleaseTemps(ptr); }
return true;
}
EmitFixedMemcpy(args[0], args[1], len, ctx);
return true;
}
void EmitHelperMemoryAndBit()
{
// Promote the byte count to a word by clearing its high byte before entering memcpy.
EmitHelperStart("__memcpy_small");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 5));
EmitAsm("JMP", Abs("__memcpy"));
// Advance both argument pointers and decrement the word count in place; overlapping copies are not reversed.
EmitHelperStart("__memcpy");
string mloop = NewGeneratedLabel("memcpy_loop");
string mdone = NewGeneratedLabel("memcpy_done");
EmitAsm("LDA", Mem(CallArgBase + 4));
EmitAsm("ORA", Mem(CallArgBase + 5));
EmitAsm("BEQ", Rel(mdone));
_assembly.Add(Expr.Make(Tag.Label, mloop));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", IndY(CallArgBase + 2));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("INC", Mem(CallArgBase + 2));
string ms1 = NewGeneratedLabel("memcpy_src_page_ok");
EmitAsm("BNE", Rel(ms1));
EmitAsm("INC", Mem(CallArgBase + 3));
_assembly.Add(Expr.Make(Tag.Label, ms1));
EmitAsm("INC", Mem(CallArgBase));
string md1 = NewGeneratedLabel("memcpy_dst_page_ok");
EmitAsm("BNE", Rel(md1));
EmitAsm("INC", Mem(CallArgBase + 1));
_assembly.Add(Expr.Make(Tag.Label, md1));
EmitAsm("LDA", Mem(CallArgBase + 4));
string mlononzero = NewGeneratedLabel("memcpy_lenlo_nonzero");
EmitAsm("BNE", Rel(mlononzero));
EmitAsm("DEC", Mem(CallArgBase + 5));
_assembly.Add(Expr.Make(Tag.Label, mlononzero));
EmitAsm("DEC", Mem(CallArgBase + 4));
EmitAsm("LDA", Mem(CallArgBase + 4));
EmitAsm("ORA", Mem(CallArgBase + 5));
EmitAsm("BNE", Rel(mloop));
_assembly.Add(Expr.Make(Tag.Label, mdone));
EmitAsm("RTS");
// Clear the high count byte for the small-fill wrapper.
EmitHelperStart("__memset_small");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 4));
EmitAsm("JMP", Abs("__memset"));
// Advance the destination pointer while consuming the word count in the shared argument area.
EmitHelperStart("__memset");
string sloop = NewGeneratedLabel("memset_loop");
string sdone = NewGeneratedLabel("memset_done");
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("ORA", Mem(CallArgBase + 4));
EmitAsm("BEQ", Rel(sdone));
_assembly.Add(Expr.Make(Tag.Label, sloop));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("INC", Mem(CallArgBase));
string sd1 = NewGeneratedLabel("memset_dst_page_ok");
EmitAsm("BNE", Rel(sd1));
EmitAsm("INC", Mem(CallArgBase + 1));
_assembly.Add(Expr.Make(Tag.Label, sd1));
EmitAsm("LDA", Mem(CallArgBase + 3));
string slononzero = NewGeneratedLabel("memset_lenlo_nonzero");
EmitAsm("BNE", Rel(slononzero));
EmitAsm("DEC", Mem(CallArgBase + 4));
_assembly.Add(Expr.Make(Tag.Label, slononzero));
EmitAsm("DEC", Mem(CallArgBase + 3));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("ORA", Mem(CallArgBase + 4));
EmitAsm("BNE", Rel(sloop));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("RTS");
}
void EmitFixedMemcpy(Expr dstExpr, Expr srcExpr, int len, FunctionContext ctx)
{
int[] ptr = AcquireTemps(4);
try
{
EmitLoadValue(dstExpr, ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
EmitLoadValue(srcExpr, ctx, 2);
EmitAsm("STA", Mem(ptr[2]));
EmitAsm("STX", Mem(ptr[3]));
for (int i = 0; i < len; i++)
{
EmitAsm("LDY", Imm(i));
EmitAsm("LDA", IndY(ptr[2]));
EmitAsm("STA", IndY(ptr[0]));
}
}
finally { ReleaseTemps(ptr); }
}
// kitaqfc/kitaqfc/Lowerer.cs:2128
if (expr.MatchAny(Tag.Call, out callFunc, out callArgs))
{
string knownCallName;
if (callFunc != null && callFunc.Match(Tag.Name, out knownCallName) &&
knownCallName == "__cgb_is_cgb" &&
callArgs != null && callArgs.Length == 0 &&
// A resolved target configuration can replace the zero-argument CGB query with a literal.
Program.TryGetKnownCgbRuntimeValue(out int knownCgbValue))
{
return Expr.Make(Tag.Integer, knownCgbValue).WithSource(expr.Source);
}
Expr newFunc = LowerExpr(ctx, callFunc, false, prefix);
// Optimization (post-A3): when we must spill a non-atomic argument into a temp,
// prefer a u8 temp if the *parameter type* is u8-like.
// This preserves semantics (implicit truncation to u8) while avoiding needless
// u16 temporaries created by integer promotions.
CType[] paramTypes = null;
string __fname = null;
if (newFunc.Match(Tag.Name, out __fname))
{
paramTypes = ctx.FindFuncParams(__fname);
if (paramTypes == null)
{
CType fnTypeFromName;
if (TryGetCallableFunctionType(ctx.FindTypeOfName(__fname), out fnTypeFromName))
paramTypes = fnTypeFromName.ParamTypes;
}
}
else
{
CType fnTypeFromExpr;
if (TryGetCallableFunctionType(InferType(ctx, newFunc), out fnTypeFromExpr))
paramTypes = fnTypeFromExpr.ParamTypes;
}
Expr[] newArgs = new Expr[callArgs.Length];
for (int i = 0; i < callArgs.Length; i++)
{
Expr a = LowerExpr(ctx, callArgs[i], allowExtract, prefix);
bool preserveConstantArgIntrinsic = false;
if (a.MatchAny(Tag.Call, out Expr argFunc, out Expr[] _argCallArgs) &&
argFunc.Match(Tag.Name, out string argFuncName))
{
preserveConstantArgIntrinsic = (argFuncName == "__bankof" || argFuncName == "__cgb_is_cgb");
}
// Keep evaluable constants and special intrinsic/bank operands visible to code generation instead of spilling them.
bool preserveCompileTimeConstantArg = ctx.Global.TryEvalConstInt(a, out _);
bool preserveFarcallBankArg = (__fname == "__farcall" && i == 0);
if (allowExtract && !preserveFarcallBankArg && !preserveConstantArgIntrinsic && !preserveCompileTimeConstantArg && !IsAtom(a))
{
// If we know the callee's parameter type, use it to choose a smaller temp.
// Example: (u8)(row*32) passed to a u8 param should spill as u8, not u16.
CType spillAs = null;
if (paramTypes != null && i < paramTypes.Length)
{
CType pt = paramTypes[i];
if (IsU8Like(pt)) spillAs = CType.UInt8;
}
if (spillAs == null) spillAs = InferType(ctx, a);
if (!IsAggregateType(spillAs) && SizeOfType(spillAs) <= 2)
a = SpillToTemp(ctx, a, spillAs, prefix);
}
newArgs[i] = a;
}
// Lint: const discard for argument passing
// Warn when passing a const-qualified pointer (or const array decay) to a non-const pointer parameter.
// Suppress when the argument has an explicit cast, or inside __unsafe.
if (!ctx.InUnsafe && paramTypes != null)
{
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
CType pt = paramTypes[i];
if (pt == null) continue;
if (!IsPointerToNonConst(pt)) continue;
if (HasTopLevelCast(newArgs[i])) continue;
CType at = InferType(ctx, newArgs[i]);
if (at != null && (IsPointerToConst(at) || IsArrayOfConst(at)))
{
string fn = __fname ?? "<call>";
WarnConstDiscard(expr, $"argument {i} to '{fn}'");
break; // keep it quiet: one warning per call is enough
}
}
}
if (paramTypes != null)
{
string fn = __fname ?? "<call>";
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
FilePosition argPos = newArgs[i].Source;
if (string.IsNullOrEmpty(argPos.Filename) || argPos.Filename == "<unknown>")
argPos = expr.Source;
WarnImplicitNarrowingIfNeeded(ctx, newArgs[i], InferType(ctx, newArgs[i]), paramTypes[i], argPos,
"argument " + i + " to '" + fn + "'");
}
}
// Lint: __enum_strict argument passing.
if (paramTypes != null)
{
string fn = __fname ?? "<call>";
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
CType pt = paramTypes[i];
if (pt == null) continue;
CType at = InferType(ctx, newArgs[i]);
if (at == null) continue;
if (HasTopLevelCast(newArgs[i])) continue;
if (IsStrictEnum(pt))
{
if (!IsSameEnum(pt, at))
{
Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
"argument {0} to '{1}' expects __enum_strict {2}, got {3}; cast explicitly to silence",
i, fn, pt.Show(), at.Show());
}
}
else if (IsPlainInteger(pt) && IsStrictEnum(at))
{
Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
"argument {0} to '{1}' passes __enum_strict {2} to integer parameter {3}; cast explicitly to silence",
i, fn, at.Show(), pt.Show());
}
}
}
if (!ctx.InUnsafe)
RestrictAliasLint(ctx, newFunc, newArgs, expr.Source);
// __slice(ptr,len) helper: represent a "slice" (ptr+len) in the IR.
// Semantics: evaluate both arguments, result type is the pointer/array (same as arg0).
// Bounds checks are injected only with -Zcheck (see Program.CheckSliceBounds).
// Note: keep the intrinsic name conservative to avoid collisions ("slice" is allowed only
// when there is no declared function named "slice").
if (newFunc.Match(Tag.Name, out string __sfname))
{
bool isSliceIntrinsic = (__sfname == "__slice") || (__sfname == "slice" && ctx.FindFuncParams("slice") == null);
if (isSliceIntrinsic)
{
if (newArgs.Length != 2)
{
Program.Error(expr.Source, ErrorCode.ParseError, "{0}: expected 2 arguments: __slice(ptr,len)", __sfname);
}
else
{
CType pty = InferType(ctx, newArgs[0]);
if (pty == null || !(pty.IsPointer || pty.IsArray))
Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: first argument must be a pointer or array, got: {1}", __sfname, (pty == null ? "<unknown>" : pty.Show()));
CType lty = InferType(ctx, newArgs[1]);
if (lty == null || !lty.IsInteger)
Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: second argument must be an integer length, got: {1}", __sfname, (lty == null ? "<unknown>" : lty.Show()));
}
return Expr.Make(Tag.Slice, newArgs[0], newArgs[1]).WithSource(expr.Source);
}
}
return MakeCall(newFunc, newArgs).WithSource(expr.Source);
}__memcpy_small
8비트 길이를 사용해 최대 255바이트의 블록을 복사합니다. 전송 길이가 한 바이트에 들어갈 때 사용하세요. _small은 길이의 자료형을 뜻하며 특정 실행 시간이나 생성 코드 크기를 보장하지는 않습니다.
사용 형식
void __memcpy_small(u8* dst, const u8* src, u8 len);매개변수
dst쓰기 대상의 첫 바이트를 가리키는 포인터입니다. 전체 작업에 필요한 연속 공간을 미리 확보하세요. 버퍼를 자동으로 할당하지는 않습니다.
src읽기 원본의 첫 바이트를 가리키는 포인터입니다. 복사하는 동안 전체 범위를 읽을 수 있어야 하며 내용이 바뀌면 안 됩니다.
lenu8로 지정하는 0~255의 바이트 수입니다. 256바이트 블록 전체를 뜻하려고 256을 넘기면 안 됩니다. 이 인수의 자료형에 들어가지 않습니다. 256바이트 이상은_small이 없는 함수를 사용하세요.
반환값
없음(void).
사용 조건과 제한
길이가 0이면 바이트를 전송하지 않습니다. 단, 인수 식의 부수 효과는 실행됩니다. 예제에서는 포인터를 반환하는 함수 두 개를 인수로 넘겨, 복사 길이가 0이어도 두 함수가 모두 호출됨을 확인합니다.
원본과 대상에는 서로 겹치지 않는 영역을 사용하세요. 낮은 주소부터 복사하며, 겹침을 안전하게 처리하는 memmove 기능은 제공하지 않습니다. 아직 읽지 않은 원본 위치에 쓰면 뒤쪽 데이터가 손상될 수 있습니다.
작업을 완료한 뒤 호출한 곳으로 돌아갑니다. ROM·RAM 뱅크를 전환하거나 버퍼 경계에 맞춰 범위를 잘라 주지 않습니다. 매핑을 유지하고 유효한 범위를 지정하며, 같은 버퍼를 쓰는 인터럽트 처리와 접근을 조정하세요.
FC에서 dst는 PPU 주소가 아닌 CPU 주소입니다. CPU RAM에 복사해도 CHR·네임테이블·팔레트는 갱신되지 않으므로 전송에는 PPU·VRAM API를 사용하세요. 생성된 보조 루틴은 인수·작업 공간을 공유하므로 실행 중에 NMI·IRQ에서 다시 진입하면 안 됩니다.
사용 예
memory_reset();
__memcpy_small(memory_destination+1,memory_source+1,255);
memory_copy_byte=memory_check(255,0); // Maximum representable byte count.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 예제는 __memcpy_small로 일정한 패턴의 255바이트를 오프셋 1부터 복사합니다. memory_check가 원본과 대상 배열 전체를 비교하며, COPY BYTE 행에는 255가 표시되어야 합니다. 버퍼 첫 바이트는 보호 영역으로 남기고, 복사 범위 뒤쪽 바이트도 바뀌지 않았는지 확인합니다.
전체 프로그램은 작업마다 264바이트 버퍼 두 개를 초기화한 뒤 원본·대상의 모든 바이트와 변경되면 안 되는 앞뒤 보호 영역을 검사합니다. 정상 표시는 COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002, FAILED 000입니다. WORD·BYTE는 복사할 원소의 크기가 아니라 길이의 자료형을 뜻합니다. 숫자는 예제의 검사 결과이며, 반환값이 없는 이 함수들이 반환한 값이 아닙니다.
캡처 화면은 DMG가 흰 바탕에 검은 글자, CGB가 흰 바탕에 파란 글자, FC가 검은 바탕에 파란 글자입니다. RAM 동작을 확인하는 예제이며 글자는 검사 결과를 보여 주기 위한 것입니다. 도형 그리기 함수의 검증은 아닙니다. 별도 에뮬레이터 검사에서는 플랫폼마다 88개 사례를 확인했습니다. 상수·실행 시 길이 0, 1, 16, 17, 32, 33, 64, 65, 255바이트와 16비트 길이의 256·257바이트를 포함합니다. 실제 기기에서는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\memory_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\memory_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:23
Forward copy with an 8-bit length, zero through 255; the dynamic helper promotes it to a word count.
kitaqfc/kitaqfc/CodeGenerator.cs:4610
case "__memcpy_small":
if (TryEmitMemcpyOrMemsetInline(funcName, args, ctx, isSet: false, lenIndex: 2, lenSize: 1)) return true;
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2,1}, "__memcpy_small");
bool TryEmitMemcpyOrMemsetInline(string funcName, Expr[] args, FunctionContext ctx, bool isSet, int lenIndex, int lenSize)
{
args = args ?? Array.Empty<Expr>();
int expected = isSet ? 3 : 3;
if (args.Length != expected) return false;
if (!TryEvaluateConstant(args[lenIndex], out int len)) return false;
if (len < 0 || len > 32) return false;
int valueConst = 0;
if (isSet && !TryEvaluateConstant(args[1], out valueConst)) return false;
// A zero-length expansion emits no address or fill evaluation in this helper.
if (len == 0) return true;
if (isSet)
{
int[] ptr = AcquireTemps(2);
try
{
EmitLoadValue(args[0], ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
for (int i = 0; i < len; i++)
{
EmitAsm("LDY", Imm(i));
EmitAsm("LDA", Imm(valueConst));
EmitAsm("STA", IndY(ptr[0]));
}
}
finally { ReleaseTemps(ptr); }
return true;
}
EmitFixedMemcpy(args[0], args[1], len, ctx);
return true;
}
void EmitHelperMemoryAndBit()
{
// Promote the byte count to a word by clearing its high byte before entering memcpy.
EmitHelperStart("__memcpy_small");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 5));
EmitAsm("JMP", Abs("__memcpy"));
// Advance both argument pointers and decrement the word count in place; overlapping copies are not reversed.
EmitHelperStart("__memcpy");
string mloop = NewGeneratedLabel("memcpy_loop");
string mdone = NewGeneratedLabel("memcpy_done");
EmitAsm("LDA", Mem(CallArgBase + 4));
EmitAsm("ORA", Mem(CallArgBase + 5));
EmitAsm("BEQ", Rel(mdone));
_assembly.Add(Expr.Make(Tag.Label, mloop));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", IndY(CallArgBase + 2));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("INC", Mem(CallArgBase + 2));
string ms1 = NewGeneratedLabel("memcpy_src_page_ok");
EmitAsm("BNE", Rel(ms1));
EmitAsm("INC", Mem(CallArgBase + 3));
_assembly.Add(Expr.Make(Tag.Label, ms1));
EmitAsm("INC", Mem(CallArgBase));
string md1 = NewGeneratedLabel("memcpy_dst_page_ok");
EmitAsm("BNE", Rel(md1));
EmitAsm("INC", Mem(CallArgBase + 1));
_assembly.Add(Expr.Make(Tag.Label, md1));
EmitAsm("LDA", Mem(CallArgBase + 4));
string mlononzero = NewGeneratedLabel("memcpy_lenlo_nonzero");
EmitAsm("BNE", Rel(mlononzero));
EmitAsm("DEC", Mem(CallArgBase + 5));
_assembly.Add(Expr.Make(Tag.Label, mlononzero));
EmitAsm("DEC", Mem(CallArgBase + 4));
EmitAsm("LDA", Mem(CallArgBase + 4));
EmitAsm("ORA", Mem(CallArgBase + 5));
EmitAsm("BNE", Rel(mloop));
_assembly.Add(Expr.Make(Tag.Label, mdone));
EmitAsm("RTS");
// Clear the high count byte for the small-fill wrapper.
EmitHelperStart("__memset_small");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 4));
EmitAsm("JMP", Abs("__memset"));
// Advance the destination pointer while consuming the word count in the shared argument area.
EmitHelperStart("__memset");
string sloop = NewGeneratedLabel("memset_loop");
string sdone = NewGeneratedLabel("memset_done");
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("ORA", Mem(CallArgBase + 4));
EmitAsm("BEQ", Rel(sdone));
_assembly.Add(Expr.Make(Tag.Label, sloop));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("INC", Mem(CallArgBase));
string sd1 = NewGeneratedLabel("memset_dst_page_ok");
EmitAsm("BNE", Rel(sd1));
EmitAsm("INC", Mem(CallArgBase + 1));
_assembly.Add(Expr.Make(Tag.Label, sd1));
EmitAsm("LDA", Mem(CallArgBase + 3));
string slononzero = NewGeneratedLabel("memset_lenlo_nonzero");
EmitAsm("BNE", Rel(slononzero));
EmitAsm("DEC", Mem(CallArgBase + 4));
_assembly.Add(Expr.Make(Tag.Label, slononzero));
EmitAsm("DEC", Mem(CallArgBase + 3));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("ORA", Mem(CallArgBase + 4));
EmitAsm("BNE", Rel(sloop));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("RTS");
}
void EmitFixedMemcpy(Expr dstExpr, Expr srcExpr, int len, FunctionContext ctx)
{
int[] ptr = AcquireTemps(4);
try
{
EmitLoadValue(dstExpr, ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
EmitLoadValue(srcExpr, ctx, 2);
EmitAsm("STA", Mem(ptr[2]));
EmitAsm("STX", Mem(ptr[3]));
for (int i = 0; i < len; i++)
{
EmitAsm("LDY", Imm(i));
EmitAsm("LDA", IndY(ptr[2]));
EmitAsm("STA", IndY(ptr[0]));
}
}
finally { ReleaseTemps(ptr); }
}
// kitaqfc/kitaqfc/Lowerer.cs:2128
if (expr.MatchAny(Tag.Call, out callFunc, out callArgs))
{
string knownCallName;
if (callFunc != null && callFunc.Match(Tag.Name, out knownCallName) &&
knownCallName == "__cgb_is_cgb" &&
callArgs != null && callArgs.Length == 0 &&
// A resolved target configuration can replace the zero-argument CGB query with a literal.
Program.TryGetKnownCgbRuntimeValue(out int knownCgbValue))
{
return Expr.Make(Tag.Integer, knownCgbValue).WithSource(expr.Source);
}
Expr newFunc = LowerExpr(ctx, callFunc, false, prefix);
// Optimization (post-A3): when we must spill a non-atomic argument into a temp,
// prefer a u8 temp if the *parameter type* is u8-like.
// This preserves semantics (implicit truncation to u8) while avoiding needless
// u16 temporaries created by integer promotions.
CType[] paramTypes = null;
string __fname = null;
if (newFunc.Match(Tag.Name, out __fname))
{
paramTypes = ctx.FindFuncParams(__fname);
if (paramTypes == null)
{
CType fnTypeFromName;
if (TryGetCallableFunctionType(ctx.FindTypeOfName(__fname), out fnTypeFromName))
paramTypes = fnTypeFromName.ParamTypes;
}
}
else
{
CType fnTypeFromExpr;
if (TryGetCallableFunctionType(InferType(ctx, newFunc), out fnTypeFromExpr))
paramTypes = fnTypeFromExpr.ParamTypes;
}
Expr[] newArgs = new Expr[callArgs.Length];
for (int i = 0; i < callArgs.Length; i++)
{
Expr a = LowerExpr(ctx, callArgs[i], allowExtract, prefix);
bool preserveConstantArgIntrinsic = false;
if (a.MatchAny(Tag.Call, out Expr argFunc, out Expr[] _argCallArgs) &&
argFunc.Match(Tag.Name, out string argFuncName))
{
preserveConstantArgIntrinsic = (argFuncName == "__bankof" || argFuncName == "__cgb_is_cgb");
}
// Keep evaluable constants and special intrinsic/bank operands visible to code generation instead of spilling them.
bool preserveCompileTimeConstantArg = ctx.Global.TryEvalConstInt(a, out _);
bool preserveFarcallBankArg = (__fname == "__farcall" && i == 0);
if (allowExtract && !preserveFarcallBankArg && !preserveConstantArgIntrinsic && !preserveCompileTimeConstantArg && !IsAtom(a))
{
// If we know the callee's parameter type, use it to choose a smaller temp.
// Example: (u8)(row*32) passed to a u8 param should spill as u8, not u16.
CType spillAs = null;
if (paramTypes != null && i < paramTypes.Length)
{
CType pt = paramTypes[i];
if (IsU8Like(pt)) spillAs = CType.UInt8;
}
if (spillAs == null) spillAs = InferType(ctx, a);
if (!IsAggregateType(spillAs) && SizeOfType(spillAs) <= 2)
a = SpillToTemp(ctx, a, spillAs, prefix);
}
newArgs[i] = a;
}
// Lint: const discard for argument passing
// Warn when passing a const-qualified pointer (or const array decay) to a non-const pointer parameter.
// Suppress when the argument has an explicit cast, or inside __unsafe.
if (!ctx.InUnsafe && paramTypes != null)
{
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
CType pt = paramTypes[i];
if (pt == null) continue;
if (!IsPointerToNonConst(pt)) continue;
if (HasTopLevelCast(newArgs[i])) continue;
CType at = InferType(ctx, newArgs[i]);
if (at != null && (IsPointerToConst(at) || IsArrayOfConst(at)))
{
string fn = __fname ?? "<call>";
WarnConstDiscard(expr, $"argument {i} to '{fn}'");
break; // keep it quiet: one warning per call is enough
}
}
}
if (paramTypes != null)
{
string fn = __fname ?? "<call>";
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
FilePosition argPos = newArgs[i].Source;
if (string.IsNullOrEmpty(argPos.Filename) || argPos.Filename == "<unknown>")
argPos = expr.Source;
WarnImplicitNarrowingIfNeeded(ctx, newArgs[i], InferType(ctx, newArgs[i]), paramTypes[i], argPos,
"argument " + i + " to '" + fn + "'");
}
}
// Lint: __enum_strict argument passing.
if (paramTypes != null)
{
string fn = __fname ?? "<call>";
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
CType pt = paramTypes[i];
if (pt == null) continue;
CType at = InferType(ctx, newArgs[i]);
if (at == null) continue;
if (HasTopLevelCast(newArgs[i])) continue;
if (IsStrictEnum(pt))
{
if (!IsSameEnum(pt, at))
{
Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
"argument {0} to '{1}' expects __enum_strict {2}, got {3}; cast explicitly to silence",
i, fn, pt.Show(), at.Show());
}
}
else if (IsPlainInteger(pt) && IsStrictEnum(at))
{
Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
"argument {0} to '{1}' passes __enum_strict {2} to integer parameter {3}; cast explicitly to silence",
i, fn, at.Show(), pt.Show());
}
}
}
if (!ctx.InUnsafe)
RestrictAliasLint(ctx, newFunc, newArgs, expr.Source);
// __slice(ptr,len) helper: represent a "slice" (ptr+len) in the IR.
// Semantics: evaluate both arguments, result type is the pointer/array (same as arg0).
// Bounds checks are injected only with -Zcheck (see Program.CheckSliceBounds).
// Note: keep the intrinsic name conservative to avoid collisions ("slice" is allowed only
// when there is no declared function named "slice").
if (newFunc.Match(Tag.Name, out string __sfname))
{
bool isSliceIntrinsic = (__sfname == "__slice") || (__sfname == "slice" && ctx.FindFuncParams("slice") == null);
if (isSliceIntrinsic)
{
if (newArgs.Length != 2)
{
Program.Error(expr.Source, ErrorCode.ParseError, "{0}: expected 2 arguments: __slice(ptr,len)", __sfname);
}
else
{
CType pty = InferType(ctx, newArgs[0]);
if (pty == null || !(pty.IsPointer || pty.IsArray))
Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: first argument must be a pointer or array, got: {1}", __sfname, (pty == null ? "<unknown>" : pty.Show()));
CType lty = InferType(ctx, newArgs[1]);
if (lty == null || !lty.IsInteger)
Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: second argument must be an integer length, got: {1}", __sfname, (lty == null ? "<unknown>" : lty.Show()));
}
return Expr.Make(Tag.Slice, newArgs[0], newArgs[1]).WithSource(expr.Source);
}
}
return MakeCall(newFunc, newArgs).WithSource(expr.Source);
}__memset
대상 영역을 같은 1바이트 값으로 채웁니다. 길이는 16비트이며 큰 버퍼를 지우거나 테이블을 초기화할 때 유용합니다. 예를 들어 0x5A를 지정하면 5A 5A 5A…를 씁니다. 여러 바이트로 된 정수를 반복해서 쓰는 기능은 아닙니다.
사용 형식
void __memset(u8* dst, u8 value, u16 len);매개변수
dst쓰기 대상의 첫 바이트를 가리키는 포인터입니다. 전체 작업에 필요한 연속 공간을 미리 확보하세요. 버퍼를 자동으로 할당하지는 않습니다.
value각 주소에 반복해서 쓸 0~255의 1바이트 값입니다. 0을 쓰면 영역을 0으로 채웁니다.
u16배열을 0이 아닌 수치로 초기화하려면 각 원소에 따로 대입하세요.lenu16으로 지정하는 바이트 수(0~65535)이며 원소 개수가 아닙니다. 자료형의 최댓값만큼 큰 버퍼를 실제 기기에서 확보할 수 있다는 뜻은 아닙니다. 전체 범위가 현재 메모리 매핑 안에 들어가야 합니다.
반환값
없음(void).
사용 조건과 제한
길이가 0이면 바이트를 전송하지 않습니다. 단, 인수 식의 부수 효과는 실행됩니다. 예제에서는 포인터를 반환하는 함수 두 개를 인수로 넘겨, 복사 길이가 0이어도 두 함수가 모두 호출됨을 확인합니다.
작업을 완료한 뒤 호출한 곳으로 돌아갑니다. ROM·RAM 뱅크를 전환하거나 버퍼 경계에 맞춰 범위를 잘라 주지 않습니다. 매핑을 유지하고 유효한 범위를 지정하며, 같은 버퍼를 쓰는 인터럽트 처리와 접근을 조정하세요.
FC에서 dst는 PPU 주소가 아닌 CPU 주소입니다. CPU RAM에 복사해도 CHR·네임테이블·팔레트는 갱신되지 않으므로 전송에는 PPU·VRAM API를 사용하세요. 생성된 보조 루틴은 인수·작업 공간을 공유하므로 실행 중에 NMI·IRQ에서 다시 진입하면 안 됩니다.
사용 예
memory_reset();
__memset(memory_destination+1,0x5A,257);
memory_fill_word=memory_check(257,1); // Repeat one byte, not a 16-bit word.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 예제는 __memset로 오프셋 1부터 257바이트를 0x5A로 채웁니다. memory_check는 해당 범위의 모든 바이트가 0x5A인지, 보호 바이트가 0xCC로 유지되는지, 별도의 원본 버퍼가 바뀌지 않았는지 검사합니다. FILL WORD 행에는 257가 표시되어야 합니다.
전체 프로그램은 작업마다 264바이트 버퍼 두 개를 초기화한 뒤 원본·대상의 모든 바이트와 변경되면 안 되는 앞뒤 보호 영역을 검사합니다. 정상 표시는 COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002, FAILED 000입니다. WORD·BYTE는 복사할 원소의 크기가 아니라 길이의 자료형을 뜻합니다. 숫자는 예제의 검사 결과이며, 반환값이 없는 이 함수들이 반환한 값이 아닙니다.
캡처 화면은 DMG가 흰 바탕에 검은 글자, CGB가 흰 바탕에 파란 글자, FC가 검은 바탕에 파란 글자입니다. RAM 동작을 확인하는 예제이며 글자는 검사 결과를 보여 주기 위한 것입니다. 도형 그리기 함수의 검증은 아닙니다. 별도 에뮬레이터 검사에서는 플랫폼마다 88개 사례를 확인했습니다. 상수·실행 시 길이 0, 1, 16, 17, 32, 33, 64, 65, 255바이트와 16비트 길이의 256·257바이트를 포함합니다. 실제 기기에서는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\memory_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\memory_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:21
Fill len CPU bytes with value. Zero length still evaluates argument side effects.
kitaqfc/kitaqfc/CodeGenerator.cs:4613
case "__memset":
if (TryEmitMemcpyOrMemsetInline(funcName, args, ctx, isSet: true, lenIndex: 2, lenSize: 2)) return true;
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,1,2}, "__memset");
bool TryEmitMemcpyOrMemsetInline(string funcName, Expr[] args, FunctionContext ctx, bool isSet, int lenIndex, int lenSize)
{
args = args ?? Array.Empty<Expr>();
int expected = isSet ? 3 : 3;
if (args.Length != expected) return false;
if (!TryEvaluateConstant(args[lenIndex], out int len)) return false;
if (len < 0 || len > 32) return false;
int valueConst = 0;
if (isSet && !TryEvaluateConstant(args[1], out valueConst)) return false;
// A zero-length expansion emits no address or fill evaluation in this helper.
if (len == 0) return true;
if (isSet)
{
int[] ptr = AcquireTemps(2);
try
{
EmitLoadValue(args[0], ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
for (int i = 0; i < len; i++)
{
EmitAsm("LDY", Imm(i));
EmitAsm("LDA", Imm(valueConst));
EmitAsm("STA", IndY(ptr[0]));
}
}
finally { ReleaseTemps(ptr); }
return true;
}
EmitFixedMemcpy(args[0], args[1], len, ctx);
return true;
}
void EmitHelperMemoryAndBit()
{
// Promote the byte count to a word by clearing its high byte before entering memcpy.
EmitHelperStart("__memcpy_small");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 5));
EmitAsm("JMP", Abs("__memcpy"));
// Advance both argument pointers and decrement the word count in place; overlapping copies are not reversed.
EmitHelperStart("__memcpy");
string mloop = NewGeneratedLabel("memcpy_loop");
string mdone = NewGeneratedLabel("memcpy_done");
EmitAsm("LDA", Mem(CallArgBase + 4));
EmitAsm("ORA", Mem(CallArgBase + 5));
EmitAsm("BEQ", Rel(mdone));
_assembly.Add(Expr.Make(Tag.Label, mloop));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", IndY(CallArgBase + 2));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("INC", Mem(CallArgBase + 2));
string ms1 = NewGeneratedLabel("memcpy_src_page_ok");
EmitAsm("BNE", Rel(ms1));
EmitAsm("INC", Mem(CallArgBase + 3));
_assembly.Add(Expr.Make(Tag.Label, ms1));
EmitAsm("INC", Mem(CallArgBase));
string md1 = NewGeneratedLabel("memcpy_dst_page_ok");
EmitAsm("BNE", Rel(md1));
EmitAsm("INC", Mem(CallArgBase + 1));
_assembly.Add(Expr.Make(Tag.Label, md1));
EmitAsm("LDA", Mem(CallArgBase + 4));
string mlononzero = NewGeneratedLabel("memcpy_lenlo_nonzero");
EmitAsm("BNE", Rel(mlononzero));
EmitAsm("DEC", Mem(CallArgBase + 5));
_assembly.Add(Expr.Make(Tag.Label, mlononzero));
EmitAsm("DEC", Mem(CallArgBase + 4));
EmitAsm("LDA", Mem(CallArgBase + 4));
EmitAsm("ORA", Mem(CallArgBase + 5));
EmitAsm("BNE", Rel(mloop));
_assembly.Add(Expr.Make(Tag.Label, mdone));
EmitAsm("RTS");
// Clear the high count byte for the small-fill wrapper.
EmitHelperStart("__memset_small");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 4));
EmitAsm("JMP", Abs("__memset"));
// Advance the destination pointer while consuming the word count in the shared argument area.
EmitHelperStart("__memset");
string sloop = NewGeneratedLabel("memset_loop");
string sdone = NewGeneratedLabel("memset_done");
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("ORA", Mem(CallArgBase + 4));
EmitAsm("BEQ", Rel(sdone));
_assembly.Add(Expr.Make(Tag.Label, sloop));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("INC", Mem(CallArgBase));
string sd1 = NewGeneratedLabel("memset_dst_page_ok");
EmitAsm("BNE", Rel(sd1));
EmitAsm("INC", Mem(CallArgBase + 1));
_assembly.Add(Expr.Make(Tag.Label, sd1));
EmitAsm("LDA", Mem(CallArgBase + 3));
string slononzero = NewGeneratedLabel("memset_lenlo_nonzero");
EmitAsm("BNE", Rel(slononzero));
EmitAsm("DEC", Mem(CallArgBase + 4));
_assembly.Add(Expr.Make(Tag.Label, slononzero));
EmitAsm("DEC", Mem(CallArgBase + 3));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("ORA", Mem(CallArgBase + 4));
EmitAsm("BNE", Rel(sloop));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("RTS");
}
void EmitFixedMemcpy(Expr dstExpr, Expr srcExpr, int len, FunctionContext ctx)
{
int[] ptr = AcquireTemps(4);
try
{
EmitLoadValue(dstExpr, ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
EmitLoadValue(srcExpr, ctx, 2);
EmitAsm("STA", Mem(ptr[2]));
EmitAsm("STX", Mem(ptr[3]));
for (int i = 0; i < len; i++)
{
EmitAsm("LDY", Imm(i));
EmitAsm("LDA", IndY(ptr[2]));
EmitAsm("STA", IndY(ptr[0]));
}
}
finally { ReleaseTemps(ptr); }
}
// kitaqfc/kitaqfc/Lowerer.cs:2128
if (expr.MatchAny(Tag.Call, out callFunc, out callArgs))
{
string knownCallName;
if (callFunc != null && callFunc.Match(Tag.Name, out knownCallName) &&
knownCallName == "__cgb_is_cgb" &&
callArgs != null && callArgs.Length == 0 &&
// A resolved target configuration can replace the zero-argument CGB query with a literal.
Program.TryGetKnownCgbRuntimeValue(out int knownCgbValue))
{
return Expr.Make(Tag.Integer, knownCgbValue).WithSource(expr.Source);
}
Expr newFunc = LowerExpr(ctx, callFunc, false, prefix);
// Optimization (post-A3): when we must spill a non-atomic argument into a temp,
// prefer a u8 temp if the *parameter type* is u8-like.
// This preserves semantics (implicit truncation to u8) while avoiding needless
// u16 temporaries created by integer promotions.
CType[] paramTypes = null;
string __fname = null;
if (newFunc.Match(Tag.Name, out __fname))
{
paramTypes = ctx.FindFuncParams(__fname);
if (paramTypes == null)
{
CType fnTypeFromName;
if (TryGetCallableFunctionType(ctx.FindTypeOfName(__fname), out fnTypeFromName))
paramTypes = fnTypeFromName.ParamTypes;
}
}
else
{
CType fnTypeFromExpr;
if (TryGetCallableFunctionType(InferType(ctx, newFunc), out fnTypeFromExpr))
paramTypes = fnTypeFromExpr.ParamTypes;
}
Expr[] newArgs = new Expr[callArgs.Length];
for (int i = 0; i < callArgs.Length; i++)
{
Expr a = LowerExpr(ctx, callArgs[i], allowExtract, prefix);
bool preserveConstantArgIntrinsic = false;
if (a.MatchAny(Tag.Call, out Expr argFunc, out Expr[] _argCallArgs) &&
argFunc.Match(Tag.Name, out string argFuncName))
{
preserveConstantArgIntrinsic = (argFuncName == "__bankof" || argFuncName == "__cgb_is_cgb");
}
// Keep evaluable constants and special intrinsic/bank operands visible to code generation instead of spilling them.
bool preserveCompileTimeConstantArg = ctx.Global.TryEvalConstInt(a, out _);
bool preserveFarcallBankArg = (__fname == "__farcall" && i == 0);
if (allowExtract && !preserveFarcallBankArg && !preserveConstantArgIntrinsic && !preserveCompileTimeConstantArg && !IsAtom(a))
{
// If we know the callee's parameter type, use it to choose a smaller temp.
// Example: (u8)(row*32) passed to a u8 param should spill as u8, not u16.
CType spillAs = null;
if (paramTypes != null && i < paramTypes.Length)
{
CType pt = paramTypes[i];
if (IsU8Like(pt)) spillAs = CType.UInt8;
}
if (spillAs == null) spillAs = InferType(ctx, a);
if (!IsAggregateType(spillAs) && SizeOfType(spillAs) <= 2)
a = SpillToTemp(ctx, a, spillAs, prefix);
}
newArgs[i] = a;
}
// Lint: const discard for argument passing
// Warn when passing a const-qualified pointer (or const array decay) to a non-const pointer parameter.
// Suppress when the argument has an explicit cast, or inside __unsafe.
if (!ctx.InUnsafe && paramTypes != null)
{
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
CType pt = paramTypes[i];
if (pt == null) continue;
if (!IsPointerToNonConst(pt)) continue;
if (HasTopLevelCast(newArgs[i])) continue;
CType at = InferType(ctx, newArgs[i]);
if (at != null && (IsPointerToConst(at) || IsArrayOfConst(at)))
{
string fn = __fname ?? "<call>";
WarnConstDiscard(expr, $"argument {i} to '{fn}'");
break; // keep it quiet: one warning per call is enough
}
}
}
if (paramTypes != null)
{
string fn = __fname ?? "<call>";
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
FilePosition argPos = newArgs[i].Source;
if (string.IsNullOrEmpty(argPos.Filename) || argPos.Filename == "<unknown>")
argPos = expr.Source;
WarnImplicitNarrowingIfNeeded(ctx, newArgs[i], InferType(ctx, newArgs[i]), paramTypes[i], argPos,
"argument " + i + " to '" + fn + "'");
}
}
// Lint: __enum_strict argument passing.
if (paramTypes != null)
{
string fn = __fname ?? "<call>";
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
CType pt = paramTypes[i];
if (pt == null) continue;
CType at = InferType(ctx, newArgs[i]);
if (at == null) continue;
if (HasTopLevelCast(newArgs[i])) continue;
if (IsStrictEnum(pt))
{
if (!IsSameEnum(pt, at))
{
Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
"argument {0} to '{1}' expects __enum_strict {2}, got {3}; cast explicitly to silence",
i, fn, pt.Show(), at.Show());
}
}
else if (IsPlainInteger(pt) && IsStrictEnum(at))
{
Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
"argument {0} to '{1}' passes __enum_strict {2} to integer parameter {3}; cast explicitly to silence",
i, fn, at.Show(), pt.Show());
}
}
}
if (!ctx.InUnsafe)
RestrictAliasLint(ctx, newFunc, newArgs, expr.Source);
// __slice(ptr,len) helper: represent a "slice" (ptr+len) in the IR.
// Semantics: evaluate both arguments, result type is the pointer/array (same as arg0).
// Bounds checks are injected only with -Zcheck (see Program.CheckSliceBounds).
// Note: keep the intrinsic name conservative to avoid collisions ("slice" is allowed only
// when there is no declared function named "slice").
if (newFunc.Match(Tag.Name, out string __sfname))
{
bool isSliceIntrinsic = (__sfname == "__slice") || (__sfname == "slice" && ctx.FindFuncParams("slice") == null);
if (isSliceIntrinsic)
{
if (newArgs.Length != 2)
{
Program.Error(expr.Source, ErrorCode.ParseError, "{0}: expected 2 arguments: __slice(ptr,len)", __sfname);
}
else
{
CType pty = InferType(ctx, newArgs[0]);
if (pty == null || !(pty.IsPointer || pty.IsArray))
Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: first argument must be a pointer or array, got: {1}", __sfname, (pty == null ? "<unknown>" : pty.Show()));
CType lty = InferType(ctx, newArgs[1]);
if (lty == null || !lty.IsInteger)
Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: second argument must be an integer length, got: {1}", __sfname, (lty == null ? "<unknown>" : lty.Show()));
}
return Expr.Make(Tag.Slice, newArgs[0], newArgs[1]).WithSource(expr.Source);
}
}
return MakeCall(newFunc, newArgs).WithSource(expr.Source);
}__memset_small
8비트 길이를 사용해 연속된 최대 255바이트를 같은 1바이트 값으로 채웁니다. 작은 작업 버퍼를 초기화할 때 적합합니다. 필요한 길이가 255바이트를 넘으면 __memset을 사용하세요.
사용 형식
void __memset_small(u8* dst, u8 value, u8 len);매개변수
dst쓰기 대상의 첫 바이트를 가리키는 포인터입니다. 전체 작업에 필요한 연속 공간을 미리 확보하세요. 버퍼를 자동으로 할당하지는 않습니다.
value각 주소에 반복해서 쓸 0~255의 1바이트 값입니다. 0을 쓰면 영역을 0으로 채웁니다.
u16배열을 0이 아닌 수치로 초기화하려면 각 원소에 따로 대입하세요.lenu8로 지정하는 0~255의 바이트 수입니다. 256바이트 블록 전체를 뜻하려고 256을 넘기면 안 됩니다. 이 인수의 자료형에 들어가지 않습니다. 256바이트 이상은_small이 없는 함수를 사용하세요.
반환값
없음(void).
사용 조건과 제한
길이가 0이면 바이트를 전송하지 않습니다. 단, 인수 식의 부수 효과는 실행됩니다. 예제에서는 포인터를 반환하는 함수 두 개를 인수로 넘겨, 복사 길이가 0이어도 두 함수가 모두 호출됨을 확인합니다.
작업을 완료한 뒤 호출한 곳으로 돌아갑니다. ROM·RAM 뱅크를 전환하거나 버퍼 경계에 맞춰 범위를 잘라 주지 않습니다. 매핑을 유지하고 유효한 범위를 지정하며, 같은 버퍼를 쓰는 인터럽트 처리와 접근을 조정하세요.
FC에서 dst는 PPU 주소가 아닌 CPU 주소입니다. CPU RAM에 복사해도 CHR·네임테이블·팔레트는 갱신되지 않으므로 전송에는 PPU·VRAM API를 사용하세요. 생성된 보조 루틴은 인수·작업 공간을 공유하므로 실행 중에 NMI·IRQ에서 다시 진입하면 안 됩니다.
사용 예
memory_reset();
__memset_small(memory_destination+1,0x5A,255);
memory_fill_byte=memory_check(255,1);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 예제는 __memset_small로 오프셋 1부터 255바이트를 0x5A로 채웁니다. memory_check는 해당 범위의 모든 바이트가 0x5A인지, 보호 바이트가 0xCC로 유지되는지, 별도의 원본 버퍼가 바뀌지 않았는지 검사합니다. FILL BYTE 행에는 255가 표시되어야 합니다.
전체 프로그램은 작업마다 264바이트 버퍼 두 개를 초기화한 뒤 원본·대상의 모든 바이트와 변경되면 안 되는 앞뒤 보호 영역을 검사합니다. 정상 표시는 COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002, FAILED 000입니다. WORD·BYTE는 복사할 원소의 크기가 아니라 길이의 자료형을 뜻합니다. 숫자는 예제의 검사 결과이며, 반환값이 없는 이 함수들이 반환한 값이 아닙니다.
캡처 화면은 DMG가 흰 바탕에 검은 글자, CGB가 흰 바탕에 파란 글자, FC가 검은 바탕에 파란 글자입니다. RAM 동작을 확인하는 예제이며 글자는 검사 결과를 보여 주기 위한 것입니다. 도형 그리기 함수의 검증은 아닙니다. 별도 에뮬레이터 검사에서는 플랫폼마다 88개 사례를 확인했습니다. 상수·실행 시 길이 0, 1, 16, 17, 32, 33, 64, 65, 255바이트와 16비트 길이의 256·257바이트를 포함합니다. 실제 기기에서는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\memory_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\memory_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:25
Fill zero through 255 CPU bytes; this is ordinary RAM access, not a PPU transfer.
kitaqfc/kitaqfc/CodeGenerator.cs:4616
case "__memset_small":
if (TryEmitMemcpyOrMemsetInline(funcName, args, ctx, isSet: true, lenIndex: 2, lenSize: 1)) return true;
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,1,1}, "__memset_small");
// These fixed-size copies bypass the variable-length helper argument area.
bool TryEmitMemcpyOrMemsetInline(string funcName, Expr[] args, FunctionContext ctx, bool isSet, int lenIndex, int lenSize)
{
args = args ?? Array.Empty<Expr>();
int expected = isSet ? 3 : 3;
if (args.Length != expected) return false;
if (!TryEvaluateConstant(args[lenIndex], out int len)) return false;
if (len < 0 || len > 32) return false;
int valueConst = 0;
if (isSet && !TryEvaluateConstant(args[1], out valueConst)) return false;
// A zero-length expansion emits no address or fill evaluation in this helper.
if (len == 0) return true;
if (isSet)
{
int[] ptr = AcquireTemps(2);
try
{
EmitLoadValue(args[0], ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
for (int i = 0; i < len; i++)
{
EmitAsm("LDY", Imm(i));
EmitAsm("LDA", Imm(valueConst));
EmitAsm("STA", IndY(ptr[0]));
}
}
finally { ReleaseTemps(ptr); }
return true;
}
EmitFixedMemcpy(args[0], args[1], len, ctx);
return true;
}
void EmitHelperMemoryAndBit()
{
// Promote the byte count to a word by clearing its high byte before entering memcpy.
EmitHelperStart("__memcpy_small");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 5));
EmitAsm("JMP", Abs("__memcpy"));
// Advance both argument pointers and decrement the word count in place; overlapping copies are not reversed.
EmitHelperStart("__memcpy");
string mloop = NewGeneratedLabel("memcpy_loop");
string mdone = NewGeneratedLabel("memcpy_done");
EmitAsm("LDA", Mem(CallArgBase + 4));
EmitAsm("ORA", Mem(CallArgBase + 5));
EmitAsm("BEQ", Rel(mdone));
_assembly.Add(Expr.Make(Tag.Label, mloop));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", IndY(CallArgBase + 2));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("INC", Mem(CallArgBase + 2));
string ms1 = NewGeneratedLabel("memcpy_src_page_ok");
EmitAsm("BNE", Rel(ms1));
EmitAsm("INC", Mem(CallArgBase + 3));
_assembly.Add(Expr.Make(Tag.Label, ms1));
EmitAsm("INC", Mem(CallArgBase));
string md1 = NewGeneratedLabel("memcpy_dst_page_ok");
EmitAsm("BNE", Rel(md1));
EmitAsm("INC", Mem(CallArgBase + 1));
_assembly.Add(Expr.Make(Tag.Label, md1));
EmitAsm("LDA", Mem(CallArgBase + 4));
string mlononzero = NewGeneratedLabel("memcpy_lenlo_nonzero");
EmitAsm("BNE", Rel(mlononzero));
EmitAsm("DEC", Mem(CallArgBase + 5));
_assembly.Add(Expr.Make(Tag.Label, mlononzero));
EmitAsm("DEC", Mem(CallArgBase + 4));
EmitAsm("LDA", Mem(CallArgBase + 4));
EmitAsm("ORA", Mem(CallArgBase + 5));
EmitAsm("BNE", Rel(mloop));
_assembly.Add(Expr.Make(Tag.Label, mdone));
EmitAsm("RTS");
// Clear the high count byte for the small-fill wrapper.
EmitHelperStart("__memset_small");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 4));
EmitAsm("JMP", Abs("__memset"));
// Advance the destination pointer while consuming the word count in the shared argument area.
EmitHelperStart("__memset");
string sloop = NewGeneratedLabel("memset_loop");
string sdone = NewGeneratedLabel("memset_done");
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("ORA", Mem(CallArgBase + 4));
EmitAsm("BEQ", Rel(sdone));
_assembly.Add(Expr.Make(Tag.Label, sloop));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("INC", Mem(CallArgBase));
string sd1 = NewGeneratedLabel("memset_dst_page_ok");
EmitAsm("BNE", Rel(sd1));
EmitAsm("INC", Mem(CallArgBase + 1));
_assembly.Add(Expr.Make(Tag.Label, sd1));
EmitAsm("LDA", Mem(CallArgBase + 3));
string slononzero = NewGeneratedLabel("memset_lenlo_nonzero");
EmitAsm("BNE", Rel(slononzero));
EmitAsm("DEC", Mem(CallArgBase + 4));
_assembly.Add(Expr.Make(Tag.Label, slononzero));
EmitAsm("DEC", Mem(CallArgBase + 3));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("ORA", Mem(CallArgBase + 4));
EmitAsm("BNE", Rel(sloop));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("RTS");
}
void EmitFixedMemcpy(Expr dstExpr, Expr srcExpr, int len, FunctionContext ctx)
{
int[] ptr = AcquireTemps(4);
try
{
EmitLoadValue(dstExpr, ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
EmitLoadValue(srcExpr, ctx, 2);
EmitAsm("STA", Mem(ptr[2]));
EmitAsm("STX", Mem(ptr[3]));
for (int i = 0; i < len; i++)
{
EmitAsm("LDY", Imm(i));
EmitAsm("LDA", IndY(ptr[2]));
EmitAsm("STA", IndY(ptr[0]));
}
}
finally { ReleaseTemps(ptr); }
}
// kitaqfc/kitaqfc/Lowerer.cs:2128
if (expr.MatchAny(Tag.Call, out callFunc, out callArgs))
{
string knownCallName;
if (callFunc != null && callFunc.Match(Tag.Name, out knownCallName) &&
knownCallName == "__cgb_is_cgb" &&
callArgs != null && callArgs.Length == 0 &&
// A resolved target configuration can replace the zero-argument CGB query with a literal.
Program.TryGetKnownCgbRuntimeValue(out int knownCgbValue))
{
return Expr.Make(Tag.Integer, knownCgbValue).WithSource(expr.Source);
}
Expr newFunc = LowerExpr(ctx, callFunc, false, prefix);
// Optimization (post-A3): when we must spill a non-atomic argument into a temp,
// prefer a u8 temp if the *parameter type* is u8-like.
// This preserves semantics (implicit truncation to u8) while avoiding needless
// u16 temporaries created by integer promotions.
CType[] paramTypes = null;
string __fname = null;
if (newFunc.Match(Tag.Name, out __fname))
{
paramTypes = ctx.FindFuncParams(__fname);
if (paramTypes == null)
{
CType fnTypeFromName;
if (TryGetCallableFunctionType(ctx.FindTypeOfName(__fname), out fnTypeFromName))
paramTypes = fnTypeFromName.ParamTypes;
}
}
else
{
CType fnTypeFromExpr;
if (TryGetCallableFunctionType(InferType(ctx, newFunc), out fnTypeFromExpr))
paramTypes = fnTypeFromExpr.ParamTypes;
}
Expr[] newArgs = new Expr[callArgs.Length];
for (int i = 0; i < callArgs.Length; i++)
{
Expr a = LowerExpr(ctx, callArgs[i], allowExtract, prefix);
bool preserveConstantArgIntrinsic = false;
if (a.MatchAny(Tag.Call, out Expr argFunc, out Expr[] _argCallArgs) &&
argFunc.Match(Tag.Name, out string argFuncName))
{
preserveConstantArgIntrinsic = (argFuncName == "__bankof" || argFuncName == "__cgb_is_cgb");
}
// Keep evaluable constants and special intrinsic/bank operands visible to code generation instead of spilling them.
bool preserveCompileTimeConstantArg = ctx.Global.TryEvalConstInt(a, out _);
bool preserveFarcallBankArg = (__fname == "__farcall" && i == 0);
if (allowExtract && !preserveFarcallBankArg && !preserveConstantArgIntrinsic && !preserveCompileTimeConstantArg && !IsAtom(a))
{
// If we know the callee's parameter type, use it to choose a smaller temp.
// Example: (u8)(row*32) passed to a u8 param should spill as u8, not u16.
CType spillAs = null;
if (paramTypes != null && i < paramTypes.Length)
{
CType pt = paramTypes[i];
if (IsU8Like(pt)) spillAs = CType.UInt8;
}
if (spillAs == null) spillAs = InferType(ctx, a);
if (!IsAggregateType(spillAs) && SizeOfType(spillAs) <= 2)
a = SpillToTemp(ctx, a, spillAs, prefix);
}
newArgs[i] = a;
}
// Lint: const discard for argument passing
// Warn when passing a const-qualified pointer (or const array decay) to a non-const pointer parameter.
// Suppress when the argument has an explicit cast, or inside __unsafe.
if (!ctx.InUnsafe && paramTypes != null)
{
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
CType pt = paramTypes[i];
if (pt == null) continue;
if (!IsPointerToNonConst(pt)) continue;
if (HasTopLevelCast(newArgs[i])) continue;
CType at = InferType(ctx, newArgs[i]);
if (at != null && (IsPointerToConst(at) || IsArrayOfConst(at)))
{
string fn = __fname ?? "<call>";
WarnConstDiscard(expr, $"argument {i} to '{fn}'");
break; // keep it quiet: one warning per call is enough
}
}
}
if (paramTypes != null)
{
string fn = __fname ?? "<call>";
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
FilePosition argPos = newArgs[i].Source;
if (string.IsNullOrEmpty(argPos.Filename) || argPos.Filename == "<unknown>")
argPos = expr.Source;
WarnImplicitNarrowingIfNeeded(ctx, newArgs[i], InferType(ctx, newArgs[i]), paramTypes[i], argPos,
"argument " + i + " to '" + fn + "'");
}
}
// Lint: __enum_strict argument passing.
if (paramTypes != null)
{
string fn = __fname ?? "<call>";
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
CType pt = paramTypes[i];
if (pt == null) continue;
CType at = InferType(ctx, newArgs[i]);
if (at == null) continue;
if (HasTopLevelCast(newArgs[i])) continue;
if (IsStrictEnum(pt))
{
if (!IsSameEnum(pt, at))
{
Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
"argument {0} to '{1}' expects __enum_strict {2}, got {3}; cast explicitly to silence",
i, fn, pt.Show(), at.Show());
}
}
else if (IsPlainInteger(pt) && IsStrictEnum(at))
{
Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
"argument {0} to '{1}' passes __enum_strict {2} to integer parameter {3}; cast explicitly to silence",
i, fn, at.Show(), pt.Show());
}
}
}
if (!ctx.InUnsafe)
RestrictAliasLint(ctx, newFunc, newArgs, expr.Source);
// __slice(ptr,len) helper: represent a "slice" (ptr+len) in the IR.
// Semantics: evaluate both arguments, result type is the pointer/array (same as arg0).
// Bounds checks are injected only with -Zcheck (see Program.CheckSliceBounds).
// Note: keep the intrinsic name conservative to avoid collisions ("slice" is allowed only
// when there is no declared function named "slice").
if (newFunc.Match(Tag.Name, out string __sfname))
{
bool isSliceIntrinsic = (__sfname == "__slice") || (__sfname == "slice" && ctx.FindFuncParams("slice") == null);
if (isSliceIntrinsic)
{
if (newArgs.Length != 2)
{
Program.Error(expr.Source, ErrorCode.ParseError, "{0}: expected 2 arguments: __slice(ptr,len)", __sfname);
}
else
{
CType pty = InferType(ctx, newArgs[0]);
if (pty == null || !(pty.IsPointer || pty.IsArray))
Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: first argument must be a pointer or array, got: {1}", __sfname, (pty == null ? "<unknown>" : pty.Show()));
CType lty = InferType(ctx, newArgs[1]);
if (lty == null || !lty.IsInteger)
Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: second argument must be an integer length, got: {1}", __sfname, (lty == null ? "<unknown>" : lty.Show()));
}
return Expr.Make(Tag.Slice, newArgs[0], newArgs[1]).WithSource(expr.Source);
}
}
return MakeCall(newFunc, newArgs).WithSource(expr.Source);
}__metasprite_draw
종료 표시가 있는 스프라이트 부품 데이터열을 페이지 02 섀도 버퍼의 연속된 항목으로 펼칩니다. 각 부품은 바이트 크기의 X/Y 오프셋을 기준 위치에 더하고 자체 타일과 속성을 지정합니다. 다음 OAM 번호를 반환합니다.
사용 형식
u8 __metasprite_draw(u8 oam_index, u8 base_x, u8 base_y, const u8* metasprite);매개변수
oam_index스프라이트 항목 번호 0–63입니다. 바이트 오프셋이 아닙니다. 내부에서 8비트 커서로 4를 곱하므로 범위를 벗어나면 64를 주기로 순환해 다른 항목을 덮어쓸 수 있습니다. 할당 여부는 검사하지 않습니다.
base_x픽셀 단위의 가로 위치를 부호 없는 8비트 값으로 전달합니다. 타일 좌표가 아닙니다.
base_yOAM에 저장할 Y 값입니다. 화면 1번 줄 이후에 윗부분을 놓으려면 표시할 윗좌표에서 1을 뺍니다. 239~255는 240줄 표시 영역 밖입니다.
metaspritedx, dy, tile, attr의 4바이트 레코드로 이루어진 읽기 가능한 데이터열의 포인터입니다. 다음 dx 위치의0xFF가 종료를 나타내며 그 뒤의 바이트는 필요 없습니다. 오프셋은 256을 주기로 더합니다.0xFE는 −2, dy의0xFF는 −1로 사용할 수 있지만 dx의0xFF는 종료 전용입니다. 포인터는 현재 매핑된 메모리에서 유효해야 합니다.
반환값
다음 OAM 항목 번호인 (oam_index + 기록한 부품 수) modulo 64를 반환합니다. 그린 부품 수가 아니라 다음 기록 위치입니다. 빈 데이터열에서는 oam_index modulo 64입니다.
사용 조건과 제한
이 작업은 CPU RAM의 0x0200–0x02FF를 수정하며 하드웨어 OAM에는 즉시 반영되지 않습니다. 페이지를 완성한 뒤 __oam_dma로 전송하세요. 스프라이트 라이브러리의 할당 플래그, 좌표 배열, 사용 개수는 갱신하지 않습니다. 준비 중인 페이지를 인터럽트 처리에서 수정하거나 전송하지 않도록 하세요.
부품 수, 화면 잘라내기, 용량을 검사하지 않습니다. 종료 표시를 반드시 넣고 부품 수를 64−oam_index 이하로 제한하세요. 좌표는 256을 주기로 순환하며 63번 다음에는 0번에 기록합니다. 빈 데이터열은 아무 항목도 변경하지 않습니다. 이 형식은 스프라이트 라이브러리의 metasprite_draw가 사용하는 개수 지정 MetaSpritePart 배열과 다릅니다.
사용 예
fc_oam_next=__metasprite_draw(5,136,95,fc_oam_pair); // Returns 7 after two entries.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
작업마다 이름을 붙인 행으로 구분한 예제입니다. SET과 MOVE는 (144,16), (144,32)의 8×8 오른쪽 화살표, TILE은 (144,48)의 초록 정사각형, ATTR은 (144,64)의 상하좌우 반전 화살표입니다. HIDE의 (144,80)과 CLEAR의 (144,112)는 비어 있어야 합니다. META는 (136,96), (144,96)의 두 화살표이며 두 번째는 좌우 반전입니다. NEXT 07은 5번과 6번에 기록한 뒤의 다음 위치입니다. 화살표 테두리는 빨강, 몸통은 초록, 끝부분의 일부는 파랑입니다. 이미지당 8,704픽셀과 전송된 OAM 바이트를 대조하며 실제 기기에서는 검증하지 않았습니다.
TRANSFER의 (144,128)에 화살표를 표시합니다. 이 프로그램은 각 스프라이트 작업으로 준비한 모든 필드가 들어 있는 페이지 02를 __oam_dma로 직접 전송합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\oam_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\sprite_example.chr -o .\out\oam_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:203
Expand dx,dy,tile,attr records into page-02 shadow OAM until dx=FF; return the next index. Coordinates and the byte OAM cursor wrap; keep the terminated stream within the available slots.
EmitHelperStart("__metasprite_draw");
// args: oam_index, base_x, base_y, metasprite_ptr16.
// stream: dx, dy, tile, attr ... dx=$FF terminates. Returns next OAM index in A.
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("ASL");
EmitAsm("ASL");
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address)); // dst byte offset
string msLoop = NewGeneratedLabel("metasprite_loop");
string msDone = NewGeneratedLabel("metasprite_done");
_assembly.Add(Expr.Make(Tag.Label, msLoop));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", IndY(CallArgBase + 3));
EmitAsm("CMP", Imm(0xFF));
EmitAsm("BEQ", Rel(msDone));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address)); // dx
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("INY");
EmitAsm("LDA", IndY(CallArgBase + 3));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase + 2));
EmitAsm("STA", new AsmOperand(OamRamBase + 0, AddressMode.AbsoluteX));
EmitAsm("INY");
EmitAsm("LDA", IndY(CallArgBase + 3));
EmitAsm("STA", new AsmOperand(OamRamBase + 1, AddressMode.AbsoluteX));
EmitAsm("INY");
EmitAsm("LDA", IndY(CallArgBase + 3));
EmitAsm("STA", new AsmOperand(OamRamBase + 2, AddressMode.AbsoluteX));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", new AsmOperand(OamRamBase + 3, AddressMode.AbsoluteX));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("CLC");
EmitAsm("ADC", Imm(4));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("CLC");
EmitAsm("ADC", Imm(4));
EmitAsm("STA", Mem(CallArgBase + 3));
// Propagate stream-pointer page carry after consuming the complete four-byte record.
string msPtrOk = NewGeneratedLabel("metasprite_ptr_ok");
EmitAsm("BCC", Rel(msPtrOk));
EmitAsm("INC", Mem(CallArgBase + 4));
_assembly.Add(Expr.Make(Tag.Label, msPtrOk));
EmitAsm("JMP", Abs(msLoop));
_assembly.Add(Expr.Make(Tag.Label, msDone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LSR");
EmitAsm("LSR");
EmitAsm("RTS");__mic_read2p
패미컴 2번 컨트롤러의 마이크에 해당하는 디지털 신호를 $4016의 비트 2에서 읽습니다. 레지스터를 한 번 읽고 0x04로 마스킹한 뒤 반전 없이 0 또는 1로 정규화합니다. 컨트롤러를 래치하거나 오디오를 녹음하고 음량을 측정하거나 음성을 인식하지 않습니다.
사용 형식
u8 __mic_read2p(void);반환값
$4016 & 0x04가 0이 아니면 정확히 1, 그 외에는 0입니다. 읽어 온 디지털 레벨이며 버튼 마스크, 진폭, 주파수 또는 말한 단어 수가 아닙니다.
사용 조건과 제한
마이크는 2번 컨트롤러에 있지만 이 헬퍼는 $4017이 아니라 $4016에 접근합니다. 스트로브가 낮을 때 레지스터를 읽으면 컨트롤러 직렬 스트림도 진행될 수 있으므로 다른 컨트롤러의 8비트 읽기 루프 중간에 마이크 읽기를 끼워 넣지 마세요. 입력 이력 캐시는 갱신하지 않습니다.
공개된 KUROSAKI 컨트롤러 모델은 D0와 고정된 비트 6만 반환합니다. 따라서 일반 버튼을 누르고 있어도 이 에뮬레이터의 D1과 마이크 비트는 항상 0입니다. 제공된 실행 결과는 빌드, 일반 버튼 입력과 이 0 신호 표시를 확인한 것입니다. 주변기기의 0이 아닌 신호, 전기적 타이밍과 실기 호환성은 검증하지 않았습니다. 여기서 0이 나온다고 장치가 정상 작동한다거나 실기에서도 읽을 수 없다는 뜻은 아닙니다.
사용 예
mic_direct=__mic_read2p(); // A separate sample of the same digital signal.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
일반 D0 입력, 확장 D1 입력과 마이크 디지털 레벨을 비교하는 프로그램입니다. 상태가 캡처될 때까지 컨트롤러 1의 A와 컨트롤러 2의 B를 누릅니다. NORMAL P1과 NORMAL P2는 001과 002입니다. 제공된 KUROSAKI 실행에서는 해당 신호선을 에뮬레이션하지 않으므로 D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL, MIC DIRECT가 모두 000입니다. 캡처 후 표시값은 고정됩니다. 적합한 주변기기와 신호선 구현이 있는 환경에서는 실제 입력에 따라 값이 달라지므로, 이 0을 기대하지 말고 위의 비트 배치에 따라 해석하세요.
공개된 KUROSAKI 컨트롤러 모델은 D0와 고정된 비트 6만 반환합니다. 따라서 일반 버튼을 누르고 있어도 이 에뮬레이터의 D1과 마이크 비트는 항상 0입니다. 제공된 실행 결과는 빌드, 일반 버튼 입력과 이 0 신호 표시를 확인한 것입니다. 주변기기의 0이 아닌 신호, 전기적 타이밍과 실기 호환성은 검증하지 않았습니다. 여기서 0이 나온다고 장치가 정상 작동한다거나 실기에서도 읽을 수 없다는 뜻은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\expansion_input.c -I .\kitaq-docs\samples -I .\kitaqfc\lib -o .\out\expansion_input.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:233
Alias the same normalized microphone input bit as __joypad2p_voice.
__midi_clock
타이밍 클록 바이트 F8을 한 번 보냅니다. 반복 틱의 시점은 호출자가 관리합니다.
사용 형식
void __midi_clock(void);반환값
반환값은 없습니다. 사운드 레지스터나 드라이버 상태를 변경합니다.
사용 조건과 제한
$4016 D0을 TX, $4017 D4를 RX에 연결하는 외부 회로가 필요합니다. 일반 컨트롤러는 MIDI를 제공하지 않으며 이 호출들은 내부 APU를 재생하지 않습니다. 전송 중 DMC 재생/DMA를 멈추고 NMI·IRQ를 차단하며 포트와 공유 임시 공간에 동시 접근하지 마세요. NTSC의 비트당 57사이클은 약 31,400 bps로 31,250보다 약 +0.5%입니다. PAL 타이밍과 실제 어댑터의 왕복 통신은 검증하지 않았습니다.
사용 예
// Controller-port byte encoding. This does not synthesize sound inside the FC.
#define SOUND_PORT_ONLY
#include "fc_sound_example.h"
void main(void) {
sound_begin("MIDI PORT BYTES");
// sound_begin has disabled DMC DMA and all internal APU voices. A compatible external circuit is required.
// Stop NMI and mask IRQ so neither can split a software-timed serial frame.
__ppu_ctrl_set(0); __irq_disable();
__midi_out_byte(0x7E);
__midi_program_change(2,5);
__midi_control_change(2,7,100);
__midi_note_on(2,69,96);
__midi_note_off(2,69,0);
__midi_clock(); __midi_start(); __midi_continue(); __midi_stop();
// The emulator's unconnected input bit is low. Reading returns zero here.
// A real input held high would block waiting for its start bit, without timeout.
sound_result[0]=__midi_in_byte();
sound_result[1]=1;
__ppu_ctrl_set(0x80); __irq_enable();
sound_end();
}예상 결과
화면은 EXTERNAL PORT DATA를 표시하며 내부 음원은 울리지 않습니다. 전송 바이트는 16진수로 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC입니다. 검사는 NTSC 전송 셀 57사이클과 데이터 읽기 간격 57사이클을 요구합니다. 수신 00은 에뮬레이터의 연결되지 않은 입력에서 나온 값이며 외부 장치 통신을 입증하지 않습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_midi.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-midi.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:304
Transmit the single realtime byte F8 synchronously; this does not schedule future ticks.
kitaqfc/kitaqfc/CodeGenerator.cs:4803
case "__midi_clock": if (!Need(0)) return true; EmitAsm("LDA", Imm(0xF8)); EmitAsm("JSR", Abs("__kq_midi_out_a")); return true;
EmitHelperStart("__kq_midi_out_a");
bool midiScratchInZeroPage = _runtimeIntrinsicTmp0Address <= 0xFF;
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
// Unroll so neither branch cost nor payload bits change the baud.
// Each data edge: 44 NOP cycles + LSR zp (5) + LDA/ADC (4) + STA (4).
for (int bit = 0; bit < 8; bit++)
{
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LSR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("ADC", Imm(0));
EmitAsm("STA", Mem(0x4016));
}
// 51 delay cycles plus LDA/STA (6) place the stop bit 57 cycles later.
for (int delay = 0; delay < 24; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(0x4016));
EmitMidiBitDelay(); // Hold stop high for at least one full bit cell.
EmitAsm("RTS");
EmitHelperStart("__midi_out_byte");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
// Poll in nine-cycle steps, then sample near data-bit centers.
// IRQ/NMI masking belongs to the caller; no timeout/framing status is returned.
EmitHelperStart("__midi_in_byte");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
string waitStart = NewGeneratedLabel("midi_wait_start");
_assembly.Add(Expr.Make(Tag.Label, waitStart));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("BNE", Rel(waitStart));
// 81 cycles from the start-detect read to the first data read.
// Polling uncertainty is at most nine cycles within a 57-cycle cell.
for (int delay = 0; delay < 35; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
for (int bit = 0; bit < 8; bit++)
{
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("CMP", Imm(0x10));
EmitAsm("ROR", Mem(_runtimeIntrinsicTmp0Address));
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
}
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Compose the channelized note-on status, then send note and velocity arguments.__midi_continue
내부 음악 드라이버를 조작하지 않고 실시간 Continue 바이트 FB를 보냅니다.
사용 형식
void __midi_continue(void);반환값
반환값은 없습니다. 사운드 레지스터나 드라이버 상태를 변경합니다.
사용 조건과 제한
$4016 D0을 TX, $4017 D4를 RX에 연결하는 외부 회로가 필요합니다. 일반 컨트롤러는 MIDI를 제공하지 않으며 이 호출들은 내부 APU를 재생하지 않습니다. 전송 중 DMC 재생/DMA를 멈추고 NMI·IRQ를 차단하며 포트와 공유 임시 공간에 동시 접근하지 마세요. NTSC의 비트당 57사이클은 약 31,400 bps로 31,250보다 약 +0.5%입니다. PAL 타이밍과 실제 어댑터의 왕복 통신은 검증하지 않았습니다.
사용 예
// Controller-port byte encoding. This does not synthesize sound inside the FC.
#define SOUND_PORT_ONLY
#include "fc_sound_example.h"
void main(void) {
sound_begin("MIDI PORT BYTES");
// sound_begin has disabled DMC DMA and all internal APU voices. A compatible external circuit is required.
// Stop NMI and mask IRQ so neither can split a software-timed serial frame.
__ppu_ctrl_set(0); __irq_disable();
__midi_out_byte(0x7E);
__midi_program_change(2,5);
__midi_control_change(2,7,100);
__midi_note_on(2,69,96);
__midi_note_off(2,69,0);
__midi_clock(); __midi_start(); __midi_continue(); __midi_stop();
// The emulator's unconnected input bit is low. Reading returns zero here.
// A real input held high would block waiting for its start bit, without timeout.
sound_result[0]=__midi_in_byte();
sound_result[1]=1;
__ppu_ctrl_set(0x80); __irq_enable();
sound_end();
}예상 결과
화면은 EXTERNAL PORT DATA를 표시하며 내부 음원은 울리지 않습니다. 전송 바이트는 16진수로 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC입니다. 검사는 NTSC 전송 셀 57사이클과 데이터 읽기 간격 57사이클을 요구합니다. 수신 00은 에뮬레이터의 연결되지 않은 입력에서 나온 값이며 외부 장치 통신을 입증하지 않습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_midi.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-midi.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:308
Transmit realtime Continue (FB) through the serial adapter path.
kitaqfc/kitaqfc/CodeGenerator.cs:4805
case "__midi_continue": if (!Need(0)) return true; EmitAsm("LDA", Imm(0xFB)); EmitAsm("JSR", Abs("__kq_midi_out_a")); return true;
EmitHelperStart("__kq_midi_out_a");
bool midiScratchInZeroPage = _runtimeIntrinsicTmp0Address <= 0xFF;
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
// Unroll so neither branch cost nor payload bits change the baud.
// Each data edge: 44 NOP cycles + LSR zp (5) + LDA/ADC (4) + STA (4).
for (int bit = 0; bit < 8; bit++)
{
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LSR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("ADC", Imm(0));
EmitAsm("STA", Mem(0x4016));
}
// 51 delay cycles plus LDA/STA (6) place the stop bit 57 cycles later.
for (int delay = 0; delay < 24; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(0x4016));
EmitMidiBitDelay(); // Hold stop high for at least one full bit cell.
EmitAsm("RTS");
EmitHelperStart("__midi_out_byte");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
// Poll in nine-cycle steps, then sample near data-bit centers.
// IRQ/NMI masking belongs to the caller; no timeout/framing status is returned.
EmitHelperStart("__midi_in_byte");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
string waitStart = NewGeneratedLabel("midi_wait_start");
_assembly.Add(Expr.Make(Tag.Label, waitStart));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("BNE", Rel(waitStart));
// 81 cycles from the start-detect read to the first data read.
// Polling uncertainty is at most nine cycles within a 57-cycle cell.
for (int delay = 0; delay < 35; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
for (int bit = 0; bit < 8; bit++)
{
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("CMP", Imm(0x10));
EmitAsm("ROR", Mem(_runtimeIntrinsicTmp0Address));
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
}
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Compose the channelized note-on status, then send note and velocity arguments.__midi_control_change
컨트롤 체인지를 0xB0 | (ch & 0x0F), 컨트롤러 번호, 값으로 보냅니다.
사용 형식
void __midi_control_change(u8 ch, u8 cc, u8 value);매개변수
chMIDI 채널 0..15입니다. 하위 니블이 상태 바이트에 들어갑니다. 0은 표시상의 MIDI 채널 1입니다.
cc컨트롤러 번호 0..127입니다. 7은 채널 음량입니다.
value컨트롤러값 0..127입니다. 상위 비트는 자동으로 지우지 않습니다.
반환값
반환값은 없습니다. 사운드 레지스터나 드라이버 상태를 변경합니다.
사용 조건과 제한
$4016 D0을 TX, $4017 D4를 RX에 연결하는 외부 회로가 필요합니다. 일반 컨트롤러는 MIDI를 제공하지 않으며 이 호출들은 내부 APU를 재생하지 않습니다. 전송 중 DMC 재생/DMA를 멈추고 NMI·IRQ를 차단하며 포트와 공유 임시 공간에 동시 접근하지 마세요. NTSC의 비트당 57사이클은 약 31,400 bps로 31,250보다 약 +0.5%입니다. PAL 타이밍과 실제 어댑터의 왕복 통신은 검증하지 않았습니다.
사용 예
// Controller-port byte encoding. This does not synthesize sound inside the FC.
#define SOUND_PORT_ONLY
#include "fc_sound_example.h"
void main(void) {
sound_begin("MIDI PORT BYTES");
// sound_begin has disabled DMC DMA and all internal APU voices. A compatible external circuit is required.
// Stop NMI and mask IRQ so neither can split a software-timed serial frame.
__ppu_ctrl_set(0); __irq_disable();
__midi_out_byte(0x7E);
__midi_program_change(2,5);
__midi_control_change(2,7,100);
__midi_note_on(2,69,96);
__midi_note_off(2,69,0);
__midi_clock(); __midi_start(); __midi_continue(); __midi_stop();
// The emulator's unconnected input bit is low. Reading returns zero here.
// A real input held high would block waiting for its start bit, without timeout.
sound_result[0]=__midi_in_byte();
sound_result[1]=1;
__ppu_ctrl_set(0x80); __irq_enable();
sound_end();
}예상 결과
화면은 EXTERNAL PORT DATA를 표시하며 내부 음원은 울리지 않습니다. 전송 바이트는 16진수로 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC입니다. 검사는 NTSC 전송 셀 57사이클과 데이터 읽기 간격 57사이클을 요구합니다. 수신 00은 에뮬레이터의 연결되지 않은 입력에서 나온 값이며 외부 장치 통신을 입증하지 않습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_midi.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-midi.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:300
Send B0|(ch&0F), controller and value; both data arguments must be valid 7-bit values.
kitaqfc/kitaqfc/CodeGenerator.cs:4800
case "__midi_control_change": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1}, funcName);
EmitHelperStart("__midi_control_change");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("AND", Imm(0x0F));
EmitAsm("ORA", Imm(0xB0));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
EmitHelperStart("__kq_midi_out_a");
bool midiScratchInZeroPage = _runtimeIntrinsicTmp0Address <= 0xFF;
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
// Unroll so neither branch cost nor payload bits change the baud.
// Each data edge: 44 NOP cycles + LSR zp (5) + LDA/ADC (4) + STA (4).
for (int bit = 0; bit < 8; bit++)
{
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LSR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("ADC", Imm(0));
EmitAsm("STA", Mem(0x4016));
}
// 51 delay cycles plus LDA/STA (6) place the stop bit 57 cycles later.
for (int delay = 0; delay < 24; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(0x4016));
EmitMidiBitDelay(); // Hold stop high for at least one full bit cell.
EmitAsm("RTS");
EmitHelperStart("__midi_out_byte");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
// Poll in nine-cycle steps, then sample near data-bit centers.
// IRQ/NMI masking belongs to the caller; no timeout/framing status is returned.
EmitHelperStart("__midi_in_byte");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
string waitStart = NewGeneratedLabel("midi_wait_start");
_assembly.Add(Expr.Make(Tag.Label, waitStart));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("BNE", Rel(waitStart));
// 81 cycles from the start-detect read to the first data read.
// Polling uncertainty is at most nine cycles within a 57-cycle cell.
for (int delay = 0; delay < 35; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
for (int bit = 0; bit < 8; bit++)
{
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("CMP", Imm(0x10));
EmitAsm("ROR", Mem(_runtimeIntrinsicTmp0Address));
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
}
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Compose the channelized note-on status, then send note and velocity arguments.__midi_in_byte
수신 입력이 낮아질 때까지 기다린 뒤 정해진 간격으로 8비트를 샘플링합니다.
사용 형식
u8 __midi_in_byte(void);반환값
수신한 u8을 반환합니다. 시간 제한, 프레이밍 검사, 수신 버퍼는 없습니다. 입력이 높게 유지되면 무한 대기하고 낮게 유지되면 0으로 읽습니다.
사용 조건과 제한
$4016 D0을 TX, $4017 D4를 RX에 연결하는 외부 회로가 필요합니다. 일반 컨트롤러는 MIDI를 제공하지 않으며 이 호출들은 내부 APU를 재생하지 않습니다. 전송 중 DMC 재생/DMA를 멈추고 NMI·IRQ를 차단하며 포트와 공유 임시 공간에 동시 접근하지 마세요. NTSC의 비트당 57사이클은 약 31,400 bps로 31,250보다 약 +0.5%입니다. PAL 타이밍과 실제 어댑터의 왕복 통신은 검증하지 않았습니다.
사용 예
// Controller-port byte encoding. This does not synthesize sound inside the FC.
#define SOUND_PORT_ONLY
#include "fc_sound_example.h"
void main(void) {
sound_begin("MIDI PORT BYTES");
// sound_begin has disabled DMC DMA and all internal APU voices. A compatible external circuit is required.
// Stop NMI and mask IRQ so neither can split a software-timed serial frame.
__ppu_ctrl_set(0); __irq_disable();
__midi_out_byte(0x7E);
__midi_program_change(2,5);
__midi_control_change(2,7,100);
__midi_note_on(2,69,96);
__midi_note_off(2,69,0);
__midi_clock(); __midi_start(); __midi_continue(); __midi_stop();
// The emulator's unconnected input bit is low. Reading returns zero here.
// A real input held high would block waiting for its start bit, without timeout.
sound_result[0]=__midi_in_byte();
sound_result[1]=1;
__ppu_ctrl_set(0x80); __irq_enable();
sound_end();
}예상 결과
화면은 EXTERNAL PORT DATA를 표시하며 내부 음원은 울리지 않습니다. 전송 바이트는 16진수로 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC입니다. 검사는 NTSC 전송 셀 57사이클과 데이터 읽기 간격 57사이클을 요구합니다. 수신 00은 에뮬레이터의 연결되지 않은 입력에서 나온 값이며 외부 장치 통신을 입증하지 않습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_midi.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-midi.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:294
Block for a low start signal and sample eight bits. No timeout, framing check or receive buffer is provided.
kitaqfc/kitaqfc/CodeGenerator.cs:4796
case "__midi_in_byte": if (!Need(0)) return true; EmitAsm("JSR", Abs("__midi_in_byte")); return true;
// Pack MIDI message fields as bytes; real-time commands below load their fixed status byte directly.
EmitHelperStart("__midi_in_byte");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
string waitStart = NewGeneratedLabel("midi_wait_start");
_assembly.Add(Expr.Make(Tag.Label, waitStart));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("BNE", Rel(waitStart));
// 81 cycles from the start-detect read to the first data read.
// Polling uncertainty is at most nine cycles within a 57-cycle cell.
for (int delay = 0; delay < 35; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
for (int bit = 0; bit < 8; bit++)
{
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("CMP", Imm(0x10));
EmitAsm("ROR", Mem(_runtimeIntrinsicTmp0Address));
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
}
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
EmitHelperStart("__kq_midi_out_a");
bool midiScratchInZeroPage = _runtimeIntrinsicTmp0Address <= 0xFF;
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
// Unroll so neither branch cost nor payload bits change the baud.
// Each data edge: 44 NOP cycles + LSR zp (5) + LDA/ADC (4) + STA (4).
for (int bit = 0; bit < 8; bit++)
{
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LSR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("ADC", Imm(0));
EmitAsm("STA", Mem(0x4016));
}
// 51 delay cycles plus LDA/STA (6) place the stop bit 57 cycles later.
for (int delay = 0; delay < 24; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(0x4016));
EmitMidiBitDelay(); // Hold stop high for at least one full bit cell.
EmitAsm("RTS");
EmitHelperStart("__midi_out_byte");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
// Poll in nine-cycle steps, then sample near data-bit centers.
// IRQ/NMI masking belongs to the caller; no timeout/framing status is returned.
EmitHelperStart("__midi_in_byte");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
string waitStart = NewGeneratedLabel("midi_wait_start");
_assembly.Add(Expr.Make(Tag.Label, waitStart));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("BNE", Rel(waitStart));
// 81 cycles from the start-detect read to the first data read.
// Polling uncertainty is at most nine cycles within a 57-cycle cell.
for (int delay = 0; delay < 35; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
for (int bit = 0; bit < 8; bit++)
{
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("CMP", Imm(0x10));
EmitAsm("ROR", Mem(_runtimeIntrinsicTmp0Address));
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
}
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Compose the channelized note-on status, then send note and velocity arguments.__midi_note_off
노트 오프를 0x80 | (ch & 0x0F), note, velocity의 3바이트로 보냅니다.
사용 형식
void __midi_note_off(u8 ch, u8 note, u8 velocity);매개변수
chMIDI 채널 0..15입니다. 하위 니블이 상태 바이트에 들어갑니다. 0은 표시상의 MIDI 채널 1입니다.
noteMIDI 음 번호 0..127이며 69는 A4입니다. 7비트 마스크를 적용하지 않으므로 호출자가 범위를 지켜야 합니다.
velocity벨로시티 0..127입니다. 7비트로 자동 마스킹하지 않습니다.
반환값
반환값은 없습니다. 사운드 레지스터나 드라이버 상태를 변경합니다.
사용 조건과 제한
$4016 D0을 TX, $4017 D4를 RX에 연결하는 외부 회로가 필요합니다. 일반 컨트롤러는 MIDI를 제공하지 않으며 이 호출들은 내부 APU를 재생하지 않습니다. 전송 중 DMC 재생/DMA를 멈추고 NMI·IRQ를 차단하며 포트와 공유 임시 공간에 동시 접근하지 마세요. NTSC의 비트당 57사이클은 약 31,400 bps로 31,250보다 약 +0.5%입니다. PAL 타이밍과 실제 어댑터의 왕복 통신은 검증하지 않았습니다.
사용 예
// Controller-port byte encoding. This does not synthesize sound inside the FC.
#define SOUND_PORT_ONLY
#include "fc_sound_example.h"
void main(void) {
sound_begin("MIDI PORT BYTES");
// sound_begin has disabled DMC DMA and all internal APU voices. A compatible external circuit is required.
// Stop NMI and mask IRQ so neither can split a software-timed serial frame.
__ppu_ctrl_set(0); __irq_disable();
__midi_out_byte(0x7E);
__midi_program_change(2,5);
__midi_control_change(2,7,100);
__midi_note_on(2,69,96);
__midi_note_off(2,69,0);
__midi_clock(); __midi_start(); __midi_continue(); __midi_stop();
// The emulator's unconnected input bit is low. Reading returns zero here.
// A real input held high would block waiting for its start bit, without timeout.
sound_result[0]=__midi_in_byte();
sound_result[1]=1;
__ppu_ctrl_set(0x80); __irq_enable();
sound_end();
}예상 결과
화면은 EXTERNAL PORT DATA를 표시하며 내부 음원은 울리지 않습니다. 전송 바이트는 16진수로 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC입니다. 검사는 NTSC 전송 셀 57사이클과 데이터 읽기 간격 57사이클을 요구합니다. 수신 00은 에뮬레이터의 연결되지 않은 입력에서 나온 값이며 외부 장치 통신을 입증하지 않습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_midi.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-midi.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:298
Send 80|(ch&0F), note and velocity through the same synchronous serial path.
kitaqfc/kitaqfc/CodeGenerator.cs:4799
case "__midi_note_off": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1}, funcName);
EmitHelperStart("__midi_note_off");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("AND", Imm(0x0F));
EmitAsm("ORA", Imm(0x80));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
EmitHelperStart("__kq_midi_out_a");
bool midiScratchInZeroPage = _runtimeIntrinsicTmp0Address <= 0xFF;
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
// Unroll so neither branch cost nor payload bits change the baud.
// Each data edge: 44 NOP cycles + LSR zp (5) + LDA/ADC (4) + STA (4).
for (int bit = 0; bit < 8; bit++)
{
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LSR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("ADC", Imm(0));
EmitAsm("STA", Mem(0x4016));
}
// 51 delay cycles plus LDA/STA (6) place the stop bit 57 cycles later.
for (int delay = 0; delay < 24; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(0x4016));
EmitMidiBitDelay(); // Hold stop high for at least one full bit cell.
EmitAsm("RTS");
EmitHelperStart("__midi_out_byte");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
// Poll in nine-cycle steps, then sample near data-bit centers.
// IRQ/NMI masking belongs to the caller; no timeout/framing status is returned.
EmitHelperStart("__midi_in_byte");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
string waitStart = NewGeneratedLabel("midi_wait_start");
_assembly.Add(Expr.Make(Tag.Label, waitStart));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("BNE", Rel(waitStart));
// 81 cycles from the start-detect read to the first data read.
// Polling uncertainty is at most nine cycles within a 57-cycle cell.
for (int delay = 0; delay < 35; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
for (int bit = 0; bit < 8; bit++)
{
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("CMP", Imm(0x10));
EmitAsm("ROR", Mem(_runtimeIntrinsicTmp0Address));
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
}
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Compose the channelized note-on status, then send note and velocity arguments.__midi_note_on
노트 온을 0x90 | (ch & 0x0F), note, velocity의 3바이트로 보냅니다.
사용 형식
void __midi_note_on(u8 ch, u8 note, u8 velocity);매개변수
chMIDI 채널 0..15입니다. 하위 니블이 상태 바이트에 들어갑니다. 0은 표시상의 MIDI 채널 1입니다.
noteMIDI 음 번호 0..127이며 69는 A4입니다. 7비트 마스크를 적용하지 않으므로 호출자가 범위를 지켜야 합니다.
velocity벨로시티 0..127입니다. 7비트로 자동 마스킹하지 않습니다.
반환값
반환값은 없습니다. 사운드 레지스터나 드라이버 상태를 변경합니다.
사용 조건과 제한
$4016 D0을 TX, $4017 D4를 RX에 연결하는 외부 회로가 필요합니다. 일반 컨트롤러는 MIDI를 제공하지 않으며 이 호출들은 내부 APU를 재생하지 않습니다. 전송 중 DMC 재생/DMA를 멈추고 NMI·IRQ를 차단하며 포트와 공유 임시 공간에 동시 접근하지 마세요. NTSC의 비트당 57사이클은 약 31,400 bps로 31,250보다 약 +0.5%입니다. PAL 타이밍과 실제 어댑터의 왕복 통신은 검증하지 않았습니다.
사용 예
// Controller-port byte encoding. This does not synthesize sound inside the FC.
#define SOUND_PORT_ONLY
#include "fc_sound_example.h"
void main(void) {
sound_begin("MIDI PORT BYTES");
// sound_begin has disabled DMC DMA and all internal APU voices. A compatible external circuit is required.
// Stop NMI and mask IRQ so neither can split a software-timed serial frame.
__ppu_ctrl_set(0); __irq_disable();
__midi_out_byte(0x7E);
__midi_program_change(2,5);
__midi_control_change(2,7,100);
__midi_note_on(2,69,96);
__midi_note_off(2,69,0);
__midi_clock(); __midi_start(); __midi_continue(); __midi_stop();
// The emulator's unconnected input bit is low. Reading returns zero here.
// A real input held high would block waiting for its start bit, without timeout.
sound_result[0]=__midi_in_byte();
sound_result[1]=1;
__ppu_ctrl_set(0x80); __irq_enable();
sound_end();
}예상 결과
화면은 EXTERNAL PORT DATA를 표시하며 내부 음원은 울리지 않습니다. 전송 바이트는 16진수로 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC입니다. 검사는 NTSC 전송 셀 57사이클과 데이터 읽기 간격 57사이클을 요구합니다. 수신 00은 에뮬레이터의 연결되지 않은 입력에서 나온 값이며 외부 장치 통신을 입증하지 않습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_midi.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-midi.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:296
Send 90|(ch&0F), note and velocity. Supply 7-bit data bytes; they are not masked by this helper.
kitaqfc/kitaqfc/CodeGenerator.cs:4798
case "__midi_note_on": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1}, funcName);
EmitHelperStart("__midi_note_on");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("AND", Imm(0x0F));
EmitAsm("ORA", Imm(0x90));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
EmitHelperStart("__kq_midi_out_a");
bool midiScratchInZeroPage = _runtimeIntrinsicTmp0Address <= 0xFF;
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
// Unroll so neither branch cost nor payload bits change the baud.
// Each data edge: 44 NOP cycles + LSR zp (5) + LDA/ADC (4) + STA (4).
for (int bit = 0; bit < 8; bit++)
{
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LSR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("ADC", Imm(0));
EmitAsm("STA", Mem(0x4016));
}
// 51 delay cycles plus LDA/STA (6) place the stop bit 57 cycles later.
for (int delay = 0; delay < 24; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(0x4016));
EmitMidiBitDelay(); // Hold stop high for at least one full bit cell.
EmitAsm("RTS");
EmitHelperStart("__midi_out_byte");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
// Poll in nine-cycle steps, then sample near data-bit centers.
// IRQ/NMI masking belongs to the caller; no timeout/framing status is returned.
EmitHelperStart("__midi_in_byte");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
string waitStart = NewGeneratedLabel("midi_wait_start");
_assembly.Add(Expr.Make(Tag.Label, waitStart));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("BNE", Rel(waitStart));
// 81 cycles from the start-detect read to the first data read.
// Polling uncertainty is at most nine cycles within a 57-cycle cell.
for (int delay = 0; delay < 35; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
for (int bit = 0; bit < 8; bit++)
{
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("CMP", Imm(0x10));
EmitAsm("ROR", Mem(_runtimeIntrinsicTmp0Address));
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
}
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Compose the channelized note-on status, then send note and velocity arguments.__midi_out_byte
8N1 바이트 하나를 동기 전송합니다. 시작은 낮음, 데이터 8비트는 최하위 비트부터, 정지는 높음입니다.
사용 형식
void __midi_out_byte(u8 byte);매개변수
byte상태·데이터 바이트를 포함하는 원시 바이트 0..255입니다.
반환값
반환값은 없습니다. 사운드 레지스터나 드라이버 상태를 변경합니다.
사용 조건과 제한
$4016 D0을 TX, $4017 D4를 RX에 연결하는 외부 회로가 필요합니다. 일반 컨트롤러는 MIDI를 제공하지 않으며 이 호출들은 내부 APU를 재생하지 않습니다. 전송 중 DMC 재생/DMA를 멈추고 NMI·IRQ를 차단하며 포트와 공유 임시 공간에 동시 접근하지 마세요. NTSC의 비트당 57사이클은 약 31,400 bps로 31,250보다 약 +0.5%입니다. PAL 타이밍과 실제 어댑터의 왕복 통신은 검증하지 않았습니다.
사용 예
// Controller-port byte encoding. This does not synthesize sound inside the FC.
#define SOUND_PORT_ONLY
#include "fc_sound_example.h"
void main(void) {
sound_begin("MIDI PORT BYTES");
// sound_begin has disabled DMC DMA and all internal APU voices. A compatible external circuit is required.
// Stop NMI and mask IRQ so neither can split a software-timed serial frame.
__ppu_ctrl_set(0); __irq_disable();
__midi_out_byte(0x7E);
__midi_program_change(2,5);
__midi_control_change(2,7,100);
__midi_note_on(2,69,96);
__midi_note_off(2,69,0);
__midi_clock(); __midi_start(); __midi_continue(); __midi_stop();
// The emulator's unconnected input bit is low. Reading returns zero here.
// A real input held high would block waiting for its start bit, without timeout.
sound_result[0]=__midi_in_byte();
sound_result[1]=1;
__ppu_ctrl_set(0x80); __irq_enable();
sound_end();
}예상 결과
화면은 EXTERNAL PORT DATA를 표시하며 내부 음원은 울리지 않습니다. 전송 바이트는 16진수로 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC입니다. 검사는 NTSC 전송 셀 57사이클과 데이터 읽기 간격 57사이클을 요구합니다. 수신 00은 에뮬레이터의 연결되지 않은 입력에서 나온 값이며 외부 장치 통신을 입증하지 않습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_midi.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-midi.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:292
Send start-low, eight least-significant-first bits and stop-high in 57-cycle NTSC bit cells (about 31400 bps). Stop DMC DMA and mask IRQ/NMI during transfers; PAL timing and electrical adapter compatibility are not established here.
kitaqfc/kitaqfc/CodeGenerator.cs:4795
case "__midi_out_byte": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1}, funcName);
EmitHelperStart("__midi_out_byte");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
EmitHelperStart("__kq_midi_out_a");
bool midiScratchInZeroPage = _runtimeIntrinsicTmp0Address <= 0xFF;
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
// Unroll so neither branch cost nor payload bits change the baud.
// Each data edge: 44 NOP cycles + LSR zp (5) + LDA/ADC (4) + STA (4).
for (int bit = 0; bit < 8; bit++)
{
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LSR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("ADC", Imm(0));
EmitAsm("STA", Mem(0x4016));
}
// 51 delay cycles plus LDA/STA (6) place the stop bit 57 cycles later.
for (int delay = 0; delay < 24; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(0x4016));
EmitMidiBitDelay(); // Hold stop high for at least one full bit cell.
EmitAsm("RTS");
EmitHelperStart("__midi_out_byte");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
// Poll in nine-cycle steps, then sample near data-bit centers.
// IRQ/NMI masking belongs to the caller; no timeout/framing status is returned.
EmitHelperStart("__midi_in_byte");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
string waitStart = NewGeneratedLabel("midi_wait_start");
_assembly.Add(Expr.Make(Tag.Label, waitStart));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("BNE", Rel(waitStart));
// 81 cycles from the start-detect read to the first data read.
// Polling uncertainty is at most nine cycles within a 57-cycle cell.
for (int delay = 0; delay < 35; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
for (int bit = 0; bit < 8; bit++)
{
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("CMP", Imm(0x10));
EmitAsm("ROR", Mem(_runtimeIntrinsicTmp0Address));
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
}
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Compose the channelized note-on status, then send note and velocity arguments.__midi_program_change
프로그램 체인지를 0xC0 | (ch & 0x0F) 뒤에 프로그램 번호를 붙여 보냅니다.
사용 형식
void __midi_program_change(u8 ch, u8 program);매개변수
chMIDI 채널 0..15입니다. 하위 니블이 상태 바이트에 들어갑니다. 0은 표시상의 MIDI 채널 1입니다.
program프로그램 번호 0..127입니다. 악기 매핑은 수신 장치에 따라 달라집니다.
반환값
반환값은 없습니다. 사운드 레지스터나 드라이버 상태를 변경합니다.
사용 조건과 제한
$4016 D0을 TX, $4017 D4를 RX에 연결하는 외부 회로가 필요합니다. 일반 컨트롤러는 MIDI를 제공하지 않으며 이 호출들은 내부 APU를 재생하지 않습니다. 전송 중 DMC 재생/DMA를 멈추고 NMI·IRQ를 차단하며 포트와 공유 임시 공간에 동시 접근하지 마세요. NTSC의 비트당 57사이클은 약 31,400 bps로 31,250보다 약 +0.5%입니다. PAL 타이밍과 실제 어댑터의 왕복 통신은 검증하지 않았습니다.
사용 예
// Controller-port byte encoding. This does not synthesize sound inside the FC.
#define SOUND_PORT_ONLY
#include "fc_sound_example.h"
void main(void) {
sound_begin("MIDI PORT BYTES");
// sound_begin has disabled DMC DMA and all internal APU voices. A compatible external circuit is required.
// Stop NMI and mask IRQ so neither can split a software-timed serial frame.
__ppu_ctrl_set(0); __irq_disable();
__midi_out_byte(0x7E);
__midi_program_change(2,5);
__midi_control_change(2,7,100);
__midi_note_on(2,69,96);
__midi_note_off(2,69,0);
__midi_clock(); __midi_start(); __midi_continue(); __midi_stop();
// The emulator's unconnected input bit is low. Reading returns zero here.
// A real input held high would block waiting for its start bit, without timeout.
sound_result[0]=__midi_in_byte();
sound_result[1]=1;
__ppu_ctrl_set(0x80); __irq_enable();
sound_end();
}예상 결과
화면은 EXTERNAL PORT DATA를 표시하며 내부 음원은 울리지 않습니다. 전송 바이트는 16진수로 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC입니다. 검사는 NTSC 전송 셀 57사이클과 데이터 읽기 간격 57사이클을 요구합니다. 수신 00은 에뮬레이터의 연결되지 않은 입력에서 나온 값이며 외부 장치 통신을 입증하지 않습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_midi.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-midi.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:302
Send C0|(ch&0F) followed by the caller-supplied 7-bit program value.
kitaqfc/kitaqfc/CodeGenerator.cs:4801
case "__midi_program_change": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1}, funcName);
// Real-time MIDI commands send a single status byte through the common output routine.
EmitHelperStart("__midi_program_change");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("AND", Imm(0x0F));
EmitAsm("ORA", Imm(0xC0));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
EmitHelperStart("__kq_midi_out_a");
bool midiScratchInZeroPage = _runtimeIntrinsicTmp0Address <= 0xFF;
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
// Unroll so neither branch cost nor payload bits change the baud.
// Each data edge: 44 NOP cycles + LSR zp (5) + LDA/ADC (4) + STA (4).
for (int bit = 0; bit < 8; bit++)
{
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LSR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("ADC", Imm(0));
EmitAsm("STA", Mem(0x4016));
}
// 51 delay cycles plus LDA/STA (6) place the stop bit 57 cycles later.
for (int delay = 0; delay < 24; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(0x4016));
EmitMidiBitDelay(); // Hold stop high for at least one full bit cell.
EmitAsm("RTS");
EmitHelperStart("__midi_out_byte");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
// Poll in nine-cycle steps, then sample near data-bit centers.
// IRQ/NMI masking belongs to the caller; no timeout/framing status is returned.
EmitHelperStart("__midi_in_byte");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
string waitStart = NewGeneratedLabel("midi_wait_start");
_assembly.Add(Expr.Make(Tag.Label, waitStart));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("BNE", Rel(waitStart));
// 81 cycles from the start-detect read to the first data read.
// Polling uncertainty is at most nine cycles within a 57-cycle cell.
for (int delay = 0; delay < 35; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
for (int bit = 0; bit < 8; bit++)
{
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("CMP", Imm(0x10));
EmitAsm("ROR", Mem(_runtimeIntrinsicTmp0Address));
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
}
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Compose the channelized note-on status, then send note and velocity arguments.__midi_start
게임의 곡 포인터를 바꾸지 않고 실시간 Start 바이트 FA를 보냅니다.
사용 형식
void __midi_start(void);반환값
반환값은 없습니다. 사운드 레지스터나 드라이버 상태를 변경합니다.
사용 조건과 제한
$4016 D0을 TX, $4017 D4를 RX에 연결하는 외부 회로가 필요합니다. 일반 컨트롤러는 MIDI를 제공하지 않으며 이 호출들은 내부 APU를 재생하지 않습니다. 전송 중 DMC 재생/DMA를 멈추고 NMI·IRQ를 차단하며 포트와 공유 임시 공간에 동시 접근하지 마세요. NTSC의 비트당 57사이클은 약 31,400 bps로 31,250보다 약 +0.5%입니다. PAL 타이밍과 실제 어댑터의 왕복 통신은 검증하지 않았습니다.
사용 예
// Controller-port byte encoding. This does not synthesize sound inside the FC.
#define SOUND_PORT_ONLY
#include "fc_sound_example.h"
void main(void) {
sound_begin("MIDI PORT BYTES");
// sound_begin has disabled DMC DMA and all internal APU voices. A compatible external circuit is required.
// Stop NMI and mask IRQ so neither can split a software-timed serial frame.
__ppu_ctrl_set(0); __irq_disable();
__midi_out_byte(0x7E);
__midi_program_change(2,5);
__midi_control_change(2,7,100);
__midi_note_on(2,69,96);
__midi_note_off(2,69,0);
__midi_clock(); __midi_start(); __midi_continue(); __midi_stop();
// The emulator's unconnected input bit is low. Reading returns zero here.
// A real input held high would block waiting for its start bit, without timeout.
sound_result[0]=__midi_in_byte();
sound_result[1]=1;
__ppu_ctrl_set(0x80); __irq_enable();
sound_end();
}예상 결과
화면은 EXTERNAL PORT DATA를 표시하며 내부 음원은 울리지 않습니다. 전송 바이트는 16진수로 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC입니다. 검사는 NTSC 전송 셀 57사이클과 데이터 읽기 간격 57사이클을 요구합니다. 수신 00은 에뮬레이터의 연결되지 않은 입력에서 나온 값이며 외부 장치 통신을 입증하지 않습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_midi.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-midi.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:306
Transmit realtime Start (FA) through the serial adapter path.
kitaqfc/kitaqfc/CodeGenerator.cs:4804
case "__midi_start": if (!Need(0)) return true; EmitAsm("LDA", Imm(0xFA)); EmitAsm("JSR", Abs("__kq_midi_out_a")); return true;
EmitHelperStart("__kq_midi_out_a");
bool midiScratchInZeroPage = _runtimeIntrinsicTmp0Address <= 0xFF;
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
// Unroll so neither branch cost nor payload bits change the baud.
// Each data edge: 44 NOP cycles + LSR zp (5) + LDA/ADC (4) + STA (4).
for (int bit = 0; bit < 8; bit++)
{
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LSR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("ADC", Imm(0));
EmitAsm("STA", Mem(0x4016));
}
// 51 delay cycles plus LDA/STA (6) place the stop bit 57 cycles later.
for (int delay = 0; delay < 24; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(0x4016));
EmitMidiBitDelay(); // Hold stop high for at least one full bit cell.
EmitAsm("RTS");
EmitHelperStart("__midi_out_byte");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
// Poll in nine-cycle steps, then sample near data-bit centers.
// IRQ/NMI masking belongs to the caller; no timeout/framing status is returned.
EmitHelperStart("__midi_in_byte");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
string waitStart = NewGeneratedLabel("midi_wait_start");
_assembly.Add(Expr.Make(Tag.Label, waitStart));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("BNE", Rel(waitStart));
// 81 cycles from the start-detect read to the first data read.
// Polling uncertainty is at most nine cycles within a 57-cycle cell.
for (int delay = 0; delay < 35; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
for (int bit = 0; bit < 8; bit++)
{
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("CMP", Imm(0x10));
EmitAsm("ROR", Mem(_runtimeIntrinsicTmp0Address));
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
}
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Compose the channelized note-on status, then send note and velocity arguments.__midi_stop
실시간 Stop 바이트 FC를 보냅니다. APU나 이미 보낸 음을 직접 무음으로 만들지는 않습니다.
사용 형식
void __midi_stop(void);반환값
반환값은 없습니다. 사운드 레지스터나 드라이버 상태를 변경합니다.
사용 조건과 제한
$4016 D0을 TX, $4017 D4를 RX에 연결하는 외부 회로가 필요합니다. 일반 컨트롤러는 MIDI를 제공하지 않으며 이 호출들은 내부 APU를 재생하지 않습니다. 전송 중 DMC 재생/DMA를 멈추고 NMI·IRQ를 차단하며 포트와 공유 임시 공간에 동시 접근하지 마세요. NTSC의 비트당 57사이클은 약 31,400 bps로 31,250보다 약 +0.5%입니다. PAL 타이밍과 실제 어댑터의 왕복 통신은 검증하지 않았습니다.
사용 예
// Controller-port byte encoding. This does not synthesize sound inside the FC.
#define SOUND_PORT_ONLY
#include "fc_sound_example.h"
void main(void) {
sound_begin("MIDI PORT BYTES");
// sound_begin has disabled DMC DMA and all internal APU voices. A compatible external circuit is required.
// Stop NMI and mask IRQ so neither can split a software-timed serial frame.
__ppu_ctrl_set(0); __irq_disable();
__midi_out_byte(0x7E);
__midi_program_change(2,5);
__midi_control_change(2,7,100);
__midi_note_on(2,69,96);
__midi_note_off(2,69,0);
__midi_clock(); __midi_start(); __midi_continue(); __midi_stop();
// The emulator's unconnected input bit is low. Reading returns zero here.
// A real input held high would block waiting for its start bit, without timeout.
sound_result[0]=__midi_in_byte();
sound_result[1]=1;
__ppu_ctrl_set(0x80); __irq_enable();
sound_end();
}예상 결과
화면은 EXTERNAL PORT DATA를 표시하며 내부 음원은 울리지 않습니다. 전송 바이트는 16진수로 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC입니다. 검사는 NTSC 전송 셀 57사이클과 데이터 읽기 간격 57사이클을 요구합니다. 수신 00은 에뮬레이터의 연결되지 않은 입력에서 나온 값이며 외부 장치 통신을 입증하지 않습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_midi.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-midi.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:310
Transmit realtime Stop (FC) through the serial adapter path.
kitaqfc/kitaqfc/CodeGenerator.cs:4806
case "__midi_stop": if (!Need(0)) return true; EmitAsm("LDA", Imm(0xFC)); EmitAsm("JSR", Abs("__kq_midi_out_a")); return true;
// Sprite helpers modify the runtime OAM representation; DMA remains a separate operation.
EmitHelperStart("__kq_midi_out_a");
bool midiScratchInZeroPage = _runtimeIntrinsicTmp0Address <= 0xFF;
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
// Unroll so neither branch cost nor payload bits change the baud.
// Each data edge: 44 NOP cycles + LSR zp (5) + LDA/ADC (4) + STA (4).
for (int bit = 0; bit < 8; bit++)
{
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LSR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("ADC", Imm(0));
EmitAsm("STA", Mem(0x4016));
}
// 51 delay cycles plus LDA/STA (6) place the stop bit 57 cycles later.
for (int delay = 0; delay < 24; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(0x4016));
EmitMidiBitDelay(); // Hold stop high for at least one full bit cell.
EmitAsm("RTS");
EmitHelperStart("__midi_out_byte");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
// Poll in nine-cycle steps, then sample near data-bit centers.
// IRQ/NMI masking belongs to the caller; no timeout/framing status is returned.
EmitHelperStart("__midi_in_byte");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
string waitStart = NewGeneratedLabel("midi_wait_start");
_assembly.Add(Expr.Make(Tag.Label, waitStart));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("BNE", Rel(waitStart));
// 81 cycles from the start-detect read to the first data read.
// Polling uncertainty is at most nine cycles within a 57-cycle cell.
for (int delay = 0; delay < 35; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
for (int bit = 0; bit < 8; bit++)
{
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("CMP", Imm(0x10));
EmitAsm("ROR", Mem(_runtimeIntrinsicTmp0Address));
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
}
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Compose the channelized note-on status, then send note and velocity arguments.__mirroring_set
논리 네임테이블 영역 네 개를 본체의 화면 RAM 페이지 두 개에 매핑하여 스크롤 배치나 단일 화면 페이지 선택에 사용합니다.
사용 형식
void __mirroring_set(u8 mode);매개변수
mode모드 번호는 보드에 따라 다릅니다. MMC1/SUROM: 0=아래쪽 단일 화면, 1=위쪽 단일 화면, 2=수직, 3=수평. MMC3: 0=수직, 1=수평. MMC5: 0=수평, 1=수직, 2=아래쪽 단일 화면, 3=위쪽 단일 화면. AxROM: 0=아래쪽 단일 화면, 1=위쪽 단일 화면. FME7: 0=수직, 1=수평, 2=아래쪽 단일 화면, 3=위쪽 단일 화면. MMC3/AxROM은 최하위 비트, 나머지는 하위 2비트를 사용합니다.
반환값
반환값은 없습니다.
사용 조건과 제한
내장 함수 선언은 intrinsics.h에 있습니다. nes_mapper_irq_* 매크로를 쓰려면 nes_game.h를 포함하세요. 빌드 시 --mapper로 보드를 선택합니다.
수직 미러링은 네 영역을 A B A B로, 수평 미러링은 A A B B로 매핑합니다. 아래쪽 단일 화면은 A A A A, 위쪽 단일 화면은 B B B B입니다. 메모리 매핑을 바꾸는 것이며 배경 그래픽을 뒤집거나 복사하지 않습니다. 고정 미러링 NROM을 비롯한 미지원 보드는 빌드 시 거부합니다.
MMC1은 제어값 $0C|mode를 써서 위쪽 16KiB PRG 창 고정과 8KiB CHR 모드를 선택합니다. SUROM은 제어값의 소프트웨어 사본도 갱신합니다. AxROM은 런타임 PRG 뱅크값을 유지하면서 화면 비트를 바꿉니다. 매퍼 레지스터에도 직접 쓴다면 런타임 뱅크 상태와 일치시켜야 합니다.
MMC5에서는 API가 $5105에 $50/$44/$00/$55를 씁니다. 이 배치들은 본체의 CIRAM 페이지 두 개만 사용합니다. ExRAM과 채우기 모드는 이 API 범위 밖입니다. KUROSAKI는 CIRAM만 쓰는 16가지 배치를 모두 지원하지만 MMC5 ExRAM과 채우기 렌더링은 지원하지 않습니다.
각 보드를 확인하려면 예제의 --mapper=mmc3를 mmc1, mmc5, axrom, fme7로 바꾸세요. 예제는 MMC5/FME7의 폰트 CHR 뱅크를 각 보드 레지스터를 통해 초기화합니다.
사용 예
k=1;
for (i=0;i<4;i++) {
__mirroring_set(i);
for (j=0;j<4;j++) { mirror_result[k]=mirror_read(0x2000+(u16)j*1024); k++; }
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
예제는 아래쪽 물리 RAM 페이지에 17, 위쪽 페이지에 34를 쓰고 네 논리 영역을 읽습니다. A는 아래쪽, B는 위쪽 페이지입니다. 같은 글자로 표시된 영역은 RAM을 공유하므로 한쪽을 통해 쓴 값이 다른 쪽에서도 보입니다. 열은 $2000, $2400, $2800, $2C00이고 행은 모드 0~3입니다. 읽기를 마친 뒤 글자를 표시합니다.





빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_mirroring.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_mirroring.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:322
Emit the selected mapper mirroring operation; available modes and writable control depend on the board.
__mul16x8
부호 없는 곱셈을 수행합니다.
사용 형식
u16 __mul16x8(u16 a, u8 b);매개변수
a부호 없는 워드 0~65535입니다.
b부호 없는 계수 0~255입니다.
반환값
a*b의 하위 16비트를 반환합니다.
사용 조건과 제한
FC에서 q8_8/q1_7 이름의 함수는 고정소수점용 오른쪽 시프트 없이 원래 정수 곱을 사용합니다. Q1.7 함수는 대응하는 부호 있는 연산의 별칭입니다. 단위는 호출자가 처리하며, 부호 있는 결과는 u16 비트 패턴으로 반환됩니다.
사용 예
// Compare literal coefficients with values read from RAM.
result[2]=__mul16x8(513,127);
v0=513;v1=127;
result[3]=__mul16x8(v0,v1);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
리터럴 입력과 RAM에 저장된 값을 비교합니다. 대표 결과는 부호 없는 16비트 값으로 표시하며 음수는 2의 보수 비트 패턴을 사용합니다. GB는 고정소수점 곱의 크기를 조정하지만 FC는 정수 곱을 반환하므로 같은 입력이라도 결과가 같지 않을 수 있습니다.
이 발췌 코드는 다음 16비트 값을 저장합니다: result[2]=65151, result[3]=65151. 리터럴 입력으로 계산한 각 결과는 대응하는 RAM 입력 결과와 같습니다. 화면에는 대표값을 표시하고, result 배열에는 모든 결과를 보관합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr구현과 소스
kitaqfc/lib/intrinsics.h:51
Return the low word of the unsigned product; overflow wraps without saturation.
kitaqfc/kitaqfc/CodeGenerator.cs:4647
case "__mul16x8": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,1}, funcName);
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__mul8x8_hi
부호 없는 두 바이트를 곱하고 상위 바이트를 반환합니다.
사용 형식
u8 __mul8x8_hi(u8 a, u8 b);매개변수
a부호 없는 바이트 0~255입니다.
b부호 없는 바이트 0~255입니다.
반환값
floor(a*b/256)이며, 범위는 0~254입니다.
사용 예
// Compare literal coefficients with values read from RAM.
result[0]=__mul8x8_hi(255,255);
v0=255;v1=255;
result[1]=__mul8x8_hi(v0,v1);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
리터럴 입력과 RAM에 저장된 값을 비교합니다. 대표 결과는 부호 없는 16비트 값으로 표시하며 음수는 2의 보수 비트 패턴을 사용합니다. GB는 고정소수점 곱의 크기를 조정하지만 FC는 정수 곱을 반환하므로 같은 입력이라도 결과가 같지 않을 수 있습니다.
이 발췌 코드는 다음 16비트 값을 저장합니다: result[0]=254, result[1]=254. 리터럴 입력으로 계산한 각 결과는 대응하는 RAM 입력 결과와 같습니다. 화면에는 대표값을 표시하고, result 배열에는 모든 결과를 보관합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr구현과 소스
kitaqfc/lib/intrinsics.h:49
Return the high byte of the unsigned 8-by-8 product, equivalent to floor(a*b/256).
kitaqfc/kitaqfc/CodeGenerator.cs:4649
case "__mul8x8_hi": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1}, funcName);
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__nametable_put
PPU 주소 0x2000 + y*32 + x에 네임테이블 0의 타일 바이트 하나를 직접 씁니다. 주소 설정 전에 래치를 초기화합니다. 타일 그래픽이나 팔레트 속성은 쓰지 않으며 좌표를 잘라내지 않습니다.
사용 형식
void __nametable_put(u8 x, u8 y, u8 tile);매개변수
x / y픽셀이 아닌 타일 좌표입니다. 화면의 32×30타일 영역에서는
x를0..31,y를0..29로 유지하세요. 한 타일은 8×8픽셀입니다. 바이트 인수를 제한하지 않으므로 범위 밖 좌표는 의도한 타일 영역 밖에 쓸 수 있습니다.tilePPUCTRL이 선택한 배경 패턴 테이블의 타일 번호
0..255입니다. 기존 CHR 그래픽을 선택하는 값이며 픽셀 색상이나 원본 포인터가 아닙니다.
반환값
없음(void).
사용 조건과 제한
직접 PPU 접근은 렌더링을 끄거나 전체 작업이 안전한 VBlank 구간 안에 끝나는 조건에서 수행하세요. 이 함수들은 대기, 큐 등록, NMI 차단, 스크롤 복원을 하지 않습니다. 호출 중 인터럽트가 공유 래치나 주소를 바꾸지 않도록 하세요. PPUCTRL 비트 2에 따라 대상 주소는 바이트마다 1 또는 32씩 증가합니다. 미러링과 주소 순환은 PPU 동작을 따르며 CHR-ROM에는 쓸 수 없습니다. 접근 후 렌더링 전에 원하는 스크롤 상태를 설정하세요.
사용 예
__nametable_put(2,12,1); // Blue solid marker at (16,96).호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 프로그램에서는 레지스터 설정, 타일·속성 직접 쓰기, 스크롤, 배경·스프라이트 팔레트의 개별 전송을 배웁니다. (0,16)의 파란 띠는 256×16픽셀입니다. 첫 행은 꽉 찬 8×8타일, 왼쪽 4×8막대, 8행 삼각형을 반복하고 둘째 행은 왼쪽 막대만 나열합니다. (16,48)에는 녹색으로 채운 64×32 사각형, (144,48)에는 삼각형 타일을 가로 8개·세로 4개 배치한 빨간 64×32 영역이 표시됩니다.
y=96에는 x=16, 24, 32에 각각 파란 정사각형·왼쪽 막대·삼각형, x=80에 빨간 왼쪽 막대, x=144에 녹색 삼각형이 표시됩니다. 이들은 배경 타일입니다. 별도 팔레트의 스프라이트는 y=144에 있으며 x=16은 빨간 8×8 정사각형, x=80은 파란 왼쪽 4×8막대, x=144는 녹색 8행 삼각형입니다. 최종 스크롤은 (0,0)입니다.
이미지는 스프라이트 색상을 포함해 16,384픽셀의 모양과 색상을 대조합니다. 별도 31개 상태 검사는 레지스터 교체, 상태·래치 부수 효과, 저장된 스크롤, 0/255바이트 전송, 증가량 1/32, 맵 경계, 크기 0 사각형, 테이블 선택, 전체 속성 바이트와 두 팔레트 대상을 다룹니다. Y=255는 레지스터 쓰기만 확인하며 올바른 스크롤 이미지를 검증한 것은 아닙니다. KUROSAKI의 NROM에서 검증했으며 실기는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:124
Write one tile directly at 2000+y*32+x; use x<32, y<30 for visible nametable cells.
kitaqfc/kitaqfc/CodeGenerator.cs:4820
case "__nametable_put": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1}, funcName);
void EmitHelperNametablePut()
{
EmitHelperStart("__nametable_put");
// args: x, y, tile. PPU address = $2000 + y * 32 + x.
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
for (int i = 0; i < 5; i++) { EmitAsm("ASL", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("ROL", Mem(_runtimeIntrinsicTmp1Address)); }
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Imm(0x20));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", Mem(0x2002));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("STA", Mem(0x2006));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(0x2006));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", Mem(0x2007));
EmitAsm("RTS");
}__nametable_put_nt
논리 네임테이블 nt & 3을 선택하고 0x2000 + (nt & 3)*0x400 + y*32 + x에 타일 하나를 씁니다. 주소 쌍을 쓰기 전에 래치를 초기화합니다. 카트리지 미러링에 따라 다른 논리 테이블 번호가 같은 물리 메모리를 가리킬 수 있습니다.
사용 형식
void __nametable_put_nt(u8 nt, u8 x, u8 y, u8 tile);매개변수
nt논리 네임테이블 선택값입니다. 하위 두 비트만 사용하므로(
nt & 3) 4는 테이블 0, 7은 테이블 3을 선택합니다. 독립 저장 공간을 확보하거나 매퍼 미러링을 바꾸지 않습니다.x / y픽셀이 아닌 타일 좌표입니다. 화면의 32×30타일 영역에서는
x를0..31,y를0..29로 유지하세요. 한 타일은 8×8픽셀입니다. 바이트 인수를 제한하지 않으므로 범위 밖 좌표는 의도한 타일 영역 밖에 쓸 수 있습니다.tilePPUCTRL이 선택한 배경 패턴 테이블의 타일 번호
0..255입니다. 기존 CHR 그래픽을 선택하는 값이며 픽셀 색상이나 원본 포인터가 아닙니다.
반환값
없음(void).
사용 조건과 제한
직접 PPU 접근은 렌더링을 끄거나 전체 작업이 안전한 VBlank 구간 안에 끝나는 조건에서 수행하세요. 이 함수들은 대기, 큐 등록, NMI 차단, 스크롤 복원을 하지 않습니다. 호출 중 인터럽트가 공유 래치나 주소를 바꾸지 않도록 하세요. PPUCTRL 비트 2에 따라 대상 주소는 바이트마다 1 또는 32씩 증가합니다. 미러링과 주소 순환은 PPU 동작을 따르며 CHR-ROM에는 쓸 수 없습니다. 접근 후 렌더링 전에 원하는 스크롤 상태를 설정하세요.
사용 예
__nametable_put_nt(4,10,12,2); // Red left-half marker at (80,96), table 0.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 프로그램에서는 레지스터 설정, 타일·속성 직접 쓰기, 스크롤, 배경·스프라이트 팔레트의 개별 전송을 배웁니다. (0,16)의 파란 띠는 256×16픽셀입니다. 첫 행은 꽉 찬 8×8타일, 왼쪽 4×8막대, 8행 삼각형을 반복하고 둘째 행은 왼쪽 막대만 나열합니다. (16,48)에는 녹색으로 채운 64×32 사각형, (144,48)에는 삼각형 타일을 가로 8개·세로 4개 배치한 빨간 64×32 영역이 표시됩니다.
y=96에는 x=16, 24, 32에 각각 파란 정사각형·왼쪽 막대·삼각형, x=80에 빨간 왼쪽 막대, x=144에 녹색 삼각형이 표시됩니다. 이들은 배경 타일입니다. 별도 팔레트의 스프라이트는 y=144에 있으며 x=16은 빨간 8×8 정사각형, x=80은 파란 왼쪽 4×8막대, x=144는 녹색 8행 삼각형입니다. 최종 스크롤은 (0,0)입니다.
이미지는 스프라이트 색상을 포함해 16,384픽셀의 모양과 색상을 대조합니다. 별도 31개 상태 검사는 레지스터 교체, 상태·래치 부수 효과, 저장된 스크롤, 0/255바이트 전송, 증가량 1/32, 맵 경계, 크기 0 사각형, 테이블 선택, 전체 속성 바이트와 두 팔레트 대상을 다룹니다. Y=255는 레지스터 쓰기만 확인하며 올바른 스크롤 이미지를 검증한 것은 아닙니다. KUROSAKI의 NROM에서 검증했으며 실기는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:126
Write one tile in nametable nt&3. Mirroring can alias logical nametables; coordinates are unchecked.
kitaqfc/kitaqfc/CodeGenerator.cs:4821
case "__nametable_put_nt": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1,1}, funcName);
void EmitHelperNametablePutNt()
{
EmitHelperStart("__nametable_put_nt");
// args: nt,x,y,tile. PPU address = $2000 + nt*$400 + y*32 + x.
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("AND", Imm(3));
EmitAsm("ASL");
EmitAsm("ASL");
EmitAsm("CLC");
EmitAsm("ADC", Imm(0x20));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
for (int i = 0; i < 5; i++) { EmitAsm("ASL", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("ROL", Mem(_runtimeIntrinsicTmp1Address)); }
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", Mem(0x2002));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("STA", Mem(0x2006));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(0x2006));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("STA", Mem(0x2007));
EmitAsm("RTS");
}__nametable_rect
네임테이블 0의 (x,y)에서 시작하는 너비 w, 높이 h의 타일 영역을 채웁니다. 행마다 래치와 주소를 설정하고 tile을 w개 쓴 뒤 다음 행 시작을 32바이트 앞으로 옮깁니다. 팔레트 속성은 바꾸지 않습니다.
사용 형식
void __nametable_rect(u8 x, u8 y, u8 w, u8 h, u8 tile);매개변수
x / y픽셀이 아닌 타일 좌표입니다. 화면의 32×30타일 영역에서는
x를0..31,y를0..29로 유지하세요. 한 타일은 8×8픽셀입니다. 바이트 인수를 제한하지 않으므로 범위 밖 좌표는 의도한 타일 영역 밖에 쓸 수 있습니다.w / h타일 단위 너비와 높이이며 각각 부호 없는 바이트입니다.
x+w<=32,y+h<=30을 지키세요. 높이 0은 PPU에 접근하지 않습니다. 너비 0은 타일을 쓰지 않지만h개 행의 래치 초기화와 주소 설정은 수행합니다. 가로 행에는 PPU 증가량 1을 사용하세요.tilePPUCTRL이 선택한 배경 패턴 테이블의 타일 번호
0..255입니다. 기존 CHR 그래픽을 선택하는 값이며 픽셀 색상이나 원본 포인터가 아닙니다.
반환값
없음(void).
사용 조건과 제한
직접 PPU 접근은 렌더링을 끄거나 전체 작업이 안전한 VBlank 구간 안에 끝나는 조건에서 수행하세요. 이 함수들은 대기, 큐 등록, NMI 차단, 스크롤 복원을 하지 않습니다. 호출 중 인터럽트가 공유 래치나 주소를 바꾸지 않도록 하세요. PPUCTRL 비트 2에 따라 대상 주소는 바이트마다 1 또는 32씩 증가합니다. 미러링과 주소 순환은 PPU 동작을 따르며 CHR-ROM에는 쓸 수 없습니다. 접근 후 렌더링 전에 원하는 스크롤 상태를 설정하세요.
사용 예
__nametable_rect(2,6,8,4,1); // Solid green rectangle at (16,48), 64x32 pixels.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 프로그램에서는 레지스터 설정, 타일·속성 직접 쓰기, 스크롤, 배경·스프라이트 팔레트의 개별 전송을 배웁니다. (0,16)의 파란 띠는 256×16픽셀입니다. 첫 행은 꽉 찬 8×8타일, 왼쪽 4×8막대, 8행 삼각형을 반복하고 둘째 행은 왼쪽 막대만 나열합니다. (16,48)에는 녹색으로 채운 64×32 사각형, (144,48)에는 삼각형 타일을 가로 8개·세로 4개 배치한 빨간 64×32 영역이 표시됩니다.
y=96에는 x=16, 24, 32에 각각 파란 정사각형·왼쪽 막대·삼각형, x=80에 빨간 왼쪽 막대, x=144에 녹색 삼각형이 표시됩니다. 이들은 배경 타일입니다. 별도 팔레트의 스프라이트는 y=144에 있으며 x=16은 빨간 8×8 정사각형, x=80은 파란 왼쪽 4×8막대, x=144는 녹색 8행 삼각형입니다. 최종 스크롤은 (0,0)입니다.
이미지는 스프라이트 색상을 포함해 16,384픽셀의 모양과 색상을 대조합니다. 별도 31개 상태 검사는 레지스터 교체, 상태·래치 부수 효과, 저장된 스크롤, 0/255바이트 전송, 증가량 1/32, 맵 경계, 크기 0 사각형, 테이블 선택, 전체 속성 바이트와 두 팔레트 대상을 다룹니다. Y=255는 레지스터 쓰기만 확인하며 올바른 스크롤 이미지를 검증한 것은 아닙니다. KUROSAKI의 NROM에서 검증했으며 실기는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:129
Fill rows of nametable zero directly, advancing each row start by 32 bytes. Keep the rectangle in bounds and use PPU increment one; this does not enqueue or clip.
kitaqfc/kitaqfc/CodeGenerator.cs:4822
case "__nametable_rect": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1,1,1}, funcName);
void EmitHelperNametableRect()
{
EmitHelperStart("__nametable_rect");
// args: x,y,w,h,tile; writes tile-filled rectangle to nametable 0.
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
for (int i = 0; i < 5; i++) { EmitAsm("ASL", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("ROL", Mem(_runtimeIntrinsicTmp1Address)); }
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("CLC"); EmitAsm("ADC", Mem(CallArgBase)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("ADC", Imm(0x20)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", Mem(CallArgBase + 3)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string rowLoop = NewGeneratedLabel("nt_rect_row");
string done = NewGeneratedLabel("nt_rect_done");
_assembly.Add(Expr.Make(Tag.Label, rowLoop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("BEQ", Rel(done));
EmitAsm("LDA", Mem(0x2002)); EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("STA", Mem(0x2006)); EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("STA", Mem(0x2006));
EmitAsm("LDY", Imm(0));
string colLoop = NewGeneratedLabel("nt_rect_col");
string rowDone = NewGeneratedLabel("nt_rect_row_done");
_assembly.Add(Expr.Make(Tag.Label, colLoop));
EmitAsm("CPY", Mem(CallArgBase + 2)); EmitAsm("BEQ", Rel(rowDone));
EmitAsm("LDA", Mem(CallArgBase + 4)); EmitAsm("STA", Mem(0x2007)); EmitAsm("INY"); EmitAsm("BNE", Rel(colLoop));
_assembly.Add(Expr.Make(Tag.Label, rowDone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("CLC"); EmitAsm("ADC", Imm(32)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("BCC", Rel(rowLoop + "_nohi"));
EmitAsm("INC", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, rowLoop + "_nohi"));
EmitAsm("DEC", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("JMP", Abs(rowLoop));
_assembly.Add(Expr.Make(Tag.Label, done));
EmitAsm("RTS");
}__nametable_rect_nt
논리 네임테이블 nt & 3에서 행당 w타일을 h행 채웁니다. 시작 주소는 0x2000 + (nt & 3)*0x400 + y*32 + x이며 행 시작은 32씩 증가합니다. 타일 바이트만 쓰며 속성 갱신, 경계 잘라내기, 미러링 설정은 하지 않습니다.
사용 형식
void __nametable_rect_nt(u8 nt, u8 x, u8 y, u8 w, u8 h, u8 tile);매개변수
nt논리 네임테이블 선택값입니다. 하위 두 비트만 사용하므로(
nt & 3) 4는 테이블 0, 7은 테이블 3을 선택합니다. 독립 저장 공간을 확보하거나 매퍼 미러링을 바꾸지 않습니다.x / y픽셀이 아닌 타일 좌표입니다. 화면의 32×30타일 영역에서는
x를0..31,y를0..29로 유지하세요. 한 타일은 8×8픽셀입니다. 바이트 인수를 제한하지 않으므로 범위 밖 좌표는 의도한 타일 영역 밖에 쓸 수 있습니다.w / h타일 단위 너비와 높이이며 각각 부호 없는 바이트입니다.
x+w<=32,y+h<=30을 지키세요. 높이 0은 PPU에 접근하지 않습니다. 너비 0은 타일을 쓰지 않지만h개 행의 래치 초기화와 주소 설정은 수행합니다. 가로 행에는 PPU 증가량 1을 사용하세요.tilePPUCTRL이 선택한 배경 패턴 테이블의 타일 번호
0..255입니다. 기존 CHR 그래픽을 선택하는 값이며 픽셀 색상이나 원본 포인터가 아닙니다.
반환값
없음(void).
사용 조건과 제한
직접 PPU 접근은 렌더링을 끄거나 전체 작업이 안전한 VBlank 구간 안에 끝나는 조건에서 수행하세요. 이 함수들은 대기, 큐 등록, NMI 차단, 스크롤 복원을 하지 않습니다. 호출 중 인터럽트가 공유 래치나 주소를 바꾸지 않도록 하세요. PPUCTRL 비트 2에 따라 대상 주소는 바이트마다 1 또는 32씩 증가합니다. 미러링과 주소 순환은 PPU 동작을 따르며 CHR-ROM에는 쓸 수 없습니다. 접근 후 렌더링 전에 원하는 스크롤 상태를 설정하세요.
사용 예
__nametable_rect_nt(4,18,6,8,4,3); // nt&3=0; red triangle tiles at (144,48).호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 프로그램에서는 레지스터 설정, 타일·속성 직접 쓰기, 스크롤, 배경·스프라이트 팔레트의 개별 전송을 배웁니다. (0,16)의 파란 띠는 256×16픽셀입니다. 첫 행은 꽉 찬 8×8타일, 왼쪽 4×8막대, 8행 삼각형을 반복하고 둘째 행은 왼쪽 막대만 나열합니다. (16,48)에는 녹색으로 채운 64×32 사각형, (144,48)에는 삼각형 타일을 가로 8개·세로 4개 배치한 빨간 64×32 영역이 표시됩니다.
y=96에는 x=16, 24, 32에 각각 파란 정사각형·왼쪽 막대·삼각형, x=80에 빨간 왼쪽 막대, x=144에 녹색 삼각형이 표시됩니다. 이들은 배경 타일입니다. 별도 팔레트의 스프라이트는 y=144에 있으며 x=16은 빨간 8×8 정사각형, x=80은 파란 왼쪽 4×8막대, x=144는 녹색 8행 삼각형입니다. 최종 스크롤은 (0,0)입니다.
이미지는 스프라이트 색상을 포함해 16,384픽셀의 모양과 색상을 대조합니다. 별도 31개 상태 검사는 레지스터 교체, 상태·래치 부수 효과, 저장된 스크롤, 0/255바이트 전송, 증가량 1/32, 맵 경계, 크기 0 사각형, 테이블 선택, 전체 속성 바이트와 두 팔레트 대상을 다룹니다. Y=255는 레지스터 쓰기만 확인하며 올바른 스크롤 이미지를 검증한 것은 아닙니다. KUROSAKI의 NROM에서 검증했으며 실기는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:131
Fill a rectangle in nametable nt&3 with the same unchecked, direct-write row loop.
kitaqfc/kitaqfc/CodeGenerator.cs:4823
case "__nametable_rect_nt": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1,1,1,1}, funcName);
void EmitHelperNametableRectNt()
{
EmitHelperStart("__nametable_rect_nt");
// args: nt,x,y,w,h,tile.
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("AND", Imm(3));
EmitAsm("ASL");
EmitAsm("ASL");
EmitAsm("CLC");
EmitAsm("ADC", Imm(0x20));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
for (int i = 0; i < 5; i++) { EmitAsm("ASL", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("ROL", Mem(_runtimeIntrinsicTmp1Address)); }
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("CLC"); EmitAsm("ADC", Mem(CallArgBase + 1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("ADC", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", Mem(CallArgBase + 4)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string rowLoop = NewGeneratedLabel("nt_rect_nt_row");
string done = NewGeneratedLabel("nt_rect_nt_done");
_assembly.Add(Expr.Make(Tag.Label, rowLoop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("BEQ", Rel(done));
EmitAsm("LDA", Mem(0x2002)); EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("STA", Mem(0x2006)); EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("STA", Mem(0x2006));
EmitAsm("LDY", Imm(0));
string colLoop = NewGeneratedLabel("nt_rect_nt_col");
string rowDone = NewGeneratedLabel("nt_rect_nt_row_done");
_assembly.Add(Expr.Make(Tag.Label, colLoop));
EmitAsm("CPY", Mem(CallArgBase + 3)); EmitAsm("BEQ", Rel(rowDone));
EmitAsm("LDA", Mem(CallArgBase + 5)); EmitAsm("STA", Mem(0x2007)); EmitAsm("INY"); EmitAsm("BNE", Rel(colLoop));
_assembly.Add(Expr.Make(Tag.Label, rowDone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("CLC"); EmitAsm("ADC", Imm(32)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("BCC", Rel(rowLoop + "_nohi"));
EmitAsm("INC", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, rowLoop + "_nohi"));
EmitAsm("DEC", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("JMP", Abs(rowLoop));
_assembly.Add(Expr.Make(Tag.Label, done));
EmitAsm("RTS");
}__nmi_disable
컴파일러가 보관하는 PPUCTRL 값의 비트 7을 지우고 $2000에 써서 PPU의 NMI 생성을 비활성화합니다. 나머지 비트는 유지합니다. CPU의 IRQ 마스크나 렌더링 설정은 바꾸지 않습니다.
사용 형식
void __nmi_disable(void);반환값
없음(void).
사용 조건과 제한
이 함수들은 PPUCTRL의 저장값을 사용하며 하드웨어에서 다시 읽지 않습니다. $2000에 직접 써도 저장값은 갱신되지 않습니다. VBlank 플래그가 설정된 상태에서 활성화하면 즉시 NMI가 발생할 수 있습니다. PPU의 생성을 꺼도 CPU가 이미 포착한 요청은 취소되지 않습니다.
사용 예
__nmi_disable();
before_count=__nmi_ready();
for(i=0;i<2000;i++)frozen=__nmi_ready();
frozen=(u8)(frozen-before_count); // No default-handler advances while off.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
CPU의 IRQ 마스크와 PPU의 NMI 제어가 어떻게 다른지 확인하는 프로그램입니다. 검은 배경의 파란 글자로 IRQ DISABLED I는 001, IRQ ENABLED I는 000, SAVED SECTION I는 001, RESTORED I는 000이어야 합니다. 상태 바이트 전체가 아닌 I 비트만 표시합니다. FAILED CHECKS는 000이어야 합니다.
256번 기다리는 실험이 끝나도록 에뮬레이터를 360프레임 이상 실행하세요. IRQ가 차단되어 있어도 NMI DELTA 3 WAITS는 003이어야 합니다. 1바이트가 순환하므로 NMI DELTA 256 WAITS는 000이며 NMI OFF DELTA도 000입니다. runtime.c를 링크하지 않고 기본 핸들러를 사용합니다. 별도 상태 검증에서는 상태 비트 복원, NMI 비활성화 또는 빈 사용자 핸들러에서의 제한된 실행 관찰 등 21개 사례를 확인합니다. KUROSAKI/NROM에서 검증했으며 실제 하드웨어는 미검증입니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\interrupt_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\font.chr -o .\out\interrupt_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:174
Clear bit 7 in the PPUCTRL shadow and write it to hardware; CPU IRQ masking is separate.
kitaqfc/kitaqfc/CodeGenerator.cs:4730
case "__nmi_disable":
if (!Need(0)) return true;
EmitAsm("LDA", Mem(_runtimePpuCtrlShadowAddress)); EmitAsm("AND", Imm(0x7F)); EmitAsm("STA", Mem(_runtimePpuCtrlShadowAddress)); EmitAsm("STA", Mem(0x2000)); return true;__nmi_enable
컴파일러가 보관하는 PPUCTRL 값의 비트 7을 설정하고 $2000에 써서 PPU의 NMI를 활성화합니다. 보관된 값의 나머지 비트와 CPU의 IRQ 마스크는 유지합니다. 배경이나 스프라이트 렌더링을 켜는 함수는 아닙니다.
사용 형식
void __nmi_enable(void);반환값
없음(void).
사용 조건과 제한
이 함수들은 PPUCTRL의 저장값을 사용하며 하드웨어에서 다시 읽지 않습니다. $2000에 직접 써도 저장값은 갱신되지 않습니다. VBlank 플래그가 설정된 상태에서 활성화하면 즉시 NMI가 발생할 수 있습니다. PPU의 생성을 꺼도 CPU가 이미 포착한 요청은 취소되지 않습니다.
사용 예
__nmi_enable(); // Preserve the PPUCTRL shadow's other bits; CPU I stays set.
__nmi_wait(); // The default NMI handler still runs with IRQ masked.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
CPU의 IRQ 마스크와 PPU의 NMI 제어가 어떻게 다른지 확인하는 프로그램입니다. 검은 배경의 파란 글자로 IRQ DISABLED I는 001, IRQ ENABLED I는 000, SAVED SECTION I는 001, RESTORED I는 000이어야 합니다. 상태 바이트 전체가 아닌 I 비트만 표시합니다. FAILED CHECKS는 000이어야 합니다.
256번 기다리는 실험이 끝나도록 에뮬레이터를 360프레임 이상 실행하세요. IRQ가 차단되어 있어도 NMI DELTA 3 WAITS는 003이어야 합니다. 1바이트가 순환하므로 NMI DELTA 256 WAITS는 000이며 NMI OFF DELTA도 000입니다. runtime.c를 링크하지 않고 기본 핸들러를 사용합니다. 별도 상태 검증에서는 상태 비트 복원, NMI 비활성화 또는 빈 사용자 핸들러에서의 제한된 실행 관찰 등 21개 사례를 확인합니다. KUROSAKI/NROM에서 검증했으며 실제 하드웨어는 미검증입니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\interrupt_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\font.chr -o .\out\interrupt_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:172
Set bit 7 in the PPUCTRL shadow and write it to hardware, preserving the other shadow bits.
kitaqfc/kitaqfc/CodeGenerator.cs:4727
case "__nmi_enable":
if (!Need(0)) return true;
EmitAsm("LDA", Mem(_runtimePpuCtrlShadowAddress)); EmitAsm("ORA", Imm(0x80)); EmitAsm("STA", Mem(_runtimePpuCtrlShadowAddress)); EmitAsm("STA", Mem(0x2000)); return true;__nmi_ready
기다리거나 값을 지우지 않고 컴파일러의 현재 NMI 카운터를 읽습니다. 이름에 ready가 있지만 반환값은 참·거짓이 아닌 1바이트 카운터입니다. 저장해 둔 값과 비교하여 기본 NMI 핸들러의 진행 여부를 확인할 수 있습니다.
사용 형식
u8 __nmi_ready(void);반환값
0~255의 부호 없는 1바이트 값으로, 기본 NMI 핸들러에서 증가하고 256에서 순환합니다. 0도 유효합니다. 256번 증가하면 저장값과 현재값이 다시 같아지므로 드물게 읽는 방식으로 긴 경과 횟수를 제한 없이 셀 수는 없습니다.
사용 조건과 제한
이 카운터는 컴파일러의 기본 NMI 핸들러용입니다. __nes_nmi를 정의하면 기본 핸들러가 교체됩니다. runtime.c는 별도의 nes_nmi_counter와 nes_wait_nmi를 사용합니다. 빈 사용자 핸들러에서는 컴파일러 카운터가 증가하지 않아 __nmi_wait가 반환하지 않습니다. NMI를 활성화하고 핸들러 밖에서 기다리세요.
사용 예
before_count=__nmi_ready(); // A wrapping byte, not a Boolean ready flag.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
CPU의 IRQ 마스크와 PPU의 NMI 제어가 어떻게 다른지 확인하는 프로그램입니다. 검은 배경의 파란 글자로 IRQ DISABLED I는 001, IRQ ENABLED I는 000, SAVED SECTION I는 001, RESTORED I는 000이어야 합니다. 상태 바이트 전체가 아닌 I 비트만 표시합니다. FAILED CHECKS는 000이어야 합니다.
256번 기다리는 실험이 끝나도록 에뮬레이터를 360프레임 이상 실행하세요. IRQ가 차단되어 있어도 NMI DELTA 3 WAITS는 003이어야 합니다. 1바이트가 순환하므로 NMI DELTA 256 WAITS는 000이며 NMI OFF DELTA도 000입니다. runtime.c를 링크하지 않고 기본 핸들러를 사용합니다. 별도 상태 검증에서는 상태 비트 복원, NMI 비활성화 또는 빈 사용자 핸들러에서의 제한된 실행 관찰 등 21개 사례를 확인합니다. KUROSAKI/NROM에서 검증했으며 실제 하드웨어는 미검증입니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\interrupt_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\font.chr -o .\out\interrupt_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:170
Return the current wrapping NMI counter byte, not a Boolean ready flag.
kitaqfc/kitaqfc/CodeGenerator.cs:4740
case "__nmi_ready": if (!Need(0)) return true; EmitAsm("LDA", Mem(_runtimeNmiCounterAddress)); return true;
// Reset OAMADDR and transfer page 02; the page-selecting variant evaluates its own byte argument.
// The default NMI executes the queued VRAM work and increments the runtime frame counter before RTI.
if (!_functions.ContainsKey("__nes_nmi"))
{
EmitPlacement(0, true);
_assembly.Add(Expr.Make(Tag.Function, "__kq_nmi_default"));
// NMI may interrupt argument evaluation, a pointer dispatch or an ordinary expression.
// Preserve registers and every shared byte touched by the queue executor; RTI restores P.
EmitAsm("PHA"); EmitAsm("TXA"); EmitAsm("PHA"); EmitAsm("TYA"); EmitAsm("PHA");
int[] queueScratch = { CallArgBase, CallArgBase + 1, CallArgBase + 2,
_runtimeIntrinsicTmp0Address, _runtimeIntrinsicTmp1Address, _runtimeIntrinsicTmp2Address };
foreach (int address in queueScratch) { EmitAsm("LDA", Mem(address)); EmitAsm("PHA"); }
// The optional library hook preserves its own registers and uses no
// compiler scratch. It consumes only RAM records and fixed ROM tables.
if (_functions.ContainsKey("__nes_audio_vblank_tick"))
EmitAsm("JSR", Abs("__nes_audio_vblank_tick"));
EmitAsm("JSR", Abs("__vramq_exec"));
EmitAsm("INC", Mem(_runtimeNmiCounterAddress));
foreach (int address in queueScratch.Reverse()) { EmitAsm("PLA"); EmitAsm("STA", Mem(address)); }
EmitAsm("PLA"); EmitAsm("TAY"); EmitAsm("PLA"); EmitAsm("TAX"); EmitAsm("PLA");
EmitAsm("RTI");
}__nmi_wait
호출 시 컴파일러의 NMI 카운터를 읽고 값이 바뀔 때까지 반복하며 기다립니다. 기본 NMI 핸들러에서는 커밋된 VRAM 큐 처리를 포함한 다음 핸들러 실행 완료를 기다립니다. NMI 활성화, 큐 커밋 또는 시간 제한 처리는 하지 않습니다.
사용 형식
void __nmi_wait(void);반환값
없음(void).
사용 조건과 제한
이 카운터는 컴파일러의 기본 NMI 핸들러용입니다. __nes_nmi를 정의하면 기본 핸들러가 교체됩니다. runtime.c는 별도의 nes_nmi_counter와 nes_wait_nmi를 사용합니다. 빈 사용자 핸들러에서는 컴파일러 카운터가 증가하지 않아 __nmi_wait가 반환하지 않습니다. NMI를 활성화하고 핸들러 밖에서 기다리세요.
사용 예
__nmi_wait();__nmi_wait();__nmi_wait();
delta=(u8)(__nmi_ready()-before_count); // Three completed default NMIs.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
CPU의 IRQ 마스크와 PPU의 NMI 제어가 어떻게 다른지 확인하는 프로그램입니다. 검은 배경의 파란 글자로 IRQ DISABLED I는 001, IRQ ENABLED I는 000, SAVED SECTION I는 001, RESTORED I는 000이어야 합니다. 상태 바이트 전체가 아닌 I 비트만 표시합니다. FAILED CHECKS는 000이어야 합니다.
256번 기다리는 실험이 끝나도록 에뮬레이터를 360프레임 이상 실행하세요. IRQ가 차단되어 있어도 NMI DELTA 3 WAITS는 003이어야 합니다. 1바이트가 순환하므로 NMI DELTA 256 WAITS는 000이며 NMI OFF DELTA도 000입니다. runtime.c를 링크하지 않고 기본 핸들러를 사용합니다. 별도 상태 검증에서는 상태 비트 복원, NMI 비활성화 또는 빈 사용자 핸들러에서의 제한된 실행 관찰 등 21개 사례를 확인합니다. KUROSAKI/NROM에서 검증했으며 실제 하드웨어는 미검증입니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\interrupt_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\font.chr -o .\out\interrupt_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:168
NMI / IRQ Spin until the runtime NMI counter changes; the configured handler must advance it. No timeout is emitted.
kitaqfc/kitaqfc/CodeGenerator.cs:4738
case "__nmi_wait": if (!Need(0)) return true; EmitAsm("JSR", Abs("__nmi_wait")); return true;
// Return the current NMI counter byte; callers decide how to compare it with their previous value.
void EmitHelperNmiWait()
{
EmitHelperStart("__nmi_wait");
EmitAsm("LDA", Mem(_runtimeNmiCounterAddress));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
string loop = NewGeneratedLabel("nmi_wait_loop");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LDA", Mem(_runtimeNmiCounterAddress));
EmitAsm("CMP", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("BEQ", Rel(loop));
EmitAsm("RTS");
}
// The default NMI executes the queued VRAM work and increments the runtime frame counter before RTI.
if (!_functions.ContainsKey("__nes_nmi"))
{
EmitPlacement(0, true);
_assembly.Add(Expr.Make(Tag.Function, "__kq_nmi_default"));
// NMI may interrupt argument evaluation, a pointer dispatch or an ordinary expression.
// Preserve registers and every shared byte touched by the queue executor; RTI restores P.
EmitAsm("PHA"); EmitAsm("TXA"); EmitAsm("PHA"); EmitAsm("TYA"); EmitAsm("PHA");
int[] queueScratch = { CallArgBase, CallArgBase + 1, CallArgBase + 2,
_runtimeIntrinsicTmp0Address, _runtimeIntrinsicTmp1Address, _runtimeIntrinsicTmp2Address };
foreach (int address in queueScratch) { EmitAsm("LDA", Mem(address)); EmitAsm("PHA"); }
// The optional library hook preserves its own registers and uses no
// compiler scratch. It consumes only RAM records and fixed ROM tables.
if (_functions.ContainsKey("__nes_audio_vblank_tick"))
EmitAsm("JSR", Abs("__nes_audio_vblank_tick"));
EmitAsm("JSR", Abs("__vramq_exec"));
EmitAsm("INC", Mem(_runtimeNmiCounterAddress));
foreach (int address in queueScratch.Reverse()) { EmitAsm("PLA"); EmitAsm("STA", Mem(address)); }
EmitAsm("PLA"); EmitAsm("TAY"); EmitAsm("PLA"); EmitAsm("TAX"); EmitAsm("PLA");
EmitAsm("RTI");
}__oam_clear
페이지 02의 섀도 버퍼에 있는 스프라이트 64개 모두의 Y를 0xF0으로 설정해 숨깁니다. 타일 번호, 속성, X 좌표는 유지합니다.
사용 형식
void __oam_clear(void);반환값
없음(void).
사용 조건과 제한
이 작업은 CPU RAM의 0x0200–0x02FF를 수정하며 하드웨어 OAM에는 즉시 반영되지 않습니다. 페이지를 완성한 뒤 __oam_dma로 전송하세요. 스프라이트 라이브러리의 할당 플래그, 좌표 배열, 사용 개수는 갱신하지 않습니다. 준비 중인 페이지를 인터럽트 처리에서 수정하거나 전송하지 않도록 하세요.
사용 예
__sprite_set(63,144,111,128,0); // Seed the CLEAR row with a visible entry.
__oam_clear(); // Set every Y byte to 240; tile, attributes and X are retained.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
작업마다 이름을 붙인 행으로 구분한 예제입니다. SET과 MOVE는 (144,16), (144,32)의 8×8 오른쪽 화살표, TILE은 (144,48)의 초록 정사각형, ATTR은 (144,64)의 상하좌우 반전 화살표입니다. HIDE의 (144,80)과 CLEAR의 (144,112)는 비어 있어야 합니다. META는 (136,96), (144,96)의 두 화살표이며 두 번째는 좌우 반전입니다. NEXT 07은 5번과 6번에 기록한 뒤의 다음 위치입니다. 화살표 테두리는 빨강, 몸통은 초록, 끝부분의 일부는 파랑입니다. 이미지당 8,704픽셀과 전송된 OAM 바이트를 대조하며 실제 기기에서는 검증하지 않았습니다.
TRANSFER의 (144,128)에 화살표를 표시합니다. 이 프로그램은 각 스프라이트 작업으로 준비한 모든 필드가 들어 있는 페이지 02를 __oam_dma로 직접 전송합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\oam_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\sprite_example.chr -o .\out\oam_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:190
Hide all 64 page-02 shadow sprites by writing Y=F0; hardware OAM changes only after DMA.
EmitHelperStart("__oam_clear");
EmitAsm("LDX", Imm(0));
EmitAsm("LDA", Imm(0xF0));
string loop = NewGeneratedLabel("oam_clear_loop");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("STA", new AsmOperand(0x0200, AddressMode.AbsoluteX));
EmitAsm("INX"); EmitAsm("INX"); EmitAsm("INX"); EmitAsm("INX");
EmitAsm("BNE", Rel(loop));
EmitAsm("RTS");__oam_dma
0x0200–0x02FF의 섀도 페이지 256바이트 전체를 하드웨어 OAM의 첫 항목부터 전송합니다. 전송 중에는 CPU가 멈추고 완료 후 다시 실행됩니다.
사용 형식
void __oam_dma(void);반환값
없음(void).
사용 조건과 제한
VBlank 중이거나 렌더링을 끈 상태에서 호출하세요. 두 DMA 내장 함수 모두 VBlank를 기다리지 않으며 전송 전에 OAMADDR을 0으로 설정합니다. 실제 DMA는 보통 CPU를 513 또는 514사이클 멈추고 DMC 동작에 따라 더 길어질 수 있습니다. KUROSAKI는 이를 513사이클로 근사하므로 이미지 검증만으로 정확한 실제 타이밍을 확인할 수는 없습니다. 컴파일러는 호출자 이름으로 판단하므로 명시적으로 VBlank를 기다려도 KQ2421이 나올 수 있습니다.
사용 예
m_wait();__oam_dma(); // Transfer all 256 bytes from page 02 at VBlank.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
작업마다 이름을 붙인 행으로 구분한 예제입니다. SET과 MOVE는 (144,16), (144,32)의 8×8 오른쪽 화살표, TILE은 (144,48)의 초록 정사각형, ATTR은 (144,64)의 상하좌우 반전 화살표입니다. HIDE의 (144,80)과 CLEAR의 (144,112)는 비어 있어야 합니다. META는 (136,96), (144,96)의 두 화살표이며 두 번째는 좌우 반전입니다. NEXT 07은 5번과 6번에 기록한 뒤의 다음 위치입니다. 화살표 테두리는 빨강, 몸통은 초록, 끝부분의 일부는 파랑입니다. 이미지당 8,704픽셀과 전송된 OAM 바이트를 대조하며 실제 기기에서는 검증하지 않았습니다.
TRANSFER의 (144,128)에 화살표를 표시합니다. 이 프로그램은 각 스프라이트 작업으로 준비한 모든 필드가 들어 있는 페이지 02를 __oam_dma로 직접 전송합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\oam_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\sprite_example.chr -o .\out\oam_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:186
OAM / input Set OAMADDR to zero and start DMA from CPU page 02; arrange safe PPU timing in the caller.
case "__oam_dma": if (!Need(0)) return true; EmitAsm("LDA", Imm(0)); EmitAsm("STA", Mem(0x2003)); EmitAsm("LDA", Imm(0x02)); EmitAsm("STA", Mem(0x4014)); return true;__oam_dma_page
선택한 CPU 메모리 페이지의 256바이트를 하드웨어 OAM의 첫 항목부터 전송합니다. 별도로 준비한 OAM 페이지를 페이지 02에 복사하지 않고 사용할 수 있습니다. 원본 데이터는 변경하지 않습니다.
사용 형식
void __oam_dma_page(u8 page);매개변수
page원본 CPU 주소의 상위 바이트입니다.
0x0400–0x04FF를 전송하려면0x0400이 아니라4를 전달하세요. 256바이트 전체를 읽을 수 있는 페이지를 준비하고 필요한 뱅크를 선택해 두어야 합니다. 읽기에 부수 효과가 있는 I/O 레지스터 페이지는 피하세요. 원본 유효성은 검사하지 않습니다.
반환값
없음(void).
사용 조건과 제한
VBlank 중이거나 렌더링을 끈 상태에서 호출하세요. 두 DMA 내장 함수 모두 VBlank를 기다리지 않으며 전송 전에 OAMADDR을 0으로 설정합니다. 실제 DMA는 보통 CPU를 513 또는 514사이클 멈추고 DMC 동작에 따라 더 길어질 수 있습니다. KUROSAKI는 이를 513사이클로 근사하므로 이미지 검증만으로 정확한 실제 타이밍을 확인할 수는 없습니다. 컴파일러는 호출자 이름으로 판단하므로 명시적으로 VBlank를 기다려도 KQ2421이 나올 수 있습니다.
사용 예
m_wait();__oam_dma_page(4); // The argument is a page number, not 0x0400.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
작업마다 이름을 붙인 행으로 구분한 예제입니다. SET과 MOVE는 (144,16), (144,32)의 8×8 오른쪽 화살표, TILE은 (144,48)의 초록 정사각형, ATTR은 (144,64)의 상하좌우 반전 화살표입니다. HIDE의 (144,80)과 CLEAR의 (144,112)는 비어 있어야 합니다. META는 (136,96), (144,96)의 두 화살표이며 두 번째는 좌우 반전입니다. NEXT 07은 5번과 6번에 기록한 뒤의 다음 위치입니다. 화살표 테두리는 빨강, 몸통은 초록, 끝부분의 일부는 파랑입니다. 이미지당 8,704픽셀과 전송된 OAM 바이트를 대조하며 실제 기기에서는 검증하지 않았습니다.
이 예제는 준비된 페이지 02를 0x0400의 별도 배열에 복사하고 7번 항목의 타일만 129로 바꿉니다. __oam_dma_page(4)는 TRANSFER 옆 (144,128)에 초록 정사각형을 표시해야 하며 페이지 02에는 화살표 타일 128이 남아 있어야 합니다. 나머지 행은 기본 페이지 예제와 같습니다. 하드웨어 OAM 256바이트 전체를 페이지 04와 대조하고 페이지 02가 바뀌지 않았는지도 확인합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\oam_page.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\sprite_example.chr -o .\out\oam_page.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:188
Set OAMADDR to zero and DMA 256 bytes from page<<8. The source page must remain readable.
case "__oam_dma_page": if (!Need(1)) return true; EmitAsm("LDA", Imm(0)); EmitAsm("STA", Mem(0x2003)); Load1(args[0]); EmitAsm("STA", Mem(0x4014)); return true;__pad_buttons
전달된 NES 패드 값의 비트 0~3(pad & 0x0F), 즉 A·B·Select·Start만 남깁니다. 이미 읽은 값을 필터링할 뿐 컨트롤러 하드웨어에 접근하지 않습니다.
사용 형식
u8 __pad_buttons(u8 pad);매개변수
pad__pad_read1_safe()결과처럼 이미 샘플링한 8비트 NES 패드 값입니다. 포인터, 포트 번호 또는input_*의 공통 마스크가 아니라 값 자체를 전달하세요.
반환값
위 비트 순서의 0~15 마스크입니다. 여러 비트가 설정될 수 있으므로 전체 값이 1인지 비교하지 말고 result & bit로 특정 버튼을 확인하세요.
사용 조건과 제한
NES 원시 형식은 A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80입니다. 1인 비트가 눌림을 뜻합니다. nes_pad_*와 원시 __pad_* 결과에는 이 값을 사용하세요. input.h의 공통 BTN_* 마스크와 혼동하지 마세요.
사용 예
buttons=__pad_buttons(raw1); // Low nibble of 129: 1, the A button.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
두 컨트롤러 포트, 일반 읽기와 일치 확인 읽기, 마스크 필터, 단축 매크로를 비교하는 프로그램입니다. 컨트롤러 1에서 A와 오른쪽을, 컨트롤러 2에서 B와 왼쪽을 함께 누릅니다. RAW P1, SAFE P1, ALIAS P1은 129이고 P2의 세 값은 66입니다. BUTTONS P1은 1, DIRS P1은 128입니다. 방향 필터는 상위 비트의 위치를 유지합니다. FAILED CHECKS는 0이어야 합니다. 관측값은 화면에 그대로 남습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\input_raw.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\input_raw.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-disasm --no-cache구현과 소스
kitaqfc/lib/intrinsics.h:213
Return pad & 0F for A/B/Select/Start; this masks a supplied snapshot and performs no polling.
__pad_dirs
전달된 NES 패드 값의 비트 4~7(pad & 0xF0), 즉 위·아래·왼쪽·오른쪽만 남깁니다. 비트 위치를 유지하므로 오른쪽만 누르면 8이 아니라 128을 반환합니다. 컨트롤러는 읽지 않습니다.
사용 형식
u8 __pad_dirs(u8 pad);매개변수
pad__pad_read1_safe()결과처럼 이미 샘플링한 8비트 NES 패드 값입니다. 포인터, 포트 번호 또는input_*의 공통 마스크가 아니라 값 자체를 전달하세요.
반환값
전달된 값과 0xF0의 AND 결과로 0, 16, 32, …, 240 중 하나입니다. 방향 비트는 4~7번 위치를 유지합니다.
사용 조건과 제한
NES 원시 형식은 A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80입니다. 1인 비트가 눌림을 뜻합니다. nes_pad_*와 원시 __pad_* 결과에는 이 값을 사용하세요. input.h의 공통 BTN_* 마스크와 혼동하지 마세요.
사용 예
directions=__pad_dirs(raw1); // High nibble retained in place: 128, not 8.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
두 컨트롤러 포트, 일반 읽기와 일치 확인 읽기, 마스크 필터, 단축 매크로를 비교하는 프로그램입니다. 컨트롤러 1에서 A와 오른쪽을, 컨트롤러 2에서 B와 왼쪽을 함께 누릅니다. RAW P1, SAFE P1, ALIAS P1은 129이고 P2의 세 값은 66입니다. BUTTONS P1은 1, DIRS P1은 128입니다. 방향 필터는 상위 비트의 위치를 유지합니다. FAILED CHECKS는 0이어야 합니다. 관측값은 화면에 그대로 남습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\input_raw.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\input_raw.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-disasm --no-cache구현과 소스
kitaqfc/lib/intrinsics.h:215
Return pad & F0 in the original bit positions; this does not normalize directions to bits 0..3.
__pad_read1
$4016에 1→0을 써서 두 컨트롤러 상태를 래치한 뒤 $4016의 D0에서 컨트롤러 1의 8비트를 직렬로 읽습니다. 재시도나 에지 계산 없이 NES 형식의 눌림 상태 한 번을 반환합니다.
사용 형식
u8 __pad_read1(void);반환값
NES 원시 비트 순서의 8비트 눌림 마스크입니다. 설정된 비트가 눌린 버튼을 나타내며 0은 없음을 뜻합니다. 0/1로 정규화한 불리언 값이 아닙니다.
사용 조건과 제한
NES 원시 형식은 A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80입니다. 1인 비트가 눌림을 뜻합니다. nes_pad_*와 원시 __pad_* 결과에는 이 값을 사용하세요. input.h의 공통 BTN_* 마스크와 혼동하지 마세요.
이 호출은 하드웨어 상태를 새로 읽습니다. input_*나 nes_pad1_*의 저장 상태는 갱신하지 않으며 누름·뗌 에지나 반복도 만들지 않습니다. 필요하면 결과를 저장하고 직접 보관한 이전 마스크와 비교해 변화를 구하세요.
사용 예
raw1=__pad_read1(); // Controller 1: A (1) | Right (128) = 129.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
두 컨트롤러 포트, 일반 읽기와 일치 확인 읽기, 마스크 필터, 단축 매크로를 비교하는 프로그램입니다. 컨트롤러 1에서 A와 오른쪽을, 컨트롤러 2에서 B와 왼쪽을 함께 누릅니다. RAW P1, SAFE P1, ALIAS P1은 129이고 P2의 세 값은 66입니다. BUTTONS P1은 1, DIRS P1은 128입니다. 방향 필터는 상위 비트의 위치를 유지합니다. FAILED CHECKS는 0이어야 합니다. 관측값은 화면에 그대로 남습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\input_raw.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\input_raw.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-disasm --no-cache구현과 소스
kitaqfc/lib/intrinsics.h:205
Latch and sample eight D0 bits from port 1; bit order is A,B,Select,Start,Up,Down,Left,Right.
__pad_read1_d1
$4016의 확장 입력 D1에서 8비트를 직렬로 읽습니다. $4016에 1과 0을 차례로 써서 컨트롤러 상태를 래치하고, 비트 1을 여덟 번 읽어 결과의 비트 0~7에 순서대로 넣습니다. 포트 1의 D1 채널을 한 번 읽으며, 일반 컨트롤러의 D0 채널을 읽거나 일치 확인을 위해 재시도하지 않습니다.
사용 형식
u8 __pad_read1_d1(void);반환값
NES 원시 비트 순서의 8비트 눌림 마스크입니다. 설정된 비트가 눌린 버튼을 나타내며 0은 없음을 뜻합니다. 0/1로 정규화한 불리언 값이 아닙니다.
사용 조건과 제한
NES 원시 형식은 A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80입니다. 1인 비트가 눌림을 뜻합니다. nes_pad_*와 원시 __pad_* 결과에는 이 값을 사용하세요. input.h의 공통 BTN_* 마스크와 혼동하지 마세요.
선택한 D1 선에 호환되는 8비트 직렬 패드 신호를 보내는 주변기기와 함께 사용합니다. 연결 장치를 식별하거나 모든 확장 장치 프로토콜을 지원하지는 않습니다. 호출할 때마다 두 컨트롤러 스트림을 다시 래치하므로 한 읽기를 마친 뒤 다음 읽기를 시작하세요. 결과는 새로 읽은 상태이며 input_* 또는 nes_pad1_*의 저장 상태를 갱신하지 않습니다.
공개된 KUROSAKI 컨트롤러 모델은 D0와 고정된 비트 6만 반환합니다. 따라서 일반 버튼을 누르고 있어도 이 에뮬레이터의 D1과 마이크 비트는 항상 0입니다. 제공된 실행 결과는 빌드, 일반 버튼 입력과 이 0 신호 표시를 확인한 것입니다. 주변기기의 0이 아닌 신호, 전기적 타이밍과 실기 호환성은 검증하지 않았습니다. 여기서 0이 나온다고 장치가 정상 작동한다거나 실기에서도 읽을 수 없다는 뜻은 아닙니다.
사용 예
d1_first=__pad_read1_d1();호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
일반 D0 입력, 확장 D1 입력과 마이크 디지털 레벨을 비교하는 프로그램입니다. 상태가 캡처될 때까지 컨트롤러 1의 A와 컨트롤러 2의 B를 누릅니다. NORMAL P1과 NORMAL P2는 001과 002입니다. 제공된 KUROSAKI 실행에서는 해당 신호선을 에뮬레이션하지 않으므로 D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL, MIC DIRECT가 모두 000입니다. 캡처 후 표시값은 고정됩니다. 적합한 주변기기와 신호선 구현이 있는 환경에서는 실제 입력에 따라 값이 달라지므로, 이 0을 기대하지 말고 위의 비트 배치에 따라 해석하세요.
공개된 KUROSAKI 컨트롤러 모델은 D0와 고정된 비트 6만 반환합니다. 따라서 일반 버튼을 누르고 있어도 이 에뮬레이터의 D1과 마이크 비트는 항상 0입니다. 제공된 실행 결과는 빌드, 일반 버튼 입력과 이 0 신호 표시를 확인한 것입니다. 주변기기의 0이 아닌 신호, 전기적 타이밍과 실기 호환성은 검증하지 않았습니다. 여기서 0이 나온다고 장치가 정상 작동한다거나 실기에서도 읽을 수 없다는 뜻은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\expansion_input.c -I .\kitaq-docs\samples -I .\kitaqfc\lib -o .\out\expansion_input.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:223
Famicom/NES expansion input devices. D1 readers are for expansion-port controllers/joysticks that expose a standard 8-bit joypad stream on data line D1 instead of D0. __joypad2p_voice() / __mic_read2p() return the Famicom 2P microphone bit. Latch and read an eight-button stream from port-1 data line D1 rather than D0.
__pad_read1_safe
__pad_read1()로 전체 버튼을 두 번씩 읽어 컨트롤러 1의 두 결과가 일치하면 NES 형식 마스크를 반환합니다. 새 누름 이벤트가 아니라 현재 눌림 상태입니다.
사용 형식
u8 __pad_read1_safe(void);반환값
NES 원시 비트 순서의 8비트 눌림 마스크입니다. 설정된 비트가 눌린 버튼을 나타내며 0은 없음을 뜻합니다. 0/1로 정규화한 불리언 값이 아닙니다.
사용 조건과 제한
NES 원시 형식은 A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80입니다. 1인 비트가 눌림을 뜻합니다. nes_pad_*와 원시 __pad_* 결과에는 이 값을 사용하세요. input.h의 공통 BTN_* 마스크와 혼동하지 마세요.
이 호출은 하드웨어 상태를 새로 읽습니다. input_*나 nes_pad1_*의 저장 상태는 갱신하지 않으며 누름·뗌 에지나 반복도 만들지 않습니다. 필요하면 결과를 저장하고 직접 보관한 이전 마스크와 비교해 변화를 구하세요.
safe 읽기는 전체 버튼을 두 번씩 읽어 두 마스크가 같아질 때까지 반복합니다. 매번 $4016에 스트로브를 보내 두 컨트롤러의 직렬 읽기 위치를 처음으로 돌립니다. 재시도 횟수 제한은 없습니다. 이 일치 확인만으로 모든 DMC·주변기기 조합의 호환성이 입증되지는 않습니다.
사용 예
safe1=__pad_read1_safe(); // Repeat paired controller-1 reads until they agree.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
두 컨트롤러 포트, 일반 읽기와 일치 확인 읽기, 마스크 필터, 단축 매크로를 비교하는 프로그램입니다. 컨트롤러 1에서 A와 오른쪽을, 컨트롤러 2에서 B와 왼쪽을 함께 누릅니다. RAW P1, SAFE P1, ALIAS P1은 129이고 P2의 세 값은 66입니다. BUTTONS P1은 1, DIRS P1은 128입니다. 방향 필터는 상위 비트의 위치를 유지합니다. FAILED CHECKS는 0이어야 합니다. 관측값은 화면에 그대로 남습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\input_raw.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\input_raw.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-disasm --no-cache구현과 소스
kitaqfc/lib/intrinsics.h:209
Repeat pairs of port-1 polls until both bytes match; the retry loop has no fixed limit.
__pad_read2
$4016에 1→0을 써서 두 컨트롤러 상태를 래치한 뒤 $4017의 D0에서 컨트롤러 2의 8비트를 직렬로 읽습니다. 재시도나 에지 계산 없이 NES 형식의 눌림 상태 한 번을 반환합니다.
사용 형식
u8 __pad_read2(void);반환값
NES 원시 비트 순서의 8비트 눌림 마스크입니다. 설정된 비트가 눌린 버튼을 나타내며 0은 없음을 뜻합니다. 0/1로 정규화한 불리언 값이 아닙니다.
사용 조건과 제한
NES 원시 형식은 A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80입니다. 1인 비트가 눌림을 뜻합니다. nes_pad_*와 원시 __pad_* 결과에는 이 값을 사용하세요. input.h의 공통 BTN_* 마스크와 혼동하지 마세요.
이 호출은 하드웨어 상태를 새로 읽습니다. input_*나 nes_pad1_*의 저장 상태는 갱신하지 않으며 누름·뗌 에지나 반복도 만들지 않습니다. 필요하면 결과를 저장하고 직접 보관한 이전 마스크와 비교해 변화를 구하세요.
사용 예
raw2=__pad_read2(); // Controller 2: B (2) | Left (64) = 66.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
두 컨트롤러 포트, 일반 읽기와 일치 확인 읽기, 마스크 필터, 단축 매크로를 비교하는 프로그램입니다. 컨트롤러 1에서 A와 오른쪽을, 컨트롤러 2에서 B와 왼쪽을 함께 누릅니다. RAW P1, SAFE P1, ALIAS P1은 129이고 P2의 세 값은 66입니다. BUTTONS P1은 1, DIRS P1은 128입니다. 방향 필터는 상위 비트의 위치를 유지합니다. FAILED CHECKS는 0이어야 합니다. 관측값은 화면에 그대로 남습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\input_raw.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\input_raw.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-disasm --no-cache구현과 소스
kitaqfc/lib/intrinsics.h:207
Latch and sample eight D0 bits from port 2 in the same NES button order.
__pad_read2_d1
$4017의 확장 입력 D1에서 8비트를 직렬로 읽습니다. $4016에 1과 0을 차례로 써서 컨트롤러 상태를 래치하고, 비트 1을 여덟 번 읽어 결과의 비트 0~7에 순서대로 넣습니다. 포트 2의 D1 채널을 한 번 읽으며, 일반 컨트롤러의 D0 채널을 읽거나 일치 확인을 위해 재시도하지 않습니다.
사용 형식
u8 __pad_read2_d1(void);반환값
NES 원시 비트 순서의 8비트 눌림 마스크입니다. 설정된 비트가 눌린 버튼을 나타내며 0은 없음을 뜻합니다. 0/1로 정규화한 불리언 값이 아닙니다.
사용 조건과 제한
NES 원시 형식은 A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80입니다. 1인 비트가 눌림을 뜻합니다. nes_pad_*와 원시 __pad_* 결과에는 이 값을 사용하세요. input.h의 공통 BTN_* 마스크와 혼동하지 마세요.
선택한 D1 선에 호환되는 8비트 직렬 패드 신호를 보내는 주변기기와 함께 사용합니다. 연결 장치를 식별하거나 모든 확장 장치 프로토콜을 지원하지는 않습니다. 호출할 때마다 두 컨트롤러 스트림을 다시 래치하므로 한 읽기를 마친 뒤 다음 읽기를 시작하세요. 결과는 새로 읽은 상태이며 input_* 또는 nes_pad1_*의 저장 상태를 갱신하지 않습니다.
공개된 KUROSAKI 컨트롤러 모델은 D0와 고정된 비트 6만 반환합니다. 따라서 일반 버튼을 누르고 있어도 이 에뮬레이터의 D1과 마이크 비트는 항상 0입니다. 제공된 실행 결과는 빌드, 일반 버튼 입력과 이 0 신호 표시를 확인한 것입니다. 주변기기의 0이 아닌 신호, 전기적 타이밍과 실기 호환성은 검증하지 않았습니다. 여기서 0이 나온다고 장치가 정상 작동한다거나 실기에서도 읽을 수 없다는 뜻은 아닙니다.
사용 예
d1_second=__pad_read2_d1();호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
일반 D0 입력, 확장 D1 입력과 마이크 디지털 레벨을 비교하는 프로그램입니다. 상태가 캡처될 때까지 컨트롤러 1의 A와 컨트롤러 2의 B를 누릅니다. NORMAL P1과 NORMAL P2는 001과 002입니다. 제공된 KUROSAKI 실행에서는 해당 신호선을 에뮬레이션하지 않으므로 D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL, MIC DIRECT가 모두 000입니다. 캡처 후 표시값은 고정됩니다. 적합한 주변기기와 신호선 구현이 있는 환경에서는 실제 입력에 따라 값이 달라지므로, 이 0을 기대하지 말고 위의 비트 배치에 따라 해석하세요.
공개된 KUROSAKI 컨트롤러 모델은 D0와 고정된 비트 6만 반환합니다. 따라서 일반 버튼을 누르고 있어도 이 에뮬레이터의 D1과 마이크 비트는 항상 0입니다. 제공된 실행 결과는 빌드, 일반 버튼 입력과 이 0 신호 표시를 확인한 것입니다. 주변기기의 0이 아닌 신호, 전기적 타이밍과 실기 호환성은 검증하지 않았습니다. 여기서 0이 나온다고 장치가 정상 작동한다거나 실기에서도 읽을 수 없다는 뜻은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\expansion_input.c -I .\kitaq-docs\samples -I .\kitaqfc\lib -o .\out\expansion_input.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:225
Latch and read an eight-button stream from port-2 data line D1 rather than D0.
__pad_read2_safe
__pad_read2()로 전체 버튼을 두 번씩 읽어 컨트롤러 2의 두 결과가 일치하면 NES 형식 마스크를 반환합니다. 새 누름 이벤트가 아니라 현재 눌림 상태입니다.
사용 형식
u8 __pad_read2_safe(void);반환값
NES 원시 비트 순서의 8비트 눌림 마스크입니다. 설정된 비트가 눌린 버튼을 나타내며 0은 없음을 뜻합니다. 0/1로 정규화한 불리언 값이 아닙니다.
사용 조건과 제한
NES 원시 형식은 A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80입니다. 1인 비트가 눌림을 뜻합니다. nes_pad_*와 원시 __pad_* 결과에는 이 값을 사용하세요. input.h의 공통 BTN_* 마스크와 혼동하지 마세요.
이 호출은 하드웨어 상태를 새로 읽습니다. input_*나 nes_pad1_*의 저장 상태는 갱신하지 않으며 누름·뗌 에지나 반복도 만들지 않습니다. 필요하면 결과를 저장하고 직접 보관한 이전 마스크와 비교해 변화를 구하세요.
safe 읽기는 전체 버튼을 두 번씩 읽어 두 마스크가 같아질 때까지 반복합니다. 매번 $4016에 스트로브를 보내 두 컨트롤러의 직렬 읽기 위치를 처음으로 돌립니다. 재시도 횟수 제한은 없습니다. 이 일치 확인만으로 모든 DMC·주변기기 조합의 호환성이 입증되지는 않습니다.
사용 예
safe2=__pad_read2_safe(); // The same consistency check on controller 2.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
두 컨트롤러 포트, 일반 읽기와 일치 확인 읽기, 마스크 필터, 단축 매크로를 비교하는 프로그램입니다. 컨트롤러 1에서 A와 오른쪽을, 컨트롤러 2에서 B와 왼쪽을 함께 누릅니다. RAW P1, SAFE P1, ALIAS P1은 129이고 P2의 세 값은 66입니다. BUTTONS P1은 1, DIRS P1은 128입니다. 방향 필터는 상위 비트의 위치를 유지합니다. FAILED CHECKS는 0이어야 합니다. 관측값은 화면에 그대로 남습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\input_raw.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\input_raw.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-disasm --no-cache구현과 소스
kitaqfc/lib/intrinsics.h:211
Repeat pairs of port-2 polls until both bytes match; this does not populate pad.c globals.
__palette_bg_load
PPU 래치를 초기화하고 주소를 0x3F00으로 설정한 뒤 배경 팔레트 네 개를 위해 원본 16바이트를 PPUDATA에 씁니다. 즉시 전송이며 네임테이블 속성의 팔레트 선택을 바꾸거나 타일 그래픽을 불러오지 않습니다.
사용 형식
void __palette_bg_load(const u8* pal16);매개변수
pal16NES 팔레트 코드 16바이트를 읽을 수 있는 CPU 포인터입니다. RGB 세 성분이 아닌 팔레트 코드를 넘깁니다. 동기 호출이 반환될 때까지 원본 뱅크를 유지하세요. 뱅크 전환이나 길이 검사는 하지 않습니다. 원하는 16개 팔레트 주소에 쓰려면 PPU 증가량 1을 사용하세요.
반환값
없음(void).
사용 조건과 제한
PPU 주소 0x3F10/14/18/1C는 0x3F00/04/08/0C와 저장 공간을 공유합니다. 따라서 스프라이트 팔레트를 불러오면 해당 배경 항목도 씁니다. 컴파일러는 지정한 바이트를 그대로 보내며 색상 검사, 팔레트 병합, 공유 항목 보존은 하지 않습니다.
직접 PPU 접근은 렌더링을 끄거나 전체 작업이 안전한 VBlank 구간 안에 끝나는 조건에서 수행하세요. 이 함수들은 대기, 큐 등록, NMI 차단, 스크롤 복원을 하지 않습니다. 호출 중 인터럽트가 공유 래치나 주소를 바꾸지 않도록 하세요. PPUCTRL 비트 2에 따라 대상 주소는 바이트마다 1 또는 32씩 증가합니다. 미러링과 주소 순환은 PPU 동작을 따르며 CHR-ROM에는 쓸 수 없습니다. 접근 후 렌더링 전에 원하는 스크롤 상태를 설정하세요.
사용 예
__palette_bg_load(scene_palette); // Four background palettes, 16 bytes total.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 프로그램에서는 레지스터 설정, 타일·속성 직접 쓰기, 스크롤, 배경·스프라이트 팔레트의 개별 전송을 배웁니다. (0,16)의 파란 띠는 256×16픽셀입니다. 첫 행은 꽉 찬 8×8타일, 왼쪽 4×8막대, 8행 삼각형을 반복하고 둘째 행은 왼쪽 막대만 나열합니다. (16,48)에는 녹색으로 채운 64×32 사각형, (144,48)에는 삼각형 타일을 가로 8개·세로 4개 배치한 빨간 64×32 영역이 표시됩니다.
y=96에는 x=16, 24, 32에 각각 파란 정사각형·왼쪽 막대·삼각형, x=80에 빨간 왼쪽 막대, x=144에 녹색 삼각형이 표시됩니다. 이들은 배경 타일입니다. 별도 팔레트의 스프라이트는 y=144에 있으며 x=16은 빨간 8×8 정사각형, x=80은 파란 왼쪽 4×8막대, x=144는 녹색 8행 삼각형입니다. 최종 스크롤은 (0,0)입니다.
이미지는 스프라이트 색상을 포함해 16,384픽셀의 모양과 색상을 대조합니다. 별도 31개 상태 검사는 레지스터 교체, 상태·래치 부수 효과, 저장된 스크롤, 0/255바이트 전송, 증가량 1/32, 맵 경계, 크기 0 사각형, 테이블 선택, 전체 속성 바이트와 두 팔레트 대상을 다룹니다. Y=255는 레지스터 쓰기만 확인하며 올바른 스크롤 이미지를 검증한 것은 아닙니다. KUROSAKI의 NROM에서 검증했으며 실기는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:138
Stream exactly 16 source bytes to 3F00. Arrange PPU access timing and increment one.
kitaqfc/kitaqfc/CodeGenerator.cs:4827
case "__palette_bg_load": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2}, funcName);
void EmitHelperPalette(string name, int ppuAddress)
{
EmitHelperStart(name);
EmitAsm("LDA", Mem(0x2002));
EmitAsm("LDA", Imm((ppuAddress >> 8) & 0xFF));
EmitAsm("STA", Mem(0x2006));
EmitAsm("LDA", Imm(ppuAddress & 0xFF));
EmitAsm("STA", Mem(0x2006));
EmitAsm("LDY", Imm(0));
string loop = NewGeneratedLabel("pal_loop");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LDA", IndY(CallArgBase));
EmitAsm("STA", Mem(0x2007));
EmitAsm("INY");
EmitAsm("CPY", Imm(16));
EmitAsm("BNE", Rel(loop));
EmitAsm("RTS");
}
EmitHelperPalette("__palette_bg_load", 0x3F00);__palette_sp_load
PPU 래치를 초기화하고 주소를 0x3F10으로 설정한 뒤 스프라이트 팔레트 네 개를 위해 원본 16바이트를 PPUDATA에 씁니다. 표시할 팔레트는 스프라이트 속성 비트로 선택합니다. OAM을 바꾸거나 스프라이트 타일 그래픽을 전송하지 않습니다.
사용 형식
void __palette_sp_load(const u8* pal16);매개변수
pal16NES 팔레트 코드 16바이트를 읽을 수 있는 CPU 포인터입니다. RGB 세 성분이 아닌 팔레트 코드를 넘깁니다. 동기 호출이 반환될 때까지 원본 뱅크를 유지하세요. 뱅크 전환이나 길이 검사는 하지 않습니다. 원하는 16개 팔레트 주소에 쓰려면 PPU 증가량 1을 사용하세요.
반환값
없음(void).
사용 조건과 제한
PPU 주소 0x3F10/14/18/1C는 0x3F00/04/08/0C와 저장 공간을 공유합니다. 따라서 스프라이트 팔레트를 불러오면 해당 배경 항목도 씁니다. 컴파일러는 지정한 바이트를 그대로 보내며 색상 검사, 팔레트 병합, 공유 항목 보존은 하지 않습니다.
직접 PPU 접근은 렌더링을 끄거나 전체 작업이 안전한 VBlank 구간 안에 끝나는 조건에서 수행하세요. 이 함수들은 대기, 큐 등록, NMI 차단, 스크롤 복원을 하지 않습니다. 호출 중 인터럽트가 공유 래치나 주소를 바꾸지 않도록 하세요. PPUCTRL 비트 2에 따라 대상 주소는 바이트마다 1 또는 32씩 증가합니다. 미러링과 주소 순환은 PPU 동작을 따르며 CHR-ROM에는 쓸 수 없습니다. 접근 후 렌더링 전에 원하는 스크롤 상태를 설정하세요.
사용 예
__palette_sp_load(scene_palette); // Four sprite palettes, 16 bytes total.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 프로그램에서는 레지스터 설정, 타일·속성 직접 쓰기, 스크롤, 배경·스프라이트 팔레트의 개별 전송을 배웁니다. (0,16)의 파란 띠는 256×16픽셀입니다. 첫 행은 꽉 찬 8×8타일, 왼쪽 4×8막대, 8행 삼각형을 반복하고 둘째 행은 왼쪽 막대만 나열합니다. (16,48)에는 녹색으로 채운 64×32 사각형, (144,48)에는 삼각형 타일을 가로 8개·세로 4개 배치한 빨간 64×32 영역이 표시됩니다.
y=96에는 x=16, 24, 32에 각각 파란 정사각형·왼쪽 막대·삼각형, x=80에 빨간 왼쪽 막대, x=144에 녹색 삼각형이 표시됩니다. 이들은 배경 타일입니다. 별도 팔레트의 스프라이트는 y=144에 있으며 x=16은 빨간 8×8 정사각형, x=80은 파란 왼쪽 4×8막대, x=144는 녹색 8행 삼각형입니다. 최종 스크롤은 (0,0)입니다.
이미지는 스프라이트 색상을 포함해 16,384픽셀의 모양과 색상을 대조합니다. 별도 31개 상태 검사는 레지스터 교체, 상태·래치 부수 효과, 저장된 스크롤, 0/255바이트 전송, 증가량 1/32, 맵 경계, 크기 0 사각형, 테이블 선택, 전체 속성 바이트와 두 팔레트 대상을 다룹니다. Y=255는 레지스터 쓰기만 확인하며 올바른 스크롤 이미지를 검증한 것은 아닙니다. KUROSAKI의 NROM에서 검증했으며 실기는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:140
Stream exactly 16 source bytes to 3F10; hardware palette aliases also affect background entries.
kitaqfc/kitaqfc/CodeGenerator.cs:4828
case "__palette_sp_load": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2}, funcName);
// Clear queued length and readiness; the overflow indicator has a separate reset intrinsic.
void EmitHelperPalette(string name, int ppuAddress)
{
EmitHelperStart(name);
EmitAsm("LDA", Mem(0x2002));
EmitAsm("LDA", Imm((ppuAddress >> 8) & 0xFF));
EmitAsm("STA", Mem(0x2006));
EmitAsm("LDA", Imm(ppuAddress & 0xFF));
EmitAsm("STA", Mem(0x2006));
EmitAsm("LDY", Imm(0));
string loop = NewGeneratedLabel("pal_loop");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LDA", IndY(CallArgBase));
EmitAsm("STA", Mem(0x2007));
EmitAsm("INY");
EmitAsm("CPY", Imm(16));
EmitAsm("BNE", Rel(loop));
EmitAsm("RTS");
}
EmitHelperPalette("__palette_sp_load", 0x3F10);__ppu_addr
ppu_addr를 한 번 평가하고 PPUSTATUS를 읽어 공유 쓰기 래치를 초기화한 뒤 상위·하위 주소 바이트를 PPUADDR에 씁니다. 이후 PPUDATA 접근의 대상을 설정하며 데이터 자체는 쓰지 않습니다. PPU 주소 마스킹과 미러링이 적용됩니다.
사용 형식
void __ppu_addr(u16 ppu_addr);매개변수
ppu_addrCPU 포인터가 아닌 PPU 바이트 주소(
0x0000–0x3FFF)입니다. 대상은 쓰기 가능해야 하며 패턴 변경에는 CHR ROM이 아닌 CHR RAM이 필요합니다. PPU 매핑과 미러링이 적용됩니다. 연속 복사와 채우기에는 PPUCTRL 증가량을 1로 설정하세요. 안전한 PPU 쓰기 시점에 실행한 뒤 스크롤 상태를 다시 설정하세요. 실행기는 PPUADDR와 PPUDATA를 사용하고 스크롤 설정을 복원하지 않습니다.
반환값
없음(void).
사용 조건과 제한
직접 PPU 접근은 렌더링을 끄거나 전체 작업이 안전한 VBlank 구간 안에 끝나는 조건에서 수행하세요. 이 함수들은 대기, 큐 등록, NMI 차단, 스크롤 복원을 하지 않습니다. 호출 중 인터럽트가 공유 래치나 주소를 바꾸지 않도록 하세요. PPUCTRL 비트 2에 따라 대상 주소는 바이트마다 1 또는 32씩 증가합니다. 미러링과 주소 순환은 PPU 동작을 따르며 CHR-ROM에는 쓸 수 없습니다. 접근 후 렌더링 전에 원하는 스크롤 상태를 설정하세요.
사용 예
address_port=0x21;
__ppu_addr(0x2192); // Reset latch and set the cell at tile (18,12).호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 프로그램에서는 레지스터 설정, 타일·속성 직접 쓰기, 스크롤, 배경·스프라이트 팔레트의 개별 전송을 배웁니다. (0,16)의 파란 띠는 256×16픽셀입니다. 첫 행은 꽉 찬 8×8타일, 왼쪽 4×8막대, 8행 삼각형을 반복하고 둘째 행은 왼쪽 막대만 나열합니다. (16,48)에는 녹색으로 채운 64×32 사각형, (144,48)에는 삼각형 타일을 가로 8개·세로 4개 배치한 빨간 64×32 영역이 표시됩니다.
y=96에는 x=16, 24, 32에 각각 파란 정사각형·왼쪽 막대·삼각형, x=80에 빨간 왼쪽 막대, x=144에 녹색 삼각형이 표시됩니다. 이들은 배경 타일입니다. 별도 팔레트의 스프라이트는 y=144에 있으며 x=16은 빨간 8×8 정사각형, x=80은 파란 왼쪽 4×8막대, x=144는 녹색 8행 삼각형입니다. 최종 스크롤은 (0,0)입니다.
이미지는 스프라이트 색상을 포함해 16,384픽셀의 모양과 색상을 대조합니다. 별도 31개 상태 검사는 레지스터 교체, 상태·래치 부수 효과, 저장된 스크롤, 0/255바이트 전송, 증가량 1/32, 맵 경계, 크기 0 사각형, 테이블 선택, 전체 속성 바이트와 두 팔레트 대상을 다룹니다. Y=255는 레지스터 쓰기만 확인하며 올바른 스크롤 이미지를 검증한 것은 아닙니다. KUROSAKI의 NROM에서 검증했으며 실기는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:106
Set the PPU address through the compiler helper; address writes affect the shared scroll/address latch.
kitaqfc/kitaqfc/CodeGenerator.cs:4698
case "__ppu_addr":
if (!Need(1)) return true;
PpuAddr(args[0]); return true;
void PpuAddr(Expr e)
{
int[] t = AcquireTemps(2);
try
{
EmitLoadValue(e, ctx, 2);
EmitAsm("STA", Mem(t[0]));
EmitAsm("STX", Mem(t[1]));
EmitAsm("LDA", Mem(0x2002));
EmitAsm("LDA", Mem(t[1]));
EmitAsm("STA", Mem(0x2006));
EmitAsm("LDA", Mem(t[0]));
EmitAsm("STA", Mem(0x2006));
}
finally { ReleaseTemps(t); }
}__ppu_ctrl_get
PPUCTRL의 소프트웨어 복사본을 반환합니다. 일시적으로 설정을 바꾸기 전에 NMI 활성화, 패턴 테이블 선택, 주소 증가량을 보관할 때 사용합니다.
사용 형식
u8 __ppu_ctrl_get(void);반환값
라이브러리 또는 내장 설정 함수가 관리하는 마지막 제어값을 u8로 반환합니다. PPUSTATUS는 읽지 않습니다.
사용 조건과 제한
PPUCTRL과 PPUMASK는 쓰기 전용입니다. 이 조회 함수는 하드웨어를 읽지 않습니다. $2000/$2001에 직접 쓰거나 별도 어셈블리 코드를 쓰면 소프트웨어 복사본은 갱신되지 않습니다. 대응하는 __ppu_*_set 연산을 일관되게 사용하세요. 복사본을 읽어도 래치나 인터럽트 상태는 바뀌지 않습니다.
사용 예
__ppu_ctrl_set(4);
if(__ppu_ctrl_get()!=4)failures=1; // Inspect the software shadow, not PPUSTATUS.
__ppu_ctrl_set(0);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
실행 가능한 대안은 렌더링을 끄고 화면을 준비하며, 팔레트 설정과 타일·속성 초기화 후 이 이미지를 그립니다. (0,16)부터 파란색의 꽉 찬 타일·왼쪽 절반 막대·삼각형이 반복되고 (0,96)부터 녹색으로 채워집니다. 각 영역은 256×64픽셀이지만 255개의 타일 바이트를 전송하므로 마지막 8×8 칸인 (248,72)와 (248,152)는 검은색으로 남습니다. y=208의 x=16에는 빨간 8×8 정사각형, x=224에는 왼쪽 4×8 막대, x=240에는 8행 삼각형이 표시됩니다. FAILED CHECKS는 0입니다. 별도 상태 검사는 제어 레지스터 두 개, 32바이트 팔레트 전송, 네임테이블 1,024바이트 전체 초기화를 확인합니다. 네 선언의 직접 호출은 링크 실패가 예상 결과입니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_implemented.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\ppu_implemented.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:103
Read the software PPUCTRL/PPUMASK shadows; use the setters for coherent state. Raw writes to $2000/$2001 bypass these shadows. No PPU hardware read occurs.
kitaqfc/kitaqfc/CodeGenerator.cs:4691
case "__ppu_ctrl_get":
if (!Need(0)) return true;
EmitAsm("LDA", Mem(_runtimePpuCtrlShadowAddress)); return true;__ppu_ctrl_set
PPUCTRL과 소프트웨어 저장값을 지정한 바이트로 바꿉니다. NMI 활성화, PPU 주소 증가량, 패턴 테이블, 스프라이트 크기, 기준 네임테이블을 제어합니다. PPUMASK는 바꾸지 않으며 지정하지 않은 비트를 보존하지도 않습니다.
사용 형식
void __ppu_ctrl_set(u8 value);매개변수
value지정한 그대로 쓰는 부호 없는 바이트
0..255입니다. 불리언이나 비트 번호가 아니라 레지스터 또는 데이터 바이트 전체입니다.
반환값
없음(void).
사용 예
__ppu_ctrl_set(0x80); // NMI enabled, increment 1, nametable/pattern tables 0.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 프로그램에서는 레지스터 설정, 타일·속성 직접 쓰기, 스크롤, 배경·스프라이트 팔레트의 개별 전송을 배웁니다. (0,16)의 파란 띠는 256×16픽셀입니다. 첫 행은 꽉 찬 8×8타일, 왼쪽 4×8막대, 8행 삼각형을 반복하고 둘째 행은 왼쪽 막대만 나열합니다. (16,48)에는 녹색으로 채운 64×32 사각형, (144,48)에는 삼각형 타일을 가로 8개·세로 4개 배치한 빨간 64×32 영역이 표시됩니다.
y=96에는 x=16, 24, 32에 각각 파란 정사각형·왼쪽 막대·삼각형, x=80에 빨간 왼쪽 막대, x=144에 녹색 삼각형이 표시됩니다. 이들은 배경 타일입니다. 별도 팔레트의 스프라이트는 y=144에 있으며 x=16은 빨간 8×8 정사각형, x=80은 파란 왼쪽 4×8막대, x=144는 녹색 8행 삼각형입니다. 최종 스크롤은 (0,0)입니다.
이미지는 스프라이트 색상을 포함해 16,384픽셀의 모양과 색상을 대조합니다. 별도 31개 상태 검사는 레지스터 교체, 상태·래치 부수 효과, 저장된 스크롤, 0/255바이트 전송, 증가량 1/32, 맵 경계, 크기 0 사각형, 테이블 선택, 전체 속성 바이트와 두 팔레트 대상을 다룹니다. Y=255는 레지스터 쓰기만 확인하며 올바른 스크롤 이미지를 검증한 것은 아닙니다. KUROSAKI의 NROM에서 검증했으며 실기는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:100
Replace PPUCTRL, including NMI enable, pattern-table choices and address increment mode.
kitaqfc/kitaqfc/CodeGenerator.cs:4687
case "__ppu_ctrl_set":
if (!Need(1)) return true;
Store1(args[0], _runtimePpuCtrlShadowAddress); EmitAsm("STA", Mem(0x2000)); return true;
// Read software state: the corresponding hardware registers are write-only.__ppu_data
현재 PPU 주소에 PPUDATA를 통해 value를 한 번 씁니다. 이후 주소는 PPUCTRL 설정에 따라 1 또는 32씩 증가합니다. 주소 설정, 래치 초기화, 전송 큐 등록은 하지 않습니다.
사용 형식
void __ppu_data(u8 value);매개변수
value지정한 그대로 쓰는 부호 없는 바이트
0..255입니다. 불리언이나 비트 번호가 아니라 레지스터 또는 데이터 바이트 전체입니다.
반환값
없음(void).
사용 조건과 제한
직접 PPU 접근은 렌더링을 끄거나 전체 작업이 안전한 VBlank 구간 안에 끝나는 조건에서 수행하세요. 이 함수들은 대기, 큐 등록, NMI 차단, 스크롤 복원을 하지 않습니다. 호출 중 인터럽트가 공유 래치나 주소를 바꾸지 않도록 하세요. PPUCTRL 비트 2에 따라 대상 주소는 바이트마다 1 또는 32씩 증가합니다. 미러링과 주소 순환은 PPU 동작을 따르며 CHR-ROM에는 쓸 수 없습니다. 접근 후 렌더링 전에 원하는 스크롤 상태를 설정하세요.
사용 예
__ppu_data(3); // Green triangle at (144,96); destination advances by one.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 프로그램에서는 레지스터 설정, 타일·속성 직접 쓰기, 스크롤, 배경·스프라이트 팔레트의 개별 전송을 배웁니다. (0,16)의 파란 띠는 256×16픽셀입니다. 첫 행은 꽉 찬 8×8타일, 왼쪽 4×8막대, 8행 삼각형을 반복하고 둘째 행은 왼쪽 막대만 나열합니다. (16,48)에는 녹색으로 채운 64×32 사각형, (144,48)에는 삼각형 타일을 가로 8개·세로 4개 배치한 빨간 64×32 영역이 표시됩니다.
y=96에는 x=16, 24, 32에 각각 파란 정사각형·왼쪽 막대·삼각형, x=80에 빨간 왼쪽 막대, x=144에 녹색 삼각형이 표시됩니다. 이들은 배경 타일입니다. 별도 팔레트의 스프라이트는 y=144에 있으며 x=16은 빨간 8×8 정사각형, x=80은 파란 왼쪽 4×8막대, x=144는 녹색 8행 삼각형입니다. 최종 스크롤은 (0,0)입니다.
이미지는 스프라이트 색상을 포함해 16,384픽셀의 모양과 색상을 대조합니다. 별도 31개 상태 검사는 레지스터 교체, 상태·래치 부수 효과, 저장된 스크롤, 0/255바이트 전송, 증가량 1/32, 맵 경계, 크기 0 사각형, 테이블 선택, 전체 속성 바이트와 두 팔레트 대상을 다룹니다. Y=255는 레지스터 쓰기만 확인하며 올바른 스크롤 이미지를 검증한 것은 아닙니다. KUROSAKI의 NROM에서 검증했으며 실기는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:108
Write one PPUDATA byte using the current PPU address and increment mode.
kitaqfc/kitaqfc/CodeGenerator.cs:4701
case "__ppu_data":
if (!Need(1)) return true;
Load1(args[0]); EmitAsm("STA", Mem(0x2007)); return true;
// Reading PPUSTATUS also has hardware side effects; this is not a side-effect-free status cache.__ppu_mask_get
PPUMASK의 소프트웨어 복사본을 반환합니다. 렌더링, 왼쪽 가장자리 표시, 회색조, 색 강조 설정을 보존할 때 사용합니다.
사용 형식
u8 __ppu_mask_get(void);반환값
라이브러리 또는 내장 설정 함수가 관리하는 마지막 제어값을 u8로 반환합니다. PPUSTATUS는 읽지 않습니다.
사용 조건과 제한
PPUCTRL과 PPUMASK는 쓰기 전용입니다. 이 조회 함수는 하드웨어를 읽지 않습니다. $2000/$2001에 직접 쓰거나 별도 어셈블리 코드를 쓰면 소프트웨어 복사본은 갱신되지 않습니다. 대응하는 __ppu_*_set 연산을 일관되게 사용하세요. 복사본을 읽어도 래치나 인터럽트 상태는 바뀌지 않습니다.
사용 예
__ppu_mask_set(0xE7);
if(__ppu_mask_get()!=0xE7)failures=1;
__ppu_mask_set(0);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
실행 가능한 대안은 렌더링을 끄고 화면을 준비하며, 팔레트 설정과 타일·속성 초기화 후 이 이미지를 그립니다. (0,16)부터 파란색의 꽉 찬 타일·왼쪽 절반 막대·삼각형이 반복되고 (0,96)부터 녹색으로 채워집니다. 각 영역은 256×64픽셀이지만 255개의 타일 바이트를 전송하므로 마지막 8×8 칸인 (248,72)와 (248,152)는 검은색으로 남습니다. y=208의 x=16에는 빨간 8×8 정사각형, x=224에는 왼쪽 4×8 막대, x=240에는 8행 삼각형이 표시됩니다. FAILED CHECKS는 0입니다. 별도 상태 검사는 제어 레지스터 두 개, 32바이트 팔레트 전송, 네임테이블 1,024바이트 전체 초기화를 확인합니다. 네 선언의 직접 호출은 링크 실패가 예상 결과입니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_implemented.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\ppu_implemented.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:104
kitaqfc/kitaqfc/CodeGenerator.cs:4694
case "__ppu_mask_get":
if (!Need(0)) return true;
EmitAsm("LDA", Mem(_runtimePpuMaskShadowAddress)); return true;
// Direct PPU address/data access does not wait for a safe rendering interval here.__ppu_mask_set
PPUMASK 전체 바이트와 소프트웨어 저장값을 value로 바꿉니다. 렌더링, 왼쪽 가장자리 표시, 회색조, 색상 강조를 명시적으로 선택하세요. 이전 마스크와 비트를 병합하거나 PPUCTRL을 바꾸지 않습니다.
사용 형식
void __ppu_mask_set(u8 value);매개변수
value지정한 그대로 쓰는 부호 없는 바이트
0..255입니다. 불리언이나 비트 번호가 아니라 레지스터 또는 데이터 바이트 전체입니다.
반환값
없음(void).
사용 예
__ppu_mask_set(0x0A); // Background enabled, including its leftmost eight pixels.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 프로그램에서는 레지스터 설정, 타일·속성 직접 쓰기, 스크롤, 배경·스프라이트 팔레트의 개별 전송을 배웁니다. (0,16)의 파란 띠는 256×16픽셀입니다. 첫 행은 꽉 찬 8×8타일, 왼쪽 4×8막대, 8행 삼각형을 반복하고 둘째 행은 왼쪽 막대만 나열합니다. (16,48)에는 녹색으로 채운 64×32 사각형, (144,48)에는 삼각형 타일을 가로 8개·세로 4개 배치한 빨간 64×32 영역이 표시됩니다.
y=96에는 x=16, 24, 32에 각각 파란 정사각형·왼쪽 막대·삼각형, x=80에 빨간 왼쪽 막대, x=144에 녹색 삼각형이 표시됩니다. 이들은 배경 타일입니다. 별도 팔레트의 스프라이트는 y=144에 있으며 x=16은 빨간 8×8 정사각형, x=80은 파란 왼쪽 4×8막대, x=144는 녹색 8행 삼각형입니다. 최종 스크롤은 (0,0)입니다.
이미지는 스프라이트 색상을 포함해 16,384픽셀의 모양과 색상을 대조합니다. 별도 31개 상태 검사는 레지스터 교체, 상태·래치 부수 효과, 저장된 스크롤, 0/255바이트 전송, 증가량 1/32, 맵 경계, 크기 0 사각형, 테이블 선택, 전체 속성 바이트와 두 팔레트 대상을 다룹니다. Y=255는 레지스터 쓰기만 확인하며 올바른 스크롤 이미지를 검증한 것은 아닙니다. KUROSAKI의 NROM에서 검증했으며 실기는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:98
Replace PPUMASK with the supplied byte; the caller chooses rendering, clipping and emphasis bits.
kitaqfc/kitaqfc/CodeGenerator.cs:4683
case "__ppu_mask_set":
if (!Need(1)) return true;
Store1(args[0], _runtimePpuMaskShadowAddress); EmitAsm("STA", Mem(0x2001)); return true;
// Update the PPUCTRL shadow before writing the hardware register.__ppu_off
PPUMASK와 소프트웨어 저장값에 0을 써 배경과 스프라이트 렌더링을 중지합니다. 회색조와 색상 강조를 포함한 모든 마스크 비트를 지웁니다. PPUCTRL과 NMI 활성 상태는 그대로이므로 공유 전송 상태를 준비할 때는 NMI를 별도로 제어하세요.
사용 형식
void __ppu_off(void);반환값
없음(void).
사용 예
__ppu_off(); // PPUMASK and its software shadow become zero; NMI is unchanged.
__ppu_ctrl_set(0); // Disable NMI separately while preparing the whole scene.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 프로그램에서는 레지스터 설정, 타일·속성 직접 쓰기, 스크롤, 배경·스프라이트 팔레트의 개별 전송을 배웁니다. (0,16)의 파란 띠는 256×16픽셀입니다. 첫 행은 꽉 찬 8×8타일, 왼쪽 4×8막대, 8행 삼각형을 반복하고 둘째 행은 왼쪽 막대만 나열합니다. (16,48)에는 녹색으로 채운 64×32 사각형, (144,48)에는 삼각형 타일을 가로 8개·세로 4개 배치한 빨간 64×32 영역이 표시됩니다.
y=96에는 x=16, 24, 32에 각각 파란 정사각형·왼쪽 막대·삼각형, x=80에 빨간 왼쪽 막대, x=144에 녹색 삼각형이 표시됩니다. 이들은 배경 타일입니다. 별도 팔레트의 스프라이트는 y=144에 있으며 x=16은 빨간 8×8 정사각형, x=80은 파란 왼쪽 4×8막대, x=144는 녹색 8행 삼각형입니다. 최종 스크롤은 (0,0)입니다.
이미지는 스프라이트 색상을 포함해 16,384픽셀의 모양과 색상을 대조합니다. 별도 31개 상태 검사는 레지스터 교체, 상태·래치 부수 효과, 저장된 스크롤, 0/255바이트 전송, 증가량 1/32, 맵 경계, 크기 0 사각형, 테이블 선택, 전체 속성 바이트와 두 팔레트 대상을 다룹니다. Y=255는 레지스터 쓰기만 확인하며 올바른 스크롤 이미지를 검증한 것은 아닙니다. KUROSAKI의 NROM에서 검증했으며 실기는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:96
Invoke the compiler rendering-disable helper before bulk direct PPU updates.
kitaqfc/kitaqfc/CodeGenerator.cs:4679
case "__ppu_off":
if (!Need(0)) return true;
EmitAsm("LDA", Imm(0)); EmitAsm("STA", Mem(_runtimePpuMaskShadowAddress)); EmitAsm("STA", Mem(0x2001)); return true;
// Keep the supplied mask available for later runtime code that restores PPU state.__ppu_on
PPUMASK와 소프트웨어 저장값을 0x1E로 바꿔 왼쪽 끝 8픽셀을 포함한 배경·스프라이트 렌더링을 활성화합니다. 회색조·색상 강조 설정도 덮어씁니다. PPUCTRL을 바꾸거나 NMI를 활성화하지는 않습니다.
사용 형식
void __ppu_on(void);반환값
없음(void).
사용 예
__ppu_on(); // Replace mask with 0x1E: enable sprites and background at both edges.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 프로그램에서는 레지스터 설정, 타일·속성 직접 쓰기, 스크롤, 배경·스프라이트 팔레트의 개별 전송을 배웁니다. (0,16)의 파란 띠는 256×16픽셀입니다. 첫 행은 꽉 찬 8×8타일, 왼쪽 4×8막대, 8행 삼각형을 반복하고 둘째 행은 왼쪽 막대만 나열합니다. (16,48)에는 녹색으로 채운 64×32 사각형, (144,48)에는 삼각형 타일을 가로 8개·세로 4개 배치한 빨간 64×32 영역이 표시됩니다.
y=96에는 x=16, 24, 32에 각각 파란 정사각형·왼쪽 막대·삼각형, x=80에 빨간 왼쪽 막대, x=144에 녹색 삼각형이 표시됩니다. 이들은 배경 타일입니다. 별도 팔레트의 스프라이트는 y=144에 있으며 x=16은 빨간 8×8 정사각형, x=80은 파란 왼쪽 4×8막대, x=144는 녹색 8행 삼각형입니다. 최종 스크롤은 (0,0)입니다.
이미지는 스프라이트 색상을 포함해 16,384픽셀의 모양과 색상을 대조합니다. 별도 31개 상태 검사는 레지스터 교체, 상태·래치 부수 효과, 저장된 스크롤, 0/255바이트 전송, 증가량 1/32, 맵 경계, 크기 0 사각형, 테이블 선택, 전체 속성 바이트와 두 팔레트 대상을 다룹니다. Y=255는 레지스터 쓰기만 확인하며 올바른 스크롤 이미지를 검증한 것은 아닙니다. KUROSAKI의 NROM에서 검증했으며 실기는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:94
PRG bank Resolve a symbol or function placement to its byte bank number at compilation time. u8 __bankof(u16 symbol_or_function); Select the mapper-specific PRG bank; the common bank and legal bank range depend on the target board. void __bankswitch(u8 bank); Use the same mapper PRG-switch helper as __bankswitch; retain executable code in a valid mapping. void __prg_bank_set(u8 bank); Call a declared zero-argument function; placement selects its bank and its declared type determines the result. u8 __farcall(u8 bank, u16 func); Switch to the source bank, copy len bytes forward and restore the previous bank. Keep destination RAM and helper code accessible throughout; overlapping copies are not memmove. void __far_memcpy(u8* dst, u8 bank, u16 src, u16 len); Temporarily select bank, read one byte at addr and restore the prior bank mapping. u8 __farpeek8(u8 bank, u16 addr); Temporarily select bank and read a little-endian word, then restore the previous mapping. u16 __farpeek16(u8 bank, u16 addr); PPU / VRAM Invoke the compiler rendering-enable helper; this header contains declarations, not C fallback implementations.
kitaqfc/kitaqfc/CodeGenerator.cs:4676
case "__ppu_on":
if (!Need(0)) return true;
EmitAsm("LDA", Imm(0x1E)); EmitAsm("STA", Mem(_runtimePpuMaskShadowAddress)); EmitAsm("STA", Mem(0x2001)); return true;__ppu_read_status
PPUSTATUS를 한 번 읽고 VBlank 플래그가 지워지기 전에 관측한 바이트를 반환합니다. 스크롤·주소 공유 쓰기 래치도 초기화합니다. 부수 효과가 있는 하드웨어 상태 읽기이며 저장된 프레임 카운터나 NMI 완료 검사가 아닙니다.
사용 형식
u8 __ppu_read_status(void);반환값
원시 상태 바이트입니다. 비트 7은 VBlank, 비트 6은 스프라이트 0 히트, 비트 5는 스프라이트 오버플로입니다. 필요한 플래그를 마스크로 검사하고 전체 바이트를 불리언이나 프레임 번호로 해석하지 마세요. 하위 비트는 이식 가능한 애플리케이션 상태값이 아닙니다.
사용 조건과 제한
PPUSTATUS 읽기는 NMI 타이밍과 이후 PPUSCROLL·PPUADDR 쓰기에 영향을 줄 수 있습니다. 다른 루틴이 두 바이트 중 하나만 쓴 지점에 끼워 넣지 마세요. 이 호출들은 VBlank를 기다리거나 대기 중인 전송 큐를 완료하지 않습니다.
사용 예
address_port=0x21;
status_snapshot=__ppu_read_status(); // Also resets the shared write latch.
address_port=0x21;address_port=0x84;data_port=3; // Blue triangle at (32,96).호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 프로그램에서는 레지스터 설정, 타일·속성 직접 쓰기, 스크롤, 배경·스프라이트 팔레트의 개별 전송을 배웁니다. (0,16)의 파란 띠는 256×16픽셀입니다. 첫 행은 꽉 찬 8×8타일, 왼쪽 4×8막대, 8행 삼각형을 반복하고 둘째 행은 왼쪽 막대만 나열합니다. (16,48)에는 녹색으로 채운 64×32 사각형, (144,48)에는 삼각형 타일을 가로 8개·세로 4개 배치한 빨간 64×32 영역이 표시됩니다.
y=96에는 x=16, 24, 32에 각각 파란 정사각형·왼쪽 막대·삼각형, x=80에 빨간 왼쪽 막대, x=144에 녹색 삼각형이 표시됩니다. 이들은 배경 타일입니다. 별도 팔레트의 스프라이트는 y=144에 있으며 x=16은 빨간 8×8 정사각형, x=80은 파란 왼쪽 4×8막대, x=144는 녹색 8행 삼각형입니다. 최종 스크롤은 (0,0)입니다.
이미지는 스프라이트 색상을 포함해 16,384픽셀의 모양과 색상을 대조합니다. 별도 31개 상태 검사는 레지스터 교체, 상태·래치 부수 효과, 저장된 스크롤, 0/255바이트 전송, 증가량 1/32, 맵 경계, 크기 0 사각형, 테이블 선택, 전체 속성 바이트와 두 팔레트 대상을 다룹니다. Y=255는 레지스터 쓰기만 확인하며 올바른 스크롤 이미지를 검증한 것은 아닙니다. KUROSAKI의 NROM에서 검증했으며 실기는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:110
Read PPUSTATUS; the hardware clears VBlank status and resets the shared write latch.
kitaqfc/kitaqfc/CodeGenerator.cs:4705
case "__ppu_read_status":
if (!Need(0)) return true;
EmitAsm("LDA", Mem(0x2002)); return true;__prg_bank_set
__bankswitch와 동일한 매퍼별 PRG 뱅크 전환을 수행합니다. 뱅크 번호 검사도 같으며, 이전 매핑으로 자동 복귀하지 않습니다.
사용 형식
void __prg_bank_set(u8 bank);매개변수
bank부호 없는 8비트 ROM 뱅크 번호입니다. 호출 시에는 함수 배치와 일치해야 하고, 읽기 시에는 원본 매핑을 지정합니다. 대상 카트리지에서 유효한 범위를 사용하세요.
반환값
없음(void).
사용 조건과 제한
지속되는 뱅크 변경으로 실행 중인 코드나 반환 주소가 사라져서는 안 됩니다. 예제처럼 호출 위치, 사용하는 bank_switch 본체, 이후 실행할 코드를 공통·고정 뱅크 0에 두세요.
라이브러리 기록은 0으로 시작하며 카트리지의 시작 매핑을 알아내지 않습니다. bank_switch로 초기화하세요. 내장 함수나 매퍼에 대한 직접 쓰기는 이 별도 기록을 갱신하지 않으므로 bank_get_current로 실제 하드웨어 매핑을 검증할 수는 없습니다.
FC 전환 번호는 컴파일러의 논리 뱅크 번호입니다. 0은 공통 ROM이며 전환 대상이 아닙니다. 보조 코드는 0을 뱅크 1로 처리합니다. SUROM512에서는 1~30만 유효합니다. 전환 내장 함수에 범위를 벗어난 상수를 직접 전달하면 빌드 오류가 나며, 잘못된 실행 시점 값은 1로 대체되고 매퍼 오류 상태가 설정됩니다. 공통 ROM은 뱅크 0 전환을 요청하지 말고 직접 읽으세요.
사용 예
__prg_bank_set(1);bank_expect(home1[0]==0xA1,21);
__prg_bank_set(2); // Same mapper helper as __bankswitch.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
처음 bank_switch(2)는 뱅크 2를 기록하고 선택합니다. 이후 직접 전환으로 뱅크 1을 선택해 home1에서 A1을 읽어도 bank_get_current는 여전히 2입니다. 복귀하면 home2에서 다시 B2를 읽습니다.
완성 프로그램은 뱅크 번호와 근거리 주소로 데이터를 지정하는 방법, 뱅크를 넘는 콜백, 라이브러리 기록과 실제 매핑의 구별을 보여 줍니다. 모든 표시 숫자는 16진수입니다. BANK IN과 BANK OUT은 02, BYTE HEX는 D3, WORD HEX는 5C7A, FAILED CHECKS와 FIRST FAILURE는 모두 00이어야 합니다. CGB와 FC는 파란 글자, DMG는 검은 글자를 표시합니다.
CALL COUNT는 02입니다. far_call 매크로와 __farcall이 각각 콜백을 한 번 호출합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\banked_data_calls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\banked_data_calls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:81
Use the same mapper PRG-switch helper as __bankswitch; retain executable code in a valid mapping.
kitaqfc/kitaqfc/CodeGenerator.cs:4746
case "__prg_bank_set":
case "__bankswitch":
if (!Need(1)) return true;
if (Program.NesMapperProfile.IsSurom512 && TryEvaluateConstant(args[0], out int constantBank) && (constantBank < 1 || constantBank > 30))
{
Program.Error("error KQFC2603 KQFC-SUROM-BANK-RANGE: {0} constant bank {1} is outside 1..30; common bank 0 is not a switch target.", funcName, constantBank);
return true;
}
_mapperWritingFunctions.Add(_currentFunctionName);
Load1(args[0]); EmitAsm("JSR", Abs("__kq_prg_set_bank_a")); return true;__rng8
공유 16비트 FC Galois LFSR을 한 번 진행시키고 갱신된 하위·상위 바이트의 XOR을 반환합니다. LFSR은 XOR로 피드백을 계산하는 시프트 레지스터이므로 고정 시드는 재현 가능한 바이트 수열을 만듭니다.
사용 형식
u8 __rng8(void);반환값
공유 생성기를 한 번 진행시킨 뒤 얻는 u8 결과(0–255)입니다. 값의 자료형과 범위를 설명하는 것이며 모든 바이트의 확률이 같다는 뜻은 아닙니다.
사용 조건과 제한
FC 단계는 16비트 상태를 오른쪽으로 1비트 시프트합니다. 원래 최하위 비트가 1이면 시프트한 상태에 0xB400을 XOR합니다. 반환 바이트는 하위 바이트 그대로가 아니라 low_byte XOR high_byte입니다.
두 상태 바이트가 모두 0이면 다음 추출에서 시프트 전에 0xA55A로 대체합니다. 따라서 __rng_seed(0) 다음 __rng8()은 255를 반환합니다. 시드 호출은 0을 저장하며 대체는 추출 호출에서 일어납니다.
이 호출들은 지속되는 상태를 공유합니다. 재현하려면 대상 기종, 시드, 다른 보조 함수가 호출하는 경우를 포함한 호출 순서가 같아야 합니다. 시드 설정과 추출은 한 실행 문맥에서 수행하세요. 이 검증은 인터럽트 핸들러에서의 안전한 동시 사용을 입증하지 않습니다.
GB와 FC는 서로 다른 알고리즘을 사용하므로 같은 시드에 같은 수열을 보장하지 않습니다. 게임용 결정적 생성기이며 암호학적 난수원이 아닙니다. 테스트는 구현 동작을 검사하며 통계적 품질이나 주기를 검사하지 않습니다.
사용 예
__rng_seed(0x1234);
first=__rng8();second=__rng8();third=__rng8(); // FC sequence: 19, 137, 240.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
FC 바이트를 연속 세 번 읽는 샘플입니다. 시드 0x1234는 FIRST 00019, SECOND 00137, THIRD 00240을 만듭니다. 이 값들은 호출 순서를 보여 주며 빈도나 무작위성 점수가 아닙니다.
전체 NROM 프로그램은 검은 배경에 파란색 십진수 결과를 표시합니다. FAILED는 00000이어야 합니다. 결과 화면을 켜기 전에 렌더링을 끈 상태에서 텍스트를 업로드합니다.
검증에서는 독립적인 호스트 모델과 사례별 출력 32개 및 후속 바이트 추출 8개를 비교했습니다. GB 93사례와 FC 18사례로, 상수·런타임 시드와 GB의 범위·확률·가중치 경곗값을 포함합니다. 학습용 화면은 에뮬레이터 240프레임 후 검사했습니다. 실기와 통계적 품질은 시험하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\rng_core.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\rng_core.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:39
Advance the shared 16-bit LFSR and return the XOR of its two bytes; zero state is reseeded to A55A.
kitaqfc/kitaqfc/CodeGenerator.cs:4663
case "__rng8":
if (!Need(0)) return true;
EmitAsm("JSR", Abs("__rng8"));
return true;
// Far-memory helpers pack the address, bank and length arguments according to their explicit byte/word signatures.
EmitHelperStart("__rng8");
// 16-bit Galois LFSR: if low bit set, shift right then xor $B400.
EmitAsm("LDA", Mem(_runtimeRngLoAddress));
EmitAsm("ORA", Mem(_runtimeRngHiAddress));
// Replace the all-zero LFSR state with a deterministic nonzero seed before advancing.
string seeded = NewGeneratedLabel("rng_seeded");
EmitAsm("BNE", Rel(seeded));
EmitAsm("LDA", Imm(0x5A));
EmitAsm("STA", Mem(_runtimeRngLoAddress));
EmitAsm("LDA", Imm(0xA5));
EmitAsm("STA", Mem(_runtimeRngHiAddress));
_assembly.Add(Expr.Make(Tag.Label, seeded));
EmitAsm("LDA", Mem(_runtimeRngLoAddress));
EmitAsm("AND", Imm(1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LSR", Mem(_runtimeRngHiAddress));
EmitAsm("ROR", Mem(_runtimeRngLoAddress));
string rngNoXor = NewGeneratedLabel("rng_no_xor");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("BEQ", Rel(rngNoXor));
EmitAsm("LDA", Mem(_runtimeRngHiAddress));
EmitAsm("EOR", Imm(0xB4));
EmitAsm("STA", Mem(_runtimeRngHiAddress));
_assembly.Add(Expr.Make(Tag.Label, rngNoXor));
EmitAsm("LDA", Mem(_runtimeRngLoAddress));
EmitAsm("EOR", Mem(_runtimeRngHiAddress));
EmitAsm("RTS");__rng_seed
FC 생성기의 상태 바이트 두 개를 바꿉니다. 시드를 다시 사용하면 테스트나 리플레이가 같은 의사 난수 수열 위치에서 시작할 수 있습니다. 이 호출은 생성기를 진행시키지 않습니다.
사용 형식
void __rng_seed(u16 seed);매개변수
seed부호 없는 16비트 시드 0–65535입니다. 두 바이트를 전달된 그대로 저장합니다. 재현할 게임 추출 순서가 시작되기 전에 시드를 정하고 기록하세요.
반환값
없음(void).
사용 조건과 제한
두 상태 바이트가 모두 0이면 다음 추출에서 시프트 전에 0xA55A로 대체합니다. 따라서 __rng_seed(0) 다음 __rng8()은 255를 반환합니다. 시드 호출은 0을 저장하며 대체는 추출 호출에서 일어납니다.
이 호출들은 지속되는 상태를 공유합니다. 재현하려면 대상 기종, 시드, 다른 보조 함수가 호출하는 경우를 포함한 호출 순서가 같아야 합니다. 시드 설정과 추출은 한 실행 문맥에서 수행하세요. 이 검증은 인터럽트 핸들러에서의 안전한 동시 사용을 입증하지 않습니다.
GB와 FC는 서로 다른 알고리즘을 사용하므로 같은 시드에 같은 수열을 보장하지 않습니다. 게임용 결정적 생성기이며 암호학적 난수원이 아닙니다. 테스트는 구현 동작을 검사하며 통계적 품질이나 주기를 검사하지 않습니다.
사용 예
__rng_seed(0x1234);
repeated=__rng8(); // The first byte is 19 again.
__rng_seed(0);
zero=__rng8(); // The next draw uses the FC fallback state and returns 255.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
재현 가능한 FC 초기 상태와 시드 0 규칙을 보여 주는 샘플입니다. 시드 0x1234에서 FIRST와 RESEEDED는 모두 00019입니다. 시드를 0으로 설정하면 ZERO SEED는 00255를 표시합니다.
전체 NROM 프로그램은 검은 배경에 파란색 십진수 결과를 표시합니다. FAILED는 00000이어야 합니다. 결과 화면을 켜기 전에 렌더링을 끈 상태에서 텍스트를 업로드합니다.
검증에서는 독립적인 호스트 모델과 사례별 출력 32개 및 후속 바이트 추출 8개를 비교했습니다. GB 93사례와 FC 18사례로, 상수·런타임 시드와 GB의 범위·확률·가중치 경곗값을 포함합니다. 학습용 화면은 에뮬레이터 240프레임 후 검사했습니다. 실기와 통계적 품질은 시험하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\rng_core.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\rng_core.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:37
Replace both persistent state bytes. Reusing a seed reproduces the sequence; this is not cryptographic randomness.
kitaqfc/kitaqfc/CodeGenerator.cs:4661
case "__rng_seed":
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2}, "__rng_seed");
EmitHelperStart("__rng_seed");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeRngLoAddress));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeRngHiAddress));
EmitAsm("RTS");__rob_flash
렌더링 켜짐 또는 꺼짐 마스크를 선택하고 NMI 카운터 변화 한 번을 기다립니다. 반환 후에도 선택한 마스크가 유지됩니다.
사용 형식
void __rob_flash(u8 bright);매개변수
bright0은 꺼짐, 255를 포함한 0 이외의 바이트는 켜짐입니다. 밝기 단계가 아닙니다.
반환값
반환값은 없습니다. 요청한 대기가 완료된 후 호출자로 돌아갑니다.
사용 조건과 제한
intrinsics.h를 포함하세요. 기본 NMI 핸들러가 실행되어 카운터를 증가시켜야 합니다. NMI가 꺼져 있거나 사용자 핸들러가 카운터를 갱신하지 않으면 대기에서 빠져나오지 못합니다. 시간 제한은 없습니다.
켜짐 구간은 PPUMASK와 소프트웨어 복사본을 0x1E로, 꺼짐 구간은 둘 다 0으로 설정합니다. 이전 마스크는 복원하지 않습니다. 배경·스프라이트·왼쪽 가장자리 표시를 함께 바꾸며 회색조와 색 강조 설정도 덮어씁니다.
렌더링을 켜는 것만으로 흰 화면이 생기지는 않습니다. 밝은 타일과 팔레트 항목, 꺼짐 구간용 어두운 배경색을 준비하세요. 샘플은 흰색으로 채운 타일과 검은 배경색을 사용해 동작 중 전체 화면을 깜박입니다.
대기 횟수는 NMI 카운터의 변화를 셉니다. 진입부터 첫 NMI까지는 한 프레임보다 짧을 수 있습니다. 인터럽트가 아닌 메인 코드에서 호출하세요. 다른 루틴이 렌더링 마스크를 바꾸면 패턴이 깨질 수 있습니다.
저수준 표시 마스크·대기 연산입니다. 로봇 명령의 시작 신호, 장치별 명령 시퀀스, 수신 확인은 생성하지 않습니다. 검증 범위는 KUROSAKI의 PPUMASK 쓰기, NMI 횟수, 픽셀입니다. 실제 로봇의 수신은 시험하지 않았습니다.
사용 예
start=__nmi_ready();
__rob_flash(1); // Enable rendering and wait for one NMI-counter change.
rob_result[0]=(u8)(__nmi_ready()-start);
__rob_flash(0); // Disable rendering; the black backdrop remains visible.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
실행 후 FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5, A5 BYTE WAITS 48이 표시됩니다. 도식은 0xA5를 나타내며 위쪽은 1,0,1,0, 아래쪽은 0,1,0,1입니다. 칸 하나는 대기 한 번으로, 흰색은 렌더링 켜짐, 검은색은 꺼짐입니다. 1은 흰색 4칸 뒤 검은색 2칸, 0은 흰색 2칸 뒤 검은색 4칸입니다. FINAL MASK 0과 TIMING CHECKS OK는 완료를 나타냅니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\rob_timing.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\rob_timing.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:272
Set PPUMASK and its shadow to 1E or 00, then wait for an NMI-counter change. The caller supplies image/palette brightness; rendering enable alone does not create a white screen.
kitaqfc/kitaqfc/CodeGenerator.cs:7867
EmitHelperStart("__rob_flash");
EmitAsm("LDA", Mem(CallArgBase));
string black = NewGeneratedLabel("rob_black");
EmitAsm("BEQ", Rel(black));
EmitAsm("LDA", Imm(0x1E));
EmitAsm("STA", Mem(_runtimePpuMaskShadowAddress));
EmitAsm("STA", Mem(0x2001));
EmitAsm("JSR", Abs("__nmi_wait"));
EmitAsm("RTS");
_assembly.Add(Expr.Make(Tag.Label, black));
EmitAsm("LDA", Imm(0x00));
EmitAsm("STA", Mem(_runtimePpuMaskShadowAddress));
EmitAsm("STA", Mem(0x2001));
EmitAsm("JSR", Abs("__nmi_wait"));
EmitAsm("RTS");
// Emit the requested number of enabled frames followed by the requested disabled frames.__rob_pulse
요청한 켜짐 대기 후 꺼짐 대기를 수행해 펄스 하나를 만듭니다.
사용 형식
void __rob_pulse(u8 on_frames, u8 off_frames);매개변수
on_frames켜짐 구간 대기 횟수 0..255입니다. 0이면 켜짐 구간을 건너뜁니다.
off_frames꺼짐 구간 대기 횟수 0..255입니다. 0이면 꺼짐 구간을 건너뜁니다. 전체는 최대 510회입니다.
반환값
반환값은 없습니다. 요청한 대기가 완료된 후 호출자로 돌아갑니다.
사용 조건과 제한
두 횟수가 모두 0이면 마스크와 타이밍 모두 바뀌지 않습니다. 꺼짐 대기가 있으면 최종 마스크는 0이며, 켜짐만 있는 펄스는 0x1E를 남깁니다.
intrinsics.h를 포함하세요. 기본 NMI 핸들러가 실행되어 카운터를 증가시켜야 합니다. NMI가 꺼져 있거나 사용자 핸들러가 카운터를 갱신하지 않으면 대기에서 빠져나오지 못합니다. 시간 제한은 없습니다.
켜짐 구간은 PPUMASK와 소프트웨어 복사본을 0x1E로, 꺼짐 구간은 둘 다 0으로 설정합니다. 이전 마스크는 복원하지 않습니다. 배경·스프라이트·왼쪽 가장자리 표시를 함께 바꾸며 회색조와 색 강조 설정도 덮어씁니다.
렌더링을 켜는 것만으로 흰 화면이 생기지는 않습니다. 밝은 타일과 팔레트 항목, 꺼짐 구간용 어두운 배경색을 준비하세요. 샘플은 흰색으로 채운 타일과 검은 배경색을 사용해 동작 중 전체 화면을 깜박입니다.
대기 횟수는 NMI 카운터의 변화를 셉니다. 진입부터 첫 NMI까지는 한 프레임보다 짧을 수 있습니다. 인터럽트가 아닌 메인 코드에서 호출하세요. 다른 루틴이 렌더링 마스크를 바꾸면 패턴이 깨질 수 있습니다.
저수준 표시 마스크·대기 연산입니다. 로봇 명령의 시작 신호, 장치별 명령 시퀀스, 수신 확인은 생성하지 않습니다. 검증 범위는 KUROSAKI의 PPUMASK 쓰기, NMI 횟수, 픽셀입니다. 실제 로봇의 수신은 시험하지 않았습니다.
사용 예
start=__nmi_ready();
__rob_pulse(3,2); // Three enabled waits, then two disabled waits.
rob_result[1]=(u8)(__nmi_ready()-start);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
실행 후 FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5, A5 BYTE WAITS 48이 표시됩니다. 도식은 0xA5를 나타내며 위쪽은 1,0,1,0, 아래쪽은 0,1,0,1입니다. 칸 하나는 대기 한 번으로, 흰색은 렌더링 켜짐, 검은색은 꺼짐입니다. 1은 흰색 4칸 뒤 검은색 2칸, 0은 흰색 2칸 뒤 검은색 4칸입니다. FINAL MASK 0과 TIMING CHECKS OK는 완료를 나타냅니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\rob_timing.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\rob_timing.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:274
Run on_frames enabled-mask waits followed by off_frames disabled-mask waits; NMI must advance.
kitaqfc/kitaqfc/CodeGenerator.cs:7884
EmitHelperStart("__rob_pulse");
EmitAsm("LDX", Mem(CallArgBase));
string onLoop = NewGeneratedLabel("rob_on_loop");
string offStart = NewGeneratedLabel("rob_off_start");
_assembly.Add(Expr.Make(Tag.Label, onLoop));
EmitAsm("CPX", Imm(0));
EmitAsm("BEQ", Rel(offStart));
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__rob_flash"));
EmitAsm("DEX");
EmitAsm("BNE", Rel(onLoop));
_assembly.Add(Expr.Make(Tag.Label, offStart));
EmitAsm("LDX", Mem(CallArgBase + 1));
string offLoop = NewGeneratedLabel("rob_off_loop");
string pulseDone = NewGeneratedLabel("rob_pulse_done");
_assembly.Add(Expr.Make(Tag.Label, offLoop));
EmitAsm("CPX", Imm(0));
EmitAsm("BEQ", Rel(pulseDone));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__rob_flash"));
EmitAsm("DEX");
EmitAsm("BNE", Rel(offLoop));
_assembly.Add(Expr.Make(Tag.Label, pulseDone));
EmitAsm("RTS");
// Choose a four/two or two/four frame pulse pattern for each current high bit.__rob_send_byte
바이트 하나를 최상위 비트부터 8개 점멸 패턴으로 인코딩합니다. 1은 켜짐 4회·꺼짐 2회, 0은 켜짐 2회·꺼짐 4회입니다. 총 48회의 NMI 대기입니다.
사용 형식
void __rob_send_byte(u8 pattern);매개변수
pattern8비트 패턴 0..255입니다. 0xA5의 순서는 1,0,1,0,0,1,0,1입니다. 채널 ID나 로봇 동작 번호가 아닙니다.
반환값
반환값은 없습니다. 요청한 대기가 완료된 후 호출자로 돌아갑니다.
사용 조건과 제한
마지막 비트도 꺼짐 대기로 끝나므로 PPUMASK와 복사본은 0으로 남습니다. 정확히 8비트를 보내며 시작·종료 표식은 추가하지 않습니다.
intrinsics.h를 포함하세요. 기본 NMI 핸들러가 실행되어 카운터를 증가시켜야 합니다. NMI가 꺼져 있거나 사용자 핸들러가 카운터를 갱신하지 않으면 대기에서 빠져나오지 못합니다. 시간 제한은 없습니다.
켜짐 구간은 PPUMASK와 소프트웨어 복사본을 0x1E로, 꺼짐 구간은 둘 다 0으로 설정합니다. 이전 마스크는 복원하지 않습니다. 배경·스프라이트·왼쪽 가장자리 표시를 함께 바꾸며 회색조와 색 강조 설정도 덮어씁니다.
렌더링을 켜는 것만으로 흰 화면이 생기지는 않습니다. 밝은 타일과 팔레트 항목, 꺼짐 구간용 어두운 배경색을 준비하세요. 샘플은 흰색으로 채운 타일과 검은 배경색을 사용해 동작 중 전체 화면을 깜박입니다.
대기 횟수는 NMI 카운터의 변화를 셉니다. 진입부터 첫 NMI까지는 한 프레임보다 짧을 수 있습니다. 인터럽트가 아닌 메인 코드에서 호출하세요. 다른 루틴이 렌더링 마스크를 바꾸면 패턴이 깨질 수 있습니다.
저수준 표시 마스크·대기 연산입니다. 로봇 명령의 시작 신호, 장치별 명령 시퀀스, 수신 확인은 생성하지 않습니다. 검증 범위는 KUROSAKI의 PPUMASK 쓰기, NMI 횟수, 픽셀입니다. 실제 로봇의 수신은 시험하지 않았습니다.
사용 예
start=__nmi_ready();
__rob_send_byte(0xA5); // 10100101, most-significant bit first; 48 waits.
rob_result[2]=(u8)(__nmi_ready()-start);
rob_result[3]=__ppu_mask_get(); // Zero: the sequence ends with rendering off.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
실행 후 FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5, A5 BYTE WAITS 48이 표시됩니다. 도식은 0xA5를 나타내며 위쪽은 1,0,1,0, 아래쪽은 0,1,0,1입니다. 칸 하나는 대기 한 번으로, 흰색은 렌더링 켜짐, 검은색은 꺼짐입니다. 1은 흰색 4칸 뒤 검은색 2칸, 0은 흰색 2칸 뒤 검은색 4칸입니다. FINAL MASK 0과 TIMING CHECKS OK는 완료를 나타냅니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\rob_timing.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\rob_timing.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:278
Send eight bits MSB first: one uses 4 on/2 off waits; zero uses 2 on/4 off. Requires an advancing runtime NMI counter; 48 waits in total, final PPUMASK=0. This raw pattern does not add a robot command preamble or validate reception.
kitaqfc/kitaqfc/CodeGenerator.cs:7912
EmitHelperStart("__rob_send_byte");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("LDX", Imm(8));
string bitLoop = NewGeneratedLabel("rob_bit_loop");
string bitZero = NewGeneratedLabel("rob_bit_zero");
string bitNext = NewGeneratedLabel("rob_bit_next");
_assembly.Add(Expr.Make(Tag.Label, bitLoop));
// Preserve the remaining payload and outer count on the CPU stack.
// Pulse consumes X; its NMI wait also uses the shared scratch byte.
// ASL selects the next MSB in carry, which TXA/PHA leave unchanged.
EmitAsm("ASL");
EmitAsm("PHA");
EmitAsm("TXA");
EmitAsm("PHA");
EmitAsm("BCC", Rel(bitZero));
EmitAsm("LDA", Imm(4));
EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Imm(2));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("JSR", Abs("__rob_pulse"));
EmitAsm("JMP", Abs(bitNext));
_assembly.Add(Expr.Make(Tag.Label, bitZero));
EmitAsm("LDA", Imm(2));
EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Imm(4));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("JSR", Abs("__rob_pulse"));
_assembly.Add(Expr.Make(Tag.Label, bitNext));
EmitAsm("PLA");
EmitAsm("TAX");
EmitAsm("PLA");
EmitAsm("DEX");
EmitAsm("BNE", Rel(bitLoop));
EmitAsm("RTS");
}__scroll_latch_reset
PPUSTATUS를 읽고 결과를 버려 PPU 스크롤·주소 공유 쓰기 래치를 초기화합니다. VBlank 플래그도 지워집니다. 스크롤 좌표에 0을 쓰거나 저장된 X/Y 값을 바꾸지는 않습니다.
사용 형식
void __scroll_latch_reset(void);반환값
없음(void).
사용 조건과 제한
PPUSTATUS 읽기는 NMI 타이밍과 이후 PPUSCROLL·PPUADDR 쓰기에 영향을 줄 수 있습니다. 다른 루틴이 두 바이트 중 하나만 쓴 지점에 끼워 넣지 마세요. 이 호출들은 VBlank를 기다리거나 대기 중인 전송 큐를 완료하지 않습니다.
사용 예
address_port=0x21; // Leave the shared latch halfway through an address pair.
__scroll_latch_reset();
address_port=0x21;address_port=0x83;data_port=2; // Blue left-half marker.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 프로그램에서는 레지스터 설정, 타일·속성 직접 쓰기, 스크롤, 배경·스프라이트 팔레트의 개별 전송을 배웁니다. (0,16)의 파란 띠는 256×16픽셀입니다. 첫 행은 꽉 찬 8×8타일, 왼쪽 4×8막대, 8행 삼각형을 반복하고 둘째 행은 왼쪽 막대만 나열합니다. (16,48)에는 녹색으로 채운 64×32 사각형, (144,48)에는 삼각형 타일을 가로 8개·세로 4개 배치한 빨간 64×32 영역이 표시됩니다.
y=96에는 x=16, 24, 32에 각각 파란 정사각형·왼쪽 막대·삼각형, x=80에 빨간 왼쪽 막대, x=144에 녹색 삼각형이 표시됩니다. 이들은 배경 타일입니다. 별도 팔레트의 스프라이트는 y=144에 있으며 x=16은 빨간 8×8 정사각형, x=80은 파란 왼쪽 4×8막대, x=144는 녹색 8행 삼각형입니다. 최종 스크롤은 (0,0)입니다.
이미지는 스프라이트 색상을 포함해 16,384픽셀의 모양과 색상을 대조합니다. 별도 31개 상태 검사는 레지스터 교체, 상태·래치 부수 효과, 저장된 스크롤, 0/255바이트 전송, 증가량 1/32, 맵 경계, 크기 0 사각형, 테이블 선택, 전체 속성 바이트와 두 팔레트 대상을 다룹니다. Y=255는 레지스터 쓰기만 확인하며 올바른 스크롤 이미지를 검증한 것은 아닙니다. KUROSAKI의 NROM에서 검증했으며 실기는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:112
Read PPUSTATUS to reset the scroll/address write latch; the read also clears VBlank status.
kitaqfc/kitaqfc/CodeGenerator.cs:4708
case "__scroll_latch_reset":
if (!Need(0)) return true;
EmitAsm("LDA", Mem(0x2002)); return true;
// Store both scroll components, reset the write latch, then emit the X/Y pair.__scroll_set
픽셀 단위 X/Y를 런타임 저장 영역에 기록하고 PPUSTATUS를 읽어 공유 래치를 초기화한 뒤 PPUSCROLL에 X, Y 순으로 씁니다. 스크롤을 직접 갱신하며 카메라 이동 예약, NMI 대기, 기준 네임테이블 선택은 하지 않습니다.
사용 형식
void __scroll_set(u8 x, u8 y);매개변수
x / y오프셋은 부호 없는 한 바이트의 픽셀 값입니다. X는
0..255, 일반적인 Y는0..239를 사용하세요. Y240..255도 그대로 쓰지만 PPU의 특수한 행 동작이 적용되어 일반적인 화면 스크롤이 아닙니다. 기준 네임테이블 비트는 PPUCTRL이 제공합니다. 직접 VRAM 접근 후 렌더링 전에 원하는 스크롤을 복원하세요. 함수가 실행 타이밍을 예약하지는 않습니다.
반환값
없음(void).
사용 조건과 제한
PPUSTATUS 읽기는 NMI 타이밍과 이후 PPUSCROLL·PPUADDR 쓰기에 영향을 줄 수 있습니다. 다른 루틴이 두 바이트 중 하나만 쓴 지점에 끼워 넣지 마세요. 이 호출들은 VBlank를 기다리거나 대기 중인 전송 큐를 완료하지 않습니다.
사용 예
__scroll_set(19,27); // Save both coordinates and write the two-scroll-byte pair.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 프로그램에서는 레지스터 설정, 타일·속성 직접 쓰기, 스크롤, 배경·스프라이트 팔레트의 개별 전송을 배웁니다. (0,16)의 파란 띠는 256×16픽셀입니다. 첫 행은 꽉 찬 8×8타일, 왼쪽 4×8막대, 8행 삼각형을 반복하고 둘째 행은 왼쪽 막대만 나열합니다. (16,48)에는 녹색으로 채운 64×32 사각형, (144,48)에는 삼각형 타일을 가로 8개·세로 4개 배치한 빨간 64×32 영역이 표시됩니다.
y=96에는 x=16, 24, 32에 각각 파란 정사각형·왼쪽 막대·삼각형, x=80에 빨간 왼쪽 막대, x=144에 녹색 삼각형이 표시됩니다. 이들은 배경 타일입니다. 별도 팔레트의 스프라이트는 y=144에 있으며 x=16은 빨간 8×8 정사각형, x=80은 파란 왼쪽 4×8막대, x=144는 녹색 8행 삼각형입니다. 최종 스크롤은 (0,0)입니다.
이미지는 스프라이트 색상을 포함해 16,384픽셀의 모양과 색상을 대조합니다. 별도 31개 상태 검사는 레지스터 교체, 상태·래치 부수 효과, 저장된 스크롤, 0/255바이트 전송, 증가량 1/32, 맵 경계, 크기 0 사각형, 테이블 선택, 전체 속성 바이트와 두 팔레트 대상을 다룹니다. Y=255는 레지스터 쓰기만 확인하며 올바른 스크롤 이미지를 검증한 것은 아닙니다. KUROSAKI의 NROM에서 검증했으며 실기는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:114
Save X/Y in runtime shadows, reset the PPU latch and write both scroll bytes immediately.
kitaqfc/kitaqfc/CodeGenerator.cs:4712
case "__scroll_set":
if (!Need(2)) return true;
Store1(args[0], _runtimeScrollXAddress); Store1(args[1], _runtimeScrollYAddress);
EmitAsm("LDA", Mem(0x2002)); EmitAsm("LDA", Mem(_runtimeScrollXAddress)); EmitAsm("STA", Mem(0x2005)); EmitAsm("LDA", Mem(_runtimeScrollYAddress)); EmitAsm("STA", Mem(0x2005)); return true;
// Changing X rewrites both latch bytes, using the retained Y component.__scroll_x_set
저장된 X를 바꾸고 상태 읽기로 공유 래치를 초기화한 뒤 새 X와 저장된 Y를 PPUSCROLL에 씁니다. Y는 PPU에서 읽는 값이 아니라 소프트웨어 저장값입니다. 레지스터 직접 쓰기는 이 저장값을 갱신하지 않습니다.
사용 형식
void __scroll_x_set(u8 x);매개변수
x오프셋은 부호 없는 한 바이트의 픽셀 값입니다. X는
0..255, 일반적인 Y는0..239를 사용하세요. Y240..255도 그대로 쓰지만 PPU의 특수한 행 동작이 적용되어 일반적인 화면 스크롤이 아닙니다. 기준 네임테이블 비트는 PPUCTRL이 제공합니다. 직접 VRAM 접근 후 렌더링 전에 원하는 스크롤을 복원하세요. 함수가 실행 타이밍을 예약하지는 않습니다.
반환값
없음(void).
사용 조건과 제한
PPUSTATUS 읽기는 NMI 타이밍과 이후 PPUSCROLL·PPUADDR 쓰기에 영향을 줄 수 있습니다. 다른 루틴이 두 바이트 중 하나만 쓴 지점에 끼워 넣지 마세요. 이 호출들은 VBlank를 기다리거나 대기 중인 전송 큐를 완료하지 않습니다.
사용 예
__scroll_x_set(0); // Keeps saved Y=27 while changing X to zero.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 프로그램에서는 레지스터 설정, 타일·속성 직접 쓰기, 스크롤, 배경·스프라이트 팔레트의 개별 전송을 배웁니다. (0,16)의 파란 띠는 256×16픽셀입니다. 첫 행은 꽉 찬 8×8타일, 왼쪽 4×8막대, 8행 삼각형을 반복하고 둘째 행은 왼쪽 막대만 나열합니다. (16,48)에는 녹색으로 채운 64×32 사각형, (144,48)에는 삼각형 타일을 가로 8개·세로 4개 배치한 빨간 64×32 영역이 표시됩니다.
y=96에는 x=16, 24, 32에 각각 파란 정사각형·왼쪽 막대·삼각형, x=80에 빨간 왼쪽 막대, x=144에 녹색 삼각형이 표시됩니다. 이들은 배경 타일입니다. 별도 팔레트의 스프라이트는 y=144에 있으며 x=16은 빨간 8×8 정사각형, x=80은 파란 왼쪽 4×8막대, x=144는 녹색 8행 삼각형입니다. 최종 스크롤은 (0,0)입니다.
이미지는 스프라이트 색상을 포함해 16,384픽셀의 모양과 색상을 대조합니다. 별도 31개 상태 검사는 레지스터 교체, 상태·래치 부수 효과, 저장된 스크롤, 0/255바이트 전송, 증가량 1/32, 맵 경계, 크기 0 사각형, 테이블 선택, 전체 속성 바이트와 두 팔레트 대상을 다룹니다. Y=255는 레지스터 쓰기만 확인하며 올바른 스크롤 이미지를 검증한 것은 아닙니다. KUROSAKI의 NROM에서 검증했으며 실기는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:116
Replace saved X and write an X/Y pair using the retained Y shadow.
kitaqfc/kitaqfc/CodeGenerator.cs:4717
case "__scroll_x_set":
if (!Need(1)) return true;
Store1(args[0], _runtimeScrollXAddress);
EmitAsm("LDA", Mem(0x2002)); EmitAsm("LDA", Mem(_runtimeScrollXAddress)); EmitAsm("STA", Mem(0x2005)); EmitAsm("LDA", Mem(_runtimeScrollYAddress)); EmitAsm("STA", Mem(0x2005)); return true;
// Changing Y rewrites both latch bytes, using the retained X component.__scroll_y_set
저장된 Y를 바꾸고 상태 읽기로 공유 래치를 초기화한 뒤 저장된 X와 새 Y를 PPUSCROLL에 씁니다. Y만 바꿔도 스크롤 두 바이트를 모두 씁니다. X는 런타임 저장값을 사용합니다.
사용 형식
void __scroll_y_set(u8 y);매개변수
y오프셋은 부호 없는 한 바이트의 픽셀 값입니다. X는
0..255, 일반적인 Y는0..239를 사용하세요. Y240..255도 그대로 쓰지만 PPU의 특수한 행 동작이 적용되어 일반적인 화면 스크롤이 아닙니다. 기준 네임테이블 비트는 PPUCTRL이 제공합니다. 직접 VRAM 접근 후 렌더링 전에 원하는 스크롤을 복원하세요. 함수가 실행 타이밍을 예약하지는 않습니다.
반환값
없음(void).
사용 조건과 제한
PPUSTATUS 읽기는 NMI 타이밍과 이후 PPUSCROLL·PPUADDR 쓰기에 영향을 줄 수 있습니다. 다른 루틴이 두 바이트 중 하나만 쓴 지점에 끼워 넣지 마세요. 이 호출들은 VBlank를 기다리거나 대기 중인 전송 큐를 완료하지 않습니다.
사용 예
__scroll_y_set(0); // Keeps saved X=0; the scene is now at the origin.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 프로그램에서는 레지스터 설정, 타일·속성 직접 쓰기, 스크롤, 배경·스프라이트 팔레트의 개별 전송을 배웁니다. (0,16)의 파란 띠는 256×16픽셀입니다. 첫 행은 꽉 찬 8×8타일, 왼쪽 4×8막대, 8행 삼각형을 반복하고 둘째 행은 왼쪽 막대만 나열합니다. (16,48)에는 녹색으로 채운 64×32 사각형, (144,48)에는 삼각형 타일을 가로 8개·세로 4개 배치한 빨간 64×32 영역이 표시됩니다.
y=96에는 x=16, 24, 32에 각각 파란 정사각형·왼쪽 막대·삼각형, x=80에 빨간 왼쪽 막대, x=144에 녹색 삼각형이 표시됩니다. 이들은 배경 타일입니다. 별도 팔레트의 스프라이트는 y=144에 있으며 x=16은 빨간 8×8 정사각형, x=80은 파란 왼쪽 4×8막대, x=144는 녹색 8행 삼각형입니다. 최종 스크롤은 (0,0)입니다.
이미지는 스프라이트 색상을 포함해 16,384픽셀의 모양과 색상을 대조합니다. 별도 31개 상태 검사는 레지스터 교체, 상태·래치 부수 효과, 저장된 스크롤, 0/255바이트 전송, 증가량 1/32, 맵 경계, 크기 0 사각형, 테이블 선택, 전체 속성 바이트와 두 팔레트 대상을 다룹니다. Y=255는 레지스터 쓰기만 확인하며 올바른 스크롤 이미지를 검증한 것은 아닙니다. KUROSAKI의 NROM에서 검증했으며 실기는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:118
Replace saved Y and write an X/Y pair using the retained X shadow.
kitaqfc/kitaqfc/CodeGenerator.cs:4722
case "__scroll_y_set":
if (!Need(1)) return true;
Store1(args[0], _runtimeScrollYAddress);
EmitAsm("LDA", Mem(0x2002)); EmitAsm("LDA", Mem(_runtimeScrollXAddress)); EmitAsm("STA", Mem(0x2005)); EmitAsm("LDA", Mem(_runtimeScrollYAddress)); EmitAsm("STA", Mem(0x2005)); return true;
// Modify only the NMI-enable bit in the PPUCTRL shadow and publish the new register value.__sdot2_q1_7
부호 있는 두 항의 내적을 계산합니다.
사용 형식
u16 __sdot2_q1_7(u16 a0, u16 a1, u8 b0, u8 b1);매개변수
a0−32768~32767을 나타내는 부호 있는 16비트 비트 패턴입니다.
a1−32768~32767을 나타내는 부호 있는 16비트 비트 패턴입니다.
b0−128~127 계수를 나타내는 부호 있는 바이트 비트 패턴입니다.
b1−128~127 계수를 나타내는 부호 있는 바이트 비트 패턴입니다.
반환값
a0*b0+a1*b1의 하위 16비트를 반환합니다.
사용 조건과 제한
FC에서 q8_8/q1_7 이름의 함수는 고정소수점용 오른쪽 시프트 없이 원래 정수 곱을 사용합니다. Q1.7 함수는 대응하는 부호 있는 연산의 별칭입니다. 단위는 호출자가 처리하며, 부호 있는 결과는 u16 비트 패턴으로 반환됩니다.
사용 예
// Compare literal coefficients with values read from RAM.
result[34]=__sdot2_q1_7(-513,769,-64,127);
v0=-513;v1=769;v2=-64;v3=127;
result[35]=__sdot2_q1_7(v0,v1,v2,v3);
// Reverse coefficient signs and verify negative products.
result[36]=__sdot2_q1_7(-513,769,64,-127);
v0=-513;v1=769;v2=64;v3=-127;
result[37]=__sdot2_q1_7(v0,v1,v2,v3);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
리터럴 입력과 RAM에 저장된 값을 비교합니다. 대표 결과는 부호 없는 16비트 값으로 표시하며 음수는 2의 보수 비트 패턴을 사용합니다. GB는 고정소수점 곱의 크기를 조정하지만 FC는 정수 곱을 반환하므로 같은 입력이라도 결과가 같지 않을 수 있습니다.
이 발췌 코드는 다음 16비트 값을 저장합니다: result[34]=64959, result[35]=64959, result[36]=577, result[37]=577. 리터럴 입력으로 계산한 각 결과는 대응하는 RAM 입력 결과와 같습니다. 화면에는 대표값을 표시하고, result 배열에는 모든 결과를 보관합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr구현과 소스
kitaqfc/lib/intrinsics.h:71
FC alias of __sdot2_q8_8: two raw signed-pattern products, summed without rescaling.
kitaqfc/kitaqfc/CodeGenerator.cs:4658
case "__sdot2_q1_7": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2,1,1}, funcName);
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__sdot2_q8_8
부호 있는 두 항의 내적을 계산합니다.
사용 형식
u16 __sdot2_q8_8(u16 a0, u16 a1, u8 b0, u8 b1);매개변수
a0−32768~32767을 나타내는 부호 있는 16비트 비트 패턴입니다.
a1−32768~32767을 나타내는 부호 있는 16비트 비트 패턴입니다.
b0−128~127 계수를 나타내는 부호 있는 바이트 비트 패턴입니다.
b1−128~127 계수를 나타내는 부호 있는 바이트 비트 패턴입니다.
반환값
a0*b0+a1*b1의 하위 16비트를 반환합니다.
사용 조건과 제한
FC에서 q8_8/q1_7 이름의 함수는 고정소수점용 오른쪽 시프트 없이 원래 정수 곱을 사용합니다. Q1.7 함수는 대응하는 부호 있는 연산의 별칭입니다. 단위는 호출자가 처리하며, 부호 있는 결과는 u16 비트 패턴으로 반환됩니다.
사용 예
// Compare literal coefficients with values read from RAM.
result[26]=__sdot2_q8_8(-513,769,-64,127);
v0=-513;v1=769;v2=-64;v3=127;
result[27]=__sdot2_q8_8(v0,v1,v2,v3);
// Reverse coefficient signs and verify negative products.
result[28]=__sdot2_q8_8(-513,769,64,-127);
v0=-513;v1=769;v2=64;v3=-127;
result[29]=__sdot2_q8_8(v0,v1,v2,v3);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
리터럴 입력과 RAM에 저장된 값을 비교합니다. 대표 결과는 부호 없는 16비트 값으로 표시하며 음수는 2의 보수 비트 패턴을 사용합니다. GB는 고정소수점 곱의 크기를 조정하지만 FC는 정수 곱을 반환하므로 같은 입력이라도 결과가 같지 않을 수 있습니다.
이 발췌 코드는 다음 16비트 값을 저장합니다: result[26]=64959, result[27]=64959, result[28]=577, result[29]=577. 리터럴 입력으로 계산한 각 결과는 대응하는 RAM 입력 결과와 같습니다. 화면에는 대표값을 표시하고, result 배열에는 모든 결과를 보관합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr구현과 소스
kitaqfc/lib/intrinsics.h:67
Sum two signed-pattern products modulo 65536; no Q8.8 scaling shift is generated.
kitaqfc/kitaqfc/CodeGenerator.cs:4656
case "__sdot2_q8_8": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2,1,1}, funcName);
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__sdot3_q1_7
부호 있는 세 항의 내적을 계산합니다.
사용 형식
u16 __sdot3_q1_7(u16 a0, u16 a1, u16 a2, u8 b0, u8 b1, u8 b2);매개변수
a0−32768~32767을 나타내는 부호 있는 16비트 비트 패턴입니다.
a1−32768~32767을 나타내는 부호 있는 16비트 비트 패턴입니다.
a2−32768~32767을 나타내는 부호 있는 16비트 비트 패턴입니다.
b0−128~127 계수를 나타내는 부호 있는 바이트 비트 패턴입니다.
b1−128~127 계수를 나타내는 부호 있는 바이트 비트 패턴입니다.
b2−128~127 계수를 나타내는 부호 있는 바이트 비트 패턴입니다.
반환값
a0*b0+a1*b1+a2*b2의 하위 16비트를 반환합니다.
사용 조건과 제한
FC에서 q8_8/q1_7 이름의 함수는 고정소수점용 오른쪽 시프트 없이 원래 정수 곱을 사용합니다. Q1.7 함수는 대응하는 부호 있는 연산의 별칭입니다. 단위는 호출자가 처리하며, 부호 있는 결과는 u16 비트 패턴으로 반환됩니다.
사용 예
// Compare literal coefficients with values read from RAM.
result[38]=__sdot3_q1_7(-513,769,-1025,-64,127,-3);
v0=-513;v1=769;v2=-1025;v3=-64;v4=127;v5=-3;
result[39]=__sdot3_q1_7(v0,v1,v2,v3,v4,v5);
// Reverse coefficient signs and verify negative products.
result[40]=__sdot3_q1_7(-513,769,-1025,64,-127,3);
v0=-513;v1=769;v2=-1025;v3=64;v4=-127;v5=3;
result[41]=__sdot3_q1_7(v0,v1,v2,v3,v4,v5);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
리터럴 입력과 RAM에 저장된 값을 비교합니다. 대표 결과는 부호 없는 16비트 값으로 표시하며 음수는 2의 보수 비트 패턴을 사용합니다. GB는 고정소수점 곱의 크기를 조정하지만 FC는 정수 곱을 반환하므로 같은 입력이라도 결과가 같지 않을 수 있습니다.
이 발췌 코드는 다음 16비트 값을 저장합니다: result[38]=2498, result[39]=2498, result[40]=63038, result[41]=63038. 리터럴 입력으로 계산한 각 결과는 대응하는 RAM 입력 결과와 같습니다. 화면에는 대표값을 표시하고, result 배열에는 모든 결과를 보관합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr구현과 소스
kitaqfc/lib/intrinsics.h:73
FC alias of __sdot3_q8_8: three raw signed-pattern products, summed without rescaling.
kitaqfc/kitaqfc/CodeGenerator.cs:4659
case "__sdot3_q1_7": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2,2,1,1,1}, funcName);
// Pass a word seed to the RNG helper; the zero-argument generator is called directly below.
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__sdot3_q8_8
부호 있는 세 항의 내적을 계산합니다.
사용 형식
u16 __sdot3_q8_8(u16 a0, u16 a1, u16 a2, u8 b0, u8 b1, u8 b2);매개변수
a0−32768~32767을 나타내는 부호 있는 16비트 비트 패턴입니다.
a1−32768~32767을 나타내는 부호 있는 16비트 비트 패턴입니다.
a2−32768~32767을 나타내는 부호 있는 16비트 비트 패턴입니다.
b0−128~127 계수를 나타내는 부호 있는 바이트 비트 패턴입니다.
b1−128~127 계수를 나타내는 부호 있는 바이트 비트 패턴입니다.
b2−128~127 계수를 나타내는 부호 있는 바이트 비트 패턴입니다.
반환값
a0*b0+a1*b1+a2*b2의 하위 16비트를 반환합니다.
사용 조건과 제한
FC에서 q8_8/q1_7 이름의 함수는 고정소수점용 오른쪽 시프트 없이 원래 정수 곱을 사용합니다. Q1.7 함수는 대응하는 부호 있는 연산의 별칭입니다. 단위는 호출자가 처리하며, 부호 있는 결과는 u16 비트 패턴으로 반환됩니다.
사용 예
// Compare literal coefficients with values read from RAM.
result[30]=__sdot3_q8_8(-513,769,-1025,-64,127,-3);
v0=-513;v1=769;v2=-1025;v3=-64;v4=127;v5=-3;
result[31]=__sdot3_q8_8(v0,v1,v2,v3,v4,v5);
// Reverse coefficient signs and verify negative products.
result[32]=__sdot3_q8_8(-513,769,-1025,64,-127,3);
v0=-513;v1=769;v2=-1025;v3=64;v4=-127;v5=3;
result[33]=__sdot3_q8_8(v0,v1,v2,v3,v4,v5);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
리터럴 입력과 RAM에 저장된 값을 비교합니다. 대표 결과는 부호 없는 16비트 값으로 표시하며 음수는 2의 보수 비트 패턴을 사용합니다. GB는 고정소수점 곱의 크기를 조정하지만 FC는 정수 곱을 반환하므로 같은 입력이라도 결과가 같지 않을 수 있습니다.
이 발췌 코드는 다음 16비트 값을 저장합니다: result[30]=2498, result[31]=2498, result[32]=63038, result[33]=63038. 리터럴 입력으로 계산한 각 결과는 대응하는 RAM 입력 결과와 같습니다. 화면에는 대표값을 표시하고, result 배열에는 모든 결과를 보관합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr구현과 소스
kitaqfc/lib/intrinsics.h:69
Sum three signed-pattern products modulo 65536; choose operand units explicitly in the caller.
kitaqfc/kitaqfc/CodeGenerator.cs:4657
case "__sdot3_q8_8": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2,2,1,1,1}, funcName);
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__serial_rx_bit
외부 어댑터가 제공하는 수신선으로 $4017의 비트 4를 한 번 샘플링합니다.
사용 형식
u8 __serial_rx_bit(void);반환값
비트 4가 1이면 1, 그렇지 않으면 0입니다. 데이터 준비 여부나 통신 오류를 나타내는 상태값은 아닙니다.
사용 조건과 제한
이 API는 $4016/$4017에 직접 접근합니다. 같은 포트에서 인터럽트 기반 게임패드·키보드·시리얼 연산을 끼워 넣지 마세요. 컨트롤러의 시프트 위치도 영향을 받을 수 있습니다. 예제는 입력을 캡처하는 동안 NMI를 끄고 화면 초기화 때 다시 켭니다.
이 저수준 함수는 OUT0을 TX에, $4017 D4를 RX에 연결하는 외부 어댑터를 전제로 합니다. 비트 간격, 보율 클록, 시작·정지 비트, 바이트 조립, 수신 대기 기능은 제공하지 않습니다. 바이트 단위로 동작하는 __midi_out() 같은 보조 함수와는 용도가 다릅니다.
호출자가 적절한 시점에 샘플링해야 합니다. 데이터를 기다리거나 여러 비트를 조립하지 않으며, $4016 출력도 변경하지 않습니다.
레지스터 읽기를 RAM의 시험용 값으로 돌리는 자체 테스트 ROM에서도 비트 변환을 단위 테스트합니다. 관련 없는 비트가 설정된 상태에서 00/08/10/18 조합을 확인합니다. 이 테스트는 수정하지 않은 샘플 실행이나 실제 주변기기의 동작 검증과는 별개입니다.
이 화면은 다운로드 가능한 ROM을 수정 없이 KUROSAKI에서 실행한 것입니다. 현재 입력 버스는 게임패드만 모델링하며 키보드·광선총·시리얼 어댑터 신호는 모델링하지 않습니다. 외부 입력 비트가 0으로 읽히므로 이 캡처로 해당 장치를 검증할 수 없습니다. 실제 키 입력, 광 감지, 통신은 미검증입니다.
사용 예
#include "fc_ports_scene.h"
__prg_rom u8 tx_values[6]={0,1,2,3,254,255};
void main(void){
u8 i;
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
for(i=0;i<6;i++)__serial_tx_bit(tx_values[i]);
__serial_tx_bit(1); // Leave OUT0 high; no byte framing or baud clock is added.
port_result[0]=__serial_rx_bit();port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"SERIAL BIT PRIMITIVES");m_wait();
m_text(2,5,"VALUE EXPECTED OUT0");m_wait();
for(i=0;i<6;i++){port_number(3,7+i*2,tx_values[i]);m_put(23,7+i*2,'0'+(tx_values[i]&1));m_wait();}
m_text(2,20,"RX SAMPLE");port_number(23,20,port_result[0]);m_wait();
m_text(2,23,"NO BYTE FRAMING OR CLOCK");m_wait();
while(1){m_wait();}
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
VALUE가 000/001/002/003/254/255일 때 EXPECTED OUT0은 0/1/0/1/0/1입니다. 이 열은 최하위 비트의 대응 관계를 설명하며, 실제 전기 신호를 다시 읽은 값은 아닙니다. CPU 추적으로 이 순서의 쓰기와 마지막 1 쓰기를 확인했습니다. KUROSAKI는 수신선을 구현하지 않으므로 RX SAMPLE은 000입니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_serial.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_serial.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_serial.nes --frames 120 --png .\out\peripheral_serial.png구현과 소스
kitaqfc/lib/intrinsics.h:288
Read 4017 bit 4 and normalize it to 0 or 1; an external serial adapter supplies the signal.
kitaqfc/kitaqfc/CodeGenerator.cs:7965
EmitHelperStart("__serial_rx_bit");
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
string rx0 = NewGeneratedLabel("serial_rx0");
string rxd = NewGeneratedLabel("serial_rxdone");
EmitAsm("BEQ", Rel(rx0));
EmitAsm("LDA", Imm(1));
EmitAsm("JMP", Abs(rxd));
_assembly.Add(Expr.Make(Tag.Label, rx0));
EmitAsm("LDA", Imm(0));
_assembly.Add(Expr.Make(Tag.Label, rxd));
EmitAsm("RTS");__serial_tx_bit
인수의 최하위 비트를 $4016을 통해 OUT0에 씁니다. 외부 직렬 어댑터에 1비트를 출력하는 기능입니다.
사용 형식
void __serial_tx_bit(u8 bit);매개변수
bit출력할 값입니다.
bit & 1만 사용하므로 2는 0, 255는 1을 출력합니다.
반환값
반환값은 없습니다.
사용 조건과 제한
이 API는 $4016/$4017에 직접 접근합니다. 같은 포트에서 인터럽트 기반 게임패드·키보드·시리얼 연산을 끼워 넣지 마세요. 컨트롤러의 시프트 위치도 영향을 받을 수 있습니다. 예제는 입력을 캡처하는 동안 NMI를 끄고 화면 초기화 때 다시 켭니다.
이 저수준 함수는 OUT0을 TX에, $4017 D4를 RX에 연결하는 외부 어댑터를 전제로 합니다. 비트 간격, 보율 클록, 시작·정지 비트, 바이트 조립, 수신 대기 기능은 제공하지 않습니다. 바이트 단위로 동작하는 __midi_out() 같은 보조 함수와는 용도가 다릅니다.
$4016 전체에 0 또는 1을 써서 OUT1/OUT2를 0으로 만듭니다. 신호 유지용 지연은 넣지 않습니다. OUT0은 일반 게임패드의 래치를 제어하므로 게임패드 읽기 상태도 바뀝니다.
이 화면은 다운로드 가능한 ROM을 수정 없이 KUROSAKI에서 실행한 것입니다. 현재 입력 버스는 게임패드만 모델링하며 키보드·광선총·시리얼 어댑터 신호는 모델링하지 않습니다. 외부 입력 비트가 0으로 읽히므로 이 캡처로 해당 장치를 검증할 수 없습니다. 실제 키 입력, 광 감지, 통신은 미검증입니다.
사용 예
#include "fc_ports_scene.h"
__prg_rom u8 tx_values[6]={0,1,2,3,254,255};
void main(void){
u8 i;
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
for(i=0;i<6;i++)__serial_tx_bit(tx_values[i]);
__serial_tx_bit(1); // Leave OUT0 high; no byte framing or baud clock is added.
port_result[0]=__serial_rx_bit();port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"SERIAL BIT PRIMITIVES");m_wait();
m_text(2,5,"VALUE EXPECTED OUT0");m_wait();
for(i=0;i<6;i++){port_number(3,7+i*2,tx_values[i]);m_put(23,7+i*2,'0'+(tx_values[i]&1));m_wait();}
m_text(2,20,"RX SAMPLE");port_number(23,20,port_result[0]);m_wait();
m_text(2,23,"NO BYTE FRAMING OR CLOCK");m_wait();
while(1){m_wait();}
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
VALUE가 000/001/002/003/254/255일 때 EXPECTED OUT0은 0/1/0/1/0/1입니다. 이 열은 최하위 비트의 대응 관계를 설명하며, 실제 전기 신호를 다시 읽은 값은 아닙니다. CPU 추적으로 이 순서의 쓰기와 마지막 1 쓰기를 확인했습니다. KUROSAKI는 수신선을 구현하지 않으므로 RX SAMPLE은 000입니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_serial.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_serial.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_serial.nes --frames 120 --png .\out\peripheral_serial.png구현과 소스
kitaqfc/lib/intrinsics.h:286
Expansion-port TTL serial / MIDI adapter support. The low-level serial layer assumes an external adapter that maps OUT0 to TX and D4 on $4017 to RX. MIDI helpers bit-bang 8N1 bytes at an approximate 31250 bps timing suitable for adapter bring-up and simple music sync. Write bit & 1 to 4016, replacing the port output value; coordinate with other port users.
kitaqfc/kitaqfc/CodeGenerator.cs:7958
EmitHelperStart("__serial_tx_bit");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("AND", Imm(1));
EmitAsm("STA", Mem(0x4016));
EmitAsm("RTS");__smac16
부호 있는 곱셈 누산을 수행합니다.
사용 형식
u16 __smac16(u16 acc, u16 a, u8 b);매개변수
acc더할 16비트 누산값입니다.
a−32768~32767을 나타내는 부호 있는 16비트 비트 패턴입니다.
b−128~127 계수를 나타내는 부호 있는 바이트 비트 패턴입니다.
반환값
acc+a*b의 하위 16비트를 반환합니다.
사용 조건과 제한
FC에서 q8_8/q1_7 이름의 함수는 고정소수점용 오른쪽 시프트 없이 원래 정수 곱을 사용합니다. Q1.7 함수는 대응하는 부호 있는 연산의 별칭입니다. 단위는 호출자가 처리하며, 부호 있는 결과는 u16 비트 패턴으로 반환됩니다.
사용 예
// Compare literal coefficients with values read from RAM.
result[10]=__smac16(65500,-513,-64);
v0=65500;v1=-513;v2=-64;
result[11]=__smac16(v0,v1,v2);
// Reverse coefficient signs and verify negative products.
result[12]=__smac16(65500,-513,64);
v0=65500;v1=-513;v2=64;
result[13]=__smac16(v0,v1,v2);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
리터럴 입력과 RAM에 저장된 값을 비교합니다. 대표 결과는 부호 없는 16비트 값으로 표시하며 음수는 2의 보수 비트 패턴을 사용합니다. GB는 고정소수점 곱의 크기를 조정하지만 FC는 정수 곱을 반환하므로 같은 입력이라도 결과가 같지 않을 수 있습니다.
이 발췌 코드는 다음 16비트 값을 저장합니다: result[10]=32796, result[11]=32796, result[12]=32668, result[13]=32668. 리터럴 입력으로 계산한 각 결과는 대응하는 RAM 입력 결과와 같습니다. 화면에는 대표값을 표시하고, result 배열에는 모든 결과를 보관합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr구현과 소스
kitaqfc/lib/intrinsics.h:57
Add the signed-pattern product to acc modulo 65536; the API carries the result as u16 bits.
kitaqfc/kitaqfc/CodeGenerator.cs:4651
case "__smac16": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2,1}, funcName);
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__smac16_q1_7
부호 있는 곱셈 누산을 수행합니다.
사용 형식
u16 __smac16_q1_7(u16 acc, u16 a, u8 b);매개변수
acc더할 16비트 누산값입니다.
a−32768~32767을 나타내는 부호 있는 16비트 비트 패턴입니다.
b−128~127 계수를 나타내는 부호 있는 바이트 비트 패턴입니다.
반환값
acc+a*b의 하위 16비트를 반환합니다.
사용 조건과 제한
FC에서 q8_8/q1_7 이름의 함수는 고정소수점용 오른쪽 시프트 없이 원래 정수 곱을 사용합니다. Q1.7 함수는 대응하는 부호 있는 연산의 별칭입니다. 단위는 호출자가 처리하며, 부호 있는 결과는 u16 비트 패턴으로 반환됩니다.
사용 예
// Compare literal coefficients with values read from RAM.
result[18]=__smac16_q1_7(65500,-513,-64);
v0=65500;v1=-513;v2=-64;
result[19]=__smac16_q1_7(v0,v1,v2);
// Reverse coefficient signs and verify negative products.
result[20]=__smac16_q1_7(65500,-513,64);
v0=65500;v1=-513;v2=64;
result[21]=__smac16_q1_7(v0,v1,v2);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
리터럴 입력과 RAM에 저장된 값을 비교합니다. 대표 결과는 부호 없는 16비트 값으로 표시하며 음수는 2의 보수 비트 패턴을 사용합니다. GB는 고정소수점 곱의 크기를 조정하지만 FC는 정수 곱을 반환하므로 같은 입력이라도 결과가 같지 않을 수 있습니다.
이 발췌 코드는 다음 16비트 값을 저장합니다: result[18]=32796, result[19]=32796, result[20]=32668, result[21]=32668. 리터럴 입력으로 계산한 각 결과는 대응하는 RAM 입력 결과와 같습니다. 화면에는 대표값을 표시하고, result 배열에는 모든 결과를 보관합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr구현과 소스
kitaqfc/lib/intrinsics.h:61
FC alias of __smac16: accumulate a raw signed-pattern product modulo 65536.
kitaqfc/kitaqfc/CodeGenerator.cs:4653
case "__smac16_q1_7": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2,1}, funcName);
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__smul16x8
부호 있는 곱셈을 수행합니다.
사용 형식
u16 __smul16x8(u16 a, u8 b);매개변수
a−32768~32767을 나타내는 부호 있는 16비트 비트 패턴입니다.
b−128~127 계수를 나타내는 부호 있는 바이트 비트 패턴입니다.
반환값
a*b의 하위 16비트를 반환합니다.
사용 조건과 제한
FC에서 q8_8/q1_7 이름의 함수는 고정소수점용 오른쪽 시프트 없이 원래 정수 곱을 사용합니다. Q1.7 함수는 대응하는 부호 있는 연산의 별칭입니다. 단위는 호출자가 처리하며, 부호 있는 결과는 u16 비트 패턴으로 반환됩니다.
사용 예
// Compare literal coefficients with values read from RAM.
result[4]=__smul16x8(-513,-64);
v0=-513;v1=-64;
result[5]=__smul16x8(v0,v1);
// Reverse coefficient signs and verify negative products.
result[6]=__smul16x8(-513,64);
v0=-513;v1=64;
result[7]=__smul16x8(v0,v1);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
리터럴 입력과 RAM에 저장된 값을 비교합니다. 대표 결과는 부호 없는 16비트 값으로 표시하며 음수는 2의 보수 비트 패턴을 사용합니다. GB는 고정소수점 곱의 크기를 조정하지만 FC는 정수 곱을 반환하므로 같은 입력이라도 결과가 같지 않을 수 있습니다.
이 발췌 코드는 다음 16비트 값을 저장합니다: result[4]=32832, result[5]=32832, result[6]=32704, result[7]=32704. 리터럴 입력으로 계산한 각 결과는 대응하는 RAM 입력 결과와 같습니다. 화면에는 대표값을 표시하고, result 배열에는 모든 결과를 보관합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr구현과 소스
kitaqfc/lib/intrinsics.h:53
Interpret a and b as signed two's-complement bit patterns and return the low word of their product.
kitaqfc/kitaqfc/CodeGenerator.cs:4648
case "__smul16x8": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,1}, funcName);
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__smul16x8_q1_7
부호 있는 곱셈을 수행합니다.
사용 형식
u16 __smul16x8_q1_7(u16 a, u8 b);매개변수
a−32768~32767을 나타내는 부호 있는 16비트 비트 패턴입니다.
b−128~127 계수를 나타내는 부호 있는 바이트 비트 패턴입니다.
반환값
a*b의 하위 16비트를 반환합니다.
사용 조건과 제한
FC에서 q8_8/q1_7 이름의 함수는 고정소수점용 오른쪽 시프트 없이 원래 정수 곱을 사용합니다. Q1.7 함수는 대응하는 부호 있는 연산의 별칭입니다. 단위는 호출자가 처리하며, 부호 있는 결과는 u16 비트 패턴으로 반환됩니다.
사용 예
// Compare literal coefficients with values read from RAM.
result[14]=__smul16x8_q1_7(-513,-64);
v0=-513;v1=-64;
result[15]=__smul16x8_q1_7(v0,v1);
// Reverse coefficient signs and verify negative products.
result[16]=__smul16x8_q1_7(-513,64);
v0=-513;v1=64;
result[17]=__smul16x8_q1_7(v0,v1);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
리터럴 입력과 RAM에 저장된 값을 비교합니다. 대표 결과는 부호 없는 16비트 값으로 표시하며 음수는 2의 보수 비트 패턴을 사용합니다. GB는 고정소수점 곱의 크기를 조정하지만 FC는 정수 곱을 반환하므로 같은 입력이라도 결과가 같지 않을 수 있습니다.
이 발췌 코드는 다음 16비트 값을 저장합니다: result[14]=32832, result[15]=32832, result[16]=32704, result[17]=32704. 리터럴 입력으로 계산한 각 결과는 대응하는 RAM 입력 결과와 같습니다. 화면에는 대표값을 표시하고, result 배열에는 모든 결과를 보관합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr구현과 소스
kitaqfc/lib/intrinsics.h:59
FC alias of __smul16x8: signed-pattern raw product, with no Q1.7 scaling shift.
kitaqfc/kitaqfc/CodeGenerator.cs:4652
case "__smul16x8_q1_7": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,1}, funcName);
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__split_scroll_sprite0
새 스프라이트 0 히트를 기다린 뒤 수평 분할용 스크롤 X/Y를 씁니다. 위쪽 정렬은 유지하고 아래 배경을 옮깁니다.
사용 형식
void __split_scroll_sprite0(u8 x, u8 y);매개변수
x수평 스크롤값 0..255이며 오브젝트의 화면 X 좌표가 아닙니다. 샘플은 32를 사용해 같은 배경의 아래쪽을 왼쪽으로 32픽셀 옮깁니다.
y임시 수직 스크롤 설정 0..255이며 일반 가시 범위는 0..239입니다. $2005를 쓰므로 렌더링 중 실제 수직 위치가 즉시 바뀌지는 않습니다. 수평 분할 샘플은 0을 전달합니다.
반환값
반환값은 없습니다.
사용 조건과 제한
두 레이어를 켜고 OAM 스프라이트 0의 불투명 픽셀을 불투명 배경 픽셀과 겹치게 하세요. 배경 뒤 우선순위 플래그가 있어도 히트는 가능합니다. 맨 왼쪽 8픽셀에서는 각 레이어의 해당 표시 허용이 필요하며 X=255의 겹침은 히트를 발생시키지 못합니다.
PPUSTATUS($2002)의 비트 6이 0이 될 때까지, 이어 1이 될 때까지 폴링합니다. 호출 시 이미 히트가 발생했다면 다음 프레임의 새 히트를 기다립니다. 플래그 읽기는 확인 처리가 아니며 PPU가 프리렌더 구간에서 지웁니다.
시간 제한이나 실패 반환값이 없습니다. 숨겨진 스프라이트 0, 투명 배경, 렌더링 비활성화 등 겹침이 없는 설정에서는 대기가 끝나지 않습니다. 호출 전에 히트를 보장하세요. CPU IRQ 활성화는 필요 없으며 NMI도 켜지 않습니다. 샘플은 별도 프레임 대기 때문에 NMI를 켭니다.
$2002 읽기는 공유 PPU 주소/스크롤 쓰기 래치와 VBlank 플래그에도 영향을 줍니다. 두 번 쓰는 $2005/$2006 연산 사이에 이 호출을 넣지 마세요. NMI/IRQ 핸들러가 아닌 메인 코드에서 사용하세요.
히트 후 $2002를 읽고 $2005에 X, Y 순으로 씁니다. 미세 X는 즉시, 거친 X는 다음 수평 재로드에서 적용됩니다. Y는 다른 스크롤 쓰기로 먼저 대체되지 않으면 보통 다음 프리렌더 재로드에서 적용됩니다. 임의 X/Y로 즉시 래스터 위치를 옮기는 연산은 아닙니다.
폴링과 명령 실행 때문에 쓰기에 지연이 생깁니다. KUROSAKI는 주사선 단위로 렌더링하므로 캡처가 실기의 도트 단위 경계를 입증하지는 않습니다. 샘플은 Y=96..103의 흰 띠 안에서 변경해 경계 불확실성이 파란·빨간 막대에 닿지 않게 합니다.
공유 헤더 fc_sprite0_scene.h는 독자적인 파란·빨간 막대와 흰 수평 띠를 준비합니다. 스프라이트 0은 화면 (16,96)에서 배경 뒤에 숨습니다. 표시 Y는 OAM Y+1이므로 원시 OAM Y는 95입니다. 매 프레임 VBlank에서 위쪽 스크롤을 0으로 복원한 뒤 히트를 기다립니다.
사용 예
void main(void) {
s0_prepare();
while (1) {
s0_frame();
__split_scroll_sprite0(32,0);
s0_completed++;
}
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
흰 띠 위에서는 파랑 X=64..95, 빨강 X=160..191입니다. 아래쪽 Y=104부터는 파랑 X=32..63, 빨강 X=128..159로 옮겨져 아래 영역만 왼쪽으로 32픽셀 이동합니다. 수평 띠는 Y=96..103이며 막대는 Y=207에서 끝납니다. 검증은 프레임당 완료 한 번과 모든 이미지 픽셀의 위치·색을 검사합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sprite0_split.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\sprite0_split.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\sprite0_scene.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:336
Wait for a new sprite-zero hit, reset the PPU latch and write X/Y scroll; requires a visible hit setup.
__sprite0_wait_hit
새 스프라이트 0/배경 겹침을 기다려 스크롤값을 바꾸지 않고 메인 작업을 알려진 화면 영역에 동기화합니다.
사용 형식
void __sprite0_wait_hit(void);반환값
반환값은 없습니다.
사용 조건과 제한
두 레이어를 켜고 OAM 스프라이트 0의 불투명 픽셀을 불투명 배경 픽셀과 겹치게 하세요. 배경 뒤 우선순위 플래그가 있어도 히트는 가능합니다. 맨 왼쪽 8픽셀에서는 각 레이어의 해당 표시 허용이 필요하며 X=255의 겹침은 히트를 발생시키지 못합니다.
PPUSTATUS($2002)의 비트 6이 0이 될 때까지, 이어 1이 될 때까지 폴링합니다. 호출 시 이미 히트가 발생했다면 다음 프레임의 새 히트를 기다립니다. 플래그 읽기는 확인 처리가 아니며 PPU가 프리렌더 구간에서 지웁니다.
시간 제한이나 실패 반환값이 없습니다. 숨겨진 스프라이트 0, 투명 배경, 렌더링 비활성화 등 겹침이 없는 설정에서는 대기가 끝나지 않습니다. 호출 전에 히트를 보장하세요. CPU IRQ 활성화는 필요 없으며 NMI도 켜지 않습니다. 샘플은 별도 프레임 대기 때문에 NMI를 켭니다.
$2002 읽기는 공유 PPU 주소/스크롤 쓰기 래치와 VBlank 플래그에도 영향을 줍니다. 두 번 쓰는 $2005/$2006 연산 사이에 이 호출을 넣지 마세요. NMI/IRQ 핸들러가 아닌 메인 코드에서 사용하세요.
폴링과 명령 실행 때문에 쓰기에 지연이 생깁니다. KUROSAKI는 주사선 단위로 렌더링하므로 캡처가 실기의 도트 단위 경계를 입증하지는 않습니다. 샘플은 Y=96..103의 흰 띠 안에서 변경해 경계 불확실성이 파란·빨간 막대에 닿지 않게 합니다.
공유 헤더 fc_sprite0_scene.h는 독자적인 파란·빨간 막대와 흰 수평 띠를 준비합니다. 스프라이트 0은 화면 (16,96)에서 배경 뒤에 숨습니다. 표시 Y는 OAM Y+1이므로 원시 OAM Y는 95입니다. 매 프레임 VBlank에서 위쪽 스크롤을 0으로 복원한 뒤 히트를 기다립니다.
사용 예
void main(void) {
s0_prepare();
while (1) {
s0_frame();
__sprite0_wait_hit();
s0_completed++;
}
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
파란 막대는 X=64..95, 빨간 막대는 X=160..191이며 흰 띠 위아래로 정렬됩니다. 대기만으로는 스크롤이 바뀌지 않습니다. 실행한 예제는 120프레임 후 대기 116회, 121프레임 후 117회를 완료해 계속 진행함을 확인합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sprite0_wait.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\sprite0_wait.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\sprite0_scene.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:334
Wait for the current hit flag to clear, then for a new hit. No timeout is provided.
__sprite_attr
섀도 스프라이트 하나의 속성 바이트 전체를 바꿉니다. 타일이나 위치를 변경하지 않고 팔레트, 배경 우선순위, 좌우 반전, 상하 반전을 설정할 수 있습니다.
사용 형식
void __sprite_attr(u8 index, u8 attr);매개변수
index스프라이트 항목 번호 0–63입니다. 바이트 오프셋이 아닙니다. 내부에서 8비트 커서로 4를 곱하므로 범위를 벗어나면 64를 주기로 순환해 다른 항목을 덮어쓸 수 있습니다. 할당 여부는 검사하지 않습니다.
attr속성 바이트 전체입니다. 비트 0–1은 스프라이트 팔레트 0–3, 비트 5는 불투명 배경 뒤에 표시, 비트 6과 7은 좌우 및 상하 반전을 지정합니다. 비트 2–4는 0으로 두세요. 플래그를 비트 OR로 조합하며 전달한 바이트가 기존 속성 전체를 대체합니다.
반환값
없음(void).
사용 조건과 제한
이 작업은 CPU RAM의 0x0200–0x02FF를 수정하며 하드웨어 OAM에는 즉시 반영되지 않습니다. 페이지를 완성한 뒤 __oam_dma로 전송하세요. 스프라이트 라이브러리의 할당 플래그, 좌표 배열, 사용 개수는 갱신하지 않습니다. 준비 중인 페이지를 인터럽트 처리에서 수정하거나 전송하지 않도록 하세요.
사용 예
__sprite_attr(3,0xC0); // Flip the arrow horizontally and vertically.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
작업마다 이름을 붙인 행으로 구분한 예제입니다. SET과 MOVE는 (144,16), (144,32)의 8×8 오른쪽 화살표, TILE은 (144,48)의 초록 정사각형, ATTR은 (144,64)의 상하좌우 반전 화살표입니다. HIDE의 (144,80)과 CLEAR의 (144,112)는 비어 있어야 합니다. META는 (136,96), (144,96)의 두 화살표이며 두 번째는 좌우 반전입니다. NEXT 07은 5번과 6번에 기록한 뒤의 다음 위치입니다. 화살표 테두리는 빨강, 몸통은 초록, 끝부분의 일부는 파랑입니다. 이미지당 8,704픽셀과 전송된 OAM 바이트를 대조하며 실제 기기에서는 검증하지 않았습니다.
TRANSFER의 (144,128)에 화살표를 표시합니다. 이 프로그램은 각 스프라이트 작업으로 준비한 모든 필드가 들어 있는 페이지 02를 __oam_dma로 직접 전송합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\oam_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\sprite_example.chr -o .\out\oam_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:198
Replace the selected shadow attribute byte, including palette, priority and flip bits.
EmitHelperStart("__sprite_attr");
EmitSpriteIndexToX();
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("STA", new AsmOperand(OamRamBase + 2, AddressMode.AbsoluteX));
EmitAsm("RTS");__sprite_hide
섀도 스프라이트 하나의 Y를 0xF0으로 설정해 숨깁니다. X, 타일, 속성은 남아 있어 다시 사용할 수 있습니다. 할당을 해제하거나 스프라이트 라이브러리의 사용 개수를 변경하지는 않습니다.
사용 형식
void __sprite_hide(u8 index);매개변수
index스프라이트 항목 번호 0–63입니다. 바이트 오프셋이 아닙니다. 내부에서 8비트 커서로 4를 곱하므로 범위를 벗어나면 64를 주기로 순환해 다른 항목을 덮어쓸 수 있습니다. 할당 여부는 검사하지 않습니다.
반환값
없음(void).
사용 조건과 제한
이 작업은 CPU RAM의 0x0200–0x02FF를 수정하며 하드웨어 OAM에는 즉시 반영되지 않습니다. 페이지를 완성한 뒤 __oam_dma로 전송하세요. 스프라이트 라이브러리의 할당 플래그, 좌표 배열, 사용 개수는 갱신하지 않습니다. 준비 중인 페이지를 인터럽트 처리에서 수정하거나 전송하지 않도록 하세요.
사용 예
__sprite_hide(4); // The HIDE row remains empty after DMA.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
작업마다 이름을 붙인 행으로 구분한 예제입니다. SET과 MOVE는 (144,16), (144,32)의 8×8 오른쪽 화살표, TILE은 (144,48)의 초록 정사각형, ATTR은 (144,64)의 상하좌우 반전 화살표입니다. HIDE의 (144,80)과 CLEAR의 (144,112)는 비어 있어야 합니다. META는 (136,96), (144,96)의 두 화살표이며 두 번째는 좌우 반전입니다. NEXT 07은 5번과 6번에 기록한 뒤의 다음 위치입니다. 화살표 테두리는 빨강, 몸통은 초록, 끝부분의 일부는 파랑입니다. 이미지당 8,704픽셀과 전송된 OAM 바이트를 대조하며 실제 기기에서는 검증하지 않았습니다.
TRANSFER의 (144,128)에 화살표를 표시합니다. 이 프로그램은 각 스프라이트 작업으로 준비한 모든 필드가 들어 있는 페이지 02를 __oam_dma로 직접 전송합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\oam_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\sprite_example.chr -o .\out\oam_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:200
Write F0 to the selected shadow Y byte; issue DMA to make the change visible.
EmitHelperStart("__sprite_hide");
EmitSpriteIndexToX();
EmitAsm("LDA", Imm(0xF0)); EmitAsm("STA", new AsmOperand(OamRamBase + 0, AddressMode.AbsoluteX));
EmitAsm("RTS");__sprite_move
섀도 스프라이트 하나의 X와 Y 바이트만 변경합니다. 타일 번호와 속성을 유지하므로 팔레트나 반전 상태를 바꾸지 않고 같은 그림을 이동할 수 있습니다.
사용 형식
void __sprite_move(u8 index, u8 x, u8 y);매개변수
index스프라이트 항목 번호 0–63입니다. 바이트 오프셋이 아닙니다. 내부에서 8비트 커서로 4를 곱하므로 범위를 벗어나면 64를 주기로 순환해 다른 항목을 덮어쓸 수 있습니다. 할당 여부는 검사하지 않습니다.
x픽셀 단위의 가로 위치를 부호 없는 8비트 값으로 전달합니다. 타일 좌표가 아닙니다.
yOAM에 저장할 Y 값입니다. 화면 1번 줄 이후에 윗부분을 놓으려면 표시할 윗좌표에서 1을 뺍니다. 239~255는 240줄 표시 영역 밖입니다.
반환값
없음(void).
사용 조건과 제한
이 작업은 CPU RAM의 0x0200–0x02FF를 수정하며 하드웨어 OAM에는 즉시 반영되지 않습니다. 페이지를 완성한 뒤 __oam_dma로 전송하세요. 스프라이트 라이브러리의 할당 플래그, 좌표 배열, 사용 개수는 갱신하지 않습니다. 준비 중인 페이지를 인터럽트 처리에서 수정하거나 전송하지 않도록 하세요.
사용 예
__sprite_move(1,144,31); // Move to (144,32), retaining tile 128 and attributes 0.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
작업마다 이름을 붙인 행으로 구분한 예제입니다. SET과 MOVE는 (144,16), (144,32)의 8×8 오른쪽 화살표, TILE은 (144,48)의 초록 정사각형, ATTR은 (144,64)의 상하좌우 반전 화살표입니다. HIDE의 (144,80)과 CLEAR의 (144,112)는 비어 있어야 합니다. META는 (136,96), (144,96)의 두 화살표이며 두 번째는 좌우 반전입니다. NEXT 07은 5번과 6번에 기록한 뒤의 다음 위치입니다. 화살표 테두리는 빨강, 몸통은 초록, 끝부분의 일부는 파랑입니다. 이미지당 8,704픽셀과 전송된 OAM 바이트를 대조하며 실제 기기에서는 검증하지 않았습니다.
TRANSFER의 (144,128)에 화살표를 표시합니다. 이 프로그램은 각 스프라이트 작업으로 준비한 모든 필드가 들어 있는 페이지 02를 __oam_dma로 직접 전송합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\oam_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\sprite_example.chr -o .\out\oam_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:194
Replace only shadow X/Y for index 0..63; tile and attribute bytes remain unchanged.
EmitHelperStart("__sprite_move");
EmitSpriteIndexToX();
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("STA", new AsmOperand(OamRamBase + 0, AddressMode.AbsoluteX));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("STA", new AsmOperand(OamRamBase + 3, AddressMode.AbsoluteX));
EmitAsm("RTS");__sprite_set
스프라이트 하나의 네 필드를 페이지 02 섀도 버퍼에 기록합니다. 인수 순서는 번호, X, Y, 타일, 속성이며 저장 순서는 Y, 타일, 속성, X입니다.
사용 형식
void __sprite_set(u8 index, u8 x, u8 y, u8 tile, u8 attr);매개변수
index스프라이트 항목 번호 0–63입니다. 바이트 오프셋이 아닙니다. 내부에서 8비트 커서로 4를 곱하므로 범위를 벗어나면 64를 주기로 순환해 다른 항목을 덮어쓸 수 있습니다. 할당 여부는 검사하지 않습니다.
x픽셀 단위의 가로 위치를 부호 없는 8비트 값으로 전달합니다. 타일 좌표가 아닙니다.
yOAM에 저장할 Y 값입니다. 화면 1번 줄 이후에 윗부분을 놓으려면 표시할 윗좌표에서 1을 뺍니다. 239~255는 240줄 표시 영역 밖입니다.
tile8비트 타일 번호입니다. 그래픽을 먼저 로드하세요. 8×16 스프라이트의 하드웨어 규칙은 그대로 적용되며 두 타일 묶음에 맞게 번호를 보정하지 않습니다.
attr속성 바이트 전체입니다. 비트 0–1은 스프라이트 팔레트 0–3, 비트 5는 불투명 배경 뒤에 표시, 비트 6과 7은 좌우 및 상하 반전을 지정합니다. 비트 2–4는 0으로 두세요. 플래그를 비트 OR로 조합하며 전달한 바이트가 기존 속성 전체를 대체합니다.
반환값
없음(void).
사용 조건과 제한
이 작업은 CPU RAM의 0x0200–0x02FF를 수정하며 하드웨어 OAM에는 즉시 반영되지 않습니다. 페이지를 완성한 뒤 __oam_dma로 전송하세요. 스프라이트 라이브러리의 할당 플래그, 좌표 배열, 사용 개수는 갱신하지 않습니다. 준비 중인 페이지를 인터럽트 처리에서 수정하거나 전송하지 않도록 하세요.
사용 예
__sprite_set(0,144,15,128,0); // An 8x8 arrow at visible position (144,16).호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
작업마다 이름을 붙인 행으로 구분한 예제입니다. SET과 MOVE는 (144,16), (144,32)의 8×8 오른쪽 화살표, TILE은 (144,48)의 초록 정사각형, ATTR은 (144,64)의 상하좌우 반전 화살표입니다. HIDE의 (144,80)과 CLEAR의 (144,112)는 비어 있어야 합니다. META는 (136,96), (144,96)의 두 화살표이며 두 번째는 좌우 반전입니다. NEXT 07은 5번과 6번에 기록한 뒤의 다음 위치입니다. 화살표 테두리는 빨강, 몸통은 초록, 끝부분의 일부는 파랑입니다. 이미지당 8,704픽셀과 전송된 OAM 바이트를 대조하며 실제 기기에서는 검증하지 않았습니다.
TRANSFER의 (144,128)에 화살표를 표시합니다. 이 프로그램은 각 스프라이트 작업으로 준비한 모든 필드가 들어 있는 페이지 02를 __oam_dma로 직접 전송합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\oam_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\sprite_example.chr -o .\out\oam_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:192
Write Y, tile, attributes and X to one shadow entry. Use indices 0..63; coordinates are raw OAM bytes.
EmitHelperStart("__sprite_set");
EmitSpriteIndexToX();
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("STA", new AsmOperand(OamRamBase + 0, AddressMode.AbsoluteX));
EmitAsm("LDA", Mem(CallArgBase + 3)); EmitAsm("STA", new AsmOperand(OamRamBase + 1, AddressMode.AbsoluteX));
EmitAsm("LDA", Mem(CallArgBase + 4)); EmitAsm("STA", new AsmOperand(OamRamBase + 2, AddressMode.AbsoluteX));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("STA", new AsmOperand(OamRamBase + 3, AddressMode.AbsoluteX));
EmitAsm("RTS");__sprite_tile
섀도 스프라이트 하나의 타일 번호 바이트만 바꿉니다. 위치와 속성은 유지합니다. 이미 있는 패턴을 선택하는 기능이며 CHR 메모리에 그래픽을 업로드하지 않습니다.
사용 형식
void __sprite_tile(u8 index, u8 tile);매개변수
index스프라이트 항목 번호 0–63입니다. 바이트 오프셋이 아닙니다. 내부에서 8비트 커서로 4를 곱하므로 범위를 벗어나면 64를 주기로 순환해 다른 항목을 덮어쓸 수 있습니다. 할당 여부는 검사하지 않습니다.
tile8비트 타일 번호입니다. 그래픽을 먼저 로드하세요. 8×16 스프라이트의 하드웨어 규칙은 그대로 적용되며 두 타일 묶음에 맞게 번호를 보정하지 않습니다.
반환값
없음(void).
사용 조건과 제한
이 작업은 CPU RAM의 0x0200–0x02FF를 수정하며 하드웨어 OAM에는 즉시 반영되지 않습니다. 페이지를 완성한 뒤 __oam_dma로 전송하세요. 스프라이트 라이브러리의 할당 플래그, 좌표 배열, 사용 개수는 갱신하지 않습니다. 준비 중인 페이지를 인터럽트 처리에서 수정하거나 전송하지 않도록 하세요.
사용 예
__sprite_tile(2,129); // Select the green square at (144,48).호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
작업마다 이름을 붙인 행으로 구분한 예제입니다. SET과 MOVE는 (144,16), (144,32)의 8×8 오른쪽 화살표, TILE은 (144,48)의 초록 정사각형, ATTR은 (144,64)의 상하좌우 반전 화살표입니다. HIDE의 (144,80)과 CLEAR의 (144,112)는 비어 있어야 합니다. META는 (136,96), (144,96)의 두 화살표이며 두 번째는 좌우 반전입니다. NEXT 07은 5번과 6번에 기록한 뒤의 다음 위치입니다. 화살표 테두리는 빨강, 몸통은 초록, 끝부분의 일부는 파랑입니다. 이미지당 8,704픽셀과 전송된 OAM 바이트를 대조하며 실제 기기에서는 검증하지 않았습니다.
TRANSFER의 (144,128)에 화살표를 표시합니다. 이 프로그램은 각 스프라이트 작업으로 준비한 모든 필드가 들어 있는 페이지 02를 __oam_dma로 직접 전송합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\oam_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\sprite_example.chr -o .\out\oam_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:196
Replace the selected shadow tile byte; this does not upload tile patterns.
EmitHelperStart("__sprite_tile");
EmitSpriteIndexToX();
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("STA", new AsmOperand(OamRamBase + 1, AddressMode.AbsoluteX));
EmitAsm("RTS");__vram_fill
ppu_addr를 설정한 뒤 같은 1바이트 값 value를 PPUDATA에 len번 씁니다. 반환 전에 완료되는 직접 채우기입니다. 타일 크기, 속성 또는 사각형 경계는 해석하지 않습니다.
사용 형식
void __vram_fill(u16 ppu_addr, u8 value, u8 len);매개변수
ppu_addrCPU 포인터가 아닌 PPU 바이트 주소(
0x0000–0x3FFF)입니다. 대상은 쓰기 가능해야 하며 패턴 변경에는 CHR ROM이 아닌 CHR RAM이 필요합니다. PPU 매핑과 미러링이 적용됩니다. 연속 복사와 채우기에는 PPUCTRL 증가량을 1로 설정하세요. 안전한 PPU 쓰기 시점에 실행한 뒤 스크롤 상태를 다시 설정하세요. 실행기는 PPUADDR와 PPUDATA를 사용하고 스크롤 설정을 복원하지 않습니다.value0~255의 한 바이트를 반복해서 씁니다. 화면에서의 의미는 대상이 타일 번호인지, 픽셀 비트플레인인지, 다른 바이트 구조인지에 따라 달라집니다.
len바이트 수
0..255입니다. 0이어도 PPU 주소와 래치를 설정하지만 데이터는 쓰지 않습니다. 대상 용량 검사, 경계 잘라내기, 뱅크 전환, 지연 전송은 없습니다.
반환값
없음(void).
사용 조건과 제한
직접 PPU 접근은 렌더링을 끄거나 전체 작업이 안전한 VBlank 구간 안에 끝나는 조건에서 수행하세요. 이 함수들은 대기, 큐 등록, NMI 차단, 스크롤 복원을 하지 않습니다. 호출 중 인터럽트가 공유 래치나 주소를 바꾸지 않도록 하세요. PPUCTRL 비트 2에 따라 대상 주소는 바이트마다 1 또는 32씩 증가합니다. 미러링과 주소 순환은 PPU 동작을 따르며 CHR-ROM에는 쓸 수 없습니다. 접근 후 렌더링 전에 원하는 스크롤 상태를 설정하세요.
사용 예
__vram_fill(0x2060,2,32); // A second blue row of 4-pixel-wide left halves.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 프로그램에서는 레지스터 설정, 타일·속성 직접 쓰기, 스크롤, 배경·스프라이트 팔레트의 개별 전송을 배웁니다. (0,16)의 파란 띠는 256×16픽셀입니다. 첫 행은 꽉 찬 8×8타일, 왼쪽 4×8막대, 8행 삼각형을 반복하고 둘째 행은 왼쪽 막대만 나열합니다. (16,48)에는 녹색으로 채운 64×32 사각형, (144,48)에는 삼각형 타일을 가로 8개·세로 4개 배치한 빨간 64×32 영역이 표시됩니다.
y=96에는 x=16, 24, 32에 각각 파란 정사각형·왼쪽 막대·삼각형, x=80에 빨간 왼쪽 막대, x=144에 녹색 삼각형이 표시됩니다. 이들은 배경 타일입니다. 별도 팔레트의 스프라이트는 y=144에 있으며 x=16은 빨간 8×8 정사각형, x=80은 파란 왼쪽 4×8막대, x=144는 녹색 8행 삼각형입니다. 최종 스크롤은 (0,0)입니다.
이미지는 스프라이트 색상을 포함해 16,384픽셀의 모양과 색상을 대조합니다. 별도 31개 상태 검사는 레지스터 교체, 상태·래치 부수 효과, 저장된 스크롤, 0/255바이트 전송, 증가량 1/32, 맵 경계, 크기 0 사각형, 테이블 선택, 전체 속성 바이트와 두 팔레트 대상을 다룹니다. Y=255는 레지스터 쓰기만 확인하며 올바른 스크롤 이미지를 검증한 것은 아닙니다. KUROSAKI의 NROM에서 검증했으며 실기는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:122
Write repeated bytes directly; this is not an enqueue operation.
kitaqfc/kitaqfc/CodeGenerator.cs:4818
case "__vram_fill": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,1,1}, funcName);
// Nametable and attribute helpers receive byte coordinates, values and optional table selectors.
void EmitHelperVramFill()
{
EmitHelperStart("__vram_fill");
EmitPpuAddrFromCallArg();
EmitAsm("LDX", Mem(CallArgBase + 3));
string loop = NewGeneratedLabel("vram_fill_loop");
string done = NewGeneratedLabel("vram_fill_done");
EmitAsm("BEQ", Rel(done));
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", Mem(0x2007));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
_assembly.Add(Expr.Make(Tag.Label, done));
EmitAsm("RTS");
}__vram_write
PPU 래치를 초기화하고 ppu_addr를 설정한 뒤 원본의 연속된 len바이트를 PPUDATA에 동기 전송합니다. 개수는 8비트이며 컴파일러는 인덱스 읽기를 사용합니다. 나중에 실행할 포인터를 보관하지 않고 반환 후 원본을 바꿔도 VRAM은 바뀌지 않습니다.
사용 형식
void __vram_write(u16 ppu_addr, const u8* src, u8 len);매개변수
ppu_addrCPU 포인터가 아닌 PPU 바이트 주소(
0x0000–0x3FFF)입니다. 대상은 쓰기 가능해야 하며 패턴 변경에는 CHR ROM이 아닌 CHR RAM이 필요합니다. PPU 매핑과 미러링이 적용됩니다. 연속 복사와 채우기에는 PPUCTRL 증가량을 1로 설정하세요. 안전한 PPU 쓰기 시점에 실행한 뒤 스크롤 상태를 다시 설정하세요. 실행기는 PPUADDR와 PPUDATA를 사용하고 스크롤 설정을 복원하지 않습니다.src요청한 수만큼의 바이트를 읽을 수 있는 CPU 포인터입니다. 동기 전송이 반환될 때까지 원본의 매핑과 내용을 유지하세요. 뱅크 전환, 큐에 포인터 보관, 원본 길이 검사는 하지 않습니다. 길이가 0이면 포인터를 역참조하지 않습니다.
len바이트 수
0..255입니다. 0이어도 PPU 주소와 래치를 설정하지만 데이터는 쓰지 않습니다. 대상 용량 검사, 경계 잘라내기, 뱅크 전환, 지연 전송은 없습니다.
반환값
없음(void).
사용 조건과 제한
직접 PPU 접근은 렌더링을 끄거나 전체 작업이 안전한 VBlank 구간 안에 끝나는 조건에서 수행하세요. 이 함수들은 대기, 큐 등록, NMI 차단, 스크롤 복원을 하지 않습니다. 호출 중 인터럽트가 공유 래치나 주소를 바꾸지 않도록 하세요. PPUCTRL 비트 2에 따라 대상 주소는 바이트마다 1 또는 32씩 증가합니다. 미러링과 주소 순환은 PPU 동작을 따르며 CHR-ROM에는 쓸 수 없습니다. 접근 후 렌더링 전에 원하는 스크롤 상태를 설정하세요.
사용 예
__vram_write(0x2040,pattern,32); // Blue row: solid tile, left half, triangle.
pattern[0]=3; // The completed transfer keeps its first solid tile.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
이 프로그램에서는 레지스터 설정, 타일·속성 직접 쓰기, 스크롤, 배경·스프라이트 팔레트의 개별 전송을 배웁니다. (0,16)의 파란 띠는 256×16픽셀입니다. 첫 행은 꽉 찬 8×8타일, 왼쪽 4×8막대, 8행 삼각형을 반복하고 둘째 행은 왼쪽 막대만 나열합니다. (16,48)에는 녹색으로 채운 64×32 사각형, (144,48)에는 삼각형 타일을 가로 8개·세로 4개 배치한 빨간 64×32 영역이 표시됩니다.
y=96에는 x=16, 24, 32에 각각 파란 정사각형·왼쪽 막대·삼각형, x=80에 빨간 왼쪽 막대, x=144에 녹색 삼각형이 표시됩니다. 이들은 배경 타일입니다. 별도 팔레트의 스프라이트는 y=144에 있으며 x=16은 빨간 8×8 정사각형, x=80은 파란 왼쪽 4×8막대, x=144는 녹색 8행 삼각형입니다. 최종 스크롤은 (0,0)입니다.
이미지는 스프라이트 색상을 포함해 16,384픽셀의 모양과 색상을 대조합니다. 별도 31개 상태 검사는 레지스터 교체, 상태·래치 부수 효과, 저장된 스크롤, 0/255바이트 전송, 증가량 1/32, 맵 경계, 크기 0 사각형, 테이블 선택, 전체 속성 바이트와 두 팔레트 대상을 다룹니다. Y=255는 레지스터 쓰기만 확인하며 올바른 스크롤 이미지를 검증한 것은 아닙니다. KUROSAKI의 NROM에서 검증했으며 실기는 검증하지 않았습니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:120
Write a byte-counted source range directly; arrange PPU access timing and source-bank visibility.
kitaqfc/kitaqfc/CodeGenerator.cs:4817
case "__vram_write": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2,1}, funcName);
void EmitHelperVramWrite()
{
EmitHelperStart("__vram_write");
EmitPpuAddrFromCallArg();
EmitAsm("LDY", Imm(0));
string loop = NewGeneratedLabel("vram_write_loop");
string done = NewGeneratedLabel("vram_write_done");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("CPY", Mem(CallArgBase + 4));
EmitAsm("BEQ", Rel(done));
EmitAsm("LDA", IndY(CallArgBase + 2));
EmitAsm("STA", Mem(0x2007));
EmitAsm("INY");
EmitAsm("BNE", Rel(loop));
_assembly.Add(Expr.Make(Tag.Label, done));
EmitAsm("RTS");
}__vramq_capacity
현재 사용량과 관계없이 명령 버퍼의 전체 용량을 반환합니다.
사용 형식
u8 __vramq_capacity(void);반환값
여기에 설명한 기종별 단위의 부호 없는 8비트 값입니다. 조회만으로 큐를 변경하거나 실행하지 않습니다.
사용 조건과 제한
FC는 고정 128바이트 내장 버퍼에 저장된 명령 레코드의 바이트 수를 셉니다. 타일 쓰기는 4바이트, 채우기는 5바이트, 포인터 복사는 6바이트이며 메타데이터를 포함합니다. 준비된 큐는 NMI가 소비할 수 있으므로 커밋 전에 확인·등록하고 생산자와 소비자를 조율하세요.
이 내장 함수는 lib/vram.c와 같은 명령 버퍼를 조작합니다. runtime.c의 nes_vram_queue_* 함수는 레코드 형식이 다른 별도 큐를 관리합니다. 한쪽을 지우거나 조회해도 다른 쪽이 지워지거나 측정되지는 않습니다.
큐 등록과 실행이 겹치지 않도록 조율하세요. 인터럽트가 실행 중일 때 추가하거나 관리 변수를 직접 바꾸거나 보관된 원본 데이터를 의도치 않게 덮어쓰지 마세요. 이 API는 잠금이나 이중 버퍼링을 제공하지 않습니다. 용량은 명령 버퍼의 용량이며 하드웨어 VRAM의 빈 바이트 수가 아닙니다.
사용 예
capacity=__vramq_capacity();
if (capacity!=128) failures++;호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
완성 프로그램으로 큐 용량, 보관된 포인터, 두 종류의 초기화, 직접 실행과 NMI 실행의 차이를 익힙니다. 기대값은 CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001, FAILED CHECKS 000입니다. 레코드 6개가 버퍼 30바이트를 사용해 512바이트를 씁니다. OVERFLOW SEEN은 앞서 의도적으로 거부된 시점의 값을 보관하며 종료 시 플래그가 남아 있다는 뜻은 아닙니다.
파란 복사 영역은 픽셀 (0,16), 초록 채우기 영역은 (0,88)에서 시작합니다. 각각 너비 256픽셀인 타일 행 7개와 여덟 번째 행의 248픽셀을 그려 총 255타일이 됩니다. 마지막 8 × 8칸은 검게 남습니다. 복사는 8 × 8 전체 채움·왼쪽 절반·삼각형을 반복하지만 등록 후 원본을 바꾸므로 첫 타일은 삼각형입니다. 채우기는 호출한 쪽의 변수를 바꿔도 전체 채움을 유지합니다. 모양과 색을 모두 검사합니다.
y=160에서 x=16에는 빨간 정사각형, x=224에는 빨간 왼쪽 절반 타일, x=240에는 빨간 정사각형이 나타나야 합니다. 첫 도형은 길이 0 복사와 채우기 후에도 남고, 가운데는 기본 NMI 실행, 마지막은 단일 쓰기를 확인합니다. 취소된 (248,160)은 검게 남아야 합니다. 긴 전송은 렌더링과 NMI를 끄고 실행하며 마지막 작은 쓰기만 NMI를 사용합니다. NROM 에뮬레이터 검증이며 실제 기기 검증은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\vramq_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\vramq_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:164
Return total encoded command-buffer capacity; this is independent of PPU VRAM capacity.
__vramq_clear
내장 큐의 대기 레코드를 모두 버리고 길이와 준비 플래그를 0으로 만듭니다. VRAM에 쓰거나 오버플로 기록을 지우지는 않습니다. 기록을 지우려면 __vramq_clear_overflow를 별도로 호출하세요.
사용 형식
void __vramq_clear(void);반환값
없음(void).
사용 조건과 제한
FC는 고정 128바이트 내장 버퍼에 저장된 명령 레코드의 바이트 수를 셉니다. 타일 쓰기는 4바이트, 채우기는 5바이트, 포인터 복사는 6바이트이며 메타데이터를 포함합니다. 준비된 큐는 NMI가 소비할 수 있으므로 커밋 전에 확인·등록하고 생산자와 소비자를 조율하세요.
이 내장 함수는 lib/vram.c와 같은 명령 버퍼를 조작합니다. runtime.c의 nes_vram_queue_* 함수는 레코드 형식이 다른 별도 큐를 관리합니다. 한쪽을 지우거나 조회해도 다른 쪽이 지워지거나 측정되지는 않습니다.
큐 등록과 실행이 겹치지 않도록 조율하세요. 인터럽트가 실행 중일 때 추가하거나 관리 변수를 직접 바꾸거나 보관된 원본 데이터를 의도치 않게 덮어쓰지 마세요. 이 API는 잠금이나 이중 버퍼링을 제공하지 않습니다. 용량은 명령 버퍼의 용량이며 하드웨어 VRAM의 빈 바이트 수가 아닙니다.
사용 예
__vramq_clear();
if (__vramq_len()!=0 || __vramq_overflow()!=1) failures++;
// Clearing queued work deliberately leaves the overflow latch set.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
완성 프로그램으로 큐 용량, 보관된 포인터, 두 종류의 초기화, 직접 실행과 NMI 실행의 차이를 익힙니다. 기대값은 CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001, FAILED CHECKS 000입니다. 레코드 6개가 버퍼 30바이트를 사용해 512바이트를 씁니다. OVERFLOW SEEN은 앞서 의도적으로 거부된 시점의 값을 보관하며 종료 시 플래그가 남아 있다는 뜻은 아닙니다.
파란 복사 영역은 픽셀 (0,16), 초록 채우기 영역은 (0,88)에서 시작합니다. 각각 너비 256픽셀인 타일 행 7개와 여덟 번째 행의 248픽셀을 그려 총 255타일이 됩니다. 마지막 8 × 8칸은 검게 남습니다. 복사는 8 × 8 전체 채움·왼쪽 절반·삼각형을 반복하지만 등록 후 원본을 바꾸므로 첫 타일은 삼각형입니다. 채우기는 호출한 쪽의 변수를 바꿔도 전체 채움을 유지합니다. 모양과 색을 모두 검사합니다.
y=160에서 x=16에는 빨간 정사각형, x=224에는 빨간 왼쪽 절반 타일, x=240에는 빨간 정사각형이 나타나야 합니다. 첫 도형은 길이 0 복사와 채우기 후에도 남고, 가운데는 기본 NMI 실행, 마지막은 단일 쓰기를 확인합니다. 취소된 (248,160)은 검게 남아야 합니다. 긴 전송은 렌더링과 NMI를 끄고 실행하며 마지막 작은 쓰기만 NMI를 사용합니다. NROM 에뮬레이터 검증이며 실제 기기 검증은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\vramq_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\vramq_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:146
NMI VRAM transfer queue. Queue entries are consumed by __vramq_exec(), and the default NMI handler calls __vramq_exec() automatically. Reset pending intrinsic-queue records; this is separate from runtime.c nes_vram_queue_clear.
__vramq_clear_overflow
오버플로 기록만 0으로 지웁니다. 대기 레코드, 길이, 준비 상태는 그대로입니다. 용량 부족 알림을 해제할 뿐, 거부된 레코드를 다시 시도하거나 앞서 성공한 등록을 되돌리지 않습니다.
사용 형식
void __vramq_clear_overflow(void);반환값
없음(void).
사용 조건과 제한
FC는 고정 128바이트 내장 버퍼에 저장된 명령 레코드의 바이트 수를 셉니다. 타일 쓰기는 4바이트, 채우기는 5바이트, 포인터 복사는 6바이트이며 메타데이터를 포함합니다. 준비된 큐는 NMI가 소비할 수 있으므로 커밋 전에 확인·등록하고 생산자와 소비자를 조율하세요.
이 내장 함수는 lib/vram.c와 같은 명령 버퍼를 조작합니다. runtime.c의 nes_vram_queue_* 함수는 레코드 형식이 다른 별도 큐를 관리합니다. 한쪽을 지우거나 조회해도 다른 쪽이 지워지거나 측정되지는 않습니다.
큐 등록과 실행이 겹치지 않도록 조율하세요. 인터럽트가 실행 중일 때 추가하거나 관리 변수를 직접 바꾸거나 보관된 원본 데이터를 의도치 않게 덮어쓰지 마세요. 이 API는 잠금이나 이중 버퍼링을 제공하지 않습니다. 용량은 명령 버퍼의 용량이며 하드웨어 VRAM의 빈 바이트 수가 아닙니다.
사용 예
__vramq_clear_overflow();
if (__vramq_overflow()!=0 || __vramq_len()!=4) failures++;
// Resetting the latch deliberately keeps this pending put record.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
완성 프로그램으로 큐 용량, 보관된 포인터, 두 종류의 초기화, 직접 실행과 NMI 실행의 차이를 익힙니다. 기대값은 CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001, FAILED CHECKS 000입니다. 레코드 6개가 버퍼 30바이트를 사용해 512바이트를 씁니다. OVERFLOW SEEN은 앞서 의도적으로 거부된 시점의 값을 보관하며 종료 시 플래그가 남아 있다는 뜻은 아닙니다.
파란 복사 영역은 픽셀 (0,16), 초록 채우기 영역은 (0,88)에서 시작합니다. 각각 너비 256픽셀인 타일 행 7개와 여덟 번째 행의 248픽셀을 그려 총 255타일이 됩니다. 마지막 8 × 8칸은 검게 남습니다. 복사는 8 × 8 전체 채움·왼쪽 절반·삼각형을 반복하지만 등록 후 원본을 바꾸므로 첫 타일은 삼각형입니다. 채우기는 호출한 쪽의 변수를 바꿔도 전체 채움을 유지합니다. 모양과 색을 모두 검사합니다.
y=160에서 x=16에는 빨간 정사각형, x=224에는 빨간 왼쪽 절반 타일, x=240에는 빨간 정사각형이 나타나야 합니다. 첫 도형은 길이 0 복사와 채우기 후에도 남고, 가운데는 기본 NMI 실행, 마지막은 단일 쓰기를 확인합니다. 취소된 (248,160)은 검게 남아야 합니다. 긴 전송은 렌더링과 NMI를 끄고 실행하며 마지막 작은 쓰기만 NMI를 사용합니다. NROM 에뮬레이터 검증이며 실제 기기 검증은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\vramq_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\vramq_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:162
Clear the overflow latch explicitly after handling the failed submission.
__vramq_commit
실행기가 큐를 처리할 수 있도록 준비 플래그를 1로 설정합니다. 데이터 복사, PPU 쓰기, NMI 대기는 하지 않습니다. 커밋 후에는 실행이 끝날 때까지 큐와 보관된 원본을 변경하지 마세요. NMI가 활성화되어 있으면 기본 핸들러가 비동기적으로 처리할 수 있습니다.
사용 형식
void __vramq_commit(void);반환값
없음(void).
사용 조건과 제한
FC는 고정 128바이트 내장 버퍼에 저장된 명령 레코드의 바이트 수를 셉니다. 타일 쓰기는 4바이트, 채우기는 5바이트, 포인터 복사는 6바이트이며 메타데이터를 포함합니다. 준비된 큐는 NMI가 소비할 수 있으므로 커밋 전에 확인·등록하고 생산자와 소비자를 조율하세요.
이 내장 함수는 lib/vram.c와 같은 명령 버퍼를 조작합니다. runtime.c의 nes_vram_queue_* 함수는 레코드 형식이 다른 별도 큐를 관리합니다. 한쪽을 지우거나 조회해도 다른 쪽이 지워지거나 측정되지는 않습니다.
큐 등록과 실행이 겹치지 않도록 조율하세요. 인터럽트가 실행 중일 때 추가하거나 관리 변수를 직접 바꾸거나 보관된 원본 데이터를 의도치 않게 덮어쓰지 마세요. 이 API는 잠금이나 이중 버퍼링을 제공하지 않습니다. 용량은 명령 버퍼의 용량이며 하드웨어 VRAM의 빈 바이트 수가 아닙니다.
사용 예
__vramq_commit(); // NMI is disabled here: this only marks the queue ready.
if (__vramq_len()!=30) failures++;호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
완성 프로그램으로 큐 용량, 보관된 포인터, 두 종류의 초기화, 직접 실행과 NMI 실행의 차이를 익힙니다. 기대값은 CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001, FAILED CHECKS 000입니다. 레코드 6개가 버퍼 30바이트를 사용해 512바이트를 씁니다. OVERFLOW SEEN은 앞서 의도적으로 거부된 시점의 값을 보관하며 종료 시 플래그가 남아 있다는 뜻은 아닙니다.
파란 복사 영역은 픽셀 (0,16), 초록 채우기 영역은 (0,88)에서 시작합니다. 각각 너비 256픽셀인 타일 행 7개와 여덟 번째 행의 248픽셀을 그려 총 255타일이 됩니다. 마지막 8 × 8칸은 검게 남습니다. 복사는 8 × 8 전체 채움·왼쪽 절반·삼각형을 반복하지만 등록 후 원본을 바꾸므로 첫 타일은 삼각형입니다. 채우기는 호출한 쪽의 변수를 바꿔도 전체 채움을 유지합니다. 모양과 색을 모두 검사합니다.
y=160에서 x=16에는 빨간 정사각형, x=224에는 빨간 왼쪽 절반 타일, x=240에는 빨간 정사각형이 나타나야 합니다. 첫 도형은 길이 0 복사와 채우기 후에도 남고, 가운데는 기본 NMI 실행, 마지막은 단일 쓰기를 확인합니다. 취소된 (248,160)은 검게 남아야 합니다. 긴 전송은 렌더링과 NMI를 끄고 실행하며 마지막 작은 쓰기만 NMI를 사용합니다. NROM 에뮬레이터 검증이며 실제 기기 검증은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\vramq_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\vramq_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:154
Publish the pending records to the configured consumer. Do not mutate them while consumption can run.
__vramq_copy
실행 시 src에서 ppu_addr로 len바이트를 복사하는 6바이트 레코드 하나를 추가합니다. 큐는 데이터가 아닌 원본 포인터를 보관합니다. 255바이트 복사도 명령 버퍼에서는 6바이트만 차지합니다. 원본 주소가 증가하는 순서로 읽으며 연속된 대상에는 PPU 증가량 1을 선택하세요.
사용 형식
void __vramq_copy(u16 ppu_addr, const u8* src, u8 len);매개변수
ppu_addrCPU 포인터가 아닌 PPU 바이트 주소(
0x0000–0x3FFF)입니다. 대상은 쓰기 가능해야 하며 패턴 변경에는 CHR ROM이 아닌 CHR RAM이 필요합니다. PPU 매핑과 미러링이 적용됩니다. 연속 복사와 채우기에는 PPUCTRL 증가량을 1로 설정하세요. 안전한 PPU 쓰기 시점에 실행한 뒤 스크롤 상태를 다시 설정하세요. 실행기는 PPUADDR와 PPUDATA를 사용하고 스크롤 설정을 복원하지 않습니다.src실행 때까지 보관하는 읽기 가능한 바이트의 근거리 포인터입니다. 실행이 끝날 때까지 데이터의 수명과 메모리·ROM 뱅크 매핑을 유지하세요. 뱅크 번호나 데이터 사본을 저장하지 않습니다. 비어 있지 않은 전송에는 유효한 원본이 필요하며 수명이 끝난 지역 배열을 사용하면 안 됩니다.
len바이트 수이며 범위는 0~255입니다. 레코드 하나로 전체 개수를 처리하고 127바이트씩 나누지 않습니다. 0도 복사나 채우기 레코드를 차지합니다. 실행 시 PPU 주소는 설정하지만 PPUDATA 바이트는 쓰지 않습니다. 256은 이 인수의 범위를 벗어납니다.
반환값
없음(void).
사용 조건과 제한
추가 함수는 성공 여부를 반환하지 않습니다. 레코드 전체가 들어가지 않으면 __vramq_overflow()를 1로 설정하고 기존 레코드와 길이는 유지합니다. 정확히 맞으면 등록됩니다. 이전 오버플로가 있어도 공간이 충분하면 추가할 수 있으므로 새 작업 묶음을 판단하기 전에 기록을 처리하거나 지우세요. 여러 추가를 묶어 되돌리는 기능은 없습니다.
FC는 고정 128바이트 내장 버퍼에 저장된 명령 레코드의 바이트 수를 셉니다. 타일 쓰기는 4바이트, 채우기는 5바이트, 포인터 복사는 6바이트이며 메타데이터를 포함합니다. 준비된 큐는 NMI가 소비할 수 있으므로 커밋 전에 확인·등록하고 생산자와 소비자를 조율하세요.
이 내장 함수는 lib/vram.c와 같은 명령 버퍼를 조작합니다. runtime.c의 nes_vram_queue_* 함수는 레코드 형식이 다른 별도 큐를 관리합니다. 한쪽을 지우거나 조회해도 다른 쪽이 지워지거나 측정되지는 않습니다.
큐 등록과 실행이 겹치지 않도록 조율하세요. 인터럽트가 실행 중일 때 추가하거나 관리 변수를 직접 바꾸거나 보관된 원본 데이터를 의도치 않게 덮어쓰지 마세요. 이 API는 잠금이나 이중 버퍼링을 제공하지 않습니다. 용량은 명령 버퍼의 용량이며 하드웨어 VRAM의 빈 바이트 수가 아닙니다.
사용 예
__vramq_copy(0x2040,source_bytes,255);
source_bytes[0]=3; // The executor must see this change through its retained pointer.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
완성 프로그램으로 큐 용량, 보관된 포인터, 두 종류의 초기화, 직접 실행과 NMI 실행의 차이를 익힙니다. 기대값은 CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001, FAILED CHECKS 000입니다. 레코드 6개가 버퍼 30바이트를 사용해 512바이트를 씁니다. OVERFLOW SEEN은 앞서 의도적으로 거부된 시점의 값을 보관하며 종료 시 플래그가 남아 있다는 뜻은 아닙니다.
파란 복사 영역은 픽셀 (0,16), 초록 채우기 영역은 (0,88)에서 시작합니다. 각각 너비 256픽셀인 타일 행 7개와 여덟 번째 행의 248픽셀을 그려 총 255타일이 됩니다. 마지막 8 × 8칸은 검게 남습니다. 복사는 8 × 8 전체 채움·왼쪽 절반·삼각형을 반복하지만 등록 후 원본을 바꾸므로 첫 타일은 삼각형입니다. 채우기는 호출한 쪽의 변수를 바꿔도 전체 채움을 유지합니다. 모양과 색을 모두 검사합니다.
y=160에서 x=16에는 빨간 정사각형, x=224에는 빨간 왼쪽 절반 타일, x=240에는 빨간 정사각형이 나타나야 합니다. 첫 도형은 길이 0 복사와 채우기 후에도 남고, 가운데는 기본 NMI 실행, 마지막은 단일 쓰기를 확인합니다. 취소된 (248,160)은 검게 남아야 합니다. 긴 전송은 렌더링과 NMI를 끄고 실행하며 마지막 작은 쓰기만 NMI를 사용합니다. NROM 에뮬레이터 검증이며 실제 기기 검증은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\vramq_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\vramq_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:150
Retain the source address for later consumption; keep its storage and bank mapping valid.
__vramq_exec
커밋된 큐를 레코드 순서대로 즉시 실행합니다. 준비되지 않았다면 큐를 바꾸지 않고 반환합니다. 실행 후 길이와 준비 플래그를 지우되 오버플로는 남깁니다. 안전한 PPU 접근 시점 대기, 전송량 제한, 스크롤 복원은 하지 않으므로 호출하는 쪽에서 관리하세요.
사용 형식
void __vramq_exec(void);반환값
없음(void).
사용 조건과 제한
CPU 포인터가 아닌 PPU 바이트 주소(0x0000–0x3FFF)입니다. 대상은 쓰기 가능해야 하며 패턴 변경에는 CHR ROM이 아닌 CHR RAM이 필요합니다. PPU 매핑과 미러링이 적용됩니다. 연속 복사와 채우기에는 PPUCTRL 증가량을 1로 설정하세요. 안전한 PPU 쓰기 시점에 실행한 뒤 스크롤 상태를 다시 설정하세요. 실행기는 PPUADDR와 PPUDATA를 사용하고 스크롤 설정을 복원하지 않습니다.
FC는 고정 128바이트 내장 버퍼에 저장된 명령 레코드의 바이트 수를 셉니다. 타일 쓰기는 4바이트, 채우기는 5바이트, 포인터 복사는 6바이트이며 메타데이터를 포함합니다. 준비된 큐는 NMI가 소비할 수 있으므로 커밋 전에 확인·등록하고 생산자와 소비자를 조율하세요.
이 내장 함수는 lib/vram.c와 같은 명령 버퍼를 조작합니다. runtime.c의 nes_vram_queue_* 함수는 레코드 형식이 다른 별도 큐를 관리합니다. 한쪽을 지우거나 조회해도 다른 쪽이 지워지거나 측정되지는 않습니다.
큐 등록과 실행이 겹치지 않도록 조율하세요. 인터럽트가 실행 중일 때 추가하거나 관리 변수를 직접 바꾸거나 보관된 원본 데이터를 의도치 않게 덮어쓰지 마세요. 이 API는 잠금이나 이중 버퍼링을 제공하지 않습니다. 용량은 명령 버퍼의 용량이며 하드웨어 VRAM의 빈 바이트 수가 아닙니다.
사용 예
// Rendering is stopped and the PPU increment is 1.
__vramq_exec();
after_exec=__vramq_len();
if (after_exec!=0 || __vramq_overflow()!=0) failures++;호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
완성 프로그램으로 큐 용량, 보관된 포인터, 두 종류의 초기화, 직접 실행과 NMI 실행의 차이를 익힙니다. 기대값은 CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001, FAILED CHECKS 000입니다. 레코드 6개가 버퍼 30바이트를 사용해 512바이트를 씁니다. OVERFLOW SEEN은 앞서 의도적으로 거부된 시점의 값을 보관하며 종료 시 플래그가 남아 있다는 뜻은 아닙니다.
파란 복사 영역은 픽셀 (0,16), 초록 채우기 영역은 (0,88)에서 시작합니다. 각각 너비 256픽셀인 타일 행 7개와 여덟 번째 행의 248픽셀을 그려 총 255타일이 됩니다. 마지막 8 × 8칸은 검게 남습니다. 복사는 8 × 8 전체 채움·왼쪽 절반·삼각형을 반복하지만 등록 후 원본을 바꾸므로 첫 타일은 삼각형입니다. 채우기는 호출한 쪽의 변수를 바꿔도 전체 채움을 유지합니다. 모양과 색을 모두 검사합니다.
y=160에서 x=16에는 빨간 정사각형, x=224에는 빨간 왼쪽 절반 타일, x=240에는 빨간 정사각형이 나타나야 합니다. 첫 도형은 길이 0 복사와 채우기 후에도 남고, 가운데는 기본 NMI 실행, 마지막은 단일 쓰기를 확인합니다. 취소된 (248,160)은 검게 남아야 합니다. 긴 전송은 렌더링과 NMI를 끄고 실행하며 마지막 작은 쓰기만 NMI를 사용합니다. NROM 에뮬레이터 검증이며 실제 기기 검증은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\vramq_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\vramq_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:156
Execute a ready queue directly; committing and safe PPU timing are caller responsibilities.
__vramq_fill
한 바이트 값 value를 PPU에 len번 쓰는 5바이트 레코드 하나를 추가합니다. 값은 레코드에 저장되므로 호출한 쪽의 변수를 나중에 바꿔도 등록된 채우기 값은 바뀌지 않습니다. 여러 바이트의 타일 패턴이 아닌 한 바이트를 반복합니다.
사용 형식
void __vramq_fill(u16 ppu_addr, u8 value, u8 len);매개변수
ppu_addrCPU 포인터가 아닌 PPU 바이트 주소(
0x0000–0x3FFF)입니다. 대상은 쓰기 가능해야 하며 패턴 변경에는 CHR ROM이 아닌 CHR RAM이 필요합니다. PPU 매핑과 미러링이 적용됩니다. 연속 복사와 채우기에는 PPUCTRL 증가량을 1로 설정하세요. 안전한 PPU 쓰기 시점에 실행한 뒤 스크롤 상태를 다시 설정하세요. 실행기는 PPUADDR와 PPUDATA를 사용하고 스크롤 설정을 복원하지 않습니다.value0~255의 한 바이트를 반복해서 씁니다. 화면에서의 의미는 대상이 타일 번호인지, 픽셀 비트플레인인지, 다른 바이트 구조인지에 따라 달라집니다.
len바이트 수이며 범위는 0~255입니다. 레코드 하나로 전체 개수를 처리하고 127바이트씩 나누지 않습니다. 0도 복사나 채우기 레코드를 차지합니다. 실행 시 PPU 주소는 설정하지만 PPUDATA 바이트는 쓰지 않습니다. 256은 이 인수의 범위를 벗어납니다.
반환값
없음(void).
사용 조건과 제한
추가 함수는 성공 여부를 반환하지 않습니다. 레코드 전체가 들어가지 않으면 __vramq_overflow()를 1로 설정하고 기존 레코드와 길이는 유지합니다. 정확히 맞으면 등록됩니다. 이전 오버플로가 있어도 공간이 충분하면 추가할 수 있으므로 새 작업 묶음을 판단하기 전에 기록을 처리하거나 지우세요. 여러 추가를 묶어 되돌리는 기능은 없습니다.
FC는 고정 128바이트 내장 버퍼에 저장된 명령 레코드의 바이트 수를 셉니다. 타일 쓰기는 4바이트, 채우기는 5바이트, 포인터 복사는 6바이트이며 메타데이터를 포함합니다. 준비된 큐는 NMI가 소비할 수 있으므로 커밋 전에 확인·등록하고 생산자와 소비자를 조율하세요.
이 내장 함수는 lib/vram.c와 같은 명령 버퍼를 조작합니다. runtime.c의 nes_vram_queue_* 함수는 레코드 형식이 다른 별도 큐를 관리합니다. 한쪽을 지우거나 조회해도 다른 쪽이 지워지거나 측정되지는 않습니다.
큐 등록과 실행이 겹치지 않도록 조율하세요. 인터럽트가 실행 중일 때 추가하거나 관리 변수를 직접 바꾸거나 보관된 원본 데이터를 의도치 않게 덮어쓰지 마세요. 이 API는 잠금이나 이중 버퍼링을 제공하지 않습니다. 용량은 명령 버퍼의 용량이며 하드웨어 VRAM의 빈 바이트 수가 아닙니다.
사용 예
__vramq_fill(0x2160,fill_value,255); // Seven complete rows and 31 tiles in the eighth.
fill_value=3; // The queued value stays 1, so the green block remains solid.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
완성 프로그램으로 큐 용량, 보관된 포인터, 두 종류의 초기화, 직접 실행과 NMI 실행의 차이를 익힙니다. 기대값은 CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001, FAILED CHECKS 000입니다. 레코드 6개가 버퍼 30바이트를 사용해 512바이트를 씁니다. OVERFLOW SEEN은 앞서 의도적으로 거부된 시점의 값을 보관하며 종료 시 플래그가 남아 있다는 뜻은 아닙니다.
파란 복사 영역은 픽셀 (0,16), 초록 채우기 영역은 (0,88)에서 시작합니다. 각각 너비 256픽셀인 타일 행 7개와 여덟 번째 행의 248픽셀을 그려 총 255타일이 됩니다. 마지막 8 × 8칸은 검게 남습니다. 복사는 8 × 8 전체 채움·왼쪽 절반·삼각형을 반복하지만 등록 후 원본을 바꾸므로 첫 타일은 삼각형입니다. 채우기는 호출한 쪽의 변수를 바꿔도 전체 채움을 유지합니다. 모양과 색을 모두 검사합니다.
y=160에서 x=16에는 빨간 정사각형, x=224에는 빨간 왼쪽 절반 타일, x=240에는 빨간 정사각형이 나타나야 합니다. 첫 도형은 길이 0 복사와 채우기 후에도 남고, 가운데는 기본 NMI 실행, 마지막은 단일 쓰기를 확인합니다. 취소된 (248,160)은 검게 남아야 합니다. 긴 전송은 렌더링과 NMI를 끄고 실행하며 마지막 작은 쓰기만 NMI를 사용합니다. NROM 에뮬레이터 검증이며 실제 기기 검증은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\vramq_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\vramq_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:152
Append a repeated-byte update; the source value is stored in the record.
__vramq_len
대기 중인 명령이 현재 차지하는 명령 버퍼 사용량을 반환합니다. 비디오 메모리에 쓸 데이터의 바이트 수가 아닙니다.
사용 형식
u8 __vramq_len(void);반환값
여기에 설명한 기종별 단위의 부호 없는 8비트 값입니다. 조회만으로 큐를 변경하거나 실행하지 않습니다.
사용 조건과 제한
FC는 고정 128바이트 내장 버퍼에 저장된 명령 레코드의 바이트 수를 셉니다. 타일 쓰기는 4바이트, 채우기는 5바이트, 포인터 복사는 6바이트이며 메타데이터를 포함합니다. 준비된 큐는 NMI가 소비할 수 있으므로 커밋 전에 확인·등록하고 생산자와 소비자를 조율하세요.
이 내장 함수는 lib/vram.c와 같은 명령 버퍼를 조작합니다. runtime.c의 nes_vram_queue_* 함수는 레코드 형식이 다른 별도 큐를 관리합니다. 한쪽을 지우거나 조회해도 다른 쪽이 지워지거나 측정되지는 않습니다.
큐 등록과 실행이 겹치지 않도록 조율하세요. 인터럽트가 실행 중일 때 추가하거나 관리 변수를 직접 바꾸거나 보관된 원본 데이터를 의도치 않게 덮어쓰지 마세요. 이 API는 잠금이나 이중 버퍼링을 제공하지 않습니다. 용량은 명령 버퍼의 용량이며 하드웨어 VRAM의 빈 바이트 수가 아닙니다.
사용 예
encoded=__vramq_len();
if (encoded!=30 || capacity-encoded!=98) failures++;
// 4+6+5+6+5+4 encoded bytes describe 512 actual byte writes.호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
완성 프로그램으로 큐 용량, 보관된 포인터, 두 종류의 초기화, 직접 실행과 NMI 실행의 차이를 익힙니다. 기대값은 CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001, FAILED CHECKS 000입니다. 레코드 6개가 버퍼 30바이트를 사용해 512바이트를 씁니다. OVERFLOW SEEN은 앞서 의도적으로 거부된 시점의 값을 보관하며 종료 시 플래그가 남아 있다는 뜻은 아닙니다.
파란 복사 영역은 픽셀 (0,16), 초록 채우기 영역은 (0,88)에서 시작합니다. 각각 너비 256픽셀인 타일 행 7개와 여덟 번째 행의 248픽셀을 그려 총 255타일이 됩니다. 마지막 8 × 8칸은 검게 남습니다. 복사는 8 × 8 전체 채움·왼쪽 절반·삼각형을 반복하지만 등록 후 원본을 바꾸므로 첫 타일은 삼각형입니다. 채우기는 호출한 쪽의 변수를 바꿔도 전체 채움을 유지합니다. 모양과 색을 모두 검사합니다.
y=160에서 x=16에는 빨간 정사각형, x=224에는 빨간 왼쪽 절반 타일, x=240에는 빨간 정사각형이 나타나야 합니다. 첫 도형은 길이 0 복사와 채우기 후에도 남고, 가운데는 기본 NMI 실행, 마지막은 단일 쓰기를 확인합니다. 취소된 (248,160)은 검게 남아야 합니다. 긴 전송은 렌더링과 NMI를 끄고 실행하며 마지막 작은 쓰기만 NMI를 사용합니다. NROM 에뮬레이터 검증이며 실제 기기 검증은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\vramq_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\vramq_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:158
Return encoded bytes currently occupied, not transfer payload bytes or command count.
__vramq_overflow
큐 넘침 플래그를 지우지 않고 읽습니다. 이전 등록 실패가 남아 있을 수 있습니다.
사용 형식
u8 __vramq_overflow(void);반환값
기록된 오버플로가 없으면 0입니다. 등록이 거부되면 1이 되고 명시적으로 초기화할 때까지 유지됩니다. 큐 실행만으로는 지워지지 않습니다.
사용 조건과 제한
FC는 고정 128바이트 내장 버퍼에 저장된 명령 레코드의 바이트 수를 셉니다. 타일 쓰기는 4바이트, 채우기는 5바이트, 포인터 복사는 6바이트이며 메타데이터를 포함합니다. 준비된 큐는 NMI가 소비할 수 있으므로 커밋 전에 확인·등록하고 생산자와 소비자를 조율하세요.
이 내장 함수는 lib/vram.c와 같은 명령 버퍼를 조작합니다. runtime.c의 nes_vram_queue_* 함수는 레코드 형식이 다른 별도 큐를 관리합니다. 한쪽을 지우거나 조회해도 다른 쪽이 지워지거나 측정되지는 않습니다.
큐 등록과 실행이 겹치지 않도록 조율하세요. 인터럽트가 실행 중일 때 추가하거나 관리 변수를 직접 바꾸거나 보관된 원본 데이터를 의도치 않게 덮어쓰지 마세요. 이 API는 잠금이나 이중 버퍼링을 제공하지 않습니다. 용량은 명령 버퍼의 용량이며 하드웨어 VRAM의 빈 바이트 수가 아닙니다.
사용 예
overflow_seen=__vramq_overflow();
if (overflow_seen!=1 || __vramq_overflow()!=1) failures++;호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
완성 프로그램으로 큐 용량, 보관된 포인터, 두 종류의 초기화, 직접 실행과 NMI 실행의 차이를 익힙니다. 기대값은 CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001, FAILED CHECKS 000입니다. 레코드 6개가 버퍼 30바이트를 사용해 512바이트를 씁니다. OVERFLOW SEEN은 앞서 의도적으로 거부된 시점의 값을 보관하며 종료 시 플래그가 남아 있다는 뜻은 아닙니다.
파란 복사 영역은 픽셀 (0,16), 초록 채우기 영역은 (0,88)에서 시작합니다. 각각 너비 256픽셀인 타일 행 7개와 여덟 번째 행의 248픽셀을 그려 총 255타일이 됩니다. 마지막 8 × 8칸은 검게 남습니다. 복사는 8 × 8 전체 채움·왼쪽 절반·삼각형을 반복하지만 등록 후 원본을 바꾸므로 첫 타일은 삼각형입니다. 채우기는 호출한 쪽의 변수를 바꿔도 전체 채움을 유지합니다. 모양과 색을 모두 검사합니다.
y=160에서 x=16에는 빨간 정사각형, x=224에는 빨간 왼쪽 절반 타일, x=240에는 빨간 정사각형이 나타나야 합니다. 첫 도형은 길이 0 복사와 채우기 후에도 남고, 가운데는 기본 NMI 실행, 마지막은 단일 쓰기를 확인합니다. 취소된 (248,160)은 검게 남아야 합니다. 긴 전송은 렌더링과 NMI를 끄고 실행하며 마지막 작은 쓰기만 NMI를 사용합니다. NROM 에뮬레이터 검증이며 실제 기기 검증은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\vramq_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\vramq_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:160
Read the latched capacity failure; reading does not reset it.
__vramq_put
PPU 주소와 한 바이트 값을 4바이트 레코드로 추가합니다. 값은 지금 큐에 복사되고 큐 실행 시 PPU에 쓰입니다. 네임테이블 타일 하나를 갱신할 수 있으며 다른 쓰기 가능한 PPU 바이트를 지정할 수도 있습니다.
사용 형식
void __vramq_put(u16 ppu_addr, u8 value);매개변수
ppu_addrCPU 포인터가 아닌 PPU 바이트 주소(
0x0000–0x3FFF)입니다. 대상은 쓰기 가능해야 하며 패턴 변경에는 CHR ROM이 아닌 CHR RAM이 필요합니다. PPU 매핑과 미러링이 적용됩니다. 연속 복사와 채우기에는 PPUCTRL 증가량을 1로 설정하세요. 안전한 PPU 쓰기 시점에 실행한 뒤 스크롤 상태를 다시 설정하세요. 실행기는 PPUADDR와 PPUDATA를 사용하고 스크롤 설정을 복원하지 않습니다.value0~255의 한 바이트를 반복해서 씁니다. 화면에서의 의미는 대상이 타일 번호인지, 픽셀 비트플레인인지, 다른 바이트 구조인지에 따라 달라집니다.
반환값
없음(void).
사용 조건과 제한
추가 함수는 성공 여부를 반환하지 않습니다. 레코드 전체가 들어가지 않으면 __vramq_overflow()를 1로 설정하고 기존 레코드와 길이는 유지합니다. 정확히 맞으면 등록됩니다. 이전 오버플로가 있어도 공간이 충분하면 추가할 수 있으므로 새 작업 묶음을 판단하기 전에 기록을 처리하거나 지우세요. 여러 추가를 묶어 되돌리는 기능은 없습니다.
FC는 고정 128바이트 내장 버퍼에 저장된 명령 레코드의 바이트 수를 셉니다. 타일 쓰기는 4바이트, 채우기는 5바이트, 포인터 복사는 6바이트이며 메타데이터를 포함합니다. 준비된 큐는 NMI가 소비할 수 있으므로 커밋 전에 확인·등록하고 생산자와 소비자를 조율하세요.
이 내장 함수는 lib/vram.c와 같은 명령 버퍼를 조작합니다. runtime.c의 nes_vram_queue_* 함수는 레코드 형식이 다른 별도 큐를 관리합니다. 한쪽을 지우거나 조회해도 다른 쪽이 지워지거나 측정되지는 않습니다.
큐 등록과 실행이 겹치지 않도록 조율하세요. 인터럽트가 실행 중일 때 추가하거나 관리 변수를 직접 바꾸거나 보관된 원본 데이터를 의도치 않게 덮어쓰지 마세요. 이 API는 잠금이나 이중 버퍼링을 제공하지 않습니다. 용량은 명령 버퍼의 용량이며 하드웨어 VRAM의 빈 바이트 수가 아닙니다.
사용 예
__vramq_put(0x2282,1); // Red 8x8 sentinel at pixel (16,160).호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
완성 프로그램으로 큐 용량, 보관된 포인터, 두 종류의 초기화, 직접 실행과 NMI 실행의 차이를 익힙니다. 기대값은 CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001, FAILED CHECKS 000입니다. 레코드 6개가 버퍼 30바이트를 사용해 512바이트를 씁니다. OVERFLOW SEEN은 앞서 의도적으로 거부된 시점의 값을 보관하며 종료 시 플래그가 남아 있다는 뜻은 아닙니다.
파란 복사 영역은 픽셀 (0,16), 초록 채우기 영역은 (0,88)에서 시작합니다. 각각 너비 256픽셀인 타일 행 7개와 여덟 번째 행의 248픽셀을 그려 총 255타일이 됩니다. 마지막 8 × 8칸은 검게 남습니다. 복사는 8 × 8 전체 채움·왼쪽 절반·삼각형을 반복하지만 등록 후 원본을 바꾸므로 첫 타일은 삼각형입니다. 채우기는 호출한 쪽의 변수를 바꿔도 전체 채움을 유지합니다. 모양과 색을 모두 검사합니다.
y=160에서 x=16에는 빨간 정사각형, x=224에는 빨간 왼쪽 절반 타일, x=240에는 빨간 정사각형이 나타나야 합니다. 첫 도형은 길이 0 복사와 채우기 후에도 남고, 가운데는 기본 NMI 실행, 마지막은 단일 쓰기를 확인합니다. 취소된 (248,160)은 검게 남아야 합니다. 긴 전송은 렌더링과 NMI를 끄고 실행하며 마지막 작은 쓰기만 NMI를 사용합니다. NROM 에뮬레이터 검증이며 실제 기기 검증은 아닙니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\vramq_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\vramq_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr --no-cache --no-disasm구현과 소스
kitaqfc/lib/intrinsics.h:148
Append one address/value update; use the overflow latch to detect capacity failure.
__xy_in_rect
왼쪽·위쪽을 포함하고 오른쪽·아래쪽을 제외하는 사각형 안에 있는지 검사합니다.
사용 형식
u8 __xy_in_rect(u8 x, u8 y, u8 rx, u8 ry, u8 rw, u8 rh);매개변수
x점의 X 좌표 0~255입니다.
y점의 Y 좌표 0~255입니다.
rx사각형의 왼쪽 좌표 0~255입니다.
ry사각형의 위쪽 좌표 0~255입니다.
rw너비 0~255입니다.
rh높이 0~255입니다.
반환값
안쪽이면 1, 아니면 0을 반환합니다. 너비나 높이가 0이면 비어 있습니다.
사용 조건과 제한
좌표 차이를 사용하므로 오른쪽 경계가 255를 넘어도 왼쪽으로 래핑되지 않습니다.
사용 예
result[0]=__xy_in_rect(10,20,10,20,3,2);result[1]=__xy_in_rect(13,20,10,20,3,2);result[2]=__xy_in_rect(9,20,10,20,3,2);result[3]=__xy_in_rect(255,1,250,0,10,2);result[4]=__xy_in_rect(10,20,10,20,0,2);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
내부, 경계, 외부 점, 너비 0을 검사합니다. INSIDE=1, RIGHT EDGE=0, LEFT EDGE=0, HIGH X=1입니다. 거리 15, 래핑된 거리 254, 배열 인덱스 64/65280도 확인합니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_coordinates.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_coordinates.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr구현과 소스
kitaqfc/lib/intrinsics.h:31
Return 0 or 1 for half-open bounds. Zero extents are empty; coordinates do not wrap across 255.
kitaqfc/kitaqfc/CodeGenerator.cs:4627
case "__xy_in_rect":
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1,1,1,1}, "__xy_in_rect");
void EmitHelperGeometryAndRng()
{
EmitHelperStart("__xy_in_rect");
string xyFalse = NewGeneratedLabel("xy_in_rect_false");
string xyDone = NewGeneratedLabel("xy_in_rect_done");
// return (x-rx < rw) && (y-ry < rh), using unsigned subtract to avoid rx+rw overflow.
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("SEC");
EmitAsm("SBC", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(xyFalse));
EmitAsm("CMP", Mem(CallArgBase + 4));
EmitAsm("BCS", Rel(xyFalse));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("SEC");
EmitAsm("SBC", Mem(CallArgBase + 3));
EmitAsm("BCC", Rel(xyFalse));
EmitAsm("CMP", Mem(CallArgBase + 5));
EmitAsm("BCS", Rel(xyFalse));
EmitAsm("LDA", Imm(1));
EmitAsm("JMP", Abs(xyDone));
_assembly.Add(Expr.Make(Tag.Label, xyFalse));
EmitAsm("LDA", Imm(0));
_assembly.Add(Expr.Make(Tag.Label, xyDone));
EmitAsm("RTS");
EmitHelperStart("__manhattan");
// dx = abs(x1-x2)
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("SEC");
EmitAsm("SBC", Mem(CallArgBase + 2));
string mdxOk = NewGeneratedLabel("manhattan_dx_ok");
EmitAsm("BCS", Rel(mdxOk));
EmitAsm("EOR", Imm(0xFF));
EmitAsm("CLC");
EmitAsm("ADC", Imm(1));
_assembly.Add(Expr.Make(Tag.Label, mdxOk));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
// dy = abs(y1-y2), then add dx.
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("SEC");
EmitAsm("SBC", Mem(CallArgBase + 3));
string mdyOk = NewGeneratedLabel("manhattan_dy_ok");
EmitAsm("BCS", Rel(mdyOk));
EmitAsm("EOR", Imm(0xFF));
EmitAsm("CLC");
EmitAsm("ADC", Imm(1));
_assembly.Add(Expr.Make(Tag.Label, mdyOk));
EmitAsm("CLC");
EmitAsm("ADC", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Save X-coordinate separately while multiplying width by Y, then add X with carry into the high byte.
EmitHelperStart("__map_index");
// args: x, y, width. Return y * width + x as u16.
EmitAsm("LDA", Mem(CallArgBase)); // x
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Mem(CallArgBase + 1)); // y
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", Mem(CallArgBase + 2)); // width as u16 multiplicand
EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("CLC");
EmitAsm("ADC", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("TXA");
EmitAsm("ADC", Imm(0));
EmitAsm("TAX");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Replace both bytes of the persistent generator state with the supplied seed.
EmitHelperStart("__rng_seed");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeRngLoAddress));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeRngHiAddress));
EmitAsm("RTS");
EmitHelperStart("__rng8");
// 16-bit Galois LFSR: if low bit set, shift right then xor $B400.
EmitAsm("LDA", Mem(_runtimeRngLoAddress));
EmitAsm("ORA", Mem(_runtimeRngHiAddress));
// Replace the all-zero LFSR state with a deterministic nonzero seed before advancing.
string seeded = NewGeneratedLabel("rng_seeded");
EmitAsm("BNE", Rel(seeded));
EmitAsm("LDA", Imm(0x5A));
EmitAsm("STA", Mem(_runtimeRngLoAddress));
EmitAsm("LDA", Imm(0xA5));
EmitAsm("STA", Mem(_runtimeRngHiAddress));
_assembly.Add(Expr.Make(Tag.Label, seeded));
EmitAsm("LDA", Mem(_runtimeRngLoAddress));
EmitAsm("AND", Imm(1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LSR", Mem(_runtimeRngHiAddress));
EmitAsm("ROR", Mem(_runtimeRngLoAddress));
string rngNoXor = NewGeneratedLabel("rng_no_xor");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("BEQ", Rel(rngNoXor));
EmitAsm("LDA", Mem(_runtimeRngHiAddress));
EmitAsm("EOR", Imm(0xB4));
EmitAsm("STA", Mem(_runtimeRngHiAddress));
_assembly.Add(Expr.Make(Tag.Label, rngNoXor));
EmitAsm("LDA", Mem(_runtimeRngLoAddress));
EmitAsm("EOR", Mem(_runtimeRngHiAddress));
EmitAsm("RTS");
}__zapper_light
기본 포트 2 빛 감지 읽기 함수입니다. __zapper_light2()와 같은 보조 함수를 호출합니다.
사용 형식
u8 __zapper_light(void);반환값
$4017의 비트 3이 0이면 1, 그렇지 않으면 0입니다. 포트 1은 읽지 않습니다.
사용 조건과 제한
이 API는 $4016/$4017에 직접 접근합니다. 같은 포트에서 인터럽트 기반 게임패드·키보드·시리얼 연산을 끼워 넣지 마세요. 컨트롤러의 시프트 위치도 영향을 받을 수 있습니다. 예제는 입력을 캡처하는 동안 NMI를 끄고 화면 초기화 때 다시 켭니다.
순간 신호를 읽습니다. 스트로브, 프레임 대기, 변화 검출, 조준점 위치 계산, 명중 판정은 하지 않습니다. 원시 읽기와 방아쇠·광 감지 호출은 각각 자기 시점에 레지스터를 읽으므로 신호가 변하는 동안 값이 다를 수 있습니다.
광 입력은 액티브 로우입니다. 비트 3이 0이면 1을 반환합니다. 이 변환은 광선총 연결을 감지하는 것이 아닙니다. 게임패드나 미연결 입력에서도 1이 나올 수 있습니다. 화면에 표적을 그려도 명중 판정 API가 되지는 않습니다.
레지스터 읽기를 RAM의 시험용 값으로 돌리는 자체 테스트 ROM에서도 비트 변환을 단위 테스트합니다. 관련 없는 비트가 설정된 상태에서 00/08/10/18 조합을 확인합니다. 이 테스트는 수정하지 않은 샘플 실행이나 실제 주변기기의 동작 검증과는 별개입니다.
이 화면은 다운로드 가능한 ROM을 수정 없이 KUROSAKI에서 실행한 것입니다. 현재 입력 버스는 게임패드만 모델링하며 키보드·광선총·시리얼 어댑터 신호는 모델링하지 않습니다. 외부 입력 비트가 0으로 읽히므로 이 캡처로 해당 장치를 검증할 수 없습니다. 실제 키 입력, 광 감지, 통신은 미검증입니다.
사용 예
#include "fc_ports_scene.h"
void main(void){
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
port_result[0]=__zapper_raw1();port_result[1]=__zapper_trigger1();port_result[2]=__zapper_light1();
port_result[3]=__zapper_raw2();port_result[4]=__zapper_trigger2();port_result[5]=__zapper_light2();
port_result[6]=__zapper_trigger();port_result[7]=__zapper_light();
port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"LIGHT-GUN INPUT SNAPSHOT");m_wait();
m_text(2,5,"PORT RAW TRIGGER LIGHT");m_wait();
m_put(3,8,'1');port_hex(9,8,port_result[0]);port_number(15,8,port_result[1]);port_number(24,8,port_result[2]);m_wait();
m_put(3,10,'2');port_hex(9,10,port_result[3]);port_number(15,10,port_result[4]);port_number(24,10,port_result[5]);m_wait();
m_text(2,13,"DEFAULT = PORT 2");m_wait();
m_text(2,15,"TRIGGER");port_number(15,15,port_result[6]);m_wait();
m_text(2,17,"LIGHT");port_number(15,17,port_result[7]);m_wait();
m_text(2,21,"LIGHT 1 IS NOT A HIT TEST");m_wait();
while(1){m_wait();}
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
KUROSAKI에서는 두 포트 모두 RAW=00, TRIGGER=000, LIGHT=001로 표시됩니다. 접미사가 없는 함수도 포트 2와 같은 000/001을 표시합니다. LIGHT=001은 화면 밝기를 감지한 결과가 아니라, 구현되지 않은 입력 비트 3이 0으로 읽히기 때문입니다. 화면에도 LIGHT 1 IS NOT A HIT TEST라고 표시됩니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_zapper.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_zapper.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_zapper.nes --frames 120 --png .\out\peripheral_zapper.png구현과 소스
kitaqfc/lib/intrinsics.h:253
Default to the port-2 normalized light reader.
kitaqfc/kitaqfc/CodeGenerator.cs:7753
EmitHelperStart("__zapper_light2");
EmitNormalizeBoolFromMask(0x4017, 0x08, true);
void EmitNormalizeBoolFromMask(int port, int mask, bool invert)
{
string zero = NewGeneratedLabel("bool_zero");
string done = NewGeneratedLabel("bool_done");
EmitAsm("LDA", Mem(port));
EmitAsm("AND", Imm(mask));
if (invert) EmitAsm("BNE", Rel(zero)); else EmitAsm("BEQ", Rel(zero));
EmitAsm("LDA", Imm(1));
EmitAsm("JMP", Abs(done));
_assembly.Add(Expr.Make(Tag.Label, zero));
EmitAsm("LDA", Imm(0));
_assembly.Add(Expr.Make(Tag.Label, done));
EmitAsm("RTS");
}__zapper_light1
포트 1($4016)의 빛 감지 신호를 읽습니다. 낮은 레벨이 활성 상태인 입력을 변환해 빛을 감지했을 때 1을 반환합니다.
사용 형식
u8 __zapper_light1(void);반환값
$4016의 비트 3이 0이면 1, 1이면 0입니다.
사용 조건과 제한
이 API는 $4016/$4017에 직접 접근합니다. 같은 포트에서 인터럽트 기반 게임패드·키보드·시리얼 연산을 끼워 넣지 마세요. 컨트롤러의 시프트 위치도 영향을 받을 수 있습니다. 예제는 입력을 캡처하는 동안 NMI를 끄고 화면 초기화 때 다시 켭니다.
순간 신호를 읽습니다. 스트로브, 프레임 대기, 변화 검출, 조준점 위치 계산, 명중 판정은 하지 않습니다. 원시 읽기와 방아쇠·광 감지 호출은 각각 자기 시점에 레지스터를 읽으므로 신호가 변하는 동안 값이 다를 수 있습니다.
광 입력은 액티브 로우입니다. 비트 3이 0이면 1을 반환합니다. 이 변환은 광선총 연결을 감지하는 것이 아닙니다. 게임패드나 미연결 입력에서도 1이 나올 수 있습니다. 화면에 표적을 그려도 명중 판정 API가 되지는 않습니다.
레지스터 읽기를 RAM의 시험용 값으로 돌리는 자체 테스트 ROM에서도 비트 변환을 단위 테스트합니다. 관련 없는 비트가 설정된 상태에서 00/08/10/18 조합을 확인합니다. 이 테스트는 수정하지 않은 샘플 실행이나 실제 주변기기의 동작 검증과는 별개입니다.
이 화면은 다운로드 가능한 ROM을 수정 없이 KUROSAKI에서 실행한 것입니다. 현재 입력 버스는 게임패드만 모델링하며 키보드·광선총·시리얼 어댑터 신호는 모델링하지 않습니다. 외부 입력 비트가 0으로 읽히므로 이 캡처로 해당 장치를 검증할 수 없습니다. 실제 키 입력, 광 감지, 통신은 미검증입니다.
사용 예
#include "fc_ports_scene.h"
void main(void){
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
port_result[0]=__zapper_raw1();port_result[1]=__zapper_trigger1();port_result[2]=__zapper_light1();
port_result[3]=__zapper_raw2();port_result[4]=__zapper_trigger2();port_result[5]=__zapper_light2();
port_result[6]=__zapper_trigger();port_result[7]=__zapper_light();
port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"LIGHT-GUN INPUT SNAPSHOT");m_wait();
m_text(2,5,"PORT RAW TRIGGER LIGHT");m_wait();
m_put(3,8,'1');port_hex(9,8,port_result[0]);port_number(15,8,port_result[1]);port_number(24,8,port_result[2]);m_wait();
m_put(3,10,'2');port_hex(9,10,port_result[3]);port_number(15,10,port_result[4]);port_number(24,10,port_result[5]);m_wait();
m_text(2,13,"DEFAULT = PORT 2");m_wait();
m_text(2,15,"TRIGGER");port_number(15,15,port_result[6]);m_wait();
m_text(2,17,"LIGHT");port_number(15,17,port_result[7]);m_wait();
m_text(2,21,"LIGHT 1 IS NOT A HIT TEST");m_wait();
while(1){m_wait();}
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
KUROSAKI에서는 두 포트 모두 RAW=00, TRIGGER=000, LIGHT=001로 표시됩니다. 접미사가 없는 함수도 포트 2와 같은 000/001을 표시합니다. LIGHT=001은 화면 밝기를 감지한 결과가 아니라, 구현되지 않은 입력 비트 3이 0으로 읽히기 때문입니다. 화면에도 LIGHT 1 IS NOT A HIT TEST라고 표시됩니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_zapper.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_zapper.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_zapper.nes --frames 120 --png .\out\peripheral_zapper.png구현과 소스
kitaqfc/lib/intrinsics.h:247
Invert port-1 bit 3 and normalize: one means light detected.
kitaqfc/kitaqfc/CodeGenerator.cs:7751
EmitHelperStart("__zapper_light1");
EmitNormalizeBoolFromMask(0x4016, 0x08, true);
void EmitNormalizeBoolFromMask(int port, int mask, bool invert)
{
string zero = NewGeneratedLabel("bool_zero");
string done = NewGeneratedLabel("bool_done");
EmitAsm("LDA", Mem(port));
EmitAsm("AND", Imm(mask));
if (invert) EmitAsm("BNE", Rel(zero)); else EmitAsm("BEQ", Rel(zero));
EmitAsm("LDA", Imm(1));
EmitAsm("JMP", Abs(done));
_assembly.Add(Expr.Make(Tag.Label, zero));
EmitAsm("LDA", Imm(0));
_assembly.Add(Expr.Make(Tag.Label, done));
EmitAsm("RTS");
}__zapper_light2
포트 2($4017)의 빛 감지 신호를 읽습니다. 낮은 레벨이 활성 상태인 입력을 변환해 빛을 감지했을 때 1을 반환합니다.
사용 형식
u8 __zapper_light2(void);반환값
$4017의 비트 3이 0이면 1, 1이면 0입니다.
사용 조건과 제한
이 API는 $4016/$4017에 직접 접근합니다. 같은 포트에서 인터럽트 기반 게임패드·키보드·시리얼 연산을 끼워 넣지 마세요. 컨트롤러의 시프트 위치도 영향을 받을 수 있습니다. 예제는 입력을 캡처하는 동안 NMI를 끄고 화면 초기화 때 다시 켭니다.
순간 신호를 읽습니다. 스트로브, 프레임 대기, 변화 검출, 조준점 위치 계산, 명중 판정은 하지 않습니다. 원시 읽기와 방아쇠·광 감지 호출은 각각 자기 시점에 레지스터를 읽으므로 신호가 변하는 동안 값이 다를 수 있습니다.
광 입력은 액티브 로우입니다. 비트 3이 0이면 1을 반환합니다. 이 변환은 광선총 연결을 감지하는 것이 아닙니다. 게임패드나 미연결 입력에서도 1이 나올 수 있습니다. 화면에 표적을 그려도 명중 판정 API가 되지는 않습니다.
레지스터 읽기를 RAM의 시험용 값으로 돌리는 자체 테스트 ROM에서도 비트 변환을 단위 테스트합니다. 관련 없는 비트가 설정된 상태에서 00/08/10/18 조합을 확인합니다. 이 테스트는 수정하지 않은 샘플 실행이나 실제 주변기기의 동작 검증과는 별개입니다.
이 화면은 다운로드 가능한 ROM을 수정 없이 KUROSAKI에서 실행한 것입니다. 현재 입력 버스는 게임패드만 모델링하며 키보드·광선총·시리얼 어댑터 신호는 모델링하지 않습니다. 외부 입력 비트가 0으로 읽히므로 이 캡처로 해당 장치를 검증할 수 없습니다. 실제 키 입력, 광 감지, 통신은 미검증입니다.
사용 예
#include "fc_ports_scene.h"
void main(void){
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
port_result[0]=__zapper_raw1();port_result[1]=__zapper_trigger1();port_result[2]=__zapper_light1();
port_result[3]=__zapper_raw2();port_result[4]=__zapper_trigger2();port_result[5]=__zapper_light2();
port_result[6]=__zapper_trigger();port_result[7]=__zapper_light();
port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"LIGHT-GUN INPUT SNAPSHOT");m_wait();
m_text(2,5,"PORT RAW TRIGGER LIGHT");m_wait();
m_put(3,8,'1');port_hex(9,8,port_result[0]);port_number(15,8,port_result[1]);port_number(24,8,port_result[2]);m_wait();
m_put(3,10,'2');port_hex(9,10,port_result[3]);port_number(15,10,port_result[4]);port_number(24,10,port_result[5]);m_wait();
m_text(2,13,"DEFAULT = PORT 2");m_wait();
m_text(2,15,"TRIGGER");port_number(15,15,port_result[6]);m_wait();
m_text(2,17,"LIGHT");port_number(15,17,port_result[7]);m_wait();
m_text(2,21,"LIGHT 1 IS NOT A HIT TEST");m_wait();
while(1){m_wait();}
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
KUROSAKI에서는 두 포트 모두 RAW=00, TRIGGER=000, LIGHT=001로 표시됩니다. 접미사가 없는 함수도 포트 2와 같은 000/001을 표시합니다. LIGHT=001은 화면 밝기를 감지한 결과가 아니라, 구현되지 않은 입력 비트 3이 0으로 읽히기 때문입니다. 화면에도 LIGHT 1 IS NOT A HIT TEST라고 표시됩니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_zapper.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_zapper.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_zapper.nes --frames 120 --png .\out\peripheral_zapper.png구현과 소스
kitaqfc/lib/intrinsics.h:249
Invert port-2 bit 3 and normalize: one means light detected.
kitaqfc/kitaqfc/CodeGenerator.cs:7753
EmitHelperStart("__zapper_light2");
EmitNormalizeBoolFromMask(0x4017, 0x08, true);
void EmitNormalizeBoolFromMask(int port, int mask, bool invert)
{
string zero = NewGeneratedLabel("bool_zero");
string done = NewGeneratedLabel("bool_done");
EmitAsm("LDA", Mem(port));
EmitAsm("AND", Imm(mask));
if (invert) EmitAsm("BNE", Rel(zero)); else EmitAsm("BEQ", Rel(zero));
EmitAsm("LDA", Imm(1));
EmitAsm("JMP", Abs(done));
_assembly.Add(Expr.Make(Tag.Label, zero));
EmitAsm("LDA", Imm(0));
_assembly.Add(Expr.Make(Tag.Label, done));
EmitAsm("RTS");
}__zapper_raw1
같은 레지스터 읽기 결과에서 방아쇠와 빛 감지를 해석해야 할 때 포트 1($4016)의 광선총 비트 두 개를 함께 읽습니다.
사용 형식
u8 __zapper_raw1(void);반환값
$4016 & 0x18, 즉 0x00, 0x08, 0x10, 0x18 중 하나를 반환합니다. 비트 4는 방아쇠 신호이고, 비트 3은 빛이 없음을 나타내는 원시 신호입니다.
사용 조건과 제한
이 API는 $4016/$4017에 직접 접근합니다. 같은 포트에서 인터럽트 기반 게임패드·키보드·시리얼 연산을 끼워 넣지 마세요. 컨트롤러의 시프트 위치도 영향을 받을 수 있습니다. 예제는 입력을 캡처하는 동안 NMI를 끄고 화면 초기화 때 다시 켭니다.
순간 신호를 읽습니다. 스트로브, 프레임 대기, 변화 검출, 조준점 위치 계산, 명중 판정은 하지 않습니다. 원시 읽기와 방아쇠·광 감지 호출은 각각 자기 시점에 레지스터를 읽으므로 신호가 변하는 동안 값이 다를 수 있습니다.
광 입력은 액티브 로우입니다. 비트 3이 0이면 1을 반환합니다. 이 변환은 광선총 연결을 감지하는 것이 아닙니다. 게임패드나 미연결 입력에서도 1이 나올 수 있습니다. 화면에 표적을 그려도 명중 판정 API가 되지는 않습니다.
레지스터 읽기를 RAM의 시험용 값으로 돌리는 자체 테스트 ROM에서도 비트 변환을 단위 테스트합니다. 관련 없는 비트가 설정된 상태에서 00/08/10/18 조합을 확인합니다. 이 테스트는 수정하지 않은 샘플 실행이나 실제 주변기기의 동작 검증과는 별개입니다.
이 화면은 다운로드 가능한 ROM을 수정 없이 KUROSAKI에서 실행한 것입니다. 현재 입력 버스는 게임패드만 모델링하며 키보드·광선총·시리얼 어댑터 신호는 모델링하지 않습니다. 외부 입력 비트가 0으로 읽히므로 이 캡처로 해당 장치를 검증할 수 없습니다. 실제 키 입력, 광 감지, 통신은 미검증입니다.
사용 예
#include "fc_ports_scene.h"
void main(void){
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
port_result[0]=__zapper_raw1();port_result[1]=__zapper_trigger1();port_result[2]=__zapper_light1();
port_result[3]=__zapper_raw2();port_result[4]=__zapper_trigger2();port_result[5]=__zapper_light2();
port_result[6]=__zapper_trigger();port_result[7]=__zapper_light();
port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"LIGHT-GUN INPUT SNAPSHOT");m_wait();
m_text(2,5,"PORT RAW TRIGGER LIGHT");m_wait();
m_put(3,8,'1');port_hex(9,8,port_result[0]);port_number(15,8,port_result[1]);port_number(24,8,port_result[2]);m_wait();
m_put(3,10,'2');port_hex(9,10,port_result[3]);port_number(15,10,port_result[4]);port_number(24,10,port_result[5]);m_wait();
m_text(2,13,"DEFAULT = PORT 2");m_wait();
m_text(2,15,"TRIGGER");port_number(15,15,port_result[6]);m_wait();
m_text(2,17,"LIGHT");port_number(15,17,port_result[7]);m_wait();
m_text(2,21,"LIGHT 1 IS NOT A HIT TEST");m_wait();
while(1){m_wait();}
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
KUROSAKI에서는 두 포트 모두 RAW=00, TRIGGER=000, LIGHT=001로 표시됩니다. 접미사가 없는 함수도 포트 2와 같은 000/001을 표시합니다. LIGHT=001은 화면 밝기를 감지한 결과가 아니라, 구현되지 않은 입력 비트 3이 0으로 읽히기 때문입니다. 화면에도 LIGHT 1 IS NOT A HIT TEST라고 표시됩니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_zapper.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_zapper.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_zapper.nes --frames 120 --png .\out\peripheral_zapper.png구현과 소스
kitaqfc/lib/intrinsics.h:239
Zapper / light gun. The no-suffix names default to the usual port-2/Famicom expansion-port mapping. __zapper_light*() returns 1 when light is detected. Return raw 4016 & 18, retaining trigger and active-low light bits.
kitaqfc/kitaqfc/CodeGenerator.cs:4775
case "__zapper_raw1": if (!Need(0)) return true; EmitAsm("LDA", Mem(0x4016)); EmitAsm("AND", Imm(0x18)); return true;__zapper_raw2
같은 레지스터 읽기 결과에서 방아쇠와 빛 감지를 해석해야 할 때 포트 2($4017)의 광선총 비트 두 개를 함께 읽습니다.
사용 형식
u8 __zapper_raw2(void);반환값
$4017 & 0x18, 즉 0x00, 0x08, 0x10, 0x18 중 하나를 반환합니다. 비트 4는 방아쇠 신호이고, 비트 3은 빛이 없음을 나타내는 원시 신호입니다.
사용 조건과 제한
이 API는 $4016/$4017에 직접 접근합니다. 같은 포트에서 인터럽트 기반 게임패드·키보드·시리얼 연산을 끼워 넣지 마세요. 컨트롤러의 시프트 위치도 영향을 받을 수 있습니다. 예제는 입력을 캡처하는 동안 NMI를 끄고 화면 초기화 때 다시 켭니다.
순간 신호를 읽습니다. 스트로브, 프레임 대기, 변화 검출, 조준점 위치 계산, 명중 판정은 하지 않습니다. 원시 읽기와 방아쇠·광 감지 호출은 각각 자기 시점에 레지스터를 읽으므로 신호가 변하는 동안 값이 다를 수 있습니다.
광 입력은 액티브 로우입니다. 비트 3이 0이면 1을 반환합니다. 이 변환은 광선총 연결을 감지하는 것이 아닙니다. 게임패드나 미연결 입력에서도 1이 나올 수 있습니다. 화면에 표적을 그려도 명중 판정 API가 되지는 않습니다.
레지스터 읽기를 RAM의 시험용 값으로 돌리는 자체 테스트 ROM에서도 비트 변환을 단위 테스트합니다. 관련 없는 비트가 설정된 상태에서 00/08/10/18 조합을 확인합니다. 이 테스트는 수정하지 않은 샘플 실행이나 실제 주변기기의 동작 검증과는 별개입니다.
이 화면은 다운로드 가능한 ROM을 수정 없이 KUROSAKI에서 실행한 것입니다. 현재 입력 버스는 게임패드만 모델링하며 키보드·광선총·시리얼 어댑터 신호는 모델링하지 않습니다. 외부 입력 비트가 0으로 읽히므로 이 캡처로 해당 장치를 검증할 수 없습니다. 실제 키 입력, 광 감지, 통신은 미검증입니다.
사용 예
#include "fc_ports_scene.h"
void main(void){
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
port_result[0]=__zapper_raw1();port_result[1]=__zapper_trigger1();port_result[2]=__zapper_light1();
port_result[3]=__zapper_raw2();port_result[4]=__zapper_trigger2();port_result[5]=__zapper_light2();
port_result[6]=__zapper_trigger();port_result[7]=__zapper_light();
port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"LIGHT-GUN INPUT SNAPSHOT");m_wait();
m_text(2,5,"PORT RAW TRIGGER LIGHT");m_wait();
m_put(3,8,'1');port_hex(9,8,port_result[0]);port_number(15,8,port_result[1]);port_number(24,8,port_result[2]);m_wait();
m_put(3,10,'2');port_hex(9,10,port_result[3]);port_number(15,10,port_result[4]);port_number(24,10,port_result[5]);m_wait();
m_text(2,13,"DEFAULT = PORT 2");m_wait();
m_text(2,15,"TRIGGER");port_number(15,15,port_result[6]);m_wait();
m_text(2,17,"LIGHT");port_number(15,17,port_result[7]);m_wait();
m_text(2,21,"LIGHT 1 IS NOT A HIT TEST");m_wait();
while(1){m_wait();}
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
KUROSAKI에서는 두 포트 모두 RAW=00, TRIGGER=000, LIGHT=001로 표시됩니다. 접미사가 없는 함수도 포트 2와 같은 000/001을 표시합니다. LIGHT=001은 화면 밝기를 감지한 결과가 아니라, 구현되지 않은 입력 비트 3이 0으로 읽히기 때문입니다. 화면에도 LIGHT 1 IS NOT A HIT TEST라고 표시됩니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_zapper.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_zapper.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_zapper.nes --frames 120 --png .\out\peripheral_zapper.png구현과 소스
kitaqfc/lib/intrinsics.h:241
Return raw 4017 & 18 for the second port.
kitaqfc/kitaqfc/CodeGenerator.cs:4776
case "__zapper_raw2": if (!Need(0)) return true; EmitAsm("LDA", Mem(0x4017)); EmitAsm("AND", Imm(0x18)); return true;__zapper_trigger
기본 포트 2 방아쇠 읽기 함수입니다. __zapper_trigger2()와 같은 보조 함수를 호출합니다.
사용 형식
u8 __zapper_trigger(void);반환값
$4017의 비트 4를 0 또는 1로 정규화한 값입니다. 포트 1로 자동 전환하지 않습니다.
사용 조건과 제한
이 API는 $4016/$4017에 직접 접근합니다. 같은 포트에서 인터럽트 기반 게임패드·키보드·시리얼 연산을 끼워 넣지 마세요. 컨트롤러의 시프트 위치도 영향을 받을 수 있습니다. 예제는 입력을 캡처하는 동안 NMI를 끄고 화면 초기화 때 다시 켭니다.
순간 신호를 읽습니다. 스트로브, 프레임 대기, 변화 검출, 조준점 위치 계산, 명중 판정은 하지 않습니다. 원시 읽기와 방아쇠·광 감지 호출은 각각 자기 시점에 레지스터를 읽으므로 신호가 변하는 동안 값이 다를 수 있습니다.
결과는 방아쇠 신호선의 논리 상태입니다. 방아쇠를 끝까지 당긴 상태가 유지된다는 보장은 없으며 스위치 동작은 장치에 따라 다릅니다. 게임의 발사 이벤트를 만들려면 이전 읽기값과 비교하여 원하는 변화를 검출하세요.
레지스터 읽기를 RAM의 시험용 값으로 돌리는 자체 테스트 ROM에서도 비트 변환을 단위 테스트합니다. 관련 없는 비트가 설정된 상태에서 00/08/10/18 조합을 확인합니다. 이 테스트는 수정하지 않은 샘플 실행이나 실제 주변기기의 동작 검증과는 별개입니다.
이 화면은 다운로드 가능한 ROM을 수정 없이 KUROSAKI에서 실행한 것입니다. 현재 입력 버스는 게임패드만 모델링하며 키보드·광선총·시리얼 어댑터 신호는 모델링하지 않습니다. 외부 입력 비트가 0으로 읽히므로 이 캡처로 해당 장치를 검증할 수 없습니다. 실제 키 입력, 광 감지, 통신은 미검증입니다.
사용 예
#include "fc_ports_scene.h"
void main(void){
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
port_result[0]=__zapper_raw1();port_result[1]=__zapper_trigger1();port_result[2]=__zapper_light1();
port_result[3]=__zapper_raw2();port_result[4]=__zapper_trigger2();port_result[5]=__zapper_light2();
port_result[6]=__zapper_trigger();port_result[7]=__zapper_light();
port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"LIGHT-GUN INPUT SNAPSHOT");m_wait();
m_text(2,5,"PORT RAW TRIGGER LIGHT");m_wait();
m_put(3,8,'1');port_hex(9,8,port_result[0]);port_number(15,8,port_result[1]);port_number(24,8,port_result[2]);m_wait();
m_put(3,10,'2');port_hex(9,10,port_result[3]);port_number(15,10,port_result[4]);port_number(24,10,port_result[5]);m_wait();
m_text(2,13,"DEFAULT = PORT 2");m_wait();
m_text(2,15,"TRIGGER");port_number(15,15,port_result[6]);m_wait();
m_text(2,17,"LIGHT");port_number(15,17,port_result[7]);m_wait();
m_text(2,21,"LIGHT 1 IS NOT A HIT TEST");m_wait();
while(1){m_wait();}
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
KUROSAKI에서는 두 포트 모두 RAW=00, TRIGGER=000, LIGHT=001로 표시됩니다. 접미사가 없는 함수도 포트 2와 같은 000/001을 표시합니다. LIGHT=001은 화면 밝기를 감지한 결과가 아니라, 구현되지 않은 입력 비트 3이 0으로 읽히기 때문입니다. 화면에도 LIGHT 1 IS NOT A HIT TEST라고 표시됩니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_zapper.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_zapper.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_zapper.nes --frames 120 --png .\out\peripheral_zapper.png구현과 소스
kitaqfc/lib/intrinsics.h:251
Default to the port-2 normalized trigger reader.
kitaqfc/kitaqfc/CodeGenerator.cs:7749
EmitHelperStart("__zapper_trigger2");
EmitNormalizeBoolFromMask(0x4017, 0x10, false);
void EmitNormalizeBoolFromMask(int port, int mask, bool invert)
{
string zero = NewGeneratedLabel("bool_zero");
string done = NewGeneratedLabel("bool_done");
EmitAsm("LDA", Mem(port));
EmitAsm("AND", Imm(mask));
if (invert) EmitAsm("BNE", Rel(zero)); else EmitAsm("BEQ", Rel(zero));
EmitAsm("LDA", Imm(1));
EmitAsm("JMP", Abs(done));
_assembly.Add(Expr.Make(Tag.Label, zero));
EmitAsm("LDA", Imm(0));
_assembly.Add(Expr.Make(Tag.Label, done));
EmitAsm("RTS");
}__zapper_trigger1
포트 1($4016)의 방아쇠 신호를 읽어 0 또는 1로 정규화합니다.
사용 형식
u8 __zapper_trigger1(void);반환값
$4016의 비트 4가 1이면 1, 그렇지 않으면 0입니다.
사용 조건과 제한
이 API는 $4016/$4017에 직접 접근합니다. 같은 포트에서 인터럽트 기반 게임패드·키보드·시리얼 연산을 끼워 넣지 마세요. 컨트롤러의 시프트 위치도 영향을 받을 수 있습니다. 예제는 입력을 캡처하는 동안 NMI를 끄고 화면 초기화 때 다시 켭니다.
순간 신호를 읽습니다. 스트로브, 프레임 대기, 변화 검출, 조준점 위치 계산, 명중 판정은 하지 않습니다. 원시 읽기와 방아쇠·광 감지 호출은 각각 자기 시점에 레지스터를 읽으므로 신호가 변하는 동안 값이 다를 수 있습니다.
결과는 방아쇠 신호선의 논리 상태입니다. 방아쇠를 끝까지 당긴 상태가 유지된다는 보장은 없으며 스위치 동작은 장치에 따라 다릅니다. 게임의 발사 이벤트를 만들려면 이전 읽기값과 비교하여 원하는 변화를 검출하세요.
레지스터 읽기를 RAM의 시험용 값으로 돌리는 자체 테스트 ROM에서도 비트 변환을 단위 테스트합니다. 관련 없는 비트가 설정된 상태에서 00/08/10/18 조합을 확인합니다. 이 테스트는 수정하지 않은 샘플 실행이나 실제 주변기기의 동작 검증과는 별개입니다.
이 화면은 다운로드 가능한 ROM을 수정 없이 KUROSAKI에서 실행한 것입니다. 현재 입력 버스는 게임패드만 모델링하며 키보드·광선총·시리얼 어댑터 신호는 모델링하지 않습니다. 외부 입력 비트가 0으로 읽히므로 이 캡처로 해당 장치를 검증할 수 없습니다. 실제 키 입력, 광 감지, 통신은 미검증입니다.
사용 예
#include "fc_ports_scene.h"
void main(void){
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
port_result[0]=__zapper_raw1();port_result[1]=__zapper_trigger1();port_result[2]=__zapper_light1();
port_result[3]=__zapper_raw2();port_result[4]=__zapper_trigger2();port_result[5]=__zapper_light2();
port_result[6]=__zapper_trigger();port_result[7]=__zapper_light();
port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"LIGHT-GUN INPUT SNAPSHOT");m_wait();
m_text(2,5,"PORT RAW TRIGGER LIGHT");m_wait();
m_put(3,8,'1');port_hex(9,8,port_result[0]);port_number(15,8,port_result[1]);port_number(24,8,port_result[2]);m_wait();
m_put(3,10,'2');port_hex(9,10,port_result[3]);port_number(15,10,port_result[4]);port_number(24,10,port_result[5]);m_wait();
m_text(2,13,"DEFAULT = PORT 2");m_wait();
m_text(2,15,"TRIGGER");port_number(15,15,port_result[6]);m_wait();
m_text(2,17,"LIGHT");port_number(15,17,port_result[7]);m_wait();
m_text(2,21,"LIGHT 1 IS NOT A HIT TEST");m_wait();
while(1){m_wait();}
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
KUROSAKI에서는 두 포트 모두 RAW=00, TRIGGER=000, LIGHT=001로 표시됩니다. 접미사가 없는 함수도 포트 2와 같은 000/001을 표시합니다. LIGHT=001은 화면 밝기를 감지한 결과가 아니라, 구현되지 않은 입력 비트 3이 0으로 읽히기 때문입니다. 화면에도 LIGHT 1 IS NOT A HIT TEST라고 표시됩니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_zapper.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_zapper.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_zapper.nes --frames 120 --png .\out\peripheral_zapper.png구현과 소스
kitaqfc/lib/intrinsics.h:243
Return port-1 trigger bit 4 normalized to 0 or 1.
kitaqfc/kitaqfc/CodeGenerator.cs:7747
EmitHelperStart("__zapper_trigger1");
EmitNormalizeBoolFromMask(0x4016, 0x10, false);
void EmitNormalizeBoolFromMask(int port, int mask, bool invert)
{
string zero = NewGeneratedLabel("bool_zero");
string done = NewGeneratedLabel("bool_done");
EmitAsm("LDA", Mem(port));
EmitAsm("AND", Imm(mask));
if (invert) EmitAsm("BNE", Rel(zero)); else EmitAsm("BEQ", Rel(zero));
EmitAsm("LDA", Imm(1));
EmitAsm("JMP", Abs(done));
_assembly.Add(Expr.Make(Tag.Label, zero));
EmitAsm("LDA", Imm(0));
_assembly.Add(Expr.Make(Tag.Label, done));
EmitAsm("RTS");
}__zapper_trigger2
포트 2($4017)의 방아쇠 신호를 읽어 0 또는 1로 정규화합니다.
사용 형식
u8 __zapper_trigger2(void);반환값
$4017의 비트 4가 1이면 1, 그렇지 않으면 0입니다.
사용 조건과 제한
이 API는 $4016/$4017에 직접 접근합니다. 같은 포트에서 인터럽트 기반 게임패드·키보드·시리얼 연산을 끼워 넣지 마세요. 컨트롤러의 시프트 위치도 영향을 받을 수 있습니다. 예제는 입력을 캡처하는 동안 NMI를 끄고 화면 초기화 때 다시 켭니다.
순간 신호를 읽습니다. 스트로브, 프레임 대기, 변화 검출, 조준점 위치 계산, 명중 판정은 하지 않습니다. 원시 읽기와 방아쇠·광 감지 호출은 각각 자기 시점에 레지스터를 읽으므로 신호가 변하는 동안 값이 다를 수 있습니다.
결과는 방아쇠 신호선의 논리 상태입니다. 방아쇠를 끝까지 당긴 상태가 유지된다는 보장은 없으며 스위치 동작은 장치에 따라 다릅니다. 게임의 발사 이벤트를 만들려면 이전 읽기값과 비교하여 원하는 변화를 검출하세요.
레지스터 읽기를 RAM의 시험용 값으로 돌리는 자체 테스트 ROM에서도 비트 변환을 단위 테스트합니다. 관련 없는 비트가 설정된 상태에서 00/08/10/18 조합을 확인합니다. 이 테스트는 수정하지 않은 샘플 실행이나 실제 주변기기의 동작 검증과는 별개입니다.
이 화면은 다운로드 가능한 ROM을 수정 없이 KUROSAKI에서 실행한 것입니다. 현재 입력 버스는 게임패드만 모델링하며 키보드·광선총·시리얼 어댑터 신호는 모델링하지 않습니다. 외부 입력 비트가 0으로 읽히므로 이 캡처로 해당 장치를 검증할 수 없습니다. 실제 키 입력, 광 감지, 통신은 미검증입니다.
사용 예
#include "fc_ports_scene.h"
void main(void){
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
port_result[0]=__zapper_raw1();port_result[1]=__zapper_trigger1();port_result[2]=__zapper_light1();
port_result[3]=__zapper_raw2();port_result[4]=__zapper_trigger2();port_result[5]=__zapper_light2();
port_result[6]=__zapper_trigger();port_result[7]=__zapper_light();
port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"LIGHT-GUN INPUT SNAPSHOT");m_wait();
m_text(2,5,"PORT RAW TRIGGER LIGHT");m_wait();
m_put(3,8,'1');port_hex(9,8,port_result[0]);port_number(15,8,port_result[1]);port_number(24,8,port_result[2]);m_wait();
m_put(3,10,'2');port_hex(9,10,port_result[3]);port_number(15,10,port_result[4]);port_number(24,10,port_result[5]);m_wait();
m_text(2,13,"DEFAULT = PORT 2");m_wait();
m_text(2,15,"TRIGGER");port_number(15,15,port_result[6]);m_wait();
m_text(2,17,"LIGHT");port_number(15,17,port_result[7]);m_wait();
m_text(2,21,"LIGHT 1 IS NOT A HIT TEST");m_wait();
while(1){m_wait();}
}호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
KUROSAKI에서는 두 포트 모두 RAW=00, TRIGGER=000, LIGHT=001로 표시됩니다. 접미사가 없는 함수도 포트 2와 같은 000/001을 표시합니다. LIGHT=001은 화면 밝기를 감지한 결과가 아니라, 구현되지 않은 입력 비트 3이 0으로 읽히기 때문입니다. 화면에도 LIGHT 1 IS NOT A HIT TEST라고 표시됩니다.

빌드 명령
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_zapper.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_zapper.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_zapper.nes --frames 120 --png .\out\peripheral_zapper.png구현과 소스
kitaqfc/lib/intrinsics.h:245
Return port-2 trigger bit 4 normalized to 0 or 1.
kitaqfc/kitaqfc/CodeGenerator.cs:7749
EmitHelperStart("__zapper_trigger2");
EmitNormalizeBoolFromMask(0x4017, 0x10, false);
void EmitNormalizeBoolFromMask(int port, int mask, bool invert)
{
string zero = NewGeneratedLabel("bool_zero");
string done = NewGeneratedLabel("bool_done");
EmitAsm("LDA", Mem(port));
EmitAsm("AND", Imm(mask));
if (invert) EmitAsm("BNE", Rel(zero)); else EmitAsm("BEQ", Rel(zero));
EmitAsm("LDA", Imm(1));
EmitAsm("JMP", Abs(done));
_assembly.Add(Expr.Make(Tag.Label, zero));
EmitAsm("LDA", Imm(0));
_assembly.Add(Expr.Make(Tag.Label, done));
EmitAsm("RTS");
}명령 문법 참조
아래 도움말은 이 소스 판본에서 수집한 것입니다. 선택 인수, 기본값, 허용값을 확인하고 예시 파일 이름은 자신의 파일로 바꾸세요. 실제 도구 응답은 원문을 유지합니다.
kitaqfc
KITAQFC native Rust compiler
Build a NES ROM:
kitaqfc SOURCE.c ... -I INCLUDE --mapper=nrom -o GAME.nes
kitaqfc SOURCE.c ... -I lib --mapper=mmc3 --nes-chr=tiles.chr -O2 -o GAME.nes
Build a Famicom Disk System image:
kitaqfc SOURCE.c ... --mapper=fds --fds-layout=fds32 -o GAME.fds
--fds-meta=FILE --fds-meta-out[=FILE] --fds-out[=FILE]
--fds-auto-overlay / --fds-no-auto-overlay
--fds-overlay-farcall / --fds-no-overlay-farcall
--fds-overlay-guard / --fds-no-overlay-guard
--fds-overlay-trim --fds-overlay-prefix=NAME --fds-overlay-start-id=N
--fds-overlay-table / --fds-no-overlay-table
--fds-header / --fds-raw --fds-game-code=CODE
--fds-license-bypass / --fds-no-license-bypass
Cartridge and optimization:
--target=nes|fc|famicom|6502|2a03
--mapper=nrom|uxrom|cnrom|axrom|mmc1|mmc3|mmc5|vrc6|vrc7|fme7|fds|surom512
--board=auto|generic|surom512 --mirroring=horizontal|vertical|four_screen
--battery / --no-battery --nes-chr-ram --nes-chr=FILE
-O0 / -O1 / -O2 --nes-opt=all|off
--abi=legacy|stack|fastcall --reg-cc-v2 / --no-reg-cc-v2
--zp-alloc --static-frame --small-inline --loop-lowering
--library-lto-lite --mapper-aware-placement --kurosaki-profile=FILE
--nes-local-ram=START:LENGTH --nes-temp-ram=START:LENGTH
-Zcheck -Zcheck-bounds -Zconst-scalar-in-rom
Diagnostics and reports:
--strict / --permissive --max-errors=N --diag-json[=FILE|-]
--profile=dev|release|test --cache / --no-cache --deps-out[=FILE]
--debug-output[=DIR] --trace[=tokens,ast,ir,asm] --trace-out=DIR
--no-debug-output --no-trace --fast-build --emit-path-manifest=FILE
--kurosaki-metadata[=FILE] --nes-opt-report[=FILE]
--func-size-report --hotspot-report --cross-bank-report --farcall-suggest
--abi-verify --abi-diff-report --bank-sim --opt-diff --rst-report --repro-check
Report switches accept =FILE; otherwise reports are placed beside the ROM.
Frontend and typed assembly:
tokenize SOURCE.c ... -I INCLUDE -o tokens.json
parse SOURCE.c ... -I INCLUDE -o ast.json
lower SOURCE.c ... -I INCLUDE -o lowered.json
emit-ir SOURCE.c ... -I INCLUDE -o assembly.json
assemble-ir ASSEMBLY.json --mapper=nrom -o ROM.nes
Utilities and development:
kqhelp, symfind, src2asm, romdiff, template, snippet,
conventions, fixhint, irsum, attrviz, recipe, test
SOURCE.c ... --watch [--watch-max-builds=N]
devserver SOURCE.c ... [--once --poll-ms=N --debounce-ms=N --max-builds=N]
SOURCE.c ... --repro-pack[=DIR] --auto-minimize
SOURCE.c ... --minimize[=FILE] --minimize-work=DIR --minimize-out=FILE
Legacy ROM-header and RST flags retain the C# NES backend's behavior.
NES disassembly and managed debugger attachment do not run in that backend.개발용 명령 문법
사용법은 Program.DebugTools.cs와 Program.VibeTools.cs에서 수집했습니다. ROM 비교, 심볼 검색, 템플릿은 일반 C 컴파일과 별도의 하위 명령입니다.
usage: kitaqfc kqhelp <KQxxxx>usage: kitaqfc symfind <pattern> [--map=<.map>] [--dbg=<.dbg>] [--regex] [--ignore-case] [--out=<file>|-]usage: kitaqfc src2asm <source.c:line> [--disasm=<dis.s>] [--funcsizes=<.funcsizes.txt>] [--context=N] [--out=<file>|-]usage: kitaqfc romdiff <old.nes> <new.nes> [--old-func=<.funcsizes.txt>] [--new-func=<.funcsizes.txt>] [--old-map=<.map>] [--new-map=<.map>] [--out=<file>|-]usage: kitaqfc template [out.c] [--out=<file.c>] [--overwrite] [--no-build-script] [--no-readme]usage: kitaqfc conventions [--out=<file>|--stdout]usage: kitaqfc snippet list | get <id> [--out=<file>|-] | emit <category|all> [--out=<file>|-]usage: kitaqfc fixhint [diag.json|stderr.log] [--in=<file>] [--out=<file>|-] [--max=N]usage: kitaqfc irsum [--ir=<syntax_tree_lowered.txt>] [--asm=<assembly_code.txt>] [--top=N] [--out=<file>|-]usage: kitaqfc devserver [--poll-ms=N] [--debounce-ms=N] [--once] [--max-builds=N] [--deps=<file>] <compile args...>usage: kitaqfc recipe [--history=<file>] [--last=N] [--out=<file>] [--script=<file>] [--stdout]명령줄 분석기가 인식하는 긴 옵션
호환·조사용 옵션을 포함하여 src/cli.rs에서 표기를 추출했습니다. 각 옵션에 값이 필요한지는 도움말과 해당 처리기를 확인하세요.
--abi--abi-diff-report--abi-verify--attach--auto-inline-small--auto-minimize--bank-sim--battery--board--cache--cart--cgb--cgb-consistency--chr-rom--container--cross-bank-report--debug-out--debug-output--deps-out--dest--diag-json--disasm--disasm-changed--disasm-changed-out--emit-ai-metadata--farcall-suggest--fast--fast-build--fastcall-v2--fds-approval-bypass--fds-auto-overlay--fds-auto-overlays--fds-builder-manifest--fds-bypass-license--fds-game-code--fds-header--fds-image--fds-layout--fds-license-bypass--fds-manifest--fds-meta--fds-meta-out--fds-metadata--fds-metadata-out--fds-no-approval-bypass--fds-no-auto-overlay--fds-no-auto-overlays--fds-no-header--fds-no-license-bypass--fds-no-overlay-farcall--fds-no-overlay-guard--fds-no-overlay-table--fds-no-residency-guard--fds-out--fds-overlay-farcall--fds-overlay-guard--fds-overlay-id-base--fds-overlay-no-trim--fds-overlay-prefix--fds-overlay-start-id--fds-overlay-table--fds-overlay-trim--fds-raw--fds-residency-guard--func-size-report--help--hotspot-report--include-dir--kurosaki-meta--kurosaki-metadata--kurosaki-out--kurosaki-profile--library-lto-lite--loop-lowering--lto-lite--mapper--mapper-aware-placement--max-errors--minimize-on-fail--mirroring--nes-chr--nes-chr-ram--nes-local-ram--nes-opt--nes-opt-report--nes-temp-ram--no-auto-inline-small--no-battery--no-cache--no-disasm--no-fastcall-v2--no-library-lto-lite--no-loop-lowering--no-lto-lite--no-mapper-aware-placement--no-reg-cc-v2--no-rst--no-small-inline--no-static-frame--no-whole-program-zp--no-zp-alloc--opt-diff--output-format--permissive--profile--ramsize--reg-cc-v2--repro-check--repro-pack--rom-header--rom-title--romsize--rst--rst-disable--rst-enable--rst-exclude--rst-max-calls--rst-max-vectors--rst-off--rst-on--rst-report--rst-safe--rst-speed-safe--rst-unsafe--rst-use-38--sgb--small-inline--static-frame--strict--target--trace--trace-out--verify-cgb-symbols--version--vlist--vlist-out--whole-program-zp--zp-alloc부록: 어셈블리 명령어 색인
src/machine/opcodes.rs에서 추출한 표로, 컴파일러 내부 표기와 피연산자 형식을 설명합니다. 분기 대상이나 메모리 주소가 들어 있는 항목은 문법 예이며 독립 프로그램이 아닙니다. 보존 레지스터와 플래그 변화는 호출 규약과 코드 생성에서 확인하세요.
| opcode | 명령어 | 형식 | 문법 예 |
|---|---|---|---|
| 00 | NOP | IMP | NOP |
| F3 | DI | IMP | DI |
| FB | EI | IMP | EI |
| 76 | HALT | IMP | HALT |
| 10 | STOP | IMM8 | STOP #1 |
| C9 | RET | IMP | RET |
| D9 | RETI | IMP | RETI |
| C3 | JP | ABS | JP 0xC000 |
| E9 | JP_HL | IMP | JP_HL |
| 18 | JR | REL | JR +target |
| 20 | JR_NZ | REL | JR_NZ +target |
| 28 | JR_Z | REL | JR_Z +target |
| 30 | JR_NC | REL | JR_NC +target |
| 38 | JR_C | REL | JR_C +target |
| CD | CALL | ABS | CALL 0xC000 |
| C4 | CALL_NZ | ABS | CALL_NZ 0xC000 |
| CC | CALL_Z | ABS | CALL_Z 0xC000 |
| D4 | CALL_NC | ABS | CALL_NC 0xC000 |
| DC | CALL_C | ABS | CALL_C 0xC000 |
| C2 | JP_NZ | ABS | JP_NZ 0xC000 |
| CA | JP_Z | ABS | JP_Z 0xC000 |
| D2 | JP_NC | ABS | JP_NC 0xC000 |
| DA | JP_C | ABS | JP_C 0xC000 |
| C7 | RST_00 | IMP | RST_00 |
| CF | RST_08 | IMP | RST_08 |
| D7 | RST_10 | IMP | RST_10 |
| DF | RST_18 | IMP | RST_18 |
| E7 | RST_20 | IMP | RST_20 |
| EF | RST_28 | IMP | RST_28 |
| F7 | RST_30 | IMP | RST_30 |
| FF | RST_38 | IMP | RST_38 |
| 3E | LD_A_IMM | IMM8 | LD_A_IMM #1 |
| 06 | LD_B_IMM | IMM8 | LD_B_IMM #1 |
| 0E | LD_C_IMM | IMM8 | LD_C_IMM #1 |
| 16 | LD_D_IMM | IMM8 | LD_D_IMM #1 |
| 1E | LD_E_IMM | IMM8 | LD_E_IMM #1 |
| 26 | LD_H_IMM | IMM8 | LD_H_IMM #1 |
| 2E | LD_L_IMM | IMM8 | LD_L_IMM #1 |
| 7F | LD_A_A | IMP | LD_A_A |
| 78 | LD_A_B | IMP | LD_A_B |
| 79 | LD_A_C | IMP | LD_A_C |
| 7A | LD_A_D | IMP | LD_A_D |
| 7B | LD_A_E | IMP | LD_A_E |
| 7C | LD_A_H | IMP | LD_A_H |
| 7D | LD_A_L | IMP | LD_A_L |
| 47 | LD_B_A | IMP | LD_B_A |
| 40 | LD_B_B | IMP | LD_B_B |
| 41 | LD_B_C | IMP | LD_B_C |
| 42 | LD_B_D | IMP | LD_B_D |
| 43 | LD_B_E | IMP | LD_B_E |
| 44 | LD_B_H | IMP | LD_B_H |
| 45 | LD_B_L | IMP | LD_B_L |
| 4F | LD_C_A | IMP | LD_C_A |
| 48 | LD_C_B | IMP | LD_C_B |
| 49 | LD_C_C | IMP | LD_C_C |
| 4A | LD_C_D | IMP | LD_C_D |
| 4B | LD_C_E | IMP | LD_C_E |
| 4C | LD_C_H | IMP | LD_C_H |
| 4D | LD_C_L | IMP | LD_C_L |
| 57 | LD_D_A | IMP | LD_D_A |
| 50 | LD_D_B | IMP | LD_D_B |
| 51 | LD_D_C | IMP | LD_D_C |
| 52 | LD_D_D | IMP | LD_D_D |
| 53 | LD_D_E | IMP | LD_D_E |
| 54 | LD_D_H | IMP | LD_D_H |
| 55 | LD_D_L | IMP | LD_D_L |
| 5F | LD_E_A | IMP | LD_E_A |
| 58 | LD_E_B | IMP | LD_E_B |
| 59 | LD_E_C | IMP | LD_E_C |
| 5A | LD_E_D | IMP | LD_E_D |
| 5B | LD_E_E | IMP | LD_E_E |
| 5C | LD_E_H | IMP | LD_E_H |
| 5D | LD_E_L | IMP | LD_E_L |
| 67 | LD_H_A | IMP | LD_H_A |
| 60 | LD_H_B | IMP | LD_H_B |
| 61 | LD_H_C | IMP | LD_H_C |
| 62 | LD_H_D | IMP | LD_H_D |
| 63 | LD_H_E | IMP | LD_H_E |
| 64 | LD_H_H | IMP | LD_H_H |
| 65 | LD_H_L | IMP | LD_H_L |
| 6F | LD_L_A | IMP | LD_L_A |
| 68 | LD_L_B | IMP | LD_L_B |
| 69 | LD_L_C | IMP | LD_L_C |
| 6A | LD_L_D | IMP | LD_L_D |
| 6B | LD_L_E | IMP | LD_L_E |
| 6C | LD_L_H | IMP | LD_L_H |
| 6D | LD_L_L | IMP | LD_L_L |
| EA | LD_MEM_A | ABS | LD_MEM_A 0xC000 |
| FA | LD_A_MEM | ABS | LD_A_MEM 0xC000 |
| E0 | LDH_MEM_A | LDH | LDH_MEM_A 0x40 |
| F0 | LDH_A_MEM | LDH | LDH_A_MEM 0x40 |
| E2 | LD_C_MEM_A | IMP | LD_C_MEM_A |
| F2 | LD_A_MEM_C | IMP | LD_A_MEM_C |
| 77 | LD_HL_A | IMP | LD_HL_A |
| 7E | LD_A_HL | IMP | LD_A_HL |
| 70 | LD_HL_B | IMP | LD_HL_B |
| 71 | LD_HL_C | IMP | LD_HL_C |
| 72 | LD_HL_D | IMP | LD_HL_D |
| 73 | LD_HL_E | IMP | LD_HL_E |
| 74 | LD_HL_H | IMP | LD_HL_H |
| 75 | LD_HL_L | IMP | LD_HL_L |
| 36 | LD_HL_REF_IMM | IMM8 | LD_HL_REF_IMM #1 |
| 12 | LD_DE_A | IMP | LD_DE_A |
| 1A | LD_A_DE | IMP | LD_A_DE |
| 02 | LD_BC_A | IMP | LD_BC_A |
| 0A | LD_A_BC | IMP | LD_A_BC |
| 22 | LDI_HL_A | IMP | LDI_HL_A |
| 2A | LDI_A_HL | IMP | LDI_A_HL |
| 32 | LDD_HL_A | IMP | LDD_HL_A |
| 3A | LDD_A_HL | IMP | LDD_A_HL |
| 01 | LD_BC_IMM | IMM16 | LD_BC_IMM #0xC000 |
| 11 | LD_DE_IMM | IMM16 | LD_DE_IMM #0xC000 |
| 21 | LD_HL_IMM | IMM16 | LD_HL_IMM #0xC000 |
| 31 | LD_SP_IMM | IMM16 | LD_SP_IMM #0xC000 |
| F9 | LD_SP_HL | IMP | LD_SP_HL |
| F8 | LD_HL_SP_IMM | REL | LD_HL_SP_IMM +target |
| E8 | ADD_SP_IMM | REL | ADD_SP_IMM +target |
| 08 | LD_MEM_SP | IMM16 | LD_MEM_SP #0xC000 |
| C5 | PUSH_BC | IMP | PUSH_BC |
| C1 | POP_BC | IMP | POP_BC |
| D5 | PUSH_DE | IMP | PUSH_DE |
| D1 | POP_DE | IMP | POP_DE |
| E5 | PUSH_HL | IMP | PUSH_HL |
| E1 | POP_HL | IMP | POP_HL |
| F5 | PUSH_AF | IMP | PUSH_AF |
| F1 | POP_AF | IMP | POP_AF |
| 3C | INC_A | IMP | INC_A |
| 04 | INC_B | IMP | INC_B |
| 0C | INC_C | IMP | INC_C |
| 14 | INC_D | IMP | INC_D |
| 1C | INC_E | IMP | INC_E |
| 24 | INC_H | IMP | INC_H |
| 2C | INC_L | IMP | INC_L |
| 34 | INC_HL_REF | IMP | INC_HL_REF |
| 3D | DEC_A | IMP | DEC_A |
| 05 | DEC_B | IMP | DEC_B |
| 0D | DEC_C | IMP | DEC_C |
| 15 | DEC_D | IMP | DEC_D |
| 1D | DEC_E | IMP | DEC_E |
| 25 | DEC_H | IMP | DEC_H |
| 2D | DEC_L | IMP | DEC_L |
| 35 | DEC_HL_REF | IMP | DEC_HL_REF |
| 87 | ADD_A | IMP | ADD_A |
| 80 | ADD_B | IMP | ADD_B |
| 81 | ADD_C | IMP | ADD_C |
| 82 | ADD_D | IMP | ADD_D |
| 83 | ADD_E | IMP | ADD_E |
| 84 | ADD_H | IMP | ADD_H |
| 85 | ADD_L | IMP | ADD_L |
| 86 | ADD_HL_REF | IMP | ADD_HL_REF |
| C6 | ADD_A_IMM | IMM8 | ADD_A_IMM #1 |
| 8F | ADC_A | IMP | ADC_A |
| 88 | ADC_B | IMP | ADC_B |
| 89 | ADC_C | IMP | ADC_C |
| 8A | ADC_D | IMP | ADC_D |
| 8B | ADC_E | IMP | ADC_E |
| 8C | ADC_H | IMP | ADC_H |
| 8D | ADC_L | IMP | ADC_L |
| 8E | ADC_HL_REF | IMP | ADC_HL_REF |
| CE | ADC_IMM | IMM8 | ADC_IMM #1 |
| 97 | SUB_A | IMP | SUB_A |
| 90 | SUB_B | IMP | SUB_B |
| 91 | SUB_C | IMP | SUB_C |
| 92 | SUB_D | IMP | SUB_D |
| 93 | SUB_E | IMP | SUB_E |
| 94 | SUB_H | IMP | SUB_H |
| 95 | SUB_L | IMP | SUB_L |
| 96 | SUB_HL_REF | IMP | SUB_HL_REF |
| D6 | SUB_IMM | IMM8 | SUB_IMM #1 |
| 9F | SBC_A | IMP | SBC_A |
| 98 | SBC_B | IMP | SBC_B |
| 99 | SBC_C | IMP | SBC_C |
| 9A | SBC_D | IMP | SBC_D |
| 9B | SBC_E | IMP | SBC_E |
| 9C | SBC_H | IMP | SBC_H |
| 9D | SBC_L | IMP | SBC_L |
| 9E | SBC_HL_REF | IMP | SBC_HL_REF |
| DE | SBC_IMM | IMM8 | SBC_IMM #1 |
| A7 | AND_A | IMP | AND_A |
| A0 | AND_B | IMP | AND_B |
| A1 | AND_C | IMP | AND_C |
| A2 | AND_D | IMP | AND_D |
| A3 | AND_E | IMP | AND_E |
| A4 | AND_H | IMP | AND_H |
| A5 | AND_L | IMP | AND_L |
| A6 | AND_HL_REF | IMP | AND_HL_REF |
| E6 | AND_IMM | IMM8 | AND_IMM #1 |
| B7 | OR_A | IMP | OR_A |
| B0 | OR_B | IMP | OR_B |
| B1 | OR_C | IMP | OR_C |
| B2 | OR_D | IMP | OR_D |
| B3 | OR_E | IMP | OR_E |
| B4 | OR_H | IMP | OR_H |
| B5 | OR_L | IMP | OR_L |
| B6 | OR_HL_REF | IMP | OR_HL_REF |
| F6 | OR_IMM | IMM8 | OR_IMM #1 |
| AF | XOR_A | IMP | XOR_A |
| A8 | XOR_B | IMP | XOR_B |
| A9 | XOR_C | IMP | XOR_C |
| AA | XOR_D | IMP | XOR_D |
| AB | XOR_E | IMP | XOR_E |
| AC | XOR_H | IMP | XOR_H |
| AD | XOR_L | IMP | XOR_L |
| AE | XOR_HL_REF | IMP | XOR_HL_REF |
| EE | XOR_IMM | IMM8 | XOR_IMM #1 |
| BF | CP_A | IMP | CP_A |
| B8 | CP_B | IMP | CP_B |
| B9 | CP_C | IMP | CP_C |
| BA | CP_D | IMP | CP_D |
| BB | CP_E | IMP | CP_E |
| BC | CP_H | IMP | CP_H |
| BD | CP_L | IMP | CP_L |
| BE | CP_HL_REF | IMP | CP_HL_REF |
| FE | CP_IMM | IMM8 | CP_IMM #1 |
| 03 | INC_BC | IMP | INC_BC |
| 0B | DEC_BC | IMP | DEC_BC |
| 13 | INC_DE | IMP | INC_DE |
| 1B | DEC_DE | IMP | DEC_DE |
| 23 | INC_HL | IMP | INC_HL |
| 2B | DEC_HL | IMP | DEC_HL |
| 33 | INC_SP | IMP | INC_SP |
| 3B | DEC_SP | IMP | DEC_SP |
| 09 | ADD_HL_BC | IMP | ADD_HL_BC |
| 19 | ADD_HL_DE | IMP | ADD_HL_DE |
| 29 | ADD_HL_HL | IMP | ADD_HL_HL |
| 39 | ADD_HL_SP | IMP | ADD_HL_SP |
| 07 | RLCA | IMP | RLCA |
| 17 | RLA | IMP | RLA |
| 0F | RRCA | IMP | RRCA |
| 1F | RRA | IMP | RRA |
| CB | PREFIX_CB | IMP | PREFIX_CB |
| 2F | CPL | IMP | CPL |
| 3F | CCF | IMP | CCF |
| 37 | SCF | IMP | SCF |
| 27 | DAA | IMP | DAA |