mirror of
https://gitlab.com/camelot/kickc.git
synced 2025-01-01 13:30:50 +00:00
Added support for NES platform with a working demo program. Closes #456
This commit is contained in:
parent
0ed5059c8a
commit
c8cd5a0e51
@ -0,0 +1,3 @@
|
||||
eor #$ff
|
||||
sec
|
||||
adc ({z1}),y
|
29
src/main/kc/include/nes.h
Normal file
29
src/main/kc/include/nes.h
Normal file
@ -0,0 +1,29 @@
|
||||
// Nintendo Entertainment System (NES
|
||||
// https://en.wikipedia.org/wiki/Nintendo_Entertainment_System_(Model_NES-101)
|
||||
// https://github.com/gregkrsak/first_nes
|
||||
#include <ricoh_2c02.h>
|
||||
#include <ricoh_2a03.h>
|
||||
|
||||
// NES Picture Processing Unit (PPU)
|
||||
struct RICOH_2C02 * PPU = 0x2000;
|
||||
|
||||
// NES CPU and audion processing unit (APU)
|
||||
struct RICOH_2A03 * APU = 0x4000;
|
||||
|
||||
// Pointer to the start of RAM memory
|
||||
char * const MEMORY = 0;
|
||||
|
||||
// Disable audio output
|
||||
void disableAudioOutput();
|
||||
|
||||
// Disable video output. This will cause a black screen and disable vblank.
|
||||
void disableVideoOutput();
|
||||
|
||||
// Enable video output. This will enable vblank.
|
||||
void enableVideoOutput();
|
||||
|
||||
// Wait for vblank to start
|
||||
void waitForVBlank();
|
||||
|
||||
// Clear the vblank flag
|
||||
void clearVBlankFlag();
|
123
src/main/kc/include/ricoh_2a03.h
Normal file
123
src/main/kc/include/ricoh_2a03.h
Normal file
@ -0,0 +1,123 @@
|
||||
// Ricoh 2A03 Nintendo Entertainment System CPU and audio processing unit (APU)
|
||||
// Ricoh 2A03 or RP2A03 (NTSC version) / Ricoh 2A07 or RP2A07 (PAL version)
|
||||
// https://en.wikipedia.org/wiki/Ricoh_2A03
|
||||
// https://www.nesdev.com/2A03%20technical%20reference.txt
|
||||
// https://wiki.nesdev.com/w/index.php/2A03
|
||||
// https://wiki.nesdev.com/w/index.php/APU
|
||||
// Based on: https://github.com/gregkrsak/first_nes written by Greg M. Krsak, 2018.
|
||||
|
||||
|
||||
// The APU (Audio Processing Unit) is the sound hardware the NES console which generates sound.
|
||||
struct RICOH_2A03 {
|
||||
// APU Square wave channels 1 and 2
|
||||
// Reference: https://wiki.nesdev.com/w/index.php/APU_Pulse
|
||||
// Duty and volume for square wave 1
|
||||
// $4000 SQ1_VOL Duty and volume for square wave 1
|
||||
char SQ1_VOL;
|
||||
// $4001 SQ1_SWEEP Sweep control register for square wave 1
|
||||
char SQ1_SWEEP;
|
||||
// $4002 SQ1_LO Low byte of period for square wave 1
|
||||
char SQ1_LO;
|
||||
// $4003 SQ1_HI High byte of period and length counter value for square wave 1
|
||||
char SQ1_HI;
|
||||
// $4004 SQ2_VOL Duty and volume for square wave 2
|
||||
char SQ2_VOL;
|
||||
// $4005 SQ2_SWEEP Sweep control register for square wave 2
|
||||
char SQ2_SWEEP;
|
||||
// $4006 SQ2_LO Low byte of period for square wave 2
|
||||
char SQ2_LO;
|
||||
// $4007 SQ2_HI High byte of period and length counter value for square wave 2
|
||||
char SQ2_HI;
|
||||
// APU Triangle wave channel
|
||||
// Reference: https://wiki.nesdev.com/w/index.php/APU_Triangle
|
||||
// $4008 TRI_LINEAR Triangle wave linear counter
|
||||
char TRI_LINEAR;
|
||||
// $4009 Unused, but is eventually accessed in memory-clearing loops
|
||||
char UNUSED1;
|
||||
// $400A TRI_LO Low byte of period for triangle wave
|
||||
char TRI_LO;
|
||||
// $400B TRI_HI High byte of period and length counter value for triangle wave
|
||||
char TRI_HI;
|
||||
// APU Noise generator
|
||||
// Reference: https://wiki.nesdev.com/w/index.php/APU_Noise
|
||||
// $400C NOISE_VOL Volume for noise generator
|
||||
char NOISE_VOL;
|
||||
// $400D Unused, but is eventually accessed in memory-clearing loops
|
||||
char UNUSED2;
|
||||
// $400E NOISE_LO Period and waveform shape for noise generator
|
||||
char NOISE_LO;
|
||||
// $400F NOISE_HI Length counter value for noise generator
|
||||
char NOISE_HI;
|
||||
// APU Delta Modulation Channel
|
||||
// Reference: https://wiki.nesdev.com/w/index.php/APU_DMC
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
// $4010 | W | DMC_FREQ Play mode FLAGS and frequency for DMC samples
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
// | 7 | IRQ enabled flag. If clear, the interrupt flag is cleared.
|
||||
// | 6 | Loop flag
|
||||
// | 3-0 | Rate index
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
// Rate $0 $1 $2 $3 $4 $5 $6 $7 $8 $9 $A $B $C $D $E $F
|
||||
// ------------------------------------------------------------------------------
|
||||
// NTSC 428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54
|
||||
// PAL 398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50
|
||||
//
|
||||
// The rate determines for how many CPU cycles happen between changes in the output level during automatic delta-encoded sample playback.
|
||||
// For example, on NTSC (1.789773 MHz), a rate of 428 gives a frequency of 1789773/428 Hz = 4181.71 Hz.
|
||||
// These periods are all even numbers because there are 2 CPU cycles in an APU cycle. A rate of 428 means the output level changes every 214 APU cycles.
|
||||
char DMC_FREQ;
|
||||
// $4011 DMC_RAW 7-bit DAC
|
||||
char DMC_RAW;
|
||||
// $4012 DMC_START Start of DMC waveform is at address $C000 + $40*$xx
|
||||
char DMC_START;
|
||||
// $4013 DMC_LEN Length of DMC waveform is $10*$xx + 1 bytes (128*$xx + 8 samples)
|
||||
char DMC_LEN;
|
||||
// $4014 OAMDMA Writing $xx copies 256 bytes by reading from $xx00-$xxFF and writing to OAMDATA ($2004). The CPU is suspended while the transfer is taking place.
|
||||
// Reference: https://wiki.nesdev.com/w/index.php/PPU_registers#OAMDMA
|
||||
char OAMDMA;
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
// $4015 | W | Sound Channel Switch
|
||||
// | 0 | Channel 1, 1 = enable sound.
|
||||
// | 1 | Channel 2, 1 = enable sound.
|
||||
// | 2 | Channel 3, 1 = enable sound.
|
||||
// | 3 | Channel 4, 1 = enable sound.
|
||||
// | 4 | Channel 5, 1 = enable sound.
|
||||
// | 5-7 | Unused (???)
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
// $4015 SND_CHN Sound channels enable and status
|
||||
// Reference: https://wiki.nesdev.com/w/index.php/APU#Status_.28.244015.29
|
||||
char SND_CHN;
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
// $4016 | W | JOY1 Joystick 1 data (R) and joystick strobe (W)
|
||||
// | 0 | Controller port latch bit
|
||||
// | 1-2 | Expansion port latch bits
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
// $4016 | R | JOY1 Joystick 1 data (R) and joystick strobe (W)
|
||||
// | 0-4 | Input data lines /D4 D3 D2 D1 D0) controller port 1
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
// https://wiki.nesdev.com/w/index.php/Input_devices
|
||||
// https://wiki.nesdev.com/w/index.php/Controller_reading
|
||||
char JOY1;
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
// $4017 | R | JOY2 Joystick 2 data (R) and frame counter control (W)
|
||||
// | 0-4 | Input data lines /D4 D3 D2 D1 D0) controller port 2
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
char JOY2;
|
||||
};
|
||||
|
||||
// APU Frame Counter
|
||||
// generates low-frequency clocks for the channels and an optional 60 Hz interrupt.
|
||||
// https://wiki.nesdev.com/w/index.php/APU_Frame_Counter
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
// $4017 | W | FR_COUNTER Frame Counter Set mode and interrupt
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
// | 7 | Sequencer mode: 0 selects 4-step sequence, 1 selects 5-step sequence
|
||||
// | 6 | Interrupt inhibit flag. If set, the frame interrupt flag is cleared, otherwise it is unaffected.
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
// Side effects After 3 or 4 CPU clock cycles*, the timer is reset.
|
||||
// If the mode flag is set, then both "quarter frame" and "half frame" signals are also generated.
|
||||
char * const FR_COUNTER = 0x4017;
|
||||
|
||||
|
||||
|
||||
|
132
src/main/kc/include/ricoh_2c02.h
Normal file
132
src/main/kc/include/ricoh_2c02.h
Normal file
@ -0,0 +1,132 @@
|
||||
// Ricoh 2C02 - NES Picture Processing Unit (PPU)
|
||||
// Ricoh RP2C02 (NTSC version) / RP2C07 (PAL version),
|
||||
// https://en.wikipedia.org/wiki/Picture_Processing_Unit
|
||||
// https://wiki.nesdev.com/w/index.php/PPU_registers
|
||||
// http://nesdev.com/2C02%20technical%20reference.TXT
|
||||
// Based on: https://github.com/gregkrsak/first_nes written by Greg M. Krsak, 2018.
|
||||
|
||||
// PPU Memory Map
|
||||
// $0000-$0FFF $1000 Pattern table 0
|
||||
char * const PPU_PATTERN_TABLE_0 = 0x0000;
|
||||
// $1000-$1FFF $1000 Pattern table 1
|
||||
char * const PPU_PATTERN_TABLE_1 = 0x1000;
|
||||
// $2000-$23FF $0400 Nametable 0
|
||||
char * const PPU_NAME_TABLE_0 = 0x2000;
|
||||
// $2400-$27FF $0400 Nametable 1
|
||||
char * const PPU_NAME_TABLE_1 = 0x2400;
|
||||
// $2800-$2BFF $0400 Nametable 2
|
||||
char * const PPU_NAME_TABLE_2 = 0x2800;
|
||||
// $2C00-$2FFF $0400 Nametable 3
|
||||
char * const PPU_NAME_TABLE_3 = 0x2c00;
|
||||
// $3000-$3EFF $0F00 Mirrors of $2000-$2EFF
|
||||
// $3F00-$3F1F $0020 Palette RAM indexes
|
||||
char * const PPU_PALETTE = 0x3f00;
|
||||
// $3F20-$3FFF $00E0 Mirrors of $3F00-$3F1F
|
||||
|
||||
struct RICOH_2C02 {
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
// $2000 | RW | PPU Control Register 1
|
||||
// | 0-1 | Name Table Address:
|
||||
// | |
|
||||
// | | +-----------+-----------+
|
||||
// | | | 2 ($2800) | 3 ($2C00) |
|
||||
// | | +-----------+-----------+
|
||||
// | | | 0 ($2000) | 1 ($2400) |
|
||||
// | | +-----------+-----------+
|
||||
// | |
|
||||
// | | Remember that because of the mirroring there are only 2
|
||||
// | | real Name Tables, not 4. Also, PPU will automatically
|
||||
// | | switch to another Name Table when running off the current
|
||||
// | | Name Table during scroll (see picture above).
|
||||
// | 2 | Vertical Write, 1 = PPU memory address increments by 32:
|
||||
// | |
|
||||
// | | Name Table, VW=0 Name Table, VW=1
|
||||
// | | +----------------+ +----------------+
|
||||
// | | |----> write | | | write |
|
||||
// | | | | | V |
|
||||
// | |
|
||||
// | 3 | Sprite Pattern Table Address, 1 = $1000, 0 = $0000.
|
||||
// | 4 | Screen Pattern Table Address, 1 = $1000, 0 = $0000.
|
||||
// | 5 | Sprite Size, 1 = 8x16, 0 = 8x8.
|
||||
// | 6 | PPU Master/Slave Mode, not used in NES.
|
||||
// | 7 | VBlank Enable, 1 = generate interrupts on VBlank.
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
char PPUCTRL; // Reference: https://wiki.nesdev.com/w/index.php/PPU_registers#PPUCTRL
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
// $2001 | RW | PPU Control Register 2
|
||||
// | 0 | Unknown (???)
|
||||
// | 1 | Image Mask, 0 = don't show left 8 columns of the screen.
|
||||
// | 2 | Sprite Mask, 0 = don't show sprites in left 8 columns.
|
||||
// | 3 | Screen Enable, 1 = show picture, 0 = blank screen.
|
||||
// | 4 | Sprites Enable, 1 = show sprites, 0 = hide sprites.
|
||||
// | 5-7 | Background Color, 0 = black, 1 = blue, 2 = green, 4 = red.
|
||||
// | | Do not use any other numbers as you may damage PPU hardware.
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
char PPUMASK; // Reference: https://wiki.nesdev.com/w/index.php/PPU_registers#PPUMASK
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
// $2002 | R | PPU Status Register
|
||||
// | 0-5 | Unknown (???)
|
||||
// | 6 | Hit Flag, 1 = Sprite refresh has hit sprite #0.
|
||||
// | | This flag resets to 0 when screen refresh starts.
|
||||
// | 7 | VBlank Flag, 1 = PPU is in VBlank state.
|
||||
// | | This flag resets to 0 when VBlank ends or CPU reads $2002.
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
volatile char PPUSTATUS; // Reference: https://wiki.nesdev.com/w/index.php/PPU_registers#PPUSTATUS
|
||||
// The OAM (Object Attribute Memory) is internal memory inside the PPU that contains a lookup table
|
||||
// of up to 64 sprites, where each table entry consists of 4 bytes.
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
// $2003 | W | Sprite Memory Address
|
||||
// | | Used to set the address of the 256-byte Sprite Memory to be
|
||||
// | | accessed via $2004. This address will increment by 1 after
|
||||
// | | each access to $2004. Sprite Memory contains coordinates,
|
||||
// | | colors, and other sprite attributes.
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
char OAMADDR; // Reference: https://wiki.nesdev.com/w/index.php/PPU_registers#OAMADDR
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
// $2004 | RW | Sprite Memory Data
|
||||
// | | Used to read/write the Sprite Memory. The address is set via
|
||||
// | | $2003 and increments by 1 after each access. Sprite Memory
|
||||
// | | contains coordinates, colors, and other sprite attributes
|
||||
// | | sprites.
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
char OAMDATA; // Reference: https://wiki.nesdev.com/w/index.php/PPU_registers#OAMDATA
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
// $2005 | W | Screen Scroll Offsets
|
||||
// | | There are two scroll registers, vertical and horizontal,
|
||||
// | | which are both written via this port. The first value written
|
||||
// | | will go into the Vertical Scroll Register (unless it is >239,
|
||||
// | | then it will be ignored). The second value will appear in the
|
||||
// | | Horizontal Scroll Register. Name Tables are assumed to be
|
||||
// | | arranged in the following way:
|
||||
// | |
|
||||
// | | +-----------+-----------+
|
||||
// | | | 2 ($2800) | 3 ($2C00) |
|
||||
// | | +-----------+-----------+
|
||||
// | | | 0 ($2000) | 1 ($2400) |
|
||||
// | | +-----------+-----------+
|
||||
// | |
|
||||
// | | When scrolled, the picture may span over several Name Tables.
|
||||
// | | Remember that because of the mirroring there are only 2 real
|
||||
// | | Name Tables, not 4.
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
char PPUSCROLL; // Reference: https://wiki.nesdev.com/w/index.php/PPU_registers#PPUSCROLL
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
// $2006 | W | PPU Memory Address
|
||||
// | | Used to set the address of PPU Memory to be accessed via
|
||||
// | | $2007. The first write to this register will set 8 lower
|
||||
// | | address bits. The second write will set 6 upper bits. The
|
||||
// | | address will increment either by 1 or by 32 after each
|
||||
// | | access to $2007.
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
char PPUADDR; // Reference: https://wiki.nesdev.com/w/index.php/PPU_registers#PPUADDR
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
// $2007 | RW | PPU Memory Data
|
||||
// | | Used to read/write the PPU Memory. The address is set via
|
||||
// | | $2006 and increments after each access, either by 1 or by 32.
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
char PPUDATA; // Reference: https://wiki.nesdev.com/w/index.php/PPU_registers#PPUDATA
|
||||
};
|
||||
|
||||
// PPU Status Register for reading in ASM
|
||||
volatile char * PPU_PPUSTATUS = 0x2002;
|
||||
|
38
src/main/kc/lib/nes.c
Normal file
38
src/main/kc/lib/nes.c
Normal file
@ -0,0 +1,38 @@
|
||||
// Nintendo Entertainment System (NES
|
||||
// https://en.wikipedia.org/wiki/Nintendo_Entertainment_System_(Model_NES-101)
|
||||
// https://github.com/gregkrsak/first_nes
|
||||
#include <nes.h>
|
||||
|
||||
// Disable audio output
|
||||
inline void disableAudioOutput() {
|
||||
// Disable APU frame IRQ
|
||||
*FR_COUNTER = 0b01000000;
|
||||
// Disable digital sound IRQs
|
||||
APU->DMC_FREQ = 0b01000000;
|
||||
}
|
||||
|
||||
// Disable video output. This will cause a black screen and disable vblank.
|
||||
inline void disableVideoOutput() {
|
||||
// Disable vertical blank interrupt
|
||||
PPU->PPUCTRL = 0;
|
||||
// Disable sprite rendering
|
||||
PPU->PPUMASK = 0;
|
||||
}
|
||||
|
||||
// Enable video output. This will enable vblank.
|
||||
inline void enableVideoOutput() {
|
||||
// Enable vertical blank interrupt
|
||||
PPU->PPUCTRL = 0b10000000;
|
||||
// Enable sprite rendering
|
||||
PPU->PPUMASK = 0b00010000;
|
||||
}
|
||||
|
||||
// Wait for vblank to start
|
||||
inline void waitForVBlank() {
|
||||
while(!(PPU->PPUSTATUS&0x80)) { }
|
||||
}
|
||||
|
||||
// Clear the vblank flag
|
||||
inline void clearVBlankFlag() {
|
||||
asm { lda PPU_PPUSTATUS }
|
||||
}
|
27
src/main/kc/target/nes.ld
Normal file
27
src/main/kc/target/nes.ld
Normal file
@ -0,0 +1,27 @@
|
||||
// Nintendo Entertainment System (NES) ROM
|
||||
// https://sadistech.com/nesromtool/romdoc.html
|
||||
// https://forums.nesdev.com/viewtopic.php?f=2&t=9896
|
||||
// https://github.com/gregkrsak/first_nes
|
||||
.file [name="%O", type="bin", segments="NesRom"]
|
||||
.file [name="%O_hdr", type="bin", segments="Header"]
|
||||
.file [name="%O_prg", type="bin", segments="ProgramRom"]
|
||||
.file [name="%O_chr", type="bin", segments="CharacterRom"]
|
||||
.segmentdef Header [ start=$0000, min=$0000, max=$000f, fill ]
|
||||
.segmentdef Tiles [ start=$0000, min=$0000, max=$1fff, fill ]
|
||||
.segmentdef Code [ start=$c000, min=$c000, max=$fff9 ]
|
||||
.segmentdef Data [ startAfter="Code", min=$c000, max=$fff9 ]
|
||||
.segmentdef Vectors [ start=$fffa, min=$fffa, max=$ffff ]
|
||||
.segmentdef ProgramRom [ segments="Code, Data, Vectors" ]
|
||||
.segmentdef CharacterRom [ segments="Tiles" ]
|
||||
.segmentdef NesRom
|
||||
//.segment NesRom
|
||||
//.segmentout [ segments="Header" ]
|
||||
//.segmentout [ segments="ProgramRom" ]
|
||||
//.segmentout [ segments="CharacterRom" ]
|
||||
.segment Header
|
||||
.text @"NES\$1a"
|
||||
.byte $01 // 1x 16KB ROM (PRG)
|
||||
.byte $01 // 1x 8KB VROM (CHR)
|
||||
.byte %00000001 // Mapper nibble 0000 == No mapping (a simple 16KB PRG + 8KB CHR game)
|
||||
// Mirroring nibble 0001 == Vertical mirroring only
|
||||
.segment Code
|
10
src/main/kc/target/nes.tgt
Normal file
10
src/main/kc/target/nes.tgt
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"extension": "nes",
|
||||
"link": "nes.ld",
|
||||
"cpu": "MOS6502",
|
||||
"emulator": "nestopia",
|
||||
"zp_reserve": [ ],
|
||||
"defines": {
|
||||
"__NES__": 1
|
||||
}
|
||||
}
|
@ -44,6 +44,11 @@ public class TestPrograms {
|
||||
public TestPrograms() {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNesDemo() throws IOException, URISyntaxException {
|
||||
compileAndCompare("examples/nes/nes-demo.c");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAtari2600Sprites() throws IOException, URISyntaxException {
|
||||
compileAndCompare("examples/atari2600/atari2600-sprites.c");
|
||||
|
159
src/test/kc/examples/nes/nes-demo.c
Normal file
159
src/test/kc/examples/nes/nes-demo.c
Normal file
@ -0,0 +1,159 @@
|
||||
// A minimal NES demo
|
||||
// Based on: https://github.com/gregkrsak/first_nes written by Greg M. Krsak, 2018.
|
||||
#pragma target(nes)
|
||||
#include <nes.h>
|
||||
#include <string.h>
|
||||
|
||||
// RESET Called when the NES is reset, including when it is turned on.
|
||||
void main() {
|
||||
asm {
|
||||
cld
|
||||
ldx #$ff
|
||||
txs
|
||||
}
|
||||
|
||||
// Initialize the video & audio
|
||||
disableVideoOutput();
|
||||
disableAudioOutput();
|
||||
// Note: When the system is first turned on or reset, the PPU may not be in a usable state right
|
||||
// away. You should wait at least 30,000 (thirty thousand) CPU cycles for the PPU to initialize,
|
||||
// which may be accomplished by waiting for 2 (two) vertical blank intervals.
|
||||
clearVBlankFlag();
|
||||
waitForVBlank();
|
||||
// Clear RAM - since it has all variables and the stack it is necesary to do it inline
|
||||
char i=0;
|
||||
do {
|
||||
(MEMORY+0x000)[i] = 0;
|
||||
(MEMORY+0x100)[i] = 0;
|
||||
(MEMORY+0x200)[i] = 0;
|
||||
(MEMORY+0x300)[i] = 0;
|
||||
(MEMORY+0x400)[i] = 0;
|
||||
(MEMORY+0x500)[i] = 0;
|
||||
(MEMORY+0x600)[i] = 0;
|
||||
(MEMORY+0x700)[i] = 0;
|
||||
} while (++i);
|
||||
waitForVBlank();
|
||||
// Now the PPU is ready.
|
||||
|
||||
initPaletteData();
|
||||
initSpriteData();
|
||||
enableVideoOutput();
|
||||
|
||||
// Infinite loop
|
||||
while(1) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// NMI Called when the PPU refreshes the screen (also known as the V-Blank period)
|
||||
interrupt(hardware_stack) void vblank() {
|
||||
|
||||
// Refresh DRAM-stored sprite data before it decays.
|
||||
// Set OAM start address to sprite#0
|
||||
PPU->OAMADDR = 0;
|
||||
// Set the high byte (02) of the RAM address and start the DMA transfer to OAM memory
|
||||
APU->OAMDMA = >OAM_BUFFER;
|
||||
|
||||
// Freeze the button positions.
|
||||
APU->JOY1 = 1;
|
||||
APU->JOY1 = 0;
|
||||
// Controllers for first and second player are now latched and will not change
|
||||
|
||||
// Read button A on controller 1
|
||||
if(APU->JOY1&0b00000001) {
|
||||
moveLuigiRight();
|
||||
}
|
||||
|
||||
// Read button B on controller 1
|
||||
if(APU->JOY1&0b00000001) {
|
||||
moveLuigiLeft();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// move the Luigi sprites right
|
||||
void moveLuigiRight() {
|
||||
OAM_BUFFER[0x03]++;
|
||||
OAM_BUFFER[0x07]++;
|
||||
OAM_BUFFER[0x0b]++;
|
||||
OAM_BUFFER[0x0f]++;
|
||||
}
|
||||
|
||||
// move the Luigi sprites left
|
||||
void moveLuigiLeft() {
|
||||
OAM_BUFFER[0x03]--;
|
||||
OAM_BUFFER[0x07]--;
|
||||
OAM_BUFFER[0x0b]--;
|
||||
OAM_BUFFER[0x0f]--;
|
||||
}
|
||||
|
||||
// Copy palette values to PPU
|
||||
void initPaletteData() {
|
||||
// Reset the high/low latch to "high"
|
||||
asm { lda PPU_PPUSTATUS }
|
||||
// Write the high byte of PPU Palette address
|
||||
PPU->PPUADDR = >PPU_PALETTE;
|
||||
// Write the low byte of PPU Palette address
|
||||
PPU->PPUADDR = <PPU_PALETTE;
|
||||
// Write to PPU
|
||||
for(char i=0;i<sizeof(PALETTE);i++)
|
||||
PPU->PPUDATA = PALETTE[i];
|
||||
}
|
||||
|
||||
// OAM (Object Attribute Memory) Buffer
|
||||
// Will be transfered to the PPU via DMA
|
||||
char * const OAM_BUFFER = 0x0200;
|
||||
|
||||
// Initialize OAM (Object Attribute Memory) Buffer
|
||||
void initSpriteData() {
|
||||
for(char i=0;i<sizeof(SPRITES);i++)
|
||||
OAM_BUFFER[i] = SPRITES[i];
|
||||
}
|
||||
|
||||
char PALETTE[0x20] = {
|
||||
// Background palettes
|
||||
0x0f, 0x31, 0x32, 0x33,
|
||||
0x0f, 0x35, 0x36, 0x37,
|
||||
0x0f, 0x39, 0x3a, 0x3b,
|
||||
0x0f, 0x3d, 0x3e, 0x0f,
|
||||
// Sprite palettes (selected by the attribute bits 0-1 of the sprites)
|
||||
0x0f, 0x1c, 0x15, 0x14,
|
||||
0x0f, 0x02, 0x38, 0x3c,
|
||||
0x0f, 0x30, 0x37, 0x1a, // Luigi-like colors
|
||||
0x0f, 0x0f, 0x0f, 0x0f // All black
|
||||
};
|
||||
|
||||
// Small Luigi Sprite Data
|
||||
char SPRITES[] = {
|
||||
// Y , TILE, ATTR , X
|
||||
128, 0x36, 0b00000010, 128, // Sprite 0
|
||||
128, 0x37, 0b00000010, 136, // Sprite 1
|
||||
136, 0x38, 0b00000010, 128, // Sprite 2
|
||||
136, 0x39, 0b00000010, 136 // Sprite 3
|
||||
};
|
||||
|
||||
// Tiles
|
||||
#pragma data_seg(Tiles)
|
||||
export char TILES[] = kickasm(resource "smb1_chr.bin") {{
|
||||
.import binary "smb1_chr.bin"
|
||||
}};
|
||||
|
||||
// Interrupt Vectors
|
||||
#pragma data_seg(Vectors)
|
||||
export void()* const VECTORS[] = {
|
||||
// NMI Called when the PPU refreshes the screen (also known as the V-Blank period)
|
||||
&vblank,
|
||||
// RESET Called when the NES is reset, including when it is turned on.
|
||||
&main,
|
||||
// IRQ Called when a BRK instruction is executed.
|
||||
0
|
||||
};
|
||||
|
||||
// Generate the NES ROM contents
|
||||
// Can be moved into the linker file when KickAsm 5.15 is released.
|
||||
#pragma data_seg(NesRom)
|
||||
export char NES_ROM[] = kickasm {{
|
||||
.segmentout [ segments="Header" ]
|
||||
.segmentout [ segments="ProgramRom" ]
|
||||
.segmentout [ segments="CharacterRom" ]
|
||||
}};
|
BIN
src/test/kc/examples/nes/smb1_chr.bin
Normal file
BIN
src/test/kc/examples/nes/smb1_chr.bin
Normal file
Binary file not shown.
273
src/test/ref/examples/nes/nes-demo.asm
Normal file
273
src/test/ref/examples/nes/nes-demo.asm
Normal file
@ -0,0 +1,273 @@
|
||||
// A minimal NES demo
|
||||
// Based on: https://github.com/gregkrsak/first_nes written by Greg M. Krsak, 2018.
|
||||
// Nintendo Entertainment System (NES) ROM
|
||||
// https://sadistech.com/nesromtool/romdoc.html
|
||||
// https://forums.nesdev.com/viewtopic.php?f=2&t=9896
|
||||
// https://github.com/gregkrsak/first_nes
|
||||
.file [name="nes-demo.nes", type="bin", segments="NesRom"]
|
||||
.file [name="nes-demo.nes_hdr", type="bin", segments="Header"]
|
||||
.file [name="nes-demo.nes_prg", type="bin", segments="ProgramRom"]
|
||||
.file [name="nes-demo.nes_chr", type="bin", segments="CharacterRom"]
|
||||
.segmentdef Header [ start=$0000, min=$0000, max=$000f, fill ]
|
||||
.segmentdef Tiles [ start=$0000, min=$0000, max=$1fff, fill ]
|
||||
.segmentdef Code [ start=$c000, min=$c000, max=$fff9 ]
|
||||
.segmentdef Data [ startAfter="Code", min=$c000, max=$fff9 ]
|
||||
.segmentdef Vectors [ start=$fffa, min=$fffa, max=$ffff ]
|
||||
.segmentdef ProgramRom [ segments="Code, Data, Vectors" ]
|
||||
.segmentdef CharacterRom [ segments="Tiles" ]
|
||||
.segmentdef NesRom
|
||||
//.segment NesRom
|
||||
//.segmentout [ segments="Header" ]
|
||||
//.segmentout [ segments="ProgramRom" ]
|
||||
//.segmentout [ segments="CharacterRom" ]
|
||||
.segment Header
|
||||
.text @"NES\$1a"
|
||||
.byte $01 // 1x 16KB ROM (PRG)
|
||||
.byte $01 // 1x 8KB VROM (CHR)
|
||||
.byte %00000001 // Mapper nibble 0000 == No mapping (a simple 16KB PRG + 8KB CHR game)
|
||||
// Mirroring nibble 0001 == Vertical mirroring only
|
||||
.segment Code
|
||||
|
||||
.const OFFSET_STRUCT_RICOH_2A03_DMC_FREQ = $10
|
||||
.const OFFSET_STRUCT_RICOH_2C02_PPUMASK = 1
|
||||
.const OFFSET_STRUCT_RICOH_2C02_PPUSTATUS = 2
|
||||
.const OFFSET_STRUCT_RICOH_2C02_OAMADDR = 3
|
||||
.const OFFSET_STRUCT_RICOH_2A03_OAMDMA = $14
|
||||
.const OFFSET_STRUCT_RICOH_2A03_JOY1 = $16
|
||||
.const OFFSET_STRUCT_RICOH_2C02_PPUADDR = 6
|
||||
.const OFFSET_STRUCT_RICOH_2C02_PPUDATA = 7
|
||||
.const SIZEOF_BYTE = 1
|
||||
// $3000-$3EFF $0F00 Mirrors of $2000-$2EFF
|
||||
// $3F00-$3F1F $0020 Palette RAM indexes
|
||||
.label PPU_PALETTE = $3f00
|
||||
// APU Frame Counter
|
||||
// generates low-frequency clocks for the channels and an optional 60 Hz interrupt.
|
||||
// https://wiki.nesdev.com/w/index.php/APU_Frame_Counter
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
// $4017 | W | FR_COUNTER Frame Counter Set mode and interrupt
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
// | 7 | Sequencer mode: 0 selects 4-step sequence, 1 selects 5-step sequence
|
||||
// | 6 | Interrupt inhibit flag. If set, the frame interrupt flag is cleared, otherwise it is unaffected.
|
||||
// ------+-----+---------------------------------------------------------------
|
||||
// Side effects After 3 or 4 CPU clock cycles*, the timer is reset.
|
||||
// If the mode flag is set, then both "quarter frame" and "half frame" signals are also generated.
|
||||
.label FR_COUNTER = $4017
|
||||
// Pointer to the start of RAM memory
|
||||
.label MEMORY = 0
|
||||
// OAM (Object Attribute Memory) Buffer
|
||||
// Will be transfered to the PPU via DMA
|
||||
.label OAM_BUFFER = $200
|
||||
// PPU Status Register for reading in ASM
|
||||
.label PPU_PPUSTATUS = $2002
|
||||
// NES Picture Processing Unit (PPU)
|
||||
.label PPU = $2000
|
||||
// NES CPU and audion processing unit (APU)
|
||||
.label APU = $4000
|
||||
.segment Code
|
||||
// RESET Called when the NES is reset, including when it is turned on.
|
||||
main: {
|
||||
// asm
|
||||
cld
|
||||
ldx #$ff
|
||||
txs
|
||||
// PPU->PPUCTRL = 0
|
||||
lda #0
|
||||
sta PPU
|
||||
// PPU->PPUMASK = 0
|
||||
sta PPU+OFFSET_STRUCT_RICOH_2C02_PPUMASK
|
||||
// *FR_COUNTER = 0b01000000
|
||||
lda #$40
|
||||
sta FR_COUNTER
|
||||
// APU->DMC_FREQ = 0b01000000
|
||||
sta APU+OFFSET_STRUCT_RICOH_2A03_DMC_FREQ
|
||||
// asm
|
||||
lda PPU_PPUSTATUS
|
||||
waitForVBlank1:
|
||||
// PPU->PPUSTATUS&0x80
|
||||
lda #$80
|
||||
and PPU+OFFSET_STRUCT_RICOH_2C02_PPUSTATUS
|
||||
// while(!(PPU->PPUSTATUS&0x80))
|
||||
cmp #0
|
||||
beq waitForVBlank1
|
||||
ldx #0
|
||||
__b1:
|
||||
// (MEMORY+0x000)[i] = 0
|
||||
lda #0
|
||||
sta MEMORY,x
|
||||
// (MEMORY+0x100)[i] = 0
|
||||
sta MEMORY+$100,x
|
||||
// (MEMORY+0x200)[i] = 0
|
||||
sta MEMORY+$200,x
|
||||
// (MEMORY+0x300)[i] = 0
|
||||
sta MEMORY+$300,x
|
||||
// (MEMORY+0x400)[i] = 0
|
||||
sta MEMORY+$400,x
|
||||
// (MEMORY+0x500)[i] = 0
|
||||
sta MEMORY+$500,x
|
||||
// (MEMORY+0x600)[i] = 0
|
||||
sta MEMORY+$600,x
|
||||
// (MEMORY+0x700)[i] = 0
|
||||
sta MEMORY+$700,x
|
||||
// while (++i)
|
||||
inx
|
||||
cpx #0
|
||||
bne __b1
|
||||
waitForVBlank2:
|
||||
// PPU->PPUSTATUS&0x80
|
||||
lda #$80
|
||||
and PPU+OFFSET_STRUCT_RICOH_2C02_PPUSTATUS
|
||||
// while(!(PPU->PPUSTATUS&0x80))
|
||||
cmp #0
|
||||
beq waitForVBlank2
|
||||
// initPaletteData()
|
||||
// Now the PPU is ready.
|
||||
jsr initPaletteData
|
||||
// initSpriteData()
|
||||
jsr initSpriteData
|
||||
// PPU->PPUCTRL = 0b10000000
|
||||
lda #$80
|
||||
sta PPU
|
||||
// PPU->PPUMASK = 0b00010000
|
||||
lda #$10
|
||||
sta PPU+OFFSET_STRUCT_RICOH_2C02_PPUMASK
|
||||
__b2:
|
||||
// Infinite loop
|
||||
jmp __b2
|
||||
}
|
||||
// Initialize OAM (Object Attribute Memory) Buffer
|
||||
initSpriteData: {
|
||||
ldx #0
|
||||
__b1:
|
||||
// for(char i=0;i<sizeof(SPRITES);i++)
|
||||
cpx #$10*SIZEOF_BYTE
|
||||
bcc __b2
|
||||
// }
|
||||
rts
|
||||
__b2:
|
||||
// OAM_BUFFER[i] = SPRITES[i]
|
||||
lda SPRITES,x
|
||||
sta OAM_BUFFER,x
|
||||
// for(char i=0;i<sizeof(SPRITES);i++)
|
||||
inx
|
||||
jmp __b1
|
||||
}
|
||||
// Copy palette values to PPU
|
||||
initPaletteData: {
|
||||
// asm
|
||||
// Reset the high/low latch to "high"
|
||||
lda PPU_PPUSTATUS
|
||||
// PPU->PPUADDR = >PPU_PALETTE
|
||||
// Write the high byte of PPU Palette address
|
||||
lda #>PPU_PALETTE
|
||||
sta PPU+OFFSET_STRUCT_RICOH_2C02_PPUADDR
|
||||
// PPU->PPUADDR = <PPU_PALETTE
|
||||
// Write the low byte of PPU Palette address
|
||||
lda #0
|
||||
sta PPU+OFFSET_STRUCT_RICOH_2C02_PPUADDR
|
||||
tax
|
||||
// Write to PPU
|
||||
__b1:
|
||||
// for(char i=0;i<sizeof(PALETTE);i++)
|
||||
cpx #$20*SIZEOF_BYTE
|
||||
bcc __b2
|
||||
// }
|
||||
rts
|
||||
__b2:
|
||||
// PPU->PPUDATA = PALETTE[i]
|
||||
lda PALETTE,x
|
||||
sta PPU+OFFSET_STRUCT_RICOH_2C02_PPUDATA
|
||||
// for(char i=0;i<sizeof(PALETTE);i++)
|
||||
inx
|
||||
jmp __b1
|
||||
}
|
||||
// NMI Called when the PPU refreshes the screen (also known as the V-Blank period)
|
||||
vblank: {
|
||||
pha
|
||||
txa
|
||||
pha
|
||||
tya
|
||||
pha
|
||||
// PPU->OAMADDR = 0
|
||||
// Refresh DRAM-stored sprite data before it decays.
|
||||
// Set OAM start address to sprite#0
|
||||
lda #0
|
||||
sta PPU+OFFSET_STRUCT_RICOH_2C02_OAMADDR
|
||||
// APU->OAMDMA = >OAM_BUFFER
|
||||
// Set the high byte (02) of the RAM address and start the DMA transfer to OAM memory
|
||||
lda #>OAM_BUFFER
|
||||
sta APU+OFFSET_STRUCT_RICOH_2A03_OAMDMA
|
||||
// APU->JOY1 = 1
|
||||
// Freeze the button positions.
|
||||
lda #1
|
||||
sta APU+OFFSET_STRUCT_RICOH_2A03_JOY1
|
||||
// APU->JOY1 = 0
|
||||
lda #0
|
||||
sta APU+OFFSET_STRUCT_RICOH_2A03_JOY1
|
||||
// APU->JOY1&0b00000001
|
||||
lda #1
|
||||
and APU+OFFSET_STRUCT_RICOH_2A03_JOY1
|
||||
// if(APU->JOY1&0b00000001)
|
||||
cmp #0
|
||||
beq __b1
|
||||
// moveLuigiRight()
|
||||
jsr moveLuigiRight
|
||||
__b1:
|
||||
// APU->JOY1&0b00000001
|
||||
lda #1
|
||||
and APU+OFFSET_STRUCT_RICOH_2A03_JOY1
|
||||
// if(APU->JOY1&0b00000001)
|
||||
cmp #0
|
||||
beq __breturn
|
||||
// moveLuigiLeft()
|
||||
jsr moveLuigiLeft
|
||||
__breturn:
|
||||
// }
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
rti
|
||||
}
|
||||
// move the Luigi sprites left
|
||||
moveLuigiLeft: {
|
||||
// OAM_BUFFER[0x03]--;
|
||||
dec OAM_BUFFER+3
|
||||
// OAM_BUFFER[0x07]--;
|
||||
dec OAM_BUFFER+7
|
||||
// OAM_BUFFER[0x0b]--;
|
||||
dec OAM_BUFFER+$b
|
||||
// OAM_BUFFER[0x0f]--;
|
||||
dec OAM_BUFFER+$f
|
||||
// }
|
||||
rts
|
||||
}
|
||||
// move the Luigi sprites right
|
||||
moveLuigiRight: {
|
||||
// OAM_BUFFER[0x03]++;
|
||||
inc OAM_BUFFER+3
|
||||
// OAM_BUFFER[0x07]++;
|
||||
inc OAM_BUFFER+7
|
||||
// OAM_BUFFER[0x0b]++;
|
||||
inc OAM_BUFFER+$b
|
||||
// OAM_BUFFER[0x0f]++;
|
||||
inc OAM_BUFFER+$f
|
||||
// }
|
||||
rts
|
||||
}
|
||||
.segment Data
|
||||
PALETTE: .byte $f, $31, $32, $33, $f, $35, $36, $37, $f, $39, $3a, $3b, $f, $3d, $3e, $f, $f, $1c, $15, $14, $f, 2, $38, $3c, $f, $30, $37, $1a, $f, $f, $f, $f
|
||||
// Small Luigi Sprite Data
|
||||
SPRITES: .byte $80, $36, 2, $80, $80, $37, 2, $88, $88, $38, 2, $80, $88, $39, 2, $88
|
||||
.segment Tiles
|
||||
TILES:
|
||||
.import binary "smb1_chr.bin"
|
||||
|
||||
.segment Vectors
|
||||
VECTORS: .word vblank, main, 0
|
||||
.segment NesRom
|
||||
NES_ROM:
|
||||
.segmentout [ segments="Header" ]
|
||||
.segmentout [ segments="ProgramRom" ]
|
||||
.segmentout [ segments="CharacterRom" ]
|
||||
|
148
src/test/ref/examples/nes/nes-demo.cfg
Normal file
148
src/test/ref/examples/nes/nes-demo.cfg
Normal file
@ -0,0 +1,148 @@
|
||||
@begin: scope:[] from
|
||||
[0] phi()
|
||||
to:@1
|
||||
@1: scope:[] from @begin
|
||||
[1] phi()
|
||||
[2] call main
|
||||
to:@end
|
||||
@end: scope:[] from @1
|
||||
[3] phi()
|
||||
|
||||
(void()) main()
|
||||
main: scope:[main] from @1
|
||||
asm { cld ldx#$ff txs }
|
||||
to:main::disableVideoOutput1
|
||||
main::disableVideoOutput1: scope:[main] from main
|
||||
[5] *((byte*)(const struct RICOH_2C02*) PPU) ← (byte) 0
|
||||
[6] *((byte*)(const struct RICOH_2C02*) PPU+(const byte) OFFSET_STRUCT_RICOH_2C02_PPUMASK) ← (byte) 0
|
||||
to:main::disableAudioOutput1
|
||||
main::disableAudioOutput1: scope:[main] from main::disableVideoOutput1
|
||||
[7] *((const nomodify byte*) FR_COUNTER) ← (byte) $40
|
||||
[8] *((byte*)(const struct RICOH_2A03*) APU+(const byte) OFFSET_STRUCT_RICOH_2A03_DMC_FREQ) ← (byte) $40
|
||||
to:main::clearVBlankFlag1
|
||||
main::clearVBlankFlag1: scope:[main] from main::disableAudioOutput1
|
||||
asm { ldaPPU_PPUSTATUS }
|
||||
to:main::waitForVBlank1
|
||||
main::waitForVBlank1: scope:[main] from main::clearVBlankFlag1
|
||||
[10] phi()
|
||||
to:main::waitForVBlank1_@1
|
||||
main::waitForVBlank1_@1: scope:[main] from main::waitForVBlank1 main::waitForVBlank1_@1
|
||||
[11] (byte~) main::waitForVBlank1_$0 ← *((byte*)(const struct RICOH_2C02*) PPU+(const byte) OFFSET_STRUCT_RICOH_2C02_PPUSTATUS) & (byte) $80
|
||||
[12] if((byte) 0==(byte~) main::waitForVBlank1_$0) goto main::waitForVBlank1_@1
|
||||
to:main::@1
|
||||
main::@1: scope:[main] from main::@1 main::waitForVBlank1_@1
|
||||
[13] (byte) main::i#2 ← phi( main::@1/(byte) main::i#1 main::waitForVBlank1_@1/(byte) 0 )
|
||||
[14] *((const nomodify byte*) MEMORY + (byte) main::i#2) ← (byte) 0
|
||||
[15] *((const nomodify byte*) MEMORY+(word) $100 + (byte) main::i#2) ← (byte) 0
|
||||
[16] *((const nomodify byte*) MEMORY+(word) $200 + (byte) main::i#2) ← (byte) 0
|
||||
[17] *((const nomodify byte*) MEMORY+(word) $300 + (byte) main::i#2) ← (byte) 0
|
||||
[18] *((const nomodify byte*) MEMORY+(word) $400 + (byte) main::i#2) ← (byte) 0
|
||||
[19] *((const nomodify byte*) MEMORY+(word) $500 + (byte) main::i#2) ← (byte) 0
|
||||
[20] *((const nomodify byte*) MEMORY+(word) $600 + (byte) main::i#2) ← (byte) 0
|
||||
[21] *((const nomodify byte*) MEMORY+(word) $700 + (byte) main::i#2) ← (byte) 0
|
||||
[22] (byte) main::i#1 ← ++ (byte) main::i#2
|
||||
[23] if((byte) 0!=(byte) main::i#1) goto main::@1
|
||||
to:main::waitForVBlank2
|
||||
main::waitForVBlank2: scope:[main] from main::@1
|
||||
[24] phi()
|
||||
to:main::waitForVBlank2_@1
|
||||
main::waitForVBlank2_@1: scope:[main] from main::waitForVBlank2 main::waitForVBlank2_@1
|
||||
[25] (byte~) main::waitForVBlank2_$0 ← *((byte*)(const struct RICOH_2C02*) PPU+(const byte) OFFSET_STRUCT_RICOH_2C02_PPUSTATUS) & (byte) $80
|
||||
[26] if((byte) 0==(byte~) main::waitForVBlank2_$0) goto main::waitForVBlank2_@1
|
||||
to:main::@3
|
||||
main::@3: scope:[main] from main::waitForVBlank2_@1
|
||||
[27] phi()
|
||||
[28] call initPaletteData
|
||||
to:main::@4
|
||||
main::@4: scope:[main] from main::@3
|
||||
[29] phi()
|
||||
[30] call initSpriteData
|
||||
to:main::enableVideoOutput1
|
||||
main::enableVideoOutput1: scope:[main] from main::@4
|
||||
[31] *((byte*)(const struct RICOH_2C02*) PPU) ← (byte) $80
|
||||
[32] *((byte*)(const struct RICOH_2C02*) PPU+(const byte) OFFSET_STRUCT_RICOH_2C02_PPUMASK) ← (byte) $10
|
||||
to:main::@2
|
||||
main::@2: scope:[main] from main::@2 main::enableVideoOutput1
|
||||
[33] phi()
|
||||
to:main::@2
|
||||
|
||||
(void()) initSpriteData()
|
||||
initSpriteData: scope:[initSpriteData] from main::@4
|
||||
[34] phi()
|
||||
to:initSpriteData::@1
|
||||
initSpriteData::@1: scope:[initSpriteData] from initSpriteData initSpriteData::@2
|
||||
[35] (byte) initSpriteData::i#2 ← phi( initSpriteData/(byte) 0 initSpriteData::@2/(byte) initSpriteData::i#1 )
|
||||
[36] if((byte) initSpriteData::i#2<(byte) $10*(const byte) SIZEOF_BYTE) goto initSpriteData::@2
|
||||
to:initSpriteData::@return
|
||||
initSpriteData::@return: scope:[initSpriteData] from initSpriteData::@1
|
||||
[37] return
|
||||
to:@return
|
||||
initSpriteData::@2: scope:[initSpriteData] from initSpriteData::@1
|
||||
[38] *((const nomodify byte*) OAM_BUFFER + (byte) initSpriteData::i#2) ← *((const byte*) SPRITES + (byte) initSpriteData::i#2)
|
||||
[39] (byte) initSpriteData::i#1 ← ++ (byte) initSpriteData::i#2
|
||||
to:initSpriteData::@1
|
||||
|
||||
(void()) initPaletteData()
|
||||
initPaletteData: scope:[initPaletteData] from main::@3
|
||||
asm { ldaPPU_PPUSTATUS }
|
||||
[41] *((byte*)(const struct RICOH_2C02*) PPU+(const byte) OFFSET_STRUCT_RICOH_2C02_PPUADDR) ← >(const nomodify byte*) PPU_PALETTE
|
||||
[42] *((byte*)(const struct RICOH_2C02*) PPU+(const byte) OFFSET_STRUCT_RICOH_2C02_PPUADDR) ← (byte) 0
|
||||
to:initPaletteData::@1
|
||||
initPaletteData::@1: scope:[initPaletteData] from initPaletteData initPaletteData::@2
|
||||
[43] (byte) initPaletteData::i#2 ← phi( initPaletteData/(byte) 0 initPaletteData::@2/(byte) initPaletteData::i#1 )
|
||||
[44] if((byte) initPaletteData::i#2<(byte) $20*(const byte) SIZEOF_BYTE) goto initPaletteData::@2
|
||||
to:initPaletteData::@return
|
||||
initPaletteData::@return: scope:[initPaletteData] from initPaletteData::@1
|
||||
[45] return
|
||||
to:@return
|
||||
initPaletteData::@2: scope:[initPaletteData] from initPaletteData::@1
|
||||
[46] *((byte*)(const struct RICOH_2C02*) PPU+(const byte) OFFSET_STRUCT_RICOH_2C02_PPUDATA) ← *((const byte*) PALETTE + (byte) initPaletteData::i#2)
|
||||
[47] (byte) initPaletteData::i#1 ← ++ (byte) initPaletteData::i#2
|
||||
to:initPaletteData::@1
|
||||
|
||||
interrupt(HARDWARE_STACK)(void()) vblank()
|
||||
vblank: scope:[vblank] from
|
||||
[48] *((byte*)(const struct RICOH_2C02*) PPU+(const byte) OFFSET_STRUCT_RICOH_2C02_OAMADDR) ← (byte) 0
|
||||
[49] *((byte*)(const struct RICOH_2A03*) APU+(const byte) OFFSET_STRUCT_RICOH_2A03_OAMDMA) ← >(const nomodify byte*) OAM_BUFFER
|
||||
[50] *((byte*)(const struct RICOH_2A03*) APU+(const byte) OFFSET_STRUCT_RICOH_2A03_JOY1) ← (byte) 1
|
||||
[51] *((byte*)(const struct RICOH_2A03*) APU+(const byte) OFFSET_STRUCT_RICOH_2A03_JOY1) ← (byte) 0
|
||||
[52] (byte~) vblank::$1 ← *((byte*)(const struct RICOH_2A03*) APU+(const byte) OFFSET_STRUCT_RICOH_2A03_JOY1) & (byte) 1
|
||||
[53] if((byte) 0==(byte~) vblank::$1) goto vblank::@1
|
||||
to:vblank::@2
|
||||
vblank::@2: scope:[vblank] from vblank
|
||||
[54] phi()
|
||||
[55] call moveLuigiRight
|
||||
to:vblank::@1
|
||||
vblank::@1: scope:[vblank] from vblank vblank::@2
|
||||
[56] (byte~) vblank::$3 ← *((byte*)(const struct RICOH_2A03*) APU+(const byte) OFFSET_STRUCT_RICOH_2A03_JOY1) & (byte) 1
|
||||
[57] if((byte) 0==(byte~) vblank::$3) goto vblank::@return
|
||||
to:vblank::@3
|
||||
vblank::@3: scope:[vblank] from vblank::@1
|
||||
[58] phi()
|
||||
[59] call moveLuigiLeft
|
||||
to:vblank::@return
|
||||
vblank::@return: scope:[vblank] from vblank::@1 vblank::@3
|
||||
[60] return
|
||||
to:@return
|
||||
|
||||
(void()) moveLuigiLeft()
|
||||
moveLuigiLeft: scope:[moveLuigiLeft] from vblank::@3
|
||||
[61] *((const nomodify byte*) OAM_BUFFER+(byte) 3) ← -- *((const nomodify byte*) OAM_BUFFER+(byte) 3)
|
||||
[62] *((const nomodify byte*) OAM_BUFFER+(byte) 7) ← -- *((const nomodify byte*) OAM_BUFFER+(byte) 7)
|
||||
[63] *((const nomodify byte*) OAM_BUFFER+(byte) $b) ← -- *((const nomodify byte*) OAM_BUFFER+(byte) $b)
|
||||
[64] *((const nomodify byte*) OAM_BUFFER+(byte) $f) ← -- *((const nomodify byte*) OAM_BUFFER+(byte) $f)
|
||||
to:moveLuigiLeft::@return
|
||||
moveLuigiLeft::@return: scope:[moveLuigiLeft] from moveLuigiLeft
|
||||
[65] return
|
||||
to:@return
|
||||
|
||||
(void()) moveLuigiRight()
|
||||
moveLuigiRight: scope:[moveLuigiRight] from vblank::@2
|
||||
[66] *((const nomodify byte*) OAM_BUFFER+(byte) 3) ← ++ *((const nomodify byte*) OAM_BUFFER+(byte) 3)
|
||||
[67] *((const nomodify byte*) OAM_BUFFER+(byte) 7) ← ++ *((const nomodify byte*) OAM_BUFFER+(byte) 7)
|
||||
[68] *((const nomodify byte*) OAM_BUFFER+(byte) $b) ← ++ *((const nomodify byte*) OAM_BUFFER+(byte) $b)
|
||||
[69] *((const nomodify byte*) OAM_BUFFER+(byte) $f) ← ++ *((const nomodify byte*) OAM_BUFFER+(byte) $f)
|
||||
to:moveLuigiRight::@return
|
||||
moveLuigiRight::@return: scope:[moveLuigiRight] from moveLuigiRight
|
||||
[70] return
|
||||
to:@return
|
2571
src/test/ref/examples/nes/nes-demo.log
Normal file
2571
src/test/ref/examples/nes/nes-demo.log
Normal file
File diff suppressed because it is too large
Load Diff
111
src/test/ref/examples/nes/nes-demo.sym
Normal file
111
src/test/ref/examples/nes/nes-demo.sym
Normal file
@ -0,0 +1,111 @@
|
||||
(label) @1
|
||||
(label) @begin
|
||||
(label) @end
|
||||
(const struct RICOH_2A03*) APU = (struct RICOH_2A03*) 16384
|
||||
(const nomodify byte*) FR_COUNTER = (byte*) 16407
|
||||
(const nomodify byte*) MEMORY = (byte*) 0
|
||||
(const byte*) NES_ROM[] = kickasm {{ .segmentout [ segments="Header" ]
|
||||
.segmentout [ segments="ProgramRom" ]
|
||||
.segmentout [ segments="CharacterRom" ]
|
||||
}}
|
||||
(const nomodify byte*) OAM_BUFFER = (byte*) 512
|
||||
(const byte) OFFSET_STRUCT_RICOH_2A03_DMC_FREQ = (byte) $10
|
||||
(const byte) OFFSET_STRUCT_RICOH_2A03_JOY1 = (byte) $16
|
||||
(const byte) OFFSET_STRUCT_RICOH_2A03_OAMDMA = (byte) $14
|
||||
(const byte) OFFSET_STRUCT_RICOH_2C02_OAMADDR = (byte) 3
|
||||
(const byte) OFFSET_STRUCT_RICOH_2C02_PPUADDR = (byte) 6
|
||||
(const byte) OFFSET_STRUCT_RICOH_2C02_PPUDATA = (byte) 7
|
||||
(const byte) OFFSET_STRUCT_RICOH_2C02_PPUMASK = (byte) 1
|
||||
(const byte) OFFSET_STRUCT_RICOH_2C02_PPUSTATUS = (byte) 2
|
||||
(const byte*) PALETTE[(number) $20] = { (byte) $f, (byte) $31, (byte) $32, (byte) $33, (byte) $f, (byte) $35, (byte) $36, (byte) $37, (byte) $f, (byte) $39, (byte) $3a, (byte) $3b, (byte) $f, (byte) $3d, (byte) $3e, (byte) $f, (byte) $f, (byte) $1c, (byte) $15, (byte) $14, (byte) $f, (byte) 2, (byte) $38, (byte) $3c, (byte) $f, (byte) $30, (byte) $37, (byte) $1a, (byte) $f, (byte) $f, (byte) $f, (byte) $f }
|
||||
(const struct RICOH_2C02*) PPU = (struct RICOH_2C02*) 8192
|
||||
(const nomodify byte*) PPU_PALETTE = (byte*) 16128
|
||||
(const to_volatile byte*) PPU_PPUSTATUS = (byte*) 8194
|
||||
(byte) RICOH_2A03::DMC_FREQ
|
||||
(byte) RICOH_2A03::DMC_LEN
|
||||
(byte) RICOH_2A03::DMC_RAW
|
||||
(byte) RICOH_2A03::DMC_START
|
||||
(byte) RICOH_2A03::JOY1
|
||||
(byte) RICOH_2A03::JOY2
|
||||
(byte) RICOH_2A03::NOISE_HI
|
||||
(byte) RICOH_2A03::NOISE_LO
|
||||
(byte) RICOH_2A03::NOISE_VOL
|
||||
(byte) RICOH_2A03::OAMDMA
|
||||
(byte) RICOH_2A03::SND_CHN
|
||||
(byte) RICOH_2A03::SQ1_HI
|
||||
(byte) RICOH_2A03::SQ1_LO
|
||||
(byte) RICOH_2A03::SQ1_SWEEP
|
||||
(byte) RICOH_2A03::SQ1_VOL
|
||||
(byte) RICOH_2A03::SQ2_HI
|
||||
(byte) RICOH_2A03::SQ2_LO
|
||||
(byte) RICOH_2A03::SQ2_SWEEP
|
||||
(byte) RICOH_2A03::SQ2_VOL
|
||||
(byte) RICOH_2A03::TRI_HI
|
||||
(byte) RICOH_2A03::TRI_LINEAR
|
||||
(byte) RICOH_2A03::TRI_LO
|
||||
(byte) RICOH_2A03::UNUSED1
|
||||
(byte) RICOH_2A03::UNUSED2
|
||||
(byte) RICOH_2C02::OAMADDR
|
||||
(byte) RICOH_2C02::OAMDATA
|
||||
(byte) RICOH_2C02::PPUADDR
|
||||
(byte) RICOH_2C02::PPUCTRL
|
||||
(byte) RICOH_2C02::PPUDATA
|
||||
(byte) RICOH_2C02::PPUMASK
|
||||
(byte) RICOH_2C02::PPUSCROLL
|
||||
(volatile byte) RICOH_2C02::PPUSTATUS loadstore
|
||||
(const byte) SIZEOF_BYTE = (byte) 1
|
||||
(const byte*) SPRITES[] = { (byte) $80, (byte) $36, (byte) 2, (byte) $80, (byte) $80, (byte) $37, (byte) 2, (byte) $88, (byte) $88, (byte) $38, (byte) 2, (byte) $80, (byte) $88, (byte) $39, (byte) 2, (byte) $88 }
|
||||
(const byte*) TILES[] = kickasm {{ .import binary "smb1_chr.bin"
|
||||
}}
|
||||
(const to_nomodify void()**) VECTORS[] = { &interrupt(HARDWARE_STACK)(void()) vblank(), &(void()) main(), (void()*) 0 }
|
||||
(void()) initPaletteData()
|
||||
(label) initPaletteData::@1
|
||||
(label) initPaletteData::@2
|
||||
(label) initPaletteData::@return
|
||||
(byte) initPaletteData::i
|
||||
(byte) initPaletteData::i#1 reg byte x 2002.0
|
||||
(byte) initPaletteData::i#2 reg byte x 1334.6666666666667
|
||||
(void()) initSpriteData()
|
||||
(label) initSpriteData::@1
|
||||
(label) initSpriteData::@2
|
||||
(label) initSpriteData::@return
|
||||
(byte) initSpriteData::i
|
||||
(byte) initSpriteData::i#1 reg byte x 2002.0
|
||||
(byte) initSpriteData::i#2 reg byte x 1668.3333333333335
|
||||
(void()) main()
|
||||
(label) main::@1
|
||||
(label) main::@2
|
||||
(label) main::@3
|
||||
(label) main::@4
|
||||
(label) main::clearVBlankFlag1
|
||||
(label) main::disableAudioOutput1
|
||||
(label) main::disableVideoOutput1
|
||||
(label) main::enableVideoOutput1
|
||||
(byte) main::i
|
||||
(byte) main::i#1 reg byte x 151.5
|
||||
(byte) main::i#2 reg byte x 112.22222222222223
|
||||
(label) main::waitForVBlank1
|
||||
(byte~) main::waitForVBlank1_$0 reg byte a 202.0
|
||||
(label) main::waitForVBlank1_@1
|
||||
(label) main::waitForVBlank2
|
||||
(byte~) main::waitForVBlank2_$0 reg byte a 202.0
|
||||
(label) main::waitForVBlank2_@1
|
||||
(void()) moveLuigiLeft()
|
||||
(label) moveLuigiLeft::@return
|
||||
(void()) moveLuigiRight()
|
||||
(label) moveLuigiRight::@return
|
||||
interrupt(HARDWARE_STACK)(void()) vblank()
|
||||
(byte~) vblank::$1 reg byte a 4.0
|
||||
(byte~) vblank::$3 reg byte a 4.0
|
||||
(label) vblank::@1
|
||||
(label) vblank::@2
|
||||
(label) vblank::@3
|
||||
(label) vblank::@return
|
||||
|
||||
reg byte x [ main::i#2 main::i#1 ]
|
||||
reg byte x [ initSpriteData::i#2 initSpriteData::i#1 ]
|
||||
reg byte x [ initPaletteData::i#2 initPaletteData::i#1 ]
|
||||
reg byte a [ main::waitForVBlank1_$0 ]
|
||||
reg byte a [ main::waitForVBlank2_$0 ]
|
||||
reg byte a [ vblank::$1 ]
|
||||
reg byte a [ vblank::$3 ]
|
@ -2,6 +2,9 @@
|
||||
"folders": [
|
||||
{
|
||||
"path": "../kc"
|
||||
},
|
||||
{
|
||||
"path": "../../../../tmp/first_nes"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
|
Loading…
Reference in New Issue
Block a user