KITAQ 開發系統
使用手冊 2026.09

KITAQ SERIES • 參考版

KITAQFC

FC 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,或使用完整路徑執行。

OSkitaqfc
Windows x64kitaqfc.exe
Linux x64bin/Linux-X64/kitaqfc
macOS ARM64bin/macOS-ARM64/kitaqfc
macOS Intelbin/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 保留原範例字型。

sh
kitaqfc game.c -I lib -o game.nes --mapper=nrom

目前編譯器與程式庫的發行配置 — 2026年10月4日

請使用同一份簽出版本中的 kitaqfc.exe 和 lib。在父目錄執行下列 Release 建置指令碼後,專案設定會將執行檔複製到儲存庫根目錄。BINARY_BUILD.json 記錄編譯器原始檔、執行檔的雜湊及該次建置的驗證結果。過長的診斷行僅顯示200個字元和省略號。

使用29個輸入比較目前C#建置與先前發行版。接受的15個輸入產生完全相同的ROM位元組,另外14個輸入產生相同的拒絕診斷。另外將兩個ROM各在KUROSAKI執行4影格,分別檢查實體配置、回呼、槽位重用,以及有號運算、KQBody3D 配置和音訊佇列常數。報告列出18位元組RAM的預期值與實際值。這些檢查未涵蓋音訊播放或全部API;先前的回歸測試結果在 BINARY_BUILD.json 中分開標示。

.\kitaqfc\scripts\build.ps1
.\kitaqfc\kitaqfc.exe --help

原始碼範例與執行驗證紀錄

1. KITAQFC 與 GB 編譯器的關係

KITAQFC 使用 KITAQGB 的前端,為 NES/FC 的 6502 系列 CPU 產生程式碼。它不會把 GB ROM 轉成 NES ROM;程式仍須依目標機器的畫面、聲音、記憶體與 mapper 設計。

KITAQFC 支援結構複製、一般函式呼叫,以及 for、while 和 do-while。do-while 至少執行一次迴圈本體,再檢查條件;continue 會跳到最後的條件檢查,break 則離開迴圈。switch 依選擇值比對 0..255 範圍內的 case 常數,沒有符合的 case 時執行 default 本體。選擇運算式只求值一次。break 離開最內層的迴圈或 switch;switch 裡的 continue 則讓外層迴圈進入下一次反覆執行。

2. 開發環境與建置

使用 Rust 1.85 或更新版本,可為 Windows、Linux、macOS ARM 和 macOS Intel 建置編譯器及所有輔助工具。原生程式執行不需要 .NET,素材處理工具也不需要 Python 或 Pillow。

sh
cd kitaqfc
cargo test --locked --tests
cargo build --locked --release

Windows 程式位於儲存庫根目錄;Linux 和 macOS 程式位於下表所列的 bin/ 平台資料夾。請保留 lib/ 和授權聲明。在 Linux/macOS 下載後執行 chmod +x,並將資料夾加入 PATH,或使用完整路徑執行。

OSkitaqfc
Windows x64kitaqfc.exe
Linux x64bin/Linux-X64/kitaqfc
macOS ARM64bin/macOS-ARM64/kitaqfc
macOS Intelbin/macOS-X64/kitaqfc
powershell
.\scripts\build.ps1
.\kitaqfc.exe --help
sh
sh scripts/build.sh

PowerShell 範例建置使用根目錄的 Windows 程式。在 Linux/macOS 使用原生編譯器並傳入相同的 C 輸入及選項,或使用 PowerShell 7。CHR 圖像與 C 原始碼是不同輸入;font.chr 保留原範例字型。

3. 第一個程式

c
#include "fc_common.h"
void main(void) {
    m_init();
    m_text(2, 5, "HELLO WORLD");
    m_number(42);
    while (1) { m_wait(); }
}
powershell
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\fc_hello.c -I .\kitaq-docs\samples -I .\kitaqfc\lib --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr -o .\out\hello.nes --kurosaki-metadata=.\out\hello.debug.json

範例顯示 HELLO WORLD 和 042。m_wait 提交 VRAM 佇列、等待 NMI,再還原捲動畫面的位置。透過 PPUADDR 傳輸會影響內部捲動狀態,若漏掉還原,文字可能偏向邊緣。請熟悉「關閉顯示、準備素材、開啟顯示、與 NMI 同步」的順序。

4. 語言入門

GB 卷的敘述與運算式是共同基礎。FC 接受 unsigned char、unsigned short;core.h 定義 u8、u16、s8、s16,fc.h 是統整用標頭。這裡可使用無引數入口 void main(void)。

c
#include "core.h"
u8 clamp_score(u8 n) {
    if (n > 99) return 99;
    return n;
}

整數應在 8 或 16 位元範圍內使用,陣列索引從 0 開始。fc_aggregate.c 示範函式、指標、結構,fc_arithmetic.c 示範算術,fc_control.c 示範迴圈。不要加入 CGB 暫存器或 GB 專用內建函式。

運算式結果與求值

比較運算子 ==、!=、<、<=、>、>= 與邏輯運算子 !、&&、|| 在假時回傳 0,真時回傳 1。結果可存入 u16、作為參數傳遞或回傳,也可參與算術運算。例如 score = 500 + (lives != 0); 在仍有生命時得到 501,否則得到 500。

&& 在左運算元為零時不求值右運算元;|| 在左運算元非零時不求值右運算元。在 pointer != 0 && pointer->active != 0 中,空指標會阻止成員存取。16 位元值的兩個位元組都會參與真假判斷,因此 256 為真。位元運算子 & 和 | 不會短路求值。

++value 回傳更新後的值,value++ 回傳原本的值。這些運算子也可用於陣列元素、解參照的指標與結構成員。buffer[index()]++ 只會呼叫 index() 一次。對 u16 *p 而言,p++ 前進兩個位元組以指向下一個元素,(*p)++ 則增加所指向的值。

sizeof(array) 給出整個陣列的位元組大小,sizeof(pointer) 則是 2。對 u16 values[9]; 而言,sizeof(values) 是 18。ROM 陣列、區域陣列與陣列成員皆適用。sizeof(function()) 只檢查回傳型別,不會呼叫函式。

函式宣告與定義的參數型別及順序必須一致,但參數名稱可以不同;函式本體使用定義中的名稱。例如,u8 next(u8 input); 可以定義為 u8 next(u8 value) { return (u8)(value + 1); }。參數與區域變數會遮蔽同名的全域變數。

使用 ?: 選擇陣列或字串時,結果是指向所選元素型別的指標。可直接將它傳入函式,例如 show(ready ? "READY" : "WAIT");。對 u16 陣列 a 和 b 而言,(ready ? a : b) + 1 會前進兩個位元組,指向所選陣列的第二個元素;不會複製陣列。

condition ? yes : no 先求值條件,再只求值被選中的分支。條件與兩個分支都可包含位移量由變數決定的移位運算。例如 on = (pattern & (0x80 >> bit)) != 0 ? 4 : 2; 依所選位元決定使用 4 或 2。被 && 或 || 略過的右運算元內,函式呼叫也不會執行。for 迴圈中的 continue 會先執行一次更新運算式,再重新判斷條件;在 while 和 do ... while 中,則跳到條件判斷。

5. 迴圈與狀態分派

c
do {
    update();
} while (condition);

// Dispatch a state with if/else.
if (state == 0) { title_update(); }
else if (state == 1) { game_update(); }
else { pause_update(); }

這些片段示範至少更新一次的迴圈,以及依目前狀態選擇處理函式的分支。請在程式中定義 update、condition、state 和各狀態處理函式。完整迴圈範例請見 fc_control.c;完整場景管理 ROM 請見函式庫的影格與場景範例。

請依宣告要求的參數與回傳型別註冊場景、實體與系統回呼。函式庫會在對應的更新、繪製或影格等待操作中呼叫已註冊的處理函式。請遵守各 API 的 ROM 記憶體庫與映射要求,不要假設遞迴或可變參數函式具有與桌面環境相同的支援。

6. 記憶體與 PPU

NES CPU 內部 RAM 位於 0x0000~0x07FF;0x0800 以上的鏡射區域不是額外 RAM。6502 堆疊使用第 1 頁,OAM 影子緩衝區與佇列也會保留其他區域。--nes-local-ram=START:LENGTH、--nes-temp-ram=START:LENGTH 是需要檢查配置的進階設定。

PPU 有獨立位址空間。CHR 提供圖樣,名稱表安排圖塊,屬性表選擇調色盤群組,調色盤存放色彩編號。背景屬性通常套用至 16×16 像素區域,與 GB 圖塊屬性不同。

7. NMI 與 VRAM 佇列

NMI 是與顯示影格邊界相關的中斷。顯示期間直接大量寫入 PPU,可能破壞畫面。初始化應在關閉顯示時直接完成;平常更新則用 __vramq_put、__vramq_copy、__vramq_fill 並提交佇列。

c
__vramq_put(0x2000 + 8 * 32 + 3, '4');
__vramq_commit();
__nmi_wait();
__scroll_set(0, 0);

檢查佇列容量和來源資料的生命週期。預設 NMI 會處理佇列。自行編寫 __nes_nmi 時,須保留必要的佇列執行、OAM 工作和暫存器保存。

8. Mapper 與 ROM 配置

選項常見入門用途
nrom小型固定 ROM 教學程式
uxrom / cnrom / axrom簡單 PRG 或 CHR 切換
mmc1 / mmc3 / mmc5大型程式與 mapper 專屬功能
vrc6 / vrc7 / fme7分頁及相應擴充功能
fds磁碟映像檔輸出

這些是編譯器選項,不是硬體或模擬器的完成度表。--board=surom512 選擇特定 MMC1 電路板配置;只把檔案填充到 512 KiB,並不會建立該配置。請搭配 KUROSAKI 的電路板檢查。

powershell
.\kitaqfc\kitaqfc.exe main.c --mapper=mmc3 --nes-chr=tiles.chr --mirroring=vertical -o .\out\game.nes --kurosaki-metadata=.\out\game.debug.json --emit-ai-metadata=.\out\build.json

確認 --battery / --no-battery、CHR 容量、PRG 配置與跨 bank 呼叫符合電路板要求。變更 mapper 或鏡射方式後,除了產生 ROM,也要測試啟動、捲動和資料切換。

8.1 使用 CHR RAM 更新圖形

--nes-chr-ram 會建置使用 8 KiB 可寫入 CHR RAM 的卡匣 ROM。ROM 檔案不儲存 CHR 資料,因此程式必須在初始化時將圖塊圖樣上傳至 PPU。wire3d.c 繪圖器使用此模式。

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\wire3d_projection.c -I .\kitaqfc\lib --mapper=nrom --nes-chr-ram -o .\out\wire3d_projection.nes

--nes-chr=tiles.chr 或 --chr-rom=tiles.chr 會將準備好的圖樣納入 CHR ROM。這兩個選項都不能與 --nes-chr-ram 同時使用。CNROM 設定也不接受 CHR RAM 模式。請確認目標映射器與電路板具備所需的 CHR RAM。

省略所有 CHR 選項時,編譯器會加入空白的 8 KiB CHR ROM。執行期間上傳圖樣的範例,必須明確指定 --nes-chr-ram。CHR RAM 與 CPU 端的 PRG RAM 是不同的記憶體;線框繪圖的暫存緩衝區也需要另外配置 PRG RAM。

一般的 ROM 陣列索引讀取與 C 指標參照,會將陣列放入共用記憶體庫 0,除非已用 #pragma fixed_bank 明確固定位置。這可讓呼叫端在可切換記憶體庫中執行時仍看得到資料。明確的遠端存取須在同一次呼叫中傳入陣列名稱本身,以及對同名陣列使用的 __bankof,例如 __farpeek8(__bankof(table), table)。單獨查詢記憶體庫編號不會要求共用庫配置。對明確指定固定庫的資料,以及組合語言內的參照,您仍須維持正確映射。這是依語法進行的配置檢查,不是指標流向分析;共用庫的容量限制仍然適用。

9. FDS、擴充音源與周邊

FDS 涉及磁碟檔案配置、啟動、覆疊載入及儲存。請參閱 fds_manifest_sample.json 與 FDS 標頭。執行所需的 BIOS 必須自行準備,公開套件不含 BIOS。

呼叫 VRC6 或 VRC7 音源操作不會變更 ROM 的 mapper 設定,兩者必須搭配。周邊裝置應分別驗證輸入值、連線狀態,以及對一般控制器讀取路徑的影響。

10. 診斷與建置結果

KQ 診斷和 symfind、src2asm、romdiff 等開發命令與 GB 類似,但繼承的 GB 說明選項未必都對應已實作的 NES 功能。FC 辭典另外從 FC 原始碼與標頭收集。

即使是關閉顯示時的初始化,也可能出現直接 PPU 操作警告,例如 KQ2421。不要為了消除警告而破壞安全初始化;應檢查顯示時序和執行日誌。「沒有錯誤」與「沒有警告」是兩種結果。

11. 參考範例:使用生成式 AI 開發遊戲

填寫需求後,將完整提示詞交給 AI。內容涵蓋實作、模擬器測試、SARAKURA 分析,以及修正後的重新驗證。

· 下載 Markdown

填寫需求後,將本文完整交給 AI。命令假設 kitaqgb、kitaqfc、kokura、kurosaki、sarakura、kitaq-docs 儲存庫與 game-gb 或 game-fc 專案位於同一個上層目錄。請從該目錄執行,並依實際環境調整路徑。

需求

  • 遊戲名稱:<填寫>
  • 類型與核心玩法:<填寫>
  • 操作方式及成功、失敗條件:<填寫>
  • 必備畫面、關卡、敵人與道具:<填寫>
  • 美術風格、背景音樂與音效:<填寫,並註明提供素材的路徑>
  • 存檔、通訊、周邊設備及其他需求:<填寫,或無>
  • 專案目錄:<填寫>
  • 再散布條件:<例如,自行撰寫的程式與原創素材可採 MIT 授權公開>
  • 目標:<NES/紅白機卡匣或 FDS>
  • Mapper、ROM 大小與鏡像方式:<指定,或依需求選擇>
  • 影像制式與效能目標:<例如 NTSC,每秒更新遊戲邏輯 60 次>

請執行的工作

請以 KITAQFC 及其函式庫實作遊戲。使用 KUROSAKI 執行與除錯,使用 SARAKURA 整理診斷並比較修正前後的結果。

持續重複以下流程,直到符合驗收標準:具體化規格 → 實作小幅變更 → 建置 → 輸入操作並觀察 → 調查原因 → 修正 → 在相同條件下重新驗證。不可只因提出計畫、提供程式碼或編譯成功,就認定工作完成。

確認環境與驗收標準

  1. 閱讀工作目錄的指示、各工具 README、HTML 手冊,以及所用函式庫的標頭檔和實作。記錄執行檔路徑及版本或 SHA-256;以實際 --help 輸出確認命令,以原始碼確認 API。
  2. 為輸入、畫面、聲音、遊戲流程和更新頻率訂出可判定的驗收標準。例如,按下再放開 START 後開始遊戲;碰撞減少一條命;暫停時指定聲音靜音,繼續後恢復播放。
  3. 只針對重要的模糊需求提問,一般可復原的實作決策請自主處理。不得自行降低需求或驗收標準。
  4. 先用隨附的小範例串接編譯器、模擬器與 SARAKURA。這只能確認工具銜接,不能代表所需遊戲已完成。

先完成可玩的最小流程

  • 小型遊戲先考慮 NROM;若規模或分頁切換需求需要,再選用 MMC3 等 Mapper。不能只憑名稱判斷支援程度,應透過 inspect-rom、mapper-info、audit-board 和實作確認所需卡匣電路板功能。
  • 規劃 PRG/CHR 大小、CHR-ROM 或 CHR-RAM、鏡像、固定分頁、中斷向量與存檔 RAM。最佳化或變更分頁後,核對標頭與實際配置。--nes-local-ram 使用 CPU 內部 RAM $0000–$07FF;避免與零頁、堆疊、OAM 緩衝區及執行階段、函式庫區域重疊。
  • 使用 KITAQFC 的 C 方言、FC 函式庫與 void main(void)。不要假設 GB API 相容。有些標頭項目只有宣告,需確認實作位置並加入必要的 .c 檔案。
  • 考慮 PPU 暫存器、NMI、OAM DMA、每掃描線精靈上限、捲動、鏡像、屬性表及 APU/DMC 行為。佇列剩餘容量不等於 PPU VRAM 容量,應規劃每次 NMI 可負擔的工作量。
  • 將提供的原創 ascii.c 字型轉換成 FC CHR,確認 CHR、調色盤、名稱表與屬性資料。FDS 的磁碟存取、存檔與 BIOS 條件需另行驗證,不可套用卡匣的啟動條件。
  • 先串起開機、標題畫面、可操控角色、成功或失敗與重新開始,再擴充內容。
  • 保留可編輯的圖形、音樂、音效原始檔及產生步驟,並確認建置確實讀取匯出的資料。
  • 原始碼註解使用英文,進度報告使用繁體中文。SARAKURA 的標準報告維持英文。

對應每次建置與執行結果

以 out/iter-001 等目錄區分每輪輸出。記錄命令、結束碼,以及原始碼、素材、工具、ROM、中繼資料的雜湊值。建置失敗後,不可執行殘留的舊 ROM。配置映射、原始碼映射與除錯資訊必須和 ROM 來自同一次建置。

以下是不提供輸入的 NROM 檢查範例。請準備 main.c、必要的函式庫實作與 CHR 檔案,並選擇合適的 Mapper。未確認格式前,不要把編譯器的建置中繼資料傳給 KUROSAKI 的 --kitaqfc-debug。

powershell
$iteration = '.\game-fc\out\iter-001'
New-Item -ItemType Directory -Force $iteration | Out-Null

# Include all additional implementation units required by the game.
& '.\kitaqfc\kitaqfc.exe' '.\game-fc\src\main.c' `
  -I '.\kitaqfc\lib' -o "$iteration\game.nes" `
  --mapper=nrom '--nes-chr=.\game-fc\assets\game.chr' --no-disasm `
  "--kurosaki-metadata=$iteration\build.json"
if ($LASTEXITCODE -ne 0) { throw 'Build failed; inspect the build log.' }

& '.\kurosaki\kurosaki.exe' run "$iteration\game.nes" `
  --frames 300 --pad1 0 --png "$iteration\frame.png" `
  --json "$iteration\run.json" --emit-diagnostics "$iteration\events.jsonl"
if ($LASTEXITCODE -ne 0) { throw 'Emulator run failed; inspect the run log.' }

& '.\sarakura\sarakura.exe' fc analyze `
  --metadata "$iteration\build.json" --events "$iteration\events.jsonl" `
  --frames 300 --out "$iteration\analysis" --fail-on error
if ($LASTEXITCODE -ne 0) { throw 'Inspect the analysis report and fix the cause.' }

無輸入執行 300 影格只是初步檢查。在認定遊戲正常之前,必須加入具有操作順序的遊玩情境。

重現有先後順序的輸入

  • --pad1 和 --pad2 使用 NES 原始位元遮罩:A=1、B=2、SELECT=4、START=8、UP=16、DOWN=32、LEFT=64、RIGHT=128。不要與函式庫的 BTN_* 數值混淆。
  • run --pad1 使用固定輸入。連續操作應製作重播資料,區分按下、按住與放開。閱讀 Replay、ReplayFrame 定義。公開 CLI 的 replay-record 記錄的是無輸入狀態,不是玩家的互動操作。
  • 自行核對 ROM SHA-256、重播對象與影格範圍。replay-run --verify 只在提供預期最終雜湊時進行比較,並不完整驗證遊戲行為或 ROM 對應關係。不得只為通過測試,就把預期值一律改成實測值。
powershell
& '.\kurosaki\kurosaki.exe' replay-run `
  '.\game-fc\out\iter-001\game.nes' '.\game-fc\tests\start-and-play.replay.json' `
  --frames 900 --json '.\game-fc\out\iter-001\play.json' `
  --png '.\game-fc\out\iter-001\play.png' `
  --wav '.\game-fc\out\iter-001\play.wav'

為待測 ROM 準備相符的重播資料。公開 CLI 的 replay-run 沒有 --emit-diagnostics 選項。不要捏造選項,也不要把 CPU 追蹤當成診斷事件。若要以 SARAKURA 分析順序輸入情境,請在專案內製作測試驅動程式,使用公開 kurosaki-core 的 RunOptions.replay_frames 和 diagnostic_events_from_trace_and_report。以相同 ROM、重播和影格條件執行,從該次執行的追蹤與診斷報告產生 JSONL。確認追蹤設定與保留範圍,並將驅動程式的畫面、觀測結果與 CLI 重播比對。不得把無輸入執行的診斷當作遊玩情境的證據。若缺少必要環境,應將此項驗證列為未完成。

檢查畫面、聲音、狀態與效能

  • 保存輸入情境,區分按下、按住與放開。走過規格中的全部路徑:開機、開始、移動、動作、碰撞、捲動、關卡切換、遊戲結束、重新開始、暫停,以及適用的存檔與通訊。
  • 保留關鍵影格 PNG、輸入資料、執行報告、診斷 JSONL、WAV 和必要的狀態、記憶體觀測。確認實際到達的影格數與停止原因。務必開啟圖片查看;一張截圖無法證明移動或輸入反應。將計數器、座標與狀態變化和預期值比對,也要檢查畫面邊緣、圖塊與屬性邊界及精靈密集情境。
  • 檢查音樂、音效、同時發聲、斷音、暫停與恢復。僅產生 WAV 不能證明聲音正確。無法試聽時,請區分已執行的波形、數值檢查與尚未確認的聽感。
  • 測量高負載場景的目標 CPU 工作量、遊戲更新與傳輸量;FC 還要計入 NMI 工作。主機上模擬器的執行速度不等於遊戲更新頻率,也不能證明實機速度。使用 --allow-unimplemented 後能繼續執行,不代表未實作功能已受支援。

分析、修正並重新驗證

  • 將待測 ROM 的建置中繼資料與該次執行的診斷 JSONL 交給 SARAKURA。CPU 追蹤或一般執行報告不能取代它。--frames 指定分析條件;SARAKURA 不會執行 ROM,也不會自動修改原始碼。
  • 閱讀 report.html、ai_diagnostics.json、repair_prompt.md、retest_plan.json,並與重現步驟、畫面、聲音及原始碼核對。區分推測的位置、原因與已確認事實,也要區分正常等待迴圈與當機。逐項判讀警告,記錄未支援事件與分析限制。不可透過隱藏警告或縮短測試來取得通過結果。
  • 將問題縮減成最小重現案例,修正原因後重新建置。若根源在編譯器或模擬器,應與遊戲程式問題分開確認,並為工具修正加入回歸驗證。
  • 重新驗證時,保持輸入、亂數種子、機型與影像制式、Mapper、觀測影格及診斷設定一致。新 ROM 使用相符的中繼資料;程式或 RAM 配置變更後,不可直接沿用即時存檔。
powershell
& '.\sarakura\sarakura.exe' baseline-delta `
  --baseline '.\game-fc\out\iter-001\analysis' `
  --current '.\game-fc\out\iter-002\analysis' `
  --out '.\game-fc\out\delta.json' --markdown '.\game-fc\out\delta.md' `
  --fail-on-new error --fail-on-regression error --enforce

診斷差異應搭配操作、畫面與聲音的驗收結果判斷。相同失敗反覆發生時,請重新檢視證據與假設,不要漫無根據地繼續修改。

完成條件與交付內容

以交付的原始碼與設定建置最終 ROM,再執行所有必要情境。僅使用無敵狀態、自動測試輸入或另一種 Mapper,無法驗證最終版本的正常遊玩。提供需求與測試對照表,說明剩餘警告的原因,明列未驗證、未支援項目。未做實機測試時,請標註「實機未驗證」。

交付原始碼、工具與函式庫識別資訊、可編輯素材、可重現的建置與測試指令稿、ROM、最終驗證證據,以及說明安裝、操作與已知限制的 README。視需要附上重播資料和測試驅動程式。只在明確授權範圍內公開或對外傳送檔案。驗證後刪除不必要的中間建置與暫存追蹤,但保留原始碼、素材、最終成果及必要的回歸證據。

若環境或權限阻礙必要檢查,請回報確切的重現步驟與所需處理,不得標記為完成。

原始碼位置

編譯器原始碼與專案檔位於儲存庫內的同名子目錄,執行檔位於儲存庫根目錄,函式庫位於 lib/。專案路徑與建置需求請參閱目錄結構說明。

完整範例程式

程式、共用標頭、提供的 ascii.c 與轉換素材均已附上。m_ 開頭的名稱是本手冊的輔助函式。請搭配預期結果和驗證紀錄閱讀建置命令,並從各並列儲存庫的上層目錄執行。

批次建置指令碼 / 結果與畫面擷取

顯示提供的字元集 fc_font.c

目的: 了解提供的原創字型包含哪些字母、數字與標點符號,以及如何繪製。

運作方式: m_init 準備顯示與字型。m_text 繪製各列,m_put 則以字元碼 34 繪製雙引號。

操作方式與預期結果: 請確認有 26 個大寫字母、26 個小寫字母、10 個數字及 30 個標點符號,並檢查每個字形是否清晰可辨。無須輸入操作。

顯示提供的字元集:請確認有 26 個大寫字母、26 個小寫字母、10 個數字及 30 個標點符號,並檢查每個字形是否清晰可辨。無須輸入操作。
顯示範例:請確認有 26 個大寫字母、26 個小寫字母、10 個數字及 30 個標點符號,並檢查每個字形是否清晰可辨。無須輸入操作。

下載原始碼 / 驗證狀態

powershell
.\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
c
// 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。前面的零來自三位數十進位格式。

第一次顯示文字:畫面顯示 HELLO WORLD 與 042。前面的零來自三位數十進位格式。
顯示範例:畫面顯示 HELLO WORLD 與 042。前面的零來自三位數十進位格式。

下載原始碼 / 驗證狀態

powershell
.\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
c
// 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

目的: 學習如何保留超出八位元範圍的中間結果。

運作方式: (u16)200 + 100 的結果為 300。程式先除以 10,再轉為 u8 顯示。請注意,擴大型別的轉型發生在加法之前。

操作方式與預期結果: 030 表示計算結果 30。

使用較寬型別的中間值運算:030 表示計算結果 30。
顯示範例:030 表示計算結果 30。

下載原始碼 / 驗證狀態

powershell
.\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
c
// 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 分支。

分支與迴圈:最終結果為 042。此程式使用 if/else 分支。
顯示範例:042 是最終結果。此程式使用 if/else 分支。

下載原始碼 / 驗證狀態

powershell
.\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
c
// 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] 寫入與函式傳回值。
顯示範例:總和顯示為 042。請追蹤結構複製、ptr[i] 寫入與函式傳回值。

下載原始碼 / 驗證狀態

powershell
.\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
c
// 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 清除四個位元組。程式設定位元 3、切換位元 1,再以 __memcpy 複製全部四個位元組。

操作方式與預期結果: 目的緩衝區的第一個位元組顯示為 010,其中位元 3 貢獻 8,位元 1 貢獻 2。

位元旗標與緩衝區複製:目的緩衝區的第一個位元組顯示為 010,其中位元 3 貢獻 8,位元 1 貢獻 2。
顯示範例:目的緩衝區的第一個位元組顯示為 010,其中位元 3 貢獻 8,位元 1 貢獻 2。

下載原始碼 / 驗證狀態

powershell
.\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
c
// 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。

計算按鈕新按下的次數:初始值為 000。按 A 後變成 001,持續按住不會累加。放開後再按一次便變成 002。此位元組計數器會從 255 回繞至 000。
顯示範例:初始值為 000。按 A 後變成 001,持續按住不會累加。放開後再按一次便變成 002。此位元組計數器會從 255 回繞至 000。

下載原始碼 / 驗證狀態

powershell
.\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
c
// 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 倍的值。

Q8.8 定點數相乘:012 表示 3 × 4 = 12,是轉換後的整數,不是內部放大 256 倍的值。
顯示範例:012 表示 3 × 4 = 12,是轉換後的整數,不是內部放大 256 倍的值。

下載原始碼 / 驗證狀態

powershell
.\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
c
// 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 或座標。此範例不更新或繪製物件,因此不會出現移動角色。

從固定物件池配置:001 是啟用中的物件數,不是 ID 或座標。此範例不更新或繪製物件,因此不會出現移動角色。
顯示範例:001 是啟用中的物件數,不是 ID 或座標。此範例不更新或繪製物件,因此不會出現移動角色。

下載原始碼 / 驗證狀態

powershell
.\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
c
// 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 存於診斷緩衝區,僅靠畫面無法顯示或驗證該內容。

將診斷值記錄至 RAM:001 是追蹤項目數。HP=42 存於診斷緩衝區,僅靠畫面無法顯示或驗證該內容。
顯示範例:001 是追蹤項目數。HP=42 存於診斷緩衝區,僅靠畫面無法顯示或驗證該內容。

下載原始碼 / 驗證狀態

powershell
.\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
c
// 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 掛接可容納四個點的陣列,接著 chain_push_head 記錄 (20,30)。

操作方式與預期結果: 042 是固定完成標記,不是座標或數量。請檢查 trail 與 points 以確認儲存的歷史。此程式不繪製跟隨的身體。

儲存位置歷程:042 是固定完成標記,不是座標或數量。請檢查 trail 與 points 以確認儲存的歷史。此程式不繪製跟隨的身體。
顯示範例:042 是固定完成標記,不是座標或數量。請檢查 trail 與 points 以確認儲存的歷史。此程式不繪製跟隨的身體。

下載原始碼 / 驗證狀態

powershell
.\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
c
// 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,因此顯示值不會持續增加。

讀取函式庫影格計數器:結果為 002。閒置迴圈使用手冊輔助函式 m_wait,因此顯示值不會持續增加。
顯示範例:結果為 002。閒置迴圈使用手冊輔助函式 m_wait,因此顯示值不會持續增加。

下載原始碼 / 驗證狀態

powershell
.\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
c
// 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,以區分標題畫面、遊戲進行等場景。

運作方式: 程式登錄兩個以零初始化的 SceneDef 項目,再以 scene_change 選取 ID 1。

操作方式與預期結果: 001 是 scene_get_current 傳回的目前 ID。此範例未註冊場景回呼或轉場繪製。

選取目前場景 ID:001 是 scene_get_current 傳回的目前 ID。此範例未註冊場景回呼或轉場繪製。
顯示範例:001 是 scene_get_current 傳回的目前 ID。此範例未註冊場景回呼或轉場繪製。

下載原始碼 / 驗證狀態

powershell
.\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
c
// 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 像素累加八次。右移運算取出整數像素,遮罩則保留餘數。

操作方式與預期結果: 總位移為兩像素,因此畫面將 X=42 顯示為 042。此手動積分範例處理正向移動;負向移動須搭配借位處理。

累積 Q5.3 小數位移:總位移為兩像素,因此畫面將 X=42 顯示為 042。此手動積分範例處理正向移動;負向移動須搭配借位處理。
顯示範例:總位移為兩像素,因此畫面將 X=42 顯示為 042。此手動積分範例處理正向移動;負向移動須搭配借位處理。

下載原始碼 / 驗證狀態

powershell
.\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
c
// 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。這不是跨越多個名稱表的寬幅地圖範例。

捲動 NES 背景:SCROLL 字樣的水平位置會改變。phase 從 255 回繞至 0。這不是跨越多個名稱表的寬幅地圖範例。
顯示範例:SCROLL 字樣的水平位置會改變。phase 從 255 回繞至 0。這不是跨越多個名稱表的寬幅地圖範例。

下載原始碼 / 驗證狀態

powershell
.\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
c
// 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 將圖塊 52 排入名稱表位址 0x2103,__vramq_commit 再提交。該位址對應圖塊座標 (3,8)。

操作方式與預期結果: 背景座標 (3,8) 出現一個 4。提供的字型中,圖塊 52 就是數字 4。

透過 NMI 套用圖塊更新:背景座標 (3,8) 出現一個 4。提供的字型中,圖塊 52 就是數字 4。
顯示範例:背景座標 (3,8) 出現一個 4。提供的字型中,圖塊 52 就是數字 4。

下載原始碼 / 驗證狀態

powershell
.\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
c
// 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 設定與傳輸,不是動畫。

顯示 NES OAM 精靈:靜止的字母 A 以精靈顯示。此範例示範 OAM 設定與傳輸,不是動畫。
顯示範例:靜止的字母 A 以精靈顯示。此範例示範 OAM 設定與傳輸,不是動畫。

下載原始碼 / 驗證狀態

powershell
.\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
c
// 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 可停止。僅靠畫面標記無法驗證聲音輸出。

播放 APU 脈衝音:顯示 042 時持續播放脈波音。呼叫 nes_apu_silence_all 可停止。僅靠畫面標記無法驗證聲音輸出。
顯示範例:顯示 042 時持續播放脈波音。呼叫 nes_apu_silence_all 可停止。僅靠畫面標記無法驗證聲音輸出。

下載原始碼 / 驗證狀態

powershell
.\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
c
// 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 播放音樂

四聲道音樂在 NMI 中推進。先初始化、設定曲目並預先填入佇列,再啟用 NMI 播放,之後由主程式持續補充。NMI 讀取 RAM 佇列,因此只要及時補充,少量 RAM 也能播放長串流。每個聲道都有獨立的 HOLD/STOP 命令。

每筆記錄包含五個位元組:delay, CH1, CH2, CH3, CH4。延遲值 1~255 表示新狀態持續的 NMI 節拍數;零會結束播放,並忽略其餘四個位元組。音符值 0~71 對應 C2~B7(24=C4)。雜訊值 0~15 選擇長模式的週期,16~31 則選擇短模式。所有聲道均以 255=NES_AUDIO_HOLD 維持原狀,以 254=NES_AUDIO_STOP 靜音。

此四聲道驅動程式針對 NTSC 調校:CH1=脈衝波 1、CH2=脈衝波 2、CH3=三角波、CH4=雜訊,索引為 0~3。不支援 DMC 或擴充音源。請勿與直接寫入 APU 的程式同時使用,例如 audio.c 中的音效。它不提供 PAL 音高表或節奏校正。

延遲結束卻沒有下一筆 BGM 記錄時,會停止 BGM 音符,待資料到達後於 NMI 恢復。獨立播放的 SFX 會繼續。計數的是進入資料不足狀態的次數,而非每個空佇列影格。只插入 HOLD 值不會恢復先前的 BGM 音符。通常應每影格補充一次,並在耗時工作前預先排入足夠長的播放資料。

fds.h — FDS 磁碟操作

__fds_available

回報編譯目標是否為 FDS,例如可在共用遊戲程式碼中顯示目標專屬資訊。

語法

c
u8 __fds_available(void);

回傳值

FDS 建置為 1,否則為 0。這不是執行期周邊裝置偵測。

使用條件與限制

此查詢也適用於 NROM 等卡匣目標。將 FDS 專用 API 放入 if (__fds_available()),不會免除該 API 的目標檢查;請依目標安排原始碼。

擷取使用 KUROSAKI 將磁碟開機檔直接載入 RAM 的功能。範例不呼叫 BIOS 常式,也不驗證檔案傳輸、換片或實機開機。空白測試韌體不是替代 BIOS。範例以 NMI 更新畫面,並停用這些查詢不需要的可遮罩 IRQ。

建置範例會產生原生配置的 .fds,不含額外的核准畫面開機檔。此設定用於檢查 KUROSAKI 直接啟動;實機磁碟的開機程序須另行驗證。

使用範例

c
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 原始碼會依編譯目標傳回不同值,與是否插入磁碟無關。

__fds_available: AVAILABLE-FDS-DEFAULT — FDS 建置顯示 001,NROM 建置顯示 000。同一份 C 原始碼會依編譯目標傳回不同值,與是否插入磁碟無關。
AVAILABLE-FDS-DEFAULT — FDS 建置顯示 001,NROM 建置顯示 000。同一份 C 原始碼會依編譯目標傳回不同值,與是否插入磁碟無關。
__fds_available: AVAILABLE-NROM-DEFAULT — FDS 建置顯示 001,NROM 建置顯示 000。同一份 C 原始碼會依編譯目標傳回不同值,與是否插入磁碟無關。
AVAILABLE-NROM-DEFAULT — FDS 建置顯示 001,NROM 建置顯示 000。同一份 C 原始碼會依編譯目標傳回不同值,與是否插入磁碟無關。

完整範例程式

建置命令

powershell
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

csharp
if (funcName == "__fds_available")
            {
                if (!Need(0)) return true;
                EmitAsm("LDA", Imm(isFds ? 1 : 0));
                return true;
            }
__fds_disk_ready

立即檢查磁碟是否存在,讓插片提示可每影格查詢一次而不阻塞。

語法

c
u8 __fds_disk_ready(void);

回傳值

$4032 位元 0 為零時傳回 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 直接啟動;實機磁碟的開機程序須另行驗證。

NESdev:FDS 硬體暫存器

使用範例

c
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。

__fds_disk_ready: STATUS-FDS-DEFAULT — 畫面在 PRESENT / PROTECT / STATUS 下顯示 1 / 0 / 0,接著在 COMPLETED PRESENCE WAITS 下顯示 2。這表示兩次等待磁碟存在的呼叫都已返回,不是檔案傳輸成功指標。下方另顯示 NO FILE TRANSFER TESTED。
STATUS-FDS-DEFAULT — 畫面在 PRESENT / PROTECT / STATUS 下顯示 1 / 0 / 0,接著在 COMPLETED PRESENCE WAITS 下顯示 2。這表示兩次等待磁碟存在的呼叫都已返回,不是檔案傳輸成功指標。下方另顯示 NO FILE TRANSFER TESTED。

完整範例程式

建置命令

powershell
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

csharp
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 硬體狀態暫存器。這不是上次磁碟操作保存的錯誤值。

語法

c
u8 __fds_error(void);

回傳值

傳回完整的八位元 $4030 值。處理 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 直接啟動;實機磁碟的開機程序須另行驗證。

NESdev:FDS 硬體暫存器

使用範例

c
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。

__fds_error: STATUS-FDS-DEFAULT — 畫面在 PRESENT / PROTECT / STATUS 下顯示 1 / 0 / 0,接著在 COMPLETED PRESENCE WAITS 下顯示 2。這表示兩次等待磁碟存在的呼叫都已返回,不是檔案傳輸成功指標。下方另顯示 NO FILE TRANSFER TESTED。
STATUS-FDS-DEFAULT — 畫面在 PRESENT / PROTECT / STATUS 下顯示 1 / 0 / 0,接著在 COMPLETED PRESENCE WAITS 下顯示 2。這表示兩次等待磁碟存在的呼叫都已返回,不是檔案傳輸成功指標。下方另顯示 NO FILE TRANSFER TESTED。

完整範例程式

建置命令

powershell
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

csharp
case "__fds_error":
                    if (!Need(0)) return true;
                    EmitAsm("LDA", Mem(0x4030));
                    return true;
__fds_side

傳回從 $4032 遮罩取出的寫入保護位元。此函式不辨識磁碟 A 面或 B 面。

語法

c
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 直接啟動;實機磁碟的開機程序須另行驗證。

NESdev:FDS 硬體暫存器

使用範例

c
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。

__fds_side: STATUS-FDS-DEFAULT — 畫面在 PRESENT / PROTECT / STATUS 下顯示 1 / 0 / 0,接著在 COMPLETED PRESENCE WAITS 下顯示 2。這表示兩次等待磁碟存在的呼叫都已返回,不是檔案傳輸成功指標。下方另顯示 NO FILE TRANSFER TESTED。
STATUS-FDS-DEFAULT — 畫面在 PRESENT / PROTECT / STATUS 下顯示 1 / 0 / 0,接著在 COMPLETED PRESENCE WAITS 下顯示 2。這表示兩次等待磁碟存在的呼叫都已返回,不是檔案傳輸成功指標。下方另顯示 NO FILE TRANSFER TESTED。

完整範例程式

建置命令

powershell
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

csharp
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() 相同的磁碟存在輪詢迴圈。

語法

c
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 直接啟動;實機磁碟的開機程序須另行驗證。

NESdev:FDS 硬體暫存器

使用範例

c
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。

__fds_wait_insert: STATUS-FDS-DEFAULT — 畫面在 PRESENT / PROTECT / STATUS 下顯示 1 / 0 / 0,接著在 COMPLETED PRESENCE WAITS 下顯示 2。這表示兩次等待磁碟存在的呼叫都已返回,不是檔案傳輸成功指標。下方另顯示 NO FILE TRANSFER TESTED。
STATUS-FDS-DEFAULT — 畫面在 PRESENT / PROTECT / STATUS 下顯示 1 / 0 / 0,接著在 COMPLETED PRESENCE WAITS 下顯示 2。這表示兩次等待磁碟存在的呼叫都已返回,不是檔案傳輸成功指標。下方另顯示 NO FILE TRANSFER TESTED。

完整範例程式

建置命令

powershell
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

csharp
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 輪詢迴圈中等待,直到磁碟存在。

語法

c
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 直接啟動;實機磁碟的開機程序須另行驗證。

NESdev:FDS 硬體暫存器

使用範例

c
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。

__fds_wait_ready: STATUS-FDS-DEFAULT — 畫面在 PRESENT / PROTECT / STATUS 下顯示 1 / 0 / 0,接著在 COMPLETED PRESENCE WAITS 下顯示 2。這表示兩次等待磁碟存在的呼叫都已返回,不是檔案傳輸成功指標。下方另顯示 NO FILE TRANSFER TESTED。
STATUS-FDS-DEFAULT — 畫面在 PRESENT / PROTECT / STATUS 下顯示 1 / 0 / 0,接著在 COMPLETED PRESENCE WAITS 下顯示 2。這表示兩次等待磁碟存在的呼叫都已返回,不是檔案傳輸成功指標。下方另顯示 NO FILE TRANSFER TESTED。

完整範例程式

建置命令

powershell
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

csharp
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。大小為零的記錄仍視為存在。

語法

c
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 含原創的 513 位元組序列:0~255 重複兩次,最後加零。ID 17 的內容為空。兩者皆為非開機 PRG 檔(類型 0),本教學不會載入它們。來源相對路徑以清單所在目錄為基準解析。

擷取使用 KUROSAKI 將磁碟開機檔直接載入 RAM 的功能。範例不呼叫 BIOS 常式,也不驗證檔案傳輸、換片或實機開機。空白測試韌體不是替代 BIOS。範例以 NMI 更新畫面,並停用這些查詢不需要的可遮罩 IRQ。

建置範例會產生原生配置的 .fds,不含額外的核准畫面開機檔。此設定用於檢查 KUROSAKI 直接啟動;實機磁碟的開機程序須另行驗證。

使用範例

c
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 存在且為零位元組,ID 18 不存在且為零位元組。TABLE VALUES MATCH: PASS 是取回值與此組合比較的結果。單憑大小為零,無法區分空記錄與不存在的 ID。

__fds_file_exists: METADATA-FDS-DEFAULT — 表格顯示 ID 16 存在且為 513 位元組,ID 17 存在且為零位元組,ID 18 不存在且為零位元組。TABLE VALUES MATCH: PASS 是取回值與此組合比較的結果。單憑大小為零,無法區分空記錄與不存在的 ID。
METADATA-FDS-DEFAULT — 表格顯示 ID 16 存在且為 513 位元組,ID 17 存在且為零位元組,ID 18 不存在且為零位元組。TABLE VALUES MATCH: PASS 是取回值與此組合比較的結果。單憑大小為零,無法區分空記錄與不存在的 ID。

完整範例程式

建置命令

powershell
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

csharp
EmitHelperStart("__fds_file_exists");
            EmitFdsMetadataSearch(foundReturnsSize: false);

            // Return the compiled metadata size, using zero for an unmatched ID.
__fds_file_size

傳回編譯中繼資料中為該 ID 宣告的位元組大小,例如用於查看資源預計載入的大小。

語法

c
u16 __fds_file_size(u8 id);

參數

id

要查詢的檔案 ID。中繼資料使用 ID 0~254,255 保留為結束值。ID 不同於檔案編號、檔名或目的位址。

回傳值

宣告的無號 16 位元大小,範圍為 0~65535。不存在的 ID 也傳回零;需要區分時,請用 __fds_file_exists()。

使用條件與限制

請以 --mapper=fds 建置。以卡匣映射器為目標時,呼叫 __fds_available 以外的 FDS 專用函式會以 KQFC2420 拒絕。

搜尋由 --fds-meta=... 或 --fds-manifest=... 編譯的表格,以及產生的覆疊記錄。不會讀取磁碟目錄。即使磁碟內封裝了檔案,若無表格記錄仍找不到。手動與自動產生的記錄合計上限為 42。

大小是中繼資料宣告的值,不一定等於來源檔案推算長度、裁短覆疊的有效資料長度,或換片後的磁碟內容。若用這些查詢限制資料存取範圍,請宣告正確大小。

請將 fds_metadata.json 及其兩個 .bin 檔放在 C 原始碼旁。ID 16 含原創的 513 位元組序列:0~255 重複兩次,最後加零。ID 17 的內容為空。兩者皆為非開機 PRG 檔(類型 0),本教學不會載入它們。來源相對路徑以清單所在目錄為基準解析。

擷取使用 KUROSAKI 將磁碟開機檔直接載入 RAM 的功能。範例不呼叫 BIOS 常式,也不驗證檔案傳輸、換片或實機開機。空白測試韌體不是替代 BIOS。範例以 NMI 更新畫面,並停用這些查詢不需要的可遮罩 IRQ。

建置範例會產生原生配置的 .fds,不含額外的核准畫面開機檔。此設定用於檢查 KUROSAKI 直接啟動;實機磁碟的開機程序須另行驗證。

使用範例

c
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 存在且為零位元組,ID 18 不存在且為零位元組。TABLE VALUES MATCH: PASS 是取回值與此組合比較的結果。單憑大小為零,無法區分空記錄與不存在的 ID。

__fds_file_size: METADATA-FDS-DEFAULT — 表格顯示 ID 16 存在且為 513 位元組,ID 17 存在且為零位元組,ID 18 不存在且為零位元組。TABLE VALUES MATCH: PASS 是取回值與此組合比較的結果。單憑大小為零,無法區分空記錄與不存在的 ID。
METADATA-FDS-DEFAULT — 表格顯示 ID 16 存在且為 513 位元組,ID 17 存在且為零位元組,ID 18 不存在且為零位元組。TABLE VALUES MATCH: PASS 是取回值與此組合比較的結果。單憑大小為零,無法區分空記錄與不存在的 ID。

完整範例程式

建置命令

powershell
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

csharp
EmitHelperStart("__fds_file_size");
            EmitFdsMetadataSearch(foundReturnsSize: true);
__fds_load_file

透過 BIOS 讀取檔案,並將 PRG 資料複製至 CPU 緩衝區。可用於將存檔資料或關卡資源載入遊戲預留的記憶體。

語法

c
u8 __fds_load_file(u8 id, u8* dst);

參數

id

產生磁碟中非開機檔案的 ID,範圍為 0~254;範例使用 73。這與檔案的實際排列位置不同。ID 255、不存在的 ID 及開機檔案都會被拒絕。

dst

PRG 檔案的目的位址,須有足夠空間容納封裝後的完整長度。傳入零時不複製,資料留在標頭指定的目的地。對 CHR/名稱表 PPU 檔案只接受零,由 BIOS 傳輸至原本的 PPU 目的地。

回傳值

成功時為零。非零 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 作為暫存區,並在 $8500 與 $8600 使用四位元組緩衝區。

這些是同步 BIOS 呼叫。請從共用程式碼呼叫,以免傳輸覆寫正在執行的程式碼或返回位址;不可從中斷處理常式呼叫。磁碟 I/O 期間須符合 BIOS 前提:停止繪圖及 NMI、將 PPU 位址遞增量設為一,並停用非磁碟 IRQ 來源。不保證傳輸期間遊戲或音樂能持續運作。

此擷取使用自行撰寫的 BIOS 呼叫慣例測試程式,檢查編譯後的 CPU 程式碼、緩衝區內容、儲存後讀回的行為,以及每個顯示像素。它不驗證真正的 BIOS、實體磁碟寫入、CRC 或斷電後復原。單靠 KUROSAKI 的一般 FDS 執行,無法證明相同的儲存行為。

請將範例與同一資料夾中的 fds_file_io_manifest.json、fds_file_io_payload.bin 一起使用。下方檢查工具會從原創原始碼產生暫時測試程式,完成後移除。不包含商用 BIOS,且此測試程式不是一般遊戲用 BIOS。

FDS BIOS 呼叫慣例

使用範例

c
#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 的四個位元組的首尾值。範例將它們改為 7、14、21、28,儲存後再讀入 $8600,因此 SAVED FIRST / LAST 顯示 007 與 028。STAGING 8000 / SIZE 4 表示磁碟檔案的載入位址與長度。獨立測試也會確認 ID 73 寫入實際的第四個檔案,也就是從零起算的位置 3。

__fds_load_file: 預設設定 — LOAD、SAVE、RELOAD STATUS 與 BYTE CHECK ERRORS 均顯示 000。INITIAL FIRST/LAST 顯示 011 與 044,對應載入 $8500 的四個位元組的首尾值。範例將它們改為 7、14、21、28,儲存後再讀入 $8600,因此 SAVED FIRST / LAST 顯示 007 與 028。STAGING 8000 / SIZE 4 表示磁碟檔案的載入位址與長度。獨立測試也會確認 ID 73 寫入實際的第四個檔案,也就是從零起算的位置 3。
預設設定 — LOAD、SAVE、RELOAD STATUS 與 BYTE CHECK ERRORS 均顯示 000。INITIAL FIRST/LAST 顯示 011 與 044,對應載入 $8500 的四個位元組的首尾值。範例將它們改為 7、14、21、28,儲存後再讀入 $8600,因此 SAVED FIRST / LAST 顯示 007 與 028。STAGING 8000 / SIZE 4 表示磁碟檔案的載入位址與長度。獨立測試也會確認 ID 73 寫入實際的第四個檔案,也就是從零起算的位置 3。

完整範例程式

fds_file_io_manifest.json

fds_file_io_payload.bin

建置命令

powershell
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

csharp
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

以 CPU 緩衝區的位元組覆寫磁碟面尾端預留的固定大小 PRG 存檔槽。檔案會保留 ID、名稱及之後使用的載入位址。

語法

c
u8 __fds_save_file(u8 id, const u8* src, u16 len);

參數

id

產生磁碟中非開機檔案的 ID,範圍為 0~254;範例使用 73。這與檔案的實際排列位置不同。ID 255、不存在的 ID 及開機檔案都會被拒絕。

src

CPU RAM 中資料的起始位址,須可讀取 len 個位元組。此 API 不會直接儲存 VRAM。

len

要儲存的位元組數,必須等於封裝的檔案長度。遊戲資料較少時,請在呼叫前填好存檔緩衝區未使用的部分。

回傳值

成功時為零,否則傳回 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 作為暫存區,並在 $8500 與 $8600 使用四位元組緩衝區。

這些是同步 BIOS 呼叫。請從共用程式碼呼叫,以免傳輸覆寫正在執行的程式碼或返回位址;不可從中斷處理常式呼叫。磁碟 I/O 期間須符合 BIOS 前提:停止繪圖及 NMI、將 PPU 位址遞增量設為一,並停用非磁碟 IRQ 來源。不保證傳輸期間遊戲或音樂能持續運作。

此擷取使用自行撰寫的 BIOS 呼叫慣例測試程式,檢查編譯後的 CPU 程式碼、緩衝區內容、儲存後讀回的行為,以及每個顯示像素。它不驗證真正的 BIOS、實體磁碟寫入、CRC 或斷電後復原。單靠 KUROSAKI 的一般 FDS 執行,無法證明相同的儲存行為。

請將範例與同一資料夾中的 fds_file_io_manifest.json、fds_file_io_payload.bin 一起使用。下方檢查工具會從原創原始碼產生暫時測試程式,完成後移除。不包含商用 BIOS,且此測試程式不是一般遊戲用 BIOS。

FDS BIOS 呼叫慣例

使用範例

c
#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 的四個位元組的首尾值。範例將它們改為 7、14、21、28,儲存後再讀入 $8600,因此 SAVED FIRST / LAST 顯示 007 與 028。STAGING 8000 / SIZE 4 表示磁碟檔案的載入位址與長度。獨立測試也會確認 ID 73 寫入實際的第四個檔案,也就是從零起算的位置 3。

__fds_save_file: 預設設定 — LOAD、SAVE、RELOAD STATUS 與 BYTE CHECK ERRORS 均顯示 000。INITIAL FIRST/LAST 顯示 011 與 044,對應載入 $8500 的四個位元組的首尾值。範例將它們改為 7、14、21、28,儲存後再讀入 $8600,因此 SAVED FIRST / LAST 顯示 007 與 028。STAGING 8000 / SIZE 4 表示磁碟檔案的載入位址與長度。獨立測試也會確認 ID 73 寫入實際的第四個檔案,也就是從零起算的位置 3。
預設設定 — LOAD、SAVE、RELOAD STATUS 與 BYTE CHECK ERRORS 均顯示 000。INITIAL FIRST/LAST 顯示 011 與 044,對應載入 $8500 的四個位元組的首尾值。範例將它們改為 7、14、21、28,儲存後再讀入 $8600,因此 SAVED FIRST / LAST 顯示 007 與 028。STAGING 8000 / SIZE 4 表示磁碟檔案的載入位址與長度。獨立測試也會確認 ID 73 寫入實際的第四個檔案,也就是從零起算的位置 3。

完整範例程式

fds_file_io_manifest.json

fds_file_io_payload.bin

建置命令

powershell
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

csharp
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

不讀取磁碟,直接傳回紀錄為已駐留的邏輯記憶體庫。

語法

c
u8 __fds_current_bank(void);

回傳值

八位元軟體駐留紀錄;標準啟動後立即為 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 直接啟動;實機磁碟的開機程序須另行驗證。

使用範例

c
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 的驗證建置中,只有最後一個值會變為零。

__fds_current_bank: RESIDENCY-FDS-DEFAULT — CURRENT BANK 為 1,BANK 1 RESIDENT 為 1,BANK 2 RESIDENT 為 0,OVERLAY FUNCTIONS 為 2。將兩個函式封裝於記憶體庫 2,不代表啟動時該記憶體庫就已駐留。使用 --fds-no-overlay-table 的驗證建置中,只有最後一個值會變為零。
RESIDENCY-FDS-DEFAULT — CURRENT BANK 為 1,BANK 1 RESIDENT 為 1,BANK 2 RESIDENT 為 0,OVERLAY FUNCTIONS 為 2。將兩個函式封裝於記憶體庫 2,不代表啟動時該記憶體庫就已駐留。使用 --fds-no-overlay-table 的驗證建置中,只有最後一個值會變為零。
__fds_current_bank: RESIDENCY-FDS-TABLE-OFF-DEFAULT — CURRENT BANK 為 1,BANK 1 RESIDENT 為 1,BANK 2 RESIDENT 為 0,OVERLAY FUNCTIONS 為 2。將兩個函式封裝於記憶體庫 2,不代表啟動時該記憶體庫就已駐留。使用 --fds-no-overlay-table 的驗證建置中,只有最後一個值會變為零。
RESIDENCY-FDS-TABLE-OFF-DEFAULT — CURRENT BANK 為 1,BANK 1 RESIDENT 為 1,BANK 2 RESIDENT 為 0,OVERLAY FUNCTIONS 為 2。將兩個函式封裝於記憶體庫 2,不代表啟動時該記憶體庫就已駐留。使用 --fds-no-overlay-table 的驗證建置中,只有最後一個值會變為零。

完整範例程式

建置命令

powershell
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

csharp
case "__fds_current_bank":
                    if (!Need(0)) return true;
                    EmitAsm("LDA", Mem(_runtimeFdsResidentBankAddress));
                    return true;
__fds_farcall

以 FDS 專用寫法執行無參數跨庫呼叫。載入與還原行為和 __fds_overlay_farcall() 完全相同。

語法

c
u8 __fds_farcall(u8 bank, u16 func);

參數

bank

表示記憶體庫的運算式。呼叫前會求值一次,但實際載入的記憶體庫由 func 連結後的位置決定。這不是將位址派送到執行期任意選定記憶體庫的功能。請傳入相符的值,例如 __bankof(func)。

func

已宣告且不帶參數的函式名稱。範例傳入 u8 overlay_answer(void)。不接受數值位址、執行期函式指標或需要引數的函式。需要引數時,請使用一般的 target(a, b) 呼叫。

回傳值

目標函式的八位元結果。若載入失敗但可還原前一個記憶體庫,就不呼叫目標,改傳回載入錯誤位元組。請設計能區分錯誤與正常結果的傳回值慣例。

使用條件與限制

請搭配 --mapper=fds --fds-layout=fds32 使用。記憶體庫 0 是位於 $A000–$DFFF 的共用程式碼。啟動時記憶體庫 1 佔用 $6000–$9FFF;記憶體庫 2 以上則是載入同一個 16 KiB 視窗的覆疊。請用 #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 檢查工具會將五個教學範例分別以啟用及停用最佳化建置,再於 KUROSAKI 檢查。它會從 scripts/fds_abi_fixture.py 建立暫時測試程式並於完成後移除,因此這些檢查不需要外部 BIOS。此測試程式不是一般遊戲用 BIOS。

使用範例

c
#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。這不是實際磁碟傳輸測試的擷取畫面。

__fds_farcall: FARCALL-DEFAULT — 使用自行撰寫的 LoadFiles ABI 測試程式時,RETURN VALUE 為 042;CALLBACK COUNT 與 BANK EXPR COUNT 皆為 001;BANK IN CALLBACK 為 002;BANK AFTER RETURN 為 001。函式與記憶體庫運算式各執行一次,呼叫記憶體庫 2 後還原了記憶體庫 1。這不是實際磁碟傳輸測試的擷取畫面。
FARCALL-DEFAULT — 使用自行撰寫的 LoadFiles ABI 測試程式時,RETURN VALUE 為 042;CALLBACK COUNT 與 BANK EXPR COUNT 皆為 001;BANK IN CALLBACK 為 002;BANK AFTER RETURN 為 001。函式與記憶體庫運算式各執行一次,呼叫記憶體庫 2 後還原了記憶體庫 1。這不是實際磁碟傳輸測試的擷取畫面。

完整範例程式

建置命令

powershell
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

csharp
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

將要求的記憶體庫與駐留紀錄比較,可協助判斷是否需要載入,但本身不執行載入。

語法

c
u8 __fds_is_bank_resident(u8 bank);

參數

bank

要與紀錄比較的邏輯記憶體庫編號。標準啟動記錄為 1,產生的覆疊使用 2 以上。記憶體庫 0 存放共用程式碼,並非此駐留紀錄的啟動值。

回傳值

有效記憶體庫與紀錄相符時為 1,否則為 0。記憶體庫 255(未知)一律傳回零,即使紀錄也是 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 直接啟動;實機磁碟的開機程序須另行驗證。

使用範例

c
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 的驗證建置中,只有最後一個值會變為零。

__fds_is_bank_resident: RESIDENCY-FDS-DEFAULT — CURRENT BANK 為 1,BANK 1 RESIDENT 為 1,BANK 2 RESIDENT 為 0,OVERLAY FUNCTIONS 為 2。將兩個函式封裝於記憶體庫 2,不代表啟動時該記憶體庫就已駐留。使用 --fds-no-overlay-table 的驗證建置中,只有最後一個值會變為零。
RESIDENCY-FDS-DEFAULT — CURRENT BANK 為 1,BANK 1 RESIDENT 為 1,BANK 2 RESIDENT 為 0,OVERLAY FUNCTIONS 為 2。將兩個函式封裝於記憶體庫 2,不代表啟動時該記憶體庫就已駐留。使用 --fds-no-overlay-table 的驗證建置中,只有最後一個值會變為零。
__fds_is_bank_resident: RESIDENCY-FDS-TABLE-OFF-DEFAULT — CURRENT BANK 為 1,BANK 1 RESIDENT 為 1,BANK 2 RESIDENT 為 0,OVERLAY FUNCTIONS 為 2。將兩個函式封裝於記憶體庫 2,不代表啟動時該記憶體庫就已駐留。使用 --fds-no-overlay-table 的驗證建置中,只有最後一個值會變為零。
RESIDENCY-FDS-TABLE-OFF-DEFAULT — CURRENT BANK 為 1,BANK 1 RESIDENT 為 1,BANK 2 RESIDENT 為 0,OVERLAY FUNCTIONS 為 2。將兩個函式封裝於記憶體庫 2,不代表啟動時該記憶體庫就已駐留。使用 --fds-no-overlay-table 的驗證建置中,只有最後一個值會變為零。

完整範例程式

建置命令

powershell
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

csharp
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 覆疊視窗,之後記錄該記憶體庫已駐留。

語法

c
u8 __fds_load_bank(u8 bank);

參數

bank

要載入的邏輯記憶體庫編號,範圍為 1~254。記憶體庫 0 是共用程式碼,不是可載入視窗;255 保留用來表示駐留狀態未知。記憶體庫 2 以上計算出的檔案 ID 也須落在 0~254。

回傳值

成功時為零。記憶體庫為 0 或 255、檔案 ID 溢位,或記憶體庫 1 沒有還原檔案時,傳回 255。BIOS 傳輸失敗時傳回其錯誤位元組。傳輸前的引數錯誤會保留駐留紀錄;BIOS 傳輸失敗則將其標記為 255(未知)。

使用條件與限制

請搭配 --mapper=fds --fds-layout=fds32 使用。記憶體庫 0 是位於 $A000–$DFFF 的共用程式碼。啟動時記憶體庫 1 佔用 $6000–$9FFF;記憶體庫 2 以上則是載入同一個 16 KiB 視窗的覆疊。請用 #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。

預設的駐留檢查發現已記錄相同記憶體庫時,會不讀取磁碟而直接傳回零。--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 檢查工具會將五個教學範例分別以啟用及停用最佳化建置,再於 KUROSAKI 檢查。它會從 scripts/fds_abi_fixture.py 建立暫時測試程式並於完成後移除,因此這些檢查不需要外部 BIOS。此測試程式不是一般遊戲用 BIOS。

使用範例

c
#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 顯示傳輸的四位元組資料中的 011 與 044;還原後 RESIDENT BANK 為 001。無效記憶體庫 0 與 255 各傳回 255。要求相同記憶體庫不會額外載入;確認到的檔案請求依序為 32、0。

__fds_load_bank: BANK_LOAD-DEFAULT — 使用自行撰寫的 LoadFiles ABI 測試程式時,LOAD BANK 2 STATUS、REQUIRE SAME BANK 與 RESTORE BANK 1 均為 000。FIRST / LAST BYTE 顯示傳輸的四位元組資料中的 011 與 044;還原後 RESIDENT BANK 為 001。無效記憶體庫 0 與 255 各傳回 255。要求相同記憶體庫不會額外載入;確認到的檔案請求依序為 32、0。
BANK_LOAD-DEFAULT — 使用自行撰寫的 LoadFiles ABI 測試程式時,LOAD BANK 2 STATUS、REQUIRE SAME BANK 與 RESTORE BANK 1 均為 000。FIRST / LAST BYTE 顯示傳輸的四位元組資料中的 011 與 044;還原後 RESIDENT BANK 為 001。無效記憶體庫 0 與 255 各傳回 255。要求相同記憶體庫不會額外載入;確認到的檔案請求依序為 32、0。

完整範例程式

建置命令

powershell
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

csharp
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。目的位址由檔案標頭決定。

語法

c
u8 __fds_load_overlay(u8 id);

參數

id

磁碟檔案 ID,範圍為 0~254。不可傳入 255,因為它是 BIOS 檔案清單的結束值。預設配置中,ID 32 包含記憶體庫 2。

回傳值

BIOS 錯誤位元組,零表示成功。無論成功或失敗,駐留紀錄都標記為 255(未知);單憑檔案 ID 無法確立邏輯記憶體庫映射。

使用條件與限制

請搭配 --mapper=fds --fds-layout=fds32 使用。記憶體庫 0 是位於 $A000–$DFFF 的共用程式碼。啟動時記憶體庫 1 佔用 $6000–$9FFF;記憶體庫 2 以上則是載入同一個 16 KiB 視窗的覆疊。請用 #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 檢查工具會將五個教學範例分別以啟用及停用最佳化建置,再於 KUROSAKI 檢查。它會從 scripts/fds_abi_fixture.py 建立暫時測試程式並於完成後移除,因此這些檢查不需要外部 BIOS。此測試程式不是一般遊戲用 BIOS。

使用範例

c
#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 載入與管理邏輯記憶體庫的差異。

__fds_load_overlay: RAW_OVERLAY-DEFAULT — 使用自行撰寫的 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 載入與管理邏輯記憶體庫的差異。
RAW_OVERLAY-DEFAULT — 使用自行撰寫的 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 載入與管理邏輯記憶體庫的差異。

完整範例程式

建置命令

powershell
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

csharp
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

呼叫覆疊中不帶參數的函式,並在返回前還原先前的記憶體庫。

語法

c
u8 __fds_overlay_farcall(u8 bank, u16 func);

參數

bank

表示記憶體庫的運算式。呼叫前會求值一次,但實際載入的記憶體庫由 func 連結後的位置決定。這不是將位址派送到執行期任意選定記憶體庫的功能。請傳入相符的值,例如 __bankof(func)。

func

已宣告且不帶參數的函式名稱。範例傳入 u8 overlay_answer(void)。不接受數值位址、執行期函式指標或需要引數的函式。需要引數時,請使用一般的 target(a, b) 呼叫。

回傳值

目標函式的八位元結果。若載入失敗但可還原前一個記憶體庫,就不呼叫目標,改傳回載入錯誤位元組。請設計能區分錯誤與正常結果的傳回值慣例。

使用條件與限制

請搭配 --mapper=fds --fds-layout=fds32 使用。記憶體庫 0 是位於 $A000–$DFFF 的共用程式碼。啟動時記憶體庫 1 佔用 $6000–$9FFF;記憶體庫 2 以上則是載入同一個 16 KiB 視窗的覆疊。請用 #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 檢查工具會將五個教學範例分別以啟用及停用最佳化建置,再於 KUROSAKI 檢查。它會從 scripts/fds_abi_fixture.py 建立暫時測試程式並於完成後移除,因此這些檢查不需要外部 BIOS。此測試程式不是一般遊戲用 BIOS。

使用範例

c
#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。這不是實際磁碟傳輸測試的擷取畫面。

__fds_overlay_farcall: OVERLAY_CALL-DEFAULT — 使用自行撰寫的 LoadFiles ABI 測試程式時,RETURN VALUE 為 042;CALLBACK COUNT 與 BANK EXPR COUNT 皆為 001;BANK IN CALLBACK 為 002;BANK AFTER RETURN 為 001。函式與記憶體庫運算式各執行一次,呼叫記憶體庫 2 後還原了記憶體庫 1。這不是實際磁碟傳輸測試的擷取畫面。
OVERLAY_CALL-DEFAULT — 使用自行撰寫的 LoadFiles ABI 測試程式時,RETURN VALUE 為 042;CALLBACK COUNT 與 BANK EXPR COUNT 皆為 001;BANK IN CALLBACK 為 002;BANK AFTER RETURN 為 001。函式與記憶體庫運算式各執行一次,呼叫記憶體庫 2 後還原了記憶體庫 1。這不是實際磁碟傳輸測試的擷取畫面。

完整範例程式

建置命令

powershell
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

csharp
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 以上的函式數量。這不是駐留函式數,也不是檔案數。

語法

c
u8 __fds_overlay_function_count(void);

回傳值

表格開頭的數量位元組,範圍為 0~255。沒有覆疊函式或使用 --fds-no-overlay-table 時為零。超過 255 筆項目會以 KQFC2511 拒絕。

使用條件與限制

請以 --mapper=fds 建置。以卡匣映射器為目標時,呼叫 __fds_available 以外的 FDS 專用函式會以 KQFC2420 拒絕。

產生程式碼時先建立值為零的預留位置;原生 FDS 連結會在記憶體庫與位址配置後,以表格取代。範例在記憶體庫 2 封裝兩個函式,但不呼叫。--fds-layout=legacy 的初始資料不是覆疊函式總數的統計。

擷取使用 KUROSAKI 將磁碟開機檔直接載入 RAM 的功能。範例不呼叫 BIOS 常式,也不驗證檔案傳輸、換片或實機開機。空白測試韌體不是替代 BIOS。範例以 NMI 更新畫面,並停用這些查詢不需要的可遮罩 IRQ。

建置範例會產生原生配置的 .fds,不含額外的核准畫面開機檔。此設定用於檢查 KUROSAKI 直接啟動;實機磁碟的開機程序須另行驗證。

使用範例

c
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 的驗證建置中,只有最後一個值會變為零。

__fds_overlay_function_count: RESIDENCY-FDS-DEFAULT — CURRENT BANK 為 1,BANK 1 RESIDENT 為 1,BANK 2 RESIDENT 為 0,OVERLAY FUNCTIONS 為 2。將兩個函式封裝於記憶體庫 2,不代表啟動時該記憶體庫就已駐留。使用 --fds-no-overlay-table 的驗證建置中,只有最後一個值會變為零。
RESIDENCY-FDS-DEFAULT — CURRENT BANK 為 1,BANK 1 RESIDENT 為 1,BANK 2 RESIDENT 為 0,OVERLAY FUNCTIONS 為 2。將兩個函式封裝於記憶體庫 2,不代表啟動時該記憶體庫就已駐留。使用 --fds-no-overlay-table 的驗證建置中,只有最後一個值會變為零。
__fds_overlay_function_count: RESIDENCY-FDS-TABLE-OFF-DEFAULT — CURRENT BANK 為 1,BANK 1 RESIDENT 為 1,BANK 2 RESIDENT 為 0,OVERLAY FUNCTIONS 為 2。將兩個函式封裝於記憶體庫 2,不代表啟動時該記憶體庫就已駐留。使用 --fds-no-overlay-table 的驗證建置中,只有最後一個值會變為零。
RESIDENCY-FDS-TABLE-OFF-DEFAULT — CURRENT BANK 為 1,BANK 1 RESIDENT 為 1,BANK 2 RESIDENT 為 0,OVERLAY FUNCTIONS 為 2。將兩個函式封裝於記憶體庫 2,不代表啟動時該記憶體庫就已駐留。使用 --fds-no-overlay-table 的驗證建置中,只有最後一個值會變為零。

完整範例程式

建置命令

powershell
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

csharp
case "__fds_overlay_function_count":
                    if (!Need(0)) return true;
                    EmitAsm("LDA", Abs("__kq_fds_overlay_function_table"));
                    return true;
__fds_require_bank

只在尚未記錄為已駐留時載入記憶體庫。存取資源前呼叫,可在資料已可用時避免不必要的傳輸。

語法

c
u8 __fds_require_bank(u8 bank);

參數

bank

要載入的邏輯記憶體庫編號,範圍為 1~254。記憶體庫 0 是共用程式碼,不是可載入視窗;255 保留用來表示駐留狀態未知。記憶體庫 2 以上計算出的檔案 ID 也須落在 0~254。

回傳值

成功時為零。記憶體庫為 0 或 255、檔案 ID 溢位,或記憶體庫 1 沒有還原檔案時,傳回 255。BIOS 傳輸失敗時傳回其錯誤位元組。傳輸前的引數錯誤會保留駐留紀錄;BIOS 傳輸失敗則將其標記為 255(未知)。

使用條件與限制

請搭配 --mapper=fds --fds-layout=fds32 使用。記憶體庫 0 是位於 $A000–$DFFF 的共用程式碼。啟動時記憶體庫 1 佔用 $6000–$9FFF;記憶體庫 2 以上則是載入同一個 16 KiB 視窗的覆疊。請用 #pragma fixed_bank 2 等指示詞配置覆疊程式碼或資料。

有效記憶體庫與紀錄相符時傳回零,否則呼叫 __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 檢查工具會將五個教學範例分別以啟用及停用最佳化建置,再於 KUROSAKI 檢查。它會從 scripts/fds_abi_fixture.py 建立暫時測試程式並於完成後移除,因此這些檢查不需要外部 BIOS。此測試程式不是一般遊戲用 BIOS。

使用範例

c
#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 顯示傳輸的四位元組資料中的 011 與 044;還原後 RESIDENT BANK 為 001。無效記憶體庫 0 與 255 各傳回 255。要求相同記憶體庫不會額外載入;確認到的檔案請求依序為 32、0。

__fds_require_bank: BANK_LOAD-DEFAULT — 使用自行撰寫的 LoadFiles ABI 測試程式時,LOAD BANK 2 STATUS、REQUIRE SAME BANK 與 RESTORE BANK 1 均為 000。FIRST / LAST BYTE 顯示傳輸的四位元組資料中的 011 與 044;還原後 RESIDENT BANK 為 001。無效記憶體庫 0 與 255 各傳回 255。要求相同記憶體庫不會額外載入;確認到的檔案請求依序為 32、0。
BANK_LOAD-DEFAULT — 使用自行撰寫的 LoadFiles ABI 測試程式時,LOAD BANK 2 STATUS、REQUIRE SAME BANK 與 RESTORE BANK 1 均為 000。FIRST / LAST BYTE 顯示傳輸的四位元組資料中的 011 與 044;還原後 RESIDENT BANK 為 001。無效記憶體庫 0 與 255 各傳回 255。要求相同記憶體庫不會額外載入;確認到的檔案請求依序為 32、0。

完整範例程式

建置命令

powershell
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

csharp
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 映像。此範例不進行磁碟 I/O,只操作聲音。錄音使用 KUROSAKI 的 RAM 直接啟動與音訊模型。測試使用的空韌體未實作 BIOS 常式,也不隨附散布。一般磁碟啟動與實機需要適當的啟動環境。

__fds_env_set

依序將三個原始引數寫入 $4080、$4084、$408A。

語法

c
void __fds_env_set(u8 speed, u8 gain, u8 mode);

參數

speed

雖然參數名稱如此,此值實際是原始 $4080:位元 7 為直接音量,位元 6 為遞增包絡。

gain

寫入 $4084 的原始調變包絡值。

mode

寫入 $408A 的包絡主速度。

回傳值

無回傳值。會改變音效暫存器或驅動狀態。

使用條件與限制

以 --mapper=fds 建置 .fds 映像。此範例不進行磁碟 I/O,只操作聲音。錄音使用 KUROSAKI 的 RAM 直接啟動與音訊模型。測試使用的空韌體未實作 BIOS 常式,也不隨附散布。一般磁碟啟動與實機需要適當的啟動環境。

使用範例

c
// 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 驅動的磁碟開機。

__fds_env_set: NTSC
NTSC
NTSC — 錄製的音訊

完整範例程式

建置命令

powershell
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

csharp
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 高位控制位元。

語法

c
void __fds_freq_set(u16 freq);

參數

freq

FDS 頻率暫存器值 0..4095。高位控制位元會清除。

回傳值

無回傳值。會改變音效暫存器或驅動狀態。

使用條件與限制

以 --mapper=fds 建置 .fds 映像。此範例不進行磁碟 I/O,只操作聲音。錄音使用 KUROSAKI 的 RAM 直接啟動與音訊模型。測試使用的空韌體未實作 BIOS 常式,也不隨附散布。一般磁碟啟動與實機需要適當的啟動環境。

使用範例

c
// 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 驅動的磁碟開機。

__fds_freq_set: NTSC
NTSC
NTSC — 錄製的音訊

完整範例程式

建置命令

powershell
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

csharp
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

依序將 32 位元組送至調變表寫入埠 $4088。

語法

c
void __fds_mod_load(const u8* mod32);

參數

mod32

FDS 調變表的 32 個位元組,各為控制碼 0..7。載入前須停止調變。

回傳值

無回傳值。會改變音效暫存器或驅動狀態。

使用條件與限制

以 --mapper=fds 建置 .fds 映像。此範例不進行磁碟 I/O,只操作聲音。錄音使用 KUROSAKI 的 RAM 直接啟動與音訊模型。測試使用的空韌體未實作 BIOS 常式,也不隨附散布。一般磁碟啟動與實機需要適當的啟動環境。

使用範例

c
// 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 驅動的磁碟開機。

__fds_mod_load: NTSC
NTSC
NTSC — 錄製的音訊

完整範例程式

建置命令

powershell
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

csharp
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 歸零。

語法

c
void __fds_sound_enable(void);

回傳值

無回傳值。會改變音效暫存器或驅動狀態。

使用條件與限制

以 --mapper=fds 建置 .fds 映像。此範例不進行磁碟 I/O,只操作聲音。錄音使用 KUROSAKI 的 RAM 直接啟動與音訊模型。測試使用的空韌體未實作 BIOS 常式,也不隨附散布。一般磁碟啟動與實機需要適當的啟動環境。

使用範例

c
// 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 驅動的磁碟開機。

__fds_sound_enable: NTSC
NTSC
NTSC — 錄製的音訊

完整範例程式

建置命令

powershell
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

csharp
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

將音量低六位元與 0x80 合併後寫入 $4080,直接控制音量。

語法

c
void __fds_volume_set(u8 vol);

參數

vol

FDS 直接音量 0..63;遮罩至六位元使 64 回繞為零。

回傳值

無回傳值。會改變音效暫存器或驅動狀態。

使用條件與限制

以 --mapper=fds 建置 .fds 映像。此範例不進行磁碟 I/O,只操作聲音。錄音使用 KUROSAKI 的 RAM 直接啟動與音訊模型。測試使用的空韌體未實作 BIOS 常式,也不隨附散布。一般磁碟啟動與實機需要適當的啟動環境。

使用範例

c
// 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 驅動的磁碟開機。

__fds_volume_set: NTSC
NTSC
NTSC — 錄製的音訊

完整範例程式

建置命令

powershell
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

csharp
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 歸零。

語法

c
void __fds_wave_load(const u8* wave64);

參數

wave64

64 個 FDS 波形位元組,各為 0..63 的 6 位元振幅。不檢查 NULL。

回傳值

無回傳值。會改變音效暫存器或驅動狀態。

使用條件與限制

以 --mapper=fds 建置 .fds 映像。此範例不進行磁碟 I/O,只操作聲音。錄音使用 KUROSAKI 的 RAM 直接啟動與音訊模型。測試使用的空韌體未實作 BIOS 常式,也不隨附散布。一般磁碟啟動與實機需要適當的啟動環境。

使用範例

c
// 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 驅動的磁碟開機。

__fds_wave_load: NTSC
NTSC
NTSC — 錄製的音訊

完整範例程式

建置命令

powershell
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

csharp
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

此 NMI 掛鉤會推進 BGM 與 SFX 延遲,並依目前各聲道的使用權更新 APU。

語法

c
void __nes_audio_vblank_tick(void);

回傳值

無傳回值。

使用條件與限制

請包含 audio_vblank.h,並在建置時加入一次 audio_vblank.c。無論使用預設或自訂 NMI 處理常式,編譯器都會讓它呼叫一次音樂掛鉤。請啟用 PPU NMI。主迴圈不必呼叫音樂更新常式。

此四聲道驅動程式針對 NTSC 調校:CH1=脈衝波 1、CH2=脈衝波 2、CH3=三角波、CH4=雜訊,索引為 0~3。不支援 DMC 或擴充音源。請勿與直接寫入 APU 的程式同時使用,例如 audio.c 中的音效。它不提供 PAL 音高表或節奏校正。

請勿從應用程式直接呼叫。連結音樂函式庫後,編譯器會將它接入 NMI。掛鉤會保存 A/X/Y,且不使用共用運算式暫存區。掛鉤與音高表位於固定 ROM 中,不會在 NMI 內切換 ROM 記憶體庫。此保存保證僅適用於音樂掛鉤本身;自訂 NMI 處理常式仍須自行保存其他工作使用的狀態。

每筆記錄包含五個位元組:delay, CH1, CH2, CH3, CH4。延遲值 1~255 表示新狀態持續的 NMI 節拍數;零會結束播放,並忽略其餘四個位元組。音符值 0~71 對應 C2~B7(24=C4)。雜訊值 0~15 選擇長模式的週期,16~31 則選擇短模式。所有聲道均以 255=NES_AUDIO_HOLD 維持原狀,以 254=NES_AUDIO_STOP 靜音。

使用範例

c
// 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();

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

範例會播放一次原創四聲道樂句。主程式連續 90 個影格執行整數運算而不補充佇列時,音樂仍會持續。“PHRASE COMPLETE / SILENT” 與 “UNDERRUNS 0 / MATH OK” 表示播放已結束、未發生佇列資料不足,且運算結果符合預期。請聆聽錄製的音訊,確認脈衝波旋律與伴奏、三角波低音及雜訊;結尾應為靜音。影像呈現控制結果,聲音則須透過音訊評估。

__nes_audio_vblank_tick: NROM
NROM
NROM — 錄製的音訊

完整範例程式

建置命令

powershell
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

c
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 圖塊屬性區域。四個兩位元調色盤欄位都會被替換,不是僅合併選定象限。

語法

c
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 不可寫入。存取結束後、開始繪製前,請設定所需的捲動狀態。

NESdev:PPU 暫存器

NESdev:PPU 捲動

使用範例

c
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 直條和八行三角形,第二行只有左半直條。(16,48) 處是綠色實心 64×32 矩形,(144,48) 處的紅色 64×32 區域由橫八、縱四個三角形圖塊組成。

y=96 處,x=16、24、32 的藍色標記分別為正方形、左半直條和三角形;x=80 為紅色左半直條,x=144 為綠色三角形。這些是背景圖塊。使用獨立調色盤的精靈位於 y=144:x=16 是紅色 8×8 正方形,x=80 是藍色左半 4×8 直條,x=144 是綠色八行三角形。最終捲動為 (0,0)。

影像檢查 16,384 個像素的預期形狀和色彩,包括精靈色彩。另有 31 個狀態測試,涵蓋暫存器替換、狀態與鎖存器副作用、捲動儲存值、0/255 位元組傳輸、增量 1/32、地圖邊緣、矩形零尺寸、表選擇、完整屬性位元組及兩種調色盤目標。Y=255 僅檢查暫存器寫入,不代表驗證了正確的捲動畫面。證據來自 KUROSAKI 的 NROM 環境,未測試實機。

__attr_set: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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) 寫入一個完整屬性位元組。該位元組中的所有調色盤象限都會被覆寫。表選擇遵循卡匣目前的鏡像。

語法

c
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 不可寫入。存取結束後、開始繪製前,請設定所需的捲動狀態。

NESdev:PPU 暫存器

NESdev:PPU 捲動

使用範例

c
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 直條和八行三角形,第二行只有左半直條。(16,48) 處是綠色實心 64×32 矩形,(144,48) 處的紅色 64×32 區域由橫八、縱四個三角形圖塊組成。

y=96 處,x=16、24、32 的藍色標記分別為正方形、左半直條和三角形;x=80 為紅色左半直條,x=144 為綠色三角形。這些是背景圖塊。使用獨立調色盤的精靈位於 y=144:x=16 是紅色 8×8 正方形,x=80 是藍色左半 4×8 直條,x=144 是綠色八行三角形。最終捲動為 (0,0)。

影像檢查 16,384 個像素的預期形狀和色彩,包括精靈色彩。另有 31 個狀態測試,涵蓋暫存器替換、狀態與鎖存器副作用、捲動儲存值、0/255 位元組傳輸、增量 1/32、地圖邊緣、矩形零尺寸、表選擇、完整屬性位元組及兩種調色盤目標。Y=255 僅檢查暫存器寫入,不代表驗證了正確的捲動畫面。證據來自 KUROSAKI 的 NROM 環境,未測試實機。

__attr_set_nt: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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 區塊編號。此操作解析配置資訊,不讀取目前映射的區塊,也不解參照指標。

語法

c
u8 bank = __bankof(symbol);

參數

symbol

編譯器或連結器已知配置的 ROM 資料物件或函式名稱。指標變數名稱不代表其指向的物件;執行時運算式不能作為符號名稱。

回傳值

u8 區塊編號。0 可表示共用 ROM,但並非所有目標都允許將其用於切換。

使用範例

c
bank_expect(__bankof(payload)==3,3);
bank_expect(__bankof(callback)==3,4);

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

範例檢查 payload 與 callback 均位於區塊 3。這驗證配置中繼資料;執行時區塊行為由獨立的讀取與回呼檢查驗證。

完整程式示範如何以區塊編號與近位址確定資料、如何跨區塊呼叫回呼,以及如何區分函式庫記錄與實際映射。所有數值皆為十六進位。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 各執行一次回呼。

__bankof: MMC3
MMC3

完整範例程式

建置命令

powershell
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

csharp
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

透過編譯器對應 mapper 的程序選擇邏輯 PRG 區塊,並更新其內部狀態。呼叫後維持新的映射,但不更新 bank 函式庫另外保存的記錄。

語法

c
void __bankswitch(u8 bank);

參數

bank

無號 8 位元 ROM 區塊編號。呼叫時應與函式配置一致,讀取時用來選擇來源映射。請使用目標卡匣支援的範圍。

回傳值

無(void)。

使用條件與限制

持續生效的區塊切換不能使目前程式碼或返回位址失去映射。請如範例所示,將呼叫位置、使用的 bank_switch 本體及後續程式碼放在共用或固定區塊 0。

函式庫記錄初值為 0,不會查詢卡匣啟動時的映射。請用 bank_switch 初始化。直接呼叫切換內建函式或寫入 mapper 不會更新這份獨立記錄,因此 bank_get_current 無法驗證實際硬體映射。

FC 切換編號是編譯器的邏輯編號。0 表示共用 ROM,不是切換目標;輔助程式將 0 視為區塊 1。SUROM512 僅允許 1–30:直接傳給切換內建函式的越界常數會導致建置錯誤;無效執行時值會退回 1 並設定 mapper 故障狀態。共用 ROM 應直接讀取,不要要求切換到區塊 0。

使用範例

c
__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。

完整程式示範如何以區塊編號與近位址確定資料、如何跨區塊呼叫回呼,以及如何區分函式庫記錄與實際映射。所有數值皆為十六進位。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 各執行一次回呼。

__bankswitch: MMC3
MMC3

完整範例程式

建置命令

powershell
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

csharp
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。

語法

c
void __bit_clear(u8* base, u16 bit);

參數

base

指向 CPU 可存取記憶體中旗標陣列首位元組的指標。此呼叫不配置或初始化陣列。設定、清除及反轉操作須使用可寫入 RAM;測試操作只讀取。

bit

從零起算的 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 重入。

使用範例

c
__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(二進位 10100010,十進位 162);CLEAR BIT2 列應顯示 162。再次清除位元 2,結果不變;其他已儲存旗標在兩次呼叫後都保留。

FC 預期的十進位各列為: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 位元組邊界的常數/執行階段索引,以及八種可能的非零位元測試遮罩。這些是 RAM 狀態檢查,不是繪圖示範。尚未測試實機或中斷並行寫入。

__bit_clear: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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

保留同一位元組的其他七個位元,將所選旗標設為 1。例如可標記任務已完成或道具已取得。已為 1 的位元再次設定後仍不變;此操作不是切換。

語法

c
void __bit_set(u8* base, u16 bit);

參數

base

指向 CPU 可存取記憶體中旗標陣列首位元組的指標。此呼叫不配置或初始化陣列。設定、清除及反轉操作須使用可寫入 RAM;測試操作只讀取。

bit

從零起算的 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 重入。

使用範例

c
__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(十進位 166)。SET BIT1 列應顯示 166。範例再次設定該位元,確認仍為 0xA6,示範重複設定不會反轉旗標。

FC 預期的十進位各列為: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 位元組邊界的常數/執行階段索引,以及八種可能的非零位元測試遮罩。這些是 RAM 狀態檢查,不是繪圖示範。尚未測試實機或中斷並行寫入。

__bit_set: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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 位元組可容納八個獨立的開關旗標。結果表示所選位元是清除或設定狀態;其數值表示方式依目標平台而異。

語法

c
u8 __bit_test(u8* base, u16 bit);

參數

base

指向 CPU 可存取記憶體中旗標陣列首位元組的指標。此呼叫不配置或初始化陣列。設定、清除及反轉操作須使用可寫入 RAM;測試操作只讀取。

bit

從零起算的 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 重入。

使用範例

c
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(二進位 10100100)。測試位元 1 傳回 0;測試位元 7 在 GB 傳回 1,在 FC 傳回 128。範例也將此非零結果轉成共用開關旗標,並確認原始位元組未改變。請查看下方畫面的 TEST OFF 與 TEST ON。

FC 預期的十進位各列為: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 位元組邊界的常數/執行階段索引,以及八種可能的非零位元測試遮罩。這些是 RAM 狀態檢查,不是繪圖示範。尚未測試實機或中斷並行寫入。

__bit_test: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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,其他位元保留。若兩次呼叫之間沒有其他程式寫入該位元,對同一位元呼叫兩次即可還原原始狀態。

語法

c
void __bit_toggle(u8* base, u16 bit);

參數

base

指向 CPU 可存取記憶體中旗標陣列首位元組的指標。此呼叫不配置或初始化陣列。設定、清除及反轉操作須使用可寫入 RAM;測試操作只讀取。

bit

從零起算的 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 重入。

使用範例

c
__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(十進位 34),因此 TOGGLE BIT7 顯示 034。再次反轉會還原為 0xA2,RESTORE 顯示 162。這兩個結果示範在保留其他位元的同時,完成雙向反轉。

FC 預期的十進位各列為: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 位元組邊界的常數/執行階段索引,以及八種可能的非零位元測試遮罩。這些是 RAM 狀態檢查,不是繪圖示範。尚未測試實機或中斷並行寫入。

__bit_toggle: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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 的 2 KiB 視窗;CNROM 則變更 $0000–$1FFF 的整個 8 KiB 區域。

語法

c
void __chr_bank_set(u8 bank);

參數

bank

CNROM 使用 8 KiB CHR 記憶體庫編號。MMC3 則以 1 KiB 記憶體庫編號選擇 2 KiB 視窗:最低位元被忽略,因此 2 與 3 都會選到 1 KiB 記憶體庫 2、3 這一組。可用記憶體庫取決於 CHR ROM 容量;API 不做範圍檢查。

回傳值

無傳回值。

使用條件與限制

內建函式宣告位於 intrinsics.h。使用 nes_mapper_irq_* 巨集時請包含 nes_game.h,並在建置時以 --mapper 選擇電路板。

對 MMC3 記憶體庫選擇暫存器寫入 0 或 1,也會清除 PRG 模式位元 6 與 CHR 反轉位元 7。請勿與需要保留這些位元的 PRG/CHR 配置併用。繪製中切換會改變後續圖樣讀取。範例在關閉繪製時檢查,顯示結果前再還原字型記憶體庫。

這三個內建函式支援 CNROM 與 MMC3,其他電路板在編譯時會被拒絕。SUROM 也以 CHR 控制選擇外層 PRG 區域,因此不允許這些 CHR 操作。

使用範例

c
__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:前三次明確啟用各允許一次中斷,停用後次數不變。每階段等待六個影格。

__chr_bank_set: MMC3-DEFAULT
MMC3-DEFAULT

完整範例程式

建置命令

powershell
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

使用範例

c
__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 讀取自行製作的標記位元組,示範這三個名稱都會切換整個 8 KiB 圖樣區域。

__chr_bank_set: CNROM-DEFAULT
CNROM-DEFAULT

完整範例程式

建置命令

powershell
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 的 2 KiB 視窗,等同 __chr_bank_set。CNROM 則變更整個 8 KiB 區域。

語法

c
void __chr_bank_set0(u8 bank);

參數

bank

CNROM 使用 8 KiB CHR 記憶體庫編號。MMC3 則以 1 KiB 記憶體庫編號選擇 2 KiB 視窗:最低位元被忽略,因此 2 與 3 都會選到 1 KiB 記憶體庫 2、3 這一組。可用記憶體庫取決於 CHR ROM 容量;API 不做範圍檢查。

回傳值

無傳回值。

使用條件與限制

內建函式宣告位於 intrinsics.h。使用 nes_mapper_irq_* 巨集時請包含 nes_game.h,並在建置時以 --mapper 選擇電路板。

對 MMC3 記憶體庫選擇暫存器寫入 0 或 1,也會清除 PRG 模式位元 6 與 CHR 反轉位元 7。請勿與需要保留這些位元的 PRG/CHR 配置併用。繪製中切換會改變後續圖樣讀取。範例在關閉繪製時檢查,顯示結果前再還原字型記憶體庫。

這三個內建函式支援 CNROM 與 MMC3,其他電路板在編譯時會被拒絕。SUROM 也以 CHR 控制選擇外層 PRG 區域,因此不允許這些 CHR 操作。

使用範例

c
__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:前三次明確啟用各允許一次中斷,停用後次數不變。每階段等待六個影格。

__chr_bank_set0: MMC3-DEFAULT
MMC3-DEFAULT

完整範例程式

建置命令

powershell
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

使用範例

c
__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 讀取自行製作的標記位元組,示範這三個名稱都會切換整個 8 KiB 圖樣區域。

__chr_bank_set0: CNROM-DEFAULT
CNROM-DEFAULT

完整範例程式

建置命令

powershell
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 的 2 KiB 視窗,R0 不變。CNROM 沒有獨立的第二個視窗,因此會變更整個 8 KiB 區域。

語法

c
void __chr_bank_set1(u8 bank);

參數

bank

CNROM 使用 8 KiB CHR 記憶體庫編號。MMC3 則以 1 KiB 記憶體庫編號選擇 2 KiB 視窗:最低位元被忽略,因此 2 與 3 都會選到 1 KiB 記憶體庫 2、3 這一組。可用記憶體庫取決於 CHR ROM 容量;API 不做範圍檢查。

回傳值

無傳回值。

使用條件與限制

內建函式宣告位於 intrinsics.h。使用 nes_mapper_irq_* 巨集時請包含 nes_game.h,並在建置時以 --mapper 選擇電路板。

對 MMC3 記憶體庫選擇暫存器寫入 0 或 1,也會清除 PRG 模式位元 6 與 CHR 反轉位元 7。請勿與需要保留這些位元的 PRG/CHR 配置併用。繪製中切換會改變後續圖樣讀取。範例在關閉繪製時檢查,顯示結果前再還原字型記憶體庫。

這三個內建函式支援 CNROM 與 MMC3,其他電路板在編譯時會被拒絕。SUROM 也以 CHR 控制選擇外層 PRG 區域,因此不允許這些 CHR 操作。

使用範例

c
__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:前三次明確啟用各允許一次中斷,停用後次數不變。每階段等待六個影格。

__chr_bank_set1: MMC3-DEFAULT
MMC3-DEFAULT

完整範例程式

建置命令

powershell
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

使用範例

c
__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 讀取自行製作的標記位元組,示範這三個名稱都會切換整個 8 KiB 圖樣區域。

__chr_bank_set1: CNROM-DEFAULT
CNROM-DEFAULT

完整範例程式

建置命令

powershell
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 指位元組數,而非位元數;此呼叫不會將圖像上傳至顯示硬體。

語法

c
void __copy16(u8* dst, const u8* src);

參數

dst

指向目標區域第一個可寫入位元組的指標。必須預先準備足夠的連續空間;函式不會自動配置緩衝區。

src

指向來源區域第一個可讀取位元組的指標。複製期間,整個來源區域必須維持可存取且內容不變。

回傳值

無(void)。

使用條件與限制

來源區域與目標區域應互不重疊。這些函式依位址遞增方向複製,不提供 memmove 對重疊區域的安全保證;寫入尚未讀取的來源區域會破壞後續資料。

操作完成後才返回呼叫端。函式不會切換 ROM/RAM 記憶體庫,也不會將範圍自動裁切至緩衝區邊界。請維持映射穩定、指定有效範圍,並協調與使用相同緩衝區的中斷處理程式之間的存取。

在 FC 上,dst 是 CPU 位址,而非 PPU 位址。複製至 CPU RAM 不會更新 CHR、名稱表或調色盤;上傳這些資料應使用 PPU/VRAM API。產生的輔助常式共用引數與暫存空間,呼叫尚未結束時,請勿從 NMI/IRQ 再次進入這些常式。

使用範例

c
memory_reset();
__copy16(memory_destination+1,memory_source+1);
memory_copy_16=memory_check(16,0); // Exactly 16 bytes, not 16 bits.

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此例以 __copy16 從偏移量 1 開始複製 16 位元組的規律資料。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 位元組。尚未進行實機驗證。

__copy16: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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 位元讀取,也不會切換記憶體庫。

語法

c
void __copy32(u8* dst, const u8* src);

參數

dst

指向目標區域第一個可寫入位元組的指標。必須預先準備足夠的連續空間;函式不會自動配置緩衝區。

src

指向來源區域第一個可讀取位元組的指標。複製期間,整個來源區域必須維持可存取且內容不變。

回傳值

無(void)。

使用條件與限制

來源區域與目標區域應互不重疊。這些函式依位址遞增方向複製,不提供 memmove 對重疊區域的安全保證;寫入尚未讀取的來源區域會破壞後續資料。

操作完成後才返回呼叫端。函式不會切換 ROM/RAM 記憶體庫,也不會將範圍自動裁切至緩衝區邊界。請維持映射穩定、指定有效範圍,並協調與使用相同緩衝區的中斷處理程式之間的存取。

在 FC 上,dst 是 CPU 位址,而非 PPU 位址。複製至 CPU RAM 不會更新 CHR、名稱表或調色盤;上傳這些資料應使用 PPU/VRAM API。產生的輔助常式共用引數與暫存空間,呼叫尚未結束時,請勿從 NMI/IRQ 再次進入這些常式。

使用範例

c
memory_reset();
__copy32(memory_destination+1,memory_source+1);
memory_copy_32=memory_check(32,0);

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此例以 __copy32 從偏移量 1 開始複製 32 位元組的規律資料。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 位元組。尚未進行實機驗證。

__copy32: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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

計算兩項無號內積。

語法

c
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 位元樣式傳回。

使用範例

c
// 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 位元值顯示;負數使用二補數位元樣式。GB 會縮放固定小數點乘積,FC 則傳回整數乘積,因此相同輸入不一定得到相同結果。

此程式碼片段儲存以下 16 位元值:result[22]=64959, result[23]=64959。各項常值輸入計算皆與對應的 RAM 輸入結果一致。畫面顯示代表值,result 陣列則保存所有結果。

__dot2_q8_8: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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

計算三項無號內積。

語法

c
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 位元樣式傳回。

使用範例

c
// 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 位元值顯示;負數使用二補數位元樣式。GB 會縮放固定小數點乘積,FC 則傳回整數乘積,因此相同輸入不一定得到相同結果。

此程式碼片段儲存以下 16 位元值:result[24]=2498, result[25]=2498。各項常值輸入計算皆與對應的 RAM 輸入結果一致。畫面顯示代表值,result 陣列則保存所有結果。

__dot3_q8_8: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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訊號,回傳相同的八位元結果,副作用也相同。此名稱用於表明擴充輸入用途,不會啟用其他裝置模式,也不會合併D0與D1。

語法

c
u8 __exp_pad_read1(void);

回傳值

NES 原始位元順序的 8 位元按住遮罩。每個設為 1 的位元代表一個按下按鈕,0 表示沒有按鈕。不是正規化的布林值。

使用條件與限制

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線上提供相容八位元序列手把訊號的周邊裝置。函式不會識別已連接的裝置,也沒有實作所有擴充裝置協定。每次呼叫都會重新鎖存兩個控制器的資料流,因此應完成一次完整讀取後再開始下一次。結果為新擷取的狀態,不會更新input_*或nes_pad1_*快取。

公開版KUROSAKI的控制器模型僅回傳D0與固定的位元6。因此即使按住一般控制器按鈕,該模擬器中的D1與麥克風位元也始終為0。所附執行結果驗證了編譯、一般按鈕輸入及此零訊號現象。周邊裝置的非零訊號、電氣時序及實機相容性仍未驗證;這裡的0既不能證明連接的裝置正常運作,也不能證明這些函式在實機上無法讀取它。

使用範例

c
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,因為對應訊號線未被模擬。擷取後顯示值保持不變。在具備適合的周邊裝置及其訊號線實作的環境中,擴充與麥克風數值取決於實際輸入;應依上述位元配置解讀,而不是期待這些零值。

公開版KUROSAKI的控制器模型僅回傳D0與固定的位元6。因此即使按住一般控制器按鈕,該模擬器中的D1與麥克風位元也始終為0。所附執行結果驗證了編譯、一般按鈕輸入及此零訊號現象。周邊裝置的非零訊號、電氣時序及實機相容性仍未驗證;這裡的0既不能證明連接的裝置正常運作,也不能證明這些函式在實機上無法讀取它。

__exp_pad_read1: NROM
NROM

完整範例程式

建置命令

powershell
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訊號,回傳相同的八位元結果,副作用也相同。此名稱用於表明擴充輸入用途,不會啟用其他裝置模式,也不會合併D0與D1。

語法

c
u8 __exp_pad_read2(void);

回傳值

NES 原始位元順序的 8 位元按住遮罩。每個設為 1 的位元代表一個按下按鈕,0 表示沒有按鈕。不是正規化的布林值。

使用條件與限制

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線上提供相容八位元序列手把訊號的周邊裝置。函式不會識別已連接的裝置,也沒有實作所有擴充裝置協定。每次呼叫都會重新鎖存兩個控制器的資料流,因此應完成一次完整讀取後再開始下一次。結果為新擷取的狀態,不會更新input_*或nes_pad1_*快取。

公開版KUROSAKI的控制器模型僅回傳D0與固定的位元6。因此即使按住一般控制器按鈕,該模擬器中的D1與麥克風位元也始終為0。所附執行結果驗證了編譯、一般按鈕輸入及此零訊號現象。周邊裝置的非零訊號、電氣時序及實機相容性仍未驗證;這裡的0既不能證明連接的裝置正常運作,也不能證明這些函式在實機上無法讀取它。

使用範例

c
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,因為對應訊號線未被模擬。擷取後顯示值保持不變。在具備適合的周邊裝置及其訊號線實作的環境中,擴充與麥克風數值取決於實際輸入;應依上述位元配置解讀,而不是期待這些零值。

公開版KUROSAKI的控制器模型僅回傳D0與固定的位元6。因此即使按住一般控制器按鈕,該模擬器中的D1與麥克風位元也始終為0。所附執行結果驗證了編譯、一般按鈕輸入及此零訊號現象。周邊裝置的非零訊號、電氣時序及實機相容性仍未驗證;這裡的0既不能證明連接的裝置正常運作,也不能證明這些函式在實機上無法讀取它。

__exp_pad_read2: NROM
NROM

完整範例程式

建置命令

powershell
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使用相同的實作。

語法

c
void __far_memcpy(u8* dst, u8 bank, u16 src, u16 len);

參數

dst

目標可寫入記憶體,容量必須足以容納實際複製的全部位元組。除了零位元組傳送外,不會檢查null或無效目標位址。

bank

選擇來源ROM映射的8位元區塊編號,須符合卡匣與編譯器的區塊配置;此參數與來源位址分開指定。

src

所選ROM映射中來源位元組的16位元CPU位址。FC版宣告使用u16,因此素材指標應以(u16)pointer轉型。

len

位元組數,範圍0–65535。設為0時不複製記憶體,但仍會執行區塊選取與恢復。

回傳值

無(void)。

使用條件與限制

整個來源範圍必須在預期ROM映射中可讀取,整個目標範圍必須可寫入。複製不會在區塊視窗邊界自動切換至下一區塊,不提供memmove式的重疊處理,也不控制VRAM安全寫入時機。寫入VRAM請使用圖形傳送API。

範例將呼叫端程式碼放在固定區塊0,避免切換來源區塊後,正在執行的程式碼變得不可見。傳送是同步操作。不要在傳送期間由中斷再次執行切換區塊的複製,也不要認為此輔助函式能確保整個程式的中斷安全。

使用範例

c
__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已恢復,並確認零長度複製不會改變0x55哨兵值。

預期圖像中,紅色一列從像素座標(16,32)開始,占72×8像素。依序重複8×8三角形、邊框厚1像素的8×8空心正方形、填滿左側4欄的圖塊,共三組。三角形從左上角1像素逐漸擴展到底列8像素。

FC中,(16,64)的藍色三角形與方框顯示__far_memcpy結果,(16,96)的綠色一對顯示asset_load_raw後再上傳CHR的結果。每對在黑底上占16×8像素,可直接查看複製的圖像位元組是否正確,而非只看數值狀態。

預期狀態為FAILED CHECKS 000與FIRST FAILURE 00。驗證會執行300影格,比較文字、圖形遮罩與顏色是否符合預期。這是模擬器驗證,並非實體硬體測試。

__far_memcpy: SUROM512
SUROM512

完整範例程式

建置命令

powershell
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 引數會被求值,但實際選擇由目標函式的配置決定。

語法

c
__farcall(bank, function_name);

參數

bank

無號 8 位元 ROM 區塊編號。呼叫時應與函式配置一致,讀取時用來選擇來源映射。請使用目標卡匣支援的範圍。

func

已宣告的無參數函式名稱。此具名呼叫內建函式不接受保存函式位址的變數。bank 引數不會作為回呼參數傳入。

回傳值

結果型別由被呼叫函式決定。void 回呼不傳回值;u8 與 u16 回呼傳回對應純量值。展示的範例使用 void 回呼。

使用條件與限制

這些呼叫不傳遞使用者引數,也不會自動使回呼可在中斷中重入。請正確管理回呼自身的資料、堆疊與中斷使用。還原區塊的呼叫路徑已測試巢狀及重複呼叫;此範例不涵蓋實機或任意中斷交錯。

FC 切換編號是編譯器的邏輯編號。0 表示共用 ROM,不是切換目標;輔助程式將 0 視為區塊 1。SUROM512 僅允許 1–30:直接傳給切換內建函式的越界常數會導致建置錯誤;無效執行時值會退回 1 並設定 mapper 故障狀態。共用 ROM 應直接讀取,不要要求切換到區塊 0。

使用範例

c
__farcall(3,callback);
bank_expect(callback_count==2 && callback_byte==0xD3,16);

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

在完整程式的此處,__farcall 使 callback_count 達到 2。回呼從區塊 3 讀取 D3。隨後透過近指標讀取 home2 並確認 B2,驗證傳回時已還原區塊 2。

完整程式示範如何以區塊編號與近位址確定資料、如何跨區塊呼叫回呼,以及如何區分函式庫記錄與實際映射。所有數值皆為十六進位。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 各執行一次回呼。

__farcall: MMC3
MMC3

完整範例程式

建置命令

powershell
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

csharp
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使用相同的實作。

語法

c
void __farmemcpy(u8* dst, u8 bank, u16 src, u16 len);

參數

dst

目標可寫入記憶體,容量必須足以容納實際複製的全部位元組。除了零位元組傳送外,不會檢查null或無效目標位址。

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 為零時,不會變更目的地的任何位元組,但仍會切換並還原記憶體庫。此函式沒有傳回值。

__far_memcpy

使用範例

c
__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已恢復,並確認零長度複製不會改變0x55哨兵值。

預期圖像中,紅色一列從像素座標(16,32)開始,占72×8像素。依序重複8×8三角形、邊框厚1像素的8×8空心正方形、填滿左側4欄的圖塊,共三組。三角形從左上角1像素逐漸擴展到底列8像素。

像素座標 (16,64) 的藍色三角形與正方形外框顯示 __farmemcpy 複製的位元組。下方 (16,96) 的綠色圖形組則來自 asset_load_raw。每組圖形在黑色背景上佔 16×8 像素;複製的位元組會上傳至 CHR RAM 供顯示。

預期狀態為FAILED CHECKS 000與FIRST FAILURE 00。驗證會執行300影格,比較文字、圖形遮罩與顏色是否符合預期。這是模擬器驗證,並非實體硬體測試。

__farmemcpy: SUROM512 — 像素座標 (16,64) 的藍色三角形與正方形外框顯示 __farmemcpy 複製的位元組。下方 (16,96) 的綠色圖形組則來自 asset_load_raw。每組圖形在黑色背景上佔 16×8 像素;複製的位元組會上傳至 CHR RAM 供顯示。
SUROM512 — 像素座標 (16,64) 的藍色三角形與正方形外框顯示 __farmemcpy 複製的位元組。下方 (16,96) 的綠色圖形組則來自 asset_load_raw。每組圖形在黑色背景上佔 16×8 像素;複製的位元組會上傳至 CHR RAM 供顯示。

完整範例程式

建置命令

powershell
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

csharp
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 使用的內建函式。

語法

c
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 並設定 mapper 故障狀態。共用 ROM 應直接讀取,不要要求切換到區塊 0。

使用範例

c
bank_expect(__farpeek16(3,(u16)(payload+1))==0x5C7A,14);

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式片段使用 __farpeek16 讀取區塊 3 中的 payload+1 並檢查 5C7A。完整程式還透過近指標讀取 home2,確認已還原區塊 2 的映射。

完整程式示範如何以區塊編號與近位址確定資料、如何跨區塊呼叫回呼,以及如何區分函式庫記錄與實際映射。所有數值皆為十六進位。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 各執行一次回呼。

__farpeek16: MMC3
MMC3

完整範例程式

建置命令

powershell
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

csharp
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 使用的內建函式。

語法

c
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 並設定 mapper 故障狀態。共用 ROM 應直接讀取,不要要求切換到區塊 0。

使用範例

c
bank_expect(__farpeek8(3,(u16)payload)==0xD3,13);

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式片段使用 __farpeek8 讀取區塊 3 中的 payload 並檢查 D3。完整程式還透過近指標讀取 home2,確認已還原區塊 2 的映射。

完整程式示範如何以區塊編號與近位址確定資料、如何跨區塊呼叫回呼,以及如何區分函式庫記錄與實際映射。所有數值皆為十六進位。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 各執行一次回呼。

__farpeek8: MMC3
MMC3

完整範例程式

建置命令

powershell
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

csharp
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

在解讀按鍵狀態前,先檢查鍵盤的偵測訊號模式。

語法

c
u8 __fkb_detect(void);

回傳值

若第 9 列、第 0 欄讀取為 0x1E,且停用矩陣後讀取為零,則傳回 1;否則傳回 0。這是依據訊號模式進行推測,並非讀取裝置識別碼。

使用條件與限制

這些 API 直接存取 $4016/$4017。不可與相同連接埠上的中斷驅動遊戲控制器、鍵盤或序列操作交錯執行。控制器移位位置也可能受影響。範例擷取輸入時停用 NMI,初始化顯示時再啟用。

向 $4016 寫入 5 以重設至第 0 列,再利用欄選擇位元由高轉低推進列。產生的程式碼除了周邊指令外,重設後另含 12 CPU 週期的 NOP,欄選擇後含 50 週期。退出時寫入零以停用矩陣。

另一項回歸檢查以實際執行的 CPU 匯流排追蹤,檢查列 0~9、兩欄、九列掃描、穩定延遲及最終連接埠狀態。不會注入實體按鍵訊號。

第 9 列沒有按鍵。任一條件不符便立即傳回零。KUROSAKI 在第一次讀取時傳回零,因此在該環境中偵測不會成功。

這些影像是在 KUROSAKI 中原樣執行可下載 ROM 的結果。目前輸入匯流排模擬遊戲控制器,但不模擬鍵盤、光線槍或序列轉接器訊號。外部輸入位元讀為零,因此畫面不驗證這些裝置。實際按鍵輸入、光學感測及通訊尚未測試。

NESdev:Family BASIC 鍵盤

使用範例

c
#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,九組 COL0/COL1 都是 00。A 鍵位於第 6 列、第 0 欄、位元 4,但偵測失敗會阻止將原始零值回報為按下。此表為啟動時的單次擷取。

__fkb_detect: KEYBOARD-DEFAULT — 此次 KUROSAKI 執行中,DEVICE DETECT、ROW 6 COL 0、A KEY - GUARDED 皆為 000,九組 COL0/COL1 都是 00。A 鍵位於第 6 列、第 0 欄、位元 4,但偵測失敗會阻止將原始零值回報為按下。此表為啟動時的單次擷取。
KEYBOARD-DEFAULT — 此次 KUROSAKI 執行中,DEVICE DETECT、ROW 6 COL 0、A KEY - GUARDED 皆為 000,九組 COL0/COL1 都是 00。A 鍵位於第 6 列、第 0 欄、位元 4,但偵測失敗會阻止將原始零值回報為按下。此表為啟動時的單次擷取。

完整範例程式

建置命令

powershell
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

csharp
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 位元組的緩衝區。

語法

c
u8 __fkb_read_row_col(u8 row, u8 col);

參數

row

按鍵列使用 0..8;沒有按鍵的偵測列使用 9。不會產生執行期間的範圍檢查。

col

0 或 1。只使用最低位元:偶數選擇群組 0,奇數選擇群組 1。

回傳值

傳回 $4017 & 0x1E,不會將位元向低位壓縮排列。

使用條件與限制

這些 API 直接存取 $4016/$4017。不可與相同連接埠上的中斷驅動遊戲控制器、鍵盤或序列操作交錯執行。控制器移位位置也可能受影響。範例擷取輸入時停用 NMI,初始化顯示時再啟用。

按鍵群組是原始位元 1~4:按下會清除對應位元,未按下的群組為 0x1E。它們不是 ASCII 文字,也不是以一表示按下的位元圖。未連接裝置也可能讀到零;未檢查存在與否前,不可解讀為所有鍵都被按住。

向 $4016 寫入 5 以重設至第 0 列,再利用欄選擇位元由高轉低推進列。產生的程式碼除了周邊指令外,重設後另含 12 CPU 週期的 NOP,欄選擇後含 50 週期。退出時寫入零以停用矩陣。

另一項回歸檢查以實際執行的 CPU 匯流排追蹤,檢查列 0~9、兩欄、九列掃描、穩定延遲及最終連接埠狀態。不會注入實體按鍵訊號。

每次呼叫都重設至第 0 列,再重新推進,不會沿用上次呼叫的列位置。

這些影像是在 KUROSAKI 中原樣執行可下載 ROM 的結果。目前輸入匯流排模擬遊戲控制器,但不模擬鍵盤、光線槍或序列轉接器訊號。外部輸入位元讀為零,因此畫面不驗證這些裝置。實際按鍵輸入、光學感測及通訊尚未測試。

NESdev:Family BASIC 鍵盤

使用範例

c
#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,九組 COL0/COL1 都是 00。A 鍵位於第 6 列、第 0 欄、位元 4,但偵測失敗會阻止將原始零值回報為按下。此表為啟動時的單次擷取。

__fkb_read_row_col: KEYBOARD-DEFAULT — 此次 KUROSAKI 執行中,DEVICE DETECT、ROW 6 COL 0、A KEY - GUARDED 皆為 000,九組 COL0/COL1 都是 00。A 鍵位於第 6 列、第 0 欄、位元 4,但偵測失敗會阻止將原始零值回報為按下。此表為啟動時的單次擷取。
KEYBOARD-DEFAULT — 此次 KUROSAKI 執行中,DEVICE DETECT、ROW 6 COL 0、A KEY - GUARDED 皆為 000,九組 COL0/COL1 都是 00。A 鍵位於第 6 列、第 0 欄、位元 4,但偵測失敗會阻止將原始零值回報為按下。此表為啟動時的單次擷取。

完整範例程式

建置命令

powershell
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

csharp
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 位元組,即可從一次矩陣擷取結果檢查多個按鍵。

語法

c
void __fkb_scan(u8* dst18);

參數

dst18

至少 18 位元組的可寫入空間。元素 row*2+col 接收該群組的原始值。不檢查空指標或長度。

回傳值

無傳回值。

使用條件與限制

這些 API 直接存取 $4016/$4017。不可與相同連接埠上的中斷驅動遊戲控制器、鍵盤或序列操作交錯執行。控制器移位位置也可能受影響。範例擷取輸入時停用 NMI,初始化顯示時再啟用。

按鍵群組是原始位元 1~4:按下會清除對應位元,未按下的群組為 0x1E。它們不是 ASCII 文字,也不是以一表示按下的位元圖。未連接裝置也可能讀到零;未檢查存在與否前,不可解讀為所有鍵都被按住。

向 $4016 寫入 5 以重設至第 0 列,再利用欄選擇位元由高轉低推進列。產生的程式碼除了周邊指令外,重設後另含 12 CPU 週期的 NOP,欄選擇後含 50 週期。退出時寫入零以停用矩陣。

另一項回歸檢查以實際執行的 CPU 匯流排追蹤,檢查列 0~9、兩欄、九列掃描、穩定延遲及最終連接埠狀態。不會注入實體按鍵訊號。

每一列先讀第 0 欄,再讀第 1 欄。這是依序掃描,並非同時擷取所有按鍵。某個群組的值為零也不會提早結束掃描。

這些影像是在 KUROSAKI 中原樣執行可下載 ROM 的結果。目前輸入匯流排模擬遊戲控制器,但不模擬鍵盤、光線槍或序列轉接器訊號。外部輸入位元讀為零,因此畫面不驗證這些裝置。實際按鍵輸入、光學感測及通訊尚未測試。

NESdev:Family BASIC 鍵盤

使用範例

c
#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,九組 COL0/COL1 都是 00。A 鍵位於第 6 列、第 0 欄、位元 4,但偵測失敗會阻止將原始零值回報為按下。此表為啟動時的單次擷取。

__fkb_scan: KEYBOARD-DEFAULT — 此次 KUROSAKI 執行中,DEVICE DETECT、ROW 6 COL 0、A KEY - GUARDED 皆為 000,九組 COL0/COL1 都是 00。A 鍵位於第 6 列、第 0 欄、位元 4,但偵測失敗會阻止將原始零值回報為按下。此表為啟動時的單次擷取。
KEYBOARD-DEFAULT — 此次 KUROSAKI 執行中,DEVICE DETECT、ROW 6 COL 0、A KEY - GUARDED 皆為 000,九組 COL0/COL1 都是 00。A 鍵位於第 6 列、第 0 欄、位元 4,但偵測失敗會阻止將原始零值回報為按下。此表為啟動時的單次擷取。

完整範例程式

建置命令

powershell
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

csharp
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 來源或保存原本的遮罩狀態。

語法

c
void __irq_disable(void);

回傳值

無(void)。

使用條件與限制

I 位元只控制一般 IRQ,NMI 仍可在此處理區間內發生。遮罩 IRQ 不會清除裝置的待處理請求,也不能保護與 NMI 共用的資料。請另外準備中斷來源及處理常式。此範例不測試產生 IRQ 的周邊設備。

NESdev: NMI

NESdev:CPU 中斷

使用範例

c
__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。

請讓模擬器執行至少 360 幀,以完成等待 256 次的示範。即使 IRQ 被遮罩,NMI DELTA 3 WAITS 也應為 003;位元組循環後,NMI DELTA 256 WAITS 應為 000。NMI OFF DELTA 也應為 000。程式不連結 runtime.c,使用預設處理常式。另有 21 項狀態檢查,涵蓋狀態位元還原,以及停用 NMI 或使用空自訂處理常式時在限定執行時間內的等待行為。已驗證 KUROSAKI/NROM,尚未驗證實機。

__irq_disable: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
case "__irq_disable": if (!Need(0)) return true; EmitAsm("SEI"); return true;
__irq_enable

產生 CLI 指令,清除 CPU 狀態暫存器的 I 位元,使一般 IRQ 請求可被處理。它不會設定映射器或 APU 的中斷來源。啟用某個中斷來源前,請先準備對應處理常式及清除請求的邏輯。

語法

c
void __irq_enable(void);

回傳值

無(void)。

使用條件與限制

I 位元只控制一般 IRQ,NMI 仍可在此處理區間內發生。遮罩 IRQ 不會清除裝置的待處理請求,也不能保護與 NMI 共用的資料。請另外準備中斷來源及處理常式。此範例不測試產生 IRQ 的周邊設備。

NESdev: NMI

NESdev:CPU 中斷

使用範例

c
__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。

請讓模擬器執行至少 360 幀,以完成等待 256 次的示範。即使 IRQ 被遮罩,NMI DELTA 3 WAITS 也應為 003;位元組循環後,NMI DELTA 256 WAITS 應為 000。NMI OFF DELTA 也應為 000。程式不連結 runtime.c,使用預設處理常式。另有 21 項狀態檢查,涵蓋狀態位元還原,以及停用 NMI 或使用空自訂處理常式時在限定執行時間內的等待行為。已驗證 KUROSAKI/NROM,尚未驗證實機。

__irq_enable: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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 運算式可能再次改變運算旗標。

語法

c
void __irq_restore(u8 state);

參數

state

__irq_save 保存的完整 8 位元 CPU 狀態值。保留全部位元供 __irq_restore 使用,不要轉為布林值。第 2 位元是 I:1 遮罩 IRQ,0 允許 IRQ。PHP 推入堆疊時也會將第 4、5 位元設為 1。

回傳值

無(void)。

使用條件與限制

I 位元只控制一般 IRQ,NMI 仍可在此處理區間內發生。遮罩 IRQ 不會清除裝置的待處理請求,也不能保護與 NMI 共用的資料。請另外準備中斷來源及處理常式。此範例不測試產生 IRQ 的周邊設備。

NESdev: NMI

NESdev:CPU 中斷

使用範例

c
__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。

請讓模擬器執行至少 360 幀,以完成等待 256 次的示範。即使 IRQ 被遮罩,NMI DELTA 3 WAITS 也應為 003;位元組循環後,NMI DELTA 256 WAITS 應為 000。NMI OFF DELTA 也應為 000。程式不連結 runtime.c,使用預設處理常式。另有 21 項狀態檢查,涵蓋狀態位元還原,以及停用 NMI 或使用空自訂處理常式時在限定執行時間內的等待行為。已驗證 KUROSAKI/NROM,尚未驗證實機。

__irq_restore: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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 的狀態。

語法

c
u8 __irq_save(void);

回傳值

__irq_save 保存的完整 8 位元 CPU 狀態值。保留全部位元供 __irq_restore 使用,不要轉為布林值。第 2 位元是 I:1 遮罩 IRQ,0 允許 IRQ。PHP 推入堆疊時也會將第 4、5 位元設為 1。

使用條件與限制

I 位元只控制一般 IRQ,NMI 仍可在此處理區間內發生。遮罩 IRQ 不會清除裝置的待處理請求,也不能保護與 NMI 共用的資料。請另外準備中斷來源及處理常式。此範例不測試產生 IRQ 的周邊設備。

NESdev: NMI

NESdev:CPU 中斷

使用範例

c
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。

請讓模擬器執行至少 360 幀,以完成等待 256 次的示範。即使 IRQ 被遮罩,NMI DELTA 3 WAITS 也應為 003;位元組循環後,NMI DELTA 256 WAITS 應為 000。NMI OFF DELTA 也應為 000。程式不連結 runtime.c,使用預設處理常式。另有 21 項狀態檢查,涵蓋狀態位元還原,以及停用 NMI 或使用空自訂處理常式時在限定執行時間內的等待行為。已驗證 KUROSAKI/NROM,尚未驗證實機。

__irq_save: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
case "__irq_save": if (!Need(0)) return true; EmitAsm("PHP"); EmitAsm("SEI"); EmitAsm("PLA"); return true;
__irq_scanline_set

__mapper_irq_set 中 MMC3 鎖存/重新載入操作的別名。名稱中的 scanline 不保證對應絕對螢幕列。

語法

c
void __irq_scanline_set(u8 line);

參數

line

八位元計數器鎖存值,範圍為 0~255,不是絕對螢幕 Y 座標。重新載入時機與符合條件的 PPU A12 轉換決定觸發時刻。零是有效值,不代表「停用中斷」。

回傳值

無傳回值。

使用條件與限制

內建函式宣告位於 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 呼叫,也須檢查它們使用的共用儲存空間。

使用範例

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:前三次明確啟用各允許一次中斷,停用後次數不變。每階段等待六個影格。

__irq_scanline_set: MMC3-DEFAULT
MMC3-DEFAULT

完整範例程式

建置命令

powershell
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」並不表示音訊處理。分開呼叫會分別取樣,如果期間訊號發生變化,結果可能不同。

從$4016的位元2讀取紅白機第二個控制器麥克風對應的數位訊號。輔助常式讀取一次暫存器,用0x04遮罩篩選,不反轉,並將結果正規化為0或1。它不會鎖存控制器、錄製音訊、測量音量或辨識語音。

語法

c
u8 __joypad2p_voice(void);

回傳值

$4016 & 0x04非零時恰好回傳1,否則回傳0。這是取樣到的數位電位,不是按鈕遮罩、振幅、頻率或說出的詞數。

使用條件與限制

麥克風雖位於第二個控制器上,此輔助常式存取的卻是$4016,而非$4017。選通信號為低時,讀取暫存器也可能推進控制器的序列資料流,因此不要在其他控制器的八位元讀取迴圈中插入麥克風讀取。不會更新輸入歷程快取。

公開版KUROSAKI的控制器模型僅回傳D0與固定的位元6。因此即使按住一般控制器按鈕,該模擬器中的D1與麥克風位元也始終為0。所附執行結果驗證了編譯、一般按鈕輸入及此零訊號現象。周邊裝置的非零訊號、電氣時序及實機相容性仍未驗證;這裡的0既不能證明連接的裝置正常運作,也不能證明這些函式在實機上無法讀取它。

使用範例

c
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,因為對應訊號線未被模擬。擷取後顯示值保持不變。在具備適合的周邊裝置及其訊號線實作的環境中,擴充與麥克風數值取決於實際輸入;應依上述位元配置解讀,而不是期待這些零值。

公開版KUROSAKI的控制器模型僅回傳D0與固定的位元6。因此即使按住一般控制器按鈕,該模擬器中的D1與麥克風位元也始終為0。所附執行結果驗證了編譯、一般按鈕輸入及此零訊號現象。周邊裝置的非零訊號、電氣時序及實機相容性仍未驗證;這裡的0既不能證明連接的裝置正常運作,也不能證明這些函式在實機上無法讀取它。

__joypad2p_voice: NROM
NROM

完整範例程式

建置命令

powershell
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

計算無號乘加。

語法

c
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 位元樣式傳回。

使用範例

c
// 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 位元值顯示;負數使用二補數位元樣式。GB 會縮放固定小數點乘積,FC 則傳回整數乘積,因此相同輸入不一定得到相同結果。

此程式碼片段儲存以下 16 位元值:result[8]=65115, result[9]=65115。各項常值輸入計算皆與對應的 RAM 輸入結果一致。畫面顯示代表值,result 陣列則保存所有結果。

__mac16: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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 差的絕對值相加。

語法

c
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 的距離會回捲。

使用範例

c
result[5]=__manhattan(2,3,9,11);result[6]=__manhattan(0,0,255,255);

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

測試內部、邊界、外部點及零寬度:INSIDE=1、RIGHT EDGE=0、LEFT EDGE=0、HIGH X=1。另驗證距離 15、回捲距離 254,以及陣列索引 64/65280。

__manhattan: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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

將格子座標轉為逐列儲存的陣列索引。

語法

c
u16 __map_index(u8 x, u8 y, u8 width);

參數

x

欄,0..255,通常小於 width。

y

列,0..255。

width

每列格子數,0..255。

回傳值

以 u16 傳回 y*width+x;不驗證座標。

使用範例

c
result[7]=__map_index(4,3,20);result[8]=__map_index(255,255,255);

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

測試內部、邊界、外部點及零寬度:INSIDE=1、RIGHT EDGE=0、LEFT EDGE=0、HIGH X=1。另驗證距離 15、回捲距離 254,以及陣列索引 64/65280。

__map_index: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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

傳回建置時選定的映射器編號,供電路板專用初始化或顯示使用。

語法

c
u8 __mapper_id(void);

回傳值

八位元映射器編號: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 選擇電路板。

使用範例

c
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:前三次明確啟用各允許一次中斷,停用後次數不變。每階段等待六個影格。

__mapper_id: MMC3-DEFAULT
MMC3-DEFAULT

完整範例程式

建置命令

powershell
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 請求。

語法

c
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 呼叫,也須檢查它們使用的共用儲存空間。

使用範例

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:前三次明確啟用各允許一次中斷,停用後次數不變。每階段等待六個影格。

__mapper_irq_ack: MMC3-DEFAULT
MMC3-DEFAULT

完整範例程式

建置命令

powershell
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 請求並停用後續請求。

語法

c
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 呼叫,也須檢查它們使用的共用儲存空間。

使用範例

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:前三次明確啟用各允許一次中斷,停用後次數不變。每階段等待六個影格。

__mapper_irq_disable: MMC3-DEFAULT
MMC3-DEFAULT

完整範例程式

建置命令

powershell
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。不設定鎖存值,也不要求重新載入。

語法

c
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 呼叫,也須檢查它們使用的共用儲存空間。

使用範例

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:前三次明確啟用各允許一次中斷,停用後次數不變。每階段等待六個影格。

__mapper_irq_enable: MMC3-DEFAULT
MMC3-DEFAULT

完整範例程式

建置命令

powershell
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 要求下次重新載入。啟用中斷是另一項操作。

語法

c
void __mapper_irq_set(u8 scanline);

參數

scanline

八位元計數器鎖存值,範圍為 0~255,不是絕對螢幕 Y 座標。重新載入時機與符合條件的 PPU A12 轉換決定觸發時刻。零是有效值,不代表「停用中斷」。

回傳值

無傳回值。

使用條件與限制

內建函式宣告位於 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 呼叫,也須檢查它們使用的共用儲存空間。

使用範例

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:前三次明確啟用各允許一次中斷,停用後次數不變。每階段等待六個影格。

__mapper_irq_set: MMC3-DEFAULT
MMC3-DEFAULT

完整範例程式

建置命令

powershell
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 位元數值,可用於複製超過 255 位元組的資料,例如 RAM 中的整張表。讀寫依位址遞增順序進行。

語法

c
void __memcpy(u8* dst, const u8* src, u16 len);

參數

dst

指向目標區域第一個可寫入位元組的指標。必須預先準備足夠的連續空間;函式不會自動配置緩衝區。

src

指向來源區域第一個可讀取位元組的指標。複製期間,整個來源區域必須維持可存取且內容不變。

len

以 u16 指定的位元組數(0~65535),而非元素個數。型別的最大值不代表目標機器能提供同樣大小的有效緩衝區。整個範圍必須位於目前的記憶體映射之內。

回傳值

無(void)。

使用條件與限制

長度為 0 時不傳輸任何位元組,但引數運算式中的副作用仍會發生。範例將兩個回傳指標的函式作為引數,並確認即使複製長度為 0,這兩個函式仍都會被呼叫。

來源區域與目標區域應互不重疊。這些函式依位址遞增方向複製,不提供 memmove 對重疊區域的安全保證;寫入尚未讀取的來源區域會破壞後續資料。

操作完成後才返回呼叫端。函式不會切換 ROM/RAM 記憶體庫,也不會將範圍自動裁切至緩衝區邊界。請維持映射穩定、指定有效範圍,並協調與使用相同緩衝區的中斷處理程式之間的存取。

在 FC 上,dst 是 CPU 位址,而非 PPU 位址。複製至 CPU RAM 不會更新 CHR、名稱表或調色盤;上傳這些資料應使用 PPU/VRAM API。產生的輔助常式共用引數與暫存空間,呼叫尚未結束時,請勿從 NMI/IRQ 再次進入這些常式。

使用範例

c
memory_reset();
__memcpy(memory_destination+1,memory_source+1,257);
memory_copy_word=memory_check(257,0); // 257 copied bytes; guards unchanged.

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此例以 __memcpy 從偏移量 1 開始複製 257 位元組的規律資料。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 位元組。尚未進行實機驗證。

__memcpy: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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 表示長度的位元寬度,並不保證特定的執行時間或產生的程式碼大小。

語法

c
void __memcpy_small(u8* dst, const u8* src, u8 len);

參數

dst

指向目標區域第一個可寫入位元組的指標。必須預先準備足夠的連續空間;函式不會自動配置緩衝區。

src

指向來源區域第一個可讀取位元組的指標。複製期間,整個來源區域必須維持可存取且內容不變。

len

以 u8 指定的位元組數,範圍為 0~255。不要傳入 256 來表示完整的 256 位元組區塊,這個參數的型別無法表示該長度。256 位元組以上請使用名稱不含 _small 的對應函式。

回傳值

無(void)。

使用條件與限制

長度為 0 時不傳輸任何位元組,但引數運算式中的副作用仍會發生。範例將兩個回傳指標的函式作為引數,並確認即使複製長度為 0,這兩個函式仍都會被呼叫。

來源區域與目標區域應互不重疊。這些函式依位址遞增方向複製,不提供 memmove 對重疊區域的安全保證;寫入尚未讀取的來源區域會破壞後續資料。

操作完成後才返回呼叫端。函式不會切換 ROM/RAM 記憶體庫,也不會將範圍自動裁切至緩衝區邊界。請維持映射穩定、指定有效範圍,並協調與使用相同緩衝區的中斷處理程式之間的存取。

在 FC 上,dst 是 CPU 位址,而非 PPU 位址。複製至 CPU RAM 不會更新 CHR、名稱表或調色盤;上傳這些資料應使用 PPU/VRAM API。產生的輔助常式共用引數與暫存空間,呼叫尚未結束時,請勿從 NMI/IRQ 再次進入這些常式。

使用範例

c
memory_reset();
__memcpy_small(memory_destination+1,memory_source+1,255);
memory_copy_byte=memory_check(255,0); // Maximum representable byte count.

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此例以 __memcpy_small 從偏移量 1 開始複製 255 位元組的規律資料。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 位元組。尚未進行實機驗證。

__memcpy_small: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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

以相同的單一位元組值填滿目標區域。長度為 16 位元,適合清空較大的緩衝區或初始化表。例如,填入值為 0x5A 時會寫入 5A 5A 5A…,並非重複寫入一個多位元組整數。

語法

c
void __memset(u8* dst, u8 value, u16 len);

參數

dst

指向目標區域第一個可寫入位元組的指標。必須預先準備足夠的連續空間;函式不會自動配置緩衝區。

value

在每個目標位址重複寫入的單一位元組值,範圍為 0~255。使用 0 可將該區域清為零。如需將 u16 陣列初始化為非零數值,應逐一對元素賦值。

len

以 u16 指定的位元組數(0~65535),而非元素個數。型別的最大值不代表目標機器能提供同樣大小的有效緩衝區。整個範圍必須位於目前的記憶體映射之內。

回傳值

無(void)。

使用條件與限制

長度為 0 時不傳輸任何位元組,但引數運算式中的副作用仍會發生。範例將兩個回傳指標的函式作為引數,並確認即使複製長度為 0,這兩個函式仍都會被呼叫。

操作完成後才返回呼叫端。函式不會切換 ROM/RAM 記憶體庫,也不會將範圍自動裁切至緩衝區邊界。請維持映射穩定、指定有效範圍,並協調與使用相同緩衝區的中斷處理程式之間的存取。

在 FC 上,dst 是 CPU 位址,而非 PPU 位址。複製至 CPU RAM 不會更新 CHR、名稱表或調色盤;上傳這些資料應使用 PPU/VRAM API。產生的輔助常式共用引數與暫存空間,呼叫尚未結束時,請勿從 NMI/IRQ 再次進入這些常式。

使用範例

c
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 位元組。尚未進行實機驗證。

__memset: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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 個位元組填入同一個單一位元組值。適合初始化小型工作緩衝區。所需長度超過 255 位元組時,請使用 __memset。

語法

c
void __memset_small(u8* dst, u8 value, u8 len);

參數

dst

指向目標區域第一個可寫入位元組的指標。必須預先準備足夠的連續空間;函式不會自動配置緩衝區。

value

在每個目標位址重複寫入的單一位元組值,範圍為 0~255。使用 0 可將該區域清為零。如需將 u16 陣列初始化為非零數值,應逐一對元素賦值。

len

以 u8 指定的位元組數,範圍為 0~255。不要傳入 256 來表示完整的 256 位元組區塊,這個參數的型別無法表示該長度。256 位元組以上請使用名稱不含 _small 的對應函式。

回傳值

無(void)。

使用條件與限制

長度為 0 時不傳輸任何位元組,但引數運算式中的副作用仍會發生。範例將兩個回傳指標的函式作為引數,並確認即使複製長度為 0,這兩個函式仍都會被呼叫。

操作完成後才返回呼叫端。函式不會切換 ROM/RAM 記憶體庫,也不會將範圍自動裁切至緩衝區邊界。請維持映射穩定、指定有效範圍,並協調與使用相同緩衝區的中斷處理程式之間的存取。

在 FC 上,dst 是 CPU 位址,而非 PPU 位址。複製至 CPU RAM 不會更新 CHR、名稱表或調色盤;上傳這些資料應使用 PPU/VRAM API。產生的輔助常式共用引數與暫存空間,呼叫尚未結束時,請勿從 NMI/IRQ 再次進入這些常式。

使用範例

c
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 位元組。尚未進行實機驗證。

__memset_small: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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編號。

語法

c
u8 __metasprite_draw(u8 oam_index, u8 base_x, u8 base_y, const u8* metasprite);

參數

oam_index

精靈項目編號,範圍0–63,不是位元組偏移。實作使用8位元游標將其乘以4;超出範圍的值會以64為週期循環,可能覆寫其他項目。不會檢查項目是否已配置。

base_x

以像素為單位的橫向位置,使用無號位元組表示,並非圖塊座標。

base_y

直接寫入 OAM 的 Y 位元組。若要將頂端放在第 1 條掃描線或更下方,應傳入頂端座標減一;239..255 位於 240 行可見區域之外。

metasprite

指向可讀四位元組記錄的指標,格式為dx, dy, tile, attr。下一個dx位置的0xFF表示結束,其後無須其他位元組。偏移以256為週期相加:0xFE表示−2,dy的0xFF可表示−1,但dx的0xFF保留作終止標記,不能表示−1。指標必須在目前映射的記憶體中有效。

回傳值

下一個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陣列。

使用範例

c
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)顯示箭頭。程式使用__oam_dma直接傳送第02頁,其中包含各項精靈操作準備好的全部欄位。

__metasprite_draw: PAGE02
PAGE02

完整範例程式

建置命令

powershell
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.
csharp
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

從$4016的位元2讀取紅白機第二個控制器麥克風對應的數位訊號。輔助常式讀取一次暫存器,用0x04遮罩篩選,不反轉,並將結果正規化為0或1。它不會鎖存控制器、錄製音訊、測量音量或辨識語音。

語法

c
u8 __mic_read2p(void);

回傳值

$4016 & 0x04非零時恰好回傳1,否則回傳0。這是取樣到的數位電位,不是按鈕遮罩、振幅、頻率或說出的詞數。

使用條件與限制

麥克風雖位於第二個控制器上,此輔助常式存取的卻是$4016,而非$4017。選通信號為低時,讀取暫存器也可能推進控制器的序列資料流,因此不要在其他控制器的八位元讀取迴圈中插入麥克風讀取。不會更新輸入歷程快取。

公開版KUROSAKI的控制器模型僅回傳D0與固定的位元6。因此即使按住一般控制器按鈕,該模擬器中的D1與麥克風位元也始終為0。所附執行結果驗證了編譯、一般按鈕輸入及此零訊號現象。周邊裝置的非零訊號、電氣時序及實機相容性仍未驗證;這裡的0既不能證明連接的裝置正常運作,也不能證明這些函式在實機上無法讀取它。

使用範例

c
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,因為對應訊號線未被模擬。擷取後顯示值保持不變。在具備適合的周邊裝置及其訊號線實作的環境中,擴充與麥克風數值取決於實際輸入;應依上述位元配置解讀,而不是期待這些零值。

公開版KUROSAKI的控制器模型僅回傳D0與固定的位元6。因此即使按住一般控制器按鈕,該模擬器中的D1與麥克風位元也始終為0。所附執行結果驗證了編譯、一般按鈕輸入及此零訊號現象。周邊裝置的非零訊號、電氣時序及實機相容性仍未驗證;這裡的0既不能證明連接的裝置正常運作,也不能證明這些函式在實機上無法讀取它。

__mic_read2p: NROM
NROM

完整範例程式

建置命令

powershell
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。重複節拍由呼叫端排程。

語法

c
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 時序及實體轉接器的雙向通訊尚未驗證。

使用範例

c
// 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,內部聲部不發聲。傳送的十六進位位元組為 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC。檢查要求 NTSC 傳送單位為 57 週期,資料讀取間隔也為 57 週期。接收值 00 來自模擬器未連接的輸入,不能證明與外部裝置通訊。

__midi_clock: NTSC
NTSC

完整範例程式

建置命令

powershell
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

csharp
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,不操作內部音樂驅動。

語法

c
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 時序及實體轉接器的雙向通訊尚未驗證。

使用範例

c
// 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,內部聲部不發聲。傳送的十六進位位元組為 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC。檢查要求 NTSC 傳送單位為 57 週期,資料讀取間隔也為 57 週期。接收值 00 來自模擬器未連接的輸入,不能證明與外部裝置通訊。

__midi_continue: NTSC
NTSC

完整範例程式

建置命令

powershell
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

csharp
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)、控制器編號及數值傳送控制變更。

語法

c
void __midi_control_change(u8 ch, u8 cc, u8 value);

參數

ch

MIDI 通道 0..15,低半位元組放入狀態位元組。零對應顯示的 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 時序及實體轉接器的雙向通訊尚未驗證。

使用範例

c
// 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,內部聲部不發聲。傳送的十六進位位元組為 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC。檢查要求 NTSC 傳送單位為 57 週期,資料讀取間隔也為 57 週期。接收值 00 來自模擬器未連接的輸入,不能證明與外部裝置通訊。

__midi_control_change: NTSC
NTSC

完整範例程式

建置命令

powershell
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

csharp
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

等待接收輸入為低電位,再按定時間隔取樣八個位元。

語法

c
u8 __midi_in_byte(void);

回傳值

傳回接收的 u8。沒有逾時、訊框驗證或接收緩衝區。輸入保持高電位會無限等待;保持低電位則讀為零。

使用條件與限制

需要外部電路將 $4016 D0 接至 TX、$4017 D4 接至 RX。一般控制器不提供 MIDI,這些呼叫也不播放內部 APU。傳輸期間須停止 DMC 播放/DMA 並遮蔽 NMI、IRQ,避免並行存取連接埠及共用暫存區。NTSC 每位元 57 週期約為 31,400 bps,比 31,250 約高 0.5%。PAL 時序及實體轉接器的雙向通訊尚未驗證。

使用範例

c
// 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,內部聲部不發聲。傳送的十六進位位元組為 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC。檢查要求 NTSC 傳送單位為 57 週期,資料讀取間隔也為 57 週期。接收值 00 來自模擬器未連接的輸入,不能證明與外部裝置通訊。

__midi_in_byte: NTSC
NTSC

完整範例程式

建置命令

powershell
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

csharp
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

以三個位元組傳送 note-off:0x80 | (ch & 0x0F)、note、velocity。

語法

c
void __midi_note_off(u8 ch, u8 note, u8 velocity);

參數

ch

MIDI 通道 0..15,低半位元組放入狀態位元組。零對應顯示的 MIDI 通道 1。

note

MIDI 音符 0..127,69 為 A4。函式不遮罩為七位元,呼叫端須確保範圍。

velocity

力度 0..127,不會自動遮罩為七位元。

回傳值

無回傳值。會改變音效暫存器或驅動狀態。

使用條件與限制

需要外部電路將 $4016 D0 接至 TX、$4017 D4 接至 RX。一般控制器不提供 MIDI,這些呼叫也不播放內部 APU。傳輸期間須停止 DMC 播放/DMA 並遮蔽 NMI、IRQ,避免並行存取連接埠及共用暫存區。NTSC 每位元 57 週期約為 31,400 bps,比 31,250 約高 0.5%。PAL 時序及實體轉接器的雙向通訊尚未驗證。

使用範例

c
// 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,內部聲部不發聲。傳送的十六進位位元組為 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC。檢查要求 NTSC 傳送單位為 57 週期,資料讀取間隔也為 57 週期。接收值 00 來自模擬器未連接的輸入,不能證明與外部裝置通訊。

__midi_note_off: NTSC
NTSC

完整範例程式

建置命令

powershell
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

csharp
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

以三個位元組傳送 note-on:0x90 | (ch & 0x0F)、note、velocity。

語法

c
void __midi_note_on(u8 ch, u8 note, u8 velocity);

參數

ch

MIDI 通道 0..15,低半位元組放入狀態位元組。零對應顯示的 MIDI 通道 1。

note

MIDI 音符 0..127,69 為 A4。函式不遮罩為七位元,呼叫端須確保範圍。

velocity

力度 0..127,不會自動遮罩為七位元。

回傳值

無回傳值。會改變音效暫存器或驅動狀態。

使用條件與限制

需要外部電路將 $4016 D0 接至 TX、$4017 D4 接至 RX。一般控制器不提供 MIDI,這些呼叫也不播放內部 APU。傳輸期間須停止 DMC 播放/DMA 並遮蔽 NMI、IRQ,避免並行存取連接埠及共用暫存區。NTSC 每位元 57 週期約為 31,400 bps,比 31,250 約高 0.5%。PAL 時序及實體轉接器的雙向通訊尚未驗證。

使用範例

c
// 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,內部聲部不發聲。傳送的十六進位位元組為 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC。檢查要求 NTSC 傳送單位為 57 週期,資料讀取間隔也為 57 週期。接收值 00 來自模擬器未連接的輸入,不能證明與外部裝置通訊。

__midi_note_on: NTSC
NTSC

完整範例程式

建置命令

powershell
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

csharp
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 位元組:低電位起始、最低位元先送的八個資料位元、高電位停止。

語法

c
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 時序及實體轉接器的雙向通訊尚未驗證。

使用範例

c
// 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,內部聲部不發聲。傳送的十六進位位元組為 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC。檢查要求 NTSC 傳送單位為 57 週期,資料讀取間隔也為 57 週期。接收值 00 來自模擬器未連接的輸入,不能證明與外部裝置通訊。

__midi_out_byte: NTSC
NTSC

完整範例程式

建置命令

powershell
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

csharp
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) 後接音色程式編號,傳送程式變更。

語法

c
void __midi_program_change(u8 ch, u8 program);

參數

ch

MIDI 通道 0..15,低半位元組放入狀態位元組。零對應顯示的 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 時序及實體轉接器的雙向通訊尚未驗證。

使用範例

c
// 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,內部聲部不發聲。傳送的十六進位位元組為 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC。檢查要求 NTSC 傳送單位為 57 週期,資料讀取間隔也為 57 週期。接收值 00 來自模擬器未連接的輸入,不能證明與外部裝置通訊。

__midi_program_change: NTSC
NTSC

完整範例程式

建置命令

powershell
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

csharp
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,不改變遊戲歌曲指標。

語法

c
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 時序及實體轉接器的雙向通訊尚未驗證。

使用範例

c
// 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,內部聲部不發聲。傳送的十六進位位元組為 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC。檢查要求 NTSC 傳送單位為 57 週期,資料讀取間隔也為 57 週期。接收值 00 來自模擬器未連接的輸入,不能證明與外部裝置通訊。

__midi_start: NTSC
NTSC

完整範例程式

建置命令

powershell
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

csharp
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 或先前送出的音符靜音。

語法

c
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 時序及實體轉接器的雙向通訊尚未驗證。

使用範例

c
// 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,內部聲部不發聲。傳送的十六進位位元組為 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC。檢查要求 NTSC 傳送單位為 57 週期,資料讀取間隔也為 57 週期。接收值 00 來自模擬器未連接的輸入,不能證明與外部裝置通訊。

__midi_stop: NTSC
NTSC

完整範例程式

建置命令

powershell
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

csharp
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,用於捲動配置或單畫面頁選擇。

語法

c
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 使用最低位元,其餘使用低兩位元。

回傳值

無傳回值。

使用條件與限制

內建函式宣告位於 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,選擇固定高位 16 KiB PRG 視窗及 8 KiB CHR 模式。SUROM 也更新控制暫存副本。AxROM 保留執行期 PRG 記憶體庫值,只變更畫面位元。若也直接寫入映射器暫存器,須使其與執行期記憶體庫狀態一致。

MMC5 時,API 對 $5105 寫入 $50/$44/$00/$55。這些配置只使用主機的兩個 CIRAM 頁。ExRAM 與填充模式不在此 API 範圍內。KUROSAKI 支援全部十六種純 CIRAM 配置,但仍不支援 MMC5 ExRAM 與填充繪製。

要查看各電路板,請將範例的 --mapper=mmc3 改為 mmc1、mmc5、axrom 或 fme7。範例透過各板暫存器初始化 MMC5/FME7 字型 CHR 記憶體庫。

使用範例

c
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。讀回後才顯示文字。

__mirroring_set: MMC1-DEFAULT
MMC1-DEFAULT
__mirroring_set: MMC3-DEFAULT
MMC3-DEFAULT
__mirroring_set: MMC5-DEFAULT
MMC5-DEFAULT
__mirroring_set: AXROM-DEFAULT
AXROM-DEFAULT
__mirroring_set: FME7-DEFAULT
FME7-DEFAULT

完整範例程式

建置命令

powershell
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

計算無號乘法。

語法

c
u16 __mul16x8(u16 a, u8 b);

參數

a

無號雙位元組值,0..65535。

b

無號係數,0..255。

回傳值

傳回 a*b 的低 16 位元。

使用條件與限制

FC 中名為 q8_8/q1_7 的函式使用原始整數乘積,不做固定小數點縮放右移。Q1.7 函式是對應帶正負號運算的別名。單位換算由呼叫端處理;帶正負號的結果以 u16 位元樣式傳回。

使用範例

c
// Compare literal coefficients with values read from RAM.
result[2]=__mul16x8(513,127);
v0=513;v1=127;
result[3]=__mul16x8(v0,v1);

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

比較常值輸入與 RAM 中儲存的值。代表結果以無號 16 位元值顯示;負數使用二補數位元樣式。GB 會縮放固定小數點乘積,FC 則傳回整數乘積,因此相同輸入不一定得到相同結果。

此程式碼片段儲存以下 16 位元值:result[2]=65151, result[3]=65151。各項常值輸入計算皆與對應的 RAM 輸入結果一致。畫面顯示代表值,result 陣列則保存所有結果。

__mul16x8: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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

將兩個無號位元組相乘,傳回高位元組。

語法

c
u8 __mul8x8_hi(u8 a, u8 b);

參數

a

無號位元組,0..255。

b

無號位元組,0..255。

回傳值

floor(a*b/256),範圍為 0..254。

使用範例

c
// 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 位元值顯示;負數使用二補數位元樣式。GB 會縮放固定小數點乘積,FC 則傳回整數乘積,因此相同輸入不一定得到相同結果。

此程式碼片段儲存以下 16 位元值:result[0]=254, result[1]=254。各項常值輸入計算皆與對應的 RAM 輸入結果一致。畫面顯示代表值,result 陣列則保存所有結果。

__mul8x8_hi: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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 的一個圖塊位元組。設定位址前會重設鎖存器。不會寫入圖塊圖像或調色盤屬性,也不裁切座標。

語法

c
void __nametable_put(u8 x, u8 y, u8 tile);

參數

x / y

圖塊座標,不是像素座標。可見的 32×30 圖塊地圖應使用 x=0..31、y=0..29;每個圖塊為 8×8 像素。位元組參數不會被限制在範圍內,無效座標可能寫到預期圖塊區域之外。

tile

PPUCTRL 所選背景圖樣表中的圖塊編號 0..255。它選擇既有 CHR 圖像,不是像素色彩或來源指標。

回傳值

無(void)。

使用條件與限制

直接存取 PPU 時,必須關閉繪製,或確保整個操作位於安全的 VBlank 時間內。這些函式不等待、不排入佇列、不阻擋 NMI,也不還原捲動。呼叫期間應防止中斷改變共用鎖存器或位址。PPUCTRL 第 2 位元決定每個資料位元組使目的位址增加 1 還是 32。鏡像和位址繞回由 PPU 處理;CHR-ROM 不可寫入。存取結束後、開始繪製前,請設定所需的捲動狀態。

NESdev:PPU 暫存器

NESdev:PPU 捲動

使用範例

c
__nametable_put(2,12,1); // Blue solid marker at (16,96).

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式用於學習暫存器設定、圖塊與屬性直接寫入、捲動,以及背景和精靈調色盤的分別載入。(0,16) 處藍色條帶為 256×16 像素:首行重複實心 8×8 圖塊、左半 4×8 直條和八行三角形,第二行只有左半直條。(16,48) 處是綠色實心 64×32 矩形,(144,48) 處的紅色 64×32 區域由橫八、縱四個三角形圖塊組成。

y=96 處,x=16、24、32 的藍色標記分別為正方形、左半直條和三角形;x=80 為紅色左半直條,x=144 為綠色三角形。這些是背景圖塊。使用獨立調色盤的精靈位於 y=144:x=16 是紅色 8×8 正方形,x=80 是藍色左半 4×8 直條,x=144 是綠色八行三角形。最終捲動為 (0,0)。

影像檢查 16,384 個像素的預期形狀和色彩,包括精靈色彩。另有 31 個狀態測試,涵蓋暫存器替換、狀態與鎖存器副作用、捲動儲存值、0/255 位元組傳輸、增量 1/32、地圖邊緣、矩形零尺寸、表選擇、完整屬性位元組及兩種調色盤目標。Y=255 僅檢查暫存器寫入,不代表驗證了正確的捲動畫面。證據來自 KUROSAKI 的 NROM 環境,未測試實機。

__nametable_put: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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 寫入一個圖塊。寫入位址對之前重設鎖存器。卡匣鏡像可能讓不同的邏輯表編號指向同一實體記憶體。

語法

c
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 像素。位元組參數不會被限制在範圍內,無效座標可能寫到預期圖塊區域之外。

tile

PPUCTRL 所選背景圖樣表中的圖塊編號 0..255。它選擇既有 CHR 圖像,不是像素色彩或來源指標。

回傳值

無(void)。

使用條件與限制

直接存取 PPU 時,必須關閉繪製,或確保整個操作位於安全的 VBlank 時間內。這些函式不等待、不排入佇列、不阻擋 NMI,也不還原捲動。呼叫期間應防止中斷改變共用鎖存器或位址。PPUCTRL 第 2 位元決定每個資料位元組使目的位址增加 1 還是 32。鏡像和位址繞回由 PPU 處理;CHR-ROM 不可寫入。存取結束後、開始繪製前,請設定所需的捲動狀態。

NESdev:PPU 暫存器

NESdev:PPU 捲動

使用範例

c
__nametable_put_nt(4,10,12,2); // Red left-half marker at (80,96), table 0.

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式用於學習暫存器設定、圖塊與屬性直接寫入、捲動,以及背景和精靈調色盤的分別載入。(0,16) 處藍色條帶為 256×16 像素:首行重複實心 8×8 圖塊、左半 4×8 直條和八行三角形,第二行只有左半直條。(16,48) 處是綠色實心 64×32 矩形,(144,48) 處的紅色 64×32 區域由橫八、縱四個三角形圖塊組成。

y=96 處,x=16、24、32 的藍色標記分別為正方形、左半直條和三角形;x=80 為紅色左半直條,x=144 為綠色三角形。這些是背景圖塊。使用獨立調色盤的精靈位於 y=144:x=16 是紅色 8×8 正方形,x=80 是藍色左半 4×8 直條,x=144 是綠色八行三角形。最終捲動為 (0,0)。

影像檢查 16,384 個像素的預期形狀和色彩,包括精靈色彩。另有 31 個狀態測試,涵蓋暫存器替換、狀態與鎖存器副作用、捲動儲存值、0/255 位元組傳輸、增量 1/32、地圖邊緣、矩形零尺寸、表選擇、完整屬性位元組及兩種調色盤目標。Y=255 僅檢查暫存器寫入,不代表驗證了正確的捲動畫面。證據來自 KUROSAKI 的 NROM 環境,未測試實機。

__nametable_put_nt: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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 的圖塊區域。每行重設鎖存器並設定位址,寫入 w 個 tile,再將下一行起點前移 32 位元組。不改變調色盤屬性。

語法

c
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。

tile

PPUCTRL 所選背景圖樣表中的圖塊編號 0..255。它選擇既有 CHR 圖像,不是像素色彩或來源指標。

回傳值

無(void)。

使用條件與限制

直接存取 PPU 時,必須關閉繪製,或確保整個操作位於安全的 VBlank 時間內。這些函式不等待、不排入佇列、不阻擋 NMI,也不還原捲動。呼叫期間應防止中斷改變共用鎖存器或位址。PPUCTRL 第 2 位元決定每個資料位元組使目的位址增加 1 還是 32。鏡像和位址繞回由 PPU 處理;CHR-ROM 不可寫入。存取結束後、開始繪製前,請設定所需的捲動狀態。

NESdev:PPU 暫存器

NESdev:PPU 捲動

使用範例

c
__nametable_rect(2,6,8,4,1); // Solid green rectangle at (16,48), 64x32 pixels.

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式用於學習暫存器設定、圖塊與屬性直接寫入、捲動,以及背景和精靈調色盤的分別載入。(0,16) 處藍色條帶為 256×16 像素:首行重複實心 8×8 圖塊、左半 4×8 直條和八行三角形,第二行只有左半直條。(16,48) 處是綠色實心 64×32 矩形,(144,48) 處的紅色 64×32 區域由橫八、縱四個三角形圖塊組成。

y=96 處,x=16、24、32 的藍色標記分別為正方形、左半直條和三角形;x=80 為紅色左半直條,x=144 為綠色三角形。這些是背景圖塊。使用獨立調色盤的精靈位於 y=144:x=16 是紅色 8×8 正方形,x=80 是藍色左半 4×8 直條,x=144 是綠色八行三角形。最終捲動為 (0,0)。

影像檢查 16,384 個像素的預期形狀和色彩,包括精靈色彩。另有 31 個狀態測試,涵蓋暫存器替換、狀態與鎖存器副作用、捲動儲存值、0/255 位元組傳輸、增量 1/32、地圖邊緣、矩形零尺寸、表選擇、完整屬性位元組及兩種調色盤目標。Y=255 僅檢查暫存器寫入,不代表驗證了正確的捲動畫面。證據來自 KUROSAKI 的 NROM 環境,未測試實機。

__nametable_rect: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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。只寫圖塊位元組,不更新屬性、不裁切,也不設定鏡像。

語法

c
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。

tile

PPUCTRL 所選背景圖樣表中的圖塊編號 0..255。它選擇既有 CHR 圖像,不是像素色彩或來源指標。

回傳值

無(void)。

使用條件與限制

直接存取 PPU 時,必須關閉繪製,或確保整個操作位於安全的 VBlank 時間內。這些函式不等待、不排入佇列、不阻擋 NMI,也不還原捲動。呼叫期間應防止中斷改變共用鎖存器或位址。PPUCTRL 第 2 位元決定每個資料位元組使目的位址增加 1 還是 32。鏡像和位址繞回由 PPU 處理;CHR-ROM 不可寫入。存取結束後、開始繪製前,請設定所需的捲動狀態。

NESdev:PPU 暫存器

NESdev:PPU 捲動

使用範例

c
__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 直條和八行三角形,第二行只有左半直條。(16,48) 處是綠色實心 64×32 矩形,(144,48) 處的紅色 64×32 區域由橫八、縱四個三角形圖塊組成。

y=96 處,x=16、24、32 的藍色標記分別為正方形、左半直條和三角形;x=80 為紅色左半直條,x=144 為綠色三角形。這些是背景圖塊。使用獨立調色盤的精靈位於 y=144:x=16 是紅色 8×8 正方形,x=80 是藍色左半 4×8 直條,x=144 是綠色八行三角形。最終捲動為 (0,0)。

影像檢查 16,384 個像素的預期形狀和色彩,包括精靈色彩。另有 31 個狀態測試,涵蓋暫存器替換、狀態與鎖存器副作用、捲動儲存值、0/255 位元組傳輸、增量 1/32、地圖邊緣、矩形零尺寸、表選擇、完整屬性位元組及兩種調色盤目標。Y=255 僅檢查暫存器寫入,不代表驗證了正確的捲動畫面。證據來自 KUROSAKI 的 NROM 環境,未測試實機。

__nametable_rect_nt: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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 位元清為 0 並寫入 $2000,停用 PPU 的 NMI 產生功能。保留其他位元,不改變 CPU 的 IRQ 遮罩狀態,也不關閉繪製。

語法

c
void __nmi_disable(void);

回傳值

無(void)。

使用條件與限制

這些函式使用 PPUCTRL 的保存值,不讀取硬體目前值;直接寫入 $2000 不會更新保存值。VBlank 旗標已設定時,啟用 NMI 可能立即觸發它。停用 PPU 的產生機制無法取消 CPU 已鎖存的請求。

NESdev: NMI

NESdev:CPU 中斷

使用範例

c
__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。

請讓模擬器執行至少 360 幀,以完成等待 256 次的示範。即使 IRQ 被遮罩,NMI DELTA 3 WAITS 也應為 003;位元組循環後,NMI DELTA 256 WAITS 應為 000。NMI OFF DELTA 也應為 000。程式不連結 runtime.c,使用預設處理常式。另有 21 項狀態檢查,涵蓋狀態位元還原,以及停用 NMI 或使用空自訂處理常式時在限定執行時間內的等待行為。已驗證 KUROSAKI/NROM,尚未驗證實機。

__nmi_disable: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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 位元設為 1,再寫入 $2000,以啟用 PPU 產生的 NMI。保留保存值中的其他位元及 CPU 的 IRQ 遮罩狀態。此函式不會開啟背景或精靈繪製。

語法

c
void __nmi_enable(void);

回傳值

無(void)。

使用條件與限制

這些函式使用 PPUCTRL 的保存值,不讀取硬體目前值;直接寫入 $2000 不會更新保存值。VBlank 旗標已設定時,啟用 NMI 可能立即觸發它。停用 PPU 的產生機制無法取消 CPU 已鎖存的請求。

NESdev: NMI

NESdev:CPU 中斷

使用範例

c
__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。

請讓模擬器執行至少 360 幀,以完成等待 256 次的示範。即使 IRQ 被遮罩,NMI DELTA 3 WAITS 也應為 003;位元組循環後,NMI DELTA 256 WAITS 應為 000。NMI OFF DELTA 也應為 000。程式不連結 runtime.c,使用預設處理常式。另有 21 項狀態檢查,涵蓋狀態位元還原,以及停用 NMI 或使用空自訂處理常式時在限定執行時間內的等待行為。已驗證 KUROSAKI/NROM,尚未驗證實機。

__nmi_enable: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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,回傳值卻是一個位元組的計數值,而非布林就緒旗標。與保存的值比較,即可判斷預設 NMI 處理常式是否有新的進展。

語法

c
u8 __nmi_ready(void);

回傳值

0~255 的無號位元組,由預設 NMI 處理常式遞增並按模 256 循環。0 也是有效值。經過 256 次遞增後,目前值再次等於保存值;間隔很長的輪詢無法無限計數。

使用條件與限制

此計數器屬於編譯器的預設 NMI 處理常式。定義 __nes_nmi 會取代它。runtime.c 使用獨立的 nes_nmi_counter 與 nes_wait_nmi。空的自訂處理常式不會遞增編譯器計數器,因此 __nmi_wait 不會返回。請啟用 NMI,並在 NMI 處理常式之外呼叫等待函式。

NESdev: NMI

NESdev:CPU 中斷

使用範例

c
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。

請讓模擬器執行至少 360 幀,以完成等待 256 次的示範。即使 IRQ 被遮罩,NMI DELTA 3 WAITS 也應為 003;位元組循環後,NMI DELTA 256 WAITS 應為 000。NMI OFF DELTA 也應為 000。程式不連結 runtime.c,使用預設處理常式。另有 21 項狀態檢查,涵蓋狀態位元還原,以及停用 NMI 或使用空自訂處理常式時在限定執行時間內的等待行為。已驗證 KUROSAKI/NROM,尚未驗證實機。

__nmi_ready: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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、提交佇列命令或設定逾時。

語法

c
void __nmi_wait(void);

回傳值

無(void)。

使用條件與限制

此計數器屬於編譯器的預設 NMI 處理常式。定義 __nes_nmi 會取代它。runtime.c 使用獨立的 nes_nmi_counter 與 nes_wait_nmi。空的自訂處理常式不會遞增編譯器計數器,因此 __nmi_wait 不會返回。請啟用 NMI,並在 NMI 處理常式之外呼叫等待函式。

NESdev: NMI

NESdev:CPU 中斷

使用範例

c
__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。

請讓模擬器執行至少 360 幀,以完成等待 256 次的示範。即使 IRQ 被遮罩,NMI DELTA 3 WAITS 也應為 003;位元組循環後,NMI DELTA 256 WAITS 應為 000。NMI OFF DELTA 也應為 000。程式不連結 runtime.c,使用預設處理常式。另有 21 項狀態檢查,涵蓋狀態位元還原,以及停用 NMI 或使用空自訂處理常式時在限定執行時間內的等待行為。已驗證 KUROSAKI/NROM,尚未驗證實機。

__nmi_wait: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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座標保持不變。

語法

c
void __oam_clear(void);

回傳值

無(void)。

使用條件與限制

這些操作會修改CPU RAM中的0x0200–0x02FF,不會立即改變硬體OAM。請準備好完整頁面後再呼叫__oam_dma。它們不會維護精靈函式庫的配置旗標、座標陣列或使用數量。應避免中斷處理常式修改或傳送尚未準備完成的頁面。

使用範例

c
__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)顯示箭頭。程式使用__oam_dma直接傳送第02頁,其中包含各項精靈操作準備好的全部欄位。

__oam_clear: PAGE02
PAGE02

完整範例程式

建置命令

powershell
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.
csharp
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,從第0個項目開始寫入。CPU會在傳送期間暫停,完成後繼續執行。

語法

c
void __oam_dma(void);

回傳值

無(void)。

使用條件與限制

請在VBlank期間或關閉繪製時呼叫;兩個DMA內建函式都不會等待VBlank,並會在傳送前將OAMADDR設為0。硬體DMA通常使CPU暫停513或514週期,DMC活動可能延長時間。KUROSAKI將其近似為513週期,因此影像驗證無法證明實機的精確時序。編譯器依呼叫端名稱分類,所以即使明確等待VBlank,仍可能發出KQ2421。

使用範例

c
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)顯示箭頭。程式使用__oam_dma直接傳送第02頁,其中包含各項精靈操作準備好的全部欄位。

__oam_dma: PAGE02
PAGE02

完整範例程式

建置命令

powershell
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.
csharp
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,從第0個項目開始寫入。如此可直接使用另外準備的OAM頁面,無須先複製至第02頁。來源資料不會被修改。

語法

c
void __oam_dma_page(u8 page);

參數

page

來源CPU位址的高位元組:傳送0x0400–0x04FF時應傳入4,而不是0x0400。請提供完整的256位元組可讀頁面,並保持所需記憶體庫已映射。避免使用含I/O暫存器的頁面,因為讀取可能產生副作用。函式不會檢查來源位址。

回傳值

無(void)。

使用條件與限制

請在VBlank期間或關閉繪製時呼叫;兩個DMA內建函式都不會等待VBlank,並會在傳送前將OAMADDR設為0。硬體DMA通常使CPU暫停513或514週期,DMC活動可能延長時間。KUROSAKI將其近似為513週期,因此影像驗證無法證明實機的精確時序。編譯器依呼叫端名稱分類,所以即使明確等待VBlank,仍可能發出KQ2421。

使用範例

c
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。其他各行與預設頁面範例相同。驗證會將全部256個硬體OAM位元組與第04頁比較,並確認第02頁未被修改。

__oam_dma_page: PAGE04
PAGE04

完整範例程式

建置命令

powershell
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.
csharp
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。只篩選已讀取的數值,不存取控制器硬體。

語法

c
u8 __pad_buttons(u8 pad);

參數

pad

已讀取的 8 位元 NES 手把值,例如 __pad_read1_safe() 的結果。傳入數值本身,不是指標、連接埠編號或 input_* 的共用遮罩。

回傳值

依上述位元順序回傳 0~15 的遮罩。可能同時設定多個位元;應以 result & bit 檢查特定按鈕,而非判斷整個結果是否等於 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_* 遮罩混用。

使用範例

c
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。擷取到的數值會保留在畫面上。

__pad_buttons: NROM
NROM

完整範例程式

建置命令

powershell
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):上、下、左、右。位元位置保持不變,因此只有右鍵時回傳 128,而不是 8。不讀取控制器。

語法

c
u8 __pad_dirs(u8 pad);

參數

pad

已讀取的 8 位元 NES 手把值,例如 __pad_read1_safe() 的結果。傳入數值本身,不是指標、連接埠編號或 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_* 遮罩混用。

使用範例

c
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。擷取到的數值會保留在畫面上。

__pad_dirs: NROM
NROM

完整範例程式

建置命令

powershell
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 的八位元資料。回傳一次 NES 格式的按住狀態,不重試,也不計算按下或放開事件。

語法

c
u8 __pad_read1(void);

回傳值

NES 原始位元順序的 8 位元按住遮罩。每個設為 1 的位元代表一個按下按鈕,0 表示沒有按鈕。不是正規化的布林值。

使用條件與限制

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_* 的快取,也不產生按下、放開或重複事件。需要偵測變化時,請保存讀取結果,與自行保存的前次遮罩比較。

使用範例

c
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。擷取到的數值會保留在畫面上。

__pad_read1: NROM
NROM

完整範例程式

建置命令

powershell
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序列讀取八個位元。向$4016依序寫入1與0以鎖存控制器狀態,讀取位元1八次,並依序放入結果的位元0至7。這是對連接埠1的D1通道進行一次讀取;不會讀取一般控制器的D0通道,也不會為檢查一致性而重試。

語法

c
u8 __pad_read1_d1(void);

回傳值

NES 原始位元順序的 8 位元按住遮罩。每個設為 1 的位元代表一個按下按鈕,0 表示沒有按鈕。不是正規化的布林值。

使用條件與限制

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線上提供相容八位元序列手把訊號的周邊裝置。函式不會識別已連接的裝置,也沒有實作所有擴充裝置協定。每次呼叫都會重新鎖存兩個控制器的資料流,因此應完成一次完整讀取後再開始下一次。結果為新擷取的狀態,不會更新input_*或nes_pad1_*快取。

公開版KUROSAKI的控制器模型僅回傳D0與固定的位元6。因此即使按住一般控制器按鈕,該模擬器中的D1與麥克風位元也始終為0。所附執行結果驗證了編譯、一般按鈕輸入及此零訊號現象。周邊裝置的非零訊號、電氣時序及實機相容性仍未驗證;這裡的0既不能證明連接的裝置正常運作,也不能證明這些函式在實機上無法讀取它。

使用範例

c
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,因為對應訊號線未被模擬。擷取後顯示值保持不變。在具備適合的周邊裝置及其訊號線實作的環境中,擴充與麥克風數值取決於實際輸入;應依上述位元配置解讀,而不是期待這些零值。

公開版KUROSAKI的控制器模型僅回傳D0與固定的位元6。因此即使按住一般控制器按鈕,該模擬器中的D1與麥克風位元也始終為0。所附執行結果驗證了編譯、一般按鈕輸入及此零訊號現象。周邊裝置的非零訊號、電氣時序及實機相容性仍未驗證;這裡的0既不能證明連接的裝置正常運作,也不能證明這些函式在實機上無法讀取它。

__pad_read1_d1: NROM
NROM

完整範例程式

建置命令

powershell
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 格式遮罩。回傳的是按住狀態,不是新按下事件。

語法

c
u8 __pad_read1_safe(void);

回傳值

NES 原始位元順序的 8 位元按住遮罩。每個設為 1 的位元代表一個按下按鈕,0 表示沒有按鈕。不是正規化的布林值。

使用條件與限制

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 與周邊裝置組合。

使用範例

c
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。擷取到的數值會保留在畫面上。

__pad_read1_safe: NROM
NROM

完整範例程式

建置命令

powershell
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 的八位元資料。回傳一次 NES 格式的按住狀態,不重試,也不計算按下或放開事件。

語法

c
u8 __pad_read2(void);

回傳值

NES 原始位元順序的 8 位元按住遮罩。每個設為 1 的位元代表一個按下按鈕,0 表示沒有按鈕。不是正規化的布林值。

使用條件與限制

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_* 的快取,也不產生按下、放開或重複事件。需要偵測變化時,請保存讀取結果,與自行保存的前次遮罩比較。

使用範例

c
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。擷取到的數值會保留在畫面上。

__pad_read2: NROM
NROM

完整範例程式

建置命令

powershell
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序列讀取八個位元。向$4016依序寫入1與0以鎖存控制器狀態,讀取位元1八次,並依序放入結果的位元0至7。這是對連接埠2的D1通道進行一次讀取;不會讀取一般控制器的D0通道,也不會為檢查一致性而重試。

語法

c
u8 __pad_read2_d1(void);

回傳值

NES 原始位元順序的 8 位元按住遮罩。每個設為 1 的位元代表一個按下按鈕,0 表示沒有按鈕。不是正規化的布林值。

使用條件與限制

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線上提供相容八位元序列手把訊號的周邊裝置。函式不會識別已連接的裝置,也沒有實作所有擴充裝置協定。每次呼叫都會重新鎖存兩個控制器的資料流,因此應完成一次完整讀取後再開始下一次。結果為新擷取的狀態,不會更新input_*或nes_pad1_*快取。

公開版KUROSAKI的控制器模型僅回傳D0與固定的位元6。因此即使按住一般控制器按鈕,該模擬器中的D1與麥克風位元也始終為0。所附執行結果驗證了編譯、一般按鈕輸入及此零訊號現象。周邊裝置的非零訊號、電氣時序及實機相容性仍未驗證;這裡的0既不能證明連接的裝置正常運作,也不能證明這些函式在實機上無法讀取它。

使用範例

c
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,因為對應訊號線未被模擬。擷取後顯示值保持不變。在具備適合的周邊裝置及其訊號線實作的環境中,擴充與麥克風數值取決於實際輸入;應依上述位元配置解讀,而不是期待這些零值。

公開版KUROSAKI的控制器模型僅回傳D0與固定的位元6。因此即使按住一般控制器按鈕,該模擬器中的D1與麥克風位元也始終為0。所附執行結果驗證了編譯、一般按鈕輸入及此零訊號現象。周邊裝置的非零訊號、電氣時序及實機相容性仍未驗證;這裡的0既不能證明連接的裝置正常運作,也不能證明這些函式在實機上無法讀取它。

__pad_read2_d1: NROM
NROM

完整範例程式

建置命令

powershell
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 格式遮罩。回傳的是按住狀態,不是新按下事件。

語法

c
u8 __pad_read2_safe(void);

回傳值

NES 原始位元順序的 8 位元按住遮罩。每個設為 1 的位元代表一個按下按鈕,0 表示沒有按鈕。不是正規化的布林值。

使用條件與限制

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 與周邊裝置組合。

使用範例

c
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。擷取到的數值會保留在畫面上。

__pad_read2_safe: NROM
NROM

完整範例程式

建置命令

powershell
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,透過 PPUDATA 寫入來源的 16 位元組,供四組背景調色盤使用。這是立即傳輸,不改變名稱表屬性中的調色盤選擇,也不載入圖塊圖像。

語法

c
void __palette_bg_load(const u8* pal16);

參數

pal16

CPU 指標,指向 16 個可讀的 NES 調色盤代碼位元組。這些是調色盤代碼,不是 RGB 三元組。同步呼叫返回前應保持來源記憶體庫映射;不會切換記憶體庫或檢查長度。請使用 PPU 增量 1,以寫入預期的 16 個調色盤位址。

回傳值

無(void)。

使用條件與限制

PPU 位址 0x3F10/14/18/1C 與 0x3F00/04/08/0C 共用儲存位置。因此載入精靈調色盤也會寫入這些背景項目。編譯器原樣傳送位元組,不驗證色彩選擇、不合併調色盤,也不保留共用項目。

直接存取 PPU 時,必須關閉繪製,或確保整個操作位於安全的 VBlank 時間內。這些函式不等待、不排入佇列、不阻擋 NMI,也不還原捲動。呼叫期間應防止中斷改變共用鎖存器或位址。PPUCTRL 第 2 位元決定每個資料位元組使目的位址增加 1 還是 32。鏡像和位址繞回由 PPU 處理;CHR-ROM 不可寫入。存取結束後、開始繪製前,請設定所需的捲動狀態。

NESdev:PPU 暫存器

NESdev:PPU 捲動

使用範例

c
__palette_bg_load(scene_palette); // Four background palettes, 16 bytes total.

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式用於學習暫存器設定、圖塊與屬性直接寫入、捲動,以及背景和精靈調色盤的分別載入。(0,16) 處藍色條帶為 256×16 像素:首行重複實心 8×8 圖塊、左半 4×8 直條和八行三角形,第二行只有左半直條。(16,48) 處是綠色實心 64×32 矩形,(144,48) 處的紅色 64×32 區域由橫八、縱四個三角形圖塊組成。

y=96 處,x=16、24、32 的藍色標記分別為正方形、左半直條和三角形;x=80 為紅色左半直條,x=144 為綠色三角形。這些是背景圖塊。使用獨立調色盤的精靈位於 y=144:x=16 是紅色 8×8 正方形,x=80 是藍色左半 4×8 直條,x=144 是綠色八行三角形。最終捲動為 (0,0)。

影像檢查 16,384 個像素的預期形狀和色彩,包括精靈色彩。另有 31 個狀態測試,涵蓋暫存器替換、狀態與鎖存器副作用、捲動儲存值、0/255 位元組傳輸、增量 1/32、地圖邊緣、矩形零尺寸、表選擇、完整屬性位元組及兩種調色盤目標。Y=255 僅檢查暫存器寫入,不代表驗證了正確的捲動畫面。證據來自 KUROSAKI 的 NROM 環境,未測試實機。

__palette_bg_load: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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,透過 PPUDATA 寫入來源的 16 位元組,供四組精靈調色盤使用。精靈屬性位元決定顯示哪組調色盤。此呼叫不修改 OAM,也不傳輸精靈圖塊圖像。

語法

c
void __palette_sp_load(const u8* pal16);

參數

pal16

CPU 指標,指向 16 個可讀的 NES 調色盤代碼位元組。這些是調色盤代碼,不是 RGB 三元組。同步呼叫返回前應保持來源記憶體庫映射;不會切換記憶體庫或檢查長度。請使用 PPU 增量 1,以寫入預期的 16 個調色盤位址。

回傳值

無(void)。

使用條件與限制

PPU 位址 0x3F10/14/18/1C 與 0x3F00/04/08/0C 共用儲存位置。因此載入精靈調色盤也會寫入這些背景項目。編譯器原樣傳送位元組,不驗證色彩選擇、不合併調色盤,也不保留共用項目。

直接存取 PPU 時,必須關閉繪製,或確保整個操作位於安全的 VBlank 時間內。這些函式不等待、不排入佇列、不阻擋 NMI,也不還原捲動。呼叫期間應防止中斷改變共用鎖存器或位址。PPUCTRL 第 2 位元決定每個資料位元組使目的位址增加 1 還是 32。鏡像和位址繞回由 PPU 處理;CHR-ROM 不可寫入。存取結束後、開始繪製前,請設定所需的捲動狀態。

NESdev:PPU 暫存器

NESdev:PPU 捲動

使用範例

c
__palette_sp_load(scene_palette); // Four sprite palettes, 16 bytes total.

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式用於學習暫存器設定、圖塊與屬性直接寫入、捲動,以及背景和精靈調色盤的分別載入。(0,16) 處藍色條帶為 256×16 像素:首行重複實心 8×8 圖塊、左半 4×8 直條和八行三角形,第二行只有左半直條。(16,48) 處是綠色實心 64×32 矩形,(144,48) 處的紅色 64×32 區域由橫八、縱四個三角形圖塊組成。

y=96 處,x=16、24、32 的藍色標記分別為正方形、左半直條和三角形;x=80 為紅色左半直條,x=144 為綠色三角形。這些是背景圖塊。使用獨立調色盤的精靈位於 y=144:x=16 是紅色 8×8 正方形,x=80 是藍色左半 4×8 直條,x=144 是綠色八行三角形。最終捲動為 (0,0)。

影像檢查 16,384 個像素的預期形狀和色彩,包括精靈色彩。另有 31 個狀態測試,涵蓋暫存器替換、狀態與鎖存器副作用、捲動儲存值、0/255 位元組傳輸、增量 1/32、地圖邊緣、矩形零尺寸、表選擇、完整屬性位元組及兩種調色盤目標。Y=255 僅檢查暫存器寫入,不代表驗證了正確的捲動畫面。證據來自 KUROSAKI 的 NROM 環境,未測試實機。

__palette_sp_load: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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 處理。

語法

c
void __ppu_addr(u16 ppu_addr);

參數

ppu_addr

PPU位元組位址(0x0000–0x3FFF),不是CPU指標。目的區域必須可寫;改寫圖樣需要CHR RAM,不能寫入CHR ROM。PPU對映與鏡像規則照常適用。連續複製和填滿應將PPUCTRL位址增量設為1。在安全的PPU寫入時段執行,然後恢復所需的捲動狀態;執行器會寫入PPUADDR和PPUDATA,不會恢復捲動設定。

回傳值

無(void)。

使用條件與限制

直接存取 PPU 時,必須關閉繪製,或確保整個操作位於安全的 VBlank 時間內。這些函式不等待、不排入佇列、不阻擋 NMI,也不還原捲動。呼叫期間應防止中斷改變共用鎖存器或位址。PPUCTRL 第 2 位元決定每個資料位元組使目的位址增加 1 還是 32。鏡像和位址繞回由 PPU 處理;CHR-ROM 不可寫入。存取結束後、開始繪製前,請設定所需的捲動狀態。

NESdev:PPU 暫存器

NESdev:PPU 捲動

使用範例

c
address_port=0x21;
__ppu_addr(0x2192); // Reset latch and set the cell at tile (18,12).

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式用於學習暫存器設定、圖塊與屬性直接寫入、捲動,以及背景和精靈調色盤的分別載入。(0,16) 處藍色條帶為 256×16 像素:首行重複實心 8×8 圖塊、左半 4×8 直條和八行三角形,第二行只有左半直條。(16,48) 處是綠色實心 64×32 矩形,(144,48) 處的紅色 64×32 區域由橫八、縱四個三角形圖塊組成。

y=96 處,x=16、24、32 的藍色標記分別為正方形、左半直條和三角形;x=80 為紅色左半直條,x=144 為綠色三角形。這些是背景圖塊。使用獨立調色盤的精靈位於 y=144:x=16 是紅色 8×8 正方形,x=80 是藍色左半 4×8 直條,x=144 是綠色八行三角形。最終捲動為 (0,0)。

影像檢查 16,384 個像素的預期形狀和色彩,包括精靈色彩。另有 31 個狀態測試,涵蓋暫存器替換、狀態與鎖存器副作用、捲動儲存值、0/255 位元組傳輸、增量 1/32、地圖邊緣、矩形零尺寸、表選擇、完整屬性位元組及兩種調色盤目標。Y=255 僅檢查暫存器寫入,不代表驗證了正確的捲動畫面。證據來自 KUROSAKI 的 NROM 環境,未測試實機。

__ppu_addr: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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 啟用、圖樣表選擇與位址遞增設定。

語法

c
u8 __ppu_ctrl_get(void);

回傳值

以 u8 傳回函式庫或內建設定函式所維護的最後控制值。不讀取 PPUSTATUS。

使用條件與限制

PPUCTRL 與 PPUMASK 只能寫入。此讀取函式不會讀取硬體。直接寫入 $2000/$2001 或使用自訂組合語言,會繞過軟體副本。請一致使用對應的 __ppu_*_set 操作。讀取副本不會改變鎖存器或中斷狀態。

使用範例

c
__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 的八行三角形。FAILED CHECKS 為 0。獨立狀態測試還確認兩個控制暫存器、32 位元組調色盤傳輸,以及名稱表全部 1,024 位元組的清空。直接呼叫四個宣告時,預期結果是連結失敗。

__ppu_ctrl_get: 其他用法
其他用法

完整範例程式

建置命令

powershell
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

csharp
case "__ppu_ctrl_get":
                    if (!Need(0)) return true;
                    EmitAsm("LDA", Mem(_runtimePpuCtrlShadowAddress)); return true;
__ppu_ctrl_set

用指定位元組替換 PPUCTRL 及其軟體儲存值,控制 NMI 啟用、PPU 位址增量、圖樣表選擇、精靈尺寸和基準名稱表。不會修改 PPUMASK,也不會保留未指定的位元。

語法

c
void __ppu_ctrl_set(u8 value);

參數

value

無號位元組 0..255,依原值寫入。這是完整的暫存器或資料位元組,不是布林值或位元編號。

回傳值

無(void)。

NESdev:PPU 暫存器

NESdev:PPU 捲動

使用範例

c
__ppu_ctrl_set(0x80); // NMI enabled, increment 1, nametable/pattern tables 0.

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式用於學習暫存器設定、圖塊與屬性直接寫入、捲動,以及背景和精靈調色盤的分別載入。(0,16) 處藍色條帶為 256×16 像素:首行重複實心 8×8 圖塊、左半 4×8 直條和八行三角形,第二行只有左半直條。(16,48) 處是綠色實心 64×32 矩形,(144,48) 處的紅色 64×32 區域由橫八、縱四個三角形圖塊組成。

y=96 處,x=16、24、32 的藍色標記分別為正方形、左半直條和三角形;x=80 為紅色左半直條,x=144 為綠色三角形。這些是背景圖塊。使用獨立調色盤的精靈位於 y=144:x=16 是紅色 8×8 正方形,x=80 是藍色左半 4×8 直條,x=144 是綠色八行三角形。最終捲動為 (0,0)。

影像檢查 16,384 個像素的預期形狀和色彩,包括精靈色彩。另有 31 個狀態測試,涵蓋暫存器替換、狀態與鎖存器副作用、捲動儲存值、0/255 位元組傳輸、增量 1/32、地圖邊緣、矩形零尺寸、表選擇、完整屬性位元組及兩種調色盤目標。Y=255 僅檢查暫存器寫入,不代表驗證了正確的捲動畫面。證據來自 KUROSAKI 的 NROM 環境,未測試實機。

__ppu_ctrl_set: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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

透過 PPUDATA 向目前 PPU 位址寫入一次 value,之後位址依 PPUCTRL 設定增加 1 或 32。此呼叫不設定位址、不重設鎖存器,也不將傳輸排入佇列。

語法

c
void __ppu_data(u8 value);

參數

value

無號位元組 0..255,依原值寫入。這是完整的暫存器或資料位元組,不是布林值或位元編號。

回傳值

無(void)。

使用條件與限制

直接存取 PPU 時,必須關閉繪製,或確保整個操作位於安全的 VBlank 時間內。這些函式不等待、不排入佇列、不阻擋 NMI,也不還原捲動。呼叫期間應防止中斷改變共用鎖存器或位址。PPUCTRL 第 2 位元決定每個資料位元組使目的位址增加 1 還是 32。鏡像和位址繞回由 PPU 處理;CHR-ROM 不可寫入。存取結束後、開始繪製前,請設定所需的捲動狀態。

NESdev:PPU 暫存器

NESdev:PPU 捲動

使用範例

c
__ppu_data(3); // Green triangle at (144,96); destination advances by one.

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式用於學習暫存器設定、圖塊與屬性直接寫入、捲動,以及背景和精靈調色盤的分別載入。(0,16) 處藍色條帶為 256×16 像素:首行重複實心 8×8 圖塊、左半 4×8 直條和八行三角形,第二行只有左半直條。(16,48) 處是綠色實心 64×32 矩形,(144,48) 處的紅色 64×32 區域由橫八、縱四個三角形圖塊組成。

y=96 處,x=16、24、32 的藍色標記分別為正方形、左半直條和三角形;x=80 為紅色左半直條,x=144 為綠色三角形。這些是背景圖塊。使用獨立調色盤的精靈位於 y=144:x=16 是紅色 8×8 正方形,x=80 是藍色左半 4×8 直條,x=144 是綠色八行三角形。最終捲動為 (0,0)。

影像檢查 16,384 個像素的預期形狀和色彩,包括精靈色彩。另有 31 個狀態測試,涵蓋暫存器替換、狀態與鎖存器副作用、捲動儲存值、0/255 位元組傳輸、增量 1/32、地圖邊緣、矩形零尺寸、表選擇、完整屬性位元組及兩種調色盤目標。Y=255 僅檢查暫存器寫入,不代表驗證了正確的捲動畫面。證據來自 KUROSAKI 的 NROM 環境,未測試實機。

__ppu_data: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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 的軟體副本,可用來保留繪製、左邊緣顯示、灰階與色彩強調設定。

語法

c
u8 __ppu_mask_get(void);

回傳值

以 u8 傳回函式庫或內建設定函式所維護的最後控制值。不讀取 PPUSTATUS。

使用條件與限制

PPUCTRL 與 PPUMASK 只能寫入。此讀取函式不會讀取硬體。直接寫入 $2000/$2001 或使用自訂組合語言,會繞過軟體副本。請一致使用對應的 __ppu_*_set 操作。讀取副本不會改變鎖存器或中斷狀態。

使用範例

c
__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 的八行三角形。FAILED CHECKS 為 0。獨立狀態測試還確認兩個控制暫存器、32 位元組調色盤傳輸,以及名稱表全部 1,024 位元組的清空。直接呼叫四個宣告時,預期結果是連結失敗。

__ppu_mask_get: 其他用法
其他用法

完整範例程式

建置命令

powershell
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

csharp
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。

語法

c
void __ppu_mask_set(u8 value);

參數

value

無號位元組 0..255,依原值寫入。這是完整的暫存器或資料位元組,不是布林值或位元編號。

回傳值

無(void)。

NESdev:PPU 暫存器

NESdev:PPU 捲動

使用範例

c
__ppu_mask_set(0x0A); // Background enabled, including its leftmost eight pixels.

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式用於學習暫存器設定、圖塊與屬性直接寫入、捲動,以及背景和精靈調色盤的分別載入。(0,16) 處藍色條帶為 256×16 像素:首行重複實心 8×8 圖塊、左半 4×8 直條和八行三角形,第二行只有左半直條。(16,48) 處是綠色實心 64×32 矩形,(144,48) 處的紅色 64×32 區域由橫八、縱四個三角形圖塊組成。

y=96 處,x=16、24、32 的藍色標記分別為正方形、左半直條和三角形;x=80 為紅色左半直條,x=144 為綠色三角形。這些是背景圖塊。使用獨立調色盤的精靈位於 y=144:x=16 是紅色 8×8 正方形,x=80 是藍色左半 4×8 直條,x=144 是綠色八行三角形。最終捲動為 (0,0)。

影像檢查 16,384 個像素的預期形狀和色彩,包括精靈色彩。另有 31 個狀態測試,涵蓋暫存器替換、狀態與鎖存器副作用、捲動儲存值、0/255 位元組傳輸、增量 1/32、地圖邊緣、矩形零尺寸、表選擇、完整屬性位元組及兩種調色盤目標。Y=255 僅檢查暫存器寫入,不代表驗證了正確的捲動畫面。證據來自 KUROSAKI 的 NROM 環境,未測試實機。

__ppu_mask_set: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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 及其軟體儲存值寫入零,停止背景和精靈繪製。包括灰階和色彩強調在內的所有遮罩位元都會清零。PPUCTRL 與 NMI 啟用狀態不變;準備共用傳輸狀態時,請視需要另行控制 NMI。

語法

c
void __ppu_off(void);

回傳值

無(void)。

NESdev:PPU 暫存器

NESdev:PPU 捲動

使用範例

c
__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 直條和八行三角形,第二行只有左半直條。(16,48) 處是綠色實心 64×32 矩形,(144,48) 處的紅色 64×32 區域由橫八、縱四個三角形圖塊組成。

y=96 處,x=16、24、32 的藍色標記分別為正方形、左半直條和三角形;x=80 為紅色左半直條,x=144 為綠色三角形。這些是背景圖塊。使用獨立調色盤的精靈位於 y=144:x=16 是紅色 8×8 正方形,x=80 是藍色左半 4×8 直條,x=144 是綠色八行三角形。最終捲動為 (0,0)。

影像檢查 16,384 個像素的預期形狀和色彩,包括精靈色彩。另有 31 個狀態測試,涵蓋暫存器替換、狀態與鎖存器副作用、捲動儲存值、0/255 位元組傳輸、增量 1/32、地圖邊緣、矩形零尺寸、表選擇、完整屬性位元組及兩種調色盤目標。Y=255 僅檢查暫存器寫入,不代表驗證了正確的捲動畫面。證據來自 KUROSAKI 的 NROM 環境,未測試實機。

__ppu_off: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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,啟用背景與精靈繪製,包括最左側八個像素。灰階和色彩強調設定也會被覆寫;不修改 PPUCTRL,也不啟用 NMI。

語法

c
void __ppu_on(void);

回傳值

無(void)。

NESdev:PPU 暫存器

NESdev:PPU 捲動

使用範例

c
__ppu_on(); // Replace mask with 0x1E: enable sprites and background at both edges.

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式用於學習暫存器設定、圖塊與屬性直接寫入、捲動,以及背景和精靈調色盤的分別載入。(0,16) 處藍色條帶為 256×16 像素:首行重複實心 8×8 圖塊、左半 4×8 直條和八行三角形,第二行只有左半直條。(16,48) 處是綠色實心 64×32 矩形,(144,48) 處的紅色 64×32 區域由橫八、縱四個三角形圖塊組成。

y=96 處,x=16、24、32 的藍色標記分別為正方形、左半直條和三角形;x=80 為紅色左半直條,x=144 為綠色三角形。這些是背景圖塊。使用獨立調色盤的精靈位於 y=144:x=16 是紅色 8×8 正方形,x=80 是藍色左半 4×8 直條,x=144 是綠色八行三角形。最終捲動為 (0,0)。

影像檢查 16,384 個像素的預期形狀和色彩,包括精靈色彩。另有 31 個狀態測試,涵蓋暫存器替換、狀態與鎖存器副作用、捲動儲存值、0/255 位元組傳輸、增量 1/32、地圖邊緣、矩形零尺寸、表選擇、完整屬性位元組及兩種調色盤目標。Y=255 僅檢查暫存器寫入,不代表驗證了正確的捲動畫面。證據來自 KUROSAKI 的 NROM 環境,未測試實機。

__ppu_on: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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 完成檢查。

語法

c
u8 __ppu_read_status(void);

回傳值

原始狀態位元組:第 7 位元為 VBlank,第 6 位元為精靈零命中,第 5 位元為精靈溢位。應使用遮罩測試所需旗標,不要將整個位元組當作布林值或影格編號。低位元不是可跨環境使用的應用狀態值。

使用條件與限制

讀取 PPUSTATUS 可能影響 NMI 時序,以及後續對 PPUSCROLL 和 PPUADDR 的寫入。不要把它插入其他常式尚未寫完的暫存器位元組對之間。這些呼叫不會等待 VBlank,也不會完成待處理傳輸佇列。

NESdev:PPU 暫存器

NESdev:PPU 捲動

使用範例

c
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 直條和八行三角形,第二行只有左半直條。(16,48) 處是綠色實心 64×32 矩形,(144,48) 處的紅色 64×32 區域由橫八、縱四個三角形圖塊組成。

y=96 處,x=16、24、32 的藍色標記分別為正方形、左半直條和三角形;x=80 為紅色左半直條,x=144 為綠色三角形。這些是背景圖塊。使用獨立調色盤的精靈位於 y=144:x=16 是紅色 8×8 正方形,x=80 是藍色左半 4×8 直條,x=144 是綠色八行三角形。最終捲動為 (0,0)。

影像檢查 16,384 個像素的預期形狀和色彩,包括精靈色彩。另有 31 個狀態測試,涵蓋暫存器替換、狀態與鎖存器副作用、捲動儲存值、0/255 位元組傳輸、增量 1/32、地圖邊緣、矩形零尺寸、表選擇、完整屬性位元組及兩種調色盤目標。Y=255 僅檢查暫存器寫入,不代表驗證了正確的捲動畫面。證據來自 KUROSAKI 的 NROM 環境,未測試實機。

__ppu_read_status: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
case "__ppu_read_status":
                    if (!Need(0)) return true;
                    EmitAsm("LDA", Mem(0x2002)); return true;
__prg_bank_set

執行與 __bankswitch 相同的 mapper 對應 PRG 區塊切換。編號檢查規則相同,不會自動還原先前的映射。

語法

c
void __prg_bank_set(u8 bank);

參數

bank

無號 8 位元 ROM 區塊編號。呼叫時應與函式配置一致,讀取時用來選擇來源映射。請使用目標卡匣支援的範圍。

回傳值

無(void)。

使用條件與限制

持續生效的區塊切換不能使目前程式碼或返回位址失去映射。請如範例所示,將呼叫位置、使用的 bank_switch 本體及後續程式碼放在共用或固定區塊 0。

函式庫記錄初值為 0,不會查詢卡匣啟動時的映射。請用 bank_switch 初始化。直接呼叫切換內建函式或寫入 mapper 不會更新這份獨立記錄,因此 bank_get_current 無法驗證實際硬體映射。

FC 切換編號是編譯器的邏輯編號。0 表示共用 ROM,不是切換目標;輔助程式將 0 視為區塊 1。SUROM512 僅允許 1–30:直接傳給切換內建函式的越界常數會導致建置錯誤;無效執行時值會退回 1 並設定 mapper 故障狀態。共用 ROM 應直接讀取,不要要求切換到區塊 0。

使用範例

c
__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。

完整程式示範如何以區塊編號與近位址確定資料、如何跨區塊呼叫回呼,以及如何區分函式庫記錄與實際映射。所有數值皆為十六進位。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 各執行一次回呼。

__prg_bank_set: MMC3
MMC3

完整範例程式

建置命令

powershell
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

csharp
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 計算回饋的移位暫存器,因此固定種子會產生可重現的位元組序列。

語法

c
u8 __rng8(void);

回傳值

共用產生器推進一次後的 u8 結果(0–255)。這描述值的型別與範圍,不代表每個位元組等機率。

使用條件與限制

FC 步驟將 16 位元狀態右移一位。若原本最低位元為 1,便將移位後狀態與 0xB400 做 XOR。回傳位元組是 low_byte XOR high_byte,不是原始低位元組。

兩個狀態位元組皆零時,下一次取值會在移位前替換為 0xA55A。因此 __rng_seed(0) 之後呼叫 __rng8() 會傳回 255。設定種子的呼叫儲存零;替換發生在取值呼叫。

這些呼叫共用持續保留的狀態。重現需要相同平台、種子與呼叫順序,包括其他輔助函式發出的呼叫。請在同一執行環境中設定種子與取值;這些檢查未證明從中斷處理常式並行使用的安全性。

GB 與 FC 使用不同演算法,不保證相同種子產生相同序列。它們是確定性的遊戲用產生器,不是密碼學亂數來源;測試檢查實作行為,不檢查統計品質或週期。

使用範例

c
__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 影格後檢查。未測試實機與統計品質。

__rng8: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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 產生器的兩個狀態位元組。重用種子可讓測試或重播從偽亂數序列的相同位置開始;此呼叫不推進產生器。

語法

c
void __rng_seed(u16 seed);

參數

seed

無號 16 位元種子,範圍 0–65535。兩個位元組照原值儲存。請在要重現的遊戲取值序列開始前選好並記錄種子。

回傳值

無(void)。

使用條件與限制

兩個狀態位元組皆零時,下一次取值會在移位前替換為 0xA55A。因此 __rng_seed(0) 之後呼叫 __rng8() 會傳回 255。設定種子的呼叫儲存零;替換發生在取值呼叫。

這些呼叫共用持續保留的狀態。重現需要相同平台、種子與呼叫順序,包括其他輔助函式發出的呼叫。請在同一執行環境中設定種子與取值;這些檢查未證明從中斷處理常式並行使用的安全性。

GB 與 FC 使用不同演算法,不保證相同種子產生相同序列。它們是確定性的遊戲用產生器,不是密碼學亂數來源;測試檢查實作行為,不檢查統計品質或週期。

使用範例

c
__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 起始狀態與零種子規則。種子 0x1234 的 FIRST 與 RESEEDED 皆顯示 00019。以零為種子後,ZERO SEED 顯示 00255。

完整 NROM 程式在黑底上顯示藍色十進位結果。FAILED 必須為 00000。先在繪製停用時上傳文字,再啟用結果畫面。

驗證將每個案例的 32 個輸出與後續 8 次位元組取值,和獨立主機模型比對:GB 93 例、FC 18 例,涵蓋常數/執行期種子及 GB 範圍、機率、權重邊界。教學畫面在模擬器 240 影格後檢查。未測試實機與統計品質。

__rng_seed: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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 計數器變化。返回後仍維持所選遮罩。

語法

c
void __rob_flash(u8 bright);

參數

bright

零選擇關閉,任何非零位元組(含 255)選擇開啟。這不是亮度等級。

回傳值

無回傳值。完成指定等待後返回呼叫端。

使用條件與限制

請引入 intrinsics.h。預設 NMI 處理常式必須持續執行並遞增計數器。若停用 NMI,或自訂處理常式未維護該計數器,等待將無法結束。沒有逾時機制。

開啟階段將 PPUMASK 與軟體副本設為 0x1E,關閉階段將兩者歸零。不還原先前遮罩。這會一起改變背景、精靈及左邊緣繪製,也取代灰階與色彩強調設定。

僅啟用繪製不會自動形成白畫面。請提供明亮圖塊與調色盤項目,並為關閉階段準備暗色底色。範例使用全白圖塊與黑色底色,在操作期間閃爍整個畫面。

等待次數計算 NMI 計數器的變化。進入函式至第一次 NMI 的間隔可能短於一個影格。請從主程式呼叫,不要從中斷呼叫。其他常式若改變繪製遮罩,可能干擾圖樣。

這些是低階顯示遮罩與等待操作,不產生機器人命令前導碼、裝置專用命令序列或接收確認。驗證涵蓋 KUROSAKI 的 PPUMASK 寫入、NMI 次數與像素;未測試實體機器人的接收。

使用範例

c
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 是四格白色後接兩格黑色;0 是兩格白色後接四格黑色。FINAL MASK 0 與 TIMING CHECKS OK 表示完成。

__rob_flash: NTSC — 執行後顯示 FLASH WAITS 1、PULSE 3 ON / 2 OFF = 5、A5 BYTE WAITS 48。圖解表示 0xA5:上列為 1,0,1,0,下列為 0,1,0,1。每格是一次等待;白色表示啟用繪製,黑色表示停用。1 是四格白色後接兩格黑色;0 是兩格白色後接四格黑色。FINAL MASK 0 與 TIMING CHECKS OK 表示完成。
NTSC — 執行後顯示 FLASH WAITS 1、PULSE 3 ON / 2 OFF = 5、A5 BYTE WAITS 48。圖解表示 0xA5:上列為 1,0,1,0,下列為 0,1,0,1。每格是一次等待;白色表示啟用繪製,黑色表示停用。1 是四格白色後接兩格黑色;0 是兩格白色後接四格黑色。FINAL MASK 0 與 TIMING CHECKS OK 表示完成。

完整範例程式

建置命令

powershell
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

csharp
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

先執行指定的開啟等待,再執行關閉等待,形成一個脈衝。

語法

c
void __rob_pulse(u8 on_frames, u8 off_frames);

參數

on_frames

開啟階段的等待次數,範圍 0..255。零會略過開啟階段。

off_frames

關閉階段的等待次數,範圍 0..255。零會略過關閉階段。總等待次數可達 510。

回傳值

無回傳值。完成指定等待後返回呼叫端。

使用條件與限制

兩個次數皆零時,遮罩與時序都不改變。只要有關閉等待,結束遮罩即為零;只有開啟階段的脈衝則留下 0x1E。

請引入 intrinsics.h。預設 NMI 處理常式必須持續執行並遞增計數器。若停用 NMI,或自訂處理常式未維護該計數器,等待將無法結束。沒有逾時機制。

開啟階段將 PPUMASK 與軟體副本設為 0x1E,關閉階段將兩者歸零。不還原先前遮罩。這會一起改變背景、精靈及左邊緣繪製,也取代灰階與色彩強調設定。

僅啟用繪製不會自動形成白畫面。請提供明亮圖塊與調色盤項目,並為關閉階段準備暗色底色。範例使用全白圖塊與黑色底色,在操作期間閃爍整個畫面。

等待次數計算 NMI 計數器的變化。進入函式至第一次 NMI 的間隔可能短於一個影格。請從主程式呼叫,不要從中斷呼叫。其他常式若改變繪製遮罩,可能干擾圖樣。

這些是低階顯示遮罩與等待操作,不產生機器人命令前導碼、裝置專用命令序列或接收確認。驗證涵蓋 KUROSAKI 的 PPUMASK 寫入、NMI 次數與像素;未測試實體機器人的接收。

使用範例

c
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 是四格白色後接兩格黑色;0 是兩格白色後接四格黑色。FINAL MASK 0 與 TIMING CHECKS OK 表示完成。

__rob_pulse: NTSC — 執行後顯示 FLASH WAITS 1、PULSE 3 ON / 2 OFF = 5、A5 BYTE WAITS 48。圖解表示 0xA5:上列為 1,0,1,0,下列為 0,1,0,1。每格是一次等待;白色表示啟用繪製,黑色表示停用。1 是四格白色後接兩格黑色;0 是兩格白色後接四格黑色。FINAL MASK 0 與 TIMING CHECKS OK 表示完成。
NTSC — 執行後顯示 FLASH WAITS 1、PULSE 3 ON / 2 OFF = 5、A5 BYTE WAITS 48。圖解表示 0xA5:上列為 1,0,1,0,下列為 0,1,0,1。每格是一次等待;白色表示啟用繪製,黑色表示停用。1 是四格白色後接兩格黑色;0 是兩格白色後接四格黑色。FINAL MASK 0 與 TIMING CHECKS OK 表示完成。

完整範例程式

建置命令

powershell
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

csharp
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

將一個位元組從最高位元開始編成八個閃爍圖樣。1 使用四次開啟等待、兩次關閉等待;0 使用兩次開啟、四次關閉。總計 48 次 NMI 等待。

語法

c
void __rob_send_byte(u8 pattern);

參數

pattern

8 位元圖樣,範圍 0..255。0xA5 的順序為 1,0,1,0,0,1,0,1。這不是通道 ID 或機器人動作編號。

回傳值

無回傳值。完成指定等待後返回呼叫端。

使用條件與限制

最後一個位元也以關閉等待結束,使 PPUMASK 與副本保持零。恰好傳送八位元,不加入起始或結束標記。

請引入 intrinsics.h。預設 NMI 處理常式必須持續執行並遞增計數器。若停用 NMI,或自訂處理常式未維護該計數器,等待將無法結束。沒有逾時機制。

開啟階段將 PPUMASK 與軟體副本設為 0x1E,關閉階段將兩者歸零。不還原先前遮罩。這會一起改變背景、精靈及左邊緣繪製,也取代灰階與色彩強調設定。

僅啟用繪製不會自動形成白畫面。請提供明亮圖塊與調色盤項目,並為關閉階段準備暗色底色。範例使用全白圖塊與黑色底色,在操作期間閃爍整個畫面。

等待次數計算 NMI 計數器的變化。進入函式至第一次 NMI 的間隔可能短於一個影格。請從主程式呼叫,不要從中斷呼叫。其他常式若改變繪製遮罩,可能干擾圖樣。

這些是低階顯示遮罩與等待操作,不產生機器人命令前導碼、裝置專用命令序列或接收確認。驗證涵蓋 KUROSAKI 的 PPUMASK 寫入、NMI 次數與像素;未測試實體機器人的接收。

使用範例

c
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 是四格白色後接兩格黑色;0 是兩格白色後接四格黑色。FINAL MASK 0 與 TIMING CHECKS OK 表示完成。

__rob_send_byte: NTSC — 執行後顯示 FLASH WAITS 1、PULSE 3 ON / 2 OFF = 5、A5 BYTE WAITS 48。圖解表示 0xA5:上列為 1,0,1,0,下列為 0,1,0,1。每格是一次等待;白色表示啟用繪製,黑色表示停用。1 是四格白色後接兩格黑色;0 是兩格白色後接四格黑色。FINAL MASK 0 與 TIMING CHECKS OK 表示完成。
NTSC — 執行後顯示 FLASH WAITS 1、PULSE 3 ON / 2 OFF = 5、A5 BYTE WAITS 48。圖解表示 0xA5:上列為 1,0,1,0,下列為 0,1,0,1。每格是一次等待;白色表示啟用繪製,黑色表示停用。1 是四格白色後接兩格黑色;0 是兩格白色後接四格黑色。FINAL MASK 0 與 TIMING CHECKS OK 表示完成。

完整範例程式

建置命令

powershell
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

csharp
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 旗標,但不會將捲動座標寫為零,也不改變儲存的 X/Y 值。

語法

c
void __scroll_latch_reset(void);

回傳值

無(void)。

使用條件與限制

讀取 PPUSTATUS 可能影響 NMI 時序,以及後續對 PPUSCROLL 和 PPUADDR 的寫入。不要把它插入其他常式尚未寫完的暫存器位元組對之間。這些呼叫不會等待 VBlank,也不會完成待處理傳輸佇列。

NESdev:PPU 暫存器

NESdev:PPU 捲動

使用範例

c
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 直條和八行三角形,第二行只有左半直條。(16,48) 處是綠色實心 64×32 矩形,(144,48) 處的紅色 64×32 區域由橫八、縱四個三角形圖塊組成。

y=96 處,x=16、24、32 的藍色標記分別為正方形、左半直條和三角形;x=80 為紅色左半直條,x=144 為綠色三角形。這些是背景圖塊。使用獨立調色盤的精靈位於 y=144:x=16 是紅色 8×8 正方形,x=80 是藍色左半 4×8 直條,x=144 是綠色八行三角形。最終捲動為 (0,0)。

影像檢查 16,384 個像素的預期形狀和色彩,包括精靈色彩。另有 31 個狀態測試,涵蓋暫存器替換、狀態與鎖存器副作用、捲動儲存值、0/255 位元組傳輸、增量 1/32、地圖邊緣、矩形零尺寸、表選擇、完整屬性位元組及兩種調色盤目標。Y=255 僅檢查暫存器寫入,不代表驗證了正確的捲動畫面。證據來自 KUROSAKI 的 NROM 環境,未測試實機。

__scroll_latch_reset: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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 重設共用鎖存器,再依 X、Y 順序寫入 PPUSCROLL。它直接更新捲動,不會排程攝影機移動、等待 NMI 或選擇基準名稱表。

語法

c
void __scroll_set(u8 x, u8 y);

參數

x / y

偏移是無號位元組的像素值。X 使用 0..255,Y 通常使用 0..239。Y 的 240..255 也會原樣寫入,但會觸發 PPU 特殊行為,並非普通可見地圖捲動。基準名稱表位元由 PPUCTRL 提供。直接存取 VRAM 後、開始繪製前,應還原所需的捲動;函式不會代為安排時序。

回傳值

無(void)。

使用條件與限制

讀取 PPUSTATUS 可能影響 NMI 時序,以及後續對 PPUSCROLL 和 PPUADDR 的寫入。不要把它插入其他常式尚未寫完的暫存器位元組對之間。這些呼叫不會等待 VBlank,也不會完成待處理傳輸佇列。

NESdev:PPU 暫存器

NESdev:PPU 捲動

使用範例

c
__scroll_set(19,27); // Save both coordinates and write the two-scroll-byte pair.

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式用於學習暫存器設定、圖塊與屬性直接寫入、捲動,以及背景和精靈調色盤的分別載入。(0,16) 處藍色條帶為 256×16 像素:首行重複實心 8×8 圖塊、左半 4×8 直條和八行三角形,第二行只有左半直條。(16,48) 處是綠色實心 64×32 矩形,(144,48) 處的紅色 64×32 區域由橫八、縱四個三角形圖塊組成。

y=96 處,x=16、24、32 的藍色標記分別為正方形、左半直條和三角形;x=80 為紅色左半直條,x=144 為綠色三角形。這些是背景圖塊。使用獨立調色盤的精靈位於 y=144:x=16 是紅色 8×8 正方形,x=80 是藍色左半 4×8 直條,x=144 是綠色八行三角形。最終捲動為 (0,0)。

影像檢查 16,384 個像素的預期形狀和色彩,包括精靈色彩。另有 31 個狀態測試,涵蓋暫存器替換、狀態與鎖存器副作用、捲動儲存值、0/255 位元組傳輸、增量 1/32、地圖邊緣、矩形零尺寸、表選擇、完整屬性位元組及兩種調色盤目標。Y=255 僅檢查暫存器寫入,不代表驗證了正確的捲動畫面。證據來自 KUROSAKI 的 NROM 環境,未測試實機。

__scroll_set: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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 讀回的值;直接寫暫存器不會更新此儲存值。

語法

c
void __scroll_x_set(u8 x);

參數

x

偏移是無號位元組的像素值。X 使用 0..255,Y 通常使用 0..239。Y 的 240..255 也會原樣寫入,但會觸發 PPU 特殊行為,並非普通可見地圖捲動。基準名稱表位元由 PPUCTRL 提供。直接存取 VRAM 後、開始繪製前,應還原所需的捲動;函式不會代為安排時序。

回傳值

無(void)。

使用條件與限制

讀取 PPUSTATUS 可能影響 NMI 時序,以及後續對 PPUSCROLL 和 PPUADDR 的寫入。不要把它插入其他常式尚未寫完的暫存器位元組對之間。這些呼叫不會等待 VBlank,也不會完成待處理傳輸佇列。

NESdev:PPU 暫存器

NESdev:PPU 捲動

使用範例

c
__scroll_x_set(0); // Keeps saved Y=27 while changing X to zero.

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式用於學習暫存器設定、圖塊與屬性直接寫入、捲動,以及背景和精靈調色盤的分別載入。(0,16) 處藍色條帶為 256×16 像素:首行重複實心 8×8 圖塊、左半 4×8 直條和八行三角形,第二行只有左半直條。(16,48) 處是綠色實心 64×32 矩形,(144,48) 處的紅色 64×32 區域由橫八、縱四個三角形圖塊組成。

y=96 處,x=16、24、32 的藍色標記分別為正方形、左半直條和三角形;x=80 為紅色左半直條,x=144 為綠色三角形。這些是背景圖塊。使用獨立調色盤的精靈位於 y=144:x=16 是紅色 8×8 正方形,x=80 是藍色左半 4×8 直條,x=144 是綠色八行三角形。最終捲動為 (0,0)。

影像檢查 16,384 個像素的預期形狀和色彩,包括精靈色彩。另有 31 個狀態測試,涵蓋暫存器替換、狀態與鎖存器副作用、捲動儲存值、0/255 位元組傳輸、增量 1/32、地圖邊緣、矩形零尺寸、表選擇、完整屬性位元組及兩種調色盤目標。Y=255 僅檢查暫存器寫入,不代表驗證了正確的捲動畫面。證據來自 KUROSAKI 的 NROM 環境,未測試實機。

__scroll_x_set: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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 使用執行階段儲存值。

語法

c
void __scroll_y_set(u8 y);

參數

y

偏移是無號位元組的像素值。X 使用 0..255,Y 通常使用 0..239。Y 的 240..255 也會原樣寫入,但會觸發 PPU 特殊行為,並非普通可見地圖捲動。基準名稱表位元由 PPUCTRL 提供。直接存取 VRAM 後、開始繪製前,應還原所需的捲動;函式不會代為安排時序。

回傳值

無(void)。

使用條件與限制

讀取 PPUSTATUS 可能影響 NMI 時序,以及後續對 PPUSCROLL 和 PPUADDR 的寫入。不要把它插入其他常式尚未寫完的暫存器位元組對之間。這些呼叫不會等待 VBlank,也不會完成待處理傳輸佇列。

NESdev:PPU 暫存器

NESdev:PPU 捲動

使用範例

c
__scroll_y_set(0); // Keeps saved X=0; the scene is now at the origin.

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式用於學習暫存器設定、圖塊與屬性直接寫入、捲動,以及背景和精靈調色盤的分別載入。(0,16) 處藍色條帶為 256×16 像素:首行重複實心 8×8 圖塊、左半 4×8 直條和八行三角形,第二行只有左半直條。(16,48) 處是綠色實心 64×32 矩形,(144,48) 處的紅色 64×32 區域由橫八、縱四個三角形圖塊組成。

y=96 處,x=16、24、32 的藍色標記分別為正方形、左半直條和三角形;x=80 為紅色左半直條,x=144 為綠色三角形。這些是背景圖塊。使用獨立調色盤的精靈位於 y=144:x=16 是紅色 8×8 正方形,x=80 是藍色左半 4×8 直條,x=144 是綠色八行三角形。最終捲動為 (0,0)。

影像檢查 16,384 個像素的預期形狀和色彩,包括精靈色彩。另有 31 個狀態測試,涵蓋暫存器替換、狀態與鎖存器副作用、捲動儲存值、0/255 位元組傳輸、增量 1/32、地圖邊緣、矩形零尺寸、表選擇、完整屬性位元組及兩種調色盤目標。Y=255 僅檢查暫存器寫入,不代表驗證了正確的捲動畫面。證據來自 KUROSAKI 的 NROM 環境,未測試實機。

__scroll_y_set: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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

計算兩項帶正負號的內積。

語法

c
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 位元樣式傳回。

使用範例

c
// 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 位元值顯示;負數使用二補數位元樣式。GB 會縮放固定小數點乘積,FC 則傳回整數乘積,因此相同輸入不一定得到相同結果。

此程式碼片段儲存以下 16 位元值:result[34]=64959, result[35]=64959, result[36]=577, result[37]=577。各項常值輸入計算皆與對應的 RAM 輸入結果一致。畫面顯示代表值,result 陣列則保存所有結果。

__sdot2_q1_7: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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

計算兩項帶正負號的內積。

語法

c
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 位元樣式傳回。

使用範例

c
// 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 位元值顯示;負數使用二補數位元樣式。GB 會縮放固定小數點乘積,FC 則傳回整數乘積,因此相同輸入不一定得到相同結果。

此程式碼片段儲存以下 16 位元值:result[26]=64959, result[27]=64959, result[28]=577, result[29]=577。各項常值輸入計算皆與對應的 RAM 輸入結果一致。畫面顯示代表值,result 陣列則保存所有結果。

__sdot2_q8_8: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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

計算三項帶正負號的內積。

語法

c
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 位元樣式傳回。

使用範例

c
// 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 位元值顯示;負數使用二補數位元樣式。GB 會縮放固定小數點乘積,FC 則傳回整數乘積,因此相同輸入不一定得到相同結果。

此程式碼片段儲存以下 16 位元值:result[38]=2498, result[39]=2498, result[40]=63038, result[41]=63038。各項常值輸入計算皆與對應的 RAM 輸入結果一致。畫面顯示代表值,result 陣列則保存所有結果。

__sdot3_q1_7: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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

計算三項帶正負號的內積。

語法

c
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 位元樣式傳回。

使用範例

c
// 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 位元值顯示;負數使用二補數位元樣式。GB 會縮放固定小數點乘積,FC 則傳回整數乘積,因此相同輸入不一定得到相同結果。

此程式碼片段儲存以下 16 位元值:result[30]=2498, result[31]=2498, result[32]=63038, result[33]=63038。各項常值輸入計算皆與對應的 RAM 輸入結果一致。畫面顯示代表值,result 陣列則保存所有結果。

__sdot3_q8_8: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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 作為外接轉接器提供的接收線,取樣一次。

語法

c
u8 __serial_rx_bit(void);

回傳值

位元 4 設為 1 時傳回 1,否則傳回 0。這不是資料就緒或通訊錯誤狀態。

使用條件與限制

這些 API 直接存取 $4016/$4017。不可與相同連接埠上的中斷驅動遊戲控制器、鍵盤或序列操作交錯執行。控制器移位位置也可能受影響。範例擷取輸入時停用 NMI,初始化顯示時再啟用。

這些低階操作假設外接轉接器將 OUT0 接至 TX,並將 $4017 D4 接至 RX。它們不提供位元間隔、鮑率時脈、起始/停止位元、位元組組合或接收等待功能,用途與 __midi_out() 等以位元組為單位的輔助函式不同。

呼叫端必須在適當時刻取樣。此操作不會等待資料或組合多個位元,也不會改變 $4016 的輸出。

位元轉換也以自行編寫的測試 ROM 進行單元測試,將暫存器讀取導向 RAM 中的測試值。測試涵蓋無關位元設為 1 時的 00/08/10/18 組合。此單元測試與未修改範例的執行結果及實際周邊裝置的行為分開看待。

這些影像是在 KUROSAKI 中原樣執行可下載 ROM 的結果。目前輸入匯流排模擬遊戲控制器,但不模擬鍵盤、光線槍或序列轉接器訊號。外部輸入位元讀為零,因此畫面不驗證這些裝置。實際按鍵輸入、光學感測及通訊尚未測試。

使用範例

c
#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。

__serial_rx_bit: SERIAL-DEFAULT — VALUE 為 000/001/002/003/254/255 時,EXPECTED OUT0 為 0/1/0/1/0/1。這一欄說明最低位元的對應關係,並不是電氣訊號的回讀值。CPU 追蹤確認了這些寫入,最後再寫入 1。KUROSAKI 尚未模擬接收線,因此 RX SAMPLE 為 000。
SERIAL-DEFAULT — VALUE 為 000/001/002/003/254/255 時,EXPECTED OUT0 為 0/1/0/1/0/1。這一欄說明最低位元的對應關係,並不是電氣訊號的回讀值。CPU 追蹤確認了這些寫入,最後再寫入 1。KUROSAKI 尚未模擬接收線,因此 RX SAMPLE 為 000。

完整範例程式

建置命令

powershell
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

csharp
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,為外接序列轉接器提供單一位元輸出操作。

語法

c
void __serial_tx_bit(u8 bit);

參數

bit

要輸出的值。只使用 bit & 1:2 會輸出零,255 會輸出 1。

回傳值

無傳回值。

使用條件與限制

這些 API 直接存取 $4016/$4017。不可與相同連接埠上的中斷驅動遊戲控制器、鍵盤或序列操作交錯執行。控制器移位位置也可能受影響。範例擷取輸入時停用 NMI,初始化顯示時再啟用。

這些低階操作假設外接轉接器將 OUT0 接至 TX,並將 $4017 D4 接至 RX。它們不提供位元間隔、鮑率時脈、起始/停止位元、位元組組合或接收等待功能,用途與 __midi_out() 等以位元組為單位的輔助函式不同。

將整個 $4016 寫為零或 1,同時清除 OUT1/OUT2。不插入維持電位的延遲。OUT0 控制一般遊戲手把的鎖存,因此此操作也會改變手把的讀取狀態。

這些影像是在 KUROSAKI 中原樣執行可下載 ROM 的結果。目前輸入匯流排模擬遊戲控制器,但不模擬鍵盤、光線槍或序列轉接器訊號。外部輸入位元讀為零,因此畫面不驗證這些裝置。實際按鍵輸入、光學感測及通訊尚未測試。

使用範例

c
#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。

__serial_tx_bit: SERIAL-DEFAULT — VALUE 為 000/001/002/003/254/255 時,EXPECTED OUT0 為 0/1/0/1/0/1。這一欄說明最低位元的對應關係,並不是電氣訊號的回讀值。CPU 追蹤確認了這些寫入,最後再寫入 1。KUROSAKI 尚未模擬接收線,因此 RX SAMPLE 為 000。
SERIAL-DEFAULT — VALUE 為 000/001/002/003/254/255 時,EXPECTED OUT0 為 0/1/0/1/0/1。這一欄說明最低位元的對應關係,並不是電氣訊號的回讀值。CPU 追蹤確認了這些寫入,最後再寫入 1。KUROSAKI 尚未模擬接收線,因此 RX SAMPLE 為 000。

完整範例程式

建置命令

powershell
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

csharp
EmitHelperStart("__serial_tx_bit");
            EmitAsm("LDA", Mem(CallArgBase));
            EmitAsm("AND", Imm(1));
            EmitAsm("STA", Mem(0x4016));
            EmitAsm("RTS");
__smac16

計算帶正負號的乘加。

語法

c
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 位元樣式傳回。

使用範例

c
// 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 位元值顯示;負數使用二補數位元樣式。GB 會縮放固定小數點乘積,FC 則傳回整數乘積,因此相同輸入不一定得到相同結果。

此程式碼片段儲存以下 16 位元值:result[10]=32796, result[11]=32796, result[12]=32668, result[13]=32668。各項常值輸入計算皆與對應的 RAM 輸入結果一致。畫面顯示代表值,result 陣列則保存所有結果。

__smac16: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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

計算帶正負號的乘加。

語法

c
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 位元樣式傳回。

使用範例

c
// 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 位元值顯示;負數使用二補數位元樣式。GB 會縮放固定小數點乘積,FC 則傳回整數乘積,因此相同輸入不一定得到相同結果。

此程式碼片段儲存以下 16 位元值:result[18]=32796, result[19]=32796, result[20]=32668, result[21]=32668。各項常值輸入計算皆與對應的 RAM 輸入結果一致。畫面顯示代表值,result 陣列則保存所有結果。

__smac16_q1_7: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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

計算帶正負號的乘法。

語法

c
u16 __smul16x8(u16 a, u8 b);

參數

a

表示 −32768..32767 的帶正負號 16 位元位元樣式。

b

帶正負號的位元組樣式,係數為 −128..127。

回傳值

傳回 a*b 的低 16 位元。

使用條件與限制

FC 中名為 q8_8/q1_7 的函式使用原始整數乘積,不做固定小數點縮放右移。Q1.7 函式是對應帶正負號運算的別名。單位換算由呼叫端處理;帶正負號的結果以 u16 位元樣式傳回。

使用範例

c
// 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 位元值顯示;負數使用二補數位元樣式。GB 會縮放固定小數點乘積,FC 則傳回整數乘積,因此相同輸入不一定得到相同結果。

此程式碼片段儲存以下 16 位元值:result[4]=32832, result[5]=32832, result[6]=32704, result[7]=32704。各項常值輸入計算皆與對應的 RAM 輸入結果一致。畫面顯示代表值,result 陣列則保存所有結果。

__smul16x8: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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

計算帶正負號的乘法。

語法

c
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 位元樣式傳回。

使用範例

c
// 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 位元值顯示;負數使用二補數位元樣式。GB 會縮放固定小數點乘積,FC 則傳回整數乘積,因此相同輸入不一定得到相同結果。

此程式碼片段儲存以下 16 位元值:result[14]=32832, result[15]=32832, result[16]=32704, result[17]=32704。各項常值輸入計算皆與對應的 RAM 輸入結果一致。畫面顯示代表值,result 陣列則保存所有結果。

__smul16x8_q1_7: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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

等待新的精靈零命中,再寫入水平分割的捲動 X/Y,保持上方對齊並移動下方背景。

語法

c
void __split_scroll_sprite0(u8 x, u8 y);

參數

x

水平捲動值 0..255,不是物件螢幕 X 座標。範例使用 32,將同一背景的下半部左移 32 像素。

y

暫時垂直捲動設定 0..255,通常可見範圍為 0..239。此函式寫入 $2005,不會在繪製中立即改變目前垂直位置。水平分割範例傳入零。

回傳值

無傳回值。

使用條件與限制

啟用兩圖層,讓 OAM 精靈 0 的不透明像素與背景不透明像素重疊。置於背景後方的優先旗標仍允許命中。最左八像素須開啟對應圖層顯示;X=255 的重疊無法觸發命中。

輪詢 PPUSTATUS($2002)位元 6,先等它清除,再等它設為 1。呼叫時若已命中,會等待下一影格的新命中。讀取不會清除此旗標;PPU 在預繪製階段清除它。

沒有逾時或失敗回傳。精靈 0 隱藏、背景透明、繪製停用或其他無重疊設定,都會使呼叫持續等待。呼叫前須確保命中。不要求啟用 CPU IRQ,也不會啟用 NMI。範例為了另外的影格等待而啟用 NMI。

讀取 $2002 也會影響共用 PPU 位址/捲動寫入鎖存器及 VBlank 旗標。勿將此呼叫插在 $2005/$2006 的兩次寫入之間。請從主程式使用,不要從 NMI/IRQ 處理常式使用。

命中後讀取 $2002,再依 X、Y 順序寫入 $2005。細部 X 立即改變,粗略 X 於下一次水平重載生效。Y 通常在下次預繪製重載生效,除非先被其他捲動寫入取代。這不是立即任意重設 X/Y 光柵位置的操作。

輪詢與指令執行會延遲寫入。KUROSAKI 以掃描線繪製,因此擷取不能證明實機逐點精確邊界。範例將變更放在 Y=96..103 白帶內,讓邊界的不確定性遠離藍、紅長條。

共用標頭 fc_sprite0_scene.h 準備原創藍/紅長條與白色水平帶。精靈 0 位於螢幕 (16,96),藏在背景後方;顯示 Y 為 OAM Y+1,因此原始 OAM Y 是 95。每影格在 VBlank 將頂部捲動還原為零,再等待命中。

NESdev:PPU 捲動

NESdev:PPU 程式設計參考

使用範例

c
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。驗證檢查每影格完成一次,並比對所有像素的位置與顏色。

__split_scroll_sprite0: SPLIT-DEFAULT — 白帶上方,藍色為 X=64..95,紅色為 X=160..191。下方自 Y=104 起,藍色移至 X=32..63,紅色移至 X=128..159,僅下方區域左移 32 像素。水平帶位於 Y=96..103,長條結束於 Y=207。驗證檢查每影格完成一次,並比對所有像素的位置與顏色。
SPLIT-DEFAULT — 白帶上方,藍色為 X=64..95,紅色為 X=160..191。下方自 Y=104 起,藍色移至 X=32..63,紅色移至 X=128..159,僅下方區域左移 32 像素。水平帶位於 Y=96..103,長條結束於 Y=207。驗證檢查每影格完成一次,並比對所有像素的位置與顏色。

完整範例程式

建置命令

powershell
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

等待新的精靈零/背景重疊,將主程式工作同步到已知畫面區域,不改變捲動值。

語法

c
void __sprite0_wait_hit(void);

回傳值

無傳回值。

使用條件與限制

啟用兩圖層,讓 OAM 精靈 0 的不透明像素與背景不透明像素重疊。置於背景後方的優先旗標仍允許命中。最左八像素須開啟對應圖層顯示;X=255 的重疊無法觸發命中。

輪詢 PPUSTATUS($2002)位元 6,先等它清除,再等它設為 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 將頂部捲動還原為零,再等待命中。

NESdev:PPU 捲動

NESdev:PPU 程式設計參考

使用範例

c
void main(void) {
    s0_prepare();
    while (1) {
        s0_frame();
        __sprite0_wait_hit();
        s0_completed++;
    }
}

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

藍條占 X=64..95,紅條占 X=160..191,白帶上下對齊。僅等待不改變捲動。執行範例在 120 影格後完成 116 次等待,121 影格後完成 117 次,驗證持續推進。

__sprite0_wait_hit: WAIT-DEFAULT — 藍條占 X=64..95,紅條占 X=160..191,白帶上下對齊。僅等待不改變捲動。執行範例在 120 影格後完成 116 次等待,121 影格後完成 117 次,驗證持續推進。
WAIT-DEFAULT — 藍條占 X=64..95,紅條占 X=160..191,白帶上下對齊。僅等待不改變捲動。執行範例在 120 影格後完成 116 次等待,121 影格後完成 117 次,驗證持續推進。

完整範例程式

建置命令

powershell
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

取代一個影子精靈的完整屬性位元組,可在不變更圖塊或位置的情況下設定調色盤、背景優先順序,以及水平或垂直翻轉。

語法

c
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。它們不會維護精靈函式庫的配置旗標、座標陣列或使用數量。應避免中斷處理常式修改或傳送尚未準備完成的頁面。

使用範例

c
__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)顯示箭頭。程式使用__oam_dma直接傳送第02頁,其中包含各項精靈操作準備好的全部欄位。

__sprite_attr: PAGE02
PAGE02

完整範例程式

建置命令

powershell
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.
csharp
EmitHelperStart("__sprite_attr");
            EmitSpriteIndexToX();
            EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("STA", new AsmOperand(OamRamBase + 2, AddressMode.AbsoluteX));
            EmitAsm("RTS");
__sprite_hide

將一個影子精靈的Y設為0xF0以隱藏它。X、圖塊及屬性會保留以供重複使用。此函式不會釋放已配置的項目,也不會變更精靈函式庫的使用數量。

語法

c
void __sprite_hide(u8 index);

參數

index

精靈項目編號,範圍0–63,不是位元組偏移。實作使用8位元游標將其乘以4;超出範圍的值會以64為週期循環,可能覆寫其他項目。不會檢查項目是否已配置。

回傳值

無(void)。

使用條件與限制

這些操作會修改CPU RAM中的0x0200–0x02FF,不會立即改變硬體OAM。請準備好完整頁面後再呼叫__oam_dma。它們不會維護精靈函式庫的配置旗標、座標陣列或使用數量。應避免中斷處理常式修改或傳送尚未準備完成的頁面。

使用範例

c
__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)顯示箭頭。程式使用__oam_dma直接傳送第02頁,其中包含各項精靈操作準備好的全部欄位。

__sprite_hide: PAGE02
PAGE02

完整範例程式

建置命令

powershell
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.
csharp
EmitHelperStart("__sprite_hide");
            EmitSpriteIndexToX();
            EmitAsm("LDA", Imm(0xF0)); EmitAsm("STA", new AsmOperand(OamRamBase + 0, AddressMode.AbsoluteX));
            EmitAsm("RTS");
__sprite_move

只修改一個影子精靈的X與Y位元組,保留圖塊編號及屬性,因此可在不變更調色盤或翻轉狀態的情況下移動同一圖形。

語法

c
void __sprite_move(u8 index, u8 x, u8 y);

參數

index

精靈項目編號,範圍0–63,不是位元組偏移。實作使用8位元游標將其乘以4;超出範圍的值會以64為週期循環,可能覆寫其他項目。不會檢查項目是否已配置。

x

以像素為單位的橫向位置,使用無號位元組表示,並非圖塊座標。

y

直接寫入 OAM 的 Y 位元組。若要將頂端放在第 1 條掃描線或更下方,應傳入頂端座標減一;239..255 位於 240 行可見區域之外。

回傳值

無(void)。

使用條件與限制

這些操作會修改CPU RAM中的0x0200–0x02FF,不會立即改變硬體OAM。請準備好完整頁面後再呼叫__oam_dma。它們不會維護精靈函式庫的配置旗標、座標陣列或使用數量。應避免中斷處理常式修改或傳送尚未準備完成的頁面。

使用範例

c
__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)顯示箭頭。程式使用__oam_dma直接傳送第02頁,其中包含各項精靈操作準備好的全部欄位。

__sprite_move: PAGE02
PAGE02

完整範例程式

建置命令

powershell
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.
csharp
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。

語法

c
void __sprite_set(u8 index, u8 x, u8 y, u8 tile, u8 attr);

參數

index

精靈項目編號,範圍0–63,不是位元組偏移。實作使用8位元游標將其乘以4;超出範圍的值會以64為週期循環,可能覆寫其他項目。不會檢查項目是否已配置。

x

以像素為單位的橫向位置,使用無號位元組表示,並非圖塊座標。

y

直接寫入 OAM 的 Y 位元組。若要將頂端放在第 1 條掃描線或更下方,應傳入頂端座標減一;239..255 位於 240 行可見區域之外。

tile

8 位元圖塊編號。須先載入圖形。8×16 精靈仍遵循硬體規則,函式不會自動調整成成對圖塊編號。

attr

完整屬性位元組:位元0–1選擇精靈調色盤0–3;位元5使不透明精靈像素位於不透明背景像素之後;位元6與7分別控制水平及垂直翻轉。位元2–4應為0。可用位元OR組合旗標;傳入的位元組會取代全部原有屬性。

回傳值

無(void)。

使用條件與限制

這些操作會修改CPU RAM中的0x0200–0x02FF,不會立即改變硬體OAM。請準備好完整頁面後再呼叫__oam_dma。它們不會維護精靈函式庫的配置旗標、座標陣列或使用數量。應避免中斷處理常式修改或傳送尚未準備完成的頁面。

使用範例

c
__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)顯示箭頭。程式使用__oam_dma直接傳送第02頁,其中包含各項精靈操作準備好的全部欄位。

__sprite_set: PAGE02
PAGE02

完整範例程式

建置命令

powershell
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.
csharp
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記憶體。

語法

c
void __sprite_tile(u8 index, u8 tile);

參數

index

精靈項目編號,範圍0–63,不是位元組偏移。實作使用8位元游標將其乘以4;超出範圍的值會以64為週期循環,可能覆寫其他項目。不會檢查項目是否已配置。

tile

8 位元圖塊編號。須先載入圖形。8×16 精靈仍遵循硬體規則,函式不會自動調整成成對圖塊編號。

回傳值

無(void)。

使用條件與限制

這些操作會修改CPU RAM中的0x0200–0x02FF,不會立即改變硬體OAM。請準備好完整頁面後再呼叫__oam_dma。它們不會維護精靈函式庫的配置旗標、座標陣列或使用數量。應避免中斷處理常式修改或傳送尚未準備完成的頁面。

使用範例

c
__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)顯示箭頭。程式使用__oam_dma直接傳送第02頁,其中包含各項精靈操作準備好的全部欄位。

__sprite_tile: PAGE02
PAGE02

完整範例程式

建置命令

powershell
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.
csharp
EmitHelperStart("__sprite_tile");
            EmitSpriteIndexToX();
            EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("STA", new AsmOperand(OamRamBase + 1, AddressMode.AbsoluteX));
            EmitAsm("RTS");
__vram_fill

設定 ppu_addr 後,將同一個位元組值 value 向 PPUDATA 寫入 len 次。這是返回前完成的直接填滿操作,不會解讀圖塊尺寸、屬性或矩形邊界。

語法

c
void __vram_fill(u16 ppu_addr, u8 value, u8 len);

參數

ppu_addr

PPU位元組位址(0x0000–0x3FFF),不是CPU指標。目的區域必須可寫;改寫圖樣需要CHR RAM,不能寫入CHR ROM。PPU對映與鏡像規則照常適用。連續複製和填滿應將PPUCTRL位址增量設為1。在安全的PPU寫入時段執行,然後恢復所需的捲動狀態;執行器會寫入PPUADDR和PPUDATA,不會恢復捲動設定。

value

重複寫入0至255的單一位元組。顯示意義取決於目標是圖塊索引、像素位元平面還是其他位元組結構。

len

位元組數 0..255。0 仍會設定 PPU 位址並重設鎖存器,但不寫資料。不檢查目的地容量、不裁切、不切換記憶體庫,也不延後傳輸。

回傳值

無(void)。

使用條件與限制

直接存取 PPU 時,必須關閉繪製,或確保整個操作位於安全的 VBlank 時間內。這些函式不等待、不排入佇列、不阻擋 NMI,也不還原捲動。呼叫期間應防止中斷改變共用鎖存器或位址。PPUCTRL 第 2 位元決定每個資料位元組使目的位址增加 1 還是 32。鏡像和位址繞回由 PPU 處理;CHR-ROM 不可寫入。存取結束後、開始繪製前,請設定所需的捲動狀態。

NESdev:PPU 暫存器

NESdev:PPU 捲動

使用範例

c
__vram_fill(0x2060,2,32); // A second blue row of 4-pixel-wide left halves.

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式用於學習暫存器設定、圖塊與屬性直接寫入、捲動,以及背景和精靈調色盤的分別載入。(0,16) 處藍色條帶為 256×16 像素:首行重複實心 8×8 圖塊、左半 4×8 直條和八行三角形,第二行只有左半直條。(16,48) 處是綠色實心 64×32 矩形,(144,48) 處的紅色 64×32 區域由橫八、縱四個三角形圖塊組成。

y=96 處,x=16、24、32 的藍色標記分別為正方形、左半直條和三角形;x=80 為紅色左半直條,x=144 為綠色三角形。這些是背景圖塊。使用獨立調色盤的精靈位於 y=144:x=16 是紅色 8×8 正方形,x=80 是藍色左半 4×8 直條,x=144 是綠色八行三角形。最終捲動為 (0,0)。

影像檢查 16,384 個像素的預期形狀和色彩,包括精靈色彩。另有 31 個狀態測試,涵蓋暫存器替換、狀態與鎖存器副作用、捲動儲存值、0/255 位元組傳輸、增量 1/32、地圖邊緣、矩形零尺寸、表選擇、完整屬性位元組及兩種調色盤目標。Y=255 僅檢查暫存器寫入,不代表驗證了正確的捲動畫面。證據來自 KUROSAKI 的 NROM 環境,未測試實機。

__vram_fill: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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。計數為八位元,編譯器使用索引讀取來源。不為稍後執行保留指標,返回後修改來源不會改變 VRAM。

語法

c
void __vram_write(u16 ppu_addr, const u8* src, u8 len);

參數

ppu_addr

PPU位元組位址(0x0000–0x3FFF),不是CPU指標。目的區域必須可寫;改寫圖樣需要CHR RAM,不能寫入CHR ROM。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 不可寫入。存取結束後、開始繪製前,請設定所需的捲動狀態。

NESdev:PPU 暫存器

NESdev:PPU 捲動

使用範例

c
__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 直條和八行三角形,第二行只有左半直條。(16,48) 處是綠色實心 64×32 矩形,(144,48) 處的紅色 64×32 區域由橫八、縱四個三角形圖塊組成。

y=96 處,x=16、24、32 的藍色標記分別為正方形、左半直條和三角形;x=80 為紅色左半直條,x=144 為綠色三角形。這些是背景圖塊。使用獨立調色盤的精靈位於 y=144:x=16 是紅色 8×8 正方形,x=80 是藍色左半 4×8 直條,x=144 是綠色八行三角形。最終捲動為 (0,0)。

影像檢查 16,384 個像素的預期形狀和色彩,包括精靈色彩。另有 31 個狀態測試,涵蓋暫存器替換、狀態與鎖存器副作用、捲動儲存值、0/255 位元組傳輸、增量 1/32、地圖邊緣、矩形零尺寸、表選擇、完整屬性位元組及兩種調色盤目標。Y=255 僅檢查暫存器寫入,不代表驗證了正確的捲動畫面。證據來自 KUROSAKI 的 NROM 環境,未測試實機。

__vram_write: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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

回傳命令緩衝區的總容量,與目前使用量無關。

語法

c
u8 __vramq_capacity(void);

回傳值

以此處說明的平台單位表示的無號八位元值。讀取不會修改或執行佇列。

使用條件與限制

FC以固定128位元組內建緩衝區中的編碼命令位元組數計量。單圖塊寫入占4位元組,填滿記錄占5位元組,指標複製記錄占6位元組,均含中繼資料。就緒佇列可能由NMI執行,應在提交前檢查及建立,並協調產生與執行階段。

這些內建函式操作的命令緩衝區與lib/vram.c相同。runtime.c中的nes_vram_queue_*管理另一個記錄格式不同的佇列。清除或查詢其中一個,不會清除或測量另一個。

應協調佇列建立與執行階段。不要在中斷執行佇列時加入、直接修改管理變數或意外覆寫保留的來源資料。這些API不提供鎖定或雙重緩衝。容量指命令緩衝區,不是硬體VRAM可用位元組數。

使用範例

c
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)開始。每塊包含7個完整的256像素寬圖塊列,以及第8列的248像素,共255個圖塊。最後一個8×8格保持黑色。複製重複實心、左半填滿和三角形的8×8圖樣;因加入後修改了來源資料,首圖塊應為三角形。填滿在呼叫端變數改變後仍保持實心。測試同時檢查形狀和顏色。

在y=160處,x=16應為紅色實心正方形,x=224為紅色左半圖塊,x=240為紅色實心正方形。第一個驗證零長度複製和填滿不會改寫;中間驗證預設NMI執行;最後驗證單次寫入。已取消的(248,160)格必須保持黑色。長傳輸在關閉繪製和NMI時執行,只有最後的小寫入使用NMI。這是NROM模擬器證據,不是實機測試。

__vramq_capacity: NROM
NROM

完整範例程式

建置命令

powershell
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。

語法

c
void __vramq_clear(void);

回傳值

無(void)。

使用條件與限制

FC以固定128位元組內建緩衝區中的編碼命令位元組數計量。單圖塊寫入占4位元組,填滿記錄占5位元組,指標複製記錄占6位元組,均含中繼資料。就緒佇列可能由NMI執行,應在提交前檢查及建立,並協調產生與執行階段。

這些內建函式操作的命令緩衝區與lib/vram.c相同。runtime.c中的nes_vram_queue_*管理另一個記錄格式不同的佇列。清除或查詢其中一個,不會清除或測量另一個。

應協調佇列建立與執行階段。不要在中斷執行佇列時加入、直接修改管理變數或意外覆寫保留的來源資料。這些API不提供鎖定或雙重緩衝。容量指命令緩衝區,不是硬體VRAM可用位元組數。

使用範例

c
__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)開始。每塊包含7個完整的256像素寬圖塊列,以及第8列的248像素,共255個圖塊。最後一個8×8格保持黑色。複製重複實心、左半填滿和三角形的8×8圖樣;因加入後修改了來源資料,首圖塊應為三角形。填滿在呼叫端變數改變後仍保持實心。測試同時檢查形狀和顏色。

在y=160處,x=16應為紅色實心正方形,x=224為紅色左半圖塊,x=240為紅色實心正方形。第一個驗證零長度複製和填滿不會改寫;中間驗證預設NMI執行;最後驗證單次寫入。已取消的(248,160)格必須保持黑色。長傳輸在關閉繪製和NMI時執行,只有最後的小寫入使用NMI。這是NROM模擬器證據,不是實機測試。

__vramq_clear: NROM
NROM

完整範例程式

建置命令

powershell
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,待處理記錄、長度和就緒狀態均不變。此操作僅確認並清除容量不足通知,不重試被拒絕的記錄,也不復原先前成功的加入操作。

語法

c
void __vramq_clear_overflow(void);

回傳值

無(void)。

使用條件與限制

FC以固定128位元組內建緩衝區中的編碼命令位元組數計量。單圖塊寫入占4位元組,填滿記錄占5位元組,指標複製記錄占6位元組,均含中繼資料。就緒佇列可能由NMI執行,應在提交前檢查及建立,並協調產生與執行階段。

這些內建函式操作的命令緩衝區與lib/vram.c相同。runtime.c中的nes_vram_queue_*管理另一個記錄格式不同的佇列。清除或查詢其中一個,不會清除或測量另一個。

應協調佇列建立與執行階段。不要在中斷執行佇列時加入、直接修改管理變數或意外覆寫保留的來源資料。這些API不提供鎖定或雙重緩衝。容量指命令緩衝區,不是硬體VRAM可用位元組數。

使用範例

c
__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)開始。每塊包含7個完整的256像素寬圖塊列,以及第8列的248像素,共255個圖塊。最後一個8×8格保持黑色。複製重複實心、左半填滿和三角形的8×8圖樣;因加入後修改了來源資料,首圖塊應為三角形。填滿在呼叫端變數改變後仍保持實心。測試同時檢查形狀和顏色。

在y=160處,x=16應為紅色實心正方形,x=224為紅色左半圖塊,x=240為紅色實心正方形。第一個驗證零長度複製和填滿不會改寫;中間驗證預設NMI執行;最後驗證單次寫入。已取消的(248,160)格必須保持黑色。長傳輸在關閉繪製和NMI時執行,只有最後的小寫入使用NMI。這是NROM模擬器證據,不是實機測試。

__vramq_clear_overflow: NROM
NROM

完整範例程式

建置命令

powershell
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開啟時,預設處理常式可能非同步執行佇列。

語法

c
void __vramq_commit(void);

回傳值

無(void)。

使用條件與限制

FC以固定128位元組內建緩衝區中的編碼命令位元組數計量。單圖塊寫入占4位元組,填滿記錄占5位元組,指標複製記錄占6位元組,均含中繼資料。就緒佇列可能由NMI執行,應在提交前檢查及建立,並協調產生與執行階段。

這些內建函式操作的命令緩衝區與lib/vram.c相同。runtime.c中的nes_vram_queue_*管理另一個記錄格式不同的佇列。清除或查詢其中一個,不會清除或測量另一個。

應協調佇列建立與執行階段。不要在中斷執行佇列時加入、直接修改管理變數或意外覆寫保留的來源資料。這些API不提供鎖定或雙重緩衝。容量指命令緩衝區,不是硬體VRAM可用位元組數。

使用範例

c
__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)開始。每塊包含7個完整的256像素寬圖塊列,以及第8列的248像素,共255個圖塊。最後一個8×8格保持黑色。複製重複實心、左半填滿和三角形的8×8圖樣;因加入後修改了來源資料,首圖塊應為三角形。填滿在呼叫端變數改變後仍保持實心。測試同時檢查形狀和顏色。

在y=160處,x=16應為紅色實心正方形,x=224為紅色左半圖塊,x=240為紅色實心正方形。第一個驗證零長度複製和填滿不會改寫;中間驗證預設NMI執行;最後驗證單次寫入。已取消的(248,160)格必須保持黑色。長傳輸在關閉繪製和NMI時執行,只有最後的小寫入使用NMI。這是NROM模擬器證據,不是實機測試。

__vramq_commit: NROM
NROM

完整範例程式

建置命令

powershell
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

加入一筆6位元組記錄,在執行時將src的len位元組複製到ppu_addr。佇列保留來源指標而非資料本身。即使複製255位元組,也只占6位元組命令緩衝區。來源資料依位址遞增順序讀取;目的連續寫入需選擇PPU增量1。

語法

c
void __vramq_copy(u16 ppu_addr, const u8* src, u8 len);

參數

ppu_addr

PPU位元組位址(0x0000–0x3FFF),不是CPU指標。目的區域必須可寫;改寫圖樣需要CHR RAM,不能寫入CHR ROM。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可用位元組數。

使用範例

c
__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)開始。每塊包含7個完整的256像素寬圖塊列,以及第8列的248像素,共255個圖塊。最後一個8×8格保持黑色。複製重複實心、左半填滿和三角形的8×8圖樣;因加入後修改了來源資料,首圖塊應為三角形。填滿在呼叫端變數改變後仍保持實心。測試同時檢查形狀和顏色。

在y=160處,x=16應為紅色實心正方形,x=224為紅色左半圖塊,x=240為紅色實心正方形。第一個驗證零長度複製和填滿不會改寫;中間驗證預設NMI執行;最後驗證單次寫入。已取消的(248,160)格必須保持黑色。長傳輸在關閉繪製和NMI時執行,只有最後的小寫入使用NMI。這是NROM模擬器證據,不是實機測試。

__vramq_copy: NROM
NROM

完整範例程式

建置命令

powershell
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存取時段、不限制傳輸量,也不恢復捲動;這些條件由呼叫端安排。

語法

c
void __vramq_exec(void);

回傳值

無(void)。

使用條件與限制

PPU位元組位址(0x0000–0x3FFF),不是CPU指標。目的區域必須可寫;改寫圖樣需要CHR RAM,不能寫入CHR ROM。PPU對映與鏡像規則照常適用。連續複製和填滿應將PPUCTRL位址增量設為1。在安全的PPU寫入時段執行,然後恢復所需的捲動狀態;執行器會寫入PPUADDR和PPUDATA,不會恢復捲動設定。

FC以固定128位元組內建緩衝區中的編碼命令位元組數計量。單圖塊寫入占4位元組,填滿記錄占5位元組,指標複製記錄占6位元組,均含中繼資料。就緒佇列可能由NMI執行,應在提交前檢查及建立,並協調產生與執行階段。

這些內建函式操作的命令緩衝區與lib/vram.c相同。runtime.c中的nes_vram_queue_*管理另一個記錄格式不同的佇列。清除或查詢其中一個,不會清除或測量另一個。

應協調佇列建立與執行階段。不要在中斷執行佇列時加入、直接修改管理變數或意外覆寫保留的來源資料。這些API不提供鎖定或雙重緩衝。容量指命令緩衝區,不是硬體VRAM可用位元組數。

使用範例

c
// 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)開始。每塊包含7個完整的256像素寬圖塊列,以及第8列的248像素,共255個圖塊。最後一個8×8格保持黑色。複製重複實心、左半填滿和三角形的8×8圖樣;因加入後修改了來源資料,首圖塊應為三角形。填滿在呼叫端變數改變後仍保持實心。測試同時檢查形狀和顏色。

在y=160處,x=16應為紅色實心正方形,x=224為紅色左半圖塊,x=240為紅色實心正方形。第一個驗證零長度複製和填滿不會改寫;中間驗證預設NMI執行;最後驗證單次寫入。已取消的(248,160)格必須保持黑色。長傳輸在關閉繪製和NMI時執行,只有最後的小寫入使用NMI。這是NROM模擬器證據,不是實機測試。

__vramq_exec: NROM
NROM

完整範例程式

建置命令

powershell
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

加入一筆5位元組記錄,將單位元組value向PPU重複寫入len次。值儲存在記錄中,之後修改呼叫端變數不會改變已加入的填充值。重複的是單位元組,不是多位元組圖塊圖樣。

語法

c
void __vramq_fill(u16 ppu_addr, u8 value, u8 len);

參數

ppu_addr

PPU位元組位址(0x0000–0x3FFF),不是CPU指標。目的區域必須可寫;改寫圖樣需要CHR RAM,不能寫入CHR ROM。PPU對映與鏡像規則照常適用。連續複製和填滿應將PPUCTRL位址增量設為1。在安全的PPU寫入時段執行,然後恢復所需的捲動狀態;執行器會寫入PPUADDR和PPUDATA,不會恢復捲動設定。

value

重複寫入0至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可用位元組數。

使用範例

c
__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)開始。每塊包含7個完整的256像素寬圖塊列,以及第8列的248像素,共255個圖塊。最後一個8×8格保持黑色。複製重複實心、左半填滿和三角形的8×8圖樣;因加入後修改了來源資料,首圖塊應為三角形。填滿在呼叫端變數改變後仍保持實心。測試同時檢查形狀和顏色。

在y=160處,x=16應為紅色實心正方形,x=224為紅色左半圖塊,x=240為紅色實心正方形。第一個驗證零長度複製和填滿不會改寫;中間驗證預設NMI執行;最後驗證單次寫入。已取消的(248,160)格必須保持黑色。長傳輸在關閉繪製和NMI時執行,只有最後的小寫入使用NMI。這是NROM模擬器證據,不是實機測試。

__vramq_fill: NROM
NROM

完整範例程式

建置命令

powershell
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

回傳待執行命令目前占用的命令緩衝區用量,不是將寫入視訊記憶體的資料位元組數。

語法

c
u8 __vramq_len(void);

回傳值

以此處說明的平台單位表示的無號八位元值。讀取不會修改或執行佇列。

使用條件與限制

FC以固定128位元組內建緩衝區中的編碼命令位元組數計量。單圖塊寫入占4位元組,填滿記錄占5位元組,指標複製記錄占6位元組,均含中繼資料。就緒佇列可能由NMI執行,應在提交前檢查及建立,並協調產生與執行階段。

這些內建函式操作的命令緩衝區與lib/vram.c相同。runtime.c中的nes_vram_queue_*管理另一個記錄格式不同的佇列。清除或查詢其中一個,不會清除或測量另一個。

應協調佇列建立與執行階段。不要在中斷執行佇列時加入、直接修改管理變數或意外覆寫保留的來源資料。這些API不提供鎖定或雙重緩衝。容量指命令緩衝區,不是硬體VRAM可用位元組數。

使用範例

c
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)開始。每塊包含7個完整的256像素寬圖塊列,以及第8列的248像素,共255個圖塊。最後一個8×8格保持黑色。複製重複實心、左半填滿和三角形的8×8圖樣;因加入後修改了來源資料,首圖塊應為三角形。填滿在呼叫端變數改變後仍保持實心。測試同時檢查形狀和顏色。

在y=160處,x=16應為紅色實心正方形,x=224為紅色左半圖塊,x=240為紅色實心正方形。第一個驗證零長度複製和填滿不會改寫;中間驗證預設NMI執行;最後驗證單次寫入。已取消的(248,160)格必須保持黑色。長傳輸在關閉繪製和NMI時執行,只有最後的小寫入使用NMI。這是NROM模擬器證據,不是實機測試。

__vramq_len: NROM
NROM

完整範例程式

建置命令

powershell
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

讀取佇列溢位旗標而不清除;先前的失敗紀錄可能仍保留。

語法

c
u8 __vramq_overflow(void);

回傳值

未記錄溢位時為0;加入被拒絕後為1,直到明確重設。執行佇列不會清除此旗標。

使用條件與限制

FC以固定128位元組內建緩衝區中的編碼命令位元組數計量。單圖塊寫入占4位元組,填滿記錄占5位元組,指標複製記錄占6位元組,均含中繼資料。就緒佇列可能由NMI執行,應在提交前檢查及建立,並協調產生與執行階段。

這些內建函式操作的命令緩衝區與lib/vram.c相同。runtime.c中的nes_vram_queue_*管理另一個記錄格式不同的佇列。清除或查詢其中一個,不會清除或測量另一個。

應協調佇列建立與執行階段。不要在中斷執行佇列時加入、直接修改管理變數或意外覆寫保留的來源資料。這些API不提供鎖定或雙重緩衝。容量指命令緩衝區,不是硬體VRAM可用位元組數。

使用範例

c
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)開始。每塊包含7個完整的256像素寬圖塊列,以及第8列的248像素,共255個圖塊。最後一個8×8格保持黑色。複製重複實心、左半填滿和三角形的8×8圖樣;因加入後修改了來源資料,首圖塊應為三角形。填滿在呼叫端變數改變後仍保持實心。測試同時檢查形狀和顏色。

在y=160處,x=16應為紅色實心正方形,x=224為紅色左半圖塊,x=240為紅色實心正方形。第一個驗證零長度複製和填滿不會改寫;中間驗證預設NMI執行;最後驗證單次寫入。已取消的(248,160)格必須保持黑色。長傳輸在關閉繪製和NMI時執行,只有最後的小寫入使用NMI。這是NROM模擬器證據,不是實機測試。

__vramq_overflow: NROM
NROM

完整範例程式

建置命令

powershell
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位元組。

語法

c
void __vramq_put(u16 ppu_addr, u8 value);

參數

ppu_addr

PPU位元組位址(0x0000–0x3FFF),不是CPU指標。目的區域必須可寫;改寫圖樣需要CHR RAM,不能寫入CHR ROM。PPU對映與鏡像規則照常適用。連續複製和填滿應將PPUCTRL位址增量設為1。在安全的PPU寫入時段執行,然後恢復所需的捲動狀態;執行器會寫入PPUADDR和PPUDATA,不會恢復捲動設定。

value

重複寫入0至255的單一位元組。顯示意義取決於目標是圖塊索引、像素位元平面還是其他位元組結構。

回傳值

無(void)。

使用條件與限制

加入函式不回傳成功值。完整記錄放不下時,將__vramq_overflow()設為1,保留原有記錄和長度。空間剛好足夠時可以加入。舊溢位旗標不會阻止放得下的新記錄,因此判斷新批次前應先處理或清除旗標。連續多次加入不構成交易。

FC以固定128位元組內建緩衝區中的編碼命令位元組數計量。單圖塊寫入占4位元組,填滿記錄占5位元組,指標複製記錄占6位元組,均含中繼資料。就緒佇列可能由NMI執行,應在提交前檢查及建立,並協調產生與執行階段。

這些內建函式操作的命令緩衝區與lib/vram.c相同。runtime.c中的nes_vram_queue_*管理另一個記錄格式不同的佇列。清除或查詢其中一個,不會清除或測量另一個。

應協調佇列建立與執行階段。不要在中斷執行佇列時加入、直接修改管理變數或意外覆寫保留的來源資料。這些API不提供鎖定或雙重緩衝。容量指命令緩衝區,不是硬體VRAM可用位元組數。

使用範例

c
__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)開始。每塊包含7個完整的256像素寬圖塊列,以及第8列的248像素,共255個圖塊。最後一個8×8格保持黑色。複製重複實心、左半填滿和三角形的8×8圖樣;因加入後修改了來源資料,首圖塊應為三角形。填滿在呼叫端變數改變後仍保持實心。測試同時檢查形狀和顏色。

在y=160處,x=16應為紅色實心正方形,x=224為紅色左半圖塊,x=240為紅色實心正方形。第一個驗證零長度複製和填滿不會改寫;中間驗證預設NMI執行;最後驗證單次寫入。已取消的(248,160)格必須保持黑色。長傳輸在關閉繪製和NMI時執行,只有最後的小寫入使用NMI。這是NROM模擬器證據,不是實機測試。

__vramq_put: NROM
NROM

完整範例程式

建置命令

powershell
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

測試是否位於矩形內,包含上/左邊界,不包含下/右邊界。

語法

c
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。寬或高為零時視為空集合。

使用條件與限制

使用座標差值,因此右邊界超過 255 時不會回捲至左側。

使用範例

c
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);

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

測試內部、邊界、外部點及零寬度:INSIDE=1、RIGHT EDGE=0、LEFT EDGE=0、HIGH X=1。另驗證距離 15、回捲距離 254,以及陣列索引 64/65280。

__xy_in_rect: NROM
NROM

完整範例程式

建置命令

powershell
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

csharp
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() 相同的輔助函式。

語法

c
u8 __zapper_light(void);

回傳值

$4017 的位元 3 為 0 時傳回 1,否則傳回 0。不讀取連接埠 1。

使用條件與限制

這些 API 直接存取 $4016/$4017。不可與相同連接埠上的中斷驅動遊戲控制器、鍵盤或序列操作交錯執行。控制器移位位置也可能受影響。範例擷取輸入時停用 NMI,初始化顯示時再啟用。

讀取瞬時訊號。不發出選通、不等待影格、不偵測邊緣、不定位瞄準點,也不判斷命中。原始值與扳機/光線呼叫各自在自己的時刻讀取暫存器,因此訊號變化時數值可能不同。

光線輸入為低有效:位元 3 清零時傳回一。此轉換不偵測是否連接光線槍;遊戲控制器或未連接輸入也可能得到一。在畫面繪製目標不會使它變成命中測試 API。

位元轉換也以自行編寫的測試 ROM 進行單元測試,將暫存器讀取導向 RAM 中的測試值。測試涵蓋無關位元設為 1 時的 00/08/10/18 組合。此單元測試與未修改範例的執行結果及實際周邊裝置的行為分開看待。

這些影像是在 KUROSAKI 中原樣執行可下載 ROM 的結果。目前輸入匯流排模擬遊戲控制器,但不模擬鍵盤、光線槍或序列轉接器訊號。外部輸入位元讀為零,因此畫面不驗證這些裝置。實際按鍵輸入、光學感測及通訊尚未測試。

NESdev: Zapper

使用範例

c
#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 讀取為零,並非偵測到螢幕亮度。畫面也標示 LIGHT 1 IS NOT A HIT TEST。

__zapper_light: ZAPPER-DEFAULT — 在 KUROSAKI 中,兩個連接埠都顯示 RAW=00、TRIGGER=000、LIGHT=001。無後綴的呼叫也顯示與連接埠 2 相同的 000/001。LIGHT=001 是因為尚未模擬的輸入位元 3 讀取為零,並非偵測到螢幕亮度。畫面也標示 LIGHT 1 IS NOT A HIT TEST。
ZAPPER-DEFAULT — 在 KUROSAKI 中,兩個連接埠都顯示 RAW=00、TRIGGER=000、LIGHT=001。無後綴的呼叫也顯示與連接埠 2 相同的 000/001。LIGHT=001 是因為尚未模擬的輸入位元 3 讀取為零,並非偵測到螢幕亮度。畫面也標示 LIGHT 1 IS NOT A HIT TEST。

完整範例程式

建置命令

powershell
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

csharp
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。

語法

c
u8 __zapper_light1(void);

回傳值

$4016 的位元 3 為 0 時傳回 1,為 1 時傳回 0。

使用條件與限制

這些 API 直接存取 $4016/$4017。不可與相同連接埠上的中斷驅動遊戲控制器、鍵盤或序列操作交錯執行。控制器移位位置也可能受影響。範例擷取輸入時停用 NMI,初始化顯示時再啟用。

讀取瞬時訊號。不發出選通、不等待影格、不偵測邊緣、不定位瞄準點,也不判斷命中。原始值與扳機/光線呼叫各自在自己的時刻讀取暫存器,因此訊號變化時數值可能不同。

光線輸入為低有效:位元 3 清零時傳回一。此轉換不偵測是否連接光線槍;遊戲控制器或未連接輸入也可能得到一。在畫面繪製目標不會使它變成命中測試 API。

位元轉換也以自行編寫的測試 ROM 進行單元測試,將暫存器讀取導向 RAM 中的測試值。測試涵蓋無關位元設為 1 時的 00/08/10/18 組合。此單元測試與未修改範例的執行結果及實際周邊裝置的行為分開看待。

這些影像是在 KUROSAKI 中原樣執行可下載 ROM 的結果。目前輸入匯流排模擬遊戲控制器,但不模擬鍵盤、光線槍或序列轉接器訊號。外部輸入位元讀為零,因此畫面不驗證這些裝置。實際按鍵輸入、光學感測及通訊尚未測試。

NESdev: Zapper

使用範例

c
#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 讀取為零,並非偵測到螢幕亮度。畫面也標示 LIGHT 1 IS NOT A HIT TEST。

__zapper_light1: ZAPPER-DEFAULT — 在 KUROSAKI 中,兩個連接埠都顯示 RAW=00、TRIGGER=000、LIGHT=001。無後綴的呼叫也顯示與連接埠 2 相同的 000/001。LIGHT=001 是因為尚未模擬的輸入位元 3 讀取為零,並非偵測到螢幕亮度。畫面也標示 LIGHT 1 IS NOT A HIT TEST。
ZAPPER-DEFAULT — 在 KUROSAKI 中,兩個連接埠都顯示 RAW=00、TRIGGER=000、LIGHT=001。無後綴的呼叫也顯示與連接埠 2 相同的 000/001。LIGHT=001 是因為尚未模擬的輸入位元 3 讀取為零,並非偵測到螢幕亮度。畫面也標示 LIGHT 1 IS NOT A HIT TEST。

完整範例程式

建置命令

powershell
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

csharp
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。

語法

c
u8 __zapper_light2(void);

回傳值

$4017 的位元 3 為 0 時傳回 1,為 1 時傳回 0。

使用條件與限制

這些 API 直接存取 $4016/$4017。不可與相同連接埠上的中斷驅動遊戲控制器、鍵盤或序列操作交錯執行。控制器移位位置也可能受影響。範例擷取輸入時停用 NMI,初始化顯示時再啟用。

讀取瞬時訊號。不發出選通、不等待影格、不偵測邊緣、不定位瞄準點,也不判斷命中。原始值與扳機/光線呼叫各自在自己的時刻讀取暫存器,因此訊號變化時數值可能不同。

光線輸入為低有效:位元 3 清零時傳回一。此轉換不偵測是否連接光線槍;遊戲控制器或未連接輸入也可能得到一。在畫面繪製目標不會使它變成命中測試 API。

位元轉換也以自行編寫的測試 ROM 進行單元測試,將暫存器讀取導向 RAM 中的測試值。測試涵蓋無關位元設為 1 時的 00/08/10/18 組合。此單元測試與未修改範例的執行結果及實際周邊裝置的行為分開看待。

這些影像是在 KUROSAKI 中原樣執行可下載 ROM 的結果。目前輸入匯流排模擬遊戲控制器,但不模擬鍵盤、光線槍或序列轉接器訊號。外部輸入位元讀為零,因此畫面不驗證這些裝置。實際按鍵輸入、光學感測及通訊尚未測試。

NESdev: Zapper

使用範例

c
#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 讀取為零,並非偵測到螢幕亮度。畫面也標示 LIGHT 1 IS NOT A HIT TEST。

__zapper_light2: ZAPPER-DEFAULT — 在 KUROSAKI 中,兩個連接埠都顯示 RAW=00、TRIGGER=000、LIGHT=001。無後綴的呼叫也顯示與連接埠 2 相同的 000/001。LIGHT=001 是因為尚未模擬的輸入位元 3 讀取為零,並非偵測到螢幕亮度。畫面也標示 LIGHT 1 IS NOT A HIT TEST。
ZAPPER-DEFAULT — 在 KUROSAKI 中,兩個連接埠都顯示 RAW=00、TRIGGER=000、LIGHT=001。無後綴的呼叫也顯示與連接埠 2 相同的 000/001。LIGHT=001 是因為尚未模擬的輸入位元 3 讀取為零,並非偵測到螢幕亮度。畫面也標示 LIGHT 1 IS NOT A HIT TEST。

完整範例程式

建置命令

powershell
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

csharp
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)的兩個光線槍位元。

語法

c
u8 __zapper_raw1(void);

回傳值

傳回 $4016 & 0x18,即 0x00、0x08、0x10 或 0x18。位元 4 是扳機線;位元 3 是未偵測到光線的原始訊號。

使用條件與限制

這些 API 直接存取 $4016/$4017。不可與相同連接埠上的中斷驅動遊戲控制器、鍵盤或序列操作交錯執行。控制器移位位置也可能受影響。範例擷取輸入時停用 NMI,初始化顯示時再啟用。

讀取瞬時訊號。不發出選通、不等待影格、不偵測邊緣、不定位瞄準點,也不判斷命中。原始值與扳機/光線呼叫各自在自己的時刻讀取暫存器,因此訊號變化時數值可能不同。

光線輸入為低有效:位元 3 清零時傳回一。此轉換不偵測是否連接光線槍;遊戲控制器或未連接輸入也可能得到一。在畫面繪製目標不會使它變成命中測試 API。

位元轉換也以自行編寫的測試 ROM 進行單元測試,將暫存器讀取導向 RAM 中的測試值。測試涵蓋無關位元設為 1 時的 00/08/10/18 組合。此單元測試與未修改範例的執行結果及實際周邊裝置的行為分開看待。

這些影像是在 KUROSAKI 中原樣執行可下載 ROM 的結果。目前輸入匯流排模擬遊戲控制器,但不模擬鍵盤、光線槍或序列轉接器訊號。外部輸入位元讀為零,因此畫面不驗證這些裝置。實際按鍵輸入、光學感測及通訊尚未測試。

NESdev: Zapper

使用範例

c
#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 讀取為零,並非偵測到螢幕亮度。畫面也標示 LIGHT 1 IS NOT A HIT TEST。

__zapper_raw1: ZAPPER-DEFAULT — 在 KUROSAKI 中,兩個連接埠都顯示 RAW=00、TRIGGER=000、LIGHT=001。無後綴的呼叫也顯示與連接埠 2 相同的 000/001。LIGHT=001 是因為尚未模擬的輸入位元 3 讀取為零,並非偵測到螢幕亮度。畫面也標示 LIGHT 1 IS NOT A HIT TEST。
ZAPPER-DEFAULT — 在 KUROSAKI 中,兩個連接埠都顯示 RAW=00、TRIGGER=000、LIGHT=001。無後綴的呼叫也顯示與連接埠 2 相同的 000/001。LIGHT=001 是因為尚未模擬的輸入位元 3 讀取為零,並非偵測到螢幕亮度。畫面也標示 LIGHT 1 IS NOT A HIT TEST。

完整範例程式

建置命令

powershell
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

csharp
case "__zapper_raw1": if (!Need(0)) return true; EmitAsm("LDA", Mem(0x4016)); EmitAsm("AND", Imm(0x18)); return true;
__zapper_raw2

若要從同一次暫存器讀取中解讀扳機與光線狀態,可一次讀取連接埠 2($4017)的兩個光線槍位元。

語法

c
u8 __zapper_raw2(void);

回傳值

傳回 $4017 & 0x18,即 0x00、0x08、0x10 或 0x18。位元 4 是扳機線;位元 3 是未偵測到光線的原始訊號。

使用條件與限制

這些 API 直接存取 $4016/$4017。不可與相同連接埠上的中斷驅動遊戲控制器、鍵盤或序列操作交錯執行。控制器移位位置也可能受影響。範例擷取輸入時停用 NMI,初始化顯示時再啟用。

讀取瞬時訊號。不發出選通、不等待影格、不偵測邊緣、不定位瞄準點,也不判斷命中。原始值與扳機/光線呼叫各自在自己的時刻讀取暫存器,因此訊號變化時數值可能不同。

光線輸入為低有效:位元 3 清零時傳回一。此轉換不偵測是否連接光線槍;遊戲控制器或未連接輸入也可能得到一。在畫面繪製目標不會使它變成命中測試 API。

位元轉換也以自行編寫的測試 ROM 進行單元測試,將暫存器讀取導向 RAM 中的測試值。測試涵蓋無關位元設為 1 時的 00/08/10/18 組合。此單元測試與未修改範例的執行結果及實際周邊裝置的行為分開看待。

這些影像是在 KUROSAKI 中原樣執行可下載 ROM 的結果。目前輸入匯流排模擬遊戲控制器,但不模擬鍵盤、光線槍或序列轉接器訊號。外部輸入位元讀為零,因此畫面不驗證這些裝置。實際按鍵輸入、光學感測及通訊尚未測試。

NESdev: Zapper

使用範例

c
#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 讀取為零,並非偵測到螢幕亮度。畫面也標示 LIGHT 1 IS NOT A HIT TEST。

__zapper_raw2: ZAPPER-DEFAULT — 在 KUROSAKI 中,兩個連接埠都顯示 RAW=00、TRIGGER=000、LIGHT=001。無後綴的呼叫也顯示與連接埠 2 相同的 000/001。LIGHT=001 是因為尚未模擬的輸入位元 3 讀取為零,並非偵測到螢幕亮度。畫面也標示 LIGHT 1 IS NOT A HIT TEST。
ZAPPER-DEFAULT — 在 KUROSAKI 中,兩個連接埠都顯示 RAW=00、TRIGGER=000、LIGHT=001。無後綴的呼叫也顯示與連接埠 2 相同的 000/001。LIGHT=001 是因為尚未模擬的輸入位元 3 讀取為零,並非偵測到螢幕亮度。畫面也標示 LIGHT 1 IS NOT A HIT TEST。

完整範例程式

建置命令

powershell
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

csharp
case "__zapper_raw2": if (!Need(0)) return true; EmitAsm("LDA", Mem(0x4017)); EmitAsm("AND", Imm(0x18)); return true;
__zapper_trigger

預設的連接埠 2 扳機讀取函式,呼叫與 __zapper_trigger2() 相同的輔助函式。

語法

c
u8 __zapper_trigger(void);

回傳值

將 $4017 的位元 4 正規化為 0 或 1。絕不會自動切換至連接埠 1。

使用條件與限制

這些 API 直接存取 $4016/$4017。不可與相同連接埠上的中斷驅動遊戲控制器、鍵盤或序列操作交錯執行。控制器移位位置也可能受影響。範例擷取輸入時停用 NMI,初始化顯示時再啟用。

讀取瞬時訊號。不發出選通、不等待影格、不偵測邊緣、不定位瞄準點,也不判斷命中。原始值與扳機/光線呼叫各自在自己的時刻讀取暫存器,因此訊號變化時數值可能不同。

結果是扳機訊號線的邏輯狀態,不保證扳機仍完全扣下;開關行為依裝置而異。遊戲要產生射擊事件時,請與前次讀值比較,偵測所需轉換。

位元轉換也以自行編寫的測試 ROM 進行單元測試,將暫存器讀取導向 RAM 中的測試值。測試涵蓋無關位元設為 1 時的 00/08/10/18 組合。此單元測試與未修改範例的執行結果及實際周邊裝置的行為分開看待。

這些影像是在 KUROSAKI 中原樣執行可下載 ROM 的結果。目前輸入匯流排模擬遊戲控制器,但不模擬鍵盤、光線槍或序列轉接器訊號。外部輸入位元讀為零,因此畫面不驗證這些裝置。實際按鍵輸入、光學感測及通訊尚未測試。

NESdev: Zapper

使用範例

c
#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 讀取為零,並非偵測到螢幕亮度。畫面也標示 LIGHT 1 IS NOT A HIT TEST。

__zapper_trigger: ZAPPER-DEFAULT — 在 KUROSAKI 中,兩個連接埠都顯示 RAW=00、TRIGGER=000、LIGHT=001。無後綴的呼叫也顯示與連接埠 2 相同的 000/001。LIGHT=001 是因為尚未模擬的輸入位元 3 讀取為零,並非偵測到螢幕亮度。畫面也標示 LIGHT 1 IS NOT A HIT TEST。
ZAPPER-DEFAULT — 在 KUROSAKI 中,兩個連接埠都顯示 RAW=00、TRIGGER=000、LIGHT=001。無後綴的呼叫也顯示與連接埠 2 相同的 000/001。LIGHT=001 是因為尚未模擬的輸入位元 3 讀取為零,並非偵測到螢幕亮度。畫面也標示 LIGHT 1 IS NOT A HIT TEST。

完整範例程式

建置命令

powershell
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

csharp
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。

語法

c
u8 __zapper_trigger1(void);

回傳值

$4016 的位元 4 設為 1 時傳回 1,否則傳回 0。

使用條件與限制

這些 API 直接存取 $4016/$4017。不可與相同連接埠上的中斷驅動遊戲控制器、鍵盤或序列操作交錯執行。控制器移位位置也可能受影響。範例擷取輸入時停用 NMI,初始化顯示時再啟用。

讀取瞬時訊號。不發出選通、不等待影格、不偵測邊緣、不定位瞄準點,也不判斷命中。原始值與扳機/光線呼叫各自在自己的時刻讀取暫存器,因此訊號變化時數值可能不同。

結果是扳機訊號線的邏輯狀態,不保證扳機仍完全扣下;開關行為依裝置而異。遊戲要產生射擊事件時,請與前次讀值比較,偵測所需轉換。

位元轉換也以自行編寫的測試 ROM 進行單元測試,將暫存器讀取導向 RAM 中的測試值。測試涵蓋無關位元設為 1 時的 00/08/10/18 組合。此單元測試與未修改範例的執行結果及實際周邊裝置的行為分開看待。

這些影像是在 KUROSAKI 中原樣執行可下載 ROM 的結果。目前輸入匯流排模擬遊戲控制器,但不模擬鍵盤、光線槍或序列轉接器訊號。外部輸入位元讀為零,因此畫面不驗證這些裝置。實際按鍵輸入、光學感測及通訊尚未測試。

NESdev: Zapper

使用範例

c
#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 讀取為零,並非偵測到螢幕亮度。畫面也標示 LIGHT 1 IS NOT A HIT TEST。

__zapper_trigger1: ZAPPER-DEFAULT — 在 KUROSAKI 中,兩個連接埠都顯示 RAW=00、TRIGGER=000、LIGHT=001。無後綴的呼叫也顯示與連接埠 2 相同的 000/001。LIGHT=001 是因為尚未模擬的輸入位元 3 讀取為零,並非偵測到螢幕亮度。畫面也標示 LIGHT 1 IS NOT A HIT TEST。
ZAPPER-DEFAULT — 在 KUROSAKI 中,兩個連接埠都顯示 RAW=00、TRIGGER=000、LIGHT=001。無後綴的呼叫也顯示與連接埠 2 相同的 000/001。LIGHT=001 是因為尚未模擬的輸入位元 3 讀取為零,並非偵測到螢幕亮度。畫面也標示 LIGHT 1 IS NOT A HIT TEST。

完整範例程式

建置命令

powershell
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

csharp
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。

語法

c
u8 __zapper_trigger2(void);

回傳值

$4017 的位元 4 設為 1 時傳回 1,否則傳回 0。

使用條件與限制

這些 API 直接存取 $4016/$4017。不可與相同連接埠上的中斷驅動遊戲控制器、鍵盤或序列操作交錯執行。控制器移位位置也可能受影響。範例擷取輸入時停用 NMI,初始化顯示時再啟用。

讀取瞬時訊號。不發出選通、不等待影格、不偵測邊緣、不定位瞄準點,也不判斷命中。原始值與扳機/光線呼叫各自在自己的時刻讀取暫存器,因此訊號變化時數值可能不同。

結果是扳機訊號線的邏輯狀態,不保證扳機仍完全扣下;開關行為依裝置而異。遊戲要產生射擊事件時,請與前次讀值比較,偵測所需轉換。

位元轉換也以自行編寫的測試 ROM 進行單元測試,將暫存器讀取導向 RAM 中的測試值。測試涵蓋無關位元設為 1 時的 00/08/10/18 組合。此單元測試與未修改範例的執行結果及實際周邊裝置的行為分開看待。

這些影像是在 KUROSAKI 中原樣執行可下載 ROM 的結果。目前輸入匯流排模擬遊戲控制器,但不模擬鍵盤、光線槍或序列轉接器訊號。外部輸入位元讀為零,因此畫面不驗證這些裝置。實際按鍵輸入、光學感測及通訊尚未測試。

NESdev: Zapper

使用範例

c
#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 讀取為零,並非偵測到螢幕亮度。畫面也標示 LIGHT 1 IS NOT A HIT TEST。

__zapper_trigger2: ZAPPER-DEFAULT — 在 KUROSAKI 中,兩個連接埠都顯示 RAW=00、TRIGGER=000、LIGHT=001。無後綴的呼叫也顯示與連接埠 2 相同的 000/001。LIGHT=001 是因為尚未模擬的輸入位元 3 讀取為零,並非偵測到螢幕亮度。畫面也標示 LIGHT 1 IS NOT A HIT TEST。
ZAPPER-DEFAULT — 在 KUROSAKI 中,兩個連接埠都顯示 RAW=00、TRIGGER=000、LIGHT=001。無後綴的呼叫也顯示與連接埠 2 相同的 000/001。LIGHT=001 是因為尚未模擬的輸入位元 3 讀取為零,並非偵測到螢幕亮度。畫面也標示 LIGHT 1 IS NOT A HIT TEST。

完整範例程式

建置命令

powershell
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

csharp
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
help
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
usage: kitaqfc kqhelp <KQxxxx>
usage
usage: kitaqfc symfind <pattern> [--map=<.map>] [--dbg=<.dbg>] [--regex] [--ignore-case] [--out=<file>|-]
usage
usage: kitaqfc src2asm <source.c:line> [--disasm=<dis.s>] [--funcsizes=<.funcsizes.txt>] [--context=N] [--out=<file>|-]
usage
usage: kitaqfc romdiff <old.nes> <new.nes> [--old-func=<.funcsizes.txt>] [--new-func=<.funcsizes.txt>] [--old-map=<.map>] [--new-map=<.map>] [--out=<file>|-]
usage
usage: kitaqfc template [out.c] [--out=<file.c>] [--overwrite] [--no-build-script] [--no-readme]
usage
usage: kitaqfc conventions [--out=<file>|--stdout]
usage
usage: kitaqfc snippet list | get <id> [--out=<file>|-] | emit <category|all> [--out=<file>|-]
usage
usage: kitaqfc fixhint [diag.json|stderr.log] [--in=<file>] [--out=<file>|-] [--max=N]
usage
usage: kitaqfc irsum [--ir=<syntax_tree_lowered.txt>] [--asm=<assembly_code.txt>] [--top=N] [--out=<file>|-]
usage
usage: kitaqfc devserver [--poll-ms=N] [--debounce-ms=N] [--once] [--max-builds=N] [--deps=<file>] <compile args...>
usage
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指令格式語法範例
00NOPIMPNOP
F3DIIMPDI
FBEIIMPEI
76HALTIMPHALT
10STOPIMM8STOP #1
C9RETIMPRET
D9RETIIMPRETI
C3JPABSJP 0xC000
E9JP_HLIMPJP_HL
18JRRELJR +target
20JR_NZRELJR_NZ +target
28JR_ZRELJR_Z +target
30JR_NCRELJR_NC +target
38JR_CRELJR_C +target
CDCALLABSCALL 0xC000
C4CALL_NZABSCALL_NZ 0xC000
CCCALL_ZABSCALL_Z 0xC000
D4CALL_NCABSCALL_NC 0xC000
DCCALL_CABSCALL_C 0xC000
C2JP_NZABSJP_NZ 0xC000
CAJP_ZABSJP_Z 0xC000
D2JP_NCABSJP_NC 0xC000
DAJP_CABSJP_C 0xC000
C7RST_00IMPRST_00
CFRST_08IMPRST_08
D7RST_10IMPRST_10
DFRST_18IMPRST_18
E7RST_20IMPRST_20
EFRST_28IMPRST_28
F7RST_30IMPRST_30
FFRST_38IMPRST_38
3ELD_A_IMMIMM8LD_A_IMM #1
06LD_B_IMMIMM8LD_B_IMM #1
0ELD_C_IMMIMM8LD_C_IMM #1
16LD_D_IMMIMM8LD_D_IMM #1
1ELD_E_IMMIMM8LD_E_IMM #1
26LD_H_IMMIMM8LD_H_IMM #1
2ELD_L_IMMIMM8LD_L_IMM #1
7FLD_A_AIMPLD_A_A
78LD_A_BIMPLD_A_B
79LD_A_CIMPLD_A_C
7ALD_A_DIMPLD_A_D
7BLD_A_EIMPLD_A_E
7CLD_A_HIMPLD_A_H
7DLD_A_LIMPLD_A_L
47LD_B_AIMPLD_B_A
40LD_B_BIMPLD_B_B
41LD_B_CIMPLD_B_C
42LD_B_DIMPLD_B_D
43LD_B_EIMPLD_B_E
44LD_B_HIMPLD_B_H
45LD_B_LIMPLD_B_L
4FLD_C_AIMPLD_C_A
48LD_C_BIMPLD_C_B
49LD_C_CIMPLD_C_C
4ALD_C_DIMPLD_C_D
4BLD_C_EIMPLD_C_E
4CLD_C_HIMPLD_C_H
4DLD_C_LIMPLD_C_L
57LD_D_AIMPLD_D_A
50LD_D_BIMPLD_D_B
51LD_D_CIMPLD_D_C
52LD_D_DIMPLD_D_D
53LD_D_EIMPLD_D_E
54LD_D_HIMPLD_D_H
55LD_D_LIMPLD_D_L
5FLD_E_AIMPLD_E_A
58LD_E_BIMPLD_E_B
59LD_E_CIMPLD_E_C
5ALD_E_DIMPLD_E_D
5BLD_E_EIMPLD_E_E
5CLD_E_HIMPLD_E_H
5DLD_E_LIMPLD_E_L
67LD_H_AIMPLD_H_A
60LD_H_BIMPLD_H_B
61LD_H_CIMPLD_H_C
62LD_H_DIMPLD_H_D
63LD_H_EIMPLD_H_E
64LD_H_HIMPLD_H_H
65LD_H_LIMPLD_H_L
6FLD_L_AIMPLD_L_A
68LD_L_BIMPLD_L_B
69LD_L_CIMPLD_L_C
6ALD_L_DIMPLD_L_D
6BLD_L_EIMPLD_L_E
6CLD_L_HIMPLD_L_H
6DLD_L_LIMPLD_L_L
EALD_MEM_AABSLD_MEM_A 0xC000
FALD_A_MEMABSLD_A_MEM 0xC000
E0LDH_MEM_ALDHLDH_MEM_A 0x40
F0LDH_A_MEMLDHLDH_A_MEM 0x40
E2LD_C_MEM_AIMPLD_C_MEM_A
F2LD_A_MEM_CIMPLD_A_MEM_C
77LD_HL_AIMPLD_HL_A
7ELD_A_HLIMPLD_A_HL
70LD_HL_BIMPLD_HL_B
71LD_HL_CIMPLD_HL_C
72LD_HL_DIMPLD_HL_D
73LD_HL_EIMPLD_HL_E
74LD_HL_HIMPLD_HL_H
75LD_HL_LIMPLD_HL_L
36LD_HL_REF_IMMIMM8LD_HL_REF_IMM #1
12LD_DE_AIMPLD_DE_A
1ALD_A_DEIMPLD_A_DE
02LD_BC_AIMPLD_BC_A
0ALD_A_BCIMPLD_A_BC
22LDI_HL_AIMPLDI_HL_A
2ALDI_A_HLIMPLDI_A_HL
32LDD_HL_AIMPLDD_HL_A
3ALDD_A_HLIMPLDD_A_HL
01LD_BC_IMMIMM16LD_BC_IMM #0xC000
11LD_DE_IMMIMM16LD_DE_IMM #0xC000
21LD_HL_IMMIMM16LD_HL_IMM #0xC000
31LD_SP_IMMIMM16LD_SP_IMM #0xC000
F9LD_SP_HLIMPLD_SP_HL
F8LD_HL_SP_IMMRELLD_HL_SP_IMM +target
E8ADD_SP_IMMRELADD_SP_IMM +target
08LD_MEM_SPIMM16LD_MEM_SP #0xC000
C5PUSH_BCIMPPUSH_BC
C1POP_BCIMPPOP_BC
D5PUSH_DEIMPPUSH_DE
D1POP_DEIMPPOP_DE
E5PUSH_HLIMPPUSH_HL
E1POP_HLIMPPOP_HL
F5PUSH_AFIMPPUSH_AF
F1POP_AFIMPPOP_AF
3CINC_AIMPINC_A
04INC_BIMPINC_B
0CINC_CIMPINC_C
14INC_DIMPINC_D
1CINC_EIMPINC_E
24INC_HIMPINC_H
2CINC_LIMPINC_L
34INC_HL_REFIMPINC_HL_REF
3DDEC_AIMPDEC_A
05DEC_BIMPDEC_B
0DDEC_CIMPDEC_C
15DEC_DIMPDEC_D
1DDEC_EIMPDEC_E
25DEC_HIMPDEC_H
2DDEC_LIMPDEC_L
35DEC_HL_REFIMPDEC_HL_REF
87ADD_AIMPADD_A
80ADD_BIMPADD_B
81ADD_CIMPADD_C
82ADD_DIMPADD_D
83ADD_EIMPADD_E
84ADD_HIMPADD_H
85ADD_LIMPADD_L
86ADD_HL_REFIMPADD_HL_REF
C6ADD_A_IMMIMM8ADD_A_IMM #1
8FADC_AIMPADC_A
88ADC_BIMPADC_B
89ADC_CIMPADC_C
8AADC_DIMPADC_D
8BADC_EIMPADC_E
8CADC_HIMPADC_H
8DADC_LIMPADC_L
8EADC_HL_REFIMPADC_HL_REF
CEADC_IMMIMM8ADC_IMM #1
97SUB_AIMPSUB_A
90SUB_BIMPSUB_B
91SUB_CIMPSUB_C
92SUB_DIMPSUB_D
93SUB_EIMPSUB_E
94SUB_HIMPSUB_H
95SUB_LIMPSUB_L
96SUB_HL_REFIMPSUB_HL_REF
D6SUB_IMMIMM8SUB_IMM #1
9FSBC_AIMPSBC_A
98SBC_BIMPSBC_B
99SBC_CIMPSBC_C
9ASBC_DIMPSBC_D
9BSBC_EIMPSBC_E
9CSBC_HIMPSBC_H
9DSBC_LIMPSBC_L
9ESBC_HL_REFIMPSBC_HL_REF
DESBC_IMMIMM8SBC_IMM #1
A7AND_AIMPAND_A
A0AND_BIMPAND_B
A1AND_CIMPAND_C
A2AND_DIMPAND_D
A3AND_EIMPAND_E
A4AND_HIMPAND_H
A5AND_LIMPAND_L
A6AND_HL_REFIMPAND_HL_REF
E6AND_IMMIMM8AND_IMM #1
B7OR_AIMPOR_A
B0OR_BIMPOR_B
B1OR_CIMPOR_C
B2OR_DIMPOR_D
B3OR_EIMPOR_E
B4OR_HIMPOR_H
B5OR_LIMPOR_L
B6OR_HL_REFIMPOR_HL_REF
F6OR_IMMIMM8OR_IMM #1
AFXOR_AIMPXOR_A
A8XOR_BIMPXOR_B
A9XOR_CIMPXOR_C
AAXOR_DIMPXOR_D
ABXOR_EIMPXOR_E
ACXOR_HIMPXOR_H
ADXOR_LIMPXOR_L
AEXOR_HL_REFIMPXOR_HL_REF
EEXOR_IMMIMM8XOR_IMM #1
BFCP_AIMPCP_A
B8CP_BIMPCP_B
B9CP_CIMPCP_C
BACP_DIMPCP_D
BBCP_EIMPCP_E
BCCP_HIMPCP_H
BDCP_LIMPCP_L
BECP_HL_REFIMPCP_HL_REF
FECP_IMMIMM8CP_IMM #1
03INC_BCIMPINC_BC
0BDEC_BCIMPDEC_BC
13INC_DEIMPINC_DE
1BDEC_DEIMPDEC_DE
23INC_HLIMPINC_HL
2BDEC_HLIMPDEC_HL
33INC_SPIMPINC_SP
3BDEC_SPIMPDEC_SP
09ADD_HL_BCIMPADD_HL_BC
19ADD_HL_DEIMPADD_HL_DE
29ADD_HL_HLIMPADD_HL_HL
39ADD_HL_SPIMPADD_HL_SP
07RLCAIMPRLCA
17RLAIMPRLA
0FRRCAIMPRRCA
1FRRAIMPRRA
CBPREFIX_CBIMPPREFIX_CB
2FCPLIMPCPL
3FCCFIMPCCF
37SCFIMPSCF
27DAAIMPDAA