KITAQ ENTWICKLUNGSSYSTEM
BENUTZERHANDBUCH 2026.09

KITAQ SERIES • REFERENZAUSGABE

KITAQFC

C-Programmierung für Famicom

Von der ersten Codezeile zur Ausführung, Beobachtung und erneuten Prüfung.
Quellenstand: 14. September 2026 · Englische Referenzausgabe: 14. September 2026

Nativer Rust-Compiler und Hilfswerkzeuge

Mit Rust 1.85 oder neuer lassen sich Compiler und sämtliche Hilfswerkzeuge für Windows, Linux, macOS ARM und macOS Intel bauen. Die nativen Programme benötigen kein .NET; die Werkzeuge für Grafikdaten benötigen auch weder Python noch Pillow.

Die Windows-Programme liegen im Repository-Stammverzeichnis, die Linux- und macOS-Programme in den unten aufgeführten bin/-Ordnern. Bewahren Sie lib/ und die Lizenzhinweise zusammen mit den Werkzeugen auf. Unter Linux/macOS setzen Sie mit chmod +x das Ausführungsrecht und ergänzen den PATH, oder verwenden den vollständigen Pfad.

OSkitaqfc
Windows x64kitaqfc.exe
Linux x64bin/Linux-X64/kitaqfc
macOS ARM64bin/macOS-ARM64/kitaqfc
macOS Intelbin/macOS-X64/kitaqfc

kitaqfc · Nativer Rust-Compiler und Hilfswerkzeuge · GitHub Actions

Native Builds und Ausführungsprüfungen waren unter Windows, Linux, macOS ARM und macOS Intel erfolgreich. KITAQGB bestand pro Umgebung 48 Tests und 395 Werkzeugprüfungen, KITAQFC 55 und 401. Rust 1.85 wurde ebenfalls getestet. PUBLIC_DISTRIBUTION.json enthält die Hashwerte der installierten Programme und die Herkunft der Prüfergebnisse. Die öffentlichen GitHub-Actions-Workflows bauen und testen diese Quellen unabhängig.

Gespeicherte Referenzausgaben prüfen ROM-Bytes, Diagnosen und Werkzeugformate. Frühere C#-Emulatorprüfungen bleiben historische Nachweise mit ihren ursprünglichen Quellfingerabdrücken. Sie belegen nicht automatisch sämtliche Rust-APIs, echte Hardware oder einen vollständigen Spielstart über das FDS-BIOS. Das nicht verfügbare ursprüngliche PNG-Skript wurde nach seiner Spezifikation neu implementiert; Bytegleichheit mit ihm kann nicht bestätigt werden.

--attach wird aus Kompatibilitätsgründen akzeptiert, verbindet aber keinen verwalteten Debugger mit dem nativen Programm. Verwenden Sie für das PC-Programm einen nativen Debugger.

KITAQGB · SHA-256 · KITAQFC · SHA-256

Beispiele auf den einzelnen Systemen bauen

Die PowerShell-Skripte verwenden das Windows-Programm im Stammverzeichnis. Unter Linux/macOS übergeben Sie dieselben C-Eingaben und Optionen an den nativen Compiler oder verwenden PowerShell 7. CHR-Grafik und C-Quellen sind getrennte Eingaben; font.chr bewahrt die Schrift der Beispiele.

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

Aktuelle Compiler- und Bibliotheksausgabe — 4. Oktober 2026

Verwenden Sie kitaqfc.exe und lib aus demselben Checkout. Starten Sie im übergeordneten Verzeichnis das unten angegebene Release-Skript; das Projekt kopiert die EXE in das Stammverzeichnis des Repositorys. BINARY_BUILD.json enthält die Hashes der Compilerquellen und der EXE sowie die Prüfungen dieses Builds. Lange Diagnosezeilen werden auf 200 Zeichen mit Auslassungszeichen begrenzt.

Der aktuelle C#-Build wurde mit der vorherigen Ausgabe anhand von 29 Eingaben verglichen. Fünfzehn akzeptierte Eingaben erzeugten identische ROM-Bytes; die übrigen vierzehn Eingaben erzeugten identische Ablehnungsdiagnosen. Zwei weitere ROMs liefen je vier Frames in KUROSAKI: Entitätsvergabe, Callbacks und Wiederverwendung von Plätzen; vorzeichenbehaftete Berechnungen, KQBody3D-Layout und Audiowarteschlangenkonstanten. Der Bericht enthält 18 erwartete und beobachtete RAM-Bytes. Audiowiedergabe und sämtliche APIs werden damit nicht geprüft; frühere Regressionen sind in BINARY_BUILD.json gesondert gekennzeichnet.

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

Quellbeispiele und Ausführungsnachweise

1. KITAQFC und der GB-Compiler

KITAQFC nutzt das Frontend von KITAQGB, um Code für die 6502-CPU-Familie des NES/Famicom zu erzeugen. Es wandelt keine GB-ROM in eine NES-ROM um. Schreiben Sie Ihr Programm passend zu Grafik, Ton, Speicher und Mapper des Zielgeräts.

KITAQFC unterstützt Strukturkopien, gewöhnliche Funktionsaufrufe sowie for-, while- und do-while-Schleifen. Eine do-while-Schleife führt ihren Rumpf mindestens einmal aus und prüft danach die Bedingung. continue springt zu dieser abschließenden Prüfung; break verlässt die Schleife. Ein switch wählt eine case-Konstante im Bereich 0 bis 255 oder den default-Zweig, wenn kein case passt. Der Auswahlwert wird genau einmal ausgewertet. break verlässt die innerste Schleife oder das innerste switch; continue innerhalb eines switch setzt die umgebende Schleife mit ihrer nächsten Iteration fort.

2. Voraussetzungen und Build

Mit Rust 1.85 oder neuer lassen sich Compiler und sämtliche Hilfswerkzeuge für Windows, Linux, macOS ARM und macOS Intel bauen. Die nativen Programme benötigen kein .NET; die Werkzeuge für Grafikdaten benötigen auch weder Python noch Pillow.

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

Die Windows-Programme liegen im Repository-Stammverzeichnis, die Linux- und macOS-Programme in den unten aufgeführten bin/-Ordnern. Bewahren Sie lib/ und die Lizenzhinweise zusammen mit den Werkzeugen auf. Unter Linux/macOS setzen Sie mit chmod +x das Ausführungsrecht und ergänzen den PATH, oder verwenden den vollständigen Pfad.

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

Die PowerShell-Skripte verwenden das Windows-Programm im Stammverzeichnis. Unter Linux/macOS übergeben Sie dieselben C-Eingaben und Optionen an den nativen Compiler oder verwenden PowerShell 7. CHR-Grafik und C-Quellen sind getrennte Eingaben; font.chr bewahrt die Schrift der Beispiele.

3. Das erste Programm

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

Das Beispiel zeigt HELLO WORLD und 042. Seine Hilfsfunktion m_wait gibt die VRAM-Warteschlange zur Ausführung frei, wartet auf NMI und stellt das Scrolling wieder her. Zugriffe über PPUADDR verändern den internen Scrollzustand. Ohne dessen Wiederherstellung kann Text an einen Bildschirmrand verschoben erscheinen. Merken Sie sich die Reihenfolge: Anzeige aus, Ressourcen vorbereiten, Anzeige ein, mit NMI synchronisieren.

4. Einführung in die Sprache

Die Anweisungen und Ausdrücke des GB-Bands bilden einen gemeinsamen Ausgangspunkt. FC akzeptiert unsigned char und unsigned short; core.h definiert u8, u16, s8 und s16. fc.h ist ein Sammelheader. Funktionen ohne Argumente dürfen hier void main(void) verwenden.

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

Halten Sie Ganzzahlen innerhalb ihrer 8- oder 16-Bit-Wertebereiche. Array-Indizes beginnen bei null. fc_aggregate.c zeigt Funktionen, Zeiger und Strukturen, fc_arithmetic.c die Arithmetik und fc_control.c Schleifen. CGB-Register und reine GB-Intrinsics gehören nicht in ein FC-Programm.

Ergebnisse und Auswertung von Ausdrücken

Die Vergleichsoperatoren ==, !=, <, <=, >, >= und die logischen Operatoren !, &&, || liefern 0 für falsch und 1 für wahr. Das Ergebnis lässt sich in einem u16 speichern, als Argument übergeben, zurückgeben oder in Berechnungen verwenden. Beispielsweise ergibt score = 500 + (lives != 0); den Wert 501, wenn noch ein Leben vorhanden ist, andernfalls 500.

&& wertet den rechten Operanden nicht aus, wenn der linke null ist. || überspringt den rechten Operanden, wenn der linke ungleich null ist. Bei pointer != 0 && pointer->active != 0 verhindert ein Nullpointer den Memberzugriff. Beide Bytes eines 16-Bit-Werts gehen in die Wahrheitsprüfung ein; 256 ist daher wahr. Die bitweisen Operatoren & und | haben keine Kurzschlussauswertung.

++value liefert den aktualisierten Wert, value++ den ursprünglichen. Diese Operatoren sind auch auf Arrayelemente, dereferenzierte Pointer und Strukturmember anwendbar. buffer[index()]++ ruft index() genau einmal auf. Bei u16 *p rückt p++ um zwei Bytes zum nächsten Element vor; (*p)++ erhöht dagegen den Wert am Ziel des Pointers.

sizeof(array) liefert die Größe des gesamten Arrays in Bytes; sizeof(pointer) ist 2. Für u16 values[9]; ist sizeof(values) gleich 18. Das gilt auch für ROM-Arrays, lokale Arrays und Arraymember. sizeof(function()) untersucht den Rückgabetyp, ohne die Funktion aufzurufen.

Deklaration und Definition einer Funktion müssen in Parametertypen und Reihenfolge übereinstimmen. Die Parameternamen dürfen verschieden sein; im Funktionsrumpf gelten die Namen der Definition. Beispielsweise kann u8 next(u8 input); als u8 next(u8 value) { return (u8)(value + 1); } definiert werden. Parameter und lokale Variablen verdecken gleichnamige globale Variablen.

Die Auswahl von Arrays oder Zeichenketten mit ?: ergibt einen Pointer auf den gewählten Elementtyp. Dieser lässt sich direkt übergeben, etwa mit show(ready ? "READY" : "WAIT");. Bei den u16-Arrays a und b rückt (ready ? a : b) + 1 um zwei Bytes zum zweiten Element des gewählten Arrays vor. Das Array wird dabei nicht kopiert.

condition ? yes : no wertet zuerst die Bedingung und danach nur den gewählten Zweig aus. Bedingung und Zweige dürfen Schiebeoperationen mit variabler Bitanzahl enthalten. Beispielsweise wählt on = (pattern & (0x80 >> bit)) != 0 ? 4 : 2; abhängig vom bezeichneten Bit vier oder zwei. Funktionsaufrufe in einem durch && oder || übersprungenen rechten Operanden werden ebenfalls nicht ausgeführt. Ein continue in einer for-Schleife führt vor der erneuten Bedingungsprüfung den Aktualisierungsausdruck einmal aus; bei while und do ... while springt es zur Bedingung.

5. Schleifen und Zustandssteuerung

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(); }

Diese Fragmente zeigen eine Schleife mit mindestens einer Aktualisierung und eine Verzweigung, die den Handler des aktuellen Zustands auswählt. Definieren Sie update, condition, state und die Zustandshandler in Ihrem Programm. fc_control.c enthält ein vollständiges Schleifenbeispiel; das Frame- und Szenenbeispiel der Bibliothek zeigt ein vollständiges ROM zur Szenenverwaltung.

Registrieren Sie Szenen-, Entity- und System-Callbacks mit den in ihren Deklarationen geforderten Argument- und Rückgabetypen. Die Bibliotheken rufen die registrierten Handler bei den entsprechenden Aktualisierungs-, Zeichen- oder Frame-Warteoperationen auf. Beachten Sie die ROM-Bank- und Mapping-Vorgaben jeder API. Setzen Sie keine Unterstützung für Rekursion oder variadische Funktionen wie auf einem Desktop voraus.

6. Speicher und PPU

Der interne CPU-RAM des NES liegt bei 0x0000–0x07FF. Seine Spiegelungen oberhalb von 0x0800 sind kein zusätzlicher RAM. Der 6502-Stack belegt Seite 1; OAM-Schattenpuffer und Warteschlangen reservieren weitere Bereiche. --nes-local-ram=START:LENGTH und --nes-temp-ram=START:LENGTH sind fortgeschrittene Einstellungen, die eine Prüfung der Map erfordern.

Die PPU besitzt einen eigenen Adressraum. CHR liefert die Pixelmuster, Nametables platzieren Kacheln, Attributtabellen wählen Palettengruppen aus und Paletten enthalten Farbcodes. Hintergrundattribute gelten gewöhnlich für 16 × 16 Pixel große Bereiche. Sie verhalten sich deshalb anders als GB-Kachelattribute.

7. NMI und VRAM-Warteschlange

NMI ist der Interrupt an der Grenze eines Bildzyklus. Große direkte PPU-Schreibzugriffe während der Darstellung können das Bild stören. Initialisieren Sie direkt bei ausgeschalteter Darstellung. Für normale Aktualisierungen verwenden Sie __vramq_put, __vramq_copy, __vramq_fill und die Freigabe der Warteschlange.

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

Prüfen Sie freie Kapazität und Gültigkeitsdauer der Quelldaten. Der Standard-NMI verarbeitet die Warteschlange. Ein eigener __nes_nmi muss die nötige Warteschlangenausführung, OAM-Arbeit und Registersicherung beibehalten.

8. Mapper und ROM-Anordnung

AuswahlTypischer Einstieg
nromKleine Übungen mit fester ROM-Belegung
uxrom / cnrom / axromEinfache PRG- oder CHR-Umschaltung
mmc1 / mmc3 / mmc5Größere Programme und Mapper-Funktionen
vrc6 / vrc7 / fme7Banking und zugehörige Erweiterungen
fdsAusgabe eines Diskettenabbilds

Dies sind Compiler-Auswahlmöglichkeiten, keine Tabelle vollständiger Hardware- oder Emulatorunterstützung. --board=surom512 wählt eine bestimmte MMC1-Platinenanordnung. Eine Datei lediglich auf 512 KiB aufzufüllen stellt diese Anordnung nicht her. Nutzen Sie ergänzend KUROSAKIs Platinenprüfung.

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

Prüfen Sie die Platinenanforderungen für --battery / --no-battery, CHR-Kapazität, PRG-Belegung und bankübergreifende Aufrufe. Testen Sie nach Änderungen an Mapper oder Spiegelungsmodus auch Start, Scrolling und Datenumschaltung.

8.1 CHR-RAM für veränderbare Grafik

--nes-chr-ram erzeugt eine Cartridge-ROM, die 8 KiB beschreibbaren CHR-RAM nutzt. Die ROM-Datei enthält keine CHR-Daten; deshalb muss das Programm die Tile-Muster während der Initialisierung an die PPU übertragen. Der Renderer wire3d.c verwendet diesen Modus.

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 oder --chr-rom=tiles.chr bindet vorbereitete Muster als CHR-ROM ein. Keine der beiden Optionen lässt sich mit --nes-chr-ram kombinieren. Auch das CNROM-Profil unterstützt den CHR-RAM-Modus nicht. Prüfen Sie, ob der gewählte Mapper und die Platine den benötigten CHR-RAM bereitstellen.

Ohne CHR-Optionen bindet der Compiler einen leeren CHR-ROM mit 8 KiB ein. Wählen Sie für Beispiele, die Muster zur Laufzeit übertragen, ausdrücklich --nes-chr-ram. CHR-RAM ist vom CPU-seitigen PRG-RAM getrennt; der Vorbereitungspuffer für die Drahtgitterdarstellung benötigt zusätzlich einen eigenen PRG-RAM-Bereich.

Normale indizierte Lesezugriffe und C-Pointerreferenzen auf ROM-Arrays platzieren diese Arrays in der gemeinsamen Bank 0, sofern #pragma fixed_bank ihre Lage nicht ausdrücklich festlegt. Dadurch bleiben die Daten sichtbar, wenn der Aufrufer in einer umschaltbaren Bank läuft. Übergeben Sie für explizite Fernzugriffe im selben Aufruf den unveränderten Arraynamen und __bankof für genau diesen Namen, etwa __farpeek8(__bankof(table), table). Eine Abfrage der Banknummer allein fordert keine Platzierung in der gemeinsamen Bank an. Bei ausdrücklich festgelegten Bankdaten und Referenzen innerhalb von Assemblercode müssen Sie das korrekte Mapping selbst sicherstellen. Diese Regel prüft die Platzierung anhand der Syntax; sie führt keine Pointerflussanalyse durch. Die Kapazitätsgrenze der gemeinsamen Bank gilt weiterhin.

9. FDS, Erweiterungssound und Zubehör

FDS umfasst Dateiplatzierung auf Diskette, Startvorgang, Overlays und Speichern. Lesen Sie fds_manifest_sample.json und die FDS-Header. Ein benötigtes BIOS müssen Sie in Ihrer eigenen Laufzeitumgebung bereitstellen; die öffentliche Distribution enthält keines.

Ein VRC6- oder VRC7-Soundaufruf ändert nicht die Mapper-Einstellung der ROM. Wählen Sie den zur Sounderweiterung passenden Mapper. Prüfen Sie bei Zubehör getrennt, ob Eingaben gelesen werden, die Verbindung besteht und der gewöhnliche Controllerpfad beeinflusst wird.

10. Diagnosen und Build-Ergebnisse

KQ-Diagnosen und Entwicklerbefehle wie symfind, src2asm und romdiff ähneln ihren GB-Gegenstücken. Einige geerbte GB-Hilfeoptionen stehen möglicherweise nicht für implementierte NES-Funktionen. Das FC-Verzeichnis wird deshalb getrennt aus FC-Quellen und Headern erstellt.

Warnungen wie KQ2421 für direkte PPU-Operationen können auch während der Initialisierung bei ausgeschalteter Anzeige auftreten. Ändern Sie eine sichere Initialisierung nicht allein, um eine Warnung zu beseitigen. Prüfen Sie den Darstellungszeitpunkt und die Ausführungsprotokolle. Keine Fehler und keine Warnungen sind unterschiedliche Ergebnisse.

11. Praxisbeispiel: ein Spiel mit KI entwickeln

Tragen Sie die Anforderungen ein und geben Sie den vollständigen Prompt an Ihre KI weiter. Er umfasst die Implementierung, Emulator-Tests, die Analyse mit SARAKURA und die erneute Prüfung nach Korrekturen.

· Markdown herunterladen

Tragen Sie die Anforderungen ein und geben Sie dieses gesamte Dokument an die KI weiter. Die Befehle setzen voraus, dass die Repositories kitaqgb, kitaqfc, kokura, kurosaki, sarakura und kitaq-docs sowie das Projekt game-gb oder game-fc im selben übergeordneten Ordner liegen. Führen Sie die Befehle dort aus und passen Sie die Pfade an die tatsächliche Umgebung an.

Anforderungen

  • Spieltitel: <ausfüllen>
  • Genre und zentrale Spielmechanik: <ausfüllen>
  • Steuerung sowie Erfolgs- und Misserfolgsbedingungen: <ausfüllen>
  • Erforderliche Bildschirme, Level, Gegner und Gegenstände: <ausfüllen>
  • Grafikstil, Musik und Geräusche: <ausfüllen; bereitgestellte Dateien angeben>
  • Speichern, Kommunikation, Zusatzgeräte und weitere Anforderungen: <ausfüllen oder keine>
  • Projektordner: <ausfüllen>
  • Bedingungen für die Weitergabe: <etwa eigener Code und eigene Medien, die unter MIT veröffentlicht werden können>
  • Ziel: <NES/Famicom-Steckmodul oder FDS>
  • Mapper, ROM-Größe und Mirroring: <festlegen oder anhand der Anforderungen wählen>
  • Videonorm und Leistungsziel: <etwa NTSC und 60 Aktualisierungen der Spiellogik pro Sekunde>

Arbeitsauftrag

Setzen Sie das Spiel mit KITAQFC und seinen Bibliotheken um. Verwenden Sie KUROSAKI zum Ausführen und Debuggen und SARAKURA zum Auswerten der Diagnosen sowie zum Vergleich vor und nach einer Korrektur.

Wiederholen Sie diesen Ablauf, bis die Abnahmekriterien erfüllt sind: Spezifikation konkretisieren → kleine Änderung umsetzen → bauen → Eingaben ausführen und beobachten → Ursache untersuchen → korrigieren → unter gleichen Bedingungen erneut testen. Ein Plan, ausgegebener Quellcode oder ein erfolgreicher Compilerlauf allein schließen den Auftrag nicht ab.

Umgebung und Abnahmekriterien klären

  1. Lesen Sie die Anweisungen im Arbeitsverzeichnis, READMEs, HTML-Handbücher sowie Header und Implementierungen der verwendeten Bibliotheken. Erfassen Sie die Pfade der Programme und ihre Versionen oder SHA-256-Werte. Prüfen Sie Befehle anhand der tatsächlichen --help-Ausgabe und APIs anhand des Quellcodes.
  2. Legen Sie überprüfbare Kriterien für Eingaben, Bild, Ton, Spielverlauf und Aktualisierungsrate fest. Beispiele: START drücken und loslassen beginnt das Spiel; eine Kollision kostet ein Leben; die Pause schaltet die vorgesehenen Töne stumm, Fortsetzen nimmt die Wiedergabe wieder auf.
  3. Fragen Sie nur bei wesentlichen Unklarheiten nach. Treffen Sie übliche, rückgängig zu machende Implementierungsentscheidungen selbstständig. Schwächen Sie Anforderungen und Abnahmekriterien nicht ab.
  4. Führen Sie zunächst ein kleines mitgeliefertes Beispiel durch Compiler, Emulator und SARAKURA. Das prüft deren Zusammenspiel, nicht die Fertigstellung des beauftragten Spiels.

Eine kleine spielbare Fassung umsetzen

  • Ziehen Sie für ein kleines Spiel NROM in Betracht. Wählen Sie MMC3 oder einen anderen Mapper, wenn Größe oder Bankwechsel dies erfordern. Prüfen Sie benötigte Platinenfunktionen mit inspect-rom, mapper-info, audit-board und dem Quellcode; der Mappername allein belegt keine Unterstützung.
  • Planen Sie PRG/CHR-Größe, CHR-ROM oder CHR-RAM, Mirroring, feste Bänke, Interruptvektoren und Speicher-RAM für Spielstände. Vergleichen Sie nach Optimierungen oder Bankänderungen Header und tatsächliche Belegung. --nes-local-ram verwendet internes CPU-RAM in $0000–$07FF; vermeiden Sie Überschneidungen mit Zero Page, Stack, OAM-Puffern und Laufzeit- oder Bibliotheksbereichen.
  • Nutzen Sie den C-Dialekt von KITAQFC, FC-Bibliotheken und void main(void). Setzen Sie keine Kompatibilität mit GB-APIs voraus. Manche Headereinträge sind reine Deklarationen: suchen Sie die Implementierungen und binden Sie die erforderlichen .c-Dateien ein.
  • Berücksichtigen Sie PPU-Register, NMI, OAM DMA, Spritegrenzen pro Bildzeile, Scrolling, Mirroring, Attributtabellen und APU/DMC-Verhalten. Freier Warteschlangenplatz ist nicht die VRAM-Kapazität der PPU; begrenzen Sie die Arbeit je NMI.
  • Wandeln Sie die bereitgestellte eigene Schrift ascii.c in FC-CHR um und prüfen Sie CHR, Paletten, Namens- und Attributtabellen. Bei FDS sind Plattenzugriff, Speichern und BIOS-Anforderungen gesondert zu prüfen; setzen Sie keine Startbedingungen eines Steckmoduls voraus.
  • Verbinden Sie zunächst Start, Titelbild, steuerbare Spielfigur, Erfolg oder Misserfolg und Neustart. Erweitern Sie danach den Inhalt.
  • Bewahren Sie bearbeitbare Originale von Grafik, Musik und Geräuschen sowie deren Erzeugungsschritte auf. Prüfen Sie, dass der Build tatsächlich die exportierten Daten verwendet.
  • Schreiben Sie Codekommentare auf Englisch und Fortschrittsberichte auf Deutsch. Lassen Sie die Standardberichte von SARAKURA auf Englisch.

Jeden Build seiner Ausführung zuordnen

Trennen Sie Ausgaben nach Iteration, etwa mit out/iter-001. Protokollieren Sie Befehle, Rückgabecodes und Hashes von Quellcode, Medien, Werkzeugen, ROM und Metadaten. Führen Sie nach einem fehlgeschlagenen Build niemals eine alte ROM aus. Speicherbelegungspläne, Quellcodezuordnungen und Debuginformationen müssen aus demselben Build wie die ROM stammen.

Das folgende Beispiel prüft NROM ohne Eingaben. Stellen Sie main.c, erforderliche Implementierungen und die CHR-Datei bereit; wählen Sie den passenden Mapper. Übergeben Sie Compiler-Buildmetadaten nicht an KUROSAKIs --kitaqfc-debug, ohne das erwartete Format zu prüfen.

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 Frames ohne Eingaben sind nur eine erste Kontrolle. Ergänzen Sie Spielszenarien mit aufeinanderfolgenden Aktionen, bevor Sie das Spiel als funktionsfähig einstufen.

Geordnete Eingaben reproduzieren

  • --pad1 und --pad2 verwenden rohe NES-Bitmasken: A=1, B=2, SELECT=4, START=8, UP=16, DOWN=32, LEFT=64, RIGHT=128. Verwechseln Sie diese nicht mit den BTN_*-Werten der Bibliothek.
  • run --pad1 setzt eine feste Eingabe. Erstellen Sie für Aktionsfolgen ein Replay, das Drücken, Halten und Loslassen unterscheidet. Lesen Sie die Definitionen von Replay und ReplayFrame. replay-record in der öffentlichen CLI zeichnet neutrale Eingaben auf, keine von einem Menschen gespielte Partie.
  • Prüfen Sie ROM-SHA-256, Replay-Zuordnung und Framebereich selbst. replay-run --verify vergleicht einen erwarteten Endhash nur, wenn dieser vorhanden ist; es prüft weder Spielverhalten noch ROM-Identität umfassend. Überschreiben Sie Sollwerte nicht ungeprüft mit Istwerten, nur damit ein Test besteht.
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'

Bereiten Sie das Replay für die getestete ROM vor. Die öffentliche CLI bietet bei replay-run kein --emit-diagnostics. Erfinden Sie diese Option nicht und geben Sie CPU-Traces nicht als Diagnoseereignisse weiter. Um geordnete Eingaben mit SARAKURA zu analysieren, erstellen Sie im Projekt ein Testprogramm mit den öffentlichen kurosaki-core-APIs RunOptions.replay_frames und diagnostic_events_from_trace_and_report. Verwenden Sie dieselbe ROM, dasselbe Replay und dieselben Framebedingungen; erzeugen Sie JSONL aus Trace und Diagnosebericht genau dieses Laufs. Prüfen Sie Tracekonfiguration und gespeicherten Bereich und vergleichen Sie Bilder und Beobachtungen des Testprogramms mit dem CLI-Replay. Stellen Sie Diagnosen ohne Eingaben nicht als Nachweis für ein Spielszenario dar. Fehlt die erforderliche Umgebung, kennzeichnen Sie diese Prüfung als unvollständig.

Bild, Ton, Zustand und Leistung prüfen

  • Speichern Sie Eingabeszenarien mit getrenntem Drücken, Halten und Loslassen. Durchlaufen Sie alle vorgesehenen Wege: Start, Spielbeginn, Bewegung, Aktionen, Kollisionen, Scrolling, Levelwechsel, Spielende, Neustart, Pause und gegebenenfalls Speichern oder Kommunikation.
  • Bewahren Sie PNGs relevanter Frames, Eingaben, Laufberichte, Diagnose-JSONL, WAVs und nötige Zustands- oder Speicherbeobachtungen auf. Prüfen Sie erreichte Frames und Abbruchgrund. Öffnen Sie die Bilder tatsächlich; ein einzelner Screenshot belegt weder Bewegung noch Eingabereaktion. Vergleichen Sie Zähler, Positionen und Zustandswechsel mit Sollwerten; prüfen Sie Bildschirmränder, Tile- und Attributgrenzen und Szenen mit vielen Sprites.
  • Prüfen Sie Musik, Geräusche, gleichzeitige Wiedergabe, Aussetzer, Pause und Fortsetzen. Eine erzeugte WAV-Datei allein belegt keinen korrekten Klang. Ist Anhören nicht möglich, trennen Sie ausgeführte Wellenform- und Zahlenprüfungen von unbestätigten Höreigenschaften.
  • Messen Sie aufwendige Szenen, Arbeit der Ziel-CPU, Spielaktualisierungen und Transfers; auf FC auch die NMI-Arbeit. Emulatordurchsatz auf dem Host ist weder Spielaktualisierungsrate noch Nachweis für reale Hardwaregeschwindigkeit. Fortsetzen mit --allow-unimplemented belegt keine Unterstützung der fehlenden Funktion.

Analysieren, korrigieren und erneut testen

  • Übergeben Sie SARAKURA die Buildmetadaten der geprüften ROM und Diagnose-JSONL aus dem zugehörigen Lauf. Ein CPU-Trace oder gewöhnlicher Laufbericht ersetzt dies nicht. --frames legt Analysebedingungen fest; SARAKURA führt weder ROMs aus noch ändert es automatisch Quellcode.
  • Lesen Sie report.html, ai_diagnostics.json, repair_prompt.md und retest_plan.json. Gleichen Sie Diagnosen mit Reproduktionsschritten, Bildern, Ton und Quellcode ab. Trennen Sie vermutete Quellstellen oder Ursachen von bestätigten Tatsachen und normale Warteschleifen von Hängern. Bewerten Sie Warnungen einzeln und dokumentieren Sie nicht unterstützte Ereignisse sowie Analysegrenzen. Verbergen Sie Warnungen nicht durch Filter und verkürzen Sie Tests nicht, um ein Bestehen zu erreichen.
  • Reduzieren Sie Fehler auf minimale Reproduktionen, beheben Sie die Ursache und bauen Sie neu. Liegt der Fehler im Compiler oder Emulator, grenzen Sie ihn vom Spielcode ab und ergänzen Sie eine Regressionsprüfung für die Werkzeugkorrektur.
  • Wiederholen Sie Tests mit gleichen Eingaben, Zufallsstartwerten, Hardware- und Videomodi, Mappern, Beobachtungsframes und Diagnoseeinstellungen. Nutzen Sie für jede ROM passende Metadaten; verwenden Sie nach Änderungen an Code oder RAM-Belegung nicht blind alte Speicherzustände weiter.
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

Nutzen Sie Diagnosedifferenzen gemeinsam mit den Abnahmekriterien für Steuerung, Grafik und Ton. Wiederholt sich derselbe Fehler, überprüfen Sie Belege und Hypothese, statt beliebige weitere Änderungen vorzunehmen.

Abschlusskriterien und Lieferumfang

Führen Sie alle Pflichtszenarien erneut mit der finalen ROM aus, die aus den gelieferten Quellen und Einstellungen gebaut wurde. Unverwundbarkeit, automatische Testeingaben oder ein anderer Mapper allein prüfen kein normales Spiel in der Endfassung. Liefern Sie eine Zuordnung von Anforderungen zu Tests, Gründe für verbleibende Warnungen und klare Angaben zu ungeprüften oder nicht unterstützten Punkten. Kennzeichnen Sie ausdrücklich, wenn nicht auf physischer Hardware getestet wurde.

Liefern Sie Quellcode, Kennungen von Werkzeugen und Bibliotheken, bearbeitbare Medien, reproduzierbare Build- und Testskripte, ROM, abschließende Nachweise und eine README mit Einrichtung, Steuerung und bekannten Grenzen. Fügen Sie bei Bedarf Replays und das Testprogramm hinzu. Veröffentlichen oder versenden Sie Dateien nur im ausdrücklich erlaubten Umfang. Entfernen Sie unnötige Zwischenbuilds und temporäre Traces nach der Prüfung, bewahren Sie jedoch Quellen, Medien, Endergebnisse und erforderliche Regressionsnachweise auf.

Verhindern Umgebung oder Berechtigungen eine Pflichtprüfung, nennen Sie die genauen Reproduktionsschritte und die nötige Maßnahme. Kennzeichnen Sie die Arbeit nicht als abgeschlossen.

Quellpfade

Die Compilerquellen und die Projektdatei liegen im gleichnamigen Unterverzeichnis des Repositorys. Die ausführbare Datei liegt im Hauptverzeichnis, die Bibliotheken liegen in lib/. Die Verzeichnisübersicht nennt die Projektpfade und Build-Voraussetzungen.

Vollständige Beispielprogramme

Enthalten sind alle Programme, die gemeinsamen Header, die bereitgestellte ascii.c und die daraus konvertierten Ressourcen. Namen mit m_ am Anfang bezeichnen Hilfsfunktionen für diese Handbücher. Lesen Sie jeden Build-Befehl zusammen mit dem erwarteten Ergebnis und dem Prüfprotokoll. Führen Sie die Befehle im übergeordneten Ordner der nebeneinanderliegenden Repositories aus.

Skript zur Stapelkompilierung / Ergebnisse und Bildschirmaufnahmen

Den mitgelieferten Zeichensatz anzeigen fc_font.c

Zweck: Zeigt, welche Buchstaben, Ziffern und Satzzeichen die mitgelieferte eigens erstellte Schrift enthält und wie sie gezeichnet werden.

Funktionsweise: m_init bereitet Anzeige und Schrift vor. m_text zeichnet jede Zeile; m_put zeichnet das doppelte Anführungszeichen mit Zeichencode 34.

Bedienung und erwartetes Ergebnis: Prüfen Sie, ob 26 Großbuchstaben, 26 Kleinbuchstaben, 10 Ziffern und 30 Satzzeichen erscheinen und alle Zeichen gut lesbar sind. Es ist keine Eingabe erforderlich.

Den mitgelieferten Zeichensatz anzeigen: Prüfen Sie, ob 26 Großbuchstaben, 26 Kleinbuchstaben, 10 Ziffern und 30 Satzzeichen erscheinen und alle Zeichen gut lesbar sind. Es ist keine Eingabe erforderlich.
Anzeigebeispiel: Prüfen Sie, ob 26 Großbuchstaben, 26 Kleinbuchstaben, 10 Ziffern und 30 Satzzeichen erscheinen und alle Zeichen gut lesbar sind. Es ist keine Eingabe erforderlich.

Quelltext herunterladen / Prüfstatus

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();  }
}
Die erste Textausgabe fc_hello.c

Zweck: Zeigt den grundlegenden Ablauf zum Starten einer ROM und zum Zeichnen von Text und einer Zahl im Hintergrund.

Funktionsweise: Nach m_init zeichnet m_text die Begrüßung und m_number formatiert 42. Die abschließende Schleife wartet auf Frames und lässt das Bild sichtbar.

Bedienung und erwartetes Ergebnis: Der Bildschirm zeigt HELLO WORLD und 042. Die führende Null entsteht durch die Dezimaldarstellung mit drei Stellen.

Die erste Textausgabe: Der Bildschirm zeigt HELLO WORLD und 042. Die führende Null entsteht durch die Dezimaldarstellung mit drei Stellen.
Anzeigebeispiel: Der Bildschirm zeigt HELLO WORLD und 042. Die führende Null entsteht durch die Dezimaldarstellung mit drei Stellen.

Quelltext herunterladen / Prüfstatus

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();  }
}
Mit breiterem Zwischentyp rechnen fc_arithmetic.c

Zweck: Zeigt, wie ein Zwischenergebnis gespeichert wird, das nicht in acht Bit passt.

Funktionsweise: (u16)200 + 100 ergibt 300. Das Programm teilt das Ergebnis durch 10 und wandelt es anschließend zur Anzeige in u8 um. Beachten Sie, dass die Typverbreiterung vor der Addition erfolgt.

Bedienung und erwartetes Ergebnis: 030 steht für das berechnete Ergebnis 30.

Mit breiterem Zwischentyp rechnen: 030 steht für das berechnete Ergebnis 30.
Anzeigebeispiel: 030 steht für das berechnete Ergebnis 30.

Quelltext herunterladen / Prüfstatus

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();  }
}
Verzweigungen und Schleifen fc_control.c

Zweck: Erprobt for, continue, while und if/else in einer kleinen Berechnung.

Funktionsweise: Die for-Schleife addiert 0 bis 7, überspringt dabei 3 und erhält 25. Die while-Schleife zählt bis 32 weiter; das folgende if weist bei Übereinstimmung den Wert 42 zu.

Bedienung und erwartetes Ergebnis: 042 ist das Endergebnis. Das Programm verwendet eine if/else-Verzweigung.

Verzweigungen und Schleifen: Das Endergebnis ist 042. Dieses Programm verwendet eine if/else-Verzweigung.
Anzeigebeispiel: 042 ist das Endergebnis. Das Programm verwendet eine if/else-Verzweigung.

Quelltext herunterladen / Prüfstatus

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();  }
}
Funktionen, Arrays, Strukturen und Zeiger fc_aggregate.c

Zweck: Zeigt, wie Werte gruppiert und über Funktionen und Zeiger angesprochen werden.

Funktionsweise: Das Programm kopiert einen Point mit (10,20) und schreibt dann über einen Zeiger 1 bis 4 in ein Array. Es addiert das Funktionsergebnis 30, die Arraywerte 1, 2 und 4 sowie die Konstante 5.

Bedienung und erwartetes Ergebnis: Die Summe wird als 042 angezeigt. Verfolgen Sie die Strukturkopie, die Schreibzugriffe auf ptr[i] und den Rückgabewert der Funktion.

Funktionen, Arrays, Strukturen und Zeiger: Die Summe wird als 042 angezeigt. Verfolgen Sie die Strukturkopie, die Schreibzugriffe auf ptr[i] und den Rückgabewert der Funktion.
Anzeigebeispiel: Die Summe wird als 042 angezeigt. Verfolgen Sie die Strukturkopie, die Schreibzugriffe auf ptr[i] und den Rückgabewert der Funktion.

Quelltext herunterladen / Prüfstatus

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();  }
}
Bitflags und Pufferkopie fc_bits_memory.c

Zweck: Packt Flags in ein Byte und kopiert einen Puffer in einen getrennten Speicherbereich.

Funktionsweise: __memset löscht vier Bytes. Das Programm setzt Bit 3, invertiert Bit 1 und kopiert anschließend alle vier Bytes mit __memcpy.

Bedienung und erwartetes Ergebnis: Das erste Zielbyte wird als 010 angezeigt; Bit 3 trägt 8 und Bit 1 trägt 2 bei.

Bitflags und Pufferkopie: Das erste Zielbyte wird als 010 angezeigt; Bit 3 trägt 8 und Bit 1 trägt 2 bei.
Anzeigebeispiel: Das erste Zielbyte wird als 010 angezeigt; Bit 3 trägt 8 und Bit 1 trägt 2 bei.

Quelltext herunterladen / Prüfstatus

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();  }
}
Neue Tastendrücke zählen fc_input.c

Zweck: Unterscheidet einen neuen Tastendruck von einer weiterhin gedrückten Taste.

Funktionsweise: In jedem Frame wird input_update aufgerufen. Der Zähler steigt nur, wenn input_pressed(BTN_A) wahr ist.

Bedienung und erwartetes Ergebnis: Der Anfangswert ist 000. Drücken Sie A, um 001 zu erhalten; Gedrückthalten erhöht den Zähler nicht weiter. Loslassen und erneut drücken ergibt 002. Der Bytezähler springt nach 255 auf 000 zurück.

Neue Tastendrücke zählen: Der Anfangswert ist 000. Drücken Sie A, um 001 zu erhalten; Gedrückthalten erhöht den Zähler nicht weiter. Loslassen und erneut drücken ergibt 002. Der Bytezähler springt nach 255 auf 000 zurück.
Anzeigebeispiel: Der Anfangswert ist 000. Drücken Sie A, um 001 zu erhalten; Gedrückthalten erhöht den Zähler nicht weiter. Loslassen und erneut drücken ergibt 002. Der Bytezähler springt nach 255 auf 000 zurück.

Quelltext herunterladen / Prüfstatus

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-Festkommawerte multiplizieren fc_fixed.c

Zweck: Zeigt die Umwandlung von Ganzzahlen in Q8.8, die Multiplikation und die Rückwandlung in eine Ganzzahl.

Funktionsweise: fix_from_int wandelt 3 und 4 in Q8.8 um. fix_mul multipliziert sie, und fix_to_int wandelt das Produkt wieder in eine Ganzzahl um.

Bedienung und erwartetes Ergebnis: 012 bedeutet 3 × 4 = 12. Es ist die umgewandelte Ganzzahl, nicht der intern mit 256 skalierte Wert.

Q8.8-Festkommawerte multiplizieren: 012 bedeutet 3 × 4 = 12. Es ist die umgewandelte Ganzzahl, nicht der intern mit 256 skalierte Wert.
Anzeigebeispiel: 012 bedeutet 3 × 4 = 12. Es ist die umgewandelte Ganzzahl, nicht der intern mit 256 skalierte Wert.

Quelltext herunterladen / Prüfstatus

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();  }
}
Aus einem festen Objektpool zuweisen fc_entity.c

Zweck: Erzeugt ein Objekt aus einer festen Menge von Speicherplätzen, ohne dynamische Speicherzuweisung.

Funktionsweise: entity_init setzt den Pool zurück. entity_create erzeugt Typ 1 bei (20,30); das Programm prüft auf die Fehler-ID 0xFF, bevor es vx auf 2 setzt.

Bedienung und erwartetes Ergebnis: 001 ist die Anzahl aktiver Objekte, keine ID oder Koordinate. Das Beispiel aktualisiert und zeichnet das Objekt nicht; daher erscheint keine bewegte Figur.

Aus einem festen Objektpool zuweisen: 001 ist die Anzahl aktiver Objekte, keine ID oder Koordinate. Das Beispiel aktualisiert und zeichnet das Objekt nicht; daher erscheint keine bewegte Figur.
Anzeigebeispiel: 001 ist die Anzahl aktiver Objekte, keine ID oder Koordinate. Das Beispiel aktualisiert und zeichnet das Objekt nicht; daher erscheint keine bewegte Figur.

Quelltext herunterladen / Prüfstatus

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();  }
}
Einen Diagnosewert im RAM aufzeichnen fc_debug.c

Zweck: Speichert einen benannten Spielwert getrennt von der Bildschirmanzeige in einem Diagnosepuffer.

Funktionsweise: Das Programm initialisiert die Diagnose, setzt die Framekennung auf 1 und zeichnet den Wert 42 unter dem Namen HP auf.

Bedienung und erwartetes Ergebnis: 001 ist die Anzahl der Trace-Einträge. HP=42 liegt im Diagnosepuffer; der Bildschirm allein zeigt oder bestätigt diesen Inhalt nicht.

Einen Diagnosewert im RAM aufzeichnen: 001 ist die Anzahl der Trace-Einträge. HP=42 liegt im Diagnosepuffer; der Bildschirm allein zeigt oder bestätigt diesen Inhalt nicht.
Anzeigebeispiel: 001 ist die Anzahl der Trace-Einträge. HP=42 liegt im Diagnosepuffer; der Bildschirm allein zeigt oder bestätigt diesen Inhalt nicht.

Quelltext herunterladen / Prüfstatus

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();  }
}
Einen Positionsverlauf speichern fc_chain.c

Zweck: Zeigt, wie ein Positionsverlauf in einem Ringpuffer gespeichert wird.

Funktionsweise: chain_init bindet ein Array mit Platz für vier Punkte ein. Anschließend zeichnet chain_push_head (20,30) auf.

Bedienung und erwartetes Ergebnis: 042 ist eine feste Abschlussmarkierung, keine Koordinate oder Anzahl. Prüfen Sie trail und points für den gespeicherten Verlauf. Dieses Programm zeichnet keinen nachfolgenden Körper.

Einen Positionsverlauf speichern: 042 ist eine feste Abschlussmarkierung, keine Koordinate oder Anzahl. Prüfen Sie trail und points für den gespeicherten Verlauf. Dieses Programm zeichnet keinen nachfolgenden Körper.
Anzeigebeispiel: 042 ist eine feste Abschlussmarkierung, keine Koordinate oder Anzahl. Prüfen Sie trail und points für den gespeicherten Verlauf. Dieses Programm zeichnet keinen nachfolgenden Körper.

Quelltext herunterladen / Prüfstatus

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();  }
}
Den Framezähler der Bibliothek lesen fc_system.c

Zweck: Zeigt, wie Wartezeiten in Frames den Framezähler der Bibliothek beeinflussen.

Funktionsweise: Nach system_init ruft das Programm system_wait_vblank zweimal auf und zeigt system_get_frame8 an.

Bedienung und erwartetes Ergebnis: Das Ergebnis ist 002. Die Warteschleife verwendet die Handbuch-Hilfsfunktion m_wait; der angezeigte Wert steigt daher nicht weiter.

Den Framezähler der Bibliothek lesen: Das Ergebnis ist 002. Die Warteschleife verwendet die Handbuch-Hilfsfunktion m_wait; der angezeigte Wert steigt daher nicht weiter.
Anzeigebeispiel: Das Ergebnis ist 002. Die Warteschleife verwendet die Handbuch-Hilfsfunktion m_wait; der angezeigte Wert steigt daher nicht weiter.

Quelltext herunterladen / Prüfstatus

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();  }
}
Die aktuelle Szenen-ID wählen fc_scene.c

Zweck: Zeigt, wie eine ID zur Unterscheidung von Szenen wie Titelbildschirm und Spiel gewählt wird.

Funktionsweise: Das Programm registriert zwei mit null initialisierte SceneDef-Einträge und wählt mit scene_change die ID 1.

Bedienung und erwartetes Ergebnis: 001 ist die aktuelle von scene_get_current gelieferte ID. In diesem Beispiel sind weder Szenen-Callbacks noch Übergangszeichnungen registriert.

Die aktuelle Szenen-ID wählen: 001 ist die aktuelle von scene_get_current gelieferte ID. In diesem Beispiel sind weder Szenen-Callbacks noch Übergangszeichnungen registriert.
Anzeigebeispiel: 001 ist die aktuelle von scene_get_current gelieferte ID. In diesem Beispiel sind weder Szenen-Callbacks noch Übergangszeichnungen registriert.

Quelltext herunterladen / Prüfstatus

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();  }
}
Gebrochene Q5.3-Bewegung aufsummieren fc_subpixel.c

Zweck: Behält Subpixelbewegungen als Rest bei und überträgt ganze Pixel auf die Position.

Funktionsweise: Ausgehend von X=40 addiert die Schleife achtmal 2/8 Pixel. Eine Rechtsverschiebung liefert die ganzen Pixel; eine Maske behält den Rest bei.

Bedienung und erwartetes Ergebnis: Die Gesamtbewegung beträgt zwei Pixel; deshalb erscheint X=42 als 042. Dieses Beispiel manueller Integration behandelt positive Bewegung; negative Bewegung benötigt einen entsprechenden Übertrag beim Subtrahieren.

Gebrochene Q5.3-Bewegung aufsummieren: Die Gesamtbewegung beträgt zwei Pixel; deshalb erscheint X=42 als 042. Dieses Beispiel manueller Integration behandelt positive Bewegung; negative Bewegung benötigt einen entsprechenden Übertrag beim Subtrahieren.
Anzeigebeispiel: Die Gesamtbewegung beträgt zwei Pixel; deshalb erscheint X=42 als 042. Dieses Beispiel manueller Integration behandelt positive Bewegung; negative Bewegung benötigt einen entsprechenden Übertrag beim Subtrahieren.

Quelltext herunterladen / Prüfstatus

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();  }
}
Den NES-Hintergrund scrollen fc_scroll.c

Zweck: Verschiebt den Hintergrund durch Aktualisieren der PPU-Scrollposition.

Funktionsweise: Nach __scroll_set(0,0) erhöht das Programm phase einmal pro Frame und verwendet den Wert als horizontale Position.

Bedienung und erwartetes Ergebnis: Das Wort SCROLL ändert seine horizontale Position. phase springt von 255 auf 0 zurück. Dies ist kein Beispiel für eine breite Karte über mehrere Nametables.

Den NES-Hintergrund scrollen: Das Wort SCROLL ändert seine horizontale Position. phase springt von 255 auf 0 zurück. Dies ist kein Beispiel für eine breite Karte über mehrere Nametables.
Anzeigebeispiel: Das Wort SCROLL ändert seine horizontale Position. phase springt von 255 auf 0 zurück. Dies ist kein Beispiel für eine breite Karte über mehrere Nametables.

Quelltext herunterladen / Prüfstatus

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); }
}
Eine Tile-Aktualisierung über NMI anwenden fc_vram_queue.c

Zweck: Reicht aus dem Hauptprogramm eine PPU-Aktualisierung ein, die anschließend im NMI-Ablauf angewendet wird.

Funktionsweise: __vramq_put reiht Tile 52 für Nametable-Adresse 0x2103 ein; __vramq_commit reicht die Aktualisierung ein. Diese Adresse entspricht Tile-Koordinate (3,8).

Bedienung und erwartetes Ergebnis: Bei Hintergrundkoordinate (3,8) erscheint eine einzelne 4. Tile 52 ist in der mitgelieferten Schrift die Ziffer 4.

Eine Tile-Aktualisierung über NMI anwenden: Bei Hintergrundkoordinate (3,8) erscheint eine einzelne 4. Tile 52 ist in der mitgelieferten Schrift die Ziffer 4.
Anzeigebeispiel: Bei Hintergrundkoordinate (3,8) erscheint eine einzelne 4. Tile 52 ist in der mitgelieferten Schrift die Ziffer 4.

Quelltext herunterladen / Prüfstatus

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();  }
}
Ein NES-OAM-Sprite anzeigen fc_sprite.c

Zweck: Bereitet Spritefarben und OAM-Daten vor, um ein Bild unabhängig vom Hintergrund zu zeichnen.

Funktionsweise: Das Programm lädt die Spritepalette, setzt Eintrag 0 auf Position (70,80), Tile 65 und Attribute 0, aktiviert die Darstellung und wiederholt den OAM-DMA.

Bedienung und erwartetes Ergebnis: Ein unbewegtes A erscheint als Sprite. Das Beispiel zeigt OAM-Einrichtung und -Übertragung, keine Animation.

Ein NES-OAM-Sprite anzeigen: Ein unbewegliches A erscheint als Sprite. Das Beispiel zeigt Einrichtung und Übertragung des OAM, keine Animation.
Anzeigebeispiel: Ein unbewegtes A erscheint als Sprite. Das Beispiel zeigt OAM-Einrichtung und -Übertragung, keine Animation.

Quelltext herunterladen / Prüfstatus

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(); }
}
Einen APU-Pulston abspielen fc_sound.c

Zweck: Stellt Steuer-, Timer- und Längenwerte des eingebauten NES-Pulskanals ein.

Funktionsweise: Nach nes_apu_init ruft das Programm nes_sfx_square1(0xBF,400,20) auf. Steuerwert 0xBF wählt konstante Lautstärke und hält den Längenzähler an, sodass der Ton anhält. 20 ist ein Index der Längentabelle, keine Dauer von 20 Frames.

Bedienung und erwartetes Ergebnis: Während 042 angezeigt wird, erklingt ein anhaltender Pulston. Beenden Sie ihn mit nes_apu_silence_all. Die Bildschirmanzeige allein bestätigt keine Tonausgabe.

Einen APU-Pulston abspielen: Während 042 angezeigt wird, erklingt ein anhaltender Pulston. Beenden Sie ihn mit nes_apu_silence_all. Die Bildschirmanzeige allein bestätigt keine Tonausgabe.
Anzeigebeispiel: Während 042 angezeigt wird, erklingt ein anhaltender Pulston. Beenden Sie ihn mit nes_apu_silence_all. Die Bildschirmanzeige allein bestätigt keine Tonausgabe.

Quelltext herunterladen / Prüfstatus

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();  }
}

Referenz der Compiler-Intrinsics

161 Einträge. Öffnen Sie einen Eintrag, um Syntax, Argumente, Quellhinweise und ein Anwendungsbeispiel zu sehen. Aufruffragmente benötigen zusätzliche Initialisierung und sind keine eigenständigen ROMs. Die Beispiele zeigen, wie vorbereitete Werte an die API übergeben werden; sie belegen keine Prüfung am Gerät.

audio_vblank.h — Musikwiedergabe im VBlank-Interrupt

Die Vierkanalmusik läuft in der NMI weiter. Initialisieren Sie den Treiber, legen Sie ein Musikstück fest und füllen Sie die Warteschlange vor. Aktivieren Sie dann NMI für die Wiedergabe und füllen Sie aus dem Hauptprogramm nach. Die NMI liest eine RAM-Warteschlange. Bei rechtzeitigem Nachfüllen reicht dadurch wenig RAM für einen langen Stream. Jeder Kanal hat unabhängige HOLD/STOP-Befehle.

Ein Datensatz besteht aus fünf Bytes: delay, CH1, CH2, CH3, CH4. Eine Dauer von 1 bis 255 gibt an, wie viele NMI-Ticks der neue Zustand anhält. Null beendet die Wiedergabe; die übrigen vier Bytes werden ignoriert. Tonwerte von 0 bis 71 entsprechen C2 bis B7 (24=C4). Rauschwerte von 0 bis 15 wählen eine Periode im langen Modus, 16 bis 31 im kurzen Modus. Für jeden Kanal gilt: 255=NES_AUDIO_HOLD behält den Zustand bei, 254=NES_AUDIO_STOP schaltet ihn stumm.

Dieser Vierkanaltreiber ist auf NTSC abgestimmt: CH1=Impuls 1, CH2=Impuls 2, CH3=Dreieck und CH4=Rauschen, mit den Indizes 0 bis 3. DMC und Erweiterungsaudio werden nicht wiedergegeben. Verwenden Sie ihn nicht gleichzeitig mit direkten APU-Zugriffen, etwa den Effekten in audio.c. Es gibt weder eine PAL-Tonhöhentabelle noch eine PAL-Tempokorrektur.

Läuft die Dauer ab, ohne dass ein weiterer BGM-Datensatz vorliegt, verstummen die BGM-Noten. Nach Eingang neuer Daten geht es bei einer NMI weiter. Ein unabhängiger SFX läuft weiter. Gezählt werden Eintritte in den Datenmangel, nicht alle leeren Frames. Das Einreihen reiner HOLD-Werte stellt die vorherigen BGM-Noten nicht wieder her. Füllen Sie normalerweise pro Frame nach und halten Sie vor längeren Arbeiten ausreichend Wiedergabezeit in der Warteschlange bereit.

fds.h — FDS-Diskettenoperationen

__fds_available

Meldet, ob das Übersetzungsziel FDS ist, etwa zur Anzeige zielspezifischer Informationen in gemeinsamem Spielcode.

Syntax

c
u8 __fds_available(void);

Rückgabewert

1 bei einem FDS-Build, sonst 0. Dies ist keine Laufzeiterkennung von Peripherie.

Bedingungen und Grenzen

Diese Abfrage ist auch für Cartridge-Ziele wie NROM gültig. Eine FDS-exklusive API innerhalb von if (__fds_available()) wird dadurch nicht von der Zielprüfung ausgenommen. Strukturieren Sie den Quelltext passend zum jeweiligen Ziel.

Die Aufnahme nutzt KUROSAKIs direktes Laden der Disketten-Bootdateien ins RAM. Das Beispiel ruft keine BIOS-Routine auf und prüft weder Dateitransfers noch Diskettenwechsel oder Hardwarestart. Die leere Testfirmware ist kein Ersatz-BIOS. Das Beispiel aktualisiert die Anzeige per NMI und deaktiviert maskierbare IRQs, die diese Abfragen nicht benötigen.

Das Buildbeispiel erzeugt eine .fds-Datei mit nativem Layout ohne zusätzliche Bootdateien für den Genehmigungsbildschirm. Diese Konfiguration prüft KUROSAKIs Direktstart; das Startverfahren einer echten Diskette muss separat validiert werden.

Anwendungsbeispiel

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(); }
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Der FDS-Build zeigt 001, der NROM-Build 000. Derselbe C-Quelltext liefert je nach Übersetzungsziel einen anderen Wert, unabhängig davon, ob eine Diskette eingelegt ist.

__fds_available: AVAILABLE-FDS-DEFAULT — Der FDS-Build zeigt 001, der NROM-Build 000. Derselbe C-Quelltext liefert je nach Übersetzungsziel einen anderen Wert, unabhängig davon, ob eine Diskette eingelegt ist.
AVAILABLE-FDS-DEFAULT — Der FDS-Build zeigt 001, der NROM-Build 000. Derselbe C-Quelltext liefert je nach Übersetzungsziel einen anderen Wert, unabhängig davon, ob eine Diskette eingelegt ist.
__fds_available: AVAILABLE-NROM-DEFAULT — Der FDS-Build zeigt 001, der NROM-Build 000. Derselbe C-Quelltext liefert je nach Übersetzungsziel einen anderen Wert, unabhängig davon, ob eine Diskette eingelegt ist.
AVAILABLE-NROM-DEFAULT — Der FDS-Build zeigt 001, der NROM-Build 000. Derselbe C-Quelltext liefert je nach Übersetzungsziel einen anderen Wert, unabhängig davon, ob eine Diskette eingelegt ist.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/fds.h:7

Deklarationshinweise im Original
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

Prüft die Diskettenanwesenheit sofort, sodass ein Einlegehinweis einmal pro Frame ohne Blockieren abfragen kann.

Syntax

c
u8 __fds_disk_ready(void);

Rückgabewert

Gibt 1 zurück, wenn Bit 0 von $4032 gelöscht ist, und 0, wenn es gesetzt ist.

Bedingungen und Grenzen

Bauen Sie mit --mapper=fds. Bei einem Cartridge-Mapper wird der Aufruf einer FDS-exklusiven Funktion außer __fds_available mit KQFC2420 abgewiesen.

Die Prüfung verwendet Bit 0 von $4032, das die Anwesenheit einer Diskette meldet. Sie prüft weder die Laufwerksbereitschaft in Bit 1 noch Dateiexistenz oder Ladeabschluss. „ready“ darf hier nicht als Freigabe zum Start einer Übertragung verstanden werden.

KUROSAKI verwendet ein Teilmodell des FDS-Controllers. Das Beispiel beobachtet Anwesenheit 1, Schutzmaske 0 und Status 0. Die Aufnahme belegt kein Verhalten bei Auswurf, geschützten Medien oder CRC-Fehlern.

Die Aufnahme nutzt KUROSAKIs direktes Laden der Disketten-Bootdateien ins RAM. Das Beispiel ruft keine BIOS-Routine auf und prüft weder Dateitransfers noch Diskettenwechsel oder Hardwarestart. Die leere Testfirmware ist kein Ersatz-BIOS. Das Beispiel aktualisiert die Anzeige per NMI und deaktiviert maskierbare IRQs, die diese Abfragen nicht benötigen.

Das Buildbeispiel erzeugt eine .fds-Datei mit nativem Layout ohne zusätzliche Bootdateien für den Genehmigungsbildschirm. Diese Konfiguration prüft KUROSAKIs Direktstart; das Startverfahren einer echten Diskette muss separat validiert werden.

NESdev: FDS-Hardwareregister

Anwendungsbeispiel

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(); }
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Unter PRESENT / PROTECT / STATUS zeigt der Bildschirm 1 / 0 / 0, unter COMPLETED PRESENCE WAITS danach 2. Beide Anwesenheits-Warteaufrufe sind zurückgekehrt. Das ist kein Erfolgsindikator für einen Dateitransfer. Darunter steht NO FILE TRANSFER TESTED.

__fds_disk_ready: STATUS-FDS-DEFAULT — Unter PRESENT / PROTECT / STATUS zeigt der Bildschirm 1 / 0 / 0, unter COMPLETED PRESENCE WAITS danach 2. Beide Anwesenheits-Warteaufrufe sind zurückgekehrt. Das ist kein Erfolgsindikator für einen Dateitransfer. Darunter steht NO FILE TRANSFER TESTED.
STATUS-FDS-DEFAULT — Unter PRESENT / PROTECT / STATUS zeigt der Bildschirm 1 / 0 / 0, unter COMPLETED PRESENCE WAITS danach 2. Beide Anwesenheits-Warteaufrufe sind zurückgekehrt. Das ist kein Erfolgsindikator für einen Dateitransfer. Darunter steht NO FILE TRANSFER TESTED.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/fds.h:9

Deklarationshinweise im Original
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

Liest das FDS-Hardwarestatusregister bei $4030. Dies ist kein gespeicherter Fehler des letzten Diskettenvorgangs.

Syntax

c
u8 __fds_error(void);

Rückgabewert

Gibt den vollständigen Acht-Bit-Wert von $4030 zurück. Bewahren Sie bei der Behandlung von BIOS-Fehlercodes den Rückgabewert von __fds_load_file() oder __fds_save_file() auf.

Bedingungen und Grenzen

Bauen Sie mit --mapper=fds. Bei einem Cartridge-Mapper wird der Aufruf einer FDS-exklusiven Funktion außer __fds_available mit KQFC2420 abgewiesen.

KUROSAKI verwendet ein Teilmodell des FDS-Controllers. Das Beispiel beobachtet Anwesenheit 1, Schutzmaske 0 und Status 0. Die Aufnahme belegt kein Verhalten bei Auswurf, geschützten Medien oder CRC-Fehlern.

Das Lesen des Status quittiert auch Interruptanforderungen. Fragen Sie nicht unkontrolliert ab, wenn ein anderer Handler denselben Status benötigt. KUROSAKI löscht bei diesem Lesezugriff sein ausstehendes FDS-IRQ-Flag.

Die Aufnahme nutzt KUROSAKIs direktes Laden der Disketten-Bootdateien ins RAM. Das Beispiel ruft keine BIOS-Routine auf und prüft weder Dateitransfers noch Diskettenwechsel oder Hardwarestart. Die leere Testfirmware ist kein Ersatz-BIOS. Das Beispiel aktualisiert die Anzeige per NMI und deaktiviert maskierbare IRQs, die diese Abfragen nicht benötigen.

Das Buildbeispiel erzeugt eine .fds-Datei mit nativem Layout ohne zusätzliche Bootdateien für den Genehmigungsbildschirm. Diese Konfiguration prüft KUROSAKIs Direktstart; das Startverfahren einer echten Diskette muss separat validiert werden.

NESdev: FDS-Hardwareregister

Anwendungsbeispiel

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(); }
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Unter PRESENT / PROTECT / STATUS zeigt der Bildschirm 1 / 0 / 0, unter COMPLETED PRESENCE WAITS danach 2. Beide Anwesenheits-Warteaufrufe sind zurückgekehrt. Das ist kein Erfolgsindikator für einen Dateitransfer. Darunter steht NO FILE TRANSFER TESTED.

__fds_error: STATUS-FDS-DEFAULT — Unter PRESENT / PROTECT / STATUS zeigt der Bildschirm 1 / 0 / 0, unter COMPLETED PRESENCE WAITS danach 2. Beide Anwesenheits-Warteaufrufe sind zurückgekehrt. Das ist kein Erfolgsindikator für einen Dateitransfer. Darunter steht NO FILE TRANSFER TESTED.
STATUS-FDS-DEFAULT — Unter PRESENT / PROTECT / STATUS zeigt der Bildschirm 1 / 0 / 0, unter COMPLETED PRESENCE WAITS danach 2. Beide Anwesenheits-Warteaufrufe sind zurückgekehrt. Das ist kein Erfolgsindikator für einen Dateitransfer. Darunter steht NO FILE TRANSFER TESTED.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/fds.h:13

Deklarationshinweise im Original
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

Gibt das maskierte Schreibschutzbit aus $4032 zurück. Die Funktion erkennt nicht Diskettenseite A oder B.

Syntax

c
u8 __fds_side(void);

Rückgabewert

Gibt $4032 & 0x04 zurück, also 0 oder 4 und keinen normalisierten booleschen Wert. Hardwarebit 2 meldet Schreibschutz oder eine ausgeworfene Diskette.

Bedingungen und Grenzen

Bauen Sie mit --mapper=fds. Bei einem Cartridge-Mapper wird der Aufruf einer FDS-exklusiven Funktion außer __fds_available mit KQFC2420 abgewiesen.

KUROSAKI verwendet ein Teilmodell des FDS-Controllers. Das Beispiel beobachtet Anwesenheit 1, Schutzmaske 0 und Status 0. Die Aufnahme belegt kein Verhalten bei Auswurf, geschützten Medien oder CRC-Fehlern.

Verwenden Sie zur Schutzprüfung (__fds_side() != 0). Um den Speichererfolg zu beurteilen, benötigen Sie außerdem Diskettenanwesenheit und Rückgabewert des BIOS-Vorgangs.

Die Aufnahme nutzt KUROSAKIs direktes Laden der Disketten-Bootdateien ins RAM. Das Beispiel ruft keine BIOS-Routine auf und prüft weder Dateitransfers noch Diskettenwechsel oder Hardwarestart. Die leere Testfirmware ist kein Ersatz-BIOS. Das Beispiel aktualisiert die Anzeige per NMI und deaktiviert maskierbare IRQs, die diese Abfragen nicht benötigen.

Das Buildbeispiel erzeugt eine .fds-Datei mit nativem Layout ohne zusätzliche Bootdateien für den Genehmigungsbildschirm. Diese Konfiguration prüft KUROSAKIs Direktstart; das Startverfahren einer echten Diskette muss separat validiert werden.

NESdev: FDS-Hardwareregister

Anwendungsbeispiel

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(); }
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Unter PRESENT / PROTECT / STATUS zeigt der Bildschirm 1 / 0 / 0, unter COMPLETED PRESENCE WAITS danach 2. Beide Anwesenheits-Warteaufrufe sind zurückgekehrt. Das ist kein Erfolgsindikator für einen Dateitransfer. Darunter steht NO FILE TRANSFER TESTED.

__fds_side: STATUS-FDS-DEFAULT — Unter PRESENT / PROTECT / STATUS zeigt der Bildschirm 1 / 0 / 0, unter COMPLETED PRESENCE WAITS danach 2. Beide Anwesenheits-Warteaufrufe sind zurückgekehrt. Das ist kein Erfolgsindikator für einen Dateitransfer. Darunter steht NO FILE TRANSFER TESTED.
STATUS-FDS-DEFAULT — Unter PRESENT / PROTECT / STATUS zeigt der Bildschirm 1 / 0 / 0, unter COMPLETED PRESENCE WAITS danach 2. Beide Anwesenheits-Warteaufrufe sind zurückgekehrt. Das ist kein Erfolgsindikator für einen Dateitransfer. Darunter steht NO FILE TRANSFER TESTED.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/fds.h:11

Deklarationshinweise im Original
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

Drückt das Warten auf eine eingelegte Diskette aus. Ruft dieselbe Anwesenheits-Abfrageschleife wie __fds_wait_ready() auf.

Syntax

c
void __fds_wait_insert(void);

Rückgabewert

Kein Rückgabewert.

Bedingungen und Grenzen

Bauen Sie mit --mapper=fds. Bei einem Cartridge-Mapper wird der Aufruf einer FDS-exklusiven Funktion außer __fds_available mit KQFC2420 abgewiesen.

Die Prüfung verwendet Bit 0 von $4032, das die Anwesenheit einer Diskette meldet. Sie prüft weder die Laufwerksbereitschaft in Bit 1 noch Dateiexistenz oder Ladeabschluss. „ready“ darf hier nicht als Freigabe zum Start einer Übertragung verstanden werden.

Es gibt keinen Timeout. Ohne Diskette bleibt der Aufruf blockiert. Fragen Sie __fds_disk_ready() einmal pro Frame ab, wenn das Spiel Anzeige oder Eingaben weiter verarbeiten muss. Das Beispiel prüft vor beiden Warteaufrufen die Anwesenheit. Eine bereits eingelegte Diskette erfüllt die Wartebedingung ohne erneutes Herausnehmen und Einlegen.

KUROSAKI verwendet ein Teilmodell des FDS-Controllers. Das Beispiel beobachtet Anwesenheit 1, Schutzmaske 0 und Status 0. Die Aufnahme belegt kein Verhalten bei Auswurf, geschützten Medien oder CRC-Fehlern.

Die Aufnahme nutzt KUROSAKIs direktes Laden der Disketten-Bootdateien ins RAM. Das Beispiel ruft keine BIOS-Routine auf und prüft weder Dateitransfers noch Diskettenwechsel oder Hardwarestart. Die leere Testfirmware ist kein Ersatz-BIOS. Das Beispiel aktualisiert die Anzeige per NMI und deaktiviert maskierbare IRQs, die diese Abfragen nicht benötigen.

Das Buildbeispiel erzeugt eine .fds-Datei mit nativem Layout ohne zusätzliche Bootdateien für den Genehmigungsbildschirm. Diese Konfiguration prüft KUROSAKIs Direktstart; das Startverfahren einer echten Diskette muss separat validiert werden.

NESdev: FDS-Hardwareregister

Anwendungsbeispiel

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(); }
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Unter PRESENT / PROTECT / STATUS zeigt der Bildschirm 1 / 0 / 0, unter COMPLETED PRESENCE WAITS danach 2. Beide Anwesenheits-Warteaufrufe sind zurückgekehrt. Das ist kein Erfolgsindikator für einen Dateitransfer. Darunter steht NO FILE TRANSFER TESTED.

__fds_wait_insert: STATUS-FDS-DEFAULT — Unter PRESENT / PROTECT / STATUS zeigt der Bildschirm 1 / 0 / 0, unter COMPLETED PRESENCE WAITS danach 2. Beide Anwesenheits-Warteaufrufe sind zurückgekehrt. Das ist kein Erfolgsindikator für einen Dateitransfer. Darunter steht NO FILE TRANSFER TESTED.
STATUS-FDS-DEFAULT — Unter PRESENT / PROTECT / STATUS zeigt der Bildschirm 1 / 0 / 0, unter COMPLETED PRESENCE WAITS danach 2. Beide Anwesenheits-Warteaufrufe sind zurückgekehrt. Das ist kein Erfolgsindikator für einen Dateitransfer. Darunter steht NO FILE TRANSFER TESTED.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/fds.h:17

Deklarationshinweise im Original
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

Wartet in einer CPU-Abfrageschleife, bis eine Diskette vorhanden ist.

Syntax

c
void __fds_wait_ready(void);

Rückgabewert

Kein Rückgabewert.

Bedingungen und Grenzen

Bauen Sie mit --mapper=fds. Bei einem Cartridge-Mapper wird der Aufruf einer FDS-exklusiven Funktion außer __fds_available mit KQFC2420 abgewiesen.

Die Prüfung verwendet Bit 0 von $4032, das die Anwesenheit einer Diskette meldet. Sie prüft weder die Laufwerksbereitschaft in Bit 1 noch Dateiexistenz oder Ladeabschluss. „ready“ darf hier nicht als Freigabe zum Start einer Übertragung verstanden werden.

Es gibt keinen Timeout. Ohne Diskette bleibt der Aufruf blockiert. Fragen Sie __fds_disk_ready() einmal pro Frame ab, wenn das Spiel Anzeige oder Eingaben weiter verarbeiten muss. Das Beispiel prüft vor beiden Warteaufrufen die Anwesenheit. Eine bereits eingelegte Diskette erfüllt die Wartebedingung ohne erneutes Herausnehmen und Einlegen.

KUROSAKI verwendet ein Teilmodell des FDS-Controllers. Das Beispiel beobachtet Anwesenheit 1, Schutzmaske 0 und Status 0. Die Aufnahme belegt kein Verhalten bei Auswurf, geschützten Medien oder CRC-Fehlern.

Die Aufnahme nutzt KUROSAKIs direktes Laden der Disketten-Bootdateien ins RAM. Das Beispiel ruft keine BIOS-Routine auf und prüft weder Dateitransfers noch Diskettenwechsel oder Hardwarestart. Die leere Testfirmware ist kein Ersatz-BIOS. Das Beispiel aktualisiert die Anzeige per NMI und deaktiviert maskierbare IRQs, die diese Abfragen nicht benötigen.

Das Buildbeispiel erzeugt eine .fds-Datei mit nativem Layout ohne zusätzliche Bootdateien für den Genehmigungsbildschirm. Diese Konfiguration prüft KUROSAKIs Direktstart; das Startverfahren einer echten Diskette muss separat validiert werden.

NESdev: FDS-Hardwareregister

Anwendungsbeispiel

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(); }
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Unter PRESENT / PROTECT / STATUS zeigt der Bildschirm 1 / 0 / 0, unter COMPLETED PRESENCE WAITS danach 2. Beide Anwesenheits-Warteaufrufe sind zurückgekehrt. Das ist kein Erfolgsindikator für einen Dateitransfer. Darunter steht NO FILE TRANSFER TESTED.

__fds_wait_ready: STATUS-FDS-DEFAULT — Unter PRESENT / PROTECT / STATUS zeigt der Bildschirm 1 / 0 / 0, unter COMPLETED PRESENCE WAITS danach 2. Beide Anwesenheits-Warteaufrufe sind zurückgekehrt. Das ist kein Erfolgsindikator für einen Dateitransfer. Darunter steht NO FILE TRANSFER TESTED.
STATUS-FDS-DEFAULT — Unter PRESENT / PROTECT / STATUS zeigt der Bildschirm 1 / 0 / 0, unter COMPLETED PRESENCE WAITS danach 2. Beide Anwesenheits-Warteaufrufe sind zurückgekehrt. Das ist kein Erfolgsindikator für einen Dateitransfer. Darunter steht NO FILE TRANSFER TESTED.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/fds.h:15

Deklarationshinweise im Original
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 — Laden von FDS-Dateien

__fds_file_exists

Prüft, ob die kompilierten Dateimetadaten die angeforderte ID enthalten. Auch ein Datensatz mit Größe null gilt als vorhanden.

Syntax

c
u8 __fds_file_exists(u8 id);

Parameter

id

Zu suchende Datei-ID. Metadaten nutzen IDs von 0 bis 254; 255 ist als Abschlusswert reserviert. Eine ID unterscheidet sich von Dateinummer, Dateiname und Zieladresse.

Rückgabewert

1, wenn die kompilierte Tabelle die ID enthält, sonst 0. Das garantiert nicht, dass die eingelegte Diskette diese Datei enthält.

Bedingungen und Grenzen

Bauen Sie mit --mapper=fds. Bei einem Cartridge-Mapper wird der Aufruf einer FDS-exklusiven Funktion außer __fds_available mit KQFC2420 abgewiesen.

Durchsucht die aus --fds-meta=... oder --fds-manifest=... kompilierte Tabelle zusammen mit erzeugten Overlayeinträgen. Es wird kein Diskettenverzeichnis gelesen. Ohne Tabelleneintrag wird eine Datei selbst dann nicht gefunden, wenn sie im Diskettenabbild enthalten ist. Manuelle und erzeugte Einträge sind zusammen auf 42 begrenzt.

Die Größe ist der in den Metadaten deklarierte Wert. Sie muss weder der ermittelten Quelldateilänge noch der Nutzdatenlänge eines gekürzten Overlays oder dem Inhalt einer gewechselten Diskette entsprechen. Deklarieren Sie korrekte Größen, wenn Sie damit Datenzugriffe begrenzen.

Legen Sie fds_metadata.json und die beiden zugehörigen .bin-Dateien neben den C-Quelltext. ID 16 enthält eine selbst erstellte Folge von 513 Bytes: zweimal 0 bis 255, danach null. ID 17 enthält keine Nutzdaten. Beide sind Nicht-Boot-PRG-Dateien (Typ 0) und werden in dieser Lektion nicht geladen. Relative Quellpfade werden vom Manifestverzeichnis aus aufgelöst.

Die Aufnahme nutzt KUROSAKIs direktes Laden der Disketten-Bootdateien ins RAM. Das Beispiel ruft keine BIOS-Routine auf und prüft weder Dateitransfers noch Diskettenwechsel oder Hardwarestart. Die leere Testfirmware ist kein Ersatz-BIOS. Das Beispiel aktualisiert die Anzeige per NMI und deaktiviert maskierbare IRQs, die diese Abfragen nicht benötigen.

Das Buildbeispiel erzeugt eine .fds-Datei mit nativem Layout ohne zusätzliche Bootdateien für den Genehmigungsbildschirm. Diese Konfiguration prüft KUROSAKIs Direktstart; das Startverfahren einer echten Diskette muss separat validiert werden.

Anwendungsbeispiel

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(); }
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Die Tabelle zeigt ID 16 als vorhanden mit 513 Bytes, ID 17 als vorhanden mit null Bytes und ID 18 als nicht vorhanden mit null Bytes. TABLE VALUES MATCH: PASS vergleicht die abgefragten Werte mit dieser Kombination. Größe null allein unterscheidet keinen leeren Datensatz von einer fehlenden ID.

__fds_file_exists: METADATA-FDS-DEFAULT — Die Tabelle zeigt ID 16 als vorhanden mit 513 Bytes, ID 17 als vorhanden mit null Bytes und ID 18 als nicht vorhanden mit null Bytes. TABLE VALUES MATCH: PASS vergleicht die abgefragten Werte mit dieser Kombination. Größe null allein unterscheidet keinen leeren Datensatz von einer fehlenden ID.
METADATA-FDS-DEFAULT — Die Tabelle zeigt ID 16 als vorhanden mit 513 Bytes, ID 17 als vorhanden mit null Bytes und ID 18 als nicht vorhanden mit null Bytes. TABLE VALUES MATCH: PASS vergleicht die abgefragten Werte mit dieser Kombination. Größe null allein unterscheidet keinen leeren Datensatz von einer fehlenden ID.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/fds_file.h:18

Deklarationshinweise im Original
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

Gibt die in den kompilierten Metadaten für eine ID deklarierte Bytegröße zurück, etwa zur Prüfung der vorgesehenen Ladegröße einer Ressource.

Syntax

c
u16 __fds_file_size(u8 id);

Parameter

id

Zu suchende Datei-ID. Metadaten nutzen IDs von 0 bis 254; 255 ist als Abschlusswert reserviert. Eine ID unterscheidet sich von Dateinummer, Dateiname und Zieladresse.

Rückgabewert

Deklarierte vorzeichenlose 16-Bit-Größe von 0 bis 65535. Fehlende IDs liefern ebenfalls null; verwenden Sie __fds_file_exists(), wenn die Unterscheidung wichtig ist.

Bedingungen und Grenzen

Bauen Sie mit --mapper=fds. Bei einem Cartridge-Mapper wird der Aufruf einer FDS-exklusiven Funktion außer __fds_available mit KQFC2420 abgewiesen.

Durchsucht die aus --fds-meta=... oder --fds-manifest=... kompilierte Tabelle zusammen mit erzeugten Overlayeinträgen. Es wird kein Diskettenverzeichnis gelesen. Ohne Tabelleneintrag wird eine Datei selbst dann nicht gefunden, wenn sie im Diskettenabbild enthalten ist. Manuelle und erzeugte Einträge sind zusammen auf 42 begrenzt.

Die Größe ist der in den Metadaten deklarierte Wert. Sie muss weder der ermittelten Quelldateilänge noch der Nutzdatenlänge eines gekürzten Overlays oder dem Inhalt einer gewechselten Diskette entsprechen. Deklarieren Sie korrekte Größen, wenn Sie damit Datenzugriffe begrenzen.

Legen Sie fds_metadata.json und die beiden zugehörigen .bin-Dateien neben den C-Quelltext. ID 16 enthält eine selbst erstellte Folge von 513 Bytes: zweimal 0 bis 255, danach null. ID 17 enthält keine Nutzdaten. Beide sind Nicht-Boot-PRG-Dateien (Typ 0) und werden in dieser Lektion nicht geladen. Relative Quellpfade werden vom Manifestverzeichnis aus aufgelöst.

Die Aufnahme nutzt KUROSAKIs direktes Laden der Disketten-Bootdateien ins RAM. Das Beispiel ruft keine BIOS-Routine auf und prüft weder Dateitransfers noch Diskettenwechsel oder Hardwarestart. Die leere Testfirmware ist kein Ersatz-BIOS. Das Beispiel aktualisiert die Anzeige per NMI und deaktiviert maskierbare IRQs, die diese Abfragen nicht benötigen.

Das Buildbeispiel erzeugt eine .fds-Datei mit nativem Layout ohne zusätzliche Bootdateien für den Genehmigungsbildschirm. Diese Konfiguration prüft KUROSAKIs Direktstart; das Startverfahren einer echten Diskette muss separat validiert werden.

Anwendungsbeispiel

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(); }
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Die Tabelle zeigt ID 16 als vorhanden mit 513 Bytes, ID 17 als vorhanden mit null Bytes und ID 18 als nicht vorhanden mit null Bytes. TABLE VALUES MATCH: PASS vergleicht die abgefragten Werte mit dieser Kombination. Größe null allein unterscheidet keinen leeren Datensatz von einer fehlenden ID.

__fds_file_size: METADATA-FDS-DEFAULT — Die Tabelle zeigt ID 16 als vorhanden mit 513 Bytes, ID 17 als vorhanden mit null Bytes und ID 18 als nicht vorhanden mit null Bytes. TABLE VALUES MATCH: PASS vergleicht die abgefragten Werte mit dieser Kombination. Größe null allein unterscheidet keinen leeren Datensatz von einer fehlenden ID.
METADATA-FDS-DEFAULT — Die Tabelle zeigt ID 16 als vorhanden mit 513 Bytes, ID 17 als vorhanden mit null Bytes und ID 18 als nicht vorhanden mit null Bytes. TABLE VALUES MATCH: PASS vergleicht die abgefragten Werte mit dieser Kombination. Größe null allein unterscheidet keinen leeren Datensatz von einer fehlenden ID.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/fds_file.h:20

Deklarationshinweise im Original
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

Liest eine Datei über das BIOS und kopiert deren PRG-Daten in einen CPU-Puffer. Damit laden Sie Speicherdaten oder Levelressourcen in vom Spiel reservierten Speicher.

Syntax

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

Parameter

id

ID einer im erzeugten Diskettenabbild enthaltenen Nicht-Bootdatei, 0 bis 254; das Beispiel verwendet 73. Sie unterscheidet sich von der physischen Dateiposition. ID 255, nicht vorhandene IDs und Bootdateien werden abgewiesen.

dst

Zieladresse einer PRG-Datei mit Platz für ihre vollständige gepackte Dateilänge. Übergeben Sie null, um die Daten ohne Kopie am im Header festgelegten Ziel zu belassen. Für CHR- oder Nametable-PPU-Dateien ist nur null erlaubt; das BIOS überträgt an das native PPU-Ziel.

Rückgabewert

Bei Erfolg null. BIOS-Fehler ungleich null werden unverändert zurückgegeben. Meldet das BIOS Erfolg, ohne genau eine Datei geladen zu haben, lautet der Rückgabewert 64 ($40). Ungültige IDs, Puffer oder Kombinationen aus Dateityp und Ziel ergeben 255 ($FF).

Bedingungen und Grenzen

Verwenden Sie die API mit --mapper=fds --fds-layout=fds32. Das Manifest --fds-meta definiert Datei-IDs, Ladeadressen, Typen, Bootstatus und Reihenfolge. Übertragungsadressen und -größen stammen aus dem beim Build erzeugten Diskettenabbild. Die API untersucht keine beliebig eingelegte Diskette.

Das BIOS lädt zunächst an die im Diskettenheader gespeicherte Adresse; anschließend kopiert diese Hilfsfunktion nach dst. Sie wählt die Kopierrichtung so, dass überlappende Daten erhalten bleiben. Auch der Zwischenbereich muss jedoch reserviert sein. Bei einem fehlgeschlagenen Transfer wird nicht nach dst kopiert. Nach einem BIOS-Aufruf wird die Bankbelegung mit 255 (unbekannt) vermerkt.

Jeder CPU-Puffer muss vollständig entweder in $0200–$07FF oder in $6000–$DFFF liegen. Überlaufende Adressbereiche und Ziele in E/A-Registern werden abgewiesen. Der Aufrufer muss außerdem Überschneidungen mit Code, OAM, anderen Variablen und Compiler-Zwischenspeicher vermeiden. Das Beispiel nutzt Bank 0 für gemeinsamen Code, $8000 als Zwischenbereich und Vier-Byte-Puffer bei $8500 und $8600.

Dies sind synchrone BIOS-Aufrufe. Rufen Sie sie aus gemeinsamem Code auf, damit die Übertragung weder laufenden Code noch dessen Rücksprungadresse überschreibt, und nicht aus einem Interrupt-Handler. Erfüllen Sie während Disketten-E/A die BIOS-Voraussetzungen: Darstellung und NMI stoppen, PPU-Adressschritt auf eins setzen und alle IRQ-Quellen außer der Diskette deaktivieren. Ein unterbrechungsfreier Spiel- oder Musikablauf während der Übertragung ist nicht garantiert.

Die Aufnahme verwendet einen eigens geschriebenen Test für die BIOS-Aufrufkonvention, um kompilierten CPU-Code, Pufferinhalte, Speichern und Zurücklesen sowie jedes angezeigte Pixel zu prüfen. Das echte BIOS, physische Diskettenschreibvorgänge, CRC und Wiederherstellung nach Stromausfall werden damit nicht geprüft. Eine gewöhnliche FDS-Ausführung in KUROSAKI allein belegt nicht dasselbe Speicherverhalten.

Verwenden Sie das Beispiel zusammen mit fds_file_io_manifest.json und fds_file_io_payload.bin aus demselben Ordner. Das folgende Prüfprogramm erzeugt aus eigenem Quellcode einen temporären Testbaustein und entfernt ihn anschließend. Es ist kein kommerzielles BIOS enthalten; der Testbaustein ist kein BIOS für normale Spiele.

Aufrufkonvention des FDS-BIOS

Anwendungsbeispiel

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();}
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

LOAD, SAVE, RELOAD STATUS und BYTE CHECK ERRORS zeigen jeweils 000. INITIAL FIRST/LAST zeigt 011 und 044, den ersten und letzten der vier nach $8500 geladenen Bytes. Das Beispiel ändert sie in 7, 14, 21 und 28, speichert sie und lädt sie nach $8600 zurück. Daher zeigt SAVED FIRST / LAST 007 und 028. STAGING 8000 / SIZE 4 bezeichnet die im Diskettenabbild festgelegte Ladeadresse und Dateilänge. Der separate Test prüft außerdem, dass ID 73 in der tatsächlich vierten Datei, also der nullbasierten Position 3, gespeichert wird.

__fds_load_file: Standardeinstellung — LOAD, SAVE, RELOAD STATUS und BYTE CHECK ERRORS zeigen jeweils 000. INITIAL FIRST/LAST zeigt 011 und 044, den ersten und letzten der vier nach $8500 geladenen Bytes. Das Beispiel ändert sie in 7, 14, 21 und 28, speichert sie und lädt sie nach $8600 zurück. Daher zeigt SAVED FIRST / LAST 007 und 028. STAGING 8000 / SIZE 4 bezeichnet die im Diskettenabbild festgelegte Ladeadresse und Dateilänge. Der separate Test prüft außerdem, dass ID 73 in der tatsächlich vierten Datei, also der nullbasierten Position 3, gespeichert wird.
Standardeinstellung — LOAD, SAVE, RELOAD STATUS und BYTE CHECK ERRORS zeigen jeweils 000. INITIAL FIRST/LAST zeigt 011 und 044, den ersten und letzten der vier nach $8500 geladenen Bytes. Das Beispiel ändert sie in 7, 14, 21 und 28, speichert sie und lädt sie nach $8600 zurück. Daher zeigt SAVED FIRST / LAST 007 und 028. STAGING 8000 / SIZE 4 bezeichnet die im Diskettenabbild festgelegte Ladeadresse und Dateilänge. Der separate Test prüft außerdem, dass ID 73 in der tatsächlich vierten Datei, also der nullbasierten Position 3, gespeichert wird.

Vollständiges Beispielprogramm

fds_file_io_manifest.json

fds_file_io_payload.bin

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/fds_file.h:11

Deklarationshinweise im Original
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

Überschreibt einen reservierten PRG-Speicherplatz fester Größe am Ende einer Diskettenseite mit Bytes aus einem CPU-Puffer. Datei-ID, Name und künftige Ladeadresse bleiben erhalten.

Syntax

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

Parameter

id

ID einer im erzeugten Diskettenabbild enthaltenen Nicht-Bootdatei, 0 bis 254; das Beispiel verwendet 73. Sie unterscheidet sich von der physischen Dateiposition. ID 255, nicht vorhandene IDs und Bootdateien werden abgewiesen.

src

Anfang der Daten im CPU-RAM, über len Bytes lesbar. Diese API speichert VRAM nicht direkt.

len

Anzahl zu speichernder Bytes; sie muss der Dateilänge im erzeugten Abbild entsprechen. Hat das Spiel weniger Daten, füllen Sie vor dem Aufruf auch den ungenutzten Teil des Speicherpuffers.

Rückgabewert

Bei Erfolg null, sonst ein BIOS-Fehler wie Schreibschutz oder fehlgeschlagene Schreibprüfung. Bei ungültiger ID, Boot- oder Nicht-PRG-Datei, einer nicht letzten Datei auf ihrer Seite, abweichender Länge oder ungültigem CPU-Adressbereich wird ohne BIOS-Aufruf 255 ($FF) zurückgegeben.

Bedingungen und Grenzen

Verwenden Sie die API mit --mapper=fds --fds-layout=fds32. Das Manifest --fds-meta definiert Datei-IDs, Ladeadressen, Typen, Bootstatus und Reihenfolge. Übertragungsadressen und -größen stammen aus dem beim Build erzeugten Diskettenabbild. Die API untersucht keine beliebig eingelegte Diskette.

BIOS WriteFile verbirgt logisch alle Dateien hinter der Schreibposition. Daher akzeptiert diese API nur die letzte Nicht-Boot-PRG-Datei jeder Seite. Sie hängt keine neue Datei an und ändert keine Speicherplatzgröße. Der Speicherplatz muss auch im endgültigen Layout mit erzeugten Boothelfern und automatischen Overlays an letzter Stelle stehen.

Der Compiler unterscheidet Datei-ID, Headernummer und physische Reihenfolge. Das Beispiel hat ID 73 und Manifestnummer 200, übergibt aber Position 3 an das BIOS. Auch Disketten- und Seitennummern stammen aus dem aufgelösten Layout. Verwenden Sie dies nicht mit einer Diskette, deren Layout unabhängig vom Manifest geändert wurde.

Jeder CPU-Puffer muss vollständig entweder in $0200–$07FF oder in $6000–$DFFF liegen. Überlaufende Adressbereiche und Ziele in E/A-Registern werden abgewiesen. Der Aufrufer muss außerdem Überschneidungen mit Code, OAM, anderen Variablen und Compiler-Zwischenspeicher vermeiden. Das Beispiel nutzt Bank 0 für gemeinsamen Code, $8000 als Zwischenbereich und Vier-Byte-Puffer bei $8500 und $8600.

Dies sind synchrone BIOS-Aufrufe. Rufen Sie sie aus gemeinsamem Code auf, damit die Übertragung weder laufenden Code noch dessen Rücksprungadresse überschreibt, und nicht aus einem Interrupt-Handler. Erfüllen Sie während Disketten-E/A die BIOS-Voraussetzungen: Darstellung und NMI stoppen, PPU-Adressschritt auf eins setzen und alle IRQ-Quellen außer der Diskette deaktivieren. Ein unterbrechungsfreier Spiel- oder Musikablauf während der Übertragung ist nicht garantiert.

Die Aufnahme verwendet einen eigens geschriebenen Test für die BIOS-Aufrufkonvention, um kompilierten CPU-Code, Pufferinhalte, Speichern und Zurücklesen sowie jedes angezeigte Pixel zu prüfen. Das echte BIOS, physische Diskettenschreibvorgänge, CRC und Wiederherstellung nach Stromausfall werden damit nicht geprüft. Eine gewöhnliche FDS-Ausführung in KUROSAKI allein belegt nicht dasselbe Speicherverhalten.

Verwenden Sie das Beispiel zusammen mit fds_file_io_manifest.json und fds_file_io_payload.bin aus demselben Ordner. Das folgende Prüfprogramm erzeugt aus eigenem Quellcode einen temporären Testbaustein und entfernt ihn anschließend. Es ist kein kommerzielles BIOS enthalten; der Testbaustein ist kein BIOS für normale Spiele.

Aufrufkonvention des FDS-BIOS

Anwendungsbeispiel

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();}
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

LOAD, SAVE, RELOAD STATUS und BYTE CHECK ERRORS zeigen jeweils 000. INITIAL FIRST/LAST zeigt 011 und 044, den ersten und letzten der vier nach $8500 geladenen Bytes. Das Beispiel ändert sie in 7, 14, 21 und 28, speichert sie und lädt sie nach $8600 zurück. Daher zeigt SAVED FIRST / LAST 007 und 028. STAGING 8000 / SIZE 4 bezeichnet die im Diskettenabbild festgelegte Ladeadresse und Dateilänge. Der separate Test prüft außerdem, dass ID 73 in der tatsächlich vierten Datei, also der nullbasierten Position 3, gespeichert wird.

__fds_save_file: Standardeinstellung — LOAD, SAVE, RELOAD STATUS und BYTE CHECK ERRORS zeigen jeweils 000. INITIAL FIRST/LAST zeigt 011 und 044, den ersten und letzten der vier nach $8500 geladenen Bytes. Das Beispiel ändert sie in 7, 14, 21 und 28, speichert sie und lädt sie nach $8600 zurück. Daher zeigt SAVED FIRST / LAST 007 und 028. STAGING 8000 / SIZE 4 bezeichnet die im Diskettenabbild festgelegte Ladeadresse und Dateilänge. Der separate Test prüft außerdem, dass ID 73 in der tatsächlich vierten Datei, also der nullbasierten Position 3, gespeichert wird.
Standardeinstellung — LOAD, SAVE, RELOAD STATUS und BYTE CHECK ERRORS zeigen jeweils 000. INITIAL FIRST/LAST zeigt 011 und 044, den ersten und letzten der vier nach $8500 geladenen Bytes. Das Beispiel ändert sie in 7, 14, 21 und 28, speichert sie und lädt sie nach $8600 zurück. Daher zeigt SAVED FIRST / LAST 007 und 028. STAGING 8000 / SIZE 4 bezeichnet die im Diskettenabbild festgelegte Ladeadresse und Dateilänge. Der separate Test prüft außerdem, dass ID 73 in der tatsächlich vierten Datei, also der nullbasierten Position 3, gespeichert wird.

Vollständiges Beispielprogramm

fds_file_io_manifest.json

fds_file_io_payload.bin

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/fds_file.h:16

Deklarationshinweise im Original
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-Overlaycode

__fds_current_bank

Gibt die als resident vermerkte logische Bank zurück, ohne von der Diskette zu lesen.

Syntax

c
u8 __fds_current_bank(void);

Rückgabewert

Acht-Bit-Softwarevermerk der Bankbelegung: direkt nach dem normalen Start 1, bei unbekannter Belegung 255.

Bedingungen und Grenzen

Bauen Sie mit --mapper=fds. Bei einem Cartridge-Mapper wird der Aufruf einer FDS-exklusiven Funktion außer __fds_available mit KQFC2420 abgewiesen.

Der normale FDS-Start setzt den Belegungsvermerk auf 1. __fds_load_bank() aktualisiert ihn nach erfolgreichem Laden. Direkte Ladevorgänge über __fds_load_file() oder __fds_load_overlay() sowie fehlgeschlagene Banktransfers setzen ihn auf 255 (unbekannt). Speicherinhalte werden nicht geprüft. Verlassen Sie sich nach direkter Änderung des Fensters nicht auf diesen Vermerk. Eine eigene Resetroutine muss die Initialisierung selbst übernehmen.

Die Aufnahme nutzt KUROSAKIs direktes Laden der Disketten-Bootdateien ins RAM. Das Beispiel ruft keine BIOS-Routine auf und prüft weder Dateitransfers noch Diskettenwechsel oder Hardwarestart. Die leere Testfirmware ist kein Ersatz-BIOS. Das Beispiel aktualisiert die Anzeige per NMI und deaktiviert maskierbare IRQs, die diese Abfragen nicht benötigen.

Das Buildbeispiel erzeugt eine .fds-Datei mit nativem Layout ohne zusätzliche Bootdateien für den Genehmigungsbildschirm. Diese Konfiguration prüft KUROSAKIs Direktstart; das Startverfahren einer echten Diskette muss separat validiert werden.

Anwendungsbeispiel

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(); }
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

CURRENT BANK ist 1, BANK 1 RESIDENT ist 1, BANK 2 RESIDENT ist 0 und OVERLAY FUNCTIONS ist 2. Zwei in Bank 2 abgelegte Funktionen machen diese Bank beim Start noch nicht resident. Im Prüfbuild mit --fds-no-overlay-table ändert sich nur der letzte Wert auf null.

__fds_current_bank: RESIDENCY-FDS-DEFAULT — CURRENT BANK ist 1, BANK 1 RESIDENT ist 1, BANK 2 RESIDENT ist 0 und OVERLAY FUNCTIONS ist 2. Zwei in Bank 2 abgelegte Funktionen machen diese Bank beim Start noch nicht resident. Im Prüfbuild mit --fds-no-overlay-table ändert sich nur der letzte Wert auf null.
RESIDENCY-FDS-DEFAULT — CURRENT BANK ist 1, BANK 1 RESIDENT ist 1, BANK 2 RESIDENT ist 0 und OVERLAY FUNCTIONS ist 2. Zwei in Bank 2 abgelegte Funktionen machen diese Bank beim Start noch nicht resident. Im Prüfbuild mit --fds-no-overlay-table ändert sich nur der letzte Wert auf null.
__fds_current_bank: RESIDENCY-FDS-TABLE-OFF-DEFAULT — CURRENT BANK ist 1, BANK 1 RESIDENT ist 1, BANK 2 RESIDENT ist 0 und OVERLAY FUNCTIONS ist 2. Zwei in Bank 2 abgelegte Funktionen machen diese Bank beim Start noch nicht resident. Im Prüfbuild mit --fds-no-overlay-table ändert sich nur der letzte Wert auf null.
RESIDENCY-FDS-TABLE-OFF-DEFAULT — CURRENT BANK ist 1, BANK 1 RESIDENT ist 1, BANK 2 RESIDENT ist 0 und OVERLAY FUNCTIONS ist 2. Zwei in Bank 2 abgelegte Funktionen machen diese Bank beim Start noch nicht resident. Im Prüfbuild mit --fds-no-overlay-table ändert sich nur der letzte Wert auf null.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/fds_overlay.h:16

Deklarationshinweise im Original
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

Führt mit der FDS-spezifischen Schreibweise einen parameterlosen bankübergreifenden Aufruf aus. Laden und Wiederherstellen entsprechen genau __fds_overlay_farcall().

Syntax

c
u8 __fds_farcall(u8 bank, u16 func);

Parameter

bank

Ausdruck für die Bank. Er wird vor dem Aufruf einmal ausgewertet; die tatsächlich geladene Bank ergibt sich jedoch aus der gelinkten Platzierung von func. Damit wird keine Adresse in einer beliebig zur Laufzeit gewählten Bank aufgerufen. Übergeben Sie einen passenden Wert wie __bankof(func).

func

Name einer deklarierten Funktion ohne Parameter. Das Beispiel übergibt u8 overlay_answer(void). Numerische Adressen, Laufzeit-Funktionszeiger und Funktionen mit erforderlichen Argumenten werden abgewiesen. Verwenden Sie bei benötigten Argumenten einen normalen Aufruf wie target(a, b).

Rückgabewert

Acht-Bit-Ergebnis der Zielfunktion. Schlägt das Laden fehl, lässt sich aber die vorherige Bank wiederherstellen, wird die Zielfunktion nicht aufgerufen; stattdessen kommt das Ladefehlerbyte zurück. Legen Sie die Rückgabekonvention so fest, dass Fehler von normalen Ergebnissen unterscheidbar sind.

Bedingungen und Grenzen

Verwenden Sie dies mit --mapper=fds --fds-layout=fds32. Bank 0 enthält gemeinsamen Code bei $A000–$DFFF. Beim Start belegt Bank 1 den Bereich $6000–$9FFF; Banken ab 2 sind Overlays, die in dasselbe 16-KiB-Fenster geladen werden. Platzieren Sie Overlaycode oder -daten mit Direktiven wie #pragma fixed_bank 2.

Bank 1 wird aus einer separaten Boot-PRG-Datei mit 16.384 Bytes bei $6000 wiederhergestellt. Ohne Manifest sind die Standard-IDs 0 für Bank 1, 1 für gemeinsamen Code und 2 für CHR. Ein eigenes Manifest muss das Boot-PRG in 16.384-Byte-Dateien bei $6000 und $A000 aufteilen. Für Banken ab 2 lautet die Datei-ID --fds-overlay-start-id (standardmäßig 32) + Banknummer − 2.

Bei einem Aufruf in eine andere Bank wird die vorherige Bank gespeichert und vor der Rückkehr wiederhergestellt. Für verschachtelte Aufrufe gilt dasselbe. Auch nach einem teilweise fehlgeschlagenen Transfer wird eine Wiederherstellung versucht. Scheitert diese selbst, hält die Ausführung im gemeinsamen Code an, statt zu überschriebenem Code zurückzukehren. Ziele in der gemeinsamen Bank und Aufrufe innerhalb derselben Bank vermeiden unnötige Transfers.

BIOS-Aufrufe sind synchron. Diese API hält während des Ladens die Spielschleife nicht einmal pro Frame am Laufen. Verwenden Sie sie aus normalem Steuercode in der gemeinsamen Bank, nicht aus einem Interrupt-Handler. Bild- und Audiokontinuität sowie das Zusammenspiel mit dem echten BIOS müssen separat geprüft werden.

Diese Aufnahmen nutzen einen eigens geschriebenen Test für die LoadFiles-Aufrufkonvention, um kompilierten CPU-Code, Speichertransfers und jedes angezeigte Pixel zu prüfen. Echtes BIOS, Diskettentransfer-Timing, CRC und Hardwarestart werden damit nicht geprüft. KUROSAKI bildet den FDS-Controller nur teilweise nach. Eine gewöhnliche Ausführung ohne diesen Testbaustein belegt nicht dieselben Übertragungsergebnisse.

Der erste Compilerbefehl unten baut das herunterladbare Beispiel. Das Python-Prüfprogramm baut alle fünf Lektionen mit und ohne Optimierung und prüft sie anschließend in KUROSAKI. Es erstellt und entfernt einen temporären Testbaustein aus scripts/fds_abi_fixture.py; für diese Prüfungen ist kein externes BIOS nötig. Der Testbaustein ist kein BIOS für normale Spiele.

Anwendungsbeispiel

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();}
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Mit dem eigens geschriebenen LoadFiles-ABI-Test ist RETURN VALUE 042; CALLBACK COUNT und BANK EXPR COUNT sind jeweils 001; BANK IN CALLBACK ist 002 und BANK AFTER RETURN 001. Funktion und Bankausdruck liefen jeweils einmal, und Bank 1 wurde nach dem Aufruf von Bank 2 wiederhergestellt. Dies ist keine Aufnahme eines echten Diskettentransfertests.

__fds_farcall: FARCALL-DEFAULT — Mit dem eigens geschriebenen LoadFiles-ABI-Test ist RETURN VALUE 042; CALLBACK COUNT und BANK EXPR COUNT sind jeweils 001; BANK IN CALLBACK ist 002 und BANK AFTER RETURN 001. Funktion und Bankausdruck liefen jeweils einmal, und Bank 1 wurde nach dem Aufruf von Bank 2 wiederhergestellt. Dies ist keine Aufnahme eines echten Diskettentransfertests.
FARCALL-DEFAULT — Mit dem eigens geschriebenen LoadFiles-ABI-Test ist RETURN VALUE 042; CALLBACK COUNT und BANK EXPR COUNT sind jeweils 001; BANK IN CALLBACK ist 002 und BANK AFTER RETURN 001. Funktion und Bankausdruck liefen jeweils einmal, und Bank 1 wurde nach dem Aufruf von Bank 2 wiederhergestellt. Dies ist keine Aufnahme eines echten Diskettentransfertests.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/fds_overlay.h:25

Deklarationshinweise im Original
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

Vergleicht die angeforderte Bank mit dem Belegungsvermerk. Das hilft zu entscheiden, ob Laden nötig ist, führt aber keinen Ladevorgang aus.

Syntax

c
u8 __fds_is_bank_resident(u8 bank);

Parameter

bank

Zu vergleichende logische Banknummer. Der normale Start vermerkt 1; erzeugte Overlays verwenden 2 und höher. Bank 0 enthält gemeinsamen Code und ist nicht der Anfangswert dieses Belegungsvermerks.

Rückgabewert

1, wenn eine gültige Bank dem Vermerk entspricht, sonst 0. Bank 255 (unbekannt) liefert immer null, selbst bei einem Vermerk von 255.

Bedingungen und Grenzen

Bauen Sie mit --mapper=fds. Bei einem Cartridge-Mapper wird der Aufruf einer FDS-exklusiven Funktion außer __fds_available mit KQFC2420 abgewiesen.

Der normale FDS-Start setzt den Belegungsvermerk auf 1. __fds_load_bank() aktualisiert ihn nach erfolgreichem Laden. Direkte Ladevorgänge über __fds_load_file() oder __fds_load_overlay() sowie fehlgeschlagene Banktransfers setzen ihn auf 255 (unbekannt). Speicherinhalte werden nicht geprüft. Verlassen Sie sich nach direkter Änderung des Fensters nicht auf diesen Vermerk. Eine eigene Resetroutine muss die Initialisierung selbst übernehmen.

Die Aufnahme nutzt KUROSAKIs direktes Laden der Disketten-Bootdateien ins RAM. Das Beispiel ruft keine BIOS-Routine auf und prüft weder Dateitransfers noch Diskettenwechsel oder Hardwarestart. Die leere Testfirmware ist kein Ersatz-BIOS. Das Beispiel aktualisiert die Anzeige per NMI und deaktiviert maskierbare IRQs, die diese Abfragen nicht benötigen.

Das Buildbeispiel erzeugt eine .fds-Datei mit nativem Layout ohne zusätzliche Bootdateien für den Genehmigungsbildschirm. Diese Konfiguration prüft KUROSAKIs Direktstart; das Startverfahren einer echten Diskette muss separat validiert werden.

Anwendungsbeispiel

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(); }
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

CURRENT BANK ist 1, BANK 1 RESIDENT ist 1, BANK 2 RESIDENT ist 0 und OVERLAY FUNCTIONS ist 2. Zwei in Bank 2 abgelegte Funktionen machen diese Bank beim Start noch nicht resident. Im Prüfbuild mit --fds-no-overlay-table ändert sich nur der letzte Wert auf null.

__fds_is_bank_resident: RESIDENCY-FDS-DEFAULT — CURRENT BANK ist 1, BANK 1 RESIDENT ist 1, BANK 2 RESIDENT ist 0 und OVERLAY FUNCTIONS ist 2. Zwei in Bank 2 abgelegte Funktionen machen diese Bank beim Start noch nicht resident. Im Prüfbuild mit --fds-no-overlay-table ändert sich nur der letzte Wert auf null.
RESIDENCY-FDS-DEFAULT — CURRENT BANK ist 1, BANK 1 RESIDENT ist 1, BANK 2 RESIDENT ist 0 und OVERLAY FUNCTIONS ist 2. Zwei in Bank 2 abgelegte Funktionen machen diese Bank beim Start noch nicht resident. Im Prüfbuild mit --fds-no-overlay-table ändert sich nur der letzte Wert auf null.
__fds_is_bank_resident: RESIDENCY-FDS-TABLE-OFF-DEFAULT — CURRENT BANK ist 1, BANK 1 RESIDENT ist 1, BANK 2 RESIDENT ist 0 und OVERLAY FUNCTIONS ist 2. Zwei in Bank 2 abgelegte Funktionen machen diese Bank beim Start noch nicht resident. Im Prüfbuild mit --fds-no-overlay-table ändert sich nur der letzte Wert auf null.
RESIDENCY-FDS-TABLE-OFF-DEFAULT — CURRENT BANK ist 1, BANK 1 RESIDENT ist 1, BANK 2 RESIDENT ist 0 und OVERLAY FUNCTIONS ist 2. Zwei in Bank 2 abgelegte Funktionen machen diese Bank beim Start noch nicht resident. Im Prüfbuild mit --fds-no-overlay-table ändert sich nur der letzte Wert auf null.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/fds_overlay.h:18

Deklarationshinweise im Original
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

Übersetzt eine logische Bank in ihre Datei-ID, lädt das Overlayfenster $6000–$9FFF und vermerkt die Bank anschließend als resident.

Syntax

c
u8 __fds_load_bank(u8 bank);

Parameter

bank

Zu ladende logische Bank, 1 bis 254. Bank 0 ist gemeinsamer Code und kein ladbares Fenster; 255 ist für unbekannte Bankbelegung reserviert. Bei Banken ab 2 muss auch die berechnete Datei-ID in 0 bis 254 liegen.

Rückgabewert

Bei Erfolg null. Gibt 255 für Bank 0 oder 255, einen Datei-ID-Überlauf oder Bank 1 ohne Wiederherstellungsdatei zurück. Ein fehlgeschlagener BIOS-Transfer liefert dessen Fehlerbyte. Argumentfehler vor dem Transfer bewahren den Belegungsvermerk; fehlgeschlagene BIOS-Transfers setzen ihn auf 255 (unbekannt).

Bedingungen und Grenzen

Verwenden Sie dies mit --mapper=fds --fds-layout=fds32. Bank 0 enthält gemeinsamen Code bei $A000–$DFFF. Beim Start belegt Bank 1 den Bereich $6000–$9FFF; Banken ab 2 sind Overlays, die in dasselbe 16-KiB-Fenster geladen werden. Platzieren Sie Overlaycode oder -daten mit Direktiven wie #pragma fixed_bank 2.

Bank 1 wird aus einer separaten Boot-PRG-Datei mit 16.384 Bytes bei $6000 wiederhergestellt. Ohne Manifest sind die Standard-IDs 0 für Bank 1, 1 für gemeinsamen Code und 2 für CHR. Ein eigenes Manifest muss das Boot-PRG in 16.384-Byte-Dateien bei $6000 und $A000 aufteilen. Für Banken ab 2 lautet die Datei-ID --fds-overlay-start-id (standardmäßig 32) + Banknummer − 2.

Mit der standardmäßigen Belegungsprüfung gibt eine bereits vermerkte Bank ohne Diskettenzugriff null zurück. --fds-no-overlay-guard deaktiviert diese Abkürzung in __fds_load_bank(). Ein erfolgreicher Ladevorgang vermerkt die angeforderte Bank. Dieser Vermerk ist keine Prüfung des Speicherinhalts.

Das direkte Laden einer anderen Bank aus Code im Fenster $6000–$9FFF kann die laufende Funktion oder deren Rücksprungadresse überschreiben. Das Beispiel legt Aufrufer und Anzeigecode in #pragma fixed_bank 0 ab. Um eine Funktion in einer anderen Bank aufzurufen und zurückzukehren, verwenden Sie __fds_overlay_farcall() oder einen normalen typisierten Funktionsaufruf.

BIOS-Aufrufe sind synchron. Diese API hält während des Ladens die Spielschleife nicht einmal pro Frame am Laufen. Verwenden Sie sie aus normalem Steuercode in der gemeinsamen Bank, nicht aus einem Interrupt-Handler. Bild- und Audiokontinuität sowie das Zusammenspiel mit dem echten BIOS müssen separat geprüft werden.

Diese Aufnahmen nutzen einen eigens geschriebenen Test für die LoadFiles-Aufrufkonvention, um kompilierten CPU-Code, Speichertransfers und jedes angezeigte Pixel zu prüfen. Echtes BIOS, Diskettentransfer-Timing, CRC und Hardwarestart werden damit nicht geprüft. KUROSAKI bildet den FDS-Controller nur teilweise nach. Eine gewöhnliche Ausführung ohne diesen Testbaustein belegt nicht dieselben Übertragungsergebnisse.

Der erste Compilerbefehl unten baut das herunterladbare Beispiel. Das Python-Prüfprogramm baut alle fünf Lektionen mit und ohne Optimierung und prüft sie anschließend in KUROSAKI. Es erstellt und entfernt einen temporären Testbaustein aus scripts/fds_abi_fixture.py; für diese Prüfungen ist kein externes BIOS nötig. Der Testbaustein ist kein BIOS für normale Spiele.

Anwendungsbeispiel

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();}
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Mit dem eigens geschriebenen LoadFiles-ABI-Test sind LOAD BANK 2 STATUS, REQUIRE SAME BANK und RESTORE BANK 1 jeweils 000. FIRST / LAST BYTE zeigt 011 und 044 aus den vier übertragenen Bytes; RESIDENT BANK ist nach der Wiederherstellung 001. Die ungültigen Banken 0 und 255 liefern jeweils 255. Das Anfordern derselben Bank löst keinen weiteren Ladevorgang aus: Die geprüften Dateianforderungen sind 32 und danach 0.

__fds_load_bank: BANK_LOAD-DEFAULT — Mit dem eigens geschriebenen LoadFiles-ABI-Test sind LOAD BANK 2 STATUS, REQUIRE SAME BANK und RESTORE BANK 1 jeweils 000. FIRST / LAST BYTE zeigt 011 und 044 aus den vier übertragenen Bytes; RESIDENT BANK ist nach der Wiederherstellung 001. Die ungültigen Banken 0 und 255 liefern jeweils 255. Das Anfordern derselben Bank löst keinen weiteren Ladevorgang aus: Die geprüften Dateianforderungen sind 32 und danach 0.
BANK_LOAD-DEFAULT — Mit dem eigens geschriebenen LoadFiles-ABI-Test sind LOAD BANK 2 STATUS, REQUIRE SAME BANK und RESTORE BANK 1 jeweils 000. FIRST / LAST BYTE zeigt 011 und 044 aus den vier übertragenen Bytes; RESIDENT BANK ist nach der Wiederherstellung 001. Die ungültigen Banken 0 und 255 liefern jeweils 255. Das Anfordern derselben Bank löst keinen weiteren Ladevorgang aus: Die geprüften Dateianforderungen sind 32 und danach 0.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/fds_overlay.h:11

Deklarationshinweise im Original
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

Ruft BIOS LoadFiles mit einer Disketten-Datei-ID statt einer logischen Banknummer auf. Der Dateiheader bestimmt das Ziel.

Syntax

c
u8 __fds_load_overlay(u8 id);

Parameter

id

Disketten-Datei-ID von 0 bis 254. Übergeben Sie nicht 255; dieser Wert beendet die BIOS-Dateiliste. Im Standardlayout enthält ID 32 die Bank 2.

Rückgabewert

BIOS-Fehlerbyte; null bedeutet Erfolg. Nach Erfolg wie Fehlschlag wird die Belegung mit 255 (unbekannt) vermerkt. Eine Datei-ID allein legt keine Zuordnung zu einer logischen Bank fest.

Bedingungen und Grenzen

Verwenden Sie dies mit --mapper=fds --fds-layout=fds32. Bank 0 enthält gemeinsamen Code bei $A000–$DFFF. Beim Start belegt Bank 1 den Bereich $6000–$9FFF; Banken ab 2 sind Overlays, die in dasselbe 16-KiB-Fenster geladen werden. Platzieren Sie Overlaycode oder -daten mit Direktiven wie #pragma fixed_bank 2.

Erstellt eine Liste mit der angeforderten ID und dem Abschlusswert 255. Prüfen Sie das Ziel im Manifest, damit die Datei weder gemeinsamen noch laufenden Code überschreibt. Verwenden Sie für logische Overlays vorzugsweise __fds_load_bank(), das die Belegung verwaltet.

Das direkte Laden einer anderen Bank aus Code im Fenster $6000–$9FFF kann die laufende Funktion oder deren Rücksprungadresse überschreiben. Das Beispiel legt Aufrufer und Anzeigecode in #pragma fixed_bank 0 ab. Um eine Funktion in einer anderen Bank aufzurufen und zurückzukehren, verwenden Sie __fds_overlay_farcall() oder einen normalen typisierten Funktionsaufruf.

BIOS-Aufrufe sind synchron. Diese API hält während des Ladens die Spielschleife nicht einmal pro Frame am Laufen. Verwenden Sie sie aus normalem Steuercode in der gemeinsamen Bank, nicht aus einem Interrupt-Handler. Bild- und Audiokontinuität sowie das Zusammenspiel mit dem echten BIOS müssen separat geprüft werden.

Diese Aufnahmen nutzen einen eigens geschriebenen Test für die LoadFiles-Aufrufkonvention, um kompilierten CPU-Code, Speichertransfers und jedes angezeigte Pixel zu prüfen. Echtes BIOS, Diskettentransfer-Timing, CRC und Hardwarestart werden damit nicht geprüft. KUROSAKI bildet den FDS-Controller nur teilweise nach. Eine gewöhnliche Ausführung ohne diesen Testbaustein belegt nicht dieselben Übertragungsergebnisse.

Der erste Compilerbefehl unten baut das herunterladbare Beispiel. Das Python-Prüfprogramm baut alle fünf Lektionen mit und ohne Optimierung und prüft sie anschließend in KUROSAKI. Es erstellt und entfernt einen temporären Testbaustein aus scripts/fds_abi_fixture.py; für diese Prüfungen ist kein externes BIOS nötig. Der Testbaustein ist kein BIOS für normale Spiele.

Anwendungsbeispiel

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();}
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Mit dem eigens geschriebenen LoadFiles-ABI-Test ist FILE 32 LOAD STATUS 000; das erste und letzte Byte der übertragenen Daten sind 011 und 044. Nach dem direkten Laden ist UNKNOWN BANK RECORD 255 und IS BANK 1 RESIDENT 000. REQUIRE BANK 1 gelingt mit 000, danach ist RESIDENT AFTER RELOAD 001. Dies verdeutlicht den Unterschied zwischen Laden per Datei-ID und Verwaltung einer logischen Bank.

__fds_load_overlay: RAW_OVERLAY-DEFAULT — Mit dem eigens geschriebenen LoadFiles-ABI-Test ist FILE 32 LOAD STATUS 000; das erste und letzte Byte der übertragenen Daten sind 011 und 044. Nach dem direkten Laden ist UNKNOWN BANK RECORD 255 und IS BANK 1 RESIDENT 000. REQUIRE BANK 1 gelingt mit 000, danach ist RESIDENT AFTER RELOAD 001. Dies verdeutlicht den Unterschied zwischen Laden per Datei-ID und Verwaltung einer logischen Bank.
RAW_OVERLAY-DEFAULT — Mit dem eigens geschriebenen LoadFiles-ABI-Test ist FILE 32 LOAD STATUS 000; das erste und letzte Byte der übertragenen Daten sind 011 und 044. Nach dem direkten Laden ist UNKNOWN BANK RECORD 255 und IS BANK 1 RESIDENT 000. REQUIRE BANK 1 gelingt mit 000, danach ist RESIDENT AFTER RELOAD 001. Dies verdeutlicht den Unterschied zwischen Laden per Datei-ID und Verwaltung einer logischen Bank.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/fds_overlay.h:7

Deklarationshinweise im Original
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

Ruft eine parameterlose Funktion in einem Overlay auf und stellt vor der Rückkehr die vorherige Bank wieder her.

Syntax

c
u8 __fds_overlay_farcall(u8 bank, u16 func);

Parameter

bank

Ausdruck für die Bank. Er wird vor dem Aufruf einmal ausgewertet; die tatsächlich geladene Bank ergibt sich jedoch aus der gelinkten Platzierung von func. Damit wird keine Adresse in einer beliebig zur Laufzeit gewählten Bank aufgerufen. Übergeben Sie einen passenden Wert wie __bankof(func).

func

Name einer deklarierten Funktion ohne Parameter. Das Beispiel übergibt u8 overlay_answer(void). Numerische Adressen, Laufzeit-Funktionszeiger und Funktionen mit erforderlichen Argumenten werden abgewiesen. Verwenden Sie bei benötigten Argumenten einen normalen Aufruf wie target(a, b).

Rückgabewert

Acht-Bit-Ergebnis der Zielfunktion. Schlägt das Laden fehl, lässt sich aber die vorherige Bank wiederherstellen, wird die Zielfunktion nicht aufgerufen; stattdessen kommt das Ladefehlerbyte zurück. Legen Sie die Rückgabekonvention so fest, dass Fehler von normalen Ergebnissen unterscheidbar sind.

Bedingungen und Grenzen

Verwenden Sie dies mit --mapper=fds --fds-layout=fds32. Bank 0 enthält gemeinsamen Code bei $A000–$DFFF. Beim Start belegt Bank 1 den Bereich $6000–$9FFF; Banken ab 2 sind Overlays, die in dasselbe 16-KiB-Fenster geladen werden. Platzieren Sie Overlaycode oder -daten mit Direktiven wie #pragma fixed_bank 2.

Bank 1 wird aus einer separaten Boot-PRG-Datei mit 16.384 Bytes bei $6000 wiederhergestellt. Ohne Manifest sind die Standard-IDs 0 für Bank 1, 1 für gemeinsamen Code und 2 für CHR. Ein eigenes Manifest muss das Boot-PRG in 16.384-Byte-Dateien bei $6000 und $A000 aufteilen. Für Banken ab 2 lautet die Datei-ID --fds-overlay-start-id (standardmäßig 32) + Banknummer − 2.

Bei einem Aufruf in eine andere Bank wird die vorherige Bank gespeichert und vor der Rückkehr wiederhergestellt. Für verschachtelte Aufrufe gilt dasselbe. Auch nach einem teilweise fehlgeschlagenen Transfer wird eine Wiederherstellung versucht. Scheitert diese selbst, hält die Ausführung im gemeinsamen Code an, statt zu überschriebenem Code zurückzukehren. Ziele in der gemeinsamen Bank und Aufrufe innerhalb derselben Bank vermeiden unnötige Transfers.

BIOS-Aufrufe sind synchron. Diese API hält während des Ladens die Spielschleife nicht einmal pro Frame am Laufen. Verwenden Sie sie aus normalem Steuercode in der gemeinsamen Bank, nicht aus einem Interrupt-Handler. Bild- und Audiokontinuität sowie das Zusammenspiel mit dem echten BIOS müssen separat geprüft werden.

Diese Aufnahmen nutzen einen eigens geschriebenen Test für die LoadFiles-Aufrufkonvention, um kompilierten CPU-Code, Speichertransfers und jedes angezeigte Pixel zu prüfen. Echtes BIOS, Diskettentransfer-Timing, CRC und Hardwarestart werden damit nicht geprüft. KUROSAKI bildet den FDS-Controller nur teilweise nach. Eine gewöhnliche Ausführung ohne diesen Testbaustein belegt nicht dieselben Übertragungsergebnisse.

Der erste Compilerbefehl unten baut das herunterladbare Beispiel. Das Python-Prüfprogramm baut alle fünf Lektionen mit und ohne Optimierung und prüft sie anschließend in KUROSAKI. Es erstellt und entfernt einen temporären Testbaustein aus scripts/fds_abi_fixture.py; für diese Prüfungen ist kein externes BIOS nötig. Der Testbaustein ist kein BIOS für normale Spiele.

Anwendungsbeispiel

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();}
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Mit dem eigens geschriebenen LoadFiles-ABI-Test ist RETURN VALUE 042; CALLBACK COUNT und BANK EXPR COUNT sind jeweils 001; BANK IN CALLBACK ist 002 und BANK AFTER RETURN 001. Funktion und Bankausdruck liefen jeweils einmal, und Bank 1 wurde nach dem Aufruf von Bank 2 wiederhergestellt. Dies ist keine Aufnahme eines echten Diskettentransfertests.

__fds_overlay_farcall: OVERLAY_CALL-DEFAULT — Mit dem eigens geschriebenen LoadFiles-ABI-Test ist RETURN VALUE 042; CALLBACK COUNT und BANK EXPR COUNT sind jeweils 001; BANK IN CALLBACK ist 002 und BANK AFTER RETURN 001. Funktion und Bankausdruck liefen jeweils einmal, und Bank 1 wurde nach dem Aufruf von Bank 2 wiederhergestellt. Dies ist keine Aufnahme eines echten Diskettentransfertests.
OVERLAY_CALL-DEFAULT — Mit dem eigens geschriebenen LoadFiles-ABI-Test ist RETURN VALUE 042; CALLBACK COUNT und BANK EXPR COUNT sind jeweils 001; BANK IN CALLBACK ist 002 und BANK AFTER RETURN 001. Funktion und Bankausdruck liefen jeweils einmal, und Bank 1 wurde nach dem Aufruf von Bank 2 wiederhergestellt. Dies ist keine Aufnahme eines echten Diskettentransfertests.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/fds_overlay.h:23

Deklarationshinweise im Original
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

Gibt die Anzahl der vom Linker in Overlaybanken ab 2 platzierten Funktionen zurück. Das ist weder die Anzahl residenter Funktionen noch die Dateianzahl.

Syntax

c
u8 __fds_overlay_function_count(void);

Rückgabewert

Führendes Anzahlbyte der Tabelle von 0 bis 255. Null, wenn es keine Overlayfunktionen gibt oder --fds-no-overlay-table verwendet wird. Mehr als 255 Einträge werden mit KQFC2511 abgewiesen.

Bedingungen und Grenzen

Bauen Sie mit --mapper=fds. Bei einem Cartridge-Mapper wird der Aufruf einer FDS-exklusiven Funktion außer __fds_available mit KQFC2420 abgewiesen.

Die Codegenerierung legt einen Null-Platzhalter an. Der native FDS-Linker ersetzt ihn nach Bank- und Adressplatzierung durch die Tabelle. Das Beispiel legt zwei Funktionen in Bank 2 ab, ohne sie aufzurufen. Die Anfangsdaten bei --fds-layout=legacy sind keine vollständige Zählung der Overlayfunktionen.

Die Aufnahme nutzt KUROSAKIs direktes Laden der Disketten-Bootdateien ins RAM. Das Beispiel ruft keine BIOS-Routine auf und prüft weder Dateitransfers noch Diskettenwechsel oder Hardwarestart. Die leere Testfirmware ist kein Ersatz-BIOS. Das Beispiel aktualisiert die Anzeige per NMI und deaktiviert maskierbare IRQs, die diese Abfragen nicht benötigen.

Das Buildbeispiel erzeugt eine .fds-Datei mit nativem Layout ohne zusätzliche Bootdateien für den Genehmigungsbildschirm. Diese Konfiguration prüft KUROSAKIs Direktstart; das Startverfahren einer echten Diskette muss separat validiert werden.

Anwendungsbeispiel

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(); }
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

CURRENT BANK ist 1, BANK 1 RESIDENT ist 1, BANK 2 RESIDENT ist 0 und OVERLAY FUNCTIONS ist 2. Zwei in Bank 2 abgelegte Funktionen machen diese Bank beim Start noch nicht resident. Im Prüfbuild mit --fds-no-overlay-table ändert sich nur der letzte Wert auf null.

__fds_overlay_function_count: RESIDENCY-FDS-DEFAULT — CURRENT BANK ist 1, BANK 1 RESIDENT ist 1, BANK 2 RESIDENT ist 0 und OVERLAY FUNCTIONS ist 2. Zwei in Bank 2 abgelegte Funktionen machen diese Bank beim Start noch nicht resident. Im Prüfbuild mit --fds-no-overlay-table ändert sich nur der letzte Wert auf null.
RESIDENCY-FDS-DEFAULT — CURRENT BANK ist 1, BANK 1 RESIDENT ist 1, BANK 2 RESIDENT ist 0 und OVERLAY FUNCTIONS ist 2. Zwei in Bank 2 abgelegte Funktionen machen diese Bank beim Start noch nicht resident. Im Prüfbuild mit --fds-no-overlay-table ändert sich nur der letzte Wert auf null.
__fds_overlay_function_count: RESIDENCY-FDS-TABLE-OFF-DEFAULT — CURRENT BANK ist 1, BANK 1 RESIDENT ist 1, BANK 2 RESIDENT ist 0 und OVERLAY FUNCTIONS ist 2. Zwei in Bank 2 abgelegte Funktionen machen diese Bank beim Start noch nicht resident. Im Prüfbuild mit --fds-no-overlay-table ändert sich nur der letzte Wert auf null.
RESIDENCY-FDS-TABLE-OFF-DEFAULT — CURRENT BANK ist 1, BANK 1 RESIDENT ist 1, BANK 2 RESIDENT ist 0 und OVERLAY FUNCTIONS ist 2. Zwei in Bank 2 abgelegte Funktionen machen diese Bank beim Start noch nicht resident. Im Prüfbuild mit --fds-no-overlay-table ändert sich nur der letzte Wert auf null.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/fds_overlay.h:20

Deklarationshinweise im Original
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

Lädt eine Bank nur, wenn sie noch nicht als resident vermerkt ist. Ein Aufruf vor dem Ressourcenzugriff vermeidet unnötige Transfers, wenn die Daten bereits verfügbar sind.

Syntax

c
u8 __fds_require_bank(u8 bank);

Parameter

bank

Zu ladende logische Bank, 1 bis 254. Bank 0 ist gemeinsamer Code und kein ladbares Fenster; 255 ist für unbekannte Bankbelegung reserviert. Bei Banken ab 2 muss auch die berechnete Datei-ID in 0 bis 254 liegen.

Rückgabewert

Bei Erfolg null. Gibt 255 für Bank 0 oder 255, einen Datei-ID-Überlauf oder Bank 1 ohne Wiederherstellungsdatei zurück. Ein fehlgeschlagener BIOS-Transfer liefert dessen Fehlerbyte. Argumentfehler vor dem Transfer bewahren den Belegungsvermerk; fehlgeschlagene BIOS-Transfers setzen ihn auf 255 (unbekannt).

Bedingungen und Grenzen

Verwenden Sie dies mit --mapper=fds --fds-layout=fds32. Bank 0 enthält gemeinsamen Code bei $A000–$DFFF. Beim Start belegt Bank 1 den Bereich $6000–$9FFF; Banken ab 2 sind Overlays, die in dasselbe 16-KiB-Fenster geladen werden. Platzieren Sie Overlaycode oder -daten mit Direktiven wie #pragma fixed_bank 2.

Eine gültige Bank, die dem Vermerk entspricht, liefert null; andernfalls wird __fds_load_bank() aufgerufen. Der anfängliche Vergleich bleibt auch mit --fds-no-overlay-guard aktiv. Bank 255 gilt niemals als resident, selbst wenn der Vermerk 255 enthält.

Bank 1 wird aus einer separaten Boot-PRG-Datei mit 16.384 Bytes bei $6000 wiederhergestellt. Ohne Manifest sind die Standard-IDs 0 für Bank 1, 1 für gemeinsamen Code und 2 für CHR. Ein eigenes Manifest muss das Boot-PRG in 16.384-Byte-Dateien bei $6000 und $A000 aufteilen. Für Banken ab 2 lautet die Datei-ID --fds-overlay-start-id (standardmäßig 32) + Banknummer − 2.

Das direkte Laden einer anderen Bank aus Code im Fenster $6000–$9FFF kann die laufende Funktion oder deren Rücksprungadresse überschreiben. Das Beispiel legt Aufrufer und Anzeigecode in #pragma fixed_bank 0 ab. Um eine Funktion in einer anderen Bank aufzurufen und zurückzukehren, verwenden Sie __fds_overlay_farcall() oder einen normalen typisierten Funktionsaufruf.

BIOS-Aufrufe sind synchron. Diese API hält während des Ladens die Spielschleife nicht einmal pro Frame am Laufen. Verwenden Sie sie aus normalem Steuercode in der gemeinsamen Bank, nicht aus einem Interrupt-Handler. Bild- und Audiokontinuität sowie das Zusammenspiel mit dem echten BIOS müssen separat geprüft werden.

Diese Aufnahmen nutzen einen eigens geschriebenen Test für die LoadFiles-Aufrufkonvention, um kompilierten CPU-Code, Speichertransfers und jedes angezeigte Pixel zu prüfen. Echtes BIOS, Diskettentransfer-Timing, CRC und Hardwarestart werden damit nicht geprüft. KUROSAKI bildet den FDS-Controller nur teilweise nach. Eine gewöhnliche Ausführung ohne diesen Testbaustein belegt nicht dieselben Übertragungsergebnisse.

Der erste Compilerbefehl unten baut das herunterladbare Beispiel. Das Python-Prüfprogramm baut alle fünf Lektionen mit und ohne Optimierung und prüft sie anschließend in KUROSAKI. Es erstellt und entfernt einen temporären Testbaustein aus scripts/fds_abi_fixture.py; für diese Prüfungen ist kein externes BIOS nötig. Der Testbaustein ist kein BIOS für normale Spiele.

Anwendungsbeispiel

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();}
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Mit dem eigens geschriebenen LoadFiles-ABI-Test sind LOAD BANK 2 STATUS, REQUIRE SAME BANK und RESTORE BANK 1 jeweils 000. FIRST / LAST BYTE zeigt 011 und 044 aus den vier übertragenen Bytes; RESIDENT BANK ist nach der Wiederherstellung 001. Die ungültigen Banken 0 und 255 liefern jeweils 255. Das Anfordern derselben Bank löst keinen weiteren Ladevorgang aus: Die geprüften Dateianforderungen sind 32 und danach 0.

__fds_require_bank: BANK_LOAD-DEFAULT — Mit dem eigens geschriebenen LoadFiles-ABI-Test sind LOAD BANK 2 STATUS, REQUIRE SAME BANK und RESTORE BANK 1 jeweils 000. FIRST / LAST BYTE zeigt 011 und 044 aus den vier übertragenen Bytes; RESIDENT BANK ist nach der Wiederherstellung 001. Die ungültigen Banken 0 und 255 liefern jeweils 255. Das Anfordern derselben Bank löst keinen weiteren Ladevorgang aus: Die geprüften Dateianforderungen sind 32 und danach 0.
BANK_LOAD-DEFAULT — Mit dem eigens geschriebenen LoadFiles-ABI-Test sind LOAD BANK 2 STATUS, REQUIRE SAME BANK und RESTORE BANK 1 jeweils 000. FIRST / LAST BYTE zeigt 011 und 044 aus den vier übertragenen Bytes; RESIDENT BANK ist nach der Wiederherstellung 001. Die ungültigen Banken 0 und 255 liefern jeweils 255. Das Anfordern derselben Bank löst keinen weiteren Ladevorgang aus: Die geprüften Dateianforderungen sind 32 und danach 0.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/fds_overlay.h:13

Deklarationshinweise im Original
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-Wavetable-Audio

Deklariert Compiler-Intrinsics für 64-stufige Wellenform, Modulationstabelle, Tonhöhe, Lautstärke und Hüllkurven. KITAQFC erzeugt ihre Implementierungen.

Erstellen Sie mit --mapper=fds ein .fds-Abbild. Diese Lektion verwendet Sound ohne Datenträger-I/O. Die Aufnahme nutzt KUROSAKIs direkten RAM-Start und Audiomodell. Die leere Testfirmware implementiert keine BIOS-Routinen und wird nicht verteilt. Normaler Diskettenstart und echte Hardware benötigen eine passende Startumgebung.

__fds_env_set

Schreibt die drei rohen Argumente der Reihe nach nach $4080, $4084 und $408A.

Syntax

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

Parameter

speed

Trotz des Parameternamens ist dies der rohe $4080-Wert: Bit 7 direkte Lautstärke, Bit 6 steigende Hüllkurve.

gain

Roher Modulationshüllkurvenwert für $4084.

mode

In $408A geschriebene Hüllkurven-Hauptgeschwindigkeit.

Rückgabewert

Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.

Bedingungen und Grenzen

Erstellen Sie mit --mapper=fds ein .fds-Abbild. Diese Lektion verwendet Sound ohne Datenträger-I/O. Die Aufnahme nutzt KUROSAKIs direkten RAM-Start und Audiomodell. Die leere Testfirmware implementiert keine BIOS-Routinen und wird nicht verteilt. Normaler Diskettenstart und echte Hardware benötigen eine passende Startumgebung.

Anwendungsbeispiel

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();
}

Erwartetes Ergebnis

Ein eigenes 64-stufiges Dreieck spielt bei etwa 440 Hz, pausiert, spielt bei etwa 660 Hz und stoppt. Die zweite Note nutzt nes_fds_wave_load und rohe Hüllkurveneinstellungen. Geprüft werden Audiomodell und Direktstart, nicht BIOS-gesteuerter Diskettenstart.

__fds_env_set: NTSC
NTSC
NTSC — Aufgenommenes Audio

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/fds_sound.h:18

Deklarationshinweise im Original
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

Schreibt die 12-Bit-Frequenz nach $4082/$4083 und löscht die oberen Steuerbits von $4083.

Syntax

c
void __fds_freq_set(u16 freq);

Parameter

freq

FDS-Frequenzregisterwert 0..4095. Obere Steuerbits werden gelöscht.

Rückgabewert

Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.

Bedingungen und Grenzen

Erstellen Sie mit --mapper=fds ein .fds-Abbild. Diese Lektion verwendet Sound ohne Datenträger-I/O. Die Aufnahme nutzt KUROSAKIs direkten RAM-Start und Audiomodell. Die leere Testfirmware implementiert keine BIOS-Routinen und wird nicht verteilt. Normaler Diskettenstart und echte Hardware benötigen eine passende Startumgebung.

Anwendungsbeispiel

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();
}

Erwartetes Ergebnis

Ein eigenes 64-stufiges Dreieck spielt bei etwa 440 Hz, pausiert, spielt bei etwa 660 Hz und stoppt. Die zweite Note nutzt nes_fds_wave_load und rohe Hüllkurveneinstellungen. Geprüft werden Audiomodell und Direktstart, nicht BIOS-gesteuerter Diskettenstart.

__fds_freq_set: NTSC
NTSC
NTSC — Aufgenommenes Audio

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/fds_sound.h:13

Deklarationshinweise im Original
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

Sendet 32 Bytes der Reihe nach an den Modulationstabellen-Schreibport $4088.

Syntax

c
void __fds_mod_load(const u8* mod32);

Parameter

mod32

Zweiunddreißig Bytes für die FDS-Modulationstabelle mit je einem Steuercode 0..7. Stoppen Sie die Modulation vor dem Laden.

Rückgabewert

Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.

Bedingungen und Grenzen

Erstellen Sie mit --mapper=fds ein .fds-Abbild. Diese Lektion verwendet Sound ohne Datenträger-I/O. Die Aufnahme nutzt KUROSAKIs direkten RAM-Start und Audiomodell. Die leere Testfirmware implementiert keine BIOS-Routinen und wird nicht verteilt. Normaler Diskettenstart und echte Hardware benötigen eine passende Startumgebung.

Anwendungsbeispiel

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();
}

Erwartetes Ergebnis

Ein eigenes 64-stufiges Dreieck spielt bei etwa 440 Hz, pausiert, spielt bei etwa 660 Hz und stoppt. Die zweite Note nutzt nes_fds_wave_load und rohe Hüllkurveneinstellungen. Geprüft werden Audiomodell und Direktstart, nicht BIOS-gesteuerter Diskettenstart.

__fds_mod_load: NTSC
NTSC
NTSC — Aufgenommenes Audio

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/fds_sound.h:11

Deklarationshinweise im Original
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

Schreibt 0x83 in Disk-/Soundsteuerung $4023 und setzt Wellensteuerung $4089 sowie Geschwindigkeit $408A auf null.

Syntax

c
void __fds_sound_enable(void);

Rückgabewert

Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.

Bedingungen und Grenzen

Erstellen Sie mit --mapper=fds ein .fds-Abbild. Diese Lektion verwendet Sound ohne Datenträger-I/O. Die Aufnahme nutzt KUROSAKIs direkten RAM-Start und Audiomodell. Die leere Testfirmware implementiert keine BIOS-Routinen und wird nicht verteilt. Normaler Diskettenstart und echte Hardware benötigen eine passende Startumgebung.

Anwendungsbeispiel

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();
}

Erwartetes Ergebnis

Ein eigenes 64-stufiges Dreieck spielt bei etwa 440 Hz, pausiert, spielt bei etwa 660 Hz und stoppt. Die zweite Note nutzt nes_fds_wave_load und rohe Hüllkurveneinstellungen. Geprüft werden Audiomodell und Direktstart, nicht BIOS-gesteuerter Diskettenstart.

__fds_sound_enable: NTSC
NTSC
NTSC — Aufgenommenes Audio

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/fds_sound.h:7

Deklarationshinweise im Original
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

Schreibt die unteren sechs Lautstärkebits zusammen mit 0x80 nach $4080 zur direkten Lautstärkesteuerung.

Syntax

c
void __fds_volume_set(u8 vol);

Parameter

vol

Direkte FDS-Lautstärke 0..63; die Sechsbitmaske lässt 64 auf null umlaufen.

Rückgabewert

Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.

Bedingungen und Grenzen

Erstellen Sie mit --mapper=fds ein .fds-Abbild. Diese Lektion verwendet Sound ohne Datenträger-I/O. Die Aufnahme nutzt KUROSAKIs direkten RAM-Start und Audiomodell. Die leere Testfirmware implementiert keine BIOS-Routinen und wird nicht verteilt. Normaler Diskettenstart und echte Hardware benötigen eine passende Startumgebung.

Anwendungsbeispiel

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();
}

Erwartetes Ergebnis

Ein eigenes 64-stufiges Dreieck spielt bei etwa 440 Hz, pausiert, spielt bei etwa 660 Hz und stoppt. Die zweite Note nutzt nes_fds_wave_load und rohe Hüllkurveneinstellungen. Geprüft werden Audiomodell und Direktstart, nicht BIOS-gesteuerter Diskettenstart.

__fds_volume_set: NTSC
NTSC
NTSC — Aufgenommenes Audio

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/fds_sound.h:15

Deklarationshinweise im Original
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

Aktiviert Wellenschreibzugriffe, kopiert 64 Bytes nach $4040..$407F und setzt $4089 auf null zurück.

Syntax

c
void __fds_wave_load(const u8* wave64);

Parameter

wave64

Vierundsechzig FDS-Wellenbytes mit je einer Sechsbitamplitude 0..63. NULL wird nicht geprüft.

Rückgabewert

Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.

Bedingungen und Grenzen

Erstellen Sie mit --mapper=fds ein .fds-Abbild. Diese Lektion verwendet Sound ohne Datenträger-I/O. Die Aufnahme nutzt KUROSAKIs direkten RAM-Start und Audiomodell. Die leere Testfirmware implementiert keine BIOS-Routinen und wird nicht verteilt. Normaler Diskettenstart und echte Hardware benötigen eine passende Startumgebung.

Anwendungsbeispiel

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();
}

Erwartetes Ergebnis

Ein eigenes 64-stufiges Dreieck spielt bei etwa 440 Hz, pausiert, spielt bei etwa 660 Hz und stoppt. Die zweite Note nutzt nes_fds_wave_load und rohe Hüllkurveneinstellungen. Geprüft werden Audiomodell und Direktstart, nicht BIOS-gesteuerter Diskettenstart.

__fds_wave_load: NTSC
NTSC
NTSC — Aufgenommenes Audio

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/fds_sound.h:9

Deklarationshinweise im Original
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 — Compiler-Intrinsics

__nes_audio_vblank_tick

NMI-Hook, der die BGM- und SFX-Dauern fortschreibt und die APU gemäß der aktuellen Kanalzuordnung aktualisiert.

Syntax

c
void __nes_audio_vblank_tick(void);

Rückgabewert

Kein Rückgabewert.

Bedingungen und Grenzen

Binden Sie audio_vblank.h ein und nehmen Sie audio_vblank.c genau einmal in den Build auf. Der Compiler lässt sowohl den Standard-NMI-Handler als auch einen eigenen NMI-Handler den Musik-Hook einmal aufrufen. Aktivieren Sie PPU-NMI. Die Hauptschleife ruft keine Musikaktualisierung auf.

Dieser Vierkanaltreiber ist auf NTSC abgestimmt: CH1=Impuls 1, CH2=Impuls 2, CH3=Dreieck und CH4=Rauschen, mit den Indizes 0 bis 3. DMC und Erweiterungsaudio werden nicht wiedergegeben. Verwenden Sie ihn nicht gleichzeitig mit direkten APU-Zugriffen, etwa den Effekten in audio.c. Es gibt weder eine PAL-Tonhöhentabelle noch eine PAL-Tempokorrektur.

Rufen Sie diese Funktion nicht direkt aus Anwendungscode auf. Durch das Linken der Musikbibliothek verbindet der Compiler sie mit NMI. Der Hook sichert A/X/Y und verwendet keinen gemeinsamen Zwischenspeicher für Ausdrücke. Hook und Tonhöhentabellen liegen im festen ROM; innerhalb der NMI findet kein ROM-Bankwechsel statt. Die Zustandssicherung gilt nur für den Musik-Hook selbst. Ein eigener NMI-Handler muss weiterhin den Zustand sichern, den seine übrigen Arbeiten verwenden.

Ein Datensatz besteht aus fünf Bytes: delay, CH1, CH2, CH3, CH4. Eine Dauer von 1 bis 255 gibt an, wie viele NMI-Ticks der neue Zustand anhält. Null beendet die Wiedergabe; die übrigen vier Bytes werden ignoriert. Tonwerte von 0 bis 71 entsprechen C2 bis B7 (24=C4). Rauschwerte von 0 bis 15 wählen eine Periode im langen Modus, 16 bis 31 im kurzen Modus. Für jeden Kanal gilt: 255=NES_AUDIO_HOLD behält den Zustand bei, 254=NES_AUDIO_STOP schaltet ihn stumm.

Anwendungsbeispiel

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

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Beispiel spielt eine eigens komponierte vierkanalige Phrase einmal ab. Während das Hauptprogramm 90 Frames lang Ganzzahlberechnungen ohne Nachfüllen ausführt, läuft die Musik weiter. „PHRASE COMPLETE / SILENT“ und „UNDERRUNS 0 / MATH OK“ bedeuten, dass die Wiedergabe beendet ist, kein Datenmangel auftrat und die Rechenergebnisse stimmen. Prüfen Sie in der Audioaufnahme die Impulsmelodie und ihre Begleitung, den Dreiecksbass und das Rauschen. Am Ende herrscht Stille. Das Bild zeigt die Steuerungsergebnisse; den Klang beurteilen Sie anhand der Audioaufnahme.

__nes_audio_vblank_tick: NROM
NROM
NROM — Aufgenommenes Audio

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/audio_vblank.h:70

Deklarationshinweise im Original
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

Schreibt das gesamte gepackte Attributbyte an 0x23C0 + (y>>2)*8 + (x>>2) in Nametable 0. X/Y sind Kachelkoordinaten, die einen Attributbereich von 4×4 Kacheln bestimmen. Alle vier 2-Bit-Palettenfelder werden ersetzt; es wird nicht nur ein ausgewählter Quadrant eingemischt.

Syntax

c
void __attr_set(u8 x, u8 y, u8 attr);

Parameter

x / y

Kachelkoordinaten, keine Pixel. Verwenden Sie x=0..31 und y=0..29 für die sichtbare Karte von 32×30 Kacheln; jede Kachel umfasst 8×8 Pixel. Die Byteargumente werden nicht begrenzt und können außerhalb des vorgesehenen Kachelbereichs schreiben.

attr

Gepacktes Attributbyte: Bits 0–1 wählen die oberen linken 2×2 Kacheln, 2–3 die oberen rechten, 4–5 die unteren linken und 6–7 die unteren rechten. Jedes Feld wählt Palette 0–3. 0x55, 0xAA und 0xFF wählen für alle vier Quadranten jeweils Palette 1, 2 oder 3.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Direkter PPU-Zugriff erfordert ausgeschaltete Bildausgabe oder einen sicheren VBlank-Zeitraum, in den die gesamte Operation passt. Diese Funktionen warten nicht, legen keine Aufträge an, sperren NMI nicht und stellen den Bildlauf nicht wieder her. Verhindern Sie, dass Interrupts während des Aufrufs den gemeinsamen Latch oder die Adresse verändern. PPUCTRL-Bit 2 wählt eine Zieladressschrittweite von 1 oder 32 pro Byte. Spiegelung und Adressumlauf übernimmt die PPU; CHR-ROM ist nicht beschreibbar. Stellen Sie nach dem Zugriff vor der Bildausgabe den gewünschten Bildlauf ein.

NESdev: PPU-Register

NESdev: PPU-Scrolling

Anwendungsbeispiel

c
for(i=2;i<12;i=(u8)(i+4)) {
__attr_set(i,6,0xFF);__attr_set(i,10,0xFF); // All quadrants select green.
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.

Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).

Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

__attr_set: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:134

Deklarationshinweise im Original
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

Wählt die logische Nametable nt & 3, setzt den PPU-Latch zurück und schreibt ein vollständiges Attributbyte an 0x23C0 + (nt & 3)*0x400 + (y>>2)*8 + (x>>2). Überschreibt alle Palettenquadranten dieses Bytes. Die Tabellenauswahl folgt der aktuellen Cartridge-Spiegelung.

Syntax

c
void __attr_set_nt(u8 nt, u8 x, u8 y, u8 attr);

Parameter

nt

Auswahl der logischen Nametable. Nur die unteren zwei Bits zählen (nt & 3): 4 wählt Tabelle 0, 7 wählt Tabelle 3. Es wird kein unabhängiger Speicher reserviert und keine Mapper-Spiegelung geändert.

x / y

Kachelkoordinaten, keine Pixel. Verwenden Sie x=0..31 und y=0..29 für die sichtbare Karte von 32×30 Kacheln; jede Kachel umfasst 8×8 Pixel. Die Byteargumente werden nicht begrenzt und können außerhalb des vorgesehenen Kachelbereichs schreiben.

attr

Gepacktes Attributbyte: Bits 0–1 wählen die oberen linken 2×2 Kacheln, 2–3 die oberen rechten, 4–5 die unteren linken und 6–7 die unteren rechten. Jedes Feld wählt Palette 0–3. 0x55, 0xAA und 0xFF wählen für alle vier Quadranten jeweils Palette 1, 2 oder 3.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Direkter PPU-Zugriff erfordert ausgeschaltete Bildausgabe oder einen sicheren VBlank-Zeitraum, in den die gesamte Operation passt. Diese Funktionen warten nicht, legen keine Aufträge an, sperren NMI nicht und stellen den Bildlauf nicht wieder her. Verhindern Sie, dass Interrupts während des Aufrufs den gemeinsamen Latch oder die Adresse verändern. PPUCTRL-Bit 2 wählt eine Zieladressschrittweite von 1 oder 32 pro Byte. Spiegelung und Adressumlauf übernimmt die PPU; CHR-ROM ist nicht beschreibbar. Stellen Sie nach dem Zugriff vor der Bildausgabe den gewünschten Bildlauf ein.

NESdev: PPU-Register

NESdev: PPU-Scrolling

Anwendungsbeispiel

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.
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.

Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).

Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

__attr_set_nt: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:136

Deklarationshinweise im Original
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

Ermittelt die logische ROM-Banknummer eines namentlich angegebenen Datenobjekts oder einer Funktion. Verwendet deren Platzierung; es wird weder die aktive Bank abgefragt noch ein Zeiger dereferenziert.

Syntax

c
u8 bank = __bankof(symbol);

Parameter

symbol

Name eines ROM-Datenobjekts oder einer Funktion mit bekannter Compiler- oder Linkerplatzierung. Eine Zeigervariable bezeichnet hier nicht ihr Zielobjekt; Laufzeitausdrücke sind keine zulässigen Symbolnamen.

Rückgabewert

Eine u8-Banknummer. Null kann gemeinsames ROM kennzeichnen, ist aber nicht auf jedem Ziel ein gültiger Umschaltwert.

Anwendungsbeispiel

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

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Beispiel prüft, dass payload und callback beide in Bank 3 liegen. Damit werden Platzierungsmetadaten geprüft; getrennte Lese- und Callback-Prüfungen bestätigen das Bankverhalten zur Laufzeit.

Das vollständige Programm zeigt Datenzugriffe über Bank und Nahadresse, bankübergreifende Callbacks und den Unterschied zwischen Bibliothekszustand und tatsächlicher Zuordnung. Alle Zahlen sind hexadezimal. BANK IN und BANK OUT müssen 02 zeigen, BYTE HEX D3, WORD HEX 5C7A sowie FAILED CHECKS und FIRST FAILURE jeweils 00. CGB und FC zeigen blaue Schrift, DMG schwarze.

CALL COUNT zeigt 02: Das Makro far_call und __farcall rufen den Callback jeweils einmal auf.

__bankof: MMC3
MMC3

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:77

Deklarationshinweise im Original
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

Wählt über die Mapper-spezifische Routine des Compilers eine logische PRG-Bank und aktualisiert deren internen Zustand. Die Zuordnung bleibt bestehen; der separate Zustand der bank-Bibliothek bleibt unverändert.

Syntax

c
void __bankswitch(u8 bank);

Parameter

bank

Vorzeichenlose 8-Bit-ROM-Banknummer. Bei Aufrufen muss sie zur Funktionsplatzierung passen, bei Lesezugriffen wählt sie die Quelle. Verwenden Sie den gültigen Bereich des Modultyps.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Ein dauerhafter Bankwechsel darf weder den laufenden Code noch seine Rückkehradresse ausblenden. Legen Sie den Aufruf, gegebenenfalls bank_switch selbst und den anschließend ausgeführten Code wie im Beispiel in die gemeinsame beziehungsweise feste Bank 0.

Der Bibliothekszustand beginnt bei null und erkennt die Startzuordnung des Moduls nicht. Initialisieren Sie ihn mit bank_switch. Direkte Intrinsics und Mapper-Schreibzugriffe aktualisieren diesen separaten Zustand nicht; bank_get_current kann die tatsächliche Hardwarezuordnung daher nicht prüfen.

FC-Banknummern sind logische Nummern des Compilers. Null bezeichnet gemeinsames ROM, kein Umschaltziel; die Hilfsroutine setzt null auf Bank 1 um. Bei SUROM512 sind nur 1–30 gültig: Eine direkt an das Umschalt-Intrinsic übergebene Konstante außerhalb des Bereichs führt zu einem Buildfehler; ein ungültiger Laufzeitwert wählt 1 und setzt den Mapper-Fehlerzustand. Lesen Sie gemeinsames ROM direkt, statt Bank null anzufordern.

Anwendungsbeispiel

c
__bankswitch(1);
bank_expect(home1[0]==0xA1,19);
bank_expect(bank_get_current()==2,20); // Intrinsics bypass the library record.
__bankswitch(2);

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Der anfängliche Aufruf bank_switch(2) speichert und wählt Bank 2. Ein späterer direkter Wechsel wählt Bank 1 und liest A1 aus home1, während bank_get_current weiterhin 2 meldet. Nach dem Zurückschalten liefert home2 wieder B2.

Das vollständige Programm zeigt Datenzugriffe über Bank und Nahadresse, bankübergreifende Callbacks und den Unterschied zwischen Bibliothekszustand und tatsächlicher Zuordnung. Alle Zahlen sind hexadezimal. BANK IN und BANK OUT müssen 02 zeigen, BYTE HEX D3, WORD HEX 5C7A sowie FAILED CHECKS und FIRST FAILURE jeweils 00. CGB und FC zeigen blaue Schrift, DMG schwarze.

CALL COUNT zeigt 02: Das Makro far_call und __farcall rufen den Callback jeweils einmal auf.

__bankswitch: MMC3
MMC3

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:79

Deklarationshinweise im Original
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

Löscht das gewählte Flag auf 0, ohne den Rest des Bytes zu löschen. Entfernen Sie so eine gespeicherte Bedingung, etwa einen vorübergehenden Status. Erneutes Löschen desselben Bits lässt es bei 0.

Syntax

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

Parameter

base

Zeiger auf das erste Byte des Flagarrays in CPU-zugänglichem Speicher. Der Aufruf reserviert und initialisiert das Array nicht. Verwenden Sie beschreibbaren RAM zum Setzen, Löschen und Umschalten; die Prüfung liest nur.

bit

Bei null beginnender Bitindex als u16 (0–65535), kein Byte-Offset und keine Bitmaske. Index 0 wählt Maske 0x01 in Byte 0, Index 7 wählt 0x80 in Byte 0 und Index 8 wählt 0x01 in Byte 1. Allgemein ist der Byte-Offset index / 8 und die Maske 1 << (index % 8). Für ein N-Byte-Array müssen index < 8*N und ein gültiger CPU-Adressbereich gelten.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Der aufrufende Code muss genügend Speicher bereitstellen. Es gibt weder Längenargument noch Bereichsbegrenzung, Bankwechsel oder automatische Grafikübertragung. Eine große Bitnummer kann ein Byte weit außerhalb des reservierten Arrays auswählen. Halten Sie die Speicherzuordnung stabil; verwenden Sie diese RAM-Flagfunktionen nicht als Abkürzung für Hardwareregister mit Nebenwirkungen beim Lesen oder Schreiben.

Setzen, Löschen und Umschalten lesen ein Byte, ändern das gewählte Bit und schreiben das Byte zurück. Diese Folge ist nicht atomar. Koordinieren Sie Schreibzugriffe von Hauptschleife und Interrupt-Handlern, damit eine zwischenzeitliche Änderung an einem anderen Bit desselben Bytes nicht verloren geht.

FC-Code kann einen konstanten Index direkt einbetten oder eine erzeugte Hilfsfunktion aufrufen. Beide Formen verwenden dieselbe Bitnummerierung und Ergebnismaske. Die Hilfsfunktionen teilen sich Argument- und Arbeitsspeicher; treten Sie während eines anderen Hilfsaufrufs nicht aus NMI/IRQ erneut ein.

Anwendungsbeispiel

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.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das vollständige Programm erreicht diesen Schritt mit Byte 0 gleich 0xA6. Löschen von Bit 2 ergibt 0xA2 (binär 10100010, dezimal 162); CLEAR BIT2 muss 162 zeigen. Erneutes Löschen von Bit 2 ändert nichts. Die anderen gespeicherten Flags bleiben bei beiden Aufrufen erhalten.

Erwartete FC-Zeilen in Dezimaldarstellung: TEST OFF 000, TEST ON 128, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000. NEXT BYTE entsteht durch Setzen von Index 9 im zweiten Byte: 0x80 wird 0x82, Byte 0 bleibt unverändert. Das aufgenommene FC-Bild zeigt blaue Schrift auf Schwarz.

Das Programm prüft jeden angezeigten Zustandswechsel und zeigt die Fehlerzahl. Setzen, Löschen und Umschalten liefern selbst keinen Wert; die Zeilen zeigen das resultierende RAM-Byte. Separate Emulatorprüfungen vergleichen alle 520 Bytes geschützter Puffer in 136 Fällen je Plattform: konstante und laufzeitabhängige Indizes über Byte- und 256-Byte-Grenzen sowie alle acht möglichen Bitprüfmasken ungleich null. Dies sind RAM-Zustandsprüfungen, keine Zeichendemonstration. Physische Hardware und gleichzeitige Interrupt-Schreibzugriffe wurden nicht getestet.

__bit_clear: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:45

Deklarationshinweise im Original
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

Setzt das gewählte Flag auf 1 und erhält die übrigen sieben Bits desselben Bytes. Markieren Sie damit etwa einen erledigten Auftrag oder einen eingesammelten Gegenstand. Ein bereits gesetztes Bit bleibt unverändert; dies ist kein Umschalten.

Syntax

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

Parameter

base

Zeiger auf das erste Byte des Flagarrays in CPU-zugänglichem Speicher. Der Aufruf reserviert und initialisiert das Array nicht. Verwenden Sie beschreibbaren RAM zum Setzen, Löschen und Umschalten; die Prüfung liest nur.

bit

Bei null beginnender Bitindex als u16 (0–65535), kein Byte-Offset und keine Bitmaske. Index 0 wählt Maske 0x01 in Byte 0, Index 7 wählt 0x80 in Byte 0 und Index 8 wählt 0x01 in Byte 1. Allgemein ist der Byte-Offset index / 8 und die Maske 1 << (index % 8). Für ein N-Byte-Array müssen index < 8*N und ein gültiger CPU-Adressbereich gelten.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Der aufrufende Code muss genügend Speicher bereitstellen. Es gibt weder Längenargument noch Bereichsbegrenzung, Bankwechsel oder automatische Grafikübertragung. Eine große Bitnummer kann ein Byte weit außerhalb des reservierten Arrays auswählen. Halten Sie die Speicherzuordnung stabil; verwenden Sie diese RAM-Flagfunktionen nicht als Abkürzung für Hardwareregister mit Nebenwirkungen beim Lesen oder Schreiben.

Setzen, Löschen und Umschalten lesen ein Byte, ändern das gewählte Bit und schreiben das Byte zurück. Diese Folge ist nicht atomar. Koordinieren Sie Schreibzugriffe von Hauptschleife und Interrupt-Handlern, damit eine zwischenzeitliche Änderung an einem anderen Bit desselben Bytes nicht verloren geht.

FC-Code kann einen konstanten Index direkt einbetten oder eine erzeugte Hilfsfunktion aufrufen. Beide Formen verwenden dieselbe Bitnummerierung und Ergebnismaske. Die Hilfsfunktionen teilen sich Argument- und Arbeitsspeicher; treten Sie während eines anderen Hilfsaufrufs nicht aus NMI/IRQ erneut ein.

Anwendungsbeispiel

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.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Setzen Sie Bit 1 von 0xA4: 10100100 | 00000010 ergibt 10100110, also 0xA6 (dezimal 166). SET BIT1 muss 166 zeigen. Das Beispiel setzt das Bit erneut und prüft, dass das Byte 0xA6 bleibt; wiederholtes Setzen invertiert das Flag also nicht.

Erwartete FC-Zeilen in Dezimaldarstellung: TEST OFF 000, TEST ON 128, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000. NEXT BYTE entsteht durch Setzen von Index 9 im zweiten Byte: 0x80 wird 0x82, Byte 0 bleibt unverändert. Das aufgenommene FC-Bild zeigt blaue Schrift auf Schwarz.

Das Programm prüft jeden angezeigten Zustandswechsel und zeigt die Fehlerzahl. Setzen, Löschen und Umschalten liefern selbst keinen Wert; die Zeilen zeigen das resultierende RAM-Byte. Separate Emulatorprüfungen vergleichen alle 520 Bytes geschützter Puffer in 136 Fällen je Plattform: konstante und laufzeitabhängige Indizes über Byte- und 256-Byte-Grenzen sowie alle acht möglichen Bitprüfmasken ungleich null. Dies sind RAM-Zustandsprüfungen, keine Zeichendemonstration. Physische Hardware und gleichzeitige Interrupt-Schreibzugriffe wurden nicht getestet.

__bit_set: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:43

Deklarationshinweise im Original
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

Liest ein Flag aus einem gepackten Bitarray, ohne das Array zu ändern. So teilen sich acht unabhängige Ein/Aus-Flags ein RAM-Byte. Das Ergebnis zeigt, ob das gewählte Bit gelöscht oder gesetzt ist; seine numerische Darstellung hängt vom Zielsystem ab.

Syntax

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

Parameter

base

Zeiger auf das erste Byte des Flagarrays in CPU-zugänglichem Speicher. Der Aufruf reserviert und initialisiert das Array nicht. Verwenden Sie beschreibbaren RAM zum Setzen, Löschen und Umschalten; die Prüfung liest nur.

bit

Bei null beginnender Bitindex als u16 (0–65535), kein Byte-Offset und keine Bitmaske. Index 0 wählt Maske 0x01 in Byte 0, Index 7 wählt 0x80 in Byte 0 und Index 8 wählt 0x01 in Byte 1. Allgemein ist der Byte-Offset index / 8 und die Maske 1 << (index % 8). Für ein N-Byte-Array müssen index < 8*N und ein gültiger CPU-Adressbereich gelten.

Rückgabewert

u8: 0 bei gelöschtem Bit, sonst seine Maske: 1, 2, 4, 8, 16, 32, 64 oder 128. Das Ergebnis ist base[bit >> 3] & (1 << (bit & 7)). Ein gesetztes Bit 7 liefert beispielsweise 128. Prüfen Sie mit result != 0 auf ein gesetztes Flag; ein Vergleich nur mit 1 übersieht Flags an anderen Bitpositionen.

Bedingungen und Grenzen

Der aufrufende Code muss genügend Speicher bereitstellen. Es gibt weder Längenargument noch Bereichsbegrenzung, Bankwechsel oder automatische Grafikübertragung. Eine große Bitnummer kann ein Byte weit außerhalb des reservierten Arrays auswählen. Halten Sie die Speicherzuordnung stabil; verwenden Sie diese RAM-Flagfunktionen nicht als Abkürzung für Hardwareregister mit Nebenwirkungen beim Lesen oder Schreiben.

FC-Code kann einen konstanten Index direkt einbetten oder eine erzeugte Hilfsfunktion aufrufen. Beide Formen verwenden dieselbe Bitnummerierung und Ergebnismaske. Die Hilfsfunktionen teilen sich Argument- und Arbeitsspeicher; treten Sie während eines anderen Hilfsaufrufs nicht aus NMI/IRQ erneut ein.

Anwendungsbeispiel

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.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Beginnen Sie mit Byte 0 gleich 0xA4 (binär 10100100). Die Prüfung von Bit 1 liefert 0, die von Bit 7 auf GB 1 und auf FC 128. Das Beispiel wandelt den Wert ungleich null außerdem in ein gemeinsames Ein/Aus-Flag um und prüft das unveränderte Originalbyte. Beachten Sie TEST OFF und TEST ON im Bild unten.

Erwartete FC-Zeilen in Dezimaldarstellung: TEST OFF 000, TEST ON 128, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000. NEXT BYTE entsteht durch Setzen von Index 9 im zweiten Byte: 0x80 wird 0x82, Byte 0 bleibt unverändert. Das aufgenommene FC-Bild zeigt blaue Schrift auf Schwarz.

Das Programm prüft jeden angezeigten Zustandswechsel und zeigt die Fehlerzahl. Setzen, Löschen und Umschalten liefern selbst keinen Wert; die Zeilen zeigen das resultierende RAM-Byte. Separate Emulatorprüfungen vergleichen alle 520 Bytes geschützter Puffer in 136 Fällen je Plattform: konstante und laufzeitabhängige Indizes über Byte- und 256-Byte-Grenzen sowie alle acht möglichen Bitprüfmasken ungleich null. Dies sind RAM-Zustandsprüfungen, keine Zeichendemonstration. Physische Hardware und gleichzeitige Interrupt-Schreibzugriffe wurden nicht getestet.

__bit_test: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:41

Deklarationshinweise im Original
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

Invertiert das gewählte Flag: 0 wird 1 und 1 wird 0. Die übrigen Bits bleiben erhalten. Zwei Aufrufe auf dasselbe Bit stellen den ursprünglichen Zustand wieder her, sofern dazwischen nichts anderes dieses Bit schreibt.

Syntax

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

Parameter

base

Zeiger auf das erste Byte des Flagarrays in CPU-zugänglichem Speicher. Der Aufruf reserviert und initialisiert das Array nicht. Verwenden Sie beschreibbaren RAM zum Setzen, Löschen und Umschalten; die Prüfung liest nur.

bit

Bei null beginnender Bitindex als u16 (0–65535), kein Byte-Offset und keine Bitmaske. Index 0 wählt Maske 0x01 in Byte 0, Index 7 wählt 0x80 in Byte 0 und Index 8 wählt 0x01 in Byte 1. Allgemein ist der Byte-Offset index / 8 und die Maske 1 << (index % 8). Für ein N-Byte-Array müssen index < 8*N und ein gültiger CPU-Adressbereich gelten.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Der aufrufende Code muss genügend Speicher bereitstellen. Es gibt weder Längenargument noch Bereichsbegrenzung, Bankwechsel oder automatische Grafikübertragung. Eine große Bitnummer kann ein Byte weit außerhalb des reservierten Arrays auswählen. Halten Sie die Speicherzuordnung stabil; verwenden Sie diese RAM-Flagfunktionen nicht als Abkürzung für Hardwareregister mit Nebenwirkungen beim Lesen oder Schreiben.

Setzen, Löschen und Umschalten lesen ein Byte, ändern das gewählte Bit und schreiben das Byte zurück. Diese Folge ist nicht atomar. Koordinieren Sie Schreibzugriffe von Hauptschleife und Interrupt-Handlern, damit eine zwischenzeitliche Änderung an einem anderen Bit desselben Bytes nicht verloren geht.

FC-Code kann einen konstanten Index direkt einbetten oder eine erzeugte Hilfsfunktion aufrufen. Beide Formen verwenden dieselbe Bitnummerierung und Ergebnismaske. Die Hilfsfunktionen teilen sich Argument- und Arbeitsspeicher; treten Sie während eines anderen Hilfsaufrufs nicht aus NMI/IRQ erneut ein.

Anwendungsbeispiel

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.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das vollständige Programm beginnt diesen Schritt mit 0xA2. Umschalten von Bit 7 ergibt 0x22 (dezimal 34), also zeigt TOGGLE BIT7 034. Erneutes Umschalten stellt 0xA2 wieder her; RESTORE zeigt 162. Dies belegt beide Richtungen der Invertierung bei Erhalt der übrigen Bits.

Erwartete FC-Zeilen in Dezimaldarstellung: TEST OFF 000, TEST ON 128, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000. NEXT BYTE entsteht durch Setzen von Index 9 im zweiten Byte: 0x80 wird 0x82, Byte 0 bleibt unverändert. Das aufgenommene FC-Bild zeigt blaue Schrift auf Schwarz.

Das Programm prüft jeden angezeigten Zustandswechsel und zeigt die Fehlerzahl. Setzen, Löschen und Umschalten liefern selbst keinen Wert; die Zeilen zeigen das resultierende RAM-Byte. Separate Emulatorprüfungen vergleichen alle 520 Bytes geschützter Puffer in 136 Fällen je Plattform: konstante und laufzeitabhängige Indizes über Byte- und 256-Byte-Grenzen sowie alle acht möglichen Bitprüfmasken ungleich null. Dies sind RAM-Zustandsprüfungen, keine Zeichendemonstration. Physische Hardware und gleichzeitige Interrupt-Schreibzugriffe wurden nicht getestet.

__bit_toggle: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:47

Deklarationshinweise im Original
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

Wählt eine CHR-Bank. Bei MMC3 wird R0 gewählt und das 2-KiB-Fenster bei PPU $0000–$07FF geändert. Bei CNROM wird der gesamte 8-KiB-Bereich $0000–$1FFF geändert.

Syntax

c
void __chr_bank_set(u8 bank);

Parameter

bank

Bei CNROM ist dies eine 8-KiB-CHR-Banknummer. Bei MMC3 wählt eine 1-KiB-Banknummer ein 2-KiB-Fenster: Das niederwertigste Bit wird ignoriert, sodass 2 und 3 beide die 1-KiB-Banken 2 und 3 auswählen. Verfügbare Banken hängen von der CHR-ROM-Kapazität ab; die API prüft keine Grenzen.

Rückgabewert

Kein Rückgabewert.

Bedingungen und Grenzen

Die Intrinsic-Deklarationen stehen in intrinsics.h. Binden Sie für die Makros nes_mapper_irq_* den Header nes_game.h ein. Wählen Sie die Platine beim Build mit --mapper.

Das Schreiben von 0 oder 1 ins MMC3-Bankauswahlregister löscht auch PRG-Modusbit 6 und CHR-Inversionsbit 7. Kombinieren Sie dies nicht mit einem PRG/CHR-Layout, das diese Bits gesetzt benötigt. Umschalten während der Darstellung verändert nachfolgende Musterzugriffe. Das Beispiel prüft bei ausgeschalteter Darstellung und stellt vor der Ergebnisanzeige die Schriftbanken wieder her.

Diese drei Intrinsics unterstützen CNROM und MMC3. Andere Platinen werden beim Kompilieren abgewiesen. SUROM verwendet die CHR-Steuerung auch zur Auswahl des äußeren PRG-Bereichs und erlaubt diese CHR-Vorgänge nicht.

Anwendungsbeispiel

c
__chr_bank_set(2);
    mapper_result[1] = mapper_read(0x03FF);
    mapper_result[2] = mapper_read(0x07FF);

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das MMC3-Beispiel zeigt CHR-Marker 19/20, 21/22 und 23/24. CIRAM-Lesewerte ergeben A B A B bei vertikaler Spiegelung und A A B B bei horizontaler. Die IRQ-Zählerstände sind 01→02→03→03: Jede der ersten drei ausdrücklichen Freigaben erlaubt einen Interrupt; nach Deaktivierung bleibt der Stand gleich. Jede Stufe wartet sechs Frames.

__chr_bank_set: MMC3-DEFAULT
MMC3-DEFAULT

Vollständiges Beispielprogramm

Build-Befehl

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

Anwendungsbeispiel

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

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Die CNROM-Lektion zeigt nach SET 2, SET0 1 und SET1 3 die Marker 33, 25 und 41. Sie liest eigens erstellte Markerbytes über die PPU und zeigt damit, dass alle drei Namen den gesamten 8-KiB-Musterbereich umschalten.

__chr_bank_set: CNROM-DEFAULT
CNROM-DEFAULT

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:316

Deklarationshinweise im Original
Select a CNROM bank or MMC3 register 0 bank; unsupported mapper profiles produce a compile error.
__chr_bank_set0

Wählt MMC3 R0 und ändert das 2-KiB-Fenster bei PPU $0000–$07FF. Entspricht __chr_bank_set. Bei CNROM wird der gesamte 8-KiB-Bereich geändert.

Syntax

c
void __chr_bank_set0(u8 bank);

Parameter

bank

Bei CNROM ist dies eine 8-KiB-CHR-Banknummer. Bei MMC3 wählt eine 1-KiB-Banknummer ein 2-KiB-Fenster: Das niederwertigste Bit wird ignoriert, sodass 2 und 3 beide die 1-KiB-Banken 2 und 3 auswählen. Verfügbare Banken hängen von der CHR-ROM-Kapazität ab; die API prüft keine Grenzen.

Rückgabewert

Kein Rückgabewert.

Bedingungen und Grenzen

Die Intrinsic-Deklarationen stehen in intrinsics.h. Binden Sie für die Makros nes_mapper_irq_* den Header nes_game.h ein. Wählen Sie die Platine beim Build mit --mapper.

Das Schreiben von 0 oder 1 ins MMC3-Bankauswahlregister löscht auch PRG-Modusbit 6 und CHR-Inversionsbit 7. Kombinieren Sie dies nicht mit einem PRG/CHR-Layout, das diese Bits gesetzt benötigt. Umschalten während der Darstellung verändert nachfolgende Musterzugriffe. Das Beispiel prüft bei ausgeschalteter Darstellung und stellt vor der Ergebnisanzeige die Schriftbanken wieder her.

Diese drei Intrinsics unterstützen CNROM und MMC3. Andere Platinen werden beim Kompilieren abgewiesen. SUROM verwendet die CHR-Steuerung auch zur Auswahl des äußeren PRG-Bereichs und erlaubt diese CHR-Vorgänge nicht.

Anwendungsbeispiel

c
__chr_bank_set0(4);
    mapper_result[3] = mapper_read(0x03FF);
    mapper_result[4] = mapper_read(0x07FF);

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das MMC3-Beispiel zeigt CHR-Marker 19/20, 21/22 und 23/24. CIRAM-Lesewerte ergeben A B A B bei vertikaler Spiegelung und A A B B bei horizontaler. Die IRQ-Zählerstände sind 01→02→03→03: Jede der ersten drei ausdrücklichen Freigaben erlaubt einen Interrupt; nach Deaktivierung bleibt der Stand gleich. Jede Stufe wartet sechs Frames.

__chr_bank_set0: MMC3-DEFAULT
MMC3-DEFAULT

Vollständiges Beispielprogramm

Build-Befehl

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

Anwendungsbeispiel

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

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Die CNROM-Lektion zeigt nach SET 2, SET0 1 und SET1 3 die Marker 33, 25 und 41. Sie liest eigens erstellte Markerbytes über die PPU und zeigt damit, dass alle drei Namen den gesamten 8-KiB-Musterbereich umschalten.

__chr_bank_set0: CNROM-DEFAULT
CNROM-DEFAULT

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:318

Deklarationshinweise im Original
Use the same CNROM/MMC3 register 0 selection as __chr_bank_set.
__chr_bank_set1

Wählt MMC3 R1 und ändert das 2-KiB-Fenster bei PPU $0800–$0FFF, während R0 unverändert bleibt. CNROM besitzt kein unabhängiges zweites Fenster; dort wird der gesamte 8-KiB-Bereich geändert.

Syntax

c
void __chr_bank_set1(u8 bank);

Parameter

bank

Bei CNROM ist dies eine 8-KiB-CHR-Banknummer. Bei MMC3 wählt eine 1-KiB-Banknummer ein 2-KiB-Fenster: Das niederwertigste Bit wird ignoriert, sodass 2 und 3 beide die 1-KiB-Banken 2 und 3 auswählen. Verfügbare Banken hängen von der CHR-ROM-Kapazität ab; die API prüft keine Grenzen.

Rückgabewert

Kein Rückgabewert.

Bedingungen und Grenzen

Die Intrinsic-Deklarationen stehen in intrinsics.h. Binden Sie für die Makros nes_mapper_irq_* den Header nes_game.h ein. Wählen Sie die Platine beim Build mit --mapper.

Das Schreiben von 0 oder 1 ins MMC3-Bankauswahlregister löscht auch PRG-Modusbit 6 und CHR-Inversionsbit 7. Kombinieren Sie dies nicht mit einem PRG/CHR-Layout, das diese Bits gesetzt benötigt. Umschalten während der Darstellung verändert nachfolgende Musterzugriffe. Das Beispiel prüft bei ausgeschalteter Darstellung und stellt vor der Ergebnisanzeige die Schriftbanken wieder her.

Diese drei Intrinsics unterstützen CNROM und MMC3. Andere Platinen werden beim Kompilieren abgewiesen. SUROM verwendet die CHR-Steuerung auch zur Auswahl des äußeren PRG-Bereichs und erlaubt diese CHR-Vorgänge nicht.

Anwendungsbeispiel

c
__chr_bank_set1(6);
    mapper_result[5] = mapper_read(0x0BFF);
    mapper_result[6] = mapper_read(0x0FFF);

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das MMC3-Beispiel zeigt CHR-Marker 19/20, 21/22 und 23/24. CIRAM-Lesewerte ergeben A B A B bei vertikaler Spiegelung und A A B B bei horizontaler. Die IRQ-Zählerstände sind 01→02→03→03: Jede der ersten drei ausdrücklichen Freigaben erlaubt einen Interrupt; nach Deaktivierung bleibt der Stand gleich. Jede Stufe wartet sechs Frames.

__chr_bank_set1: MMC3-DEFAULT
MMC3-DEFAULT

Vollständiges Beispielprogramm

Build-Befehl

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

Anwendungsbeispiel

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

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Die CNROM-Lektion zeigt nach SET 2, SET0 1 und SET1 3 die Marker 33, 25 und 41. Sie liest eigens erstellte Markerbytes über die PPU und zeigt damit, dass alle drei Namen den gesamten 8-KiB-Musterbereich umschalten.

__chr_bank_set1: CNROM-DEFAULT
CNROM-DEFAULT

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:320

Deklarationshinweise im Original
Select MMC3 register 1, or the same full CNROM bank register; this is mapper-specific banking.
__copy16

Kopiert genau 16 Bytes von src nach dst, ohne Längenargument. Damit lässt sich etwa ein 16-Byte-Datensatz oder eine GB/NES-Kachel im CPU-zugänglichen Speicher kopieren. 16 bezeichnet Bytes, nicht Bits; Grafikdaten werden dadurch nicht an die Anzeige übertragen.

Syntax

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

Parameter

dst

Zeiger auf das erste beschreibbare Zielbyte. Stellen Sie genügend zusammenhängenden Speicher für die gesamte Operation bereit; ein Puffer wird nicht automatisch angelegt.

src

Zeiger auf das erste lesbare Quellbyte. Der gesamte Bereich muss während der Kopie zugänglich und unverändert bleiben.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Verwenden Sie Quell- und Zielbereiche, die sich nicht überlappen. Diese Routinen kopieren vorwärts und bieten nicht das überlappungssichere Verhalten von memmove. Schreibzugriffe auf noch ungelesene Quellbytes können die spätere Kopie verfälschen.

Die Operation wird vor der Rückkehr abgeschlossen. Sie schaltet keine ROM/RAM-Bänke um und begrenzt den Bereich nicht automatisch auf Puffergrenzen. Halten Sie die Speicherabbildung stabil, verwenden Sie gültige Bereiche und stimmen Sie Zugriffe mit Interrupt-Routinen ab, die dieselben Puffer verwenden.

Auf dem FC ist dst eine CPU-Adresse, keine PPU-Adresse. Eine Kopie ins CPU-RAM aktualisiert weder CHR noch Nametables oder Paletten; verwenden Sie dafür die PPU/VRAM-APIs. Generierte Hilfsroutinen teilen sich Argument- und Arbeitsspeicher. Rufen Sie sie während einer laufenden Operation nicht erneut aus einer NMI/IRQ-Routine auf.

Anwendungsbeispiel

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

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Beispiel kopiert mit __copy16 ab Offset 1 ein bekanntes Muster von 16 Bytes. memory_check vergleicht die vollständigen Quell- und Zielarrays; die Zeile COPY16 muss 016 anzeigen. Das erste Byte bleibt als Schutzbereich frei. Auch die Bytes nach dem Nutzdatenbereich müssen unverändert bleiben.

Das vollständige Programm initialisiert vor jeder Operation zwei 264-Byte-Puffer und prüft danach jedes Quell- und Zielbyte sowie die unveränderten Schutzbereiche. Erwartet werden COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 und FAILED 000. WORD/BYTE bezeichnen die Zählerbreite, nicht die Größe der kopierten Elemente. Die Zahlen stammen aus den Prüfungen des Beispiels; diese void-Funktionen liefern keine Rückgabewerte.

Die Aufnahmen zeigen schwarze Schrift auf Weiß beim DMG, blaue Schrift auf Weiß beim CGB und blaue Schrift auf Schwarz beim FC. Das Beispiel prüft RAM-Operationen; die Schrift stellt die Ergebnisse dar und demonstriert keine Zeichenprimitive. Weitere Emulatorprüfungen decken je Plattform 88 Speicherfälle ab: konstante und zur Laufzeit bestimmte Längen von 0, 1, 16, 17, 32, 33, 64, 65 und 255 Bytes sowie 256 und 257 bei 16-Bit-Zählern. Auf echter Hardware wurde nicht getestet.

__copy16: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:27

Deklarationshinweise im Original
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

Kopiert genau 32 Bytes von src nach dst, ohne Längenargument. Das eignet sich für einen festen 32-Byte-Datensatz oder zwei benachbarte 16-Byte-Kachelmuster im CPU-zugänglichen Speicher. Die Kopie erfolgt byteweise vorwärts, weder als einzelner 32-Bit-Lesezugriff noch mit Bankumschaltung.

Syntax

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

Parameter

dst

Zeiger auf das erste beschreibbare Zielbyte. Stellen Sie genügend zusammenhängenden Speicher für die gesamte Operation bereit; ein Puffer wird nicht automatisch angelegt.

src

Zeiger auf das erste lesbare Quellbyte. Der gesamte Bereich muss während der Kopie zugänglich und unverändert bleiben.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Verwenden Sie Quell- und Zielbereiche, die sich nicht überlappen. Diese Routinen kopieren vorwärts und bieten nicht das überlappungssichere Verhalten von memmove. Schreibzugriffe auf noch ungelesene Quellbytes können die spätere Kopie verfälschen.

Die Operation wird vor der Rückkehr abgeschlossen. Sie schaltet keine ROM/RAM-Bänke um und begrenzt den Bereich nicht automatisch auf Puffergrenzen. Halten Sie die Speicherabbildung stabil, verwenden Sie gültige Bereiche und stimmen Sie Zugriffe mit Interrupt-Routinen ab, die dieselben Puffer verwenden.

Auf dem FC ist dst eine CPU-Adresse, keine PPU-Adresse. Eine Kopie ins CPU-RAM aktualisiert weder CHR noch Nametables oder Paletten; verwenden Sie dafür die PPU/VRAM-APIs. Generierte Hilfsroutinen teilen sich Argument- und Arbeitsspeicher. Rufen Sie sie während einer laufenden Operation nicht erneut aus einer NMI/IRQ-Routine auf.

Anwendungsbeispiel

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

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Beispiel kopiert mit __copy32 ab Offset 1 ein bekanntes Muster von 32 Bytes. memory_check vergleicht die vollständigen Quell- und Zielarrays; die Zeile COPY32 muss 032 anzeigen. Das erste Byte bleibt als Schutzbereich frei. Auch die Bytes nach dem Nutzdatenbereich müssen unverändert bleiben.

Das vollständige Programm initialisiert vor jeder Operation zwei 264-Byte-Puffer und prüft danach jedes Quell- und Zielbyte sowie die unveränderten Schutzbereiche. Erwartet werden COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 und FAILED 000. WORD/BYTE bezeichnen die Zählerbreite, nicht die Größe der kopierten Elemente. Die Zahlen stammen aus den Prüfungen des Beispiels; diese void-Funktionen liefern keine Rückgabewerte.

Die Aufnahmen zeigen schwarze Schrift auf Weiß beim DMG, blaue Schrift auf Weiß beim CGB und blaue Schrift auf Schwarz beim FC. Das Beispiel prüft RAM-Operationen; die Schrift stellt die Ergebnisse dar und demonstriert keine Zeichenprimitive. Weitere Emulatorprüfungen decken je Plattform 88 Speicherfälle ab: konstante und zur Laufzeit bestimmte Längen von 0, 1, 16, 17, 32, 33, 64, 65 und 255 Bytes sowie 256 und 257 bei 16-Bit-Zählern. Auf echter Hardware wurde nicht getestet.

__copy32: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:29

Deklarationshinweise im Original
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

Berechnet ein vorzeichenloses Skalarprodukt mit zwei Termen.

Syntax

c
u16 __dot2_q8_8(u16 a0, u16 a1, u8 b0, u8 b1);

Parameter

a0

Vorzeichenloses Wort, 0..65535.

a1

Vorzeichenloses Wort, 0..65535.

b0

Vorzeichenloser Koeffizient, 0..255.

b1

Vorzeichenloser Koeffizient, 0..255.

Rückgabewert

Gibt die unteren 16 Bits von a0*b0+a1*b1 zurück.

Bedingungen und Grenzen

Auf dem FC verwenden die q8_8/q1_7-Funktionen rohe Ganzzahlprodukte ohne Festkomma-Rechtsshift. Q1.7-Funktionen sind Aliase der entsprechenden vorzeichenbehafteten Operationen. Der aufrufende Code verwaltet die Einheiten; vorzeichenbehaftete Ergebnisse werden als u16-Bitmuster zurückgegeben.

Anwendungsbeispiel

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);

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.

Der Ausschnitt speichert folgende 16-Bit-Werte: result[22]=64959, result[23]=64959. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

__dot2_q8_8: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:63

Deklarationshinweise im Original
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

Berechnet ein vorzeichenloses Skalarprodukt mit drei Termen.

Syntax

c
u16 __dot3_q8_8(u16 a0, u16 a1, u16 a2, u8 b0, u8 b1, u8 b2);

Parameter

a0

Vorzeichenloses Wort, 0..65535.

a1

Vorzeichenloses Wort, 0..65535.

a2

Vorzeichenloses Wort, 0..65535.

b0

Vorzeichenloser Koeffizient, 0..255.

b1

Vorzeichenloser Koeffizient, 0..255.

b2

Vorzeichenloser Koeffizient, 0..255.

Rückgabewert

Gibt die unteren 16 Bits von a0*b0+a1*b1+a2*b2 zurück.

Bedingungen und Grenzen

Auf dem FC verwenden die q8_8/q1_7-Funktionen rohe Ganzzahlprodukte ohne Festkomma-Rechtsshift. Q1.7-Funktionen sind Aliase der entsprechenden vorzeichenbehafteten Operationen. Der aufrufende Code verwaltet die Einheiten; vorzeichenbehaftete Ergebnisse werden als u16-Bitmuster zurückgegeben.

Anwendungsbeispiel

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);

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.

Der Ausschnitt speichert folgende 16-Bit-Werte: result[24]=2498, result[25]=2498. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

__dot3_q8_8: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:65

Deklarationshinweise im Original
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 ruft dieselbe Compilerroutine wie __pad_read1_d1 auf. D1 an $4016 wird mit identischer Zustandsübernahme, identischem Achtbit-Ergebnis und denselben Nebenwirkungen gelesen. Der Name bezeichnet den Erweiterungseingang; er aktiviert keinen anderen Gerätemodus und kombiniert D0 nicht mit D1.

Syntax

c
u8 __exp_pad_read1(void);

Rückgabewert

Eine 8-Bit-Haltemaske in roher NES-Reihenfolge. Jedes gesetzte Bit steht für eine gedrückte Taste, 0 für keine. Kein auf 0/1 normalisierter Wahrheitswert.

Bedingungen und Grenzen

Rohes NES-Format: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Ein gesetztes Bit bedeutet gedrückt. Verwende diese Werte mit nes_pad_* und rohen __pad_*-Ergebnissen, nicht die gemeinsamen BTN_*-Masken aus input.h.

Verwenden Sie ein Peripheriegerät mit einem kompatiblen seriellen Achtbit-Controllerstrom auf der gewählten D1-Leitung. Die Funktion erkennt das Gerät nicht und implementiert nicht jedes Erweiterungsprotokoll. Jeder Aufruf übernimmt beide Controllerströme neu; beenden Sie einen vollständigen Lesevorgang, bevor Sie den nächsten beginnen. Die Ergebnisse sind neue Momentaufnahmen und aktualisieren weder input_* noch nes_pad1_*.

Das veröffentlichte KUROSAKI-Controllermodell liefert nur D0 und das feste Bit 6. D1 und das Mikrofonbit sind in diesem Emulator daher stets 0, auch bei gedrückten normalen Tasten. Der beigefügte Lauf prüft Kompilierung, normale Tasteneingaben und diese Nullsignal-Beobachtung. Peripheriesignale ungleich null, elektrische Zeitabläufe und reale Gerätekompatibilität bleiben ungeprüft. Eine Null beweist hier weder, dass das Gerät funktioniert, noch dass die Funktionen es auf echter Hardware nicht lesen können.

Anwendungsbeispiel

c
expansion_first=__exp_pad_read1(); // Same helper as __pad_read1_d1.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Dieses Programm vergleicht normale D0-Eingaben mit D1 und dem digitalen Mikrofonpegel. Halten Sie A auf Controller 1 und B auf Controller 2 bis zur Erfassung gedrückt. NORMAL P1 und NORMAL P2 zeigen 001 und 002. Im beigefügten KUROSAKI-Lauf zeigen D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL und MIC DIRECT jeweils 000, weil diese Leitungen nicht emuliert werden. Die erfassten Werte bleiben anschließend stehen. Mit geeigneten Peripheriegeräten und implementierten Signalleitungen hängen die Werte von den tatsächlichen Eingaben ab; deuten Sie sie anhand der obigen Bitbelegung, statt diese Nullen zu erwarten.

Das veröffentlichte KUROSAKI-Controllermodell liefert nur D0 und das feste Bit 6. D1 und das Mikrofonbit sind in diesem Emulator daher stets 0, auch bei gedrückten normalen Tasten. Der beigefügte Lauf prüft Kompilierung, normale Tasteneingaben und diese Nullsignal-Beobachtung. Peripheriesignale ungleich null, elektrische Zeitabläufe und reale Gerätekompatibilität bleiben ungeprüft. Eine Null beweist hier weder, dass das Gerät funktioniert, noch dass die Funktionen es auf echter Hardware nicht lesen können.

__exp_pad_read1: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:227

Deklarationshinweise im Original
Alias the port-1 D1 controller reader; the attached device must use the expected serial protocol.
__exp_pad_read2

__exp_pad_read2 ruft dieselbe Compilerroutine wie __pad_read2_d1 auf. D1 an $4017 wird mit identischer Zustandsübernahme, identischem Achtbit-Ergebnis und denselben Nebenwirkungen gelesen. Der Name bezeichnet den Erweiterungseingang; er aktiviert keinen anderen Gerätemodus und kombiniert D0 nicht mit D1.

Syntax

c
u8 __exp_pad_read2(void);

Rückgabewert

Eine 8-Bit-Haltemaske in roher NES-Reihenfolge. Jedes gesetzte Bit steht für eine gedrückte Taste, 0 für keine. Kein auf 0/1 normalisierter Wahrheitswert.

Bedingungen und Grenzen

Rohes NES-Format: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Ein gesetztes Bit bedeutet gedrückt. Verwende diese Werte mit nes_pad_* und rohen __pad_*-Ergebnissen, nicht die gemeinsamen BTN_*-Masken aus input.h.

Verwenden Sie ein Peripheriegerät mit einem kompatiblen seriellen Achtbit-Controllerstrom auf der gewählten D1-Leitung. Die Funktion erkennt das Gerät nicht und implementiert nicht jedes Erweiterungsprotokoll. Jeder Aufruf übernimmt beide Controllerströme neu; beenden Sie einen vollständigen Lesevorgang, bevor Sie den nächsten beginnen. Die Ergebnisse sind neue Momentaufnahmen und aktualisieren weder input_* noch nes_pad1_*.

Das veröffentlichte KUROSAKI-Controllermodell liefert nur D0 und das feste Bit 6. D1 und das Mikrofonbit sind in diesem Emulator daher stets 0, auch bei gedrückten normalen Tasten. Der beigefügte Lauf prüft Kompilierung, normale Tasteneingaben und diese Nullsignal-Beobachtung. Peripheriesignale ungleich null, elektrische Zeitabläufe und reale Gerätekompatibilität bleiben ungeprüft. Eine Null beweist hier weder, dass das Gerät funktioniert, noch dass die Funktionen es auf echter Hardware nicht lesen können.

Anwendungsbeispiel

c
expansion_second=__exp_pad_read2(); // Same helper as __pad_read2_d1.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Dieses Programm vergleicht normale D0-Eingaben mit D1 und dem digitalen Mikrofonpegel. Halten Sie A auf Controller 1 und B auf Controller 2 bis zur Erfassung gedrückt. NORMAL P1 und NORMAL P2 zeigen 001 und 002. Im beigefügten KUROSAKI-Lauf zeigen D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL und MIC DIRECT jeweils 000, weil diese Leitungen nicht emuliert werden. Die erfassten Werte bleiben anschließend stehen. Mit geeigneten Peripheriegeräten und implementierten Signalleitungen hängen die Werte von den tatsächlichen Eingaben ab; deuten Sie sie anhand der obigen Bitbelegung, statt diese Nullen zu erwarten.

Das veröffentlichte KUROSAKI-Controllermodell liefert nur D0 und das feste Bit 6. D1 und das Mikrofonbit sind in diesem Emulator daher stets 0, auch bei gedrückten normalen Tasten. Der beigefügte Lauf prüft Kompilierung, normale Tasteneingaben und diese Nullsignal-Beobachtung. Peripheriesignale ungleich null, elektrische Zeitabläufe und reale Gerätekompatibilität bleiben ungeprüft. Eine Null beweist hier weder, dass das Gerät funktioniert, noch dass die Funktionen es auf echter Hardware nicht lesen können.

__exp_pad_read2: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:229

Deklarationshinweise im Original
Alias the port-2 D1 controller reader; this is not an expansion-device discovery operation.
__far_memcpy

Wählt vorübergehend eine ROM-Bank, kopiert len Byte mit aufsteigenden Adressen von src nach dst und stellt anschließend die vom Compiler gespeicherte Bank wieder her. Auf dieser Plattform verwenden __farmemcpy und __far_memcpy dieselbe Implementierung.

Syntax

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

Parameter

dst

Ziel in beschreibbarem Speicher mit genügend Platz für alle kopierten Byte. Abgesehen von einem Transfer mit Länge null erfolgt keine Prüfung auf Nullzeiger oder ungültige Zieladressen.

bank

8-Bit-Bankauswahl für die Quell-ROM-Zuordnung. Die Bank muss zur Cartridge und zur Bankaufteilung des Compilers passen. Sie wird getrennt von der Quelladresse angegeben.

src

16-Bit-CPU-Adresse der Quelldaten in der gewählten ROM-Zuordnung. Die FC-Deklaration erwartet u16; wandeln Sie einen Ressourcenzeiger mit (u16)pointer um.

len

Byteanzahl, 0–65535. Bei null wird kein Speicher kopiert; Bankauswahl und Wiederherstellung werden trotzdem ausgeführt.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Der gesamte Quellbereich muss in der vorgesehenen ROM-Zuordnung lesbar und der gesamte Zielbereich beschreibbar sein. Die Kopie schaltet am Fensterende nicht automatisch zur nächsten Bank, behandelt Überlappungen nicht wie memmove und steuert keine sicheren VRAM-Schreibzeitpunkte. Verwenden Sie für VRAM die Grafikübertragungs-APIs.

Die Beispiele legen den aufrufenden Code in die feste Bank 0, damit ein Wechsel der Quellbank den laufenden Code nicht ausblendet. Die Übertragungen sind synchron. Unterbrechen Sie sie nicht durch eine weitere bankwechselnde Kopie. Die Hilfsfunktion allein macht nicht die gesamte Anwendung interruptsicher.

Anwendungsbeispiel

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);

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Kopiert zwei Muster mit insgesamt 32 Byte aus Bank 2, ausgehend von Bank 1. Das Programm vergleicht jedes Byte, prüft die Wiederherstellung von Bank 1 und den unveränderten Prüfwert 0x55 bei einer Kopie der Länge null.

Erwartetes Bild: Eine rote Reihe beginnt bei (16,32) und belegt 72×8 Pixel. Dreimal folgen ein 8×8-Dreieck, ein 8×8-Quadrat mit einem Pixel Rand und eine Kachel mit vier gefüllten linken Spalten. Das Dreieck wächst von einem Pixel oben links auf acht Pixel in der untersten Zeile.

Auf dem FC zeigt das blaue Paar bei (16,64) __far_memcpy, das grüne bei (16,96) asset_load_raw mit anschließender CHR-Übertragung. Jedes Paar belegt 16×8 Pixel auf schwarzem Hintergrund. So werden die kopierten Bilddaten sichtbar, nicht nur numerische Statuswerte.

Erwartet werden FAILED CHECKS 000 und FIRST FAILURE 00. Die Prüfung führt 300 Bilder aus und vergleicht Text, Formmasken und Farben mit den Sollwerten. Dies ist ein Emulatornachweis, kein Test auf physischer Hardware.

__far_memcpy: SUROM512
SUROM512

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:86

Deklarationshinweise im Original
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

Ruft eine benannte Funktion ohne Argumente auf. Der FC-Compiler wählt anhand ihrer Bank einen direkten Aufruf oder eine Hilfsroutine mit Bankwiederherstellung. Das Argument bank wird ausgewertet; die tatsächliche Auswahl folgt jedoch der Platzierung der Funktion.

Syntax

c
__farcall(bank, function_name);

Parameter

bank

Vorzeichenlose 8-Bit-ROM-Banknummer. Bei Aufrufen muss sie zur Funktionsplatzierung passen, bei Lesezugriffen wählt sie die Quelle. Verwenden Sie den gültigen Bereich des Modultyps.

func

Deklarierter Name einer Funktion ohne Parameter. Dieses Intrinsic für benannte Aufrufe akzeptiert keine Variable mit einer Funktionsadresse. Das Argument bank wird nicht als Callback-Parameter weitergereicht.

Rückgabewert

Der Ergebnistyp richtet sich nach der aufgerufenen Funktion. Ein void-Callback liefert keinen Wert; u8- und u16-Callbacks liefern ihren Skalarwert. Das gezeigte Beispiel verwendet void-Callbacks.

Bedingungen und Grenzen

Diese Aufrufe reichen keine Benutzerargumente weiter und machen den Callback nicht interrupt-reentrant. Verwalten Sie seine Daten, seinen Stack und seine Interruptnutzung korrekt. Die Bankwiederherstellung ist mit verschachtelten und wiederholten Aufrufen geprüft; reale Hardware und beliebige Interruptverschachtelungen deckt dieses Beispiel nicht ab.

FC-Banknummern sind logische Nummern des Compilers. Null bezeichnet gemeinsames ROM, kein Umschaltziel; die Hilfsroutine setzt null auf Bank 1 um. Bei SUROM512 sind nur 1–30 gültig: Eine direkt an das Umschalt-Intrinsic übergebene Konstante außerhalb des Bereichs führt zu einem Buildfehler; ein ungültiger Laufzeitwert wählt 1 und setzt den Mapper-Fehlerzustand. Lesen Sie gemeinsames ROM direkt, statt Bank null anzufordern.

Anwendungsbeispiel

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

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

An dieser Stelle des vollständigen Programms erhöht __farcall den Wert von callback_count auf 2. Der Callback liest D3 aus Bank 3. Ein späterer Nahzugriff auf home2 mit Ergebnis B2 bestätigt die Rückkehr bei eingeblendeter Bank 2.

Das vollständige Programm zeigt Datenzugriffe über Bank und Nahadresse, bankübergreifende Callbacks und den Unterschied zwischen Bibliothekszustand und tatsächlicher Zuordnung. Alle Zahlen sind hexadezimal. BANK IN und BANK OUT müssen 02 zeigen, BYTE HEX D3, WORD HEX 5C7A sowie FAILED CHECKS und FIRST FAILURE jeweils 00. CGB und FC zeigen blaue Schrift, DMG schwarze.

CALL COUNT zeigt 02: Das Makro far_call und __farcall rufen den Callback jeweils einmal auf.

__farcall: MMC3
MMC3

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:83

Deklarationshinweise im Original
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 ist eine alternative Schreibweise des FC-Intrinsics __far_memcpy. Argumente sowie Kopier- und Bankwiederherstellungsroutine sind identisch. Die Schreibweise ändert weder Übertragungsverfahren noch Geschwindigkeit. Das Beispiel liest damit Kachelmuster aus dem ROM ins RAM.

Wählt vorübergehend eine ROM-Bank, kopiert len Byte mit aufsteigenden Adressen von src nach dst und stellt anschließend die vom Compiler gespeicherte Bank wieder her. Auf dieser Plattform verwenden __farmemcpy und __far_memcpy dieselbe Implementierung.

Syntax

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

Parameter

dst

Ziel in beschreibbarem Speicher mit genügend Platz für alle kopierten Byte. Abgesehen von einem Transfer mit Länge null erfolgt keine Prüfung auf Nullzeiger oder ungültige Zieladressen.

bank

8-Bit-Bankauswahl für die Quell-ROM-Zuordnung. Die Bank muss zur Cartridge und zur Bankaufteilung des Compilers passen. Sie wird getrennt von der Quelladresse angegeben.

src

16-Bit-CPU-Adresse der Quelldaten in der gewählten ROM-Zuordnung. Die FC-Deklaration erwartet u16; wandeln Sie einen Ressourcenzeiger mit (u16)pointer um.

len

Byteanzahl, 0–65535. Bei null wird kein Speicher kopiert; Bankauswahl und Wiederherstellung werden trotzdem ausgeführt.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Der Compiler registriert diese Schreibweise direkt; eine zusätzliche Deklaration in intrinsics.h ist nicht erforderlich. Die Argumente sind der Reihe nach ein RAM-Zeiger, eine 8-Bit-Banknummer, eine 16-Bit-CPU-Adresse und eine 16-Bit-Byteanzahl. src ist kein Offset vom Anfang der ROM-Datei.

Der gesamte Quellbereich muss in der vorgesehenen ROM-Zuordnung lesbar und der gesamte Zielbereich beschreibbar sein. Die Kopie schaltet am Fensterende nicht automatisch zur nächsten Bank, behandelt Überlappungen nicht wie memmove und steuert keine sicheren VRAM-Schreibzeitpunkte. Verwenden Sie für VRAM die Grafikübertragungs-APIs.

Die Beispiele legen den aufrufenden Code in die feste Bank 0, damit ein Wechsel der Quellbank den laufenden Code nicht ausblendet. Die Übertragungen sind synchron. Unterbrechen Sie sie nicht durch eine weitere bankwechselnde Kopie. Die Hilfsfunktion allein macht nicht die gesamte Anwendung interruptsicher.

Bei len gleich null bleiben alle Zielbytes unverändert. Bankauswahl und Wiederherstellung finden dennoch statt. Die Funktion hat keinen Rückgabewert.

__far_memcpy

Anwendungsbeispiel

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);

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Kopiert zwei Muster mit insgesamt 32 Byte aus Bank 2, ausgehend von Bank 1. Das Programm vergleicht jedes Byte, prüft die Wiederherstellung von Bank 1 und den unveränderten Prüfwert 0x55 bei einer Kopie der Länge null.

Erwartetes Bild: Eine rote Reihe beginnt bei (16,32) und belegt 72×8 Pixel. Dreimal folgen ein 8×8-Dreieck, ein 8×8-Quadrat mit einem Pixel Rand und eine Kachel mit vier gefüllten linken Spalten. Das Dreieck wächst von einem Pixel oben links auf acht Pixel in der untersten Zeile.

Das blaue Dreieck und der blaue Quadratumriss bei Pixel (16,64) stellen die von __farmemcpy kopierten Bytes dar. Das grüne Paar darunter bei (16,96) stammt von asset_load_raw. Jedes Paar belegt 16×8 Pixel auf schwarzem Hintergrund. Die kopierten Bytes werden zur Anzeige ins CHR-RAM übertragen.

Erwartet werden FAILED CHECKS 000 und FIRST FAILURE 00. Die Prüfung führt 300 Bilder aus und vergleicht Text, Formmasken und Farben mit den Sollwerten. Dies ist ein Emulatornachweis, kein Test auf physischer Hardware.

__farmemcpy: SUROM512 — Das blaue Dreieck und der blaue Quadratumriss bei Pixel (16,64) stellen die von __farmemcpy kopierten Bytes dar. Das grüne Paar darunter bei (16,96) stammt von asset_load_raw. Jedes Paar belegt 16×8 Pixel auf schwarzem Hintergrund. Die kopierten Bytes werden zur Anzeige ins CHR-RAM übertragen.
SUROM512 — Das blaue Dreieck und der blaue Quadratumriss bei Pixel (16,64) stellen die von __farmemcpy kopierten Bytes dar. Das grüne Paar darunter bei (16,96) stammt von asset_load_raw. Jedes Paar belegt 16×8 Pixel auf schwarzem Hintergrund. Die kopierten Bytes werden zur Anzeige ins CHR-RAM übertragen.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

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

Liest bei kurzzeitig gewählter Bank einen 16-Bit-Wert in Little-Endian-Reihenfolge an addr und addr+1 und stellt die Bank wieder her. Dieses Intrinsic wird von far_data_read16 verwendet.

Syntax

c
u16 __farpeek16(u8 bank, u16 addr);

Parameter

bank

Vorzeichenlose 8-Bit-ROM-Banknummer. Bei Aufrufen muss sie zur Funktionsplatzierung passen, bei Lesezugriffen wählt sie die Quelle. Verwenden Sie den gültigen Bereich des Modultyps.

addr

16-Bit-CPU-Adresse der Quelldaten in der gewählten ROM-Zuordnung. Die FC-Deklaration erwartet u16; wandeln Sie einen Ressourcenzeiger mit (u16)pointer um.

Rückgabewert

Das gelesene Little-Endian-Wort als u16 (0–65535).

Bedingungen und Grenzen

Das gesamte Byte oder Wort muss in der gewählten CPU-Zuordnung lesbar sein. Zeiger werden nicht geprüft, Adressen nicht begrenzt und Fenstergrenzen lösen keinen automatischen Wechsel zur nächsten ROM-Bank aus. Ein Wort benötigt zwei gültige aufeinanderfolgende Bytes.

Fernlesezugriffe und Kopien arbeiten synchron und nutzen gemeinsamen Arbeitsspeicher des Compilers. Ein Interrupt darf währenddessen keinen weiteren Banktransfer über denselben Speicher ausführen. Auf GB nutzt die Kopierhilfe außerdem einen gemeinsamen HRAM-Platz für die gespeicherte Bank. Das Beispiel führt die Operationen aus festem Code ohne konkurrierenden Transfer-Handler aus.

FC-Banknummern sind logische Nummern des Compilers. Null bezeichnet gemeinsames ROM, kein Umschaltziel; die Hilfsroutine setzt null auf Bank 1 um. Bei SUROM512 sind nur 1–30 gültig: Eine direkt an das Umschalt-Intrinsic übergebene Konstante außerhalb des Bereichs führt zu einem Buildfehler; ein ungültiger Laufzeitwert wählt 1 und setzt den Mapper-Fehlerzustand. Lesen Sie gemeinsames ROM direkt, statt Bank null anzufordern.

Anwendungsbeispiel

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

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Dieser Ausschnitt liest mit __farpeek16 den Ausdruck payload+1 in Bank 3 und prüft 5C7A. Das vollständige Programm bestätigt durch einen Nahzugriff auf home2 außerdem die Wiederherstellung von Bank 2.

Das vollständige Programm zeigt Datenzugriffe über Bank und Nahadresse, bankübergreifende Callbacks und den Unterschied zwischen Bibliothekszustand und tatsächlicher Zuordnung. Alle Zahlen sind hexadezimal. BANK IN und BANK OUT müssen 02 zeigen, BYTE HEX D3, WORD HEX 5C7A sowie FAILED CHECKS und FIRST FAILURE jeweils 00. CGB und FC zeigen blaue Schrift, DMG schwarze.

CALL COUNT zeigt 02: Das Makro far_call und __farcall rufen den Callback jeweils einmal auf.

__farpeek16: MMC3
MMC3

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:90

Deklarationshinweise im Original
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

Wählt kurzzeitig die angegebene Bank, liest ein Byte an der CPU-Adresse und stellt die vorherige Bank wieder her. Dieses Intrinsic wird von far_data_read8 verwendet.

Syntax

c
u8 __farpeek8(u8 bank, u16 addr);

Parameter

bank

Vorzeichenlose 8-Bit-ROM-Banknummer. Bei Aufrufen muss sie zur Funktionsplatzierung passen, bei Lesezugriffen wählt sie die Quelle. Verwenden Sie den gültigen Bereich des Modultyps.

addr

16-Bit-CPU-Adresse der Quelldaten in der gewählten ROM-Zuordnung. Die FC-Deklaration erwartet u16; wandeln Sie einen Ressourcenzeiger mit (u16)pointer um.

Rückgabewert

Das gelesene Byte als u8 (0–255).

Bedingungen und Grenzen

Das gesamte Byte oder Wort muss in der gewählten CPU-Zuordnung lesbar sein. Zeiger werden nicht geprüft, Adressen nicht begrenzt und Fenstergrenzen lösen keinen automatischen Wechsel zur nächsten ROM-Bank aus. Ein Wort benötigt zwei gültige aufeinanderfolgende Bytes.

Fernlesezugriffe und Kopien arbeiten synchron und nutzen gemeinsamen Arbeitsspeicher des Compilers. Ein Interrupt darf währenddessen keinen weiteren Banktransfer über denselben Speicher ausführen. Auf GB nutzt die Kopierhilfe außerdem einen gemeinsamen HRAM-Platz für die gespeicherte Bank. Das Beispiel führt die Operationen aus festem Code ohne konkurrierenden Transfer-Handler aus.

FC-Banknummern sind logische Nummern des Compilers. Null bezeichnet gemeinsames ROM, kein Umschaltziel; die Hilfsroutine setzt null auf Bank 1 um. Bei SUROM512 sind nur 1–30 gültig: Eine direkt an das Umschalt-Intrinsic übergebene Konstante außerhalb des Bereichs führt zu einem Buildfehler; ein ungültiger Laufzeitwert wählt 1 und setzt den Mapper-Fehlerzustand. Lesen Sie gemeinsames ROM direkt, statt Bank null anzufordern.

Anwendungsbeispiel

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

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Dieser Ausschnitt liest mit __farpeek8 den Ausdruck payload in Bank 3 und prüft D3. Das vollständige Programm bestätigt durch einen Nahzugriff auf home2 außerdem die Wiederherstellung von Bank 2.

Das vollständige Programm zeigt Datenzugriffe über Bank und Nahadresse, bankübergreifende Callbacks und den Unterschied zwischen Bibliothekszustand und tatsächlicher Zuordnung. Alle Zahlen sind hexadezimal. BANK IN und BANK OUT müssen 02 zeigen, BYTE HEX D3, WORD HEX 5C7A sowie FAILED CHECKS und FIRST FAILURE jeweils 00. CGB und FC zeigen blaue Schrift, DMG schwarze.

CALL COUNT zeigt 02: Das Makro far_call und __farcall rufen den Callback jeweils einmal auf.

__farpeek8: MMC3
MMC3

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:88

Deklarationshinweise im Original
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

Prüfen Sie das Erkennungsmuster der Tastatur, bevor Sie Tastendrücke auswerten.

Syntax

c
u8 __fkb_detect(void);

Rückgabewert

Liefert 1, wenn Zeile 9, Spalte 0 den Wert 0x1E und die deaktivierte Matrix null ergibt; andernfalls 0. Das ist eine Heuristik anhand eines Signalmusters und keine Gerätekennung.

Bedingungen und Grenzen

Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.

Schreibt 5 nach $4016 zum Zurücksetzen auf Zeile 0 und schaltet Zeilen über fallende Flanken des Spaltenauswahlbits weiter. Neben den umgebenden Befehlen enthält der erzeugte Code 12 CPU-Zyklen NOPs nach Reset und 50 nach der Spaltenauswahl. Beim Beenden wird null geschrieben, um die Matrix zu deaktivieren.

Ein separater Regressionstest prüft anhand ausgeführter CPU-Busspuren Zeilen 0 bis 9, beide Spalten, den Neun-Zeilen-Scan, Einschwingzeiten und den abschließenden Portzustand. Er speist keine physischen Tastensignale ein.

Zeile 9 enthält keine Tasten. Sobald eine Bedingung nicht erfüllt ist, wird null zurückgegeben. KUROSAKI liefert bereits beim ersten Lesen null, weshalb die Erkennung dort fehlschlägt.

Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.

NESdev: Family-BASIC-Tastatur

Anwendungsbeispiel

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();}
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

In diesem KUROSAKI-Lauf sind DEVICE DETECT, ROW 6 COL 0 und A KEY - GUARDED jeweils 000; alle neun COL0/COL1-Paare sind 00. A liegt auf Zeile 6, Spalte 0, Bit 4. Die fehlgeschlagene Erkennung verhindert aber, dass der Rohwert null als Tastendruck gemeldet wird. Die Tabelle ist eine einmalige Startaufnahme.

__fkb_detect: KEYBOARD-DEFAULT — In diesem KUROSAKI-Lauf sind DEVICE DETECT, ROW 6 COL 0 und A KEY - GUARDED jeweils 000; alle neun COL0/COL1-Paare sind 00. A liegt auf Zeile 6, Spalte 0, Bit 4. Die fehlgeschlagene Erkennung verhindert aber, dass der Rohwert null als Tastendruck gemeldet wird. Die Tabelle ist eine einmalige Startaufnahme.
KEYBOARD-DEFAULT — In diesem KUROSAKI-Lauf sind DEVICE DETECT, ROW 6 COL 0 und A KEY - GUARDED jeweils 000; alle neun COL0/COL1-Paare sind 00. A liegt auf Zeile 6, Spalte 0, Bit 4. Die fehlgeschlagene Erkennung verhindert aber, dass der Rohwert null als Tastendruck gemeldet wird. Die Tabelle ist eine einmalige Startaufnahme.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:259

Deklarationshinweise im Original
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

Liest eine ausgewählte Zeile und Spaltengruppe ohne 18-Byte-Puffer, wenn nur diese Gruppe benötigt wird.

Syntax

c
u8 __fkb_read_row_col(u8 row, u8 col);

Parameter

row

Verwenden Sie 0..8 für Tastenzeilen oder 9 für die Erkennungszeile ohne Tasten. Es wird keine Bereichsprüfung zur Laufzeit erzeugt.

col

0 oder 1. Nur das niederwertigste Bit wird verwendet: Gerade Werte wählen Gruppe 0, ungerade Gruppe 1.

Rückgabewert

Liefert $4017 & 0x1E, ohne die Bits auf die niederwertigen Positionen zu verschieben.

Bedingungen und Grenzen

Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.

Tastengruppen sind die Rohbits 1 bis 4: Eine gedrückte Taste löscht ihr Bit, eine ruhende Gruppe ergibt 0x1E. Dies ist weder ASCII-Text noch eine Bitmaske mit eins für gedrückt. Auch ein getrenntes Gerät kann null liefern. Deuten Sie das ohne Anwesenheitsprüfung nicht als „alle Tasten gedrückt“.

Schreibt 5 nach $4016 zum Zurücksetzen auf Zeile 0 und schaltet Zeilen über fallende Flanken des Spaltenauswahlbits weiter. Neben den umgebenden Befehlen enthält der erzeugte Code 12 CPU-Zyklen NOPs nach Reset und 50 nach der Spaltenauswahl. Beim Beenden wird null geschrieben, um die Matrix zu deaktivieren.

Ein separater Regressionstest prüft anhand ausgeführter CPU-Busspuren Zeilen 0 bis 9, beide Spalten, den Neun-Zeilen-Scan, Einschwingzeiten und den abschließenden Portzustand. Er speist keine physischen Tastensignale ein.

Setzt bei jedem Aufruf auf Zeile 0 zurück und rückt erneut vor. Die Zeilenposition des vorherigen Aufrufs wird nicht übernommen.

Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.

NESdev: Family-BASIC-Tastatur

Anwendungsbeispiel

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();}
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

In diesem KUROSAKI-Lauf sind DEVICE DETECT, ROW 6 COL 0 und A KEY - GUARDED jeweils 000; alle neun COL0/COL1-Paare sind 00. A liegt auf Zeile 6, Spalte 0, Bit 4. Die fehlgeschlagene Erkennung verhindert aber, dass der Rohwert null als Tastendruck gemeldet wird. Die Tabelle ist eine einmalige Startaufnahme.

__fkb_read_row_col: KEYBOARD-DEFAULT — In diesem KUROSAKI-Lauf sind DEVICE DETECT, ROW 6 COL 0 und A KEY - GUARDED jeweils 000; alle neun COL0/COL1-Paare sind 00. A liegt auf Zeile 6, Spalte 0, Bit 4. Die fehlgeschlagene Erkennung verhindert aber, dass der Rohwert null als Tastendruck gemeldet wird. Die Tabelle ist eine einmalige Startaufnahme.
KEYBOARD-DEFAULT — In diesem KUROSAKI-Lauf sind DEVICE DETECT, ROW 6 COL 0 und A KEY - GUARDED jeweils 000; alle neun COL0/COL1-Paare sind 00. A liegt auf Zeile 6, Spalte 0, Bit 4. Die fehlgeschlagene Erkennung verhindert aber, dass der Rohwert null als Tastendruck gemeldet wird. Die Tabelle ist eine einmalige Startaufnahme.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:264

Deklarationshinweise im Original
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

Liest alle neun Zeilen mit jeweils zwei Spaltengruppen in 18 Bytes ein. So können Sie mehrere Tasten anhand derselben Matrixabfrage prüfen.

Syntax

c
void __fkb_scan(u8* dst18);

Parameter

dst18

Beschreibbarer Speicher mit mindestens 18 Bytes. Element row*2+col erhält den Rohwert der jeweiligen Gruppe. Nullzeiger und Länge werden nicht geprüft.

Rückgabewert

Kein Rückgabewert.

Bedingungen und Grenzen

Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.

Tastengruppen sind die Rohbits 1 bis 4: Eine gedrückte Taste löscht ihr Bit, eine ruhende Gruppe ergibt 0x1E. Dies ist weder ASCII-Text noch eine Bitmaske mit eins für gedrückt. Auch ein getrenntes Gerät kann null liefern. Deuten Sie das ohne Anwesenheitsprüfung nicht als „alle Tasten gedrückt“.

Schreibt 5 nach $4016 zum Zurücksetzen auf Zeile 0 und schaltet Zeilen über fallende Flanken des Spaltenauswahlbits weiter. Neben den umgebenden Befehlen enthält der erzeugte Code 12 CPU-Zyklen NOPs nach Reset und 50 nach der Spaltenauswahl. Beim Beenden wird null geschrieben, um die Matrix zu deaktivieren.

Ein separater Regressionstest prüft anhand ausgeführter CPU-Busspuren Zeilen 0 bis 9, beide Spalten, den Neun-Zeilen-Scan, Einschwingzeiten und den abschließenden Portzustand. Er speist keine physischen Tastensignale ein.

Liest in jeder Zeile zuerst Spalte 0, dann Spalte 1. Die Abfrage erfolgt nacheinander und erfasst nicht alle Tasten zum selben Zeitpunkt. Eine Gruppe mit dem Wert null beendet den Durchlauf nicht vorzeitig.

Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.

NESdev: Family-BASIC-Tastatur

Anwendungsbeispiel

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();}
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

In diesem KUROSAKI-Lauf sind DEVICE DETECT, ROW 6 COL 0 und A KEY - GUARDED jeweils 000; alle neun COL0/COL1-Paare sind 00. A liegt auf Zeile 6, Spalte 0, Bit 4. Die fehlgeschlagene Erkennung verhindert aber, dass der Rohwert null als Tastendruck gemeldet wird. Die Tabelle ist eine einmalige Startaufnahme.

__fkb_scan: KEYBOARD-DEFAULT — In diesem KUROSAKI-Lauf sind DEVICE DETECT, ROW 6 COL 0 und A KEY - GUARDED jeweils 000; alle neun COL0/COL1-Paare sind 00. A liegt auf Zeile 6, Spalte 0, Bit 4. Die fehlgeschlagene Erkennung verhindert aber, dass der Rohwert null als Tastendruck gemeldet wird. Die Tabelle ist eine einmalige Startaufnahme.
KEYBOARD-DEFAULT — In diesem KUROSAKI-Lauf sind DEVICE DETECT, ROW 6 COL 0 und A KEY - GUARDED jeweils 000; alle neun COL0/COL1-Paare sind 00. A liegt auf Zeile 6, Spalte 0, Bit 4. Die fehlgeschlagene Erkennung verhindert aber, dass der Rohwert null als Tastendruck gemeldet wird. Die Tabelle ist eine einmalige Startaufnahme.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:261

Deklarationshinweise im Original
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

Erzeugt SEI, setzt damit das I-Bit im CPU-Statusregister und maskiert normale IRQ-Anforderungen. Geeignet für einen gegen IRQ geschützten Abschnitt. NMI, APU-Tonausgabe und IRQ-Quelle bleiben unverändert; die vorherige Maske wird nicht gespeichert.

Syntax

c
void __irq_disable(void);

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Das I-Bit steuert nur normale IRQ: NMI kann diesen Abschnitt weiterhin unterbrechen. Eine IRQ-Sperre quittiert keine Geräteanforderung und schützt keine mit NMI geteilten Daten. Quellen und Handler separat vorbereiten. Das Beispiel testet kein IRQ-erzeugendes Peripheriegerät.

NESdev: NMI

NESdev: CPU-Interrupts

Anwendungsbeispiel

c
__irq_disable();
disabled=(__irq_save()&4)>>2; // The CPU I bit must be 1.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm unterscheidet zwischen der IRQ-Maske der CPU und der NMI-Steuerung der PPU. Blau auf Schwarz müssen IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 und RESTORED I = 000 erscheinen. Die Werte zeigen nur das extrahierte I-Bit, nicht das ganze Statusbyte. FAILED CHECKS muss 000 sein.

Mindestens 360 Emulatorframes ausführen, damit die Demonstration mit 256 Warteaufrufen abgeschlossen wird. NMI DELTA 3 WAITS muss trotz IRQ-Sperre 003 ergeben; NMI DELTA 256 WAITS wird durch den Byteumlauf zu 000. NMI OFF DELTA muss ebenfalls 000 sein. Das Programm verwendet den Standardhandler ohne runtime.c. Weitere 21 Zustandsprüfungen erfassen unter anderem die Wiederherstellung der Statusbits sowie zeitlich begrenzt beobachtete Wartefälle bei deaktivierter NMI oder leerem eigenem Handler. Geprüft wurde KUROSAKI/NROM, keine echte Hardware.

__irq_disable: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:176

Deklarationshinweise im Original
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

Erzeugt CLI, löscht das I-Bit im CPU-Statusregister und erlaubt die Bearbeitung normaler IRQ-Anforderungen. Eine Interruptquelle im Mapper oder in der APU wird nicht eingerichtet. Bereiten Sie vor deren Aktivierung den Handler und die Quittierung der IRQ-Anforderung vor.

Syntax

c
void __irq_enable(void);

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Das I-Bit steuert nur normale IRQ: NMI kann diesen Abschnitt weiterhin unterbrechen. Eine IRQ-Sperre quittiert keine Geräteanforderung und schützt keine mit NMI geteilten Daten. Quellen und Handler separat vorbereiten. Das Beispiel testet kein IRQ-erzeugendes Peripheriegerät.

NESdev: NMI

NESdev: CPU-Interrupts

Anwendungsbeispiel

c
__irq_enable();
enabled=(__irq_save()&4)>>2; // Save returns the old I bit: 0, then masks IRQ.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm unterscheidet zwischen der IRQ-Maske der CPU und der NMI-Steuerung der PPU. Blau auf Schwarz müssen IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 und RESTORED I = 000 erscheinen. Die Werte zeigen nur das extrahierte I-Bit, nicht das ganze Statusbyte. FAILED CHECKS muss 000 sein.

Mindestens 360 Emulatorframes ausführen, damit die Demonstration mit 256 Warteaufrufen abgeschlossen wird. NMI DELTA 3 WAITS muss trotz IRQ-Sperre 003 ergeben; NMI DELTA 256 WAITS wird durch den Byteumlauf zu 000. NMI OFF DELTA muss ebenfalls 000 sein. Das Programm verwendet den Standardhandler ohne runtime.c. Weitere 21 Zustandsprüfungen erfassen unter anderem die Wiederherstellung der Statusbits sowie zeitlich begrenzt beobachtete Wartefälle bei deaktivierter NMI oder leerem eigenem Handler. Geprüft wurde KUROSAKI/NROM, keine echte Hardware.

__irq_enable: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:178

Deklarationshinweise im Original
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

Wertet das übergebene Statusbyte aus, legt es mit PHA auf den Stack und stellt den CPU-Status mit PLP wieder her. Dabei werden die gespeicherte IRQ-Maske und die Rechenflags übernommen; IRQ wird nicht bedingungslos freigegeben. Nachfolgende C-Ausdrücke können die Rechenflags erneut verändern.

Syntax

c
void __irq_restore(u8 state);

Parameter

state

Der vollständige, von __irq_save erfasste 8-Bit-CPU-Status. Für __irq_restore aufbewahren, nicht in einen booleschen Wert umwandeln. Bit 2 ist I: 1 sperrt IRQ, 0 erlaubt IRQ. Die von PHP abgelegte Darstellung setzt die Bits 4 und 5.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Das I-Bit steuert nur normale IRQ: NMI kann diesen Abschnitt weiterhin unterbrechen. Eine IRQ-Sperre quittiert keine Geräteanforderung und schützt keine mit NMI geteilten Daten. Quellen und Handler separat vorbereiten. Das Beispiel testet kein IRQ-erzeugendes Peripheriegerät.

NESdev: NMI

NESdev: CPU-Interrupts

Anwendungsbeispiel

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.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm unterscheidet zwischen der IRQ-Maske der CPU und der NMI-Steuerung der PPU. Blau auf Schwarz müssen IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 und RESTORED I = 000 erscheinen. Die Werte zeigen nur das extrahierte I-Bit, nicht das ganze Statusbyte. FAILED CHECKS muss 000 sein.

Mindestens 360 Emulatorframes ausführen, damit die Demonstration mit 256 Warteaufrufen abgeschlossen wird. NMI DELTA 3 WAITS muss trotz IRQ-Sperre 003 ergeben; NMI DELTA 256 WAITS wird durch den Byteumlauf zu 000. NMI OFF DELTA muss ebenfalls 000 sein. Das Programm verwendet den Standardhandler ohne runtime.c. Weitere 21 Zustandsprüfungen erfassen unter anderem die Wiederherstellung der Statusbits sowie zeitlich begrenzt beobachtete Wartefälle bei deaktivierter NMI oder leerem eigenem Handler. Geprüft wurde KUROSAKI/NROM, keine echte Hardware.

__irq_restore: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:182

Deklarationshinweise im Original
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

Sichert das CPU-Statusbyte mit PHP, maskiert IRQ mit SEI und gibt das gesicherte Byte über PLA zurück. Das gespeicherte I-Bit beschreibt den Zustand vor der Maskierung. Mit __irq_restore bleibt auch bei verschachtelten kritischen Abschnitten eine zuvor aktive IRQ-Sperre erhalten.

Syntax

c
u8 __irq_save(void);

Rückgabewert

Der vollständige, von __irq_save erfasste 8-Bit-CPU-Status. Für __irq_restore aufbewahren, nicht in einen booleschen Wert umwandeln. Bit 2 ist I: 1 sperrt IRQ, 0 erlaubt IRQ. Die von PHP abgelegte Darstellung setzt die Bits 4 und 5.

Bedingungen und Grenzen

Das I-Bit steuert nur normale IRQ: NMI kann diesen Abschnitt weiterhin unterbrechen. Eine IRQ-Sperre quittiert keine Geräteanforderung und schützt keine mit NMI geteilten Daten. Quellen und Handler separat vorbereiten. Das Beispiel testet kein IRQ-erzeugendes Peripheriegerät.

NESdev: NMI

NESdev: CPU-Interrupts

Anwendungsbeispiel

c
saved_state=__irq_save(); // Save all processor-status bits, then set I.
inside=(__irq_save()&4)>>2; // IRQ remains masked inside this section.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm unterscheidet zwischen der IRQ-Maske der CPU und der NMI-Steuerung der PPU. Blau auf Schwarz müssen IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 und RESTORED I = 000 erscheinen. Die Werte zeigen nur das extrahierte I-Bit, nicht das ganze Statusbyte. FAILED CHECKS muss 000 sein.

Mindestens 360 Emulatorframes ausführen, damit die Demonstration mit 256 Warteaufrufen abgeschlossen wird. NMI DELTA 3 WAITS muss trotz IRQ-Sperre 003 ergeben; NMI DELTA 256 WAITS wird durch den Byteumlauf zu 000. NMI OFF DELTA muss ebenfalls 000 sein. Das Programm verwendet den Standardhandler ohne runtime.c. Weitere 21 Zustandsprüfungen erfassen unter anderem die Wiederherstellung der Statusbits sowie zeitlich begrenzt beobachtete Wartefälle bei deaktivierter NMI oder leerem eigenem Handler. Geprüft wurde KUROSAKI/NROM, keine echte Hardware.

__irq_save: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:180

Deklarationshinweise im Original
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

Alias des MMC3-Latch-/Nachladevorgangs in __mapper_irq_set. Das Wort scanline garantiert keine absolute Bildschirmzeile.

Syntax

c
void __irq_scanline_set(u8 line);

Parameter

line

Acht-Bit-Latchwert des Zählers von 0 bis 255, keine absolute Bildschirm-Y-Koordinate. Nachladezeitpunkt und qualifizierte PPU-A12-Übergänge bestimmen die Auslösung. Null ist gültig und bedeutet nicht „Interrupts deaktivieren“.

Rückgabewert

Kein Rückgabewert.

Bedingungen und Grenzen

Die Intrinsic-Deklarationen stehen in intrinsics.h. Binden Sie für die Makros nes_mapper_irq_* den Header nes_game.h ein. Wählen Sie die Platine beim Build mit --mapper.

Nur MMC3. Erforderlich sind ein __nes_irq-Handler, qualifizierte PPU-A12-Übergänge, Mapper-IRQ-Freigabe und CPU-__irq_enable(). Das Beispiel aktiviert die Darstellung mit Hintergrundmustern bei $0000 und Spritemustern bei $1000. Die Lektion prüft keine Synchronisierung auf eine exakte Zeile echter Hardware.

MMC3-Quittierung und -Deaktivierung schreiben beide nach $E000. Das löscht die aktuelle Anforderung und deaktiviert weitere. Rufen Sie zum gewünschten Zeitpunkt erneut __mapper_irq_enable() auf, um einen weiteren Interrupt freizugeben. Beide Vorgänge ändern weder CPU-Interruptmaske noch NMI-Freigabe.

Der Compiler erzeugt RTI für eigene Interrupt-Handler. Der Handler muss aber alle von ihm geänderten A/X/Y-Register und gemeinsamen temporären Speicher sichern. Das Beispiel sichert A/X/Y und verwendet nur ein Byteinkrement sowie eine argumentlose Quittierung. Prüfen Sie bei zusätzlichen C-Aufrufen auch deren gemeinsam genutzten Speicher.

Anwendungsbeispiel

c
irq_phase=2;
    __irq_scanline_set(7); __mapper_irq_enable();
    for (i=0;i<6;i++) { m_wait(); }
    mapper_result[17]=irq_count;

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das MMC3-Beispiel zeigt CHR-Marker 19/20, 21/22 und 23/24. CIRAM-Lesewerte ergeben A B A B bei vertikaler Spiegelung und A A B B bei horizontaler. Die IRQ-Zählerstände sind 01→02→03→03: Jede der ersten drei ausdrücklichen Freigaben erlaubt einen Interrupt; nach Deaktivierung bleibt der Stand gleich. Jede Stufe wartet sechs Frames.

__irq_scanline_set: MMC3-DEFAULT
MMC3-DEFAULT

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:324

Deklarationshinweise im Original
Write the MMC3 IRQ latch and reload registers. This does not enable IRQ delivery.
__joypad2p_voice

__joypad2p_voice ist ein weiterer Aufrufname für die Routine __mic_read2p. Beide lesen dasselbe digitale Mikrofonbit. „Voice“ bezeichnet keine Audioverarbeitung: Jeder Aufruf liest einen neuen Wert, der sich bei einer Signaländerung zwischen den Aufrufen unterscheiden kann.

Liest das digitale Mikrofonsignal des zweiten Famicom-Controllers aus Bit 2 von $4016. Die Routine liest das Register einmal, maskiert mit 0x04 und normalisiert ohne Invertierung auf 0 oder 1. Sie übernimmt keine Controllerzustände, zeichnet kein Audio auf, misst keine Lautstärke und erkennt keine Sprache.

Syntax

c
u8 __joypad2p_voice(void);

Rückgabewert

Genau 1, wenn $4016 & 0x04 ungleich null ist, andernfalls 0. Dies ist der gelesene Digitalpegel, keine Tastenmaske, Amplitude, Frequenz oder Anzahl gesprochener Wörter.

Bedingungen und Grenzen

Das Mikrofon gehört zu Controller 2, die Routine greift jedoch auf $4016 statt $4017 zu. Ein Registerzugriff kann bei niedrigem Strobe auch den seriellen Controllerstrom weiterschalten. Fügen Sie daher keine Mikrofonzugriffe in die Achtbit-Leseschleife eines anderen Controllers ein. Ein gespeicherter Eingabeverlauf wird nicht aktualisiert.

Das veröffentlichte KUROSAKI-Controllermodell liefert nur D0 und das feste Bit 6. D1 und das Mikrofonbit sind in diesem Emulator daher stets 0, auch bei gedrückten normalen Tasten. Der beigefügte Lauf prüft Kompilierung, normale Tasteneingaben und diese Nullsignal-Beobachtung. Peripheriesignale ungleich null, elektrische Zeitabläufe und reale Gerätekompatibilität bleiben ungeprüft. Eine Null beweist hier weder, dass das Gerät funktioniert, noch dass die Funktionen es auf echter Hardware nicht lesen können.

Anwendungsbeispiel

c
voice=__joypad2p_voice();

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Dieses Programm vergleicht normale D0-Eingaben mit D1 und dem digitalen Mikrofonpegel. Halten Sie A auf Controller 1 und B auf Controller 2 bis zur Erfassung gedrückt. NORMAL P1 und NORMAL P2 zeigen 001 und 002. Im beigefügten KUROSAKI-Lauf zeigen D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL und MIC DIRECT jeweils 000, weil diese Leitungen nicht emuliert werden. Die erfassten Werte bleiben anschließend stehen. Mit geeigneten Peripheriegeräten und implementierten Signalleitungen hängen die Werte von den tatsächlichen Eingaben ab; deuten Sie sie anhand der obigen Bitbelegung, statt diese Nullen zu erwarten.

Das veröffentlichte KUROSAKI-Controllermodell liefert nur D0 und das feste Bit 6. D1 und das Mikrofonbit sind in diesem Emulator daher stets 0, auch bei gedrückten normalen Tasten. Der beigefügte Lauf prüft Kompilierung, normale Tasteneingaben und diese Nullsignal-Beobachtung. Peripheriesignale ungleich null, elektrische Zeitabläufe und reale Gerätekompatibilität bleiben ungeprüft. Eine Null beweist hier weder, dass das Gerät funktioniert, noch dass die Funktionen es auf echter Hardware nicht lesen können.

__joypad2p_voice: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:231

Deklarationshinweise im Original
Return controller-port 4016 bit 2 normalized to 0 or 1; this is a microphone level bit, not PCM audio.
__mac16

Berechnet eine vorzeichenlose Multiplikation mit Akkumulation.

Syntax

c
u16 __mac16(u16 acc, u16 a, u8 b);

Parameter

acc

Zu addierender 16-Bit-Akkumulator.

a

Vorzeichenloses Wort, 0..65535.

b

Vorzeichenloser Koeffizient, 0..255.

Rückgabewert

Gibt die unteren 16 Bits von acc+a*b zurück.

Bedingungen und Grenzen

Auf dem FC verwenden die q8_8/q1_7-Funktionen rohe Ganzzahlprodukte ohne Festkomma-Rechtsshift. Q1.7-Funktionen sind Aliase der entsprechenden vorzeichenbehafteten Operationen. Der aufrufende Code verwaltet die Einheiten; vorzeichenbehaftete Ergebnisse werden als u16-Bitmuster zurückgegeben.

Anwendungsbeispiel

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);

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.

Der Ausschnitt speichert folgende 16-Bit-Werte: result[8]=65115, result[9]=65115. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

__mac16: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:55

Deklarationshinweise im Original
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

Addiert die absoluten X- und Y-Differenzen zweier Punkte.

Syntax

c
u8 __manhattan(u8 x1, u8 y1, u8 x2, u8 y2);

Parameter

x1

X-Koordinate des ersten Punkts, 0..255.

y1

Y-Koordinate des ersten Punkts, 0..255.

x2

X-Koordinate des zweiten Punkts, 0..255.

y2

Y-Koordinate des zweiten Punkts, 0..255.

Rückgabewert

Gibt das untere Byte von |x1−x2|+|y1−y2| zurück; Distanzen über 255 laufen um.

Anwendungsbeispiel

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

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Prüfen Sie Innenraum, Grenzen, einen äußeren Punkt und Breite null: INSIDE=1, RIGHT EDGE=0, LEFT EDGE=0, HIGH X=1. Prüfen Sie Distanz 15, umgelaufene Distanz 254 und Array-Indizes 64/65280.

__manhattan: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:33

Deklarationshinweise im Original
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

Wandelt eine Zellkoordinate in einen zeilenweisen Array-Index um.

Syntax

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

Parameter

x

Spalte 0..255, gewöhnlich kleiner als width.

y

Zeile, 0..255.

width

Zellen je Zeile, 0..255.

Rückgabewert

Gibt y*width+x als u16 zurück; prüft die Koordinaten nicht.

Anwendungsbeispiel

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

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Prüfen Sie Innenraum, Grenzen, einen äußeren Punkt und Breite null: INSIDE=1, RIGHT EDGE=0, LEFT EDGE=0, HIGH X=1. Prüfen Sie Distanz 15, umgelaufene Distanz 254 und Array-Indizes 64/65280.

__map_index: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:35

Deklarationshinweise im Original
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

Gibt die beim Build gewählte Mappernummer für platinenspezifische Initialisierung oder Anzeige zurück.

Syntax

c
u8 __mapper_id(void);

Rückgabewert

Acht-Bit-Mappernummer: NROM=0, MMC1=1, UxROM=2, CNROM=3, MMC3=4, MMC5=5, AxROM=7, VRC6=24, FME7=69 und VRC7=85. Der Compiler erzeugt eine Konstante; laufende Hardware wird nicht untersucht.

Bedingungen und Grenzen

Die Intrinsic-Deklarationen stehen in intrinsics.h. Binden Sie für die Makros nes_mapper_irq_* den Header nes_game.h ein. Wählen Sie die Platine beim Build mit --mapper.

Anwendungsbeispiel

c
mapper_result[0] = __mapper_id();

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das MMC3-Beispiel zeigt CHR-Marker 19/20, 21/22 und 23/24. CIRAM-Lesewerte ergeben A B A B bei vertikaler Spiegelung und A A B B bei horizontaler. Die IRQ-Zählerstände sind 01→02→03→03: Jede der ersten drei ausdrücklichen Freigaben erlaubt einen Interrupt; nach Deaktivierung bleibt der Stand gleich. Jede Stufe wartet sechs Frames.

__mapper_id: MMC3-DEFAULT
MMC3-DEFAULT

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:314

Deklarationshinweise im Original
Mapper
Return the low byte of the mapper number selected for this build; no cartridge probe is performed.
__mapper_irq_ack

Quittiert MMC3 im IRQ-Handler. Da nach $E000 geschrieben wird, werden auch weitere IRQ-Anforderungen deaktiviert.

Syntax

c
void __mapper_irq_ack(void);

Rückgabewert

Kein Rückgabewert.

Bedingungen und Grenzen

Die Intrinsic-Deklarationen stehen in intrinsics.h. Binden Sie für die Makros nes_mapper_irq_* den Header nes_game.h ein. Wählen Sie die Platine beim Build mit --mapper.

Nur MMC3. Erforderlich sind ein __nes_irq-Handler, qualifizierte PPU-A12-Übergänge, Mapper-IRQ-Freigabe und CPU-__irq_enable(). Das Beispiel aktiviert die Darstellung mit Hintergrundmustern bei $0000 und Spritemustern bei $1000. Die Lektion prüft keine Synchronisierung auf eine exakte Zeile echter Hardware.

MMC3-Quittierung und -Deaktivierung schreiben beide nach $E000. Das löscht die aktuelle Anforderung und deaktiviert weitere. Rufen Sie zum gewünschten Zeitpunkt erneut __mapper_irq_enable() auf, um einen weiteren Interrupt freizugeben. Beide Vorgänge ändern weder CPU-Interruptmaske noch NMI-Freigabe.

Der Compiler erzeugt RTI für eigene Interrupt-Handler. Der Handler muss aber alle von ihm geänderten A/X/Y-Register und gemeinsamen temporären Speicher sichern. Das Beispiel sichert A/X/Y und verwendet nur ein Byteinkrement sowie eine argumentlose Quittierung. Prüfen Sie bei zusätzlichen C-Aufrufen auch deren gemeinsam genutzten Speicher.

Anwendungsbeispiel

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
    }
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das MMC3-Beispiel zeigt CHR-Marker 19/20, 21/22 und 23/24. CIRAM-Lesewerte ergeben A B A B bei vertikaler Spiegelung und A A B B bei horizontaler. Die IRQ-Zählerstände sind 01→02→03→03: Jede der ersten drei ausdrücklichen Freigaben erlaubt einen Interrupt; nach Deaktivierung bleibt der Stand gleich. Jede Stufe wartet sechs Frames.

__mapper_irq_ack: MMC3-DEFAULT
MMC3-DEFAULT

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:332

Deklarationshinweise im Original
Use the same E000 write as disable; explicitly re-enable when another interrupt is needed.
__mapper_irq_disable

Schreibt nach $E000, löscht damit die MMC3-IRQ-Anforderung und deaktiviert weitere Anforderungen.

Syntax

c
void __mapper_irq_disable(void);

Rückgabewert

Kein Rückgabewert.

Bedingungen und Grenzen

Die Intrinsic-Deklarationen stehen in intrinsics.h. Binden Sie für die Makros nes_mapper_irq_* den Header nes_game.h ein. Wählen Sie die Platine beim Build mit --mapper.

Nur MMC3. Erforderlich sind ein __nes_irq-Handler, qualifizierte PPU-A12-Übergänge, Mapper-IRQ-Freigabe und CPU-__irq_enable(). Das Beispiel aktiviert die Darstellung mit Hintergrundmustern bei $0000 und Spritemustern bei $1000. Die Lektion prüft keine Synchronisierung auf eine exakte Zeile echter Hardware.

MMC3-Quittierung und -Deaktivierung schreiben beide nach $E000. Das löscht die aktuelle Anforderung und deaktiviert weitere. Rufen Sie zum gewünschten Zeitpunkt erneut __mapper_irq_enable() auf, um einen weiteren Interrupt freizugeben. Beide Vorgänge ändern weder CPU-Interruptmaske noch NMI-Freigabe.

Der Compiler erzeugt RTI für eigene Interrupt-Handler. Der Handler muss aber alle von ihm geänderten A/X/Y-Register und gemeinsamen temporären Speicher sichern. Das Beispiel sichert A/X/Y und verwendet nur ein Byteinkrement sowie eine argumentlose Quittierung. Prüfen Sie bei zusätzlichen C-Aufrufen auch deren gemeinsam genutzten Speicher.

Anwendungsbeispiel

c
__irq_disable(); __mapper_irq_disable();

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das MMC3-Beispiel zeigt CHR-Marker 19/20, 21/22 und 23/24. CIRAM-Lesewerte ergeben A B A B bei vertikaler Spiegelung und A A B B bei horizontaler. Die IRQ-Zählerstände sind 01→02→03→03: Jede der ersten drei ausdrücklichen Freigaben erlaubt einen Interrupt; nach Deaktivierung bleibt der Stand gleich. Jede Stufe wartet sechs Frames.

__mapper_irq_disable: MMC3-DEFAULT
MMC3-DEFAULT

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:330

Deklarationshinweise im Original
Write MMC3 E000 to disable and acknowledge mapper IRQs.
__mapper_irq_enable

Schreibt nach $E001, damit MMC3 bei erfüllter Zählerbedingung IRQ anfordern kann. Setzt weder das Latch noch eine Nachladeanforderung.

Syntax

c
void __mapper_irq_enable(void);

Rückgabewert

Kein Rückgabewert.

Bedingungen und Grenzen

Die Intrinsic-Deklarationen stehen in intrinsics.h. Binden Sie für die Makros nes_mapper_irq_* den Header nes_game.h ein. Wählen Sie die Platine beim Build mit --mapper.

Nur MMC3. Erforderlich sind ein __nes_irq-Handler, qualifizierte PPU-A12-Übergänge, Mapper-IRQ-Freigabe und CPU-__irq_enable(). Das Beispiel aktiviert die Darstellung mit Hintergrundmustern bei $0000 und Spritemustern bei $1000. Die Lektion prüft keine Synchronisierung auf eine exakte Zeile echter Hardware.

MMC3-Quittierung und -Deaktivierung schreiben beide nach $E000. Das löscht die aktuelle Anforderung und deaktiviert weitere. Rufen Sie zum gewünschten Zeitpunkt erneut __mapper_irq_enable() auf, um einen weiteren Interrupt freizugeben. Beide Vorgänge ändern weder CPU-Interruptmaske noch NMI-Freigabe.

Der Compiler erzeugt RTI für eigene Interrupt-Handler. Der Handler muss aber alle von ihm geänderten A/X/Y-Register und gemeinsamen temporären Speicher sichern. Das Beispiel sichert A/X/Y und verwendet nur ein Byteinkrement sowie eine argumentlose Quittierung. Prüfen Sie bei zusätzlichen C-Aufrufen auch deren gemeinsam genutzten Speicher.

Anwendungsbeispiel

c
__mapper_irq_set(37); __mapper_irq_enable(); __irq_enable();
    for (i=0;i<6;i++) { m_wait(); }
    mapper_result[15]=irq_count;

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das MMC3-Beispiel zeigt CHR-Marker 19/20, 21/22 und 23/24. CIRAM-Lesewerte ergeben A B A B bei vertikaler Spiegelung und A A B B bei horizontaler. Die IRQ-Zählerstände sind 01→02→03→03: Jede der ersten drei ausdrücklichen Freigaben erlaubt einen Interrupt; nach Deaktivierung bleibt der Stand gleich. Jede Stufe wartet sechs Frames.

__mapper_irq_enable: MMC3-DEFAULT
MMC3-DEFAULT

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:328

Deklarationshinweise im Original
Write MMC3 E001. The CPU IRQ mask and a valid handler are separate caller responsibilities.
__mapper_irq_set

Schreibt das MMC3-IRQ-Latch bei $C000 und fordert über $C001 das nächste Nachladen an. Die Interruptfreigabe erfolgt separat.

Syntax

c
void __mapper_irq_set(u8 scanline);

Parameter

scanline

Acht-Bit-Latchwert des Zählers von 0 bis 255, keine absolute Bildschirm-Y-Koordinate. Nachladezeitpunkt und qualifizierte PPU-A12-Übergänge bestimmen die Auslösung. Null ist gültig und bedeutet nicht „Interrupts deaktivieren“.

Rückgabewert

Kein Rückgabewert.

Bedingungen und Grenzen

Die Intrinsic-Deklarationen stehen in intrinsics.h. Binden Sie für die Makros nes_mapper_irq_* den Header nes_game.h ein. Wählen Sie die Platine beim Build mit --mapper.

Nur MMC3. Erforderlich sind ein __nes_irq-Handler, qualifizierte PPU-A12-Übergänge, Mapper-IRQ-Freigabe und CPU-__irq_enable(). Das Beispiel aktiviert die Darstellung mit Hintergrundmustern bei $0000 und Spritemustern bei $1000. Die Lektion prüft keine Synchronisierung auf eine exakte Zeile echter Hardware.

MMC3-Quittierung und -Deaktivierung schreiben beide nach $E000. Das löscht die aktuelle Anforderung und deaktiviert weitere. Rufen Sie zum gewünschten Zeitpunkt erneut __mapper_irq_enable() auf, um einen weiteren Interrupt freizugeben. Beide Vorgänge ändern weder CPU-Interruptmaske noch NMI-Freigabe.

Der Compiler erzeugt RTI für eigene Interrupt-Handler. Der Handler muss aber alle von ihm geänderten A/X/Y-Register und gemeinsamen temporären Speicher sichern. Das Beispiel sichert A/X/Y und verwendet nur ein Byteinkrement sowie eine argumentlose Quittierung. Prüfen Sie bei zusätzlichen C-Aufrufen auch deren gemeinsam genutzten Speicher.

Anwendungsbeispiel

c
__mapper_irq_set(37); __mapper_irq_enable(); __irq_enable();
    for (i=0;i<6;i++) { m_wait(); }
    mapper_result[15]=irq_count;

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das MMC3-Beispiel zeigt CHR-Marker 19/20, 21/22 und 23/24. CIRAM-Lesewerte ergeben A B A B bei vertikaler Spiegelung und A A B B bei horizontaler. Die IRQ-Zählerstände sind 01→02→03→03: Jede der ersten drei ausdrücklichen Freigaben erlaubt einen Interrupt; nach Deaktivierung bleibt der Stand gleich. Jede Stufe wartet sechs Frames.

__mapper_irq_set: MMC3-DEFAULT
MMC3-DEFAULT

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:326

Deklarationshinweise im Original
Alias the MMC3 IRQ latch/reload operation; other mapper profiles are rejected.
__memcpy

Kopiert die angegebene Anzahl Bytes von src nach dst. Der 16-Bit-Zähler erlaubt auch Übertragungen mit mehr als 255 Bytes, etwa eine vollständige Tabelle im RAM. Gelesen und geschrieben wird in aufsteigender Adressfolge.

Syntax

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

Parameter

dst

Zeiger auf das erste beschreibbare Zielbyte. Stellen Sie genügend zusammenhängenden Speicher für die gesamte Operation bereit; ein Puffer wird nicht automatisch angelegt.

src

Zeiger auf das erste lesbare Quellbyte. Der gesamte Bereich muss während der Kopie zugänglich und unverändert bleiben.

len

Byteanzahl als u16 (0–65535), keine Elementanzahl. Der Höchstwert des Typs bedeutet nicht, dass auf der Zielmaschine ein gültiger Puffer dieser Größe verfügbar ist. Der gesamte Bereich muss in die gewählte Speicherabbildung passen.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Bei Länge null werden keine Bytes übertragen. Seiteneffekte in den Argumentausdrücken finden trotzdem statt: Das Beispiel übergibt zwei Funktionen, die Zeiger liefern, und bestätigt beide Aufrufe trotz Kopierlänge null.

Verwenden Sie Quell- und Zielbereiche, die sich nicht überlappen. Diese Routinen kopieren vorwärts und bieten nicht das überlappungssichere Verhalten von memmove. Schreibzugriffe auf noch ungelesene Quellbytes können die spätere Kopie verfälschen.

Die Operation wird vor der Rückkehr abgeschlossen. Sie schaltet keine ROM/RAM-Bänke um und begrenzt den Bereich nicht automatisch auf Puffergrenzen. Halten Sie die Speicherabbildung stabil, verwenden Sie gültige Bereiche und stimmen Sie Zugriffe mit Interrupt-Routinen ab, die dieselben Puffer verwenden.

Auf dem FC ist dst eine CPU-Adresse, keine PPU-Adresse. Eine Kopie ins CPU-RAM aktualisiert weder CHR noch Nametables oder Paletten; verwenden Sie dafür die PPU/VRAM-APIs. Generierte Hilfsroutinen teilen sich Argument- und Arbeitsspeicher. Rufen Sie sie während einer laufenden Operation nicht erneut aus einer NMI/IRQ-Routine auf.

Anwendungsbeispiel

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

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Beispiel kopiert mit __memcpy ab Offset 1 ein bekanntes Muster von 257 Bytes. memory_check vergleicht die vollständigen Quell- und Zielarrays; die Zeile COPY WORD muss 257 anzeigen. Das erste Byte bleibt als Schutzbereich frei. Auch die Bytes nach dem Nutzdatenbereich müssen unverändert bleiben.

Das vollständige Programm initialisiert vor jeder Operation zwei 264-Byte-Puffer und prüft danach jedes Quell- und Zielbyte sowie die unveränderten Schutzbereiche. Erwartet werden COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 und FAILED 000. WORD/BYTE bezeichnen die Zählerbreite, nicht die Größe der kopierten Elemente. Die Zahlen stammen aus den Prüfungen des Beispiels; diese void-Funktionen liefern keine Rückgabewerte.

Die Aufnahmen zeigen schwarze Schrift auf Weiß beim DMG, blaue Schrift auf Weiß beim CGB und blaue Schrift auf Schwarz beim FC. Das Beispiel prüft RAM-Operationen; die Schrift stellt die Ergebnisse dar und demonstriert keine Zeichenprimitive. Weitere Emulatorprüfungen decken je Plattform 88 Speicherfälle ab: konstante und zur Laufzeit bestimmte Längen von 0, 1, 16, 17, 32, 33, 64, 65 und 255 Bytes sowie 256 und 257 bei 16-Bit-Zählern. Auf echter Hardware wurde nicht getestet.

__memcpy: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:19

Deklarationshinweise im Original
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

Kopiert mit einem 8-Bit-Zähler einen Block von höchstens 255 Bytes. Diese Form eignet sich, wenn die Länge in ein Byte passt. _small bezeichnet die Breite des Zählers und garantiert weder eine bestimmte Laufzeit noch eine bestimmte Codegröße.

Syntax

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

Parameter

dst

Zeiger auf das erste beschreibbare Zielbyte. Stellen Sie genügend zusammenhängenden Speicher für die gesamte Operation bereit; ein Puffer wird nicht automatisch angelegt.

src

Zeiger auf das erste lesbare Quellbyte. Der gesamte Bereich muss während der Kopie zugänglich und unverändert bleiben.

len

Byteanzahl als u8: 0–255. Übergeben Sie nicht 256, um einen vollständigen 256-Byte-Block anzugeben; diese Länge passt nicht in den Parameter. Ab 256 Bytes verwenden Sie die entsprechende Funktion ohne _small.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Bei Länge null werden keine Bytes übertragen. Seiteneffekte in den Argumentausdrücken finden trotzdem statt: Das Beispiel übergibt zwei Funktionen, die Zeiger liefern, und bestätigt beide Aufrufe trotz Kopierlänge null.

Verwenden Sie Quell- und Zielbereiche, die sich nicht überlappen. Diese Routinen kopieren vorwärts und bieten nicht das überlappungssichere Verhalten von memmove. Schreibzugriffe auf noch ungelesene Quellbytes können die spätere Kopie verfälschen.

Die Operation wird vor der Rückkehr abgeschlossen. Sie schaltet keine ROM/RAM-Bänke um und begrenzt den Bereich nicht automatisch auf Puffergrenzen. Halten Sie die Speicherabbildung stabil, verwenden Sie gültige Bereiche und stimmen Sie Zugriffe mit Interrupt-Routinen ab, die dieselben Puffer verwenden.

Auf dem FC ist dst eine CPU-Adresse, keine PPU-Adresse. Eine Kopie ins CPU-RAM aktualisiert weder CHR noch Nametables oder Paletten; verwenden Sie dafür die PPU/VRAM-APIs. Generierte Hilfsroutinen teilen sich Argument- und Arbeitsspeicher. Rufen Sie sie während einer laufenden Operation nicht erneut aus einer NMI/IRQ-Routine auf.

Anwendungsbeispiel

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

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Beispiel kopiert mit __memcpy_small ab Offset 1 ein bekanntes Muster von 255 Bytes. memory_check vergleicht die vollständigen Quell- und Zielarrays; die Zeile COPY BYTE muss 255 anzeigen. Das erste Byte bleibt als Schutzbereich frei. Auch die Bytes nach dem Nutzdatenbereich müssen unverändert bleiben.

Das vollständige Programm initialisiert vor jeder Operation zwei 264-Byte-Puffer und prüft danach jedes Quell- und Zielbyte sowie die unveränderten Schutzbereiche. Erwartet werden COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 und FAILED 000. WORD/BYTE bezeichnen die Zählerbreite, nicht die Größe der kopierten Elemente. Die Zahlen stammen aus den Prüfungen des Beispiels; diese void-Funktionen liefern keine Rückgabewerte.

Die Aufnahmen zeigen schwarze Schrift auf Weiß beim DMG, blaue Schrift auf Weiß beim CGB und blaue Schrift auf Schwarz beim FC. Das Beispiel prüft RAM-Operationen; die Schrift stellt die Ergebnisse dar und demonstriert keine Zeichenprimitive. Weitere Emulatorprüfungen decken je Plattform 88 Speicherfälle ab: konstante und zur Laufzeit bestimmte Längen von 0, 1, 16, 17, 32, 33, 64, 65 und 255 Bytes sowie 256 und 257 bei 16-Bit-Zählern. Auf echter Hardware wurde nicht getestet.

__memcpy_small: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:23

Deklarationshinweise im Original
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

Füllt den Zielbereich mit demselben Bytewert. Der 16-Bit-Zähler eignet sich zum Leeren größerer Puffer oder zum Initialisieren einer Tabelle. Mit 0x5A wird beispielsweise 5A 5A 5A… geschrieben; eine mehrbyteige Ganzzahl wird dabei nicht wiederholt.

Syntax

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

Parameter

dst

Zeiger auf das erste beschreibbare Zielbyte. Stellen Sie genügend zusammenhängenden Speicher für die gesamte Operation bereit; ein Puffer wird nicht automatisch angelegt.

value

Bytewert von 0 bis 255, der an jede Zieladresse geschrieben wird. Mit 0 wird der Bereich gelöscht. Soll ein u16-Array einen numerischen Wert ungleich null erhalten, weisen Sie diesen jedem Element einzeln zu.

len

Byteanzahl als u16 (0–65535), keine Elementanzahl. Der Höchstwert des Typs bedeutet nicht, dass auf der Zielmaschine ein gültiger Puffer dieser Größe verfügbar ist. Der gesamte Bereich muss in die gewählte Speicherabbildung passen.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Bei Länge null werden keine Bytes übertragen. Seiteneffekte in den Argumentausdrücken finden trotzdem statt: Das Beispiel übergibt zwei Funktionen, die Zeiger liefern, und bestätigt beide Aufrufe trotz Kopierlänge null.

Die Operation wird vor der Rückkehr abgeschlossen. Sie schaltet keine ROM/RAM-Bänke um und begrenzt den Bereich nicht automatisch auf Puffergrenzen. Halten Sie die Speicherabbildung stabil, verwenden Sie gültige Bereiche und stimmen Sie Zugriffe mit Interrupt-Routinen ab, die dieselben Puffer verwenden.

Auf dem FC ist dst eine CPU-Adresse, keine PPU-Adresse. Eine Kopie ins CPU-RAM aktualisiert weder CHR noch Nametables oder Paletten; verwenden Sie dafür die PPU/VRAM-APIs. Generierte Hilfsroutinen teilen sich Argument- und Arbeitsspeicher. Rufen Sie sie während einer laufenden Operation nicht erneut aus einer NMI/IRQ-Routine auf.

Anwendungsbeispiel

c
memory_reset();
__memset(memory_destination+1,0x5A,257);
memory_fill_word=memory_check(257,1); // Repeat one byte, not a 16-bit word.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Beispiel füllt mit __memset ab Offset 1 genau 257 Bytes mit 0x5A. memory_check verlangt für jedes Zielbyte 0x5A, für die Schutzbytes weiterhin 0xCC und einen unveränderten separaten Quellpuffer. Die Zeile FILL WORD muss 257 anzeigen.

Das vollständige Programm initialisiert vor jeder Operation zwei 264-Byte-Puffer und prüft danach jedes Quell- und Zielbyte sowie die unveränderten Schutzbereiche. Erwartet werden COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 und FAILED 000. WORD/BYTE bezeichnen die Zählerbreite, nicht die Größe der kopierten Elemente. Die Zahlen stammen aus den Prüfungen des Beispiels; diese void-Funktionen liefern keine Rückgabewerte.

Die Aufnahmen zeigen schwarze Schrift auf Weiß beim DMG, blaue Schrift auf Weiß beim CGB und blaue Schrift auf Schwarz beim FC. Das Beispiel prüft RAM-Operationen; die Schrift stellt die Ergebnisse dar und demonstriert keine Zeichenprimitive. Weitere Emulatorprüfungen decken je Plattform 88 Speicherfälle ab: konstante und zur Laufzeit bestimmte Längen von 0, 1, 16, 17, 32, 33, 64, 65 und 255 Bytes sowie 256 und 257 bei 16-Bit-Zählern. Auf echter Hardware wurde nicht getestet.

__memset: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:21

Deklarationshinweise im Original
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

Füllt mit einem 8-Bit-Zähler bis zu 255 aufeinanderfolgende Bytes mit demselben Wert. Das eignet sich zum Initialisieren eines kleinen Arbeitspuffers. Für mehr als 255 Bytes verwenden Sie __memset.

Syntax

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

Parameter

dst

Zeiger auf das erste beschreibbare Zielbyte. Stellen Sie genügend zusammenhängenden Speicher für die gesamte Operation bereit; ein Puffer wird nicht automatisch angelegt.

value

Bytewert von 0 bis 255, der an jede Zieladresse geschrieben wird. Mit 0 wird der Bereich gelöscht. Soll ein u16-Array einen numerischen Wert ungleich null erhalten, weisen Sie diesen jedem Element einzeln zu.

len

Byteanzahl als u8: 0–255. Übergeben Sie nicht 256, um einen vollständigen 256-Byte-Block anzugeben; diese Länge passt nicht in den Parameter. Ab 256 Bytes verwenden Sie die entsprechende Funktion ohne _small.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Bei Länge null werden keine Bytes übertragen. Seiteneffekte in den Argumentausdrücken finden trotzdem statt: Das Beispiel übergibt zwei Funktionen, die Zeiger liefern, und bestätigt beide Aufrufe trotz Kopierlänge null.

Die Operation wird vor der Rückkehr abgeschlossen. Sie schaltet keine ROM/RAM-Bänke um und begrenzt den Bereich nicht automatisch auf Puffergrenzen. Halten Sie die Speicherabbildung stabil, verwenden Sie gültige Bereiche und stimmen Sie Zugriffe mit Interrupt-Routinen ab, die dieselben Puffer verwenden.

Auf dem FC ist dst eine CPU-Adresse, keine PPU-Adresse. Eine Kopie ins CPU-RAM aktualisiert weder CHR noch Nametables oder Paletten; verwenden Sie dafür die PPU/VRAM-APIs. Generierte Hilfsroutinen teilen sich Argument- und Arbeitsspeicher. Rufen Sie sie während einer laufenden Operation nicht erneut aus einer NMI/IRQ-Routine auf.

Anwendungsbeispiel

c
memory_reset();
__memset_small(memory_destination+1,0x5A,255);
memory_fill_byte=memory_check(255,1);

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Beispiel füllt mit __memset_small ab Offset 1 genau 255 Bytes mit 0x5A. memory_check verlangt für jedes Zielbyte 0x5A, für die Schutzbytes weiterhin 0xCC und einen unveränderten separaten Quellpuffer. Die Zeile FILL BYTE muss 255 anzeigen.

Das vollständige Programm initialisiert vor jeder Operation zwei 264-Byte-Puffer und prüft danach jedes Quell- und Zielbyte sowie die unveränderten Schutzbereiche. Erwartet werden COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 und FAILED 000. WORD/BYTE bezeichnen die Zählerbreite, nicht die Größe der kopierten Elemente. Die Zahlen stammen aus den Prüfungen des Beispiels; diese void-Funktionen liefern keine Rückgabewerte.

Die Aufnahmen zeigen schwarze Schrift auf Weiß beim DMG, blaue Schrift auf Weiß beim CGB und blaue Schrift auf Schwarz beim FC. Das Beispiel prüft RAM-Operationen; die Schrift stellt die Ergebnisse dar und demonstriert keine Zeichenprimitive. Weitere Emulatorprüfungen decken je Plattform 88 Speicherfälle ab: konstante und zur Laufzeit bestimmte Längen von 0, 1, 16, 17, 32, 33, 64, 65 und 255 Bytes sowie 256 und 257 bei 16-Bit-Zählern. Auf echter Hardware wurde nicht getestet.

__memset_small: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:25

Deklarationshinweise im Original
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

Entpackt eine mit einer Endmarkierung versehene Folge von Sprite-Teilen in aufeinanderfolgende Einträge des Schattenpuffers der Seite 02. Jeder Teil addiert seine X/Y-Offsets von je einem Byte zur Basisposition und enthält eine eigene Kachel samt Attributen. Zurückgegeben wird der nächste OAM-Index.

Syntax

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

Parameter

oam_index

Sprite-Index von 0 bis 63, kein Byte-Offset. Intern wird er mit vier multipliziert und als 8-Bit-Position gespeichert. Werte außerhalb des Bereichs werden modulo 64 umgebrochen und können einen anderen Eintrag überschreiben. Eine Belegungsprüfung findet nicht statt.

base_x

Horizontale Position in Pixeln als vorzeichenloses Byte; keine Tile-Koordinate.

base_y

Y-Byte des OAM. Für eine Oberkante unterhalb der Zeile null wird ihre Position minus eins übergeben; 239..255 liegen außerhalb der 240 sichtbaren Zeilen.

metasprite

Zeiger auf lesbare Datensätze aus vier Byte: dx, dy, tile, attr. Ein 0xFF an der nächsten dx-Position beendet die Folge; weitere Bytes sind nicht nötig. Offsets werden modulo 256 addiert: 0xFE steht für −2 und dy=0xFF darf −1 darstellen. dx=0xFF ist hingegen die Endmarkierung und kann nicht für −1 verwendet werden. Der Zeiger muss im eingeblendeten Speicher gültig sein.

Rückgabewert

Nächster OAM-Index: (oam_index + geschriebene_Teile) modulo 64. Das ist eine Schreibposition, nicht die Anzahl gezeichneter Teile. Bei einer leeren Folge wird oam_index modulo 64 zurückgegeben.

Bedingungen und Grenzen

Diese Operationen ändern den CPU-RAM bei 0x0200–0x02FF, nicht sofort das Hardware-OAM. Übertragen Sie die fertige Seite mit __oam_dma. Belegungsflags, Koordinatenfelder und Zähler der Sprite-Bibliothek werden nicht mitgeführt. Interrupt-Routinen dürfen eine noch unvollständige Seite weder ändern noch übertragen.

Anzahl der Teile, Bildausschnitt und Kapazität werden nicht geprüft. Fügen Sie eine Endmarkierung ein und verwenden Sie höchstens 64−oam_index Teile. Koordinaten laufen modulo 256 über; nach Eintrag 63 wird bei Eintrag 0 weitergeschrieben. Eine leere Folge ändert nichts. Das Format unterscheidet sich vom MetaSpritePart-Array mit expliziter Anzahl, das metasprite_draw in der Sprite-Bibliothek verwendet.

Anwendungsbeispiel

c
fc_oam_next=__metasprite_draw(5,136,95,fc_oam_pair); // Returns 7 after two entries.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Beispiel zeigt jede Operation in einer beschrifteten Zeile. SET und MOVE zeigen 8×8-Pfeile nach rechts bei (144,16) und (144,32), TILE ein grünes Quadrat bei (144,48) und ATTR den in beiden Achsen gespiegelten Pfeil bei (144,64). HIDE bei (144,80) und CLEAR bei (144,112) müssen leer bleiben. META zeigt zwei Pfeile bei (136,96) und (144,96), den zweiten horizontal gespiegelt. NEXT 07 bezeichnet die nächste Position nach den Einträgen 5 und 6. Die Ränder sind rot, der Schaft grün und die Spitzendetails blau. Geprüft werden 8.704 Pixel je Bild und die übertragenen OAM-Bytes; echte Hardware wurde nicht getestet.

TRANSFER zeigt einen Pfeil bei (144,128). Dieses Programm überträgt Seite 02 direkt mit __oam_dma, einschließlich aller durch die einzelnen Sprite-Operationen vorbereiteten Felder.

__metasprite_draw: PAGE02
PAGE02

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:203

Deklarationshinweise im Original
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

Liest das digitale Mikrofonsignal des zweiten Famicom-Controllers aus Bit 2 von $4016. Die Routine liest das Register einmal, maskiert mit 0x04 und normalisiert ohne Invertierung auf 0 oder 1. Sie übernimmt keine Controllerzustände, zeichnet kein Audio auf, misst keine Lautstärke und erkennt keine Sprache.

Syntax

c
u8 __mic_read2p(void);

Rückgabewert

Genau 1, wenn $4016 & 0x04 ungleich null ist, andernfalls 0. Dies ist der gelesene Digitalpegel, keine Tastenmaske, Amplitude, Frequenz oder Anzahl gesprochener Wörter.

Bedingungen und Grenzen

Das Mikrofon gehört zu Controller 2, die Routine greift jedoch auf $4016 statt $4017 zu. Ein Registerzugriff kann bei niedrigem Strobe auch den seriellen Controllerstrom weiterschalten. Fügen Sie daher keine Mikrofonzugriffe in die Achtbit-Leseschleife eines anderen Controllers ein. Ein gespeicherter Eingabeverlauf wird nicht aktualisiert.

Das veröffentlichte KUROSAKI-Controllermodell liefert nur D0 und das feste Bit 6. D1 und das Mikrofonbit sind in diesem Emulator daher stets 0, auch bei gedrückten normalen Tasten. Der beigefügte Lauf prüft Kompilierung, normale Tasteneingaben und diese Nullsignal-Beobachtung. Peripheriesignale ungleich null, elektrische Zeitabläufe und reale Gerätekompatibilität bleiben ungeprüft. Eine Null beweist hier weder, dass das Gerät funktioniert, noch dass die Funktionen es auf echter Hardware nicht lesen können.

Anwendungsbeispiel

c
mic_direct=__mic_read2p(); // A separate sample of the same digital signal.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Dieses Programm vergleicht normale D0-Eingaben mit D1 und dem digitalen Mikrofonpegel. Halten Sie A auf Controller 1 und B auf Controller 2 bis zur Erfassung gedrückt. NORMAL P1 und NORMAL P2 zeigen 001 und 002. Im beigefügten KUROSAKI-Lauf zeigen D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL und MIC DIRECT jeweils 000, weil diese Leitungen nicht emuliert werden. Die erfassten Werte bleiben anschließend stehen. Mit geeigneten Peripheriegeräten und implementierten Signalleitungen hängen die Werte von den tatsächlichen Eingaben ab; deuten Sie sie anhand der obigen Bitbelegung, statt diese Nullen zu erwarten.

Das veröffentlichte KUROSAKI-Controllermodell liefert nur D0 und das feste Bit 6. D1 und das Mikrofonbit sind in diesem Emulator daher stets 0, auch bei gedrückten normalen Tasten. Der beigefügte Lauf prüft Kompilierung, normale Tasteneingaben und diese Nullsignal-Beobachtung. Peripheriesignale ungleich null, elektrische Zeitabläufe und reale Gerätekompatibilität bleiben ungeprüft. Eine Null beweist hier weder, dass das Gerät funktioniert, noch dass die Funktionen es auf echter Hardware nicht lesen können.

__mic_read2p: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:233

Deklarationshinweise im Original
Alias the same normalized microphone input bit as __joypad2p_voice.
__midi_clock

Sendet ein Timing-Clock-Byte F8. Wiederholte Ticks plant der Aufrufer.

Syntax

c
void __midi_clock(void);

Rückgabewert

Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.

Bedingungen und Grenzen

Benötigt eine externe Schaltung von $4016 D0 zu TX und $4017 D4 zu RX. Ein normaler Controller bietet kein MIDI; diese Aufrufe spielen nicht die interne APU. Stoppen Sie DMC-Wiedergabe/DMA und sperren Sie NMI und IRQ während Transfers; vermeiden Sie gleichzeitige Port-/Arbeitsspeicherzugriffe. 57 NTSC-Zyklen pro Bit ergeben etwa 31.400 Bit/s, rund +0,5 % gegenüber 31.250. PAL-Timing und Kommunikation in beide Richtungen mit echtem Adapter sind ungeprüft.

Anwendungsbeispiel

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();
}

Erwartetes Ergebnis

Der Bildschirm zeigt EXTERNAL PORT DATA, interne Stimmen bleiben still. Gesendet werden hexadezimal 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Die Prüfung verlangt NTSC-Sendezellen und Datenleseabstände von je 57 Zyklen. Empfangene 00 stammen vom unverbundenen Emulatoreingang und belegen keine externe Gerätekommunikation.

__midi_clock: NTSC
NTSC

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:304

Deklarationshinweise im Original
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

Sendet ein Echtzeit-Continue-Byte FB, ohne den internen Musiktreiber zu bedienen.

Syntax

c
void __midi_continue(void);

Rückgabewert

Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.

Bedingungen und Grenzen

Benötigt eine externe Schaltung von $4016 D0 zu TX und $4017 D4 zu RX. Ein normaler Controller bietet kein MIDI; diese Aufrufe spielen nicht die interne APU. Stoppen Sie DMC-Wiedergabe/DMA und sperren Sie NMI und IRQ während Transfers; vermeiden Sie gleichzeitige Port-/Arbeitsspeicherzugriffe. 57 NTSC-Zyklen pro Bit ergeben etwa 31.400 Bit/s, rund +0,5 % gegenüber 31.250. PAL-Timing und Kommunikation in beide Richtungen mit echtem Adapter sind ungeprüft.

Anwendungsbeispiel

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();
}

Erwartetes Ergebnis

Der Bildschirm zeigt EXTERNAL PORT DATA, interne Stimmen bleiben still. Gesendet werden hexadezimal 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Die Prüfung verlangt NTSC-Sendezellen und Datenleseabstände von je 57 Zyklen. Empfangene 00 stammen vom unverbundenen Emulatoreingang und belegen keine externe Gerätekommunikation.

__midi_continue: NTSC
NTSC

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:308

Deklarationshinweise im Original
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

Sendet Control Change als 0xB0 | (ch & 0x0F), Controllernummer und Wert.

Syntax

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

Parameter

ch

MIDI-Kanal 0..15; das untere Halbbyte geht ins Statusbyte ein. Null entspricht dem angezeigten MIDI-Kanal 1.

cc

Controllernummer 0..127; 7 ist Kanallautstärke.

value

Controllerwert 0..127; obere Bits werden nicht automatisch gelöscht.

Rückgabewert

Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.

Bedingungen und Grenzen

Benötigt eine externe Schaltung von $4016 D0 zu TX und $4017 D4 zu RX. Ein normaler Controller bietet kein MIDI; diese Aufrufe spielen nicht die interne APU. Stoppen Sie DMC-Wiedergabe/DMA und sperren Sie NMI und IRQ während Transfers; vermeiden Sie gleichzeitige Port-/Arbeitsspeicherzugriffe. 57 NTSC-Zyklen pro Bit ergeben etwa 31.400 Bit/s, rund +0,5 % gegenüber 31.250. PAL-Timing und Kommunikation in beide Richtungen mit echtem Adapter sind ungeprüft.

Anwendungsbeispiel

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();
}

Erwartetes Ergebnis

Der Bildschirm zeigt EXTERNAL PORT DATA, interne Stimmen bleiben still. Gesendet werden hexadezimal 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Die Prüfung verlangt NTSC-Sendezellen und Datenleseabstände von je 57 Zyklen. Empfangene 00 stammen vom unverbundenen Emulatoreingang und belegen keine externe Gerätekommunikation.

__midi_control_change: NTSC
NTSC

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:300

Deklarationshinweise im Original
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

Wartet auf niedrigen Empfangspegel und tastet dann acht Bits in festgelegten Zeitabständen ab.

Syntax

c
u8 __midi_in_byte(void);

Rückgabewert

Liefert das empfangene u8. Keine Zeitbegrenzung, Rahmenprüfung oder Empfangspufferung. Dauerhaft hoher Eingang blockiert unbegrenzt; dauerhaft niedriger ergibt null.

Bedingungen und Grenzen

Benötigt eine externe Schaltung von $4016 D0 zu TX und $4017 D4 zu RX. Ein normaler Controller bietet kein MIDI; diese Aufrufe spielen nicht die interne APU. Stoppen Sie DMC-Wiedergabe/DMA und sperren Sie NMI und IRQ während Transfers; vermeiden Sie gleichzeitige Port-/Arbeitsspeicherzugriffe. 57 NTSC-Zyklen pro Bit ergeben etwa 31.400 Bit/s, rund +0,5 % gegenüber 31.250. PAL-Timing und Kommunikation in beide Richtungen mit echtem Adapter sind ungeprüft.

Anwendungsbeispiel

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();
}

Erwartetes Ergebnis

Der Bildschirm zeigt EXTERNAL PORT DATA, interne Stimmen bleiben still. Gesendet werden hexadezimal 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Die Prüfung verlangt NTSC-Sendezellen und Datenleseabstände von je 57 Zyklen. Empfangene 00 stammen vom unverbundenen Emulatoreingang und belegen keine externe Gerätekommunikation.

__midi_in_byte: NTSC
NTSC

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:294

Deklarationshinweise im Original
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

Sendet Note-off als drei Bytes: 0x80 | (ch & 0x0F), note, velocity.

Syntax

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

Parameter

ch

MIDI-Kanal 0..15; das untere Halbbyte geht ins Statusbyte ein. Null entspricht dem angezeigten MIDI-Kanal 1.

note

MIDI-Note 0..127; 69 ist A4. Es erfolgt keine Siebenbitmaskierung; der Aufrufer hält den Bereich ein.

velocity

Anschlagstärke 0..127; keine automatische Siebenbitmaskierung.

Rückgabewert

Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.

Bedingungen und Grenzen

Benötigt eine externe Schaltung von $4016 D0 zu TX und $4017 D4 zu RX. Ein normaler Controller bietet kein MIDI; diese Aufrufe spielen nicht die interne APU. Stoppen Sie DMC-Wiedergabe/DMA und sperren Sie NMI und IRQ während Transfers; vermeiden Sie gleichzeitige Port-/Arbeitsspeicherzugriffe. 57 NTSC-Zyklen pro Bit ergeben etwa 31.400 Bit/s, rund +0,5 % gegenüber 31.250. PAL-Timing und Kommunikation in beide Richtungen mit echtem Adapter sind ungeprüft.

Anwendungsbeispiel

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();
}

Erwartetes Ergebnis

Der Bildschirm zeigt EXTERNAL PORT DATA, interne Stimmen bleiben still. Gesendet werden hexadezimal 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Die Prüfung verlangt NTSC-Sendezellen und Datenleseabstände von je 57 Zyklen. Empfangene 00 stammen vom unverbundenen Emulatoreingang und belegen keine externe Gerätekommunikation.

__midi_note_off: NTSC
NTSC

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:298

Deklarationshinweise im Original
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

Sendet Note-on als drei Bytes: 0x90 | (ch & 0x0F), note, velocity.

Syntax

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

Parameter

ch

MIDI-Kanal 0..15; das untere Halbbyte geht ins Statusbyte ein. Null entspricht dem angezeigten MIDI-Kanal 1.

note

MIDI-Note 0..127; 69 ist A4. Es erfolgt keine Siebenbitmaskierung; der Aufrufer hält den Bereich ein.

velocity

Anschlagstärke 0..127; keine automatische Siebenbitmaskierung.

Rückgabewert

Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.

Bedingungen und Grenzen

Benötigt eine externe Schaltung von $4016 D0 zu TX und $4017 D4 zu RX. Ein normaler Controller bietet kein MIDI; diese Aufrufe spielen nicht die interne APU. Stoppen Sie DMC-Wiedergabe/DMA und sperren Sie NMI und IRQ während Transfers; vermeiden Sie gleichzeitige Port-/Arbeitsspeicherzugriffe. 57 NTSC-Zyklen pro Bit ergeben etwa 31.400 Bit/s, rund +0,5 % gegenüber 31.250. PAL-Timing und Kommunikation in beide Richtungen mit echtem Adapter sind ungeprüft.

Anwendungsbeispiel

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();
}

Erwartetes Ergebnis

Der Bildschirm zeigt EXTERNAL PORT DATA, interne Stimmen bleiben still. Gesendet werden hexadezimal 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Die Prüfung verlangt NTSC-Sendezellen und Datenleseabstände von je 57 Zyklen. Empfangene 00 stammen vom unverbundenen Emulatoreingang und belegen keine externe Gerätekommunikation.

__midi_note_on: NTSC
NTSC

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:296

Deklarationshinweise im Original
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

Sendet synchron ein 8N1-Byte: niedriger Startpegel, acht Datenbits beginnend mit dem niederwertigsten, hoher Stopppegel.

Syntax

c
void __midi_out_byte(u8 byte);

Parameter

byte

Rohes Byte 0..255, einschließlich Status- oder Datenbytes.

Rückgabewert

Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.

Bedingungen und Grenzen

Benötigt eine externe Schaltung von $4016 D0 zu TX und $4017 D4 zu RX. Ein normaler Controller bietet kein MIDI; diese Aufrufe spielen nicht die interne APU. Stoppen Sie DMC-Wiedergabe/DMA und sperren Sie NMI und IRQ während Transfers; vermeiden Sie gleichzeitige Port-/Arbeitsspeicherzugriffe. 57 NTSC-Zyklen pro Bit ergeben etwa 31.400 Bit/s, rund +0,5 % gegenüber 31.250. PAL-Timing und Kommunikation in beide Richtungen mit echtem Adapter sind ungeprüft.

Anwendungsbeispiel

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();
}

Erwartetes Ergebnis

Der Bildschirm zeigt EXTERNAL PORT DATA, interne Stimmen bleiben still. Gesendet werden hexadezimal 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Die Prüfung verlangt NTSC-Sendezellen und Datenleseabstände von je 57 Zyklen. Empfangene 00 stammen vom unverbundenen Emulatoreingang und belegen keine externe Gerätekommunikation.

__midi_out_byte: NTSC
NTSC

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:292

Deklarationshinweise im Original
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

Sendet Program Change als 0xC0 | (ch & 0x0F), gefolgt von der Programmnummer.

Syntax

c
void __midi_program_change(u8 ch, u8 program);

Parameter

ch

MIDI-Kanal 0..15; das untere Halbbyte geht ins Statusbyte ein. Null entspricht dem angezeigten MIDI-Kanal 1.

program

Programm 0..127; die Instrumentzuordnung hängt vom Empfänger ab.

Rückgabewert

Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.

Bedingungen und Grenzen

Benötigt eine externe Schaltung von $4016 D0 zu TX und $4017 D4 zu RX. Ein normaler Controller bietet kein MIDI; diese Aufrufe spielen nicht die interne APU. Stoppen Sie DMC-Wiedergabe/DMA und sperren Sie NMI und IRQ während Transfers; vermeiden Sie gleichzeitige Port-/Arbeitsspeicherzugriffe. 57 NTSC-Zyklen pro Bit ergeben etwa 31.400 Bit/s, rund +0,5 % gegenüber 31.250. PAL-Timing und Kommunikation in beide Richtungen mit echtem Adapter sind ungeprüft.

Anwendungsbeispiel

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();
}

Erwartetes Ergebnis

Der Bildschirm zeigt EXTERNAL PORT DATA, interne Stimmen bleiben still. Gesendet werden hexadezimal 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Die Prüfung verlangt NTSC-Sendezellen und Datenleseabstände von je 57 Zyklen. Empfangene 00 stammen vom unverbundenen Emulatoreingang und belegen keine externe Gerätekommunikation.

__midi_program_change: NTSC
NTSC

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:302

Deklarationshinweise im Original
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

Sendet ein Echtzeit-Startbyte FA, ohne Liedzeiger des Spiels zu ändern.

Syntax

c
void __midi_start(void);

Rückgabewert

Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.

Bedingungen und Grenzen

Benötigt eine externe Schaltung von $4016 D0 zu TX und $4017 D4 zu RX. Ein normaler Controller bietet kein MIDI; diese Aufrufe spielen nicht die interne APU. Stoppen Sie DMC-Wiedergabe/DMA und sperren Sie NMI und IRQ während Transfers; vermeiden Sie gleichzeitige Port-/Arbeitsspeicherzugriffe. 57 NTSC-Zyklen pro Bit ergeben etwa 31.400 Bit/s, rund +0,5 % gegenüber 31.250. PAL-Timing und Kommunikation in beide Richtungen mit echtem Adapter sind ungeprüft.

Anwendungsbeispiel

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();
}

Erwartetes Ergebnis

Der Bildschirm zeigt EXTERNAL PORT DATA, interne Stimmen bleiben still. Gesendet werden hexadezimal 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Die Prüfung verlangt NTSC-Sendezellen und Datenleseabstände von je 57 Zyklen. Empfangene 00 stammen vom unverbundenen Emulatoreingang und belegen keine externe Gerätekommunikation.

__midi_start: NTSC
NTSC

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:306

Deklarationshinweise im Original
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

Sendet ein Echtzeit-Stopbyte FC. Dies schaltet weder APU noch zuvor gesendete Noten direkt stumm.

Syntax

c
void __midi_stop(void);

Rückgabewert

Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.

Bedingungen und Grenzen

Benötigt eine externe Schaltung von $4016 D0 zu TX und $4017 D4 zu RX. Ein normaler Controller bietet kein MIDI; diese Aufrufe spielen nicht die interne APU. Stoppen Sie DMC-Wiedergabe/DMA und sperren Sie NMI und IRQ während Transfers; vermeiden Sie gleichzeitige Port-/Arbeitsspeicherzugriffe. 57 NTSC-Zyklen pro Bit ergeben etwa 31.400 Bit/s, rund +0,5 % gegenüber 31.250. PAL-Timing und Kommunikation in beide Richtungen mit echtem Adapter sind ungeprüft.

Anwendungsbeispiel

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();
}

Erwartetes Ergebnis

Der Bildschirm zeigt EXTERNAL PORT DATA, interne Stimmen bleiben still. Gesendet werden hexadezimal 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Die Prüfung verlangt NTSC-Sendezellen und Datenleseabstände von je 57 Zyklen. Empfangene 00 stammen vom unverbundenen Emulatoreingang und belegen keine externe Gerätekommunikation.

__midi_stop: NTSC
NTSC

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:310

Deklarationshinweise im Original
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

Ordnet die vier logischen Nametable-Bereiche den zwei Bildschirmspeicherseiten der Konsole zu, für Scrolllayouts oder die Auswahl einer Einzelseite.

Syntax

c
void __mirroring_set(u8 mode);

Parameter

mode

Modusnummern hängen von der Platine ab. MMC1/SUROM: 0=untere Einzelseite, 1=obere Einzelseite, 2=vertikal, 3=horizontal. MMC3: 0=vertikal, 1=horizontal. MMC5: 0=horizontal, 1=vertikal, 2=untere Einzelseite, 3=obere Einzelseite. AxROM: 0=untere Einzelseite, 1=obere Einzelseite. FME7: 0=vertikal, 1=horizontal, 2=untere Einzelseite, 3=obere Einzelseite. MMC3/AxROM nutzen das niedrigste Bit, die anderen die beiden niedrigsten Bits.

Rückgabewert

Kein Rückgabewert.

Bedingungen und Grenzen

Die Intrinsic-Deklarationen stehen in intrinsics.h. Binden Sie für die Makros nes_mapper_irq_* den Header nes_game.h ein. Wählen Sie die Platine beim Build mit --mapper.

Vertikale Spiegelung ordnet die vier Bereiche als A B A B zu, horizontale als A A B B. Die untere Einzelseite ergibt A A A A, die obere B B B B. Das ändert die Speicherzuordnung, dreht oder kopiert aber keine Hintergrundgrafik. Nicht unterstützte Platinen einschließlich NROM mit fester Spiegelung werden beim Build abgewiesen.

MMC1 schreibt den Steuerwert $0C|mode und wählt damit ein festes oberes 16-KiB-PRG-Fenster und den 8-KiB-CHR-Modus. SUROM aktualisiert auch seine Steuerwertkopie. AxROM ändert das Bildschirmbit und behält den Laufzeit-PRG-Bankwert bei. Halten Sie direkte Mapperregister-Schreibzugriffe mit dem Laufzeit-Bankzustand konsistent.

Bei MMC5 schreibt die API $50/$44/$00/$55 nach $5105. Diese Anordnungen nutzen nur die zwei CIRAM-Seiten der Konsole. ExRAM und Füllmodus gehören nicht zu dieser API. KUROSAKI unterstützt alle sechzehn reinen CIRAM-Anordnungen, aber keine MMC5-ExRAM- oder Fülldarstellung.

Ersetzen Sie im Beispiel --mapper=mmc3 durch mmc1, mmc5, axrom oder fme7, um die jeweilige Platine zu prüfen. Das Beispiel initialisiert MMC5/FME7-Schrift-CHR-Banken über deren Platinenregister.

Anwendungsbeispiel

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++; }
    }

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Die Lektion schreibt 17 in die untere physische RAM-Seite und 34 in die obere und liest dann die vier logischen Bereiche. A bezeichnet die untere, B die obere Seite. Bereiche mit demselben Buchstaben teilen RAM; ein Schreibzugriff über einen Bereich ist deshalb auch über die anderen sichtbar. Spalten sind $2000, $2400, $2800 und $2C00, Zeilen die Modi 0 bis 3. Text erscheint erst nach dem Zurücklesen.

__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

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:322

Deklarationshinweise im Original
Emit the selected mapper mirroring operation; available modes and writable control depend on the board.
__mul16x8

Berechnet eine vorzeichenlose Multiplikation.

Syntax

c
u16 __mul16x8(u16 a, u8 b);

Parameter

a

Vorzeichenloses Wort, 0..65535.

b

Vorzeichenloser Koeffizient, 0..255.

Rückgabewert

Gibt die unteren 16 Bits von a*b zurück.

Bedingungen und Grenzen

Auf dem FC verwenden die q8_8/q1_7-Funktionen rohe Ganzzahlprodukte ohne Festkomma-Rechtsshift. Q1.7-Funktionen sind Aliase der entsprechenden vorzeichenbehafteten Operationen. Der aufrufende Code verwaltet die Einheiten; vorzeichenbehaftete Ergebnisse werden als u16-Bitmuster zurückgegeben.

Anwendungsbeispiel

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

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.

Der Ausschnitt speichert folgende 16-Bit-Werte: result[2]=65151, result[3]=65151. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

__mul16x8: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:51

Deklarationshinweise im Original
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

Multipliziert zwei vorzeichenlose Bytes und gibt das obere Byte zurück.

Syntax

c
u8 __mul8x8_hi(u8 a, u8 b);

Parameter

a

Vorzeichenloses Byte, 0..255.

b

Vorzeichenloses Byte, 0..255.

Rückgabewert

floor(a*b/256), im Bereich 0..254.

Anwendungsbeispiel

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);

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.

Der Ausschnitt speichert folgende 16-Bit-Werte: result[0]=254, result[1]=254. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

__mul8x8_hi: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:49

Deklarationshinweise im Original
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

Schreibt ein Kachelbyte direkt in Nametable 0 an 0x2000 + y*32 + x. Setzt den Latch vor der Adresse zurück. Schreibt weder Kachelgrafiken noch Palettenattribute und begrenzt die Koordinaten nicht.

Syntax

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

Parameter

x / y

Kachelkoordinaten, keine Pixel. Verwenden Sie x=0..31 und y=0..29 für die sichtbare Karte von 32×30 Kacheln; jede Kachel umfasst 8×8 Pixel. Die Byteargumente werden nicht begrenzt und können außerhalb des vorgesehenen Kachelbereichs schreiben.

tile

Kachelnummer 0..255 in der durch PPUCTRL gewählten Hintergrund-Mustertabelle. Wählt vorhandene CHR-Grafiken aus, keine Pixelfarbe und keinen Quellzeiger.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Direkter PPU-Zugriff erfordert ausgeschaltete Bildausgabe oder einen sicheren VBlank-Zeitraum, in den die gesamte Operation passt. Diese Funktionen warten nicht, legen keine Aufträge an, sperren NMI nicht und stellen den Bildlauf nicht wieder her. Verhindern Sie, dass Interrupts während des Aufrufs den gemeinsamen Latch oder die Adresse verändern. PPUCTRL-Bit 2 wählt eine Zieladressschrittweite von 1 oder 32 pro Byte. Spiegelung und Adressumlauf übernimmt die PPU; CHR-ROM ist nicht beschreibbar. Stellen Sie nach dem Zugriff vor der Bildausgabe den gewünschten Bildlauf ein.

NESdev: PPU-Register

NESdev: PPU-Scrolling

Anwendungsbeispiel

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

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.

Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).

Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

__nametable_put: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:124

Deklarationshinweise im Original
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

Wählt die logische Nametable nt & 3 und schreibt eine Kachel an 0x2000 + (nt & 3)*0x400 + y*32 + x. Setzt den Latch vor dem Adresspaar zurück. Durch Cartridge-Spiegelung können verschiedene logische Tabellennummern denselben physischen Speicher adressieren.

Syntax

c
void __nametable_put_nt(u8 nt, u8 x, u8 y, u8 tile);

Parameter

nt

Auswahl der logischen Nametable. Nur die unteren zwei Bits zählen (nt & 3): 4 wählt Tabelle 0, 7 wählt Tabelle 3. Es wird kein unabhängiger Speicher reserviert und keine Mapper-Spiegelung geändert.

x / y

Kachelkoordinaten, keine Pixel. Verwenden Sie x=0..31 und y=0..29 für die sichtbare Karte von 32×30 Kacheln; jede Kachel umfasst 8×8 Pixel. Die Byteargumente werden nicht begrenzt und können außerhalb des vorgesehenen Kachelbereichs schreiben.

tile

Kachelnummer 0..255 in der durch PPUCTRL gewählten Hintergrund-Mustertabelle. Wählt vorhandene CHR-Grafiken aus, keine Pixelfarbe und keinen Quellzeiger.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Direkter PPU-Zugriff erfordert ausgeschaltete Bildausgabe oder einen sicheren VBlank-Zeitraum, in den die gesamte Operation passt. Diese Funktionen warten nicht, legen keine Aufträge an, sperren NMI nicht und stellen den Bildlauf nicht wieder her. Verhindern Sie, dass Interrupts während des Aufrufs den gemeinsamen Latch oder die Adresse verändern. PPUCTRL-Bit 2 wählt eine Zieladressschrittweite von 1 oder 32 pro Byte. Spiegelung und Adressumlauf übernimmt die PPU; CHR-ROM ist nicht beschreibbar. Stellen Sie nach dem Zugriff vor der Bildausgabe den gewünschten Bildlauf ein.

NESdev: PPU-Register

NESdev: PPU-Scrolling

Anwendungsbeispiel

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

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.

Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).

Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

__nametable_put_nt: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:126

Deklarationshinweise im Original
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

Füllt ab (x,y) ein Rechteck aus w mal h Kachelbytes in Nametable 0. Setzt Latch und Adresse für jede Zeile zurück, schreibt w Kopien von tile und erhöht den Beginn der nächsten Zeile um 32 Bytes. Palettenattribute bleiben unverändert.

Syntax

c
void __nametable_rect(u8 x, u8 y, u8 w, u8 h, u8 tile);

Parameter

x / y

Kachelkoordinaten, keine Pixel. Verwenden Sie x=0..31 und y=0..29 für die sichtbare Karte von 32×30 Kacheln; jede Kachel umfasst 8×8 Pixel. Die Byteargumente werden nicht begrenzt und können außerhalb des vorgesehenen Kachelbereichs schreiben.

w / h

Breite und Höhe in Kacheln, jeweils ein vorzeichenloses Byte. Halten Sie x+w<=32 und y+h<=30 ein. Höhe null führt keine PPU-Zugriffe aus. Breite null schreibt keine Kachelbytes, setzt aber den Latch zurück und setzt alle h Zeilenadressen. Verwenden Sie für horizontale Zeilen PPU-Schrittweite 1.

tile

Kachelnummer 0..255 in der durch PPUCTRL gewählten Hintergrund-Mustertabelle. Wählt vorhandene CHR-Grafiken aus, keine Pixelfarbe und keinen Quellzeiger.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Direkter PPU-Zugriff erfordert ausgeschaltete Bildausgabe oder einen sicheren VBlank-Zeitraum, in den die gesamte Operation passt. Diese Funktionen warten nicht, legen keine Aufträge an, sperren NMI nicht und stellen den Bildlauf nicht wieder her. Verhindern Sie, dass Interrupts während des Aufrufs den gemeinsamen Latch oder die Adresse verändern. PPUCTRL-Bit 2 wählt eine Zieladressschrittweite von 1 oder 32 pro Byte. Spiegelung und Adressumlauf übernimmt die PPU; CHR-ROM ist nicht beschreibbar. Stellen Sie nach dem Zugriff vor der Bildausgabe den gewünschten Bildlauf ein.

NESdev: PPU-Register

NESdev: PPU-Scrolling

Anwendungsbeispiel

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

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.

Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).

Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

__nametable_rect: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:129

Deklarationshinweise im Original
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

Füllt in der logischen Nametable nt & 3 jeweils w Kacheln über h Zeilen. Die Anfangsadresse lautet 0x2000 + (nt & 3)*0x400 + y*32 + x; jeder Zeilenbeginn rückt um 32 Bytes weiter. Schreibt nur Kachelbytes, ohne Attribute zu ändern, Grenzen abzuschneiden oder Spiegelung einzustellen.

Syntax

c
void __nametable_rect_nt(u8 nt, u8 x, u8 y, u8 w, u8 h, u8 tile);

Parameter

nt

Auswahl der logischen Nametable. Nur die unteren zwei Bits zählen (nt & 3): 4 wählt Tabelle 0, 7 wählt Tabelle 3. Es wird kein unabhängiger Speicher reserviert und keine Mapper-Spiegelung geändert.

x / y

Kachelkoordinaten, keine Pixel. Verwenden Sie x=0..31 und y=0..29 für die sichtbare Karte von 32×30 Kacheln; jede Kachel umfasst 8×8 Pixel. Die Byteargumente werden nicht begrenzt und können außerhalb des vorgesehenen Kachelbereichs schreiben.

w / h

Breite und Höhe in Kacheln, jeweils ein vorzeichenloses Byte. Halten Sie x+w<=32 und y+h<=30 ein. Höhe null führt keine PPU-Zugriffe aus. Breite null schreibt keine Kachelbytes, setzt aber den Latch zurück und setzt alle h Zeilenadressen. Verwenden Sie für horizontale Zeilen PPU-Schrittweite 1.

tile

Kachelnummer 0..255 in der durch PPUCTRL gewählten Hintergrund-Mustertabelle. Wählt vorhandene CHR-Grafiken aus, keine Pixelfarbe und keinen Quellzeiger.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Direkter PPU-Zugriff erfordert ausgeschaltete Bildausgabe oder einen sicheren VBlank-Zeitraum, in den die gesamte Operation passt. Diese Funktionen warten nicht, legen keine Aufträge an, sperren NMI nicht und stellen den Bildlauf nicht wieder her. Verhindern Sie, dass Interrupts während des Aufrufs den gemeinsamen Latch oder die Adresse verändern. PPUCTRL-Bit 2 wählt eine Zieladressschrittweite von 1 oder 32 pro Byte. Spiegelung und Adressumlauf übernimmt die PPU; CHR-ROM ist nicht beschreibbar. Stellen Sie nach dem Zugriff vor der Bildausgabe den gewünschten Bildlauf ein.

NESdev: PPU-Register

NESdev: PPU-Scrolling

Anwendungsbeispiel

c
__nametable_rect_nt(4,18,6,8,4,3); // nt&3=0; red triangle tiles at (144,48).

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.

Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).

Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

__nametable_rect_nt: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:131

Deklarationshinweise im Original
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

Deaktiviert die NMI-Erzeugung der PPU: Bit 7 der PPUCTRL-Kopie des Compilers wird gelöscht und der Wert nach $2000 geschrieben. Die übrigen Bits bleiben erhalten. IRQ-Maske und Bildausgabe werden nicht verändert.

Syntax

c
void __nmi_disable(void);

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Diese Funktionen verwenden die PPUCTRL-Kopie und lesen die Hardware nicht zurück. Direkte Schreibzugriffe auf $2000 aktualisieren die Kopie nicht. Bei gesetztem VBlank-Flag kann das Einschalten sofort NMI auslösen. Das Abschalten löscht keine bereits von der CPU erfasste Anforderung.

NESdev: NMI

NESdev: CPU-Interrupts

Anwendungsbeispiel

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.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm unterscheidet zwischen der IRQ-Maske der CPU und der NMI-Steuerung der PPU. Blau auf Schwarz müssen IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 und RESTORED I = 000 erscheinen. Die Werte zeigen nur das extrahierte I-Bit, nicht das ganze Statusbyte. FAILED CHECKS muss 000 sein.

Mindestens 360 Emulatorframes ausführen, damit die Demonstration mit 256 Warteaufrufen abgeschlossen wird. NMI DELTA 3 WAITS muss trotz IRQ-Sperre 003 ergeben; NMI DELTA 256 WAITS wird durch den Byteumlauf zu 000. NMI OFF DELTA muss ebenfalls 000 sein. Das Programm verwendet den Standardhandler ohne runtime.c. Weitere 21 Zustandsprüfungen erfassen unter anderem die Wiederherstellung der Statusbits sowie zeitlich begrenzt beobachtete Wartefälle bei deaktivierter NMI oder leerem eigenem Handler. Geprüft wurde KUROSAKI/NROM, keine echte Hardware.

__nmi_disable: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:174

Deklarationshinweise im Original
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

Aktiviert die von der PPU erzeugte NMI: Bit 7 der vom Compiler geführten PPUCTRL-Kopie wird gesetzt und der Wert nach $2000 geschrieben. Die übrigen Bits dieser Kopie und die IRQ-Maske der CPU bleiben erhalten. Hintergrund- und Spriteausgabe werden dadurch nicht eingeschaltet.

Syntax

c
void __nmi_enable(void);

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Diese Funktionen verwenden die PPUCTRL-Kopie und lesen die Hardware nicht zurück. Direkte Schreibzugriffe auf $2000 aktualisieren die Kopie nicht. Bei gesetztem VBlank-Flag kann das Einschalten sofort NMI auslösen. Das Abschalten löscht keine bereits von der CPU erfasste Anforderung.

NESdev: NMI

NESdev: CPU-Interrupts

Anwendungsbeispiel

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.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm unterscheidet zwischen der IRQ-Maske der CPU und der NMI-Steuerung der PPU. Blau auf Schwarz müssen IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 und RESTORED I = 000 erscheinen. Die Werte zeigen nur das extrahierte I-Bit, nicht das ganze Statusbyte. FAILED CHECKS muss 000 sein.

Mindestens 360 Emulatorframes ausführen, damit die Demonstration mit 256 Warteaufrufen abgeschlossen wird. NMI DELTA 3 WAITS muss trotz IRQ-Sperre 003 ergeben; NMI DELTA 256 WAITS wird durch den Byteumlauf zu 000. NMI OFF DELTA muss ebenfalls 000 sein. Das Programm verwendet den Standardhandler ohne runtime.c. Weitere 21 Zustandsprüfungen erfassen unter anderem die Wiederherstellung der Statusbits sowie zeitlich begrenzt beobachtete Wartefälle bei deaktivierter NMI oder leerem eigenem Handler. Geprüft wurde KUROSAKI/NROM, keine echte Hardware.

__nmi_enable: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:172

Deklarationshinweise im Original
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

Liest den aktuellen NMI-Zähler des Compilers, ohne zu warten oder ihn zu löschen. Trotz des Namens ready ist das Ergebnis ein Zählerbyte und kein boolesches Bereitschaftsflag. Ein Vergleich mit einem gespeicherten Wert zeigt den Fortschritt des Standard-NMI-Handlers.

Syntax

c
u8 __nmi_ready(void);

Rückgabewert

Ein vorzeichenloses Byte von 0 bis 255, das der Standard-NMI-Handler modulo 256 erhöht. Null ist gültig. Nach 256 Schritten stimmt der aktuelle Wert wieder mit dem gespeicherten überein; seltenes Abfragen erlaubt keine unbegrenzt lange Zeitmessung.

Bedingungen und Grenzen

Dieser Zähler gehört zum Standard-NMI-Handler des Compilers. Eine Definition von __nes_nmi ersetzt ihn. runtime.c verwendet eigene nes_nmi_counter und nes_wait_nmi. Ein leerer eigener Handler erhöht den Compilerzähler nicht; __nmi_wait kehrt dann nicht zurück. NMI aktivieren und außerhalb des NMI-Handlers warten.

NESdev: NMI

NESdev: CPU-Interrupts

Anwendungsbeispiel

c
before_count=__nmi_ready(); // A wrapping byte, not a Boolean ready flag.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm unterscheidet zwischen der IRQ-Maske der CPU und der NMI-Steuerung der PPU. Blau auf Schwarz müssen IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 und RESTORED I = 000 erscheinen. Die Werte zeigen nur das extrahierte I-Bit, nicht das ganze Statusbyte. FAILED CHECKS muss 000 sein.

Mindestens 360 Emulatorframes ausführen, damit die Demonstration mit 256 Warteaufrufen abgeschlossen wird. NMI DELTA 3 WAITS muss trotz IRQ-Sperre 003 ergeben; NMI DELTA 256 WAITS wird durch den Byteumlauf zu 000. NMI OFF DELTA muss ebenfalls 000 sein. Das Programm verwendet den Standardhandler ohne runtime.c. Weitere 21 Zustandsprüfungen erfassen unter anderem die Wiederherstellung der Statusbits sowie zeitlich begrenzt beobachtete Wartefälle bei deaktivierter NMI oder leerem eigenem Handler. Geprüft wurde KUROSAKI/NROM, keine echte Hardware.

__nmi_ready: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:170

Deklarationshinweise im Original
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

Liest beim Aufruf den NMI-Zähler des Compilers und wartet in einer Schleife auf eine Änderung. Beim Standardhandler bedeutet dies den Abschluss eines späteren Handleraufrufs einschließlich der freigegebenen VRAM-Aufträge. Der Aufruf aktiviert keine NMI, gibt keine Aufträge frei und hat kein Zeitlimit.

Syntax

c
void __nmi_wait(void);

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Dieser Zähler gehört zum Standard-NMI-Handler des Compilers. Eine Definition von __nes_nmi ersetzt ihn. runtime.c verwendet eigene nes_nmi_counter und nes_wait_nmi. Ein leerer eigener Handler erhöht den Compilerzähler nicht; __nmi_wait kehrt dann nicht zurück. NMI aktivieren und außerhalb des NMI-Handlers warten.

NESdev: NMI

NESdev: CPU-Interrupts

Anwendungsbeispiel

c
__nmi_wait();__nmi_wait();__nmi_wait();
delta=(u8)(__nmi_ready()-before_count); // Three completed default NMIs.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm unterscheidet zwischen der IRQ-Maske der CPU und der NMI-Steuerung der PPU. Blau auf Schwarz müssen IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 und RESTORED I = 000 erscheinen. Die Werte zeigen nur das extrahierte I-Bit, nicht das ganze Statusbyte. FAILED CHECKS muss 000 sein.

Mindestens 360 Emulatorframes ausführen, damit die Demonstration mit 256 Warteaufrufen abgeschlossen wird. NMI DELTA 3 WAITS muss trotz IRQ-Sperre 003 ergeben; NMI DELTA 256 WAITS wird durch den Byteumlauf zu 000. NMI OFF DELTA muss ebenfalls 000 sein. Das Programm verwendet den Standardhandler ohne runtime.c. Weitere 21 Zustandsprüfungen erfassen unter anderem die Wiederherstellung der Statusbits sowie zeitlich begrenzt beobachtete Wartefälle bei deaktivierter NMI oder leerem eigenem Handler. Geprüft wurde KUROSAKI/NROM, keine echte Hardware.

__nmi_wait: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:168

Deklarationshinweise im Original
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

Blendet alle 64 Sprites im Schattenpuffer der Seite 02 aus, indem jedes Y-Byte auf 0xF0 gesetzt wird. Kachelnummern, Attribute und X-Koordinaten bleiben erhalten.

Syntax

c
void __oam_clear(void);

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Diese Operationen ändern den CPU-RAM bei 0x0200–0x02FF, nicht sofort das Hardware-OAM. Übertragen Sie die fertige Seite mit __oam_dma. Belegungsflags, Koordinatenfelder und Zähler der Sprite-Bibliothek werden nicht mitgeführt. Interrupt-Routinen dürfen eine noch unvollständige Seite weder ändern noch übertragen.

Anwendungsbeispiel

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.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Beispiel zeigt jede Operation in einer beschrifteten Zeile. SET und MOVE zeigen 8×8-Pfeile nach rechts bei (144,16) und (144,32), TILE ein grünes Quadrat bei (144,48) und ATTR den in beiden Achsen gespiegelten Pfeil bei (144,64). HIDE bei (144,80) und CLEAR bei (144,112) müssen leer bleiben. META zeigt zwei Pfeile bei (136,96) und (144,96), den zweiten horizontal gespiegelt. NEXT 07 bezeichnet die nächste Position nach den Einträgen 5 und 6. Die Ränder sind rot, der Schaft grün und die Spitzendetails blau. Geprüft werden 8.704 Pixel je Bild und die übertragenen OAM-Bytes; echte Hardware wurde nicht getestet.

TRANSFER zeigt einen Pfeil bei (144,128). Dieses Programm überträgt Seite 02 direkt mit __oam_dma, einschließlich aller durch die einzelnen Sprite-Operationen vorbereiteten Felder.

__oam_clear: PAGE02
PAGE02

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:190

Deklarationshinweise im Original
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

Überträgt die vollständige Schattenseite 0x0200–0x02FF mit 256 Byte ab Eintrag 0 in das Hardware-OAM. Die CPU pausiert während der Übertragung und setzt ihre Arbeit danach fort.

Syntax

c
void __oam_dma(void);

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Rufen Sie die Funktion während des VBlank oder bei deaktivierter Bildausgabe auf; keine der DMA-Funktionen wartet auf VBlank. Beide setzen OAMADDR vor der Übertragung auf null. Die Hardware hält die CPU normalerweise 513 oder 514 Zyklen an; DMC-Aktivität kann dies verlängern. KUROSAKI nähert die Dauer mit 513 Zyklen an, daher belegt die Bildprüfung keine exakten Hardware-Zeiten. KQ2421 kann trotz ausdrücklichem Warten auf VBlank erscheinen, weil der Compiler anhand des Aufrufernamens prüft.

Anwendungsbeispiel

c
m_wait();__oam_dma(); // Transfer all 256 bytes from page 02 at VBlank.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Beispiel zeigt jede Operation in einer beschrifteten Zeile. SET und MOVE zeigen 8×8-Pfeile nach rechts bei (144,16) und (144,32), TILE ein grünes Quadrat bei (144,48) und ATTR den in beiden Achsen gespiegelten Pfeil bei (144,64). HIDE bei (144,80) und CLEAR bei (144,112) müssen leer bleiben. META zeigt zwei Pfeile bei (136,96) und (144,96), den zweiten horizontal gespiegelt. NEXT 07 bezeichnet die nächste Position nach den Einträgen 5 und 6. Die Ränder sind rot, der Schaft grün und die Spitzendetails blau. Geprüft werden 8.704 Pixel je Bild und die übertragenen OAM-Bytes; echte Hardware wurde nicht getestet.

TRANSFER zeigt einen Pfeil bei (144,128). Dieses Programm überträgt Seite 02 direkt mit __oam_dma, einschließlich aller durch die einzelnen Sprite-Operationen vorbereiteten Felder.

__oam_dma: PAGE02
PAGE02

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:186

Deklarationshinweise im Original
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

Überträgt 256 Byte aus der gewählten CPU-Speicherseite ab Eintrag 0 in das Hardware-OAM. So lässt sich eine separat vorbereitete OAM-Seite verwenden, ohne sie zuvor in Seite 02 zu kopieren. Die Quelldaten bleiben unverändert.

Syntax

c
void __oam_dma_page(u8 page);

Parameter

page

Oberes Byte der CPU-Quelladresse: Übergeben Sie für 0x0400–0x04FF den Wert 4, nicht 0x0400. Stellen Sie eine vollständige lesbare Seite mit 256 Byte bereit und halten Sie die nötige Bank eingeblendet. Vermeiden Sie Seiten mit E/A-Registern, deren Lesezugriffe Nebenwirkungen haben können. Die Quelle wird nicht geprüft.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Rufen Sie die Funktion während des VBlank oder bei deaktivierter Bildausgabe auf; keine der DMA-Funktionen wartet auf VBlank. Beide setzen OAMADDR vor der Übertragung auf null. Die Hardware hält die CPU normalerweise 513 oder 514 Zyklen an; DMC-Aktivität kann dies verlängern. KUROSAKI nähert die Dauer mit 513 Zyklen an, daher belegt die Bildprüfung keine exakten Hardware-Zeiten. KQ2421 kann trotz ausdrücklichem Warten auf VBlank erscheinen, weil der Compiler anhand des Aufrufernamens prüft.

Anwendungsbeispiel

c
m_wait();__oam_dma_page(4); // The argument is a page number, not 0x0400.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Beispiel zeigt jede Operation in einer beschrifteten Zeile. SET und MOVE zeigen 8×8-Pfeile nach rechts bei (144,16) und (144,32), TILE ein grünes Quadrat bei (144,48) und ATTR den in beiden Achsen gespiegelten Pfeil bei (144,64). HIDE bei (144,80) und CLEAR bei (144,112) müssen leer bleiben. META zeigt zwei Pfeile bei (136,96) und (144,96), den zweiten horizontal gespiegelt. NEXT 07 bezeichnet die nächste Position nach den Einträgen 5 und 6. Die Ränder sind rot, der Schaft grün und die Spitzendetails blau. Geprüft werden 8.704 Pixel je Bild und die übertragenen OAM-Bytes; echte Hardware wurde nicht getestet.

Diese Variante kopiert die vorbereitete Seite 02 in ein separates Array bei 0x0400 und setzt nur die Kachel von Eintrag 7 auf 129. __oam_dma_page(4) muss neben TRANSFER bei (144,128) ein grünes Quadrat anzeigen, während Seite 02 weiterhin die Pfeilkachel 128 enthält. Alle anderen Zeilen entsprechen dem Standardbeispiel. Die Prüfung vergleicht sämtliche 256 OAM-Bytes mit Seite 04 und bestätigt, dass Seite 02 unverändert blieb.

__oam_dma_page: PAGE04
PAGE04

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:188

Deklarationshinweise im Original
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

Behält Bits 0–3 des übergebenen NES-Zustands (pad & 0x0F): A, B, Select und Start. Filtert nur einen bereits gelesenen Wert und greift nicht auf den Controller zu.

Syntax

c
u8 __pad_buttons(u8 pad);

Parameter

pad

Ein bereits gelesener 8-Bit-NES-Pad-Wert, etwa das Ergebnis von __pad_read1_safe(). Übergib den Wert selbst, keinen Zeiger, keine Portnummer und keine logische input_*-Maske.

Rückgabewert

Eine Maske von 0 bis 15 in der beschriebenen Bitreihenfolge. Mehrere Bits können gesetzt sein; eine Taste mit result & bit prüfen, statt das gesamte Ergebnis mit 1 zu vergleichen.

Bedingungen und Grenzen

Rohes NES-Format: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Ein gesetztes Bit bedeutet gedrückt. Verwende diese Werte mit nes_pad_* und rohen __pad_*-Ergebnissen, nicht die gemeinsamen BTN_*-Masken aus input.h.

Anwendungsbeispiel

c
buttons=__pad_buttons(raw1); // Low nibble of 129: 1, the A button.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Dieses Programm vergleicht beide Controlleranschlüsse, einfache und auf Übereinstimmung geprüfte Lesevorgänge, Maskenfilter und Kurzmakros. Halten Sie A + Rechts auf Controller 1 und B + Links auf Controller 2 gedrückt. RAW P1, SAFE P1 und ALIAS P1 sind 129; die drei P2-Werte sind 66. BUTTONS P1 ist 1 und DIRS P1 ist 128: Der Richtungsfilter behält die oberen Bitpositionen bei. FAILED CHECKS muss 0 sein. Die erfassten Werte bleiben sichtbar.

__pad_buttons: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:213

Deklarationshinweise im Original
Return pad & 0F for A/B/Select/Start; this masks a supplied snapshot and performs no polling.
__pad_dirs

Behält Bits 4–7 des NES-Zustands (pad & 0xF0): oben, unten, links und rechts. Die Bitpositionen bleiben erhalten; rechts allein liefert 128, nicht 8. Fragt den Controller nicht ab.

Syntax

c
u8 __pad_dirs(u8 pad);

Parameter

pad

Ein bereits gelesener 8-Bit-NES-Pad-Wert, etwa das Ergebnis von __pad_read1_safe(). Übergib den Wert selbst, keinen Zeiger, keine Portnummer und keine logische input_*-Maske.

Rückgabewert

Der übergebene Wert UND 0xF0: 0, 16, 32, …, 240. Die Richtungsbits bleiben in Bits 4–7.

Bedingungen und Grenzen

Rohes NES-Format: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Ein gesetztes Bit bedeutet gedrückt. Verwende diese Werte mit nes_pad_* und rohen __pad_*-Ergebnissen, nicht die gemeinsamen BTN_*-Masken aus input.h.

Anwendungsbeispiel

c
directions=__pad_dirs(raw1); // High nibble retained in place: 128, not 8.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Dieses Programm vergleicht beide Controlleranschlüsse, einfache und auf Übereinstimmung geprüfte Lesevorgänge, Maskenfilter und Kurzmakros. Halten Sie A + Rechts auf Controller 1 und B + Links auf Controller 2 gedrückt. RAW P1, SAFE P1 und ALIAS P1 sind 129; die drei P2-Werte sind 66. BUTTONS P1 ist 1 und DIRS P1 ist 128: Der Richtungsfilter behält die oberen Bitpositionen bei. FAILED CHECKS muss 0 sein. Die erfassten Werte bleiben sichtbar.

__pad_dirs: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:215

Deklarationshinweise im Original
Return pad & F0 in the original bit positions; this does not normalize directions to bits 0..3.
__pad_read1

Übernimmt den Zustand beider Controller durch Schreiben von 1 und anschließend 0 nach $4016 und liest die acht seriellen D0-Bits von Controller 1 an $4016. Liefert einen Haltezustand im NES-Format, ohne Wiederholung oder Flankenermittlung.

Syntax

c
u8 __pad_read1(void);

Rückgabewert

Eine 8-Bit-Haltemaske in roher NES-Reihenfolge. Jedes gesetzte Bit steht für eine gedrückte Taste, 0 für keine. Kein auf 0/1 normalisierter Wahrheitswert.

Bedingungen und Grenzen

Rohes NES-Format: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Ein gesetztes Bit bedeutet gedrückt. Verwende diese Werte mit nes_pad_* und rohen __pad_*-Ergebnissen, nicht die gemeinsamen BTN_*-Masken aus input.h.

Dieser Aufruf liest einen neuen Hardwarezustand. Er aktualisiert weder den gespeicherten input_*- noch den nes_pad1_*-Zustand und erzeugt keine Druck-, Loslass- oder Wiederholungsereignisse. Speichere das Ergebnis und vergleiche es bei Bedarf mit deiner vorherigen Maske.

Anwendungsbeispiel

c
raw1=__pad_read1(); // Controller 1: A (1) | Right (128) = 129.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Dieses Programm vergleicht beide Controlleranschlüsse, einfache und auf Übereinstimmung geprüfte Lesevorgänge, Maskenfilter und Kurzmakros. Halten Sie A + Rechts auf Controller 1 und B + Links auf Controller 2 gedrückt. RAW P1, SAFE P1 und ALIAS P1 sind 129; die drei P2-Werte sind 66. BUTTONS P1 ist 1 und DIRS P1 ist 128: Der Richtungsfilter behält die oberen Bitpositionen bei. FAILED CHECKS muss 0 sein. Die erfassten Werte bleiben sichtbar.

__pad_read1: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:205

Deklarationshinweise im Original
Latch and sample eight D0 bits from port 1; bit order is A,B,Select,Start,Up,Down,Left,Right.
__pad_read1_d1

Liest acht serielle Bits vom Erweiterungseingang D1 an $4016. Schreibt nacheinander 1 und 0 nach $4016, um die Zustände zu übernehmen, liest Bit 1 achtmal und legt die Werte der Reihe nach in Ergebnisbits 0–7 ab. Dies ist ein einzelner Lesevorgang auf D1 von Anschluss 1; weder wird D0 des normalen Controllers gelesen noch auf übereinstimmende Wiederholungswerte geprüft.

Syntax

c
u8 __pad_read1_d1(void);

Rückgabewert

Eine 8-Bit-Haltemaske in roher NES-Reihenfolge. Jedes gesetzte Bit steht für eine gedrückte Taste, 0 für keine. Kein auf 0/1 normalisierter Wahrheitswert.

Bedingungen und Grenzen

Rohes NES-Format: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Ein gesetztes Bit bedeutet gedrückt. Verwende diese Werte mit nes_pad_* und rohen __pad_*-Ergebnissen, nicht die gemeinsamen BTN_*-Masken aus input.h.

Verwenden Sie ein Peripheriegerät mit einem kompatiblen seriellen Achtbit-Controllerstrom auf der gewählten D1-Leitung. Die Funktion erkennt das Gerät nicht und implementiert nicht jedes Erweiterungsprotokoll. Jeder Aufruf übernimmt beide Controllerströme neu; beenden Sie einen vollständigen Lesevorgang, bevor Sie den nächsten beginnen. Die Ergebnisse sind neue Momentaufnahmen und aktualisieren weder input_* noch nes_pad1_*.

Das veröffentlichte KUROSAKI-Controllermodell liefert nur D0 und das feste Bit 6. D1 und das Mikrofonbit sind in diesem Emulator daher stets 0, auch bei gedrückten normalen Tasten. Der beigefügte Lauf prüft Kompilierung, normale Tasteneingaben und diese Nullsignal-Beobachtung. Peripheriesignale ungleich null, elektrische Zeitabläufe und reale Gerätekompatibilität bleiben ungeprüft. Eine Null beweist hier weder, dass das Gerät funktioniert, noch dass die Funktionen es auf echter Hardware nicht lesen können.

Anwendungsbeispiel

c
d1_first=__pad_read1_d1();

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Dieses Programm vergleicht normale D0-Eingaben mit D1 und dem digitalen Mikrofonpegel. Halten Sie A auf Controller 1 und B auf Controller 2 bis zur Erfassung gedrückt. NORMAL P1 und NORMAL P2 zeigen 001 und 002. Im beigefügten KUROSAKI-Lauf zeigen D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL und MIC DIRECT jeweils 000, weil diese Leitungen nicht emuliert werden. Die erfassten Werte bleiben anschließend stehen. Mit geeigneten Peripheriegeräten und implementierten Signalleitungen hängen die Werte von den tatsächlichen Eingaben ab; deuten Sie sie anhand der obigen Bitbelegung, statt diese Nullen zu erwarten.

Das veröffentlichte KUROSAKI-Controllermodell liefert nur D0 und das feste Bit 6. D1 und das Mikrofonbit sind in diesem Emulator daher stets 0, auch bei gedrückten normalen Tasten. Der beigefügte Lauf prüft Kompilierung, normale Tasteneingaben und diese Nullsignal-Beobachtung. Peripheriesignale ungleich null, elektrische Zeitabläufe und reale Gerätekompatibilität bleiben ungeprüft. Eine Null beweist hier weder, dass das Gerät funktioniert, noch dass die Funktionen es auf echter Hardware nicht lesen können.

__pad_read1_d1: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:223

Deklarationshinweise im Original
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

Fragt Controller 1 durch wiederholte Paare vollständiger __pad_read1()-Aufrufe ab. Liefert die NES-Maske, sobald beide Werte eines Paares übereinstimmen. Das Ergebnis ist der Haltezustand, keine neue Druckflanke.

Syntax

c
u8 __pad_read1_safe(void);

Rückgabewert

Eine 8-Bit-Haltemaske in roher NES-Reihenfolge. Jedes gesetzte Bit steht für eine gedrückte Taste, 0 für keine. Kein auf 0/1 normalisierter Wahrheitswert.

Bedingungen und Grenzen

Rohes NES-Format: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Ein gesetztes Bit bedeutet gedrückt. Verwende diese Werte mit nes_pad_* und rohen __pad_*-Ergebnissen, nicht die gemeinsamen BTN_*-Masken aus input.h.

Dieser Aufruf liest einen neuen Hardwarezustand. Er aktualisiert weder den gespeicherten input_*- noch den nes_pad1_*-Zustand und erzeugt keine Druck-, Loslass- oder Wiederholungsereignisse. Speichere das Ergebnis und vergleiche es bei Bedarf mit deiner vorherigen Maske.

Die sichere Abfrage wiederholt Paare vollständiger Lesevorgänge, bis beide Masken übereinstimmen. Jeder Lesevorgang strobt $4016 und setzt die serielle Leseposition beider Controller zurück. Die Zahl der Versuche ist unbegrenzt. Diese Prüfung belegt keine Kompatibilität mit sämtlichen DMC-/Peripheriekombinationen.

Anwendungsbeispiel

c
safe1=__pad_read1_safe(); // Repeat paired controller-1 reads until they agree.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Dieses Programm vergleicht beide Controlleranschlüsse, einfache und auf Übereinstimmung geprüfte Lesevorgänge, Maskenfilter und Kurzmakros. Halten Sie A + Rechts auf Controller 1 und B + Links auf Controller 2 gedrückt. RAW P1, SAFE P1 und ALIAS P1 sind 129; die drei P2-Werte sind 66. BUTTONS P1 ist 1 und DIRS P1 ist 128: Der Richtungsfilter behält die oberen Bitpositionen bei. FAILED CHECKS muss 0 sein. Die erfassten Werte bleiben sichtbar.

__pad_read1_safe: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:209

Deklarationshinweise im Original
Repeat pairs of port-1 polls until both bytes match; the retry loop has no fixed limit.
__pad_read2

Übernimmt den Zustand beider Controller durch Schreiben von 1 und anschließend 0 nach $4016 und liest die acht seriellen D0-Bits von Controller 2 an $4017. Liefert einen Haltezustand im NES-Format, ohne Wiederholung oder Flankenermittlung.

Syntax

c
u8 __pad_read2(void);

Rückgabewert

Eine 8-Bit-Haltemaske in roher NES-Reihenfolge. Jedes gesetzte Bit steht für eine gedrückte Taste, 0 für keine. Kein auf 0/1 normalisierter Wahrheitswert.

Bedingungen und Grenzen

Rohes NES-Format: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Ein gesetztes Bit bedeutet gedrückt. Verwende diese Werte mit nes_pad_* und rohen __pad_*-Ergebnissen, nicht die gemeinsamen BTN_*-Masken aus input.h.

Dieser Aufruf liest einen neuen Hardwarezustand. Er aktualisiert weder den gespeicherten input_*- noch den nes_pad1_*-Zustand und erzeugt keine Druck-, Loslass- oder Wiederholungsereignisse. Speichere das Ergebnis und vergleiche es bei Bedarf mit deiner vorherigen Maske.

Anwendungsbeispiel

c
raw2=__pad_read2(); // Controller 2: B (2) | Left (64) = 66.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Dieses Programm vergleicht beide Controlleranschlüsse, einfache und auf Übereinstimmung geprüfte Lesevorgänge, Maskenfilter und Kurzmakros. Halten Sie A + Rechts auf Controller 1 und B + Links auf Controller 2 gedrückt. RAW P1, SAFE P1 und ALIAS P1 sind 129; die drei P2-Werte sind 66. BUTTONS P1 ist 1 und DIRS P1 ist 128: Der Richtungsfilter behält die oberen Bitpositionen bei. FAILED CHECKS muss 0 sein. Die erfassten Werte bleiben sichtbar.

__pad_read2: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:207

Deklarationshinweise im Original
Latch and sample eight D0 bits from port 2 in the same NES button order.
__pad_read2_d1

Liest acht serielle Bits vom Erweiterungseingang D1 an $4017. Schreibt nacheinander 1 und 0 nach $4016, um die Zustände zu übernehmen, liest Bit 1 achtmal und legt die Werte der Reihe nach in Ergebnisbits 0–7 ab. Dies ist ein einzelner Lesevorgang auf D1 von Anschluss 2; weder wird D0 des normalen Controllers gelesen noch auf übereinstimmende Wiederholungswerte geprüft.

Syntax

c
u8 __pad_read2_d1(void);

Rückgabewert

Eine 8-Bit-Haltemaske in roher NES-Reihenfolge. Jedes gesetzte Bit steht für eine gedrückte Taste, 0 für keine. Kein auf 0/1 normalisierter Wahrheitswert.

Bedingungen und Grenzen

Rohes NES-Format: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Ein gesetztes Bit bedeutet gedrückt. Verwende diese Werte mit nes_pad_* und rohen __pad_*-Ergebnissen, nicht die gemeinsamen BTN_*-Masken aus input.h.

Verwenden Sie ein Peripheriegerät mit einem kompatiblen seriellen Achtbit-Controllerstrom auf der gewählten D1-Leitung. Die Funktion erkennt das Gerät nicht und implementiert nicht jedes Erweiterungsprotokoll. Jeder Aufruf übernimmt beide Controllerströme neu; beenden Sie einen vollständigen Lesevorgang, bevor Sie den nächsten beginnen. Die Ergebnisse sind neue Momentaufnahmen und aktualisieren weder input_* noch nes_pad1_*.

Das veröffentlichte KUROSAKI-Controllermodell liefert nur D0 und das feste Bit 6. D1 und das Mikrofonbit sind in diesem Emulator daher stets 0, auch bei gedrückten normalen Tasten. Der beigefügte Lauf prüft Kompilierung, normale Tasteneingaben und diese Nullsignal-Beobachtung. Peripheriesignale ungleich null, elektrische Zeitabläufe und reale Gerätekompatibilität bleiben ungeprüft. Eine Null beweist hier weder, dass das Gerät funktioniert, noch dass die Funktionen es auf echter Hardware nicht lesen können.

Anwendungsbeispiel

c
d1_second=__pad_read2_d1();

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Dieses Programm vergleicht normale D0-Eingaben mit D1 und dem digitalen Mikrofonpegel. Halten Sie A auf Controller 1 und B auf Controller 2 bis zur Erfassung gedrückt. NORMAL P1 und NORMAL P2 zeigen 001 und 002. Im beigefügten KUROSAKI-Lauf zeigen D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL und MIC DIRECT jeweils 000, weil diese Leitungen nicht emuliert werden. Die erfassten Werte bleiben anschließend stehen. Mit geeigneten Peripheriegeräten und implementierten Signalleitungen hängen die Werte von den tatsächlichen Eingaben ab; deuten Sie sie anhand der obigen Bitbelegung, statt diese Nullen zu erwarten.

Das veröffentlichte KUROSAKI-Controllermodell liefert nur D0 und das feste Bit 6. D1 und das Mikrofonbit sind in diesem Emulator daher stets 0, auch bei gedrückten normalen Tasten. Der beigefügte Lauf prüft Kompilierung, normale Tasteneingaben und diese Nullsignal-Beobachtung. Peripheriesignale ungleich null, elektrische Zeitabläufe und reale Gerätekompatibilität bleiben ungeprüft. Eine Null beweist hier weder, dass das Gerät funktioniert, noch dass die Funktionen es auf echter Hardware nicht lesen können.

__pad_read2_d1: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:225

Deklarationshinweise im Original
Latch and read an eight-button stream from port-2 data line D1 rather than D0.
__pad_read2_safe

Fragt Controller 2 durch wiederholte Paare vollständiger __pad_read2()-Aufrufe ab. Liefert die NES-Maske, sobald beide Werte eines Paares übereinstimmen. Das Ergebnis ist der Haltezustand, keine neue Druckflanke.

Syntax

c
u8 __pad_read2_safe(void);

Rückgabewert

Eine 8-Bit-Haltemaske in roher NES-Reihenfolge. Jedes gesetzte Bit steht für eine gedrückte Taste, 0 für keine. Kein auf 0/1 normalisierter Wahrheitswert.

Bedingungen und Grenzen

Rohes NES-Format: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Ein gesetztes Bit bedeutet gedrückt. Verwende diese Werte mit nes_pad_* und rohen __pad_*-Ergebnissen, nicht die gemeinsamen BTN_*-Masken aus input.h.

Dieser Aufruf liest einen neuen Hardwarezustand. Er aktualisiert weder den gespeicherten input_*- noch den nes_pad1_*-Zustand und erzeugt keine Druck-, Loslass- oder Wiederholungsereignisse. Speichere das Ergebnis und vergleiche es bei Bedarf mit deiner vorherigen Maske.

Die sichere Abfrage wiederholt Paare vollständiger Lesevorgänge, bis beide Masken übereinstimmen. Jeder Lesevorgang strobt $4016 und setzt die serielle Leseposition beider Controller zurück. Die Zahl der Versuche ist unbegrenzt. Diese Prüfung belegt keine Kompatibilität mit sämtlichen DMC-/Peripheriekombinationen.

Anwendungsbeispiel

c
safe2=__pad_read2_safe(); // The same consistency check on controller 2.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Dieses Programm vergleicht beide Controlleranschlüsse, einfache und auf Übereinstimmung geprüfte Lesevorgänge, Maskenfilter und Kurzmakros. Halten Sie A + Rechts auf Controller 1 und B + Links auf Controller 2 gedrückt. RAW P1, SAFE P1 und ALIAS P1 sind 129; die drei P2-Werte sind 66. BUTTONS P1 ist 1 und DIRS P1 ist 128: Der Richtungsfilter behält die oberen Bitpositionen bei. FAILED CHECKS muss 0 sein. Die erfassten Werte bleiben sichtbar.

__pad_read2_safe: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:211

Deklarationshinweise im Original
Repeat pairs of port-2 polls until both bytes match; this does not populate pad.c globals.
__palette_bg_load

Setzt den PPU-Latch zurück, setzt die Adresse 0x3F00 und schreibt genau 16 Quellbytes über PPUDATA für die vier Hintergrundpaletten. Die Übertragung erfolgt sofort; weder die Palettenauswahl der Nametable-Attribute noch Kachelgrafiken werden geändert.

Syntax

c
void __palette_bg_load(const u8* pal16);

Parameter

pal16

CPU-Zeiger auf 16 lesbare NES-Palettencode-Bytes, keine RGB-Tripel. Halten Sie die Quellbank bis zur synchronen Rückkehr eingeblendet; Bankwechsel und Längenprüfung finden nicht statt. Verwenden Sie PPU-Schrittweite 1, um die vorgesehenen 16 Palettenadressen zu erreichen.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Die PPU-Adressen 0x3F10/14/18/1C sind Aliase von 0x3F00/04/08/0C. Das Laden der Spritepalette beschreibt daher auch diese Hintergrundeinträge. Der Compiler sendet die Bytes unverändert und prüft keine Farben, mischt keine Paletten und erhält keine gemeinsam genutzten Einträge.

Direkter PPU-Zugriff erfordert ausgeschaltete Bildausgabe oder einen sicheren VBlank-Zeitraum, in den die gesamte Operation passt. Diese Funktionen warten nicht, legen keine Aufträge an, sperren NMI nicht und stellen den Bildlauf nicht wieder her. Verhindern Sie, dass Interrupts während des Aufrufs den gemeinsamen Latch oder die Adresse verändern. PPUCTRL-Bit 2 wählt eine Zieladressschrittweite von 1 oder 32 pro Byte. Spiegelung und Adressumlauf übernimmt die PPU; CHR-ROM ist nicht beschreibbar. Stellen Sie nach dem Zugriff vor der Bildausgabe den gewünschten Bildlauf ein.

NESdev: PPU-Register

NESdev: PPU-Scrolling

Anwendungsbeispiel

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

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.

Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).

Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

__palette_bg_load: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:138

Deklarationshinweise im Original
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

Setzt den PPU-Latch zurück, setzt die Adresse 0x3F10 und schreibt genau 16 Quellbytes über PPUDATA für die vier Spritepaletten. Die Sprite-Attributbits wählen die angezeigte Palette. Der Aufruf verändert weder OAM noch überträgt er Sprite-Kachelgrafiken.

Syntax

c
void __palette_sp_load(const u8* pal16);

Parameter

pal16

CPU-Zeiger auf 16 lesbare NES-Palettencode-Bytes, keine RGB-Tripel. Halten Sie die Quellbank bis zur synchronen Rückkehr eingeblendet; Bankwechsel und Längenprüfung finden nicht statt. Verwenden Sie PPU-Schrittweite 1, um die vorgesehenen 16 Palettenadressen zu erreichen.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Die PPU-Adressen 0x3F10/14/18/1C sind Aliase von 0x3F00/04/08/0C. Das Laden der Spritepalette beschreibt daher auch diese Hintergrundeinträge. Der Compiler sendet die Bytes unverändert und prüft keine Farben, mischt keine Paletten und erhält keine gemeinsam genutzten Einträge.

Direkter PPU-Zugriff erfordert ausgeschaltete Bildausgabe oder einen sicheren VBlank-Zeitraum, in den die gesamte Operation passt. Diese Funktionen warten nicht, legen keine Aufträge an, sperren NMI nicht und stellen den Bildlauf nicht wieder her. Verhindern Sie, dass Interrupts während des Aufrufs den gemeinsamen Latch oder die Adresse verändern. PPUCTRL-Bit 2 wählt eine Zieladressschrittweite von 1 oder 32 pro Byte. Spiegelung und Adressumlauf übernimmt die PPU; CHR-ROM ist nicht beschreibbar. Stellen Sie nach dem Zugriff vor der Bildausgabe den gewünschten Bildlauf ein.

NESdev: PPU-Register

NESdev: PPU-Scrolling

Anwendungsbeispiel

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

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.

Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).

Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

__palette_sp_load: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:140

Deklarationshinweise im Original
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

Wertet ppu_addr einmal aus, liest PPUSTATUS zum Zurücksetzen des gemeinsamen Schreib-Latches und schreibt dann das obere und untere Adressbyte nach PPUADDR. Legt das Ziel späterer PPUDATA-Zugriffe fest, ohne selbst Daten zu schreiben. Adressmaskierung und Spiegelung übernimmt die PPU.

Syntax

c
void __ppu_addr(u16 ppu_addr);

Parameter

ppu_addr

PPU-Byteadresse (0x0000–0x3FFF), kein CPU-Zeiger. Das Ziel muss beschreibbar sein: Musteränderungen benötigen CHR-RAM statt CHR-ROM. PPU-Adressabbildung und Spiegelung gelten weiterhin. Wählen Sie für fortlaufendes Kopieren und Füllen die Schrittweite 1 in PPUCTRL. Führen Sie Befehle zu einem sicheren PPU-Zeitpunkt aus und stellen Sie danach den Scrollzustand her; der Ausführer schreibt PPUADDR und PPUDATA, ohne Scrolleinstellungen wiederherzustellen.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Direkter PPU-Zugriff erfordert ausgeschaltete Bildausgabe oder einen sicheren VBlank-Zeitraum, in den die gesamte Operation passt. Diese Funktionen warten nicht, legen keine Aufträge an, sperren NMI nicht und stellen den Bildlauf nicht wieder her. Verhindern Sie, dass Interrupts während des Aufrufs den gemeinsamen Latch oder die Adresse verändern. PPUCTRL-Bit 2 wählt eine Zieladressschrittweite von 1 oder 32 pro Byte. Spiegelung und Adressumlauf übernimmt die PPU; CHR-ROM ist nicht beschreibbar. Stellen Sie nach dem Zugriff vor der Bildausgabe den gewünschten Bildlauf ein.

NESdev: PPU-Register

NESdev: PPU-Scrolling

Anwendungsbeispiel

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

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.

Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).

Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

__ppu_addr: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:106

Deklarationshinweise im Original
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

Liefert die Softwarekopie von PPUCTRL. Damit können Sie NMI-Aktivierung, Mustertabellenauswahl und Adressschritt vor einer vorübergehenden Änderung sichern.

Syntax

c
u8 __ppu_ctrl_get(void);

Rückgabewert

Liefert den letzten von Bibliothek oder intrinsischen Setzfunktionen gespeicherten Steuerwert als u8. PPUSTATUS wird nicht gelesen.

Bedingungen und Grenzen

PPUCTRL und PPUMASK sind nur beschreibbar. Diese Lesefunktion greift nicht auf die Hardware zu. Direkte Schreibzugriffe auf $2000/$2001 oder eigener Assemblercode umgehen die Softwarekopien. Verwenden Sie konsequent die entsprechenden __ppu_*_set-Operationen. Das Lesen der Kopie verändert weder Latch noch Interruptzustand.

Anwendungsbeispiel

c
__ppu_ctrl_set(4);
    if(__ppu_ctrl_get()!=4)failures=1; // Inspect the software shadow, not PPUSTATUS.
    __ppu_ctrl_set(0);

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Die ausführbare Alternative bereitet den Bildschirm bei ausgeschalteter Bildausgabe vor, setzt Paletten, löscht Kacheln und Attribute und zeichnet das Testbild. Bei (0,16) beginnt ein blaues Muster aus vollen Kacheln, linken Halbflächen und Dreiecken, bei (0,96) ein grüner Vollblock. Jeder Bereich ist 256×64 Pixel groß; die letzte 8×8-Zelle bei (248,72) beziehungsweise (248,152) bleibt wegen der Übertragung von 255 Kachelbytes schwarz. Bei y=208 stehen ein rotes 8×8-Quadrat an x=16, ein linker 4×8-Balken an x=224 und ein achtzeiliges Dreieck an x=240. FAILED CHECKS zeigt 0. Separate Zustandsprüfungen bestätigen beide Steuerregister, die 32 Palettenbytes und alle 1.024 gelöschten Nametable-Bytes. Die vier deklarierten Aufrufe müssen beim Linken scheitern.

__ppu_ctrl_get: Weitere Verwendungen
Weitere Verwendungen

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:103

Deklarationshinweise im Original
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

Ersetzt PPUCTRL und dessen Softwarekopie durch das übergebene Byte. Steuert NMI-Freigabe, PPU-Adressschrittweite, Mustertabellen, Spritegröße und Basis-Nametable. PPUMASK wird nicht geändert; nicht gesetzte Bits werden nicht aus dem bisherigen Wert übernommen.

Syntax

c
void __ppu_ctrl_set(u8 value);

Parameter

value

Vorzeichenloses Byte 0..255, das unverändert geschrieben wird. Gemeint ist ein vollständiges Register- oder Datenbyte, kein Wahrheitswert und keine Bitnummer.

Rückgabewert

Keiner (void).

NESdev: PPU-Register

NESdev: PPU-Scrolling

Anwendungsbeispiel

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

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.

Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).

Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

__ppu_ctrl_set: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:100

Deklarationshinweise im Original
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

Schreibt value einmal über PPUDATA an die aktuelle PPU-Adresse. Anschließend erhöht die PPU diese je nach PPUCTRL um 1 oder 32. Der Aufruf setzt keine Adresse, setzt keinen Latch zurück und legt keinen Übertragungsauftrag an.

Syntax

c
void __ppu_data(u8 value);

Parameter

value

Vorzeichenloses Byte 0..255, das unverändert geschrieben wird. Gemeint ist ein vollständiges Register- oder Datenbyte, kein Wahrheitswert und keine Bitnummer.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Direkter PPU-Zugriff erfordert ausgeschaltete Bildausgabe oder einen sicheren VBlank-Zeitraum, in den die gesamte Operation passt. Diese Funktionen warten nicht, legen keine Aufträge an, sperren NMI nicht und stellen den Bildlauf nicht wieder her. Verhindern Sie, dass Interrupts während des Aufrufs den gemeinsamen Latch oder die Adresse verändern. PPUCTRL-Bit 2 wählt eine Zieladressschrittweite von 1 oder 32 pro Byte. Spiegelung und Adressumlauf übernimmt die PPU; CHR-ROM ist nicht beschreibbar. Stellen Sie nach dem Zugriff vor der Bildausgabe den gewünschten Bildlauf ein.

NESdev: PPU-Register

NESdev: PPU-Scrolling

Anwendungsbeispiel

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

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.

Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).

Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

__ppu_data: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:108

Deklarationshinweise im Original
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

Liefert die Softwarekopie von PPUMASK. Damit bleiben Einstellungen für Darstellung, Sichtbarkeit am linken Rand, Graustufen und Farbbetonung erhalten.

Syntax

c
u8 __ppu_mask_get(void);

Rückgabewert

Liefert den letzten von Bibliothek oder intrinsischen Setzfunktionen gespeicherten Steuerwert als u8. PPUSTATUS wird nicht gelesen.

Bedingungen und Grenzen

PPUCTRL und PPUMASK sind nur beschreibbar. Diese Lesefunktion greift nicht auf die Hardware zu. Direkte Schreibzugriffe auf $2000/$2001 oder eigener Assemblercode umgehen die Softwarekopien. Verwenden Sie konsequent die entsprechenden __ppu_*_set-Operationen. Das Lesen der Kopie verändert weder Latch noch Interruptzustand.

Anwendungsbeispiel

c
__ppu_mask_set(0xE7);
    if(__ppu_mask_get()!=0xE7)failures=1;
    __ppu_mask_set(0);

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Die ausführbare Alternative bereitet den Bildschirm bei ausgeschalteter Bildausgabe vor, setzt Paletten, löscht Kacheln und Attribute und zeichnet das Testbild. Bei (0,16) beginnt ein blaues Muster aus vollen Kacheln, linken Halbflächen und Dreiecken, bei (0,96) ein grüner Vollblock. Jeder Bereich ist 256×64 Pixel groß; die letzte 8×8-Zelle bei (248,72) beziehungsweise (248,152) bleibt wegen der Übertragung von 255 Kachelbytes schwarz. Bei y=208 stehen ein rotes 8×8-Quadrat an x=16, ein linker 4×8-Balken an x=224 und ein achtzeiliges Dreieck an x=240. FAILED CHECKS zeigt 0. Separate Zustandsprüfungen bestätigen beide Steuerregister, die 32 Palettenbytes und alle 1.024 gelöschten Nametable-Bytes. Die vier deklarierten Aufrufe müssen beim Linken scheitern.

__ppu_mask_get: Weitere Verwendungen
Weitere Verwendungen

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

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

Ersetzt das gesamte PPUMASK-Byte und seine Softwarekopie durch value. Wählen Sie Bildausgabe, linken Rand, Graustufen und Farbbetonung ausdrücklich aus. Die Funktion kombiniert keine Bits mit der vorherigen Maske und verändert PPUCTRL nicht.

Syntax

c
void __ppu_mask_set(u8 value);

Parameter

value

Vorzeichenloses Byte 0..255, das unverändert geschrieben wird. Gemeint ist ein vollständiges Register- oder Datenbyte, kein Wahrheitswert und keine Bitnummer.

Rückgabewert

Keiner (void).

NESdev: PPU-Register

NESdev: PPU-Scrolling

Anwendungsbeispiel

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

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.

Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).

Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

__ppu_mask_set: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:98

Deklarationshinweise im Original
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

Schreibt null nach PPUMASK und in dessen Softwarekopie und schaltet Hintergrund und Sprites aus. Alle Maskenbits einschließlich Graustufen und Farbbetonung werden gelöscht. PPUCTRL und die NMI-Freigabe bleiben unverändert; steuern Sie NMI bei der Vorbereitung gemeinsam genutzter Übertragungsdaten separat.

Syntax

c
void __ppu_off(void);

Rückgabewert

Keiner (void).

NESdev: PPU-Register

NESdev: PPU-Scrolling

Anwendungsbeispiel

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.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.

Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).

Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

__ppu_off: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:96

Deklarationshinweise im Original
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

Ersetzt PPUMASK und dessen Softwarekopie durch 0x1E und aktiviert Hintergrund und Sprites einschließlich der linken acht Pixel. Graustufen- und Farbbetonungseinstellungen werden überschrieben; PPUCTRL und die NMI-Freigabe werden nicht geändert.

Syntax

c
void __ppu_on(void);

Rückgabewert

Keiner (void).

NESdev: PPU-Register

NESdev: PPU-Scrolling

Anwendungsbeispiel

c
__ppu_on(); // Replace mask with 0x1E: enable sprites and background at both edges.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.

Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).

Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

__ppu_on: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:94

Deklarationshinweise im Original
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

Liest PPUSTATUS einmal und gibt das Byte zurück, das vor dem durch den Lesezugriff ausgelösten Löschen des VBlank-Flags beobachtet wurde. Setzt außerdem den gemeinsamen Schreib-Latch für Bildlauf und Adresse zurück. Dies ist ein Hardware-Statuszugriff mit Nebenwirkungen, kein gespeicherter Bildzähler und keine NMI-Abschlussprüfung.

Syntax

c
u8 __ppu_read_status(void);

Rückgabewert

Rohes Statusbyte. Bit 7 meldet VBlank, Bit 6 Sprite-Zero-Hit und Bit 5 Sprite-Überlauf. Prüfen Sie das benötigte Flag mit einer Maske; behandeln Sie das gesamte Byte nicht als Wahrheitswert oder Bildnummer. Die unteren Bits sind kein portabler Anwendungsstatus.

Bedingungen und Grenzen

Ein PPUSTATUS-Lesezugriff kann das NMI-Timing und die nächsten Schreibzugriffe auf PPUSCROLL und PPUADDR beeinflussen. Fügen Sie ihn nicht zwischen ein erst halb geschriebenes Registerpaar einer anderen Routine ein. Diese Aufrufe warten nicht auf VBlank und arbeiten keine ausstehenden Übertragungsaufträge ab.

NESdev: PPU-Register

NESdev: PPU-Scrolling

Anwendungsbeispiel

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).

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.

Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).

Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

__ppu_read_status: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:110

Deklarationshinweise im Original
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

Verwendet dieselbe Mapper-gerechte PRG-Bankauswahl wie __bankswitch. Es gelten dieselben Bankprüfungen; die vorherige Zuordnung wird nicht automatisch wiederhergestellt.

Syntax

c
void __prg_bank_set(u8 bank);

Parameter

bank

Vorzeichenlose 8-Bit-ROM-Banknummer. Bei Aufrufen muss sie zur Funktionsplatzierung passen, bei Lesezugriffen wählt sie die Quelle. Verwenden Sie den gültigen Bereich des Modultyps.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Ein dauerhafter Bankwechsel darf weder den laufenden Code noch seine Rückkehradresse ausblenden. Legen Sie den Aufruf, gegebenenfalls bank_switch selbst und den anschließend ausgeführten Code wie im Beispiel in die gemeinsame beziehungsweise feste Bank 0.

Der Bibliothekszustand beginnt bei null und erkennt die Startzuordnung des Moduls nicht. Initialisieren Sie ihn mit bank_switch. Direkte Intrinsics und Mapper-Schreibzugriffe aktualisieren diesen separaten Zustand nicht; bank_get_current kann die tatsächliche Hardwarezuordnung daher nicht prüfen.

FC-Banknummern sind logische Nummern des Compilers. Null bezeichnet gemeinsames ROM, kein Umschaltziel; die Hilfsroutine setzt null auf Bank 1 um. Bei SUROM512 sind nur 1–30 gültig: Eine direkt an das Umschalt-Intrinsic übergebene Konstante außerhalb des Bereichs führt zu einem Buildfehler; ein ungültiger Laufzeitwert wählt 1 und setzt den Mapper-Fehlerzustand. Lesen Sie gemeinsames ROM direkt, statt Bank null anzufordern.

Anwendungsbeispiel

c
__prg_bank_set(1);bank_expect(home1[0]==0xA1,21);
__prg_bank_set(2); // Same mapper helper as __bankswitch.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Der anfängliche Aufruf bank_switch(2) speichert und wählt Bank 2. Ein späterer direkter Wechsel wählt Bank 1 und liest A1 aus home1, während bank_get_current weiterhin 2 meldet. Nach dem Zurückschalten liefert home2 wieder B2.

Das vollständige Programm zeigt Datenzugriffe über Bank und Nahadresse, bankübergreifende Callbacks und den Unterschied zwischen Bibliothekszustand und tatsächlicher Zuordnung. Alle Zahlen sind hexadezimal. BANK IN und BANK OUT müssen 02 zeigen, BYTE HEX D3, WORD HEX 5C7A sowie FAILED CHECKS und FIRST FAILURE jeweils 00. CGB und FC zeigen blaue Schrift, DMG schwarze.

CALL COUNT zeigt 02: Das Makro far_call und __farcall rufen den Callback jeweils einmal auf.

__prg_bank_set: MMC3
MMC3

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:81

Deklarationshinweise im Original
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

Schaltet das gemeinsame 16-Bit-Galois-LFSR des FC einmal weiter und liefert das XOR der aktualisierten Low- und High-Bytes. Ein LFSR ist ein Schieberegister mit per XOR berechneter Rückkopplung; ein fester Startwert erzeugt daher eine reproduzierbare Bytefolge.

Syntax

c
u8 __rng8(void);

Rückgabewert

Ein u8-Ergebnis (0–255) nach einmaligem Fortschalten des gemeinsamen Generators. Dies beschreibt Typ und Grenzen, nicht gleiche Wahrscheinlichkeit für jedes Byte.

Bedingungen und Grenzen

Der FC-Schritt schiebt den 16-Bit-Zustand um ein Bit nach rechts. War das ursprüngliche niederwertigste Bit 1, wird der geschobene Zustand per XOR mit 0xB400 verknüpft. Zurückgegeben wird low_byte XOR high_byte, nicht das rohe Low-Byte.

Sind beide Zustandsbytes null, setzt die nächste Ziehung vor dem Schieben 0xA55A ein. Daher liefert __rng_seed(0) gefolgt von __rng8() den Wert 255. Der Startwertaufruf speichert null; der Ersatz erfolgt erst beim Ziehen.

Diese Aufrufe teilen einen persistenten Zustand. Reproduktion erfordert dasselbe Zielsystem, denselben Startwert und dieselbe Aufrufreihenfolge einschließlich anderer Hilfsfunktionen. Führen Sie Startwertsetzung und Ziehungen in einem Ausführungskontext aus; die Prüfungen belegen keine sichere gleichzeitige Nutzung aus einem Interrupthandler.

GB und FC verwenden unterschiedliche Algorithmen und versprechen bei gleichem Startwert keine gleiche Folge. Dies sind deterministische Spielgeneratoren, keine Quellen kryptografischer Zufälligkeit. Die Tests prüfen Implementierungsverhalten, nicht statistische Qualität oder Periode.

Anwendungsbeispiel

c
__rng_seed(0x1234);
first=__rng8();second=__rng8();third=__rng8(); // FC sequence: 19, 137, 240.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Dieses Beispiel liest drei aufeinanderfolgende FC-Bytes. Startwert 0x1234 erzeugt FIRST 00019, SECOND 00137 und THIRD 00240. Diese Werte zeigen die Aufrufreihenfolge, keine Häufigkeit oder Zufälligkeitsbewertung.

Das vollständige NROM-Programm zeigt blaue Dezimalergebnisse auf Schwarz. FAILED muss 00000 anzeigen. Der Text wird bei deaktivierter Darstellung übertragen, bevor der Ergebnisbildschirm eingeschaltet wird.

Die Prüfung verglich je Fall 32 Ausgaben und acht anschließende Byteziehungen mit einem unabhängigen Hostmodell: 93 GB- und 18 FC-Fälle, einschließlich konstanter und zur Laufzeit gesetzter Startwerte sowie GB-Grenzfällen für Bereiche, Chancen und Gewichte. Die Lehrbildschirme wurden nach 240 Emulatorframes geprüft. Echte Hardware und statistische Qualität wurden nicht getestet.

__rng8: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:39

Deklarationshinweise im Original
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

Ersetzt beide Zustandsbytes des FC-Generators. Derselbe Startwert lässt einen Test oder eine Wiederholung am selben Punkt der Pseudozufallsfolge beginnen; der Aufruf schaltet den Generator nicht weiter.

Syntax

c
void __rng_seed(u16 seed);

Parameter

seed

Vorzeichenloser 16-Bit-Startwert in 0–65535. Beide Bytes werden unverändert gespeichert. Wählen und notieren Sie den Startwert vor der Folge von Spielziehungen, die Sie reproduzieren möchten.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Sind beide Zustandsbytes null, setzt die nächste Ziehung vor dem Schieben 0xA55A ein. Daher liefert __rng_seed(0) gefolgt von __rng8() den Wert 255. Der Startwertaufruf speichert null; der Ersatz erfolgt erst beim Ziehen.

Diese Aufrufe teilen einen persistenten Zustand. Reproduktion erfordert dasselbe Zielsystem, denselben Startwert und dieselbe Aufrufreihenfolge einschließlich anderer Hilfsfunktionen. Führen Sie Startwertsetzung und Ziehungen in einem Ausführungskontext aus; die Prüfungen belegen keine sichere gleichzeitige Nutzung aus einem Interrupthandler.

GB und FC verwenden unterschiedliche Algorithmen und versprechen bei gleichem Startwert keine gleiche Folge. Dies sind deterministische Spielgeneratoren, keine Quellen kryptografischer Zufälligkeit. Die Tests prüfen Implementierungsverhalten, nicht statistische Qualität oder Periode.

Anwendungsbeispiel

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.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Dieses Beispiel zeigt reproduzierbaren FC-Start und die Nullstartwertregel. FIRST und RESEEDED zeigen bei Startwert 0x1234 jeweils 00019. ZERO SEED zeigt nach Startwert null den Wert 00255.

Das vollständige NROM-Programm zeigt blaue Dezimalergebnisse auf Schwarz. FAILED muss 00000 anzeigen. Der Text wird bei deaktivierter Darstellung übertragen, bevor der Ergebnisbildschirm eingeschaltet wird.

Die Prüfung verglich je Fall 32 Ausgaben und acht anschließende Byteziehungen mit einem unabhängigen Hostmodell: 93 GB- und 18 FC-Fälle, einschließlich konstanter und zur Laufzeit gesetzter Startwerte sowie GB-Grenzfällen für Bereiche, Chancen und Gewichte. Die Lehrbildschirme wurden nach 240 Emulatorframes geprüft. Echte Hardware und statistische Qualität wurden nicht getestet.

__rng_seed: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:37

Deklarationshinweise im Original
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

Wählt die Ein- oder Ausdarstellungsmaske und wartet auf eine Änderung des NMI-Zählers. Die gewählte Maske bleibt nach Rückkehr wirksam.

Syntax

c
void __rob_flash(u8 bright);

Parameter

bright

Null wählt aus, jedes Byte ungleich null einschließlich 255 wählt an. Dies ist keine Helligkeitsstufe.

Rückgabewert

Kein Rückgabewert. Kehrt nach Abschluss der angeforderten Wartezeiten zum Aufrufer zurück.

Bedingungen und Grenzen

Binden Sie intrinsics.h ein. Der Standard-NMI-Handler muss laufen und seinen Zähler erhöhen. Bei deaktiviertem NMI oder einem eigenen Handler ohne diese Zählerpflege bleibt die Wartefunktion blockiert. Es gibt kein Zeitlimit.

Die Einphase setzt PPUMASK und seine Softwarekopie auf 0x1E, die Ausphase beide auf null. Die vorherige Maske wird nicht wiederhergestellt. Dies ändert Hintergrund-, Sprite- und linke Randdarstellung gemeinsam und ersetzt auch Graustufen- und Farbbetonungseinstellungen.

Das Einschalten der Darstellung erzeugt allein keinen weißen Bildschirm. Stellen Sie helle Kacheln und Paletteneinträge sowie eine dunkle Hintergrundfarbe für die Ausphase bereit. Das Beispiel nutzt vollständig weiße Kacheln und schwarzen Hintergrund, um während des Vorgangs den ganzen Bildschirm blinken zu lassen.

Wartezählungen messen Änderungen des NMI-Zählers. Die Zeit vom Eintritt bis zum ersten NMI kann kürzer als ein Frame sein. Rufen Sie aus dem Hauptcode auf, nicht aus einem Interrupt. Andere Routinen, die die Darstellungsmaske ändern, können das Muster stören.

Dies sind Grundoperationen für Anzeigemaske und Warten. Sie erzeugen weder einen Roboterbefehlsvorspann noch gerätespezifische Befehlsfolgen oder Empfangsbestätigungen. Geprüft wurden PPUMASK-Schreibzugriffe, NMI-Zählungen und Pixel in KUROSAKI; der Empfang eines echten Roboters wurde nicht getestet.

Anwendungsbeispiel

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.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Nach Ausführung zeigt der Bildschirm FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 und A5 BYTE WAITS 48. Das Diagramm stellt 0xA5 dar: oben 1,0,1,0, unten 0,1,0,1. Jede Zelle entspricht einer Warteoperation; Weiß bedeutet Darstellung an, Schwarz aus. Eine Eins hat vier weiße und zwei schwarze Zellen, eine Null zwei weiße und vier schwarze. FINAL MASK 0 und TIMING CHECKS OK zeigen den Abschluss.

__rob_flash: NTSC — Nach Ausführung zeigt der Bildschirm FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 und A5 BYTE WAITS 48. Das Diagramm stellt 0xA5 dar: oben 1,0,1,0, unten 0,1,0,1. Jede Zelle entspricht einer Warteoperation; Weiß bedeutet Darstellung an, Schwarz aus. Eine Eins hat vier weiße und zwei schwarze Zellen, eine Null zwei weiße und vier schwarze. FINAL MASK 0 und TIMING CHECKS OK zeigen den Abschluss.
NTSC — Nach Ausführung zeigt der Bildschirm FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 und A5 BYTE WAITS 48. Das Diagramm stellt 0xA5 dar: oben 1,0,1,0, unten 0,1,0,1. Jede Zelle entspricht einer Warteoperation; Weiß bedeutet Darstellung an, Schwarz aus. Eine Eins hat vier weiße und zwei schwarze Zellen, eine Null zwei weiße und vier schwarze. FINAL MASK 0 und TIMING CHECKS OK zeigen den Abschluss.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:272

Deklarationshinweise im Original
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

Führt die angeforderten Einwartephasen und danach die Auswartephasen aus und erzeugt damit einen Puls.

Syntax

c
void __rob_pulse(u8 on_frames, u8 off_frames);

Parameter

on_frames

Warteanzahl der Einphase in 0..255. Null überspringt diese Phase.

off_frames

Warteanzahl der Ausphase in 0..255. Null überspringt diese Phase. Insgesamt sind bis zu 510 Warteoperationen möglich.

Rückgabewert

Kein Rückgabewert. Kehrt nach Abschluss der angeforderten Wartezeiten zum Aufrufer zurück.

Bedingungen und Grenzen

Sind beide Anzahlen null, ändern sich weder Maske noch Timing. Jede Auswartephase hinterlässt Maske null; ein reiner Einpuls hinterlässt 0x1E.

Binden Sie intrinsics.h ein. Der Standard-NMI-Handler muss laufen und seinen Zähler erhöhen. Bei deaktiviertem NMI oder einem eigenen Handler ohne diese Zählerpflege bleibt die Wartefunktion blockiert. Es gibt kein Zeitlimit.

Die Einphase setzt PPUMASK und seine Softwarekopie auf 0x1E, die Ausphase beide auf null. Die vorherige Maske wird nicht wiederhergestellt. Dies ändert Hintergrund-, Sprite- und linke Randdarstellung gemeinsam und ersetzt auch Graustufen- und Farbbetonungseinstellungen.

Das Einschalten der Darstellung erzeugt allein keinen weißen Bildschirm. Stellen Sie helle Kacheln und Paletteneinträge sowie eine dunkle Hintergrundfarbe für die Ausphase bereit. Das Beispiel nutzt vollständig weiße Kacheln und schwarzen Hintergrund, um während des Vorgangs den ganzen Bildschirm blinken zu lassen.

Wartezählungen messen Änderungen des NMI-Zählers. Die Zeit vom Eintritt bis zum ersten NMI kann kürzer als ein Frame sein. Rufen Sie aus dem Hauptcode auf, nicht aus einem Interrupt. Andere Routinen, die die Darstellungsmaske ändern, können das Muster stören.

Dies sind Grundoperationen für Anzeigemaske und Warten. Sie erzeugen weder einen Roboterbefehlsvorspann noch gerätespezifische Befehlsfolgen oder Empfangsbestätigungen. Geprüft wurden PPUMASK-Schreibzugriffe, NMI-Zählungen und Pixel in KUROSAKI; der Empfang eines echten Roboters wurde nicht getestet.

Anwendungsbeispiel

c
start=__nmi_ready();
    __rob_pulse(3,2); // Three enabled waits, then two disabled waits.
    rob_result[1]=(u8)(__nmi_ready()-start);

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Nach Ausführung zeigt der Bildschirm FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 und A5 BYTE WAITS 48. Das Diagramm stellt 0xA5 dar: oben 1,0,1,0, unten 0,1,0,1. Jede Zelle entspricht einer Warteoperation; Weiß bedeutet Darstellung an, Schwarz aus. Eine Eins hat vier weiße und zwei schwarze Zellen, eine Null zwei weiße und vier schwarze. FINAL MASK 0 und TIMING CHECKS OK zeigen den Abschluss.

__rob_pulse: NTSC — Nach Ausführung zeigt der Bildschirm FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 und A5 BYTE WAITS 48. Das Diagramm stellt 0xA5 dar: oben 1,0,1,0, unten 0,1,0,1. Jede Zelle entspricht einer Warteoperation; Weiß bedeutet Darstellung an, Schwarz aus. Eine Eins hat vier weiße und zwei schwarze Zellen, eine Null zwei weiße und vier schwarze. FINAL MASK 0 und TIMING CHECKS OK zeigen den Abschluss.
NTSC — Nach Ausführung zeigt der Bildschirm FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 und A5 BYTE WAITS 48. Das Diagramm stellt 0xA5 dar: oben 1,0,1,0, unten 0,1,0,1. Jede Zelle entspricht einer Warteoperation; Weiß bedeutet Darstellung an, Schwarz aus. Eine Eins hat vier weiße und zwei schwarze Zellen, eine Null zwei weiße und vier schwarze. FINAL MASK 0 und TIMING CHECKS OK zeigen den Abschluss.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:274

Deklarationshinweise im Original
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

Codiert ein Byte als acht Blinkmuster, beginnend mit dem höchstwertigen Bit. Eins nutzt vier Ein- und zwei Auswartephasen, null zwei Ein- und vier Auswartephasen. Insgesamt sind es 48 NMI-Warteoperationen.

Syntax

c
void __rob_send_byte(u8 pattern);

Parameter

pattern

Achtbitmuster in 0..255. Für 0xA5 gilt die Reihenfolge 1,0,1,0,0,1,0,1. Dies ist weder Kanal-ID noch Roboteraktionsnummer.

Rückgabewert

Kein Rückgabewert. Kehrt nach Abschluss der angeforderten Wartezeiten zum Aufrufer zurück.

Bedingungen und Grenzen

Auch das letzte Bit endet mit Auswartephasen und lässt PPUMASK samt Kopie auf null. Es werden genau acht Bits gesendet, ohne Start- oder Endmarkierung.

Binden Sie intrinsics.h ein. Der Standard-NMI-Handler muss laufen und seinen Zähler erhöhen. Bei deaktiviertem NMI oder einem eigenen Handler ohne diese Zählerpflege bleibt die Wartefunktion blockiert. Es gibt kein Zeitlimit.

Die Einphase setzt PPUMASK und seine Softwarekopie auf 0x1E, die Ausphase beide auf null. Die vorherige Maske wird nicht wiederhergestellt. Dies ändert Hintergrund-, Sprite- und linke Randdarstellung gemeinsam und ersetzt auch Graustufen- und Farbbetonungseinstellungen.

Das Einschalten der Darstellung erzeugt allein keinen weißen Bildschirm. Stellen Sie helle Kacheln und Paletteneinträge sowie eine dunkle Hintergrundfarbe für die Ausphase bereit. Das Beispiel nutzt vollständig weiße Kacheln und schwarzen Hintergrund, um während des Vorgangs den ganzen Bildschirm blinken zu lassen.

Wartezählungen messen Änderungen des NMI-Zählers. Die Zeit vom Eintritt bis zum ersten NMI kann kürzer als ein Frame sein. Rufen Sie aus dem Hauptcode auf, nicht aus einem Interrupt. Andere Routinen, die die Darstellungsmaske ändern, können das Muster stören.

Dies sind Grundoperationen für Anzeigemaske und Warten. Sie erzeugen weder einen Roboterbefehlsvorspann noch gerätespezifische Befehlsfolgen oder Empfangsbestätigungen. Geprüft wurden PPUMASK-Schreibzugriffe, NMI-Zählungen und Pixel in KUROSAKI; der Empfang eines echten Roboters wurde nicht getestet.

Anwendungsbeispiel

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.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Nach Ausführung zeigt der Bildschirm FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 und A5 BYTE WAITS 48. Das Diagramm stellt 0xA5 dar: oben 1,0,1,0, unten 0,1,0,1. Jede Zelle entspricht einer Warteoperation; Weiß bedeutet Darstellung an, Schwarz aus. Eine Eins hat vier weiße und zwei schwarze Zellen, eine Null zwei weiße und vier schwarze. FINAL MASK 0 und TIMING CHECKS OK zeigen den Abschluss.

__rob_send_byte: NTSC — Nach Ausführung zeigt der Bildschirm FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 und A5 BYTE WAITS 48. Das Diagramm stellt 0xA5 dar: oben 1,0,1,0, unten 0,1,0,1. Jede Zelle entspricht einer Warteoperation; Weiß bedeutet Darstellung an, Schwarz aus. Eine Eins hat vier weiße und zwei schwarze Zellen, eine Null zwei weiße und vier schwarze. FINAL MASK 0 und TIMING CHECKS OK zeigen den Abschluss.
NTSC — Nach Ausführung zeigt der Bildschirm FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 und A5 BYTE WAITS 48. Das Diagramm stellt 0xA5 dar: oben 1,0,1,0, unten 0,1,0,1. Jede Zelle entspricht einer Warteoperation; Weiß bedeutet Darstellung an, Schwarz aus. Eine Eins hat vier weiße und zwei schwarze Zellen, eine Null zwei weiße und vier schwarze. FINAL MASK 0 und TIMING CHECKS OK zeigen den Abschluss.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:278

Deklarationshinweise im Original
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

Liest PPUSTATUS und verwirft das Ergebnis, um den gemeinsamen Schreib-Latch für Bildlauf und Adresse zurückzusetzen. Löscht dabei auch das VBlank-Flag. Es werden weder Bildlaufkoordinaten von null geschrieben noch gespeicherte X/Y-Werte verändert.

Syntax

c
void __scroll_latch_reset(void);

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Ein PPUSTATUS-Lesezugriff kann das NMI-Timing und die nächsten Schreibzugriffe auf PPUSCROLL und PPUADDR beeinflussen. Fügen Sie ihn nicht zwischen ein erst halb geschriebenes Registerpaar einer anderen Routine ein. Diese Aufrufe warten nicht auf VBlank und arbeiten keine ausstehenden Übertragungsaufträge ab.

NESdev: PPU-Register

NESdev: PPU-Scrolling

Anwendungsbeispiel

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.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.

Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).

Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

__scroll_latch_reset: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:112

Deklarationshinweise im Original
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

Speichert beide Pixelversätze in den X/Y-Kopien der Laufzeitbibliothek, liest PPUSTATUS zum Zurücksetzen des Latches und schreibt dann X und Y in dieser Reihenfolge nach PPUSCROLL. Aktualisiert den Bildlauf direkt, ohne eine Kamerabewegung vorzumerken, auf NMI zu warten oder eine Basis-Nametable auszuwählen.

Syntax

c
void __scroll_set(u8 x, u8 y);

Parameter

x / y

Die Versätze sind vorzeichenlose Pixelbytes. Verwenden Sie X 0..255 und normalerweise Y 0..239; Y 240..255 wird unverändert geschrieben, hat aber ein besonderes PPU-Zeilenverhalten statt normalen sichtbaren Kartenbildlaufs. PPUCTRL liefert die Basis-Nametable-Bits. Stellen Sie den Bildlauf nach direktem VRAM-Zugriff vor der Bildausgabe wieder her; die Funktion plant diesen Zeitpunkt nicht.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Ein PPUSTATUS-Lesezugriff kann das NMI-Timing und die nächsten Schreibzugriffe auf PPUSCROLL und PPUADDR beeinflussen. Fügen Sie ihn nicht zwischen ein erst halb geschriebenes Registerpaar einer anderen Routine ein. Diese Aufrufe warten nicht auf VBlank und arbeiten keine ausstehenden Übertragungsaufträge ab.

NESdev: PPU-Register

NESdev: PPU-Scrolling

Anwendungsbeispiel

c
__scroll_set(19,27); // Save both coordinates and write the two-scroll-byte pair.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.

Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).

Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

__scroll_set: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:114

Deklarationshinweise im Original
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

Ersetzt das gespeicherte X, setzt den Latch durch einen Statuslesezugriff zurück und schreibt das neue X sowie das gespeicherte Y nach PPUSCROLL. Y stammt aus der Softwarekopie, nicht aus der PPU; direkte Registerzugriffe aktualisieren diese Kopie nicht.

Syntax

c
void __scroll_x_set(u8 x);

Parameter

x

Die Versätze sind vorzeichenlose Pixelbytes. Verwenden Sie X 0..255 und normalerweise Y 0..239; Y 240..255 wird unverändert geschrieben, hat aber ein besonderes PPU-Zeilenverhalten statt normalen sichtbaren Kartenbildlaufs. PPUCTRL liefert die Basis-Nametable-Bits. Stellen Sie den Bildlauf nach direktem VRAM-Zugriff vor der Bildausgabe wieder her; die Funktion plant diesen Zeitpunkt nicht.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Ein PPUSTATUS-Lesezugriff kann das NMI-Timing und die nächsten Schreibzugriffe auf PPUSCROLL und PPUADDR beeinflussen. Fügen Sie ihn nicht zwischen ein erst halb geschriebenes Registerpaar einer anderen Routine ein. Diese Aufrufe warten nicht auf VBlank und arbeiten keine ausstehenden Übertragungsaufträge ab.

NESdev: PPU-Register

NESdev: PPU-Scrolling

Anwendungsbeispiel

c
__scroll_x_set(0); // Keeps saved Y=27 while changing X to zero.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.

Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).

Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

__scroll_x_set: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:116

Deklarationshinweise im Original
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

Ersetzt das gespeicherte Y, setzt den Latch durch einen Statuslesezugriff zurück und schreibt das gespeicherte X sowie das neue Y nach PPUSCROLL. Auch bei alleiniger Änderung von Y werden beide Scrollbytes geschrieben. X stammt aus der Laufzeitkopie.

Syntax

c
void __scroll_y_set(u8 y);

Parameter

y

Die Versätze sind vorzeichenlose Pixelbytes. Verwenden Sie X 0..255 und normalerweise Y 0..239; Y 240..255 wird unverändert geschrieben, hat aber ein besonderes PPU-Zeilenverhalten statt normalen sichtbaren Kartenbildlaufs. PPUCTRL liefert die Basis-Nametable-Bits. Stellen Sie den Bildlauf nach direktem VRAM-Zugriff vor der Bildausgabe wieder her; die Funktion plant diesen Zeitpunkt nicht.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Ein PPUSTATUS-Lesezugriff kann das NMI-Timing und die nächsten Schreibzugriffe auf PPUSCROLL und PPUADDR beeinflussen. Fügen Sie ihn nicht zwischen ein erst halb geschriebenes Registerpaar einer anderen Routine ein. Diese Aufrufe warten nicht auf VBlank und arbeiten keine ausstehenden Übertragungsaufträge ab.

NESdev: PPU-Register

NESdev: PPU-Scrolling

Anwendungsbeispiel

c
__scroll_y_set(0); // Keeps saved X=0; the scene is now at the origin.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.

Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).

Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

__scroll_y_set: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:118

Deklarationshinweise im Original
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

Berechnet ein vorzeichenbehaftetes Skalarprodukt mit zwei Termen.

Syntax

c
u16 __sdot2_q1_7(u16 a0, u16 a1, u8 b0, u8 b1);

Parameter

a0

Vorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.

a1

Vorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.

b0

Vorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.

b1

Vorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.

Rückgabewert

Gibt die unteren 16 Bits von a0*b0+a1*b1 zurück.

Bedingungen und Grenzen

Auf dem FC verwenden die q8_8/q1_7-Funktionen rohe Ganzzahlprodukte ohne Festkomma-Rechtsshift. Q1.7-Funktionen sind Aliase der entsprechenden vorzeichenbehafteten Operationen. Der aufrufende Code verwaltet die Einheiten; vorzeichenbehaftete Ergebnisse werden als u16-Bitmuster zurückgegeben.

Anwendungsbeispiel

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);

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.

Der Ausschnitt speichert folgende 16-Bit-Werte: result[34]=64959, result[35]=64959, result[36]=577, result[37]=577. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

__sdot2_q1_7: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:71

Deklarationshinweise im Original
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

Berechnet ein vorzeichenbehaftetes Skalarprodukt mit zwei Termen.

Syntax

c
u16 __sdot2_q8_8(u16 a0, u16 a1, u8 b0, u8 b1);

Parameter

a0

Vorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.

a1

Vorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.

b0

Vorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.

b1

Vorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.

Rückgabewert

Gibt die unteren 16 Bits von a0*b0+a1*b1 zurück.

Bedingungen und Grenzen

Auf dem FC verwenden die q8_8/q1_7-Funktionen rohe Ganzzahlprodukte ohne Festkomma-Rechtsshift. Q1.7-Funktionen sind Aliase der entsprechenden vorzeichenbehafteten Operationen. Der aufrufende Code verwaltet die Einheiten; vorzeichenbehaftete Ergebnisse werden als u16-Bitmuster zurückgegeben.

Anwendungsbeispiel

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);

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.

Der Ausschnitt speichert folgende 16-Bit-Werte: result[26]=64959, result[27]=64959, result[28]=577, result[29]=577. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

__sdot2_q8_8: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:67

Deklarationshinweise im Original
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

Berechnet ein vorzeichenbehaftetes Skalarprodukt mit drei Termen.

Syntax

c
u16 __sdot3_q1_7(u16 a0, u16 a1, u16 a2, u8 b0, u8 b1, u8 b2);

Parameter

a0

Vorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.

a1

Vorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.

a2

Vorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.

b0

Vorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.

b1

Vorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.

b2

Vorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.

Rückgabewert

Gibt die unteren 16 Bits von a0*b0+a1*b1+a2*b2 zurück.

Bedingungen und Grenzen

Auf dem FC verwenden die q8_8/q1_7-Funktionen rohe Ganzzahlprodukte ohne Festkomma-Rechtsshift. Q1.7-Funktionen sind Aliase der entsprechenden vorzeichenbehafteten Operationen. Der aufrufende Code verwaltet die Einheiten; vorzeichenbehaftete Ergebnisse werden als u16-Bitmuster zurückgegeben.

Anwendungsbeispiel

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);

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.

Der Ausschnitt speichert folgende 16-Bit-Werte: result[38]=2498, result[39]=2498, result[40]=63038, result[41]=63038. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

__sdot3_q1_7: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:73

Deklarationshinweise im Original
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

Berechnet ein vorzeichenbehaftetes Skalarprodukt mit drei Termen.

Syntax

c
u16 __sdot3_q8_8(u16 a0, u16 a1, u16 a2, u8 b0, u8 b1, u8 b2);

Parameter

a0

Vorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.

a1

Vorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.

a2

Vorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.

b0

Vorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.

b1

Vorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.

b2

Vorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.

Rückgabewert

Gibt die unteren 16 Bits von a0*b0+a1*b1+a2*b2 zurück.

Bedingungen und Grenzen

Auf dem FC verwenden die q8_8/q1_7-Funktionen rohe Ganzzahlprodukte ohne Festkomma-Rechtsshift. Q1.7-Funktionen sind Aliase der entsprechenden vorzeichenbehafteten Operationen. Der aufrufende Code verwaltet die Einheiten; vorzeichenbehaftete Ergebnisse werden als u16-Bitmuster zurückgegeben.

Anwendungsbeispiel

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);

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.

Der Ausschnitt speichert folgende 16-Bit-Werte: result[30]=2498, result[31]=2498, result[32]=63038, result[33]=63038. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

__sdot3_q8_8: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:69

Deklarationshinweise im Original
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

Tastet Bit 4 von $4017 einmal als Empfangsleitung eines externen Adapters ab.

Syntax

c
u8 __serial_rx_bit(void);

Rückgabewert

1, wenn Bit 4 gesetzt ist, andernfalls 0. Das ist weder ein Bereitschaftssignal für Daten noch ein Kommunikationsfehlerstatus.

Bedingungen und Grenzen

Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.

Diese Grundoperationen setzen einen externen Adapter voraus, der OUT0 mit TX und $4017 D4 mit RX verbindet. Sie liefern weder Bitabstände noch einen Übertragungstakt, Start-/Stoppbits, die Zusammensetzung von Bytes oder eine Empfangswarteschleife. Sie erfüllen damit eine andere Aufgabe als byteorientierte Hilfsfunktionen wie __midi_out().

Der aufrufende Code muss zum passenden Zeitpunkt abtasten. Die Funktion wartet weder auf Daten noch setzt sie mehrere Bits zusammen; auch die Ausgabe von $4016 bleibt unverändert.

Die Bitumwandlung wird außerdem in einem selbst erstellten Test-ROM geprüft. Dabei werden Registerlesezugriffe auf Testwerte im RAM umgeleitet. Die Tests decken 00/08/10/18 mit zusätzlich gesetzten, hier irrelevanten Bits ab. Dieser Modultest ist getrennt vom unveränderten Beispielprogramm und vom Verhalten echter Peripherie zu betrachten.

Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.

Anwendungsbeispiel

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();}
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Für VALUE 000/001/002/003/254/255 lautet EXPECTED OUT0 0/1/0/1/0/1. Die Spalte erläutert die Auswahl des niederwertigsten Bits; sie zeigt keinen zurückgelesenen elektrischen Pegel. CPU-Traces bestätigen diese Schreibzugriffe und ein abschließendes Schreiben von 1. RX SAMPLE ist 000, da KUROSAKI die Empfangsleitung nicht emuliert.

__serial_rx_bit: SERIAL-DEFAULT — Für VALUE 000/001/002/003/254/255 lautet EXPECTED OUT0 0/1/0/1/0/1. Die Spalte erläutert die Auswahl des niederwertigsten Bits; sie zeigt keinen zurückgelesenen elektrischen Pegel. CPU-Traces bestätigen diese Schreibzugriffe und ein abschließendes Schreiben von 1. RX SAMPLE ist 000, da KUROSAKI die Empfangsleitung nicht emuliert.
SERIAL-DEFAULT — Für VALUE 000/001/002/003/254/255 lautet EXPECTED OUT0 0/1/0/1/0/1. Die Spalte erläutert die Auswahl des niederwertigsten Bits; sie zeigt keinen zurückgelesenen elektrischen Pegel. CPU-Traces bestätigen diese Schreibzugriffe und ein abschließendes Schreiben von 1. RX SAMPLE ist 000, da KUROSAKI die Empfangsleitung nicht emuliert.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:288

Deklarationshinweise im Original
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

Schreibt das niederwertigste Bit des Arguments über $4016 auf OUT0. Damit steht eine einzelne Bitausgabe für einen externen seriellen Adapter zur Verfügung.

Syntax

c
void __serial_tx_bit(u8 bit);

Parameter

bit

Auszugebender Wert. Verwendet wird nur bit & 1: 2 gibt null aus, 255 dagegen eins.

Rückgabewert

Kein Rückgabewert.

Bedingungen und Grenzen

Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.

Diese Grundoperationen setzen einen externen Adapter voraus, der OUT0 mit TX und $4017 D4 mit RX verbindet. Sie liefern weder Bitabstände noch einen Übertragungstakt, Start-/Stoppbits, die Zusammensetzung von Bytes oder eine Empfangswarteschleife. Sie erfüllen damit eine andere Aufgabe als byteorientierte Hilfsfunktionen wie __midi_out().

Schreibt null oder eins in das gesamte Register $4016 und löscht dabei OUT1/OUT2. Eine Haltezeit wird nicht eingefügt. OUT0 steuert die Übernahme der Tastenwerte beim normalen Gamepad; der Vorgang verändert daher auch dessen Lesezustand.

Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.

Anwendungsbeispiel

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();}
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Für VALUE 000/001/002/003/254/255 lautet EXPECTED OUT0 0/1/0/1/0/1. Die Spalte erläutert die Auswahl des niederwertigsten Bits; sie zeigt keinen zurückgelesenen elektrischen Pegel. CPU-Traces bestätigen diese Schreibzugriffe und ein abschließendes Schreiben von 1. RX SAMPLE ist 000, da KUROSAKI die Empfangsleitung nicht emuliert.

__serial_tx_bit: SERIAL-DEFAULT — Für VALUE 000/001/002/003/254/255 lautet EXPECTED OUT0 0/1/0/1/0/1. Die Spalte erläutert die Auswahl des niederwertigsten Bits; sie zeigt keinen zurückgelesenen elektrischen Pegel. CPU-Traces bestätigen diese Schreibzugriffe und ein abschließendes Schreiben von 1. RX SAMPLE ist 000, da KUROSAKI die Empfangsleitung nicht emuliert.
SERIAL-DEFAULT — Für VALUE 000/001/002/003/254/255 lautet EXPECTED OUT0 0/1/0/1/0/1. Die Spalte erläutert die Auswahl des niederwertigsten Bits; sie zeigt keinen zurückgelesenen elektrischen Pegel. CPU-Traces bestätigen diese Schreibzugriffe und ein abschließendes Schreiben von 1. RX SAMPLE ist 000, da KUROSAKI die Empfangsleitung nicht emuliert.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:286

Deklarationshinweise im Original
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

Berechnet eine vorzeichenbehaftete Multiplikation mit Akkumulation.

Syntax

c
u16 __smac16(u16 acc, u16 a, u8 b);

Parameter

acc

Zu addierender 16-Bit-Akkumulator.

a

Vorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.

b

Vorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.

Rückgabewert

Gibt die unteren 16 Bits von acc+a*b zurück.

Bedingungen und Grenzen

Auf dem FC verwenden die q8_8/q1_7-Funktionen rohe Ganzzahlprodukte ohne Festkomma-Rechtsshift. Q1.7-Funktionen sind Aliase der entsprechenden vorzeichenbehafteten Operationen. Der aufrufende Code verwaltet die Einheiten; vorzeichenbehaftete Ergebnisse werden als u16-Bitmuster zurückgegeben.

Anwendungsbeispiel

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);

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.

Der Ausschnitt speichert folgende 16-Bit-Werte: result[10]=32796, result[11]=32796, result[12]=32668, result[13]=32668. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

__smac16: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:57

Deklarationshinweise im Original
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

Berechnet eine vorzeichenbehaftete Multiplikation mit Akkumulation.

Syntax

c
u16 __smac16_q1_7(u16 acc, u16 a, u8 b);

Parameter

acc

Zu addierender 16-Bit-Akkumulator.

a

Vorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.

b

Vorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.

Rückgabewert

Gibt die unteren 16 Bits von acc+a*b zurück.

Bedingungen und Grenzen

Auf dem FC verwenden die q8_8/q1_7-Funktionen rohe Ganzzahlprodukte ohne Festkomma-Rechtsshift. Q1.7-Funktionen sind Aliase der entsprechenden vorzeichenbehafteten Operationen. Der aufrufende Code verwaltet die Einheiten; vorzeichenbehaftete Ergebnisse werden als u16-Bitmuster zurückgegeben.

Anwendungsbeispiel

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);

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.

Der Ausschnitt speichert folgende 16-Bit-Werte: result[18]=32796, result[19]=32796, result[20]=32668, result[21]=32668. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

__smac16_q1_7: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:61

Deklarationshinweise im Original
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

Berechnet eine vorzeichenbehaftete Multiplikation.

Syntax

c
u16 __smul16x8(u16 a, u8 b);

Parameter

a

Vorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.

b

Vorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.

Rückgabewert

Gibt die unteren 16 Bits von a*b zurück.

Bedingungen und Grenzen

Auf dem FC verwenden die q8_8/q1_7-Funktionen rohe Ganzzahlprodukte ohne Festkomma-Rechtsshift. Q1.7-Funktionen sind Aliase der entsprechenden vorzeichenbehafteten Operationen. Der aufrufende Code verwaltet die Einheiten; vorzeichenbehaftete Ergebnisse werden als u16-Bitmuster zurückgegeben.

Anwendungsbeispiel

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);

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.

Der Ausschnitt speichert folgende 16-Bit-Werte: result[4]=32832, result[5]=32832, result[6]=32704, result[7]=32704. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

__smul16x8: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:53

Deklarationshinweise im Original
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

Berechnet eine vorzeichenbehaftete Multiplikation.

Syntax

c
u16 __smul16x8_q1_7(u16 a, u8 b);

Parameter

a

Vorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.

b

Vorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.

Rückgabewert

Gibt die unteren 16 Bits von a*b zurück.

Bedingungen und Grenzen

Auf dem FC verwenden die q8_8/q1_7-Funktionen rohe Ganzzahlprodukte ohne Festkomma-Rechtsshift. Q1.7-Funktionen sind Aliase der entsprechenden vorzeichenbehafteten Operationen. Der aufrufende Code verwaltet die Einheiten; vorzeichenbehaftete Ergebnisse werden als u16-Bitmuster zurückgegeben.

Anwendungsbeispiel

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);

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.

Der Ausschnitt speichert folgende 16-Bit-Werte: result[14]=32832, result[15]=32832, result[16]=32704, result[17]=32704. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

__smul16x8_q1_7: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:59

Deklarationshinweise im Original
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

Wartet auf einen neuen Sprite-null-Treffer und schreibt Scroll-X/Y für eine horizontale Teilung: oben ausgerichtet, unten verschoben.

Syntax

c
void __split_scroll_sprite0(u8 x, u8 y);

Parameter

x

Horizontaler Scrollwert 0..255, keine Objekt-Bildschirm-X-Koordinate. Das Beispiel nutzt 32, um den unteren Teil desselben Hintergrunds 32 Pixel nach links zu verschieben.

y

Vorübergehender vertikaler Scrollwert 0..255; üblicher sichtbarer Bereich 0..239. Die Hilfe schreibt $2005 und ändert daher während der Darstellung die laufende Vertikalposition nicht sofort. Das horizontale Teilungsbeispiel übergibt null.

Rückgabewert

Kein Rückgabewert.

Bedingungen und Grenzen

Überlagern Sie ein deckendes Pixel von OAM-Sprite 0 mit einem deckenden Hintergrundpixel bei aktivierten Ebenen. Die Priorität hinter dem Hintergrund erlaubt weiterhin einen Treffer. Die linken acht Pixel erfordern die jeweiligen Ebenenfreigaben; Überlappung bei X=255 löst keinen Treffer aus.

Fragt PPUSTATUS ($2002), Bit 6 ab, bis es gelöscht und danach gesetzt ist. Bei bereits erfolgtem Treffer wartet der Aufruf auf einen neuen im nächsten Frame. Lesen quittiert das Flag nicht; die PPU löscht es während Pre-Render.

Keine Zeitbegrenzung oder Fehlerrückgabe. Verstecktes Sprite 0, transparenter Hintergrund, deaktivierte Darstellung oder sonst fehlende Überlappung lassen den Aufruf blockieren. Stellen Sie vorher einen Treffer sicher. CPU-IRQ-Freigabe ist nicht nötig, NMI wird nicht aktiviert. Das Beispiel aktiviert NMI für seine separate Framewartefunktion.

Lesen von $2002 beeinflusst auch das gemeinsame PPU-Adress-/Scrollschreiblatch und das VBlank-Flag. Fügen Sie diesen Aufruf nicht zwischen die beiden Schreibzugriffe einer $2005-/$2006-Operation ein. Nutzen Sie Hauptcode, keinen NMI-/IRQ-Handler.

Nach dem Treffer wird $2002 gelesen und X, dann Y nach $2005 geschrieben. Fein-X wirkt sofort, Grob-X beim nächsten horizontalen Nachladen. Y wirkt normalerweise beim nächsten Pre-Render-Nachladen, sofern kein anderer Scrollzugriff es vorher ersetzt. Dies ist keine sofortige beliebige X/Y-Rasterneupositionierung.

Polling und Befehlsausführung verzögern die Schreibzugriffe. KUROSAKI rendert zeilenweise; die Aufnahmen belegen daher keine punktgenaue Hardwaregrenze. Das Beispiel legt den Wechsel in das weiße Band Y=96..103, abseits der blauen und roten Balken.

Der gemeinsame Header fc_sprite0_scene.h bereitet eigene blaue/rote Balken und ein weißes horizontales Band vor. Sprite 0 liegt bei (16,96) hinter dem Hintergrund; sein rohes OAM-Y ist 95, da Anzeige-Y=OAM-Y+1. Jeder Frame setzt während VBlank den oberen Scroll auf null zurück und wartet dann auf den Treffer.

NESdev: PPU-Scrolling

NESdev: PPU-Referenz für Programmierer

Anwendungsbeispiel

c
void main(void) {
    s0_prepare();
    while (1) {
        s0_frame();
        __split_scroll_sprite0(32,0);
        s0_completed++;
    }
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Über dem weißen Band liegt Blau bei X=64..95 und Rot bei X=160..191. Darunter ab Y=104 liegt Blau bei X=32..63, Rot bei X=128..159; nur der untere Bereich wandert 32 Pixel nach links. Das Band liegt bei Y=96..103, die Balken enden bei Y=207. Geprüft werden ein Abschluss pro Frame sowie Position und Farbe jedes Bildpixels.

__split_scroll_sprite0: SPLIT-DEFAULT — Über dem weißen Band liegt Blau bei X=64..95 und Rot bei X=160..191. Darunter ab Y=104 liegt Blau bei X=32..63, Rot bei X=128..159; nur der untere Bereich wandert 32 Pixel nach links. Das Band liegt bei Y=96..103, die Balken enden bei Y=207. Geprüft werden ein Abschluss pro Frame sowie Position und Farbe jedes Bildpixels.
SPLIT-DEFAULT — Über dem weißen Band liegt Blau bei X=64..95 und Rot bei X=160..191. Darunter ab Y=104 liegt Blau bei X=32..63, Rot bei X=128..159; nur der untere Bereich wandert 32 Pixel nach links. Das Band liegt bei Y=96..103, die Balken enden bei Y=207. Geprüft werden ein Abschluss pro Frame sowie Position und Farbe jedes Bildpixels.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:336

Deklarationshinweise im Original
Wait for a new sprite-zero hit, reset the PPU latch and write X/Y scroll; requires a visible hit setup.
__sprite0_wait_hit

Wartet auf neue Sprite-null-/Hintergrundüberlappung und synchronisiert Hauptarbeit auf einen bekannten Bildschirmbereich, ohne Scrollwerte zu ändern.

Syntax

c
void __sprite0_wait_hit(void);

Rückgabewert

Kein Rückgabewert.

Bedingungen und Grenzen

Überlagern Sie ein deckendes Pixel von OAM-Sprite 0 mit einem deckenden Hintergrundpixel bei aktivierten Ebenen. Die Priorität hinter dem Hintergrund erlaubt weiterhin einen Treffer. Die linken acht Pixel erfordern die jeweiligen Ebenenfreigaben; Überlappung bei X=255 löst keinen Treffer aus.

Fragt PPUSTATUS ($2002), Bit 6 ab, bis es gelöscht und danach gesetzt ist. Bei bereits erfolgtem Treffer wartet der Aufruf auf einen neuen im nächsten Frame. Lesen quittiert das Flag nicht; die PPU löscht es während Pre-Render.

Keine Zeitbegrenzung oder Fehlerrückgabe. Verstecktes Sprite 0, transparenter Hintergrund, deaktivierte Darstellung oder sonst fehlende Überlappung lassen den Aufruf blockieren. Stellen Sie vorher einen Treffer sicher. CPU-IRQ-Freigabe ist nicht nötig, NMI wird nicht aktiviert. Das Beispiel aktiviert NMI für seine separate Framewartefunktion.

Lesen von $2002 beeinflusst auch das gemeinsame PPU-Adress-/Scrollschreiblatch und das VBlank-Flag. Fügen Sie diesen Aufruf nicht zwischen die beiden Schreibzugriffe einer $2005-/$2006-Operation ein. Nutzen Sie Hauptcode, keinen NMI-/IRQ-Handler.

Polling und Befehlsausführung verzögern die Schreibzugriffe. KUROSAKI rendert zeilenweise; die Aufnahmen belegen daher keine punktgenaue Hardwaregrenze. Das Beispiel legt den Wechsel in das weiße Band Y=96..103, abseits der blauen und roten Balken.

Der gemeinsame Header fc_sprite0_scene.h bereitet eigene blaue/rote Balken und ein weißes horizontales Band vor. Sprite 0 liegt bei (16,96) hinter dem Hintergrund; sein rohes OAM-Y ist 95, da Anzeige-Y=OAM-Y+1. Jeder Frame setzt während VBlank den oberen Scroll auf null zurück und wartet dann auf den Treffer.

NESdev: PPU-Scrolling

NESdev: PPU-Referenz für Programmierer

Anwendungsbeispiel

c
void main(void) {
    s0_prepare();
    while (1) {
        s0_frame();
        __sprite0_wait_hit();
        s0_completed++;
    }
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Der blaue Balken belegt X=64..95, der rote X=160..191, ober- und unterhalb des weißen Bands ausgerichtet. Warten allein verändert den Scroll nicht. Das ausgeführte Beispiel beendet 116 Wartevorgänge nach 120 Frames und 117 nach 121 und bestätigt so den Fortschritt.

__sprite0_wait_hit: WAIT-DEFAULT — Der blaue Balken belegt X=64..95, der rote X=160..191, ober- und unterhalb des weißen Bands ausgerichtet. Warten allein verändert den Scroll nicht. Das ausgeführte Beispiel beendet 116 Wartevorgänge nach 120 Frames und 117 nach 121 und bestätigt so den Fortschritt.
WAIT-DEFAULT — Der blaue Balken belegt X=64..95, der rote X=160..191, ober- und unterhalb des weißen Bands ausgerichtet. Warten allein verändert den Scroll nicht. Das ausgeführte Beispiel beendet 116 Wartevorgänge nach 120 Frames und 117 nach 121 und bestätigt so den Fortschritt.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:334

Deklarationshinweise im Original
Wait for the current hit flag to clear, then for a new hit. No timeout is provided.
__sprite_attr

Ersetzt das gesamte Attributbyte eines Sprites im Schattenpuffer. Damit lassen sich Palette, Priorität gegenüber dem Hintergrund sowie horizontale und vertikale Spiegelung ändern, ohne Kachel oder Position zu verändern.

Syntax

c
void __sprite_attr(u8 index, u8 attr);

Parameter

index

Sprite-Index von 0 bis 63, kein Byte-Offset. Intern wird er mit vier multipliziert und als 8-Bit-Position gespeichert. Werte außerhalb des Bereichs werden modulo 64 umgebrochen und können einen anderen Eintrag überschreiben. Eine Belegungsprüfung findet nicht statt.

attr

Vollständiges Attributbyte: Bits 0–1 wählen Palette 0–3; Bit 5 legt undurchsichtige Sprite-Pixel hinter undurchsichtige Hintergrundpixel; Bits 6 und 7 spiegeln horizontal beziehungsweise vertikal. Bits 2–4 müssen null bleiben. Kombinieren Sie Flags mit bitweisem ODER; das Byte ersetzt sämtliche bisherigen Attribute.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Diese Operationen ändern den CPU-RAM bei 0x0200–0x02FF, nicht sofort das Hardware-OAM. Übertragen Sie die fertige Seite mit __oam_dma. Belegungsflags, Koordinatenfelder und Zähler der Sprite-Bibliothek werden nicht mitgeführt. Interrupt-Routinen dürfen eine noch unvollständige Seite weder ändern noch übertragen.

Anwendungsbeispiel

c
__sprite_attr(3,0xC0); // Flip the arrow horizontally and vertically.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Beispiel zeigt jede Operation in einer beschrifteten Zeile. SET und MOVE zeigen 8×8-Pfeile nach rechts bei (144,16) und (144,32), TILE ein grünes Quadrat bei (144,48) und ATTR den in beiden Achsen gespiegelten Pfeil bei (144,64). HIDE bei (144,80) und CLEAR bei (144,112) müssen leer bleiben. META zeigt zwei Pfeile bei (136,96) und (144,96), den zweiten horizontal gespiegelt. NEXT 07 bezeichnet die nächste Position nach den Einträgen 5 und 6. Die Ränder sind rot, der Schaft grün und die Spitzendetails blau. Geprüft werden 8.704 Pixel je Bild und die übertragenen OAM-Bytes; echte Hardware wurde nicht getestet.

TRANSFER zeigt einen Pfeil bei (144,128). Dieses Programm überträgt Seite 02 direkt mit __oam_dma, einschließlich aller durch die einzelnen Sprite-Operationen vorbereiteten Felder.

__sprite_attr: PAGE02
PAGE02

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:198

Deklarationshinweise im Original
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

Blendet einen Sprite im Schattenpuffer aus, indem Y auf 0xF0 gesetzt wird. X, Kachel und Attribute bleiben zur Wiederverwendung erhalten. Die Funktion gibt keinen Platz frei und ändert den Belegungszähler der Sprite-Bibliothek nicht.

Syntax

c
void __sprite_hide(u8 index);

Parameter

index

Sprite-Index von 0 bis 63, kein Byte-Offset. Intern wird er mit vier multipliziert und als 8-Bit-Position gespeichert. Werte außerhalb des Bereichs werden modulo 64 umgebrochen und können einen anderen Eintrag überschreiben. Eine Belegungsprüfung findet nicht statt.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Diese Operationen ändern den CPU-RAM bei 0x0200–0x02FF, nicht sofort das Hardware-OAM. Übertragen Sie die fertige Seite mit __oam_dma. Belegungsflags, Koordinatenfelder und Zähler der Sprite-Bibliothek werden nicht mitgeführt. Interrupt-Routinen dürfen eine noch unvollständige Seite weder ändern noch übertragen.

Anwendungsbeispiel

c
__sprite_hide(4); // The HIDE row remains empty after DMA.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Beispiel zeigt jede Operation in einer beschrifteten Zeile. SET und MOVE zeigen 8×8-Pfeile nach rechts bei (144,16) und (144,32), TILE ein grünes Quadrat bei (144,48) und ATTR den in beiden Achsen gespiegelten Pfeil bei (144,64). HIDE bei (144,80) und CLEAR bei (144,112) müssen leer bleiben. META zeigt zwei Pfeile bei (136,96) und (144,96), den zweiten horizontal gespiegelt. NEXT 07 bezeichnet die nächste Position nach den Einträgen 5 und 6. Die Ränder sind rot, der Schaft grün und die Spitzendetails blau. Geprüft werden 8.704 Pixel je Bild und die übertragenen OAM-Bytes; echte Hardware wurde nicht getestet.

TRANSFER zeigt einen Pfeil bei (144,128). Dieses Programm überträgt Seite 02 direkt mit __oam_dma, einschließlich aller durch die einzelnen Sprite-Operationen vorbereiteten Felder.

__sprite_hide: PAGE02
PAGE02

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:200

Deklarationshinweise im Original
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

Ändert nur die X- und Y-Bytes eines Sprites im Schattenpuffer. Kachelnummer und Attribute bleiben erhalten; dieselbe Grafik wird ohne Änderung der Palette oder Spiegelung verschoben.

Syntax

c
void __sprite_move(u8 index, u8 x, u8 y);

Parameter

index

Sprite-Index von 0 bis 63, kein Byte-Offset. Intern wird er mit vier multipliziert und als 8-Bit-Position gespeichert. Werte außerhalb des Bereichs werden modulo 64 umgebrochen und können einen anderen Eintrag überschreiben. Eine Belegungsprüfung findet nicht statt.

x

Horizontale Position in Pixeln als vorzeichenloses Byte; keine Tile-Koordinate.

y

Y-Byte des OAM. Für eine Oberkante unterhalb der Zeile null wird ihre Position minus eins übergeben; 239..255 liegen außerhalb der 240 sichtbaren Zeilen.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Diese Operationen ändern den CPU-RAM bei 0x0200–0x02FF, nicht sofort das Hardware-OAM. Übertragen Sie die fertige Seite mit __oam_dma. Belegungsflags, Koordinatenfelder und Zähler der Sprite-Bibliothek werden nicht mitgeführt. Interrupt-Routinen dürfen eine noch unvollständige Seite weder ändern noch übertragen.

Anwendungsbeispiel

c
__sprite_move(1,144,31); // Move to (144,32), retaining tile 128 and attributes 0.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Beispiel zeigt jede Operation in einer beschrifteten Zeile. SET und MOVE zeigen 8×8-Pfeile nach rechts bei (144,16) und (144,32), TILE ein grünes Quadrat bei (144,48) und ATTR den in beiden Achsen gespiegelten Pfeil bei (144,64). HIDE bei (144,80) und CLEAR bei (144,112) müssen leer bleiben. META zeigt zwei Pfeile bei (136,96) und (144,96), den zweiten horizontal gespiegelt. NEXT 07 bezeichnet die nächste Position nach den Einträgen 5 und 6. Die Ränder sind rot, der Schaft grün und die Spitzendetails blau. Geprüft werden 8.704 Pixel je Bild und die übertragenen OAM-Bytes; echte Hardware wurde nicht getestet.

TRANSFER zeigt einen Pfeil bei (144,128). Dieses Programm überträgt Seite 02 direkt mit __oam_dma, einschließlich aller durch die einzelnen Sprite-Operationen vorbereiteten Felder.

__sprite_move: PAGE02
PAGE02

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:194

Deklarationshinweise im Original
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

Schreibt alle vier Felder eines Sprites in den Schattenpuffer der Seite 02. Die Argumente lauten Index, X, Y, Kachel und Attribute; gespeichert wird in der Reihenfolge Y, Kachel, Attribute, X.

Syntax

c
void __sprite_set(u8 index, u8 x, u8 y, u8 tile, u8 attr);

Parameter

index

Sprite-Index von 0 bis 63, kein Byte-Offset. Intern wird er mit vier multipliziert und als 8-Bit-Position gespeichert. Werte außerhalb des Bereichs werden modulo 64 umgebrochen und können einen anderen Eintrag überschreiben. Eine Belegungsprüfung findet nicht statt.

x

Horizontale Position in Pixeln als vorzeichenloses Byte; keine Tile-Koordinate.

y

Y-Byte des OAM. Für eine Oberkante unterhalb der Zeile null wird ihre Position minus eins übergeben; 239..255 liegen außerhalb der 240 sichtbaren Zeilen.

tile

8-Bit-Tile-Nummer. Die Grafik muss vorher geladen werden. Die Hardware-Regeln für 8×16-Sprites gelten weiterhin; die Nummer wird nicht für Tile-Paare angepasst.

attr

Vollständiges Attributbyte: Bits 0–1 wählen Palette 0–3; Bit 5 legt undurchsichtige Sprite-Pixel hinter undurchsichtige Hintergrundpixel; Bits 6 und 7 spiegeln horizontal beziehungsweise vertikal. Bits 2–4 müssen null bleiben. Kombinieren Sie Flags mit bitweisem ODER; das Byte ersetzt sämtliche bisherigen Attribute.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Diese Operationen ändern den CPU-RAM bei 0x0200–0x02FF, nicht sofort das Hardware-OAM. Übertragen Sie die fertige Seite mit __oam_dma. Belegungsflags, Koordinatenfelder und Zähler der Sprite-Bibliothek werden nicht mitgeführt. Interrupt-Routinen dürfen eine noch unvollständige Seite weder ändern noch übertragen.

Anwendungsbeispiel

c
__sprite_set(0,144,15,128,0); // An 8x8 arrow at visible position (144,16).

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Beispiel zeigt jede Operation in einer beschrifteten Zeile. SET und MOVE zeigen 8×8-Pfeile nach rechts bei (144,16) und (144,32), TILE ein grünes Quadrat bei (144,48) und ATTR den in beiden Achsen gespiegelten Pfeil bei (144,64). HIDE bei (144,80) und CLEAR bei (144,112) müssen leer bleiben. META zeigt zwei Pfeile bei (136,96) und (144,96), den zweiten horizontal gespiegelt. NEXT 07 bezeichnet die nächste Position nach den Einträgen 5 und 6. Die Ränder sind rot, der Schaft grün und die Spitzendetails blau. Geprüft werden 8.704 Pixel je Bild und die übertragenen OAM-Bytes; echte Hardware wurde nicht getestet.

TRANSFER zeigt einen Pfeil bei (144,128). Dieses Programm überträgt Seite 02 direkt mit __oam_dma, einschließlich aller durch die einzelnen Sprite-Operationen vorbereiteten Felder.

__sprite_set: PAGE02
PAGE02

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:192

Deklarationshinweise im Original
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

Ersetzt nur die Kachelnummer eines Sprites im Schattenpuffer. Position und Attribute bleiben erhalten. Dabei wird ein vorhandenes Muster ausgewählt; es werden keine Grafikdaten in den CHR-Speicher übertragen.

Syntax

c
void __sprite_tile(u8 index, u8 tile);

Parameter

index

Sprite-Index von 0 bis 63, kein Byte-Offset. Intern wird er mit vier multipliziert und als 8-Bit-Position gespeichert. Werte außerhalb des Bereichs werden modulo 64 umgebrochen und können einen anderen Eintrag überschreiben. Eine Belegungsprüfung findet nicht statt.

tile

8-Bit-Tile-Nummer. Die Grafik muss vorher geladen werden. Die Hardware-Regeln für 8×16-Sprites gelten weiterhin; die Nummer wird nicht für Tile-Paare angepasst.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Diese Operationen ändern den CPU-RAM bei 0x0200–0x02FF, nicht sofort das Hardware-OAM. Übertragen Sie die fertige Seite mit __oam_dma. Belegungsflags, Koordinatenfelder und Zähler der Sprite-Bibliothek werden nicht mitgeführt. Interrupt-Routinen dürfen eine noch unvollständige Seite weder ändern noch übertragen.

Anwendungsbeispiel

c
__sprite_tile(2,129); // Select the green square at (144,48).

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Beispiel zeigt jede Operation in einer beschrifteten Zeile. SET und MOVE zeigen 8×8-Pfeile nach rechts bei (144,16) und (144,32), TILE ein grünes Quadrat bei (144,48) und ATTR den in beiden Achsen gespiegelten Pfeil bei (144,64). HIDE bei (144,80) und CLEAR bei (144,112) müssen leer bleiben. META zeigt zwei Pfeile bei (136,96) und (144,96), den zweiten horizontal gespiegelt. NEXT 07 bezeichnet die nächste Position nach den Einträgen 5 und 6. Die Ränder sind rot, der Schaft grün und die Spitzendetails blau. Geprüft werden 8.704 Pixel je Bild und die übertragenen OAM-Bytes; echte Hardware wurde nicht getestet.

TRANSFER zeigt einen Pfeil bei (144,128). Dieses Programm überträgt Seite 02 direkt mit __oam_dma, einschließlich aller durch die einzelnen Sprite-Operationen vorbereiteten Felder.

__sprite_tile: PAGE02
PAGE02

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:196

Deklarationshinweise im Original
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

Setzt ppu_addr und schreibt anschließend dasselbe Byte value genau len-mal nach PPUDATA. Das direkte Füllen ist bei der Rückkehr abgeschlossen. Kachelmaße, Attribute und Rechteckgrenzen werden dabei nicht ausgewertet.

Syntax

c
void __vram_fill(u16 ppu_addr, u8 value, u8 len);

Parameter

ppu_addr

PPU-Byteadresse (0x0000–0x3FFF), kein CPU-Zeiger. Das Ziel muss beschreibbar sein: Musteränderungen benötigen CHR-RAM statt CHR-ROM. PPU-Adressabbildung und Spiegelung gelten weiterhin. Wählen Sie für fortlaufendes Kopieren und Füllen die Schrittweite 1 in PPUCTRL. Führen Sie Befehle zu einem sicheren PPU-Zeitpunkt aus und stellen Sie danach den Scrollzustand her; der Ausführer schreibt PPUADDR und PPUDATA, ohne Scrolleinstellungen wiederherzustellen.

value

Ein wiederholtes Byte von 0 bis 255. Seine sichtbare Wirkung hängt davon ab, ob das Ziel Tile-Indizes, Pixelebenen oder eine andere Bytestruktur enthält.

len

Byteanzahl 0..255. Auch null setzt die PPU-Adresse und den Latch zurück, schreibt aber keine Daten. Zielkapazität und Grenzen werden nicht geprüft; Bankwechsel und verzögerte Übertragung sind nicht enthalten.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Direkter PPU-Zugriff erfordert ausgeschaltete Bildausgabe oder einen sicheren VBlank-Zeitraum, in den die gesamte Operation passt. Diese Funktionen warten nicht, legen keine Aufträge an, sperren NMI nicht und stellen den Bildlauf nicht wieder her. Verhindern Sie, dass Interrupts während des Aufrufs den gemeinsamen Latch oder die Adresse verändern. PPUCTRL-Bit 2 wählt eine Zieladressschrittweite von 1 oder 32 pro Byte. Spiegelung und Adressumlauf übernimmt die PPU; CHR-ROM ist nicht beschreibbar. Stellen Sie nach dem Zugriff vor der Bildausgabe den gewünschten Bildlauf ein.

NESdev: PPU-Register

NESdev: PPU-Scrolling

Anwendungsbeispiel

c
__vram_fill(0x2060,2,32); // A second blue row of 4-pixel-wide left halves.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.

Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).

Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

__vram_fill: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:122

Deklarationshinweise im Original
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

Setzt den PPU-Latch zurück, setzt ppu_addr und kopiert synchron len aufeinanderfolgende Quellbytes nach PPUDATA. Die Anzahl ist acht Bit breit; der Compiler verwendet indizierte Quellzugriffe. Kein Zeiger wird für eine spätere Ausführung gespeichert. Änderungen der Quelle nach der Rückkehr verändern VRAM nicht.

Syntax

c
void __vram_write(u16 ppu_addr, const u8* src, u8 len);

Parameter

ppu_addr

PPU-Byteadresse (0x0000–0x3FFF), kein CPU-Zeiger. Das Ziel muss beschreibbar sein: Musteränderungen benötigen CHR-RAM statt CHR-ROM. PPU-Adressabbildung und Spiegelung gelten weiterhin. Wählen Sie für fortlaufendes Kopieren und Füllen die Schrittweite 1 in PPUCTRL. Führen Sie Befehle zu einem sicheren PPU-Zeitpunkt aus und stellen Sie danach den Scrollzustand her; der Ausführer schreibt PPUADDR und PPUDATA, ohne Scrolleinstellungen wiederherzustellen.

src

CPU-Zeiger auf mindestens die angeforderte Anzahl lesbarer Bytes. Halten Sie die Quelle bis zur Rückkehr dieser synchronen Übertragung eingeblendet und unverändert. Es gibt keine Bankumschaltung, Speicherung des Zeigers in einer Warteschlange oder Prüfung der Quelllänge. Bei Anzahl null wird der Zeiger nicht dereferenziert.

len

Byteanzahl 0..255. Auch null setzt die PPU-Adresse und den Latch zurück, schreibt aber keine Daten. Zielkapazität und Grenzen werden nicht geprüft; Bankwechsel und verzögerte Übertragung sind nicht enthalten.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Direkter PPU-Zugriff erfordert ausgeschaltete Bildausgabe oder einen sicheren VBlank-Zeitraum, in den die gesamte Operation passt. Diese Funktionen warten nicht, legen keine Aufträge an, sperren NMI nicht und stellen den Bildlauf nicht wieder her. Verhindern Sie, dass Interrupts während des Aufrufs den gemeinsamen Latch oder die Adresse verändern. PPUCTRL-Bit 2 wählt eine Zieladressschrittweite von 1 oder 32 pro Byte. Spiegelung und Adressumlauf übernimmt die PPU; CHR-ROM ist nicht beschreibbar. Stellen Sie nach dem Zugriff vor der Bildausgabe den gewünschten Bildlauf ein.

NESdev: PPU-Register

NESdev: PPU-Scrolling

Anwendungsbeispiel

c
__vram_write(0x2040,pattern,32); // Blue row: solid tile, left half, triangle.
pattern[0]=3; // The completed transfer keeps its first solid tile.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.

Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).

Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

__vram_write: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:120

Deklarationshinweise im Original
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

Gibt die Gesamtkapazität des Befehlspuffers unabhängig von seiner aktuellen Belegung zurück.

Syntax

c
u8 __vramq_capacity(void);

Rückgabewert

Ein vorzeichenloses Byte in den hier beschriebenen plattformspezifischen Einheiten. Die Abfrage verändert oder verarbeitet die Warteschlange nicht.

Bedingungen und Grenzen

FC zählt codierte Befehlsbytes in einem festen intrinsischen 128-Byte-Puffer. Ein Tile-Schreibbefehl braucht 4 Bytes, ein Fülleintrag 5 und eine Zeigerkopie 6 einschließlich Metadaten. NMI kann eine bereite Warteschlange verarbeiten; prüfen und befüllen Sie sie vor der Freigabe und koordinieren Sie Erzeuger und Verbraucher.

Diese Intrinsics verwenden denselben Puffer wie lib/vram.c. Die Funktionen nes_vram_queue_* in runtime.c verwalten eine getrennte Warteschlange mit anderem Eintragsformat. Leeren oder Abfragen der einen betrifft die andere nicht.

Koordinieren Sie Aufbau und Verarbeitung der Warteschlange. Fügen Sie während ihrer Interrupt-Ausführung nichts hinzu, ändern Sie die Verwaltung nicht direkt und überschreiben Sie gespeicherte Quelldaten nicht versehentlich. Diese APIs bieten weder Sperren noch Doppelpufferung. Die Kapazität betrifft den Befehlspuffer, nicht freie Bytes im Hardware-VRAM.

Anwendungsbeispiel

c
capacity=__vramq_capacity();
if (capacity!=128) failures++;

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Kapazität, gespeicherte Zeiger, getrennte Rücksetzfunktionen sowie direkte und NMI-Ausführung. Erwartet werden CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 und FAILED CHECKS 000. Sechs Einträge belegen 30 Bytes und führen 512 Byte-Schreibzugriffe aus. OVERFLOW SEEN speichert die frühere absichtliche Ablehnung; es bedeutet nicht, dass am Ende noch ein Überlauf gemeldet wird.

Die blaue Kopie beginnt bei Pixel (0,16), die grüne Füllung bei (0,88). Jeder Bereich umfasst sieben vollständige Kachelzeilen mit 256 Pixeln Breite und 248 Pixel der achten: insgesamt 255 Kacheln. Das letzte Feld von 8 × 8 bleibt schwarz. Die Kopie wiederholt volle, links halbvolle und dreieckige Muster; das erste ist ein Dreieck, weil die Quelle nach dem Einreihen geändert wird. Die Füllung bleibt trotz geänderter Aufrufervariable voll. Form und Farbe werden geprüft.

Bei y=160 werden ein volles rotes Quadrat bei x=16, eine rote linke Halbkachel bei x=224 und ein volles rotes Quadrat bei x=240 erwartet. Das erste übersteht Einträge mit Länge null, das mittlere belegt die NMI-Verarbeitung, das letzte einen einzelnen Put. Das verworfene Feld bei (248,160) bleibt schwarz. Lange Transfers laufen bei abgeschaltetem Rendering und NMI; nur der letzte kleine Schreibzugriff nutzt NMI. Dies ist ein NROM-Emulatortest, kein Test auf echter Hardware.

__vramq_capacity: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:164

Deklarationshinweise im Original
Return total encoded command-buffer capacity; this is independent of PPU VRAM capacity.
__vramq_clear

Verwirft alle ausstehenden Einträge und setzt Länge sowie Bereit-Flag auf 0. Schreibt nicht ins VRAM und löscht die Überlaufmeldung nicht. Rufen Sie dafür gesondert __vramq_clear_overflow auf.

Syntax

c
void __vramq_clear(void);

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

FC zählt codierte Befehlsbytes in einem festen intrinsischen 128-Byte-Puffer. Ein Tile-Schreibbefehl braucht 4 Bytes, ein Fülleintrag 5 und eine Zeigerkopie 6 einschließlich Metadaten. NMI kann eine bereite Warteschlange verarbeiten; prüfen und befüllen Sie sie vor der Freigabe und koordinieren Sie Erzeuger und Verbraucher.

Diese Intrinsics verwenden denselben Puffer wie lib/vram.c. Die Funktionen nes_vram_queue_* in runtime.c verwalten eine getrennte Warteschlange mit anderem Eintragsformat. Leeren oder Abfragen der einen betrifft die andere nicht.

Koordinieren Sie Aufbau und Verarbeitung der Warteschlange. Fügen Sie während ihrer Interrupt-Ausführung nichts hinzu, ändern Sie die Verwaltung nicht direkt und überschreiben Sie gespeicherte Quelldaten nicht versehentlich. Diese APIs bieten weder Sperren noch Doppelpufferung. Die Kapazität betrifft den Befehlspuffer, nicht freie Bytes im Hardware-VRAM.

Anwendungsbeispiel

c
__vramq_clear();
if (__vramq_len()!=0 || __vramq_overflow()!=1) failures++;
// Clearing queued work deliberately leaves the overflow latch set.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Kapazität, gespeicherte Zeiger, getrennte Rücksetzfunktionen sowie direkte und NMI-Ausführung. Erwartet werden CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 und FAILED CHECKS 000. Sechs Einträge belegen 30 Bytes und führen 512 Byte-Schreibzugriffe aus. OVERFLOW SEEN speichert die frühere absichtliche Ablehnung; es bedeutet nicht, dass am Ende noch ein Überlauf gemeldet wird.

Die blaue Kopie beginnt bei Pixel (0,16), die grüne Füllung bei (0,88). Jeder Bereich umfasst sieben vollständige Kachelzeilen mit 256 Pixeln Breite und 248 Pixel der achten: insgesamt 255 Kacheln. Das letzte Feld von 8 × 8 bleibt schwarz. Die Kopie wiederholt volle, links halbvolle und dreieckige Muster; das erste ist ein Dreieck, weil die Quelle nach dem Einreihen geändert wird. Die Füllung bleibt trotz geänderter Aufrufervariable voll. Form und Farbe werden geprüft.

Bei y=160 werden ein volles rotes Quadrat bei x=16, eine rote linke Halbkachel bei x=224 und ein volles rotes Quadrat bei x=240 erwartet. Das erste übersteht Einträge mit Länge null, das mittlere belegt die NMI-Verarbeitung, das letzte einen einzelnen Put. Das verworfene Feld bei (248,160) bleibt schwarz. Lange Transfers laufen bei abgeschaltetem Rendering und NMI; nur der letzte kleine Schreibzugriff nutzt NMI. Dies ist ein NROM-Emulatortest, kein Test auf echter Hardware.

__vramq_clear: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:146

Deklarationshinweise im Original
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

Setzt nur die Überlaufmeldung auf 0. Einträge, Länge und Bereitschaft bleiben unverändert. Bestätigt den Platzmangel, ohne den abgelehnten Eintrag erneut zu versuchen oder frühere erfolgreiche Einträge zurückzunehmen.

Syntax

c
void __vramq_clear_overflow(void);

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

FC zählt codierte Befehlsbytes in einem festen intrinsischen 128-Byte-Puffer. Ein Tile-Schreibbefehl braucht 4 Bytes, ein Fülleintrag 5 und eine Zeigerkopie 6 einschließlich Metadaten. NMI kann eine bereite Warteschlange verarbeiten; prüfen und befüllen Sie sie vor der Freigabe und koordinieren Sie Erzeuger und Verbraucher.

Diese Intrinsics verwenden denselben Puffer wie lib/vram.c. Die Funktionen nes_vram_queue_* in runtime.c verwalten eine getrennte Warteschlange mit anderem Eintragsformat. Leeren oder Abfragen der einen betrifft die andere nicht.

Koordinieren Sie Aufbau und Verarbeitung der Warteschlange. Fügen Sie während ihrer Interrupt-Ausführung nichts hinzu, ändern Sie die Verwaltung nicht direkt und überschreiben Sie gespeicherte Quelldaten nicht versehentlich. Diese APIs bieten weder Sperren noch Doppelpufferung. Die Kapazität betrifft den Befehlspuffer, nicht freie Bytes im Hardware-VRAM.

Anwendungsbeispiel

c
__vramq_clear_overflow();
if (__vramq_overflow()!=0 || __vramq_len()!=4) failures++;
// Resetting the latch deliberately keeps this pending put record.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Kapazität, gespeicherte Zeiger, getrennte Rücksetzfunktionen sowie direkte und NMI-Ausführung. Erwartet werden CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 und FAILED CHECKS 000. Sechs Einträge belegen 30 Bytes und führen 512 Byte-Schreibzugriffe aus. OVERFLOW SEEN speichert die frühere absichtliche Ablehnung; es bedeutet nicht, dass am Ende noch ein Überlauf gemeldet wird.

Die blaue Kopie beginnt bei Pixel (0,16), die grüne Füllung bei (0,88). Jeder Bereich umfasst sieben vollständige Kachelzeilen mit 256 Pixeln Breite und 248 Pixel der achten: insgesamt 255 Kacheln. Das letzte Feld von 8 × 8 bleibt schwarz. Die Kopie wiederholt volle, links halbvolle und dreieckige Muster; das erste ist ein Dreieck, weil die Quelle nach dem Einreihen geändert wird. Die Füllung bleibt trotz geänderter Aufrufervariable voll. Form und Farbe werden geprüft.

Bei y=160 werden ein volles rotes Quadrat bei x=16, eine rote linke Halbkachel bei x=224 und ein volles rotes Quadrat bei x=240 erwartet. Das erste übersteht Einträge mit Länge null, das mittlere belegt die NMI-Verarbeitung, das letzte einen einzelnen Put. Das verworfene Feld bei (248,160) bleibt schwarz. Lange Transfers laufen bei abgeschaltetem Rendering und NMI; nur der letzte kleine Schreibzugriff nutzt NMI. Dies ist ein NROM-Emulatortest, kein Test auf echter Hardware.

__vramq_clear_overflow: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:162

Deklarationshinweise im Original
Clear the overflow latch explicitly after handling the failed submission.
__vramq_commit

Setzt das Bereit-Flag auf 1 und erlaubt damit die Verarbeitung. Kopiert keine Nutzdaten, schreibt nicht in die PPU und wartet nicht auf NMI. Halten Sie Warteschlange und gespeicherte Quellen nach der Freigabe bis zum Abschluss unverändert. Bei aktiviertem NMI kann der Standardhandler sie asynchron verarbeiten.

Syntax

c
void __vramq_commit(void);

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

FC zählt codierte Befehlsbytes in einem festen intrinsischen 128-Byte-Puffer. Ein Tile-Schreibbefehl braucht 4 Bytes, ein Fülleintrag 5 und eine Zeigerkopie 6 einschließlich Metadaten. NMI kann eine bereite Warteschlange verarbeiten; prüfen und befüllen Sie sie vor der Freigabe und koordinieren Sie Erzeuger und Verbraucher.

Diese Intrinsics verwenden denselben Puffer wie lib/vram.c. Die Funktionen nes_vram_queue_* in runtime.c verwalten eine getrennte Warteschlange mit anderem Eintragsformat. Leeren oder Abfragen der einen betrifft die andere nicht.

Koordinieren Sie Aufbau und Verarbeitung der Warteschlange. Fügen Sie während ihrer Interrupt-Ausführung nichts hinzu, ändern Sie die Verwaltung nicht direkt und überschreiben Sie gespeicherte Quelldaten nicht versehentlich. Diese APIs bieten weder Sperren noch Doppelpufferung. Die Kapazität betrifft den Befehlspuffer, nicht freie Bytes im Hardware-VRAM.

Anwendungsbeispiel

c
__vramq_commit(); // NMI is disabled here: this only marks the queue ready.
if (__vramq_len()!=30) failures++;

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Kapazität, gespeicherte Zeiger, getrennte Rücksetzfunktionen sowie direkte und NMI-Ausführung. Erwartet werden CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 und FAILED CHECKS 000. Sechs Einträge belegen 30 Bytes und führen 512 Byte-Schreibzugriffe aus. OVERFLOW SEEN speichert die frühere absichtliche Ablehnung; es bedeutet nicht, dass am Ende noch ein Überlauf gemeldet wird.

Die blaue Kopie beginnt bei Pixel (0,16), die grüne Füllung bei (0,88). Jeder Bereich umfasst sieben vollständige Kachelzeilen mit 256 Pixeln Breite und 248 Pixel der achten: insgesamt 255 Kacheln. Das letzte Feld von 8 × 8 bleibt schwarz. Die Kopie wiederholt volle, links halbvolle und dreieckige Muster; das erste ist ein Dreieck, weil die Quelle nach dem Einreihen geändert wird. Die Füllung bleibt trotz geänderter Aufrufervariable voll. Form und Farbe werden geprüft.

Bei y=160 werden ein volles rotes Quadrat bei x=16, eine rote linke Halbkachel bei x=224 und ein volles rotes Quadrat bei x=240 erwartet. Das erste übersteht Einträge mit Länge null, das mittlere belegt die NMI-Verarbeitung, das letzte einen einzelnen Put. Das verworfene Feld bei (248,160) bleibt schwarz. Lange Transfers laufen bei abgeschaltetem Rendering und NMI; nur der letzte kleine Schreibzugriff nutzt NMI. Dies ist ein NROM-Emulatortest, kein Test auf echter Hardware.

__vramq_commit: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:154

Deklarationshinweise im Original
Publish the pending records to the configured consumer. Do not mutate them while consumption can run.
__vramq_copy

Fügt einen Sechs-Byte-Eintrag an, der bei der Ausführung len Bytes von src nach ppu_addr kopiert. Gespeichert wird der Zeiger, nicht der Inhalt. Selbst eine Kopie von 255 Bytes belegt nur sechs Pufferbytes. Die Quelle wird aufsteigend gelesen; für ein fortlaufendes Ziel wählen Sie PPU-Schrittweite 1.

Syntax

c
void __vramq_copy(u16 ppu_addr, const u8* src, u8 len);

Parameter

ppu_addr

PPU-Byteadresse (0x0000–0x3FFF), kein CPU-Zeiger. Das Ziel muss beschreibbar sein: Musteränderungen benötigen CHR-RAM statt CHR-ROM. PPU-Adressabbildung und Spiegelung gelten weiterhin. Wählen Sie für fortlaufendes Kopieren und Füllen die Schrittweite 1 in PPUCTRL. Führen Sie Befehle zu einem sicheren PPU-Zeitpunkt aus und stellen Sie danach den Scrollzustand her; der Ausführer schreibt PPUADDR und PPUDATA, ohne Scrolleinstellungen wiederherzustellen.

src

Nahzeiger auf lesbare Bytes, der bis zur Ausführung gespeichert bleibt. Daten und ihre Speicher- oder ROM-Bank müssen während der gesamten Verarbeitung gültig und eingeblendet bleiben; weder Banknummer noch Kopie werden gespeichert. Nichtleere Übertragungen benötigen eine gültige Quelle. Verwenden Sie kein abgelaufenes lokales Array.

len

Byteanzahl von 0 bis 255. Ein Eintrag verarbeitet die gesamte Anzahl ohne Aufteilung in 127-Byte-Abschnitte. Auch null belegt einen Kopier- oder Fülleintrag. Bei dessen Ausführung wird die PPU-Adresse gesetzt, aber kein Byte nach PPUDATA geschrieben. 256 liegt außerhalb des Argumentbereichs.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Das Einfügen liefert keinen Erfolgswert. Passt der vollständige Eintrag nicht, wird __vramq_overflow() auf 1 gesetzt; vorhandene Einträge und Länge bleiben erhalten. Genau passender Platz wird akzeptiert. Eine frühere Überlaufmeldung verhindert keinen passenden Eintrag; behandeln oder löschen Sie sie vor der Bewertung eines neuen Stapels. Mehrere Einfügevorgänge bilden keine Transaktion.

FC zählt codierte Befehlsbytes in einem festen intrinsischen 128-Byte-Puffer. Ein Tile-Schreibbefehl braucht 4 Bytes, ein Fülleintrag 5 und eine Zeigerkopie 6 einschließlich Metadaten. NMI kann eine bereite Warteschlange verarbeiten; prüfen und befüllen Sie sie vor der Freigabe und koordinieren Sie Erzeuger und Verbraucher.

Diese Intrinsics verwenden denselben Puffer wie lib/vram.c. Die Funktionen nes_vram_queue_* in runtime.c verwalten eine getrennte Warteschlange mit anderem Eintragsformat. Leeren oder Abfragen der einen betrifft die andere nicht.

Koordinieren Sie Aufbau und Verarbeitung der Warteschlange. Fügen Sie während ihrer Interrupt-Ausführung nichts hinzu, ändern Sie die Verwaltung nicht direkt und überschreiben Sie gespeicherte Quelldaten nicht versehentlich. Diese APIs bieten weder Sperren noch Doppelpufferung. Die Kapazität betrifft den Befehlspuffer, nicht freie Bytes im Hardware-VRAM.

Anwendungsbeispiel

c
__vramq_copy(0x2040,source_bytes,255);
source_bytes[0]=3; // The executor must see this change through its retained pointer.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Kapazität, gespeicherte Zeiger, getrennte Rücksetzfunktionen sowie direkte und NMI-Ausführung. Erwartet werden CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 und FAILED CHECKS 000. Sechs Einträge belegen 30 Bytes und führen 512 Byte-Schreibzugriffe aus. OVERFLOW SEEN speichert die frühere absichtliche Ablehnung; es bedeutet nicht, dass am Ende noch ein Überlauf gemeldet wird.

Die blaue Kopie beginnt bei Pixel (0,16), die grüne Füllung bei (0,88). Jeder Bereich umfasst sieben vollständige Kachelzeilen mit 256 Pixeln Breite und 248 Pixel der achten: insgesamt 255 Kacheln. Das letzte Feld von 8 × 8 bleibt schwarz. Die Kopie wiederholt volle, links halbvolle und dreieckige Muster; das erste ist ein Dreieck, weil die Quelle nach dem Einreihen geändert wird. Die Füllung bleibt trotz geänderter Aufrufervariable voll. Form und Farbe werden geprüft.

Bei y=160 werden ein volles rotes Quadrat bei x=16, eine rote linke Halbkachel bei x=224 und ein volles rotes Quadrat bei x=240 erwartet. Das erste übersteht Einträge mit Länge null, das mittlere belegt die NMI-Verarbeitung, das letzte einen einzelnen Put. Das verworfene Feld bei (248,160) bleibt schwarz. Lange Transfers laufen bei abgeschaltetem Rendering und NMI; nur der letzte kleine Schreibzugriff nutzt NMI. Dies ist ein NROM-Emulatortest, kein Test auf echter Hardware.

__vramq_copy: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:150

Deklarationshinweise im Original
Retain the source address for later consumption; keep its storage and bank mapping valid.
__vramq_exec

Verarbeitet eine freigegebene Warteschlange sofort in Eintragsreihenfolge. Ohne Bereitschaft kehrt die Funktion unverändert zurück. Danach löscht sie Länge und Bereitschaft, behält aber die Überlaufmeldung. Sie wartet nicht auf sichere PPU-Zeitfenster, begrenzt keine Transfermenge und stellt den Scrollzustand nicht wieder her; das übernimmt der Aufrufer.

Syntax

c
void __vramq_exec(void);

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

PPU-Byteadresse (0x0000–0x3FFF), kein CPU-Zeiger. Das Ziel muss beschreibbar sein: Musteränderungen benötigen CHR-RAM statt CHR-ROM. PPU-Adressabbildung und Spiegelung gelten weiterhin. Wählen Sie für fortlaufendes Kopieren und Füllen die Schrittweite 1 in PPUCTRL. Führen Sie Befehle zu einem sicheren PPU-Zeitpunkt aus und stellen Sie danach den Scrollzustand her; der Ausführer schreibt PPUADDR und PPUDATA, ohne Scrolleinstellungen wiederherzustellen.

FC zählt codierte Befehlsbytes in einem festen intrinsischen 128-Byte-Puffer. Ein Tile-Schreibbefehl braucht 4 Bytes, ein Fülleintrag 5 und eine Zeigerkopie 6 einschließlich Metadaten. NMI kann eine bereite Warteschlange verarbeiten; prüfen und befüllen Sie sie vor der Freigabe und koordinieren Sie Erzeuger und Verbraucher.

Diese Intrinsics verwenden denselben Puffer wie lib/vram.c. Die Funktionen nes_vram_queue_* in runtime.c verwalten eine getrennte Warteschlange mit anderem Eintragsformat. Leeren oder Abfragen der einen betrifft die andere nicht.

Koordinieren Sie Aufbau und Verarbeitung der Warteschlange. Fügen Sie während ihrer Interrupt-Ausführung nichts hinzu, ändern Sie die Verwaltung nicht direkt und überschreiben Sie gespeicherte Quelldaten nicht versehentlich. Diese APIs bieten weder Sperren noch Doppelpufferung. Die Kapazität betrifft den Befehlspuffer, nicht freie Bytes im Hardware-VRAM.

Anwendungsbeispiel

c
// Rendering is stopped and the PPU increment is 1.
__vramq_exec();
after_exec=__vramq_len();
if (after_exec!=0 || __vramq_overflow()!=0) failures++;

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Kapazität, gespeicherte Zeiger, getrennte Rücksetzfunktionen sowie direkte und NMI-Ausführung. Erwartet werden CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 und FAILED CHECKS 000. Sechs Einträge belegen 30 Bytes und führen 512 Byte-Schreibzugriffe aus. OVERFLOW SEEN speichert die frühere absichtliche Ablehnung; es bedeutet nicht, dass am Ende noch ein Überlauf gemeldet wird.

Die blaue Kopie beginnt bei Pixel (0,16), die grüne Füllung bei (0,88). Jeder Bereich umfasst sieben vollständige Kachelzeilen mit 256 Pixeln Breite und 248 Pixel der achten: insgesamt 255 Kacheln. Das letzte Feld von 8 × 8 bleibt schwarz. Die Kopie wiederholt volle, links halbvolle und dreieckige Muster; das erste ist ein Dreieck, weil die Quelle nach dem Einreihen geändert wird. Die Füllung bleibt trotz geänderter Aufrufervariable voll. Form und Farbe werden geprüft.

Bei y=160 werden ein volles rotes Quadrat bei x=16, eine rote linke Halbkachel bei x=224 und ein volles rotes Quadrat bei x=240 erwartet. Das erste übersteht Einträge mit Länge null, das mittlere belegt die NMI-Verarbeitung, das letzte einen einzelnen Put. Das verworfene Feld bei (248,160) bleibt schwarz. Lange Transfers laufen bei abgeschaltetem Rendering und NMI; nur der letzte kleine Schreibzugriff nutzt NMI. Dies ist ein NROM-Emulatortest, kein Test auf echter Hardware.

__vramq_exec: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:156

Deklarationshinweise im Original
Execute a ready queue directly; committing and safe PPU timing are caller responsibilities.
__vramq_fill

Fügt einen Fünf-Byte-Eintrag an, der das Byte value bei len PPU-Schreibzugriffen wiederholt. Der Wert wird gespeichert; spätere Änderungen der Aufrufervariablen ändern den eingereihten Füllwert nicht. Wiederholt wird ein Byte, kein mehrteiliges Kachelmuster.

Syntax

c
void __vramq_fill(u16 ppu_addr, u8 value, u8 len);

Parameter

ppu_addr

PPU-Byteadresse (0x0000–0x3FFF), kein CPU-Zeiger. Das Ziel muss beschreibbar sein: Musteränderungen benötigen CHR-RAM statt CHR-ROM. PPU-Adressabbildung und Spiegelung gelten weiterhin. Wählen Sie für fortlaufendes Kopieren und Füllen die Schrittweite 1 in PPUCTRL. Führen Sie Befehle zu einem sicheren PPU-Zeitpunkt aus und stellen Sie danach den Scrollzustand her; der Ausführer schreibt PPUADDR und PPUDATA, ohne Scrolleinstellungen wiederherzustellen.

value

Ein wiederholtes Byte von 0 bis 255. Seine sichtbare Wirkung hängt davon ab, ob das Ziel Tile-Indizes, Pixelebenen oder eine andere Bytestruktur enthält.

len

Byteanzahl von 0 bis 255. Ein Eintrag verarbeitet die gesamte Anzahl ohne Aufteilung in 127-Byte-Abschnitte. Auch null belegt einen Kopier- oder Fülleintrag. Bei dessen Ausführung wird die PPU-Adresse gesetzt, aber kein Byte nach PPUDATA geschrieben. 256 liegt außerhalb des Argumentbereichs.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Das Einfügen liefert keinen Erfolgswert. Passt der vollständige Eintrag nicht, wird __vramq_overflow() auf 1 gesetzt; vorhandene Einträge und Länge bleiben erhalten. Genau passender Platz wird akzeptiert. Eine frühere Überlaufmeldung verhindert keinen passenden Eintrag; behandeln oder löschen Sie sie vor der Bewertung eines neuen Stapels. Mehrere Einfügevorgänge bilden keine Transaktion.

FC zählt codierte Befehlsbytes in einem festen intrinsischen 128-Byte-Puffer. Ein Tile-Schreibbefehl braucht 4 Bytes, ein Fülleintrag 5 und eine Zeigerkopie 6 einschließlich Metadaten. NMI kann eine bereite Warteschlange verarbeiten; prüfen und befüllen Sie sie vor der Freigabe und koordinieren Sie Erzeuger und Verbraucher.

Diese Intrinsics verwenden denselben Puffer wie lib/vram.c. Die Funktionen nes_vram_queue_* in runtime.c verwalten eine getrennte Warteschlange mit anderem Eintragsformat. Leeren oder Abfragen der einen betrifft die andere nicht.

Koordinieren Sie Aufbau und Verarbeitung der Warteschlange. Fügen Sie während ihrer Interrupt-Ausführung nichts hinzu, ändern Sie die Verwaltung nicht direkt und überschreiben Sie gespeicherte Quelldaten nicht versehentlich. Diese APIs bieten weder Sperren noch Doppelpufferung. Die Kapazität betrifft den Befehlspuffer, nicht freie Bytes im Hardware-VRAM.

Anwendungsbeispiel

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.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Kapazität, gespeicherte Zeiger, getrennte Rücksetzfunktionen sowie direkte und NMI-Ausführung. Erwartet werden CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 und FAILED CHECKS 000. Sechs Einträge belegen 30 Bytes und führen 512 Byte-Schreibzugriffe aus. OVERFLOW SEEN speichert die frühere absichtliche Ablehnung; es bedeutet nicht, dass am Ende noch ein Überlauf gemeldet wird.

Die blaue Kopie beginnt bei Pixel (0,16), die grüne Füllung bei (0,88). Jeder Bereich umfasst sieben vollständige Kachelzeilen mit 256 Pixeln Breite und 248 Pixel der achten: insgesamt 255 Kacheln. Das letzte Feld von 8 × 8 bleibt schwarz. Die Kopie wiederholt volle, links halbvolle und dreieckige Muster; das erste ist ein Dreieck, weil die Quelle nach dem Einreihen geändert wird. Die Füllung bleibt trotz geänderter Aufrufervariable voll. Form und Farbe werden geprüft.

Bei y=160 werden ein volles rotes Quadrat bei x=16, eine rote linke Halbkachel bei x=224 und ein volles rotes Quadrat bei x=240 erwartet. Das erste übersteht Einträge mit Länge null, das mittlere belegt die NMI-Verarbeitung, das letzte einen einzelnen Put. Das verworfene Feld bei (248,160) bleibt schwarz. Lange Transfers laufen bei abgeschaltetem Rendering und NMI; nur der letzte kleine Schreibzugriff nutzt NMI. Dies ist ein NROM-Emulatortest, kein Test auf echter Hardware.

__vramq_fill: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:152

Deklarationshinweise im Original
Append a repeated-byte update; the source value is stored in the record.
__vramq_len

Gibt den von ausstehenden Befehlen belegten Pufferumfang zurück, nicht die Bytezahl der späteren Videodaten.

Syntax

c
u8 __vramq_len(void);

Rückgabewert

Ein vorzeichenloses Byte in den hier beschriebenen plattformspezifischen Einheiten. Die Abfrage verändert oder verarbeitet die Warteschlange nicht.

Bedingungen und Grenzen

FC zählt codierte Befehlsbytes in einem festen intrinsischen 128-Byte-Puffer. Ein Tile-Schreibbefehl braucht 4 Bytes, ein Fülleintrag 5 und eine Zeigerkopie 6 einschließlich Metadaten. NMI kann eine bereite Warteschlange verarbeiten; prüfen und befüllen Sie sie vor der Freigabe und koordinieren Sie Erzeuger und Verbraucher.

Diese Intrinsics verwenden denselben Puffer wie lib/vram.c. Die Funktionen nes_vram_queue_* in runtime.c verwalten eine getrennte Warteschlange mit anderem Eintragsformat. Leeren oder Abfragen der einen betrifft die andere nicht.

Koordinieren Sie Aufbau und Verarbeitung der Warteschlange. Fügen Sie während ihrer Interrupt-Ausführung nichts hinzu, ändern Sie die Verwaltung nicht direkt und überschreiben Sie gespeicherte Quelldaten nicht versehentlich. Diese APIs bieten weder Sperren noch Doppelpufferung. Die Kapazität betrifft den Befehlspuffer, nicht freie Bytes im Hardware-VRAM.

Anwendungsbeispiel

c
encoded=__vramq_len();
if (encoded!=30 || capacity-encoded!=98) failures++;
// 4+6+5+6+5+4 encoded bytes describe 512 actual byte writes.

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Kapazität, gespeicherte Zeiger, getrennte Rücksetzfunktionen sowie direkte und NMI-Ausführung. Erwartet werden CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 und FAILED CHECKS 000. Sechs Einträge belegen 30 Bytes und führen 512 Byte-Schreibzugriffe aus. OVERFLOW SEEN speichert die frühere absichtliche Ablehnung; es bedeutet nicht, dass am Ende noch ein Überlauf gemeldet wird.

Die blaue Kopie beginnt bei Pixel (0,16), die grüne Füllung bei (0,88). Jeder Bereich umfasst sieben vollständige Kachelzeilen mit 256 Pixeln Breite und 248 Pixel der achten: insgesamt 255 Kacheln. Das letzte Feld von 8 × 8 bleibt schwarz. Die Kopie wiederholt volle, links halbvolle und dreieckige Muster; das erste ist ein Dreieck, weil die Quelle nach dem Einreihen geändert wird. Die Füllung bleibt trotz geänderter Aufrufervariable voll. Form und Farbe werden geprüft.

Bei y=160 werden ein volles rotes Quadrat bei x=16, eine rote linke Halbkachel bei x=224 und ein volles rotes Quadrat bei x=240 erwartet. Das erste übersteht Einträge mit Länge null, das mittlere belegt die NMI-Verarbeitung, das letzte einen einzelnen Put. Das verworfene Feld bei (248,160) bleibt schwarz. Lange Transfers laufen bei abgeschaltetem Rendering und NMI; nur der letzte kleine Schreibzugriff nutzt NMI. Dies ist ein NROM-Emulatortest, kein Test auf echter Hardware.

__vramq_len: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:158

Deklarationshinweise im Original
Return encoded bytes currently occupied, not transfer payload bytes or command count.
__vramq_overflow

Liest das Überlaufflag, ohne es zu löschen; es kann einen früheren Fehler festhalten.

Syntax

c
u8 __vramq_overflow(void);

Rückgabewert

0 ohne gespeicherten Überlauf; nach einem abgelehnten Eintrag 1 bis zum ausdrücklichen Zurücksetzen. Die Ausführung der Warteschlange löscht diese Meldung nicht.

Bedingungen und Grenzen

FC zählt codierte Befehlsbytes in einem festen intrinsischen 128-Byte-Puffer. Ein Tile-Schreibbefehl braucht 4 Bytes, ein Fülleintrag 5 und eine Zeigerkopie 6 einschließlich Metadaten. NMI kann eine bereite Warteschlange verarbeiten; prüfen und befüllen Sie sie vor der Freigabe und koordinieren Sie Erzeuger und Verbraucher.

Diese Intrinsics verwenden denselben Puffer wie lib/vram.c. Die Funktionen nes_vram_queue_* in runtime.c verwalten eine getrennte Warteschlange mit anderem Eintragsformat. Leeren oder Abfragen der einen betrifft die andere nicht.

Koordinieren Sie Aufbau und Verarbeitung der Warteschlange. Fügen Sie während ihrer Interrupt-Ausführung nichts hinzu, ändern Sie die Verwaltung nicht direkt und überschreiben Sie gespeicherte Quelldaten nicht versehentlich. Diese APIs bieten weder Sperren noch Doppelpufferung. Die Kapazität betrifft den Befehlspuffer, nicht freie Bytes im Hardware-VRAM.

Anwendungsbeispiel

c
overflow_seen=__vramq_overflow();
if (overflow_seen!=1 || __vramq_overflow()!=1) failures++;

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Kapazität, gespeicherte Zeiger, getrennte Rücksetzfunktionen sowie direkte und NMI-Ausführung. Erwartet werden CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 und FAILED CHECKS 000. Sechs Einträge belegen 30 Bytes und führen 512 Byte-Schreibzugriffe aus. OVERFLOW SEEN speichert die frühere absichtliche Ablehnung; es bedeutet nicht, dass am Ende noch ein Überlauf gemeldet wird.

Die blaue Kopie beginnt bei Pixel (0,16), die grüne Füllung bei (0,88). Jeder Bereich umfasst sieben vollständige Kachelzeilen mit 256 Pixeln Breite und 248 Pixel der achten: insgesamt 255 Kacheln. Das letzte Feld von 8 × 8 bleibt schwarz. Die Kopie wiederholt volle, links halbvolle und dreieckige Muster; das erste ist ein Dreieck, weil die Quelle nach dem Einreihen geändert wird. Die Füllung bleibt trotz geänderter Aufrufervariable voll. Form und Farbe werden geprüft.

Bei y=160 werden ein volles rotes Quadrat bei x=16, eine rote linke Halbkachel bei x=224 und ein volles rotes Quadrat bei x=240 erwartet. Das erste übersteht Einträge mit Länge null, das mittlere belegt die NMI-Verarbeitung, das letzte einen einzelnen Put. Das verworfene Feld bei (248,160) bleibt schwarz. Lange Transfers laufen bei abgeschaltetem Rendering und NMI; nur der letzte kleine Schreibzugriff nutzt NMI. Dies ist ein NROM-Emulatortest, kein Test auf echter Hardware.

__vramq_overflow: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:160

Deklarationshinweise im Original
Read the latched capacity failure; reading does not reset it.
__vramq_put

Fügt eine PPU-Adresse und einen Bytewert als Vier-Byte-Eintrag an. Der Wert wird sofort in die Warteschlange kopiert und erst bei deren Ausführung geschrieben. Dies eignet sich etwa für eine Nametable-Kachel oder ein anderes beschreibbares PPU-Byte.

Syntax

c
void __vramq_put(u16 ppu_addr, u8 value);

Parameter

ppu_addr

PPU-Byteadresse (0x0000–0x3FFF), kein CPU-Zeiger. Das Ziel muss beschreibbar sein: Musteränderungen benötigen CHR-RAM statt CHR-ROM. PPU-Adressabbildung und Spiegelung gelten weiterhin. Wählen Sie für fortlaufendes Kopieren und Füllen die Schrittweite 1 in PPUCTRL. Führen Sie Befehle zu einem sicheren PPU-Zeitpunkt aus und stellen Sie danach den Scrollzustand her; der Ausführer schreibt PPUADDR und PPUDATA, ohne Scrolleinstellungen wiederherzustellen.

value

Ein wiederholtes Byte von 0 bis 255. Seine sichtbare Wirkung hängt davon ab, ob das Ziel Tile-Indizes, Pixelebenen oder eine andere Bytestruktur enthält.

Rückgabewert

Keiner (void).

Bedingungen und Grenzen

Das Einfügen liefert keinen Erfolgswert. Passt der vollständige Eintrag nicht, wird __vramq_overflow() auf 1 gesetzt; vorhandene Einträge und Länge bleiben erhalten. Genau passender Platz wird akzeptiert. Eine frühere Überlaufmeldung verhindert keinen passenden Eintrag; behandeln oder löschen Sie sie vor der Bewertung eines neuen Stapels. Mehrere Einfügevorgänge bilden keine Transaktion.

FC zählt codierte Befehlsbytes in einem festen intrinsischen 128-Byte-Puffer. Ein Tile-Schreibbefehl braucht 4 Bytes, ein Fülleintrag 5 und eine Zeigerkopie 6 einschließlich Metadaten. NMI kann eine bereite Warteschlange verarbeiten; prüfen und befüllen Sie sie vor der Freigabe und koordinieren Sie Erzeuger und Verbraucher.

Diese Intrinsics verwenden denselben Puffer wie lib/vram.c. Die Funktionen nes_vram_queue_* in runtime.c verwalten eine getrennte Warteschlange mit anderem Eintragsformat. Leeren oder Abfragen der einen betrifft die andere nicht.

Koordinieren Sie Aufbau und Verarbeitung der Warteschlange. Fügen Sie während ihrer Interrupt-Ausführung nichts hinzu, ändern Sie die Verwaltung nicht direkt und überschreiben Sie gespeicherte Quelldaten nicht versehentlich. Diese APIs bieten weder Sperren noch Doppelpufferung. Die Kapazität betrifft den Befehlspuffer, nicht freie Bytes im Hardware-VRAM.

Anwendungsbeispiel

c
__vramq_put(0x2282,1); // Red 8x8 sentinel at pixel (16,160).

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Das Programm zeigt Kapazität, gespeicherte Zeiger, getrennte Rücksetzfunktionen sowie direkte und NMI-Ausführung. Erwartet werden CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 und FAILED CHECKS 000. Sechs Einträge belegen 30 Bytes und führen 512 Byte-Schreibzugriffe aus. OVERFLOW SEEN speichert die frühere absichtliche Ablehnung; es bedeutet nicht, dass am Ende noch ein Überlauf gemeldet wird.

Die blaue Kopie beginnt bei Pixel (0,16), die grüne Füllung bei (0,88). Jeder Bereich umfasst sieben vollständige Kachelzeilen mit 256 Pixeln Breite und 248 Pixel der achten: insgesamt 255 Kacheln. Das letzte Feld von 8 × 8 bleibt schwarz. Die Kopie wiederholt volle, links halbvolle und dreieckige Muster; das erste ist ein Dreieck, weil die Quelle nach dem Einreihen geändert wird. Die Füllung bleibt trotz geänderter Aufrufervariable voll. Form und Farbe werden geprüft.

Bei y=160 werden ein volles rotes Quadrat bei x=16, eine rote linke Halbkachel bei x=224 und ein volles rotes Quadrat bei x=240 erwartet. Das erste übersteht Einträge mit Länge null, das mittlere belegt die NMI-Verarbeitung, das letzte einen einzelnen Put. Das verworfene Feld bei (248,160) bleibt schwarz. Lange Transfers laufen bei abgeschaltetem Rendering und NMI; nur der letzte kleine Schreibzugriff nutzt NMI. Dies ist ein NROM-Emulatortest, kein Test auf echter Hardware.

__vramq_put: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:148

Deklarationshinweise im Original
Append one address/value update; use the overflow latch to detect capacity failure.
__xy_in_rect

Prüft die Zugehörigkeit zu einem Rechteck einschließlich oberer/linker und ausschließlich unterer/rechter Kante.

Syntax

c
u8 __xy_in_rect(u8 x, u8 y, u8 rx, u8 ry, u8 rw, u8 rh);

Parameter

x

Punkt-X, 0..255.

y

Punkt-Y, 0..255.

rx

Linke Rechteckkante, 0..255.

ry

Obere Rechteckkante, 0..255.

rw

Breite, 0..255.

rh

Höhe, 0..255.

Rückgabewert

Gibt innerhalb 1 zurück, sonst 0. Bei Breite oder Höhe null ist das Rechteck leer.

Bedingungen und Grenzen

Verwendet Koordinatendifferenzen: Eine rechte Grenze über 255 läuft nicht nach links um.

Anwendungsbeispiel

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);

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

Prüfen Sie Innenraum, Grenzen, einen äußeren Punkt und Breite null: INSIDE=1, RIGHT EDGE=0, LEFT EDGE=0, HIGH X=1. Prüfen Sie Distanz 15, umgelaufene Distanz 254 und Array-Indizes 64/65280.

__xy_in_rect: NROM
NROM

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:31

Deklarationshinweise im Original
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

Standardlesefunktion für die Lichterkennung an Port 2. Ruft dieselbe Hilfsfunktion wie __zapper_light2() auf.

Syntax

c
u8 __zapper_light(void);

Rückgabewert

1, wenn Bit 3 von $4017 gelöscht ist, andernfalls 0. Port 1 wird nicht gelesen.

Bedingungen und Grenzen

Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.

Liest das momentane Signal. Erzeugt keinen Strobe, wartet auf keinen Frame, erkennt keine Flanke, bestimmt keinen Zielpunkt und entscheidet keinen Treffer. Rohwert- und Abzugs-/Lichtaufrufe lesen das Register zu ihren jeweiligen Zeitpunkten und können bei wechselndem Signal unterschiedliche Werte liefern.

Der Lichteingang ist aktiv-low: Ein gelöschtes Bit 3 ergibt den Rückgabewert eins. Diese Umwandlung erkennt nicht, ob eine Lichtpistole angeschlossen ist; auch ein Gamepad oder offener Eingang kann eins liefern. Ein gezeichnetes Bildschirmziel macht daraus keine Trefferprüfungs-API.

Die Bitumwandlung wird außerdem in einem selbst erstellten Test-ROM geprüft. Dabei werden Registerlesezugriffe auf Testwerte im RAM umgeleitet. Die Tests decken 00/08/10/18 mit zusätzlich gesetzten, hier irrelevanten Bits ab. Dieser Modultest ist getrennt vom unveränderten Beispielprogramm und vom Verhalten echter Peripherie zu betrachten.

Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.

NESdev: Zapper

Anwendungsbeispiel

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();}
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.

__zapper_light: ZAPPER-DEFAULT — In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.
ZAPPER-DEFAULT — In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:253

Deklarationshinweise im Original
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

Liest das Lichtsignal von Port 1 ($4016) und wandelt den Low-aktiven Eingang so um, dass erkanntes Licht den Wert 1 ergibt.

Syntax

c
u8 __zapper_light1(void);

Rückgabewert

1, wenn Bit 3 von $4016 gelöscht ist; 0, wenn es gesetzt ist.

Bedingungen und Grenzen

Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.

Liest das momentane Signal. Erzeugt keinen Strobe, wartet auf keinen Frame, erkennt keine Flanke, bestimmt keinen Zielpunkt und entscheidet keinen Treffer. Rohwert- und Abzugs-/Lichtaufrufe lesen das Register zu ihren jeweiligen Zeitpunkten und können bei wechselndem Signal unterschiedliche Werte liefern.

Der Lichteingang ist aktiv-low: Ein gelöschtes Bit 3 ergibt den Rückgabewert eins. Diese Umwandlung erkennt nicht, ob eine Lichtpistole angeschlossen ist; auch ein Gamepad oder offener Eingang kann eins liefern. Ein gezeichnetes Bildschirmziel macht daraus keine Trefferprüfungs-API.

Die Bitumwandlung wird außerdem in einem selbst erstellten Test-ROM geprüft. Dabei werden Registerlesezugriffe auf Testwerte im RAM umgeleitet. Die Tests decken 00/08/10/18 mit zusätzlich gesetzten, hier irrelevanten Bits ab. Dieser Modultest ist getrennt vom unveränderten Beispielprogramm und vom Verhalten echter Peripherie zu betrachten.

Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.

NESdev: Zapper

Anwendungsbeispiel

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();}
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.

__zapper_light1: ZAPPER-DEFAULT — In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.
ZAPPER-DEFAULT — In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:247

Deklarationshinweise im Original
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

Liest das Lichtsignal von Port 2 ($4017) und wandelt den Low-aktiven Eingang so um, dass erkanntes Licht den Wert 1 ergibt.

Syntax

c
u8 __zapper_light2(void);

Rückgabewert

1, wenn Bit 3 von $4017 gelöscht ist; 0, wenn es gesetzt ist.

Bedingungen und Grenzen

Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.

Liest das momentane Signal. Erzeugt keinen Strobe, wartet auf keinen Frame, erkennt keine Flanke, bestimmt keinen Zielpunkt und entscheidet keinen Treffer. Rohwert- und Abzugs-/Lichtaufrufe lesen das Register zu ihren jeweiligen Zeitpunkten und können bei wechselndem Signal unterschiedliche Werte liefern.

Der Lichteingang ist aktiv-low: Ein gelöschtes Bit 3 ergibt den Rückgabewert eins. Diese Umwandlung erkennt nicht, ob eine Lichtpistole angeschlossen ist; auch ein Gamepad oder offener Eingang kann eins liefern. Ein gezeichnetes Bildschirmziel macht daraus keine Trefferprüfungs-API.

Die Bitumwandlung wird außerdem in einem selbst erstellten Test-ROM geprüft. Dabei werden Registerlesezugriffe auf Testwerte im RAM umgeleitet. Die Tests decken 00/08/10/18 mit zusätzlich gesetzten, hier irrelevanten Bits ab. Dieser Modultest ist getrennt vom unveränderten Beispielprogramm und vom Verhalten echter Peripherie zu betrachten.

Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.

NESdev: Zapper

Anwendungsbeispiel

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();}
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.

__zapper_light2: ZAPPER-DEFAULT — In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.
ZAPPER-DEFAULT — In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:249

Deklarationshinweise im Original
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

Liest beide Lightgun-Bits von Port 1 ($4016) gemeinsam, wenn Abzug und Lichterkennung aus demselben Registerlesezugriff ausgewertet werden sollen.

Syntax

c
u8 __zapper_raw1(void);

Rückgabewert

Liefert $4016 & 0x18: 0x00, 0x08, 0x10 oder 0x18. Bit 4 ist das Abzugssignal; Bit 3 ist das rohe Signal für fehlendes Licht.

Bedingungen und Grenzen

Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.

Liest das momentane Signal. Erzeugt keinen Strobe, wartet auf keinen Frame, erkennt keine Flanke, bestimmt keinen Zielpunkt und entscheidet keinen Treffer. Rohwert- und Abzugs-/Lichtaufrufe lesen das Register zu ihren jeweiligen Zeitpunkten und können bei wechselndem Signal unterschiedliche Werte liefern.

Der Lichteingang ist aktiv-low: Ein gelöschtes Bit 3 ergibt den Rückgabewert eins. Diese Umwandlung erkennt nicht, ob eine Lichtpistole angeschlossen ist; auch ein Gamepad oder offener Eingang kann eins liefern. Ein gezeichnetes Bildschirmziel macht daraus keine Trefferprüfungs-API.

Die Bitumwandlung wird außerdem in einem selbst erstellten Test-ROM geprüft. Dabei werden Registerlesezugriffe auf Testwerte im RAM umgeleitet. Die Tests decken 00/08/10/18 mit zusätzlich gesetzten, hier irrelevanten Bits ab. Dieser Modultest ist getrennt vom unveränderten Beispielprogramm und vom Verhalten echter Peripherie zu betrachten.

Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.

NESdev: Zapper

Anwendungsbeispiel

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();}
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.

__zapper_raw1: ZAPPER-DEFAULT — In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.
ZAPPER-DEFAULT — In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:239

Deklarationshinweise im Original
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

Liest beide Lightgun-Bits von Port 2 ($4017) gemeinsam, wenn Abzug und Lichterkennung aus demselben Registerlesezugriff ausgewertet werden sollen.

Syntax

c
u8 __zapper_raw2(void);

Rückgabewert

Liefert $4017 & 0x18: 0x00, 0x08, 0x10 oder 0x18. Bit 4 ist das Abzugssignal; Bit 3 ist das rohe Signal für fehlendes Licht.

Bedingungen und Grenzen

Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.

Liest das momentane Signal. Erzeugt keinen Strobe, wartet auf keinen Frame, erkennt keine Flanke, bestimmt keinen Zielpunkt und entscheidet keinen Treffer. Rohwert- und Abzugs-/Lichtaufrufe lesen das Register zu ihren jeweiligen Zeitpunkten und können bei wechselndem Signal unterschiedliche Werte liefern.

Der Lichteingang ist aktiv-low: Ein gelöschtes Bit 3 ergibt den Rückgabewert eins. Diese Umwandlung erkennt nicht, ob eine Lichtpistole angeschlossen ist; auch ein Gamepad oder offener Eingang kann eins liefern. Ein gezeichnetes Bildschirmziel macht daraus keine Trefferprüfungs-API.

Die Bitumwandlung wird außerdem in einem selbst erstellten Test-ROM geprüft. Dabei werden Registerlesezugriffe auf Testwerte im RAM umgeleitet. Die Tests decken 00/08/10/18 mit zusätzlich gesetzten, hier irrelevanten Bits ab. Dieser Modultest ist getrennt vom unveränderten Beispielprogramm und vom Verhalten echter Peripherie zu betrachten.

Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.

NESdev: Zapper

Anwendungsbeispiel

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();}
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.

__zapper_raw2: ZAPPER-DEFAULT — In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.
ZAPPER-DEFAULT — In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:241

Deklarationshinweise im Original
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

Standardlesefunktion für den Abzug an Port 2. Ruft dieselbe Hilfsfunktion wie __zapper_trigger2() auf.

Syntax

c
u8 __zapper_trigger(void);

Rückgabewert

Das auf 0 oder 1 normalisierte Bit 4 von $4017. Es wird nie automatisch auf Port 1 umgeschaltet.

Bedingungen und Grenzen

Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.

Liest das momentane Signal. Erzeugt keinen Strobe, wartet auf keinen Frame, erkennt keine Flanke, bestimmt keinen Zielpunkt und entscheidet keinen Treffer. Rohwert- und Abzugs-/Lichtaufrufe lesen das Register zu ihren jeweiligen Zeitpunkten und können bei wechselndem Signal unterschiedliche Werte liefern.

Das Ergebnis ist der logische Zustand der Abzugsleitung. Es garantiert keinen weiterhin vollständig gezogenen Abzug; das Schalterverhalten hängt vom Gerät ab. Vergleichen Sie für ein Schussereignis den vorherigen Messwert und erkennen Sie den gewünschten Übergang.

Die Bitumwandlung wird außerdem in einem selbst erstellten Test-ROM geprüft. Dabei werden Registerlesezugriffe auf Testwerte im RAM umgeleitet. Die Tests decken 00/08/10/18 mit zusätzlich gesetzten, hier irrelevanten Bits ab. Dieser Modultest ist getrennt vom unveränderten Beispielprogramm und vom Verhalten echter Peripherie zu betrachten.

Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.

NESdev: Zapper

Anwendungsbeispiel

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();}
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.

__zapper_trigger: ZAPPER-DEFAULT — In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.
ZAPPER-DEFAULT — In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:251

Deklarationshinweise im Original
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

Liest das Abzugssignal von Port 1 ($4016) und normalisiert es auf 0 oder 1.

Syntax

c
u8 __zapper_trigger1(void);

Rückgabewert

1, wenn Bit 4 von $4016 gesetzt ist, andernfalls 0.

Bedingungen und Grenzen

Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.

Liest das momentane Signal. Erzeugt keinen Strobe, wartet auf keinen Frame, erkennt keine Flanke, bestimmt keinen Zielpunkt und entscheidet keinen Treffer. Rohwert- und Abzugs-/Lichtaufrufe lesen das Register zu ihren jeweiligen Zeitpunkten und können bei wechselndem Signal unterschiedliche Werte liefern.

Das Ergebnis ist der logische Zustand der Abzugsleitung. Es garantiert keinen weiterhin vollständig gezogenen Abzug; das Schalterverhalten hängt vom Gerät ab. Vergleichen Sie für ein Schussereignis den vorherigen Messwert und erkennen Sie den gewünschten Übergang.

Die Bitumwandlung wird außerdem in einem selbst erstellten Test-ROM geprüft. Dabei werden Registerlesezugriffe auf Testwerte im RAM umgeleitet. Die Tests decken 00/08/10/18 mit zusätzlich gesetzten, hier irrelevanten Bits ab. Dieser Modultest ist getrennt vom unveränderten Beispielprogramm und vom Verhalten echter Peripherie zu betrachten.

Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.

NESdev: Zapper

Anwendungsbeispiel

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();}
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.

__zapper_trigger1: ZAPPER-DEFAULT — In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.
ZAPPER-DEFAULT — In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:243

Deklarationshinweise im Original
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

Liest das Abzugssignal von Port 2 ($4017) und normalisiert es auf 0 oder 1.

Syntax

c
u8 __zapper_trigger2(void);

Rückgabewert

1, wenn Bit 4 von $4017 gesetzt ist, andernfalls 0.

Bedingungen und Grenzen

Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.

Liest das momentane Signal. Erzeugt keinen Strobe, wartet auf keinen Frame, erkennt keine Flanke, bestimmt keinen Zielpunkt und entscheidet keinen Treffer. Rohwert- und Abzugs-/Lichtaufrufe lesen das Register zu ihren jeweiligen Zeitpunkten und können bei wechselndem Signal unterschiedliche Werte liefern.

Das Ergebnis ist der logische Zustand der Abzugsleitung. Es garantiert keinen weiterhin vollständig gezogenen Abzug; das Schalterverhalten hängt vom Gerät ab. Vergleichen Sie für ein Schussereignis den vorherigen Messwert und erkennen Sie den gewünschten Übergang.

Die Bitumwandlung wird außerdem in einem selbst erstellten Test-ROM geprüft. Dabei werden Registerlesezugriffe auf Testwerte im RAM umgeleitet. Die Tests decken 00/08/10/18 mit zusätzlich gesetzten, hier irrelevanten Bits ab. Dieser Modultest ist getrennt vom unveränderten Beispielprogramm und vom Verhalten echter Peripherie zu betrachten.

Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.

NESdev: Zapper

Anwendungsbeispiel

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();}
}

Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.

Erwartetes Ergebnis

In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.

__zapper_trigger2: ZAPPER-DEFAULT — In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.
ZAPPER-DEFAULT — In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.

Vollständiges Beispielprogramm

Build-Befehl

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
Implementierung und Quelltext

kitaqfc/lib/intrinsics.h:245

Deklarationshinweise im Original
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");
        }

Referenz der Befehlssyntax

Die folgende Hilfe wurde für diese Quellenausgabe aufgezeichnet. Sie erläutert optionale Argumente, Standardwerte und zulässige Werte. Ersetzen Sie die beispielhaften Eingabedateinamen durch Ihre eigenen Ausgaben. Aufgezeichnete Werkzeugantworten behalten ihren ursprünglichen Wortlaut.

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.

Syntax der Entwicklungsbefehle

Diese Aufrufschemata stammen aus Program.DebugTools.cs und Program.VibeTools.cs. ROM-Vergleich, Symbolsuche und Vorlagen sind eigene Unterbefehle neben der gewöhnlichen C-Kompilierung.

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]

Vom Parser erkannte lange Optionen

Die Schreibweisen wurden aus src/cli.rs entnommen, einschließlich der Kompatibilitäts- und Untersuchungsoptionen. Ob eine Option einen Wert erwartet, entnehmen Sie der Hilfe und der zugehörigen Verarbeitungsroutine.

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

Anhang: Verzeichnis der Assemblerbefehle

Diese Tabelle wurde aus src/machine/opcodes.rs erstellt. Sie beschreibt die internen Befehlsnamen und Operandenformate des Compilers. Einträge mit Sprungzielen oder Speicheradressen sind Syntaxbeispiele und keine eigenständigen Programme. Informationen zu erhaltenen Registern und veränderten Flags finden Sie in den Aufrufkonventionen und der Codegenerierung.

opcodeBefehlFormatSyntaxbeispiel
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