commit
35b687b916
@ -0,0 +1,6 @@ |
|||||||
|
media |
||||||
|
exec |
||||||
|
*log* |
||||||
|
*cache* |
||||||
|
*.patch |
||||||
|
*.out |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
all: |
||||||
|
clang bus.c cpu.c cartridge.c -o exec
|
||||||
|
|
||||||
|
clean: |
||||||
|
rm exec
|
||||||
|
|
||||||
|
debug: |
||||||
|
clang -static -g -DDEBUG bus.c cpu.c cartridge.c -o exec
|
||||||
@ -0,0 +1,143 @@ |
|||||||
|
#include "bus.h" |
||||||
|
|
||||||
|
bool debug = true; |
||||||
|
|
||||||
|
|
||||||
|
unsigned char bus[BUS_SIZE]; //this is the bus array
|
||||||
|
|
||||||
|
//Importing the different electrical components of the NES as definitions, not yet docked to the bus
|
||||||
|
|
||||||
|
#include "cpu.h" |
||||||
|
|
||||||
|
#include "cartridge.h" |
||||||
|
|
||||||
|
|
||||||
|
word mapper_resolve_read(word address){ //info about what regions are mirrored can be found on the nes dev wiki
|
||||||
|
|
||||||
|
if(address <= 0x1FFF && address >= 0x0800) |
||||||
|
address %= 0x0800; |
||||||
|
|
||||||
|
if(address <= 0x3FFF && address >= 0x2008) |
||||||
|
address = ((address - 0x2000) % 0x8) + 0x2000; |
||||||
|
|
||||||
|
return address; |
||||||
|
} |
||||||
|
|
||||||
|
word mapper_resolve_write(word address, byte data){ //info about what regions are mirrored can be found on the nes dev wiki
|
||||||
|
|
||||||
|
if(address <= 0x1FFF && address >= 0x0800) |
||||||
|
address %= 0x0800; |
||||||
|
|
||||||
|
if(address <= 0x3FFF && address >= 0x2008) |
||||||
|
address = ((address - 0x2000) % 0x8) + 0x2000; |
||||||
|
|
||||||
|
return address; |
||||||
|
} |
||||||
|
|
||||||
|
void bus_write8(word address, word data){ |
||||||
|
if(!mapper000_write(address, data, false)){ //first thing we do is we hand the operation to the mapper to resolve any cartridge-side bank switching and mirroring, if the address we wanna write to isnt on the cartridge, we return false and we write to the bus normally
|
||||||
|
address = mapper_resolve_write(address, data); //a lot of regions on the NES bus are mirrored/synced, this just ensures we are always writing to the parent region, not to a empty cloned one
|
||||||
|
printf("\nwrite-ram 0x%04X at 0x%04X\n; old_val 0x%04X", data, address, bus_read8(address)); |
||||||
|
bus[address] = (unsigned char)data; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
word bus_read8(word address){ |
||||||
|
word data; |
||||||
|
if((data = mapper000_read(address, false)) >= 0x100){ //we first ask the mapper to read the data from the address for us in case its on the cartridge, if it returns 0x100 (0xFF + 1 aka impossible to get from reading a byte) that means the data stored at that address is not on the cartridge, but rather on the nes memory, thus we hand the job over to the bus
|
||||||
|
address = mapper_resolve_read(address); //a lot of regions on the NES bus are mirrored/synced, this just ensures we are always writing to the parent region, not to a empty cloned one
|
||||||
|
return bus[address]; |
||||||
|
}else{ |
||||||
|
return data; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
word bus_read16(word address){ |
||||||
|
word d = bus_read8(address); //read msb
|
||||||
|
d <<= 8; //put msb in the msb section
|
||||||
|
d |= bus_read8(address+1); //read lsb
|
||||||
|
return d; //return whole word
|
||||||
|
} |
||||||
|
|
||||||
|
void bus_write16(word address, word data){ |
||||||
|
bus_write8(address, data >> 8); //write msb
|
||||||
|
bus_write8(address + 1, data & 0b00001111); //write lsb
|
||||||
|
} |
||||||
|
|
||||||
|
void dump_bus(){ |
||||||
|
FILE *fdump; |
||||||
|
fdump = fopen("dumpfile", "w"); |
||||||
|
|
||||||
|
#ifdef DEBUG |
||||||
|
if(fdump == NULL){ |
||||||
|
fprintf(stderr, "ERR: Could not access file! Do I have permissions to create a file?\n"); |
||||||
|
exit(EXIT_FAILURE); |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
//If the file was successfuly opened then this code will run
|
||||||
|
for(unsigned long long i = 0; i <= 0xFFFF; i++)
|
||||||
|
fprintf(fdump, "%c", bus_read8(i)); |
||||||
|
} |
||||||
|
|
||||||
|
byte debug_read_do_not_use_pls(word address){ |
||||||
|
return bus[address]; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
static inline void debug_print_instruction(CPU* __restrict__ cpu, byte opcode){ |
||||||
|
printf("\n--name: %s opcode: %02X address: %04X %d %p\n",
|
||||||
|
cpu->opcodes[opcode].name,
|
||||||
|
opcode,
|
||||||
|
cpu->PC,
|
||||||
|
cpu->opcodes[opcode].bytes,
|
||||||
|
&cpu->opcodes[opcode].microcode |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
#define PROG_START_ADDR 0xC000 |
||||||
|
|
||||||
|
int main(int argc, char * argv[]){ |
||||||
|
CPU * cpu = (CPU*)malloc(sizeof(CPU)); //create new CPU
|
||||||
|
#ifdef DEBUG |
||||||
|
if(cpu == NULL){ |
||||||
|
fprintf(stderr, "ERR: Out of RAM!\n"); |
||||||
|
exit(EXIT_FAILURE); |
||||||
|
} |
||||||
|
#endif |
||||||
|
init_cpu(cpu); //put new CPU in starting mode and dock it to the bus
|
||||||
|
cpu->PC = PROG_START_ADDR; |
||||||
|
|
||||||
|
if(argc <= 1){ //Check to see if a rom was given
|
||||||
|
fprintf(stderr, "ERR: No Rom file Specified in Arguments\n"); |
||||||
|
exit(EXIT_FAILURE); |
||||||
|
} |
||||||
|
|
||||||
|
//load the CHR and PRG banks from the .nes file (argv[1]), also loads the header for mapper construction
|
||||||
|
init_banks(argv[1]); |
||||||
|
debug = true; |
||||||
|
|
||||||
|
#ifdef DEBUG |
||||||
|
//Test to see if writing to the bus is working correctly
|
||||||
|
bus_write8(0x0000, 0xFF); |
||||||
|
printf("\nCACA CACA %02X %02X\n", bus_read8(0x0000), bus[0x0000]); |
||||||
|
#endif |
||||||
|
|
||||||
|
for(long iterations = 0; iterations != 3500; iterations++){ |
||||||
|
debug_print_instruction(cpu, bus_read8(cpu->PC)); |
||||||
|
print_registers(cpu); |
||||||
|
print_cpu(cpu);
|
||||||
|
dump_bus(); |
||||||
|
cpu_clock(cpu); |
||||||
|
fprintf(stderr, "\n\n----\n%i\n-----", iterations); |
||||||
|
} |
||||||
|
|
||||||
|
debug = false; |
||||||
|
|
||||||
|
printf("\n~~~~~~~~~~~~~~~~~~~~\n"); |
||||||
|
print_cpu(cpu); |
||||||
|
printf("\nREGISTERS:\n\n"); |
||||||
|
print_registers(cpu); |
||||||
|
dump_bus(); |
||||||
|
} |
||||||
@ -0,0 +1,17 @@ |
|||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include "common.h" |
||||||
|
|
||||||
|
|
||||||
|
#define BUS_SIZE 0xFFFF |
||||||
|
|
||||||
|
byte debug_read_do_not_use_pls(word address); |
||||||
|
|
||||||
|
void bus_write8(word address, word data); |
||||||
|
word bus_read8(word adress); |
||||||
|
word bus_read16(word address); |
||||||
|
void bus_write16(word address, word data); |
||||||
|
|
||||||
|
void dump_bus(); |
||||||
|
word mapper_resolve(word address); //gets defined in the cartridge mapper circuit code bcuz it differs from cart to cart
|
||||||
|
|
||||||
@ -0,0 +1,71 @@ |
|||||||
|
#include "cartridge.h" |
||||||
|
#include "bus.h" |
||||||
|
|
||||||
|
HEADER Header; |
||||||
|
byte PRGROM[0xFFFF]; |
||||||
|
byte CHRROM[0xFFFF]; |
||||||
|
|
||||||
|
word not_handling_this = 0x100; //0xFF + 1
|
||||||
|
|
||||||
|
void load_romfile_header(FILE * romfile){ |
||||||
|
byte verification_token[3] = "NES"; |
||||||
|
for(byte i = 0; i < 3; i++) |
||||||
|
if(verification_token[i] != getc(romfile)) return; |
||||||
|
getc(romfile); //get over the DOS EOF byte
|
||||||
|
Header.PRG_BANKS = getc(romfile); |
||||||
|
Header.CHR_BANKS = getc(romfile); |
||||||
|
|
||||||
|
for(byte i = 0; i < 5; i++){ |
||||||
|
Header.flags.array[i] = getc(romfile); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void init_banks(char name[]){ |
||||||
|
FILE * romfile; |
||||||
|
romfile = fopen(name, "r"); |
||||||
|
|
||||||
|
#ifdef DEBUG |
||||||
|
if(romfile == NULL){ |
||||||
|
fprintf(stderr, "ERR: file \"%s\" could not be opened for reading!\n", name); |
||||||
|
exit(EXIT_FAILURE); |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
load_romfile_header(romfile); |
||||||
|
|
||||||
|
if(Header.flags.flag6.trainer) //if trainer data
|
||||||
|
fseek(romfile, 512, SEEK_CUR); |
||||||
|
|
||||||
|
|
||||||
|
for(word i = 0; i < GET_PRG_BANK_SIZE(Header.PRG_BANKS); i++){ |
||||||
|
PRGROM[i] = getc(romfile); |
||||||
|
} |
||||||
|
|
||||||
|
for(word i = 0; i < GET_CHR_BANK_SIZE(Header.CHR_BANKS); i++){ |
||||||
|
CHRROM[i] = getc(romfile); |
||||||
|
} |
||||||
|
|
||||||
|
fclose(romfile); |
||||||
|
} |
||||||
|
|
||||||
|
word mapper000_read(word address, bool ppu){ |
||||||
|
if(!ppu){ //if not ppu talking
|
||||||
|
if(!((address <= 0xFFFF) && (address >= 0x8000))){ //if not in the address range the mapper functions in
|
||||||
|
return not_handling_this; //not_handling_this is a 16 bit data integer, no byte will return this ever so this is our "not handling this" return code
|
||||||
|
}else{ |
||||||
|
if(Header.PRG_BANKS > 1){ //32K model
|
||||||
|
return PRGROM[address - 0x8000 + 0x4]; |
||||||
|
}else{ //16K model
|
||||||
|
return PRGROM[(address - 0x8000 + 0x4) % 0x3FFF]; |
||||||
|
} |
||||||
|
} |
||||||
|
}else{ //if PPU
|
||||||
|
if(Header.CHR_BANKS == 0) return not_handling_this; //if no CHR banks, nothing to mirror
|
||||||
|
else if (address < 0x2000) return CHRROM[address]; |
||||||
|
else return not_handling_this; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
bool mapper000_write(word address, byte data, bool ppu){ |
||||||
|
return false; //no PRGRAM configured in this cartridge, so all writes get denied
|
||||||
|
}; |
||||||
@ -0,0 +1,77 @@ |
|||||||
|
#include "stdlib.h" |
||||||
|
#include "common.h" |
||||||
|
|
||||||
|
//extern? void (*mapper_read)(word);
|
||||||
|
//void (*mapper_write)(bool);
|
||||||
|
|
||||||
|
|
||||||
|
#define K_SIZE 1024 |
||||||
|
|
||||||
|
#define PRG_BANK_SIZE (16 * K_SIZE) |
||||||
|
#define CHR_BANK_SIZE (8 * K_SIZE) |
||||||
|
|
||||||
|
#define DEFAULT_PRG_SIZE (PRG_BANK_SIZE * 1) |
||||||
|
#define DEFAULT_CHR_SIZE (CHR_BANK_SIZE * 1) |
||||||
|
|
||||||
|
#define GET_PRG_BANK_SIZE(banks) (PRG_BANK_SIZE * banks) |
||||||
|
#define GET_CHR_BANK_SIZE(banks) (CHR_BANK_SIZE * banks) |
||||||
|
|
||||||
|
enum FLAG6_MIRRORING_MODE{ |
||||||
|
VERTICAL, |
||||||
|
HORIZONTAL |
||||||
|
}; |
||||||
|
|
||||||
|
enum FLAG9_TV_MODE{ |
||||||
|
NTSC, |
||||||
|
PAL |
||||||
|
}; |
||||||
|
|
||||||
|
typedef union { |
||||||
|
struct{ |
||||||
|
struct { |
||||||
|
byte mirroring_mode : 1; |
||||||
|
byte battery : 1; |
||||||
|
byte trainer : 1; |
||||||
|
byte disable_mirroring : 1; |
||||||
|
|
||||||
|
byte lower_mapper_number : 4; |
||||||
|
}flag6; |
||||||
|
|
||||||
|
struct { |
||||||
|
byte unisystem : 1; |
||||||
|
byte play_choice_10 : 1; |
||||||
|
byte INes_version : 2; |
||||||
|
byte disable_mirroring : 1; |
||||||
|
|
||||||
|
byte upper_mapper_number : 4; |
||||||
|
}flag7; |
||||||
|
|
||||||
|
struct { |
||||||
|
byte PRG_RAM_SIZE; |
||||||
|
}flag8; |
||||||
|
|
||||||
|
struct { |
||||||
|
byte tv_mode : 1; |
||||||
|
byte padding : 7; |
||||||
|
}flag9; |
||||||
|
}; |
||||||
|
|
||||||
|
byte array[5]; |
||||||
|
}Flags; |
||||||
|
|
||||||
|
|
||||||
|
typedef struct{ |
||||||
|
byte PRG_BANKS; |
||||||
|
byte CHR_BANKS; |
||||||
|
|
||||||
|
Flags flags; |
||||||
|
|
||||||
|
byte padding[5]; |
||||||
|
}HEADER; |
||||||
|
|
||||||
|
|
||||||
|
void init_banks(char * fn); |
||||||
|
|
||||||
|
word mapper000_read(word address, bool ppu); |
||||||
|
bool mapper000_write(word address, byte data, bool ppu); |
||||||
|
|
||||||
@ -0,0 +1,5 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
typedef unsigned char byte; //8bit (byte) variable type
|
||||||
|
typedef unsigned short word; //16bit (2byte) variable type
|
||||||
|
typedef enum {false, true} bool; |
||||||
@ -0,0 +1,67 @@ |
|||||||
|
#include "common.h" |
||||||
|
|
||||||
|
//Edit these defines to determine the debug outputs
|
||||||
|
|
||||||
|
//#define DEBUG //General debug things such as running out of memory
|
||||||
|
//#define DISABLE_LDA_DEBUG //LDA had a error in development and this flag is used to disable error reporting from LDA
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
#define STACK_RAM_OFFSET 0x100 |
||||||
|
|
||||||
|
typedef union{ |
||||||
|
struct var_t{ |
||||||
|
byte Carry : 1; |
||||||
|
byte Zero : 1; |
||||||
|
byte Interrupt : 1; |
||||||
|
byte Decimal : 1; |
||||||
|
byte Break : 1; |
||||||
|
byte ignored : 1; |
||||||
|
byte Overflow : 1; |
||||||
|
byte Negative : 1; |
||||||
|
}flags; |
||||||
|
|
||||||
|
byte data; |
||||||
|
}SR_t;
|
||||||
|
|
||||||
|
|
||||||
|
//this is a union, 'flags' (the one with individual bits) and 'data' are synced, to write a byte to the SR write to data, to flip a individual bit write to flags
|
||||||
|
// aka to set negative to false you do "cpu.SR.flags.Negative = 0;", to write a whole byte to the SR, where each bit of the byte represents a flip bit (negative, overflow, etc) do "cpu.SR.data = byte_data;"
|
||||||
|
|
||||||
|
typedef struct{ |
||||||
|
word address; |
||||||
|
word value; |
||||||
|
} busTransaction; //this is the type we use for our addressing types, they resolve to this type which contains a value and address
|
||||||
|
|
||||||
|
|
||||||
|
struct instruction; |
||||||
|
|
||||||
|
typedef struct CPU{ |
||||||
|
word PC; //Program Counter
|
||||||
|
byte A; //Accumulator
|
||||||
|
byte X; //X reg
|
||||||
|
byte Y; //Y reg
|
||||||
|
SR_t SR; //Stack register
|
||||||
|
byte SP; //Stack pointer
|
||||||
|
|
||||||
|
struct instruction * opcodes; //opcodes struct;
|
||||||
|
|
||||||
|
//not part of the ACTUAL cpu, but useful flags I use for emulation
|
||||||
|
bool pcNeedsInc; //checks if CPU needs its PC incremented by the number of bytes of the opcode (all opcodes except the sub-routine ones, look for the flag in the code)
|
||||||
|
}CPU; |
||||||
|
|
||||||
|
struct instruction{ |
||||||
|
void (*microcode)(struct CPU *, word bytes, busTransaction (*addressing)(CPU *, word)); //generic definition for opcode funcs such as BRK, ORA, etc
|
||||||
|
busTransaction (*mode)(struct CPU * cpu, word bytes); //check line 85
|
||||||
|
char * name; |
||||||
|
char bytes; |
||||||
|
char cycles; |
||||||
|
}; |
||||||
|
|
||||||
|
void print_cpu(CPU * cpu); //debug functions
|
||||||
|
void raise_error(unsigned int err, CPU * cpu); |
||||||
|
void handle_errors(CPU * cpu); |
||||||
|
void print_registers(CPU * cpu); |
||||||
|
|
||||||
|
void cpu_clock(CPU * cpu); //tick function
|
||||||
|
void init_cpu(CPU * cpu); //init cpu, allocate memory for opcode register and set flags to initial state
|
||||||
@ -0,0 +1,130 @@ |
|||||||
|
#include "ppu.h" |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void ppuWrite(PPU * Ppu, word address, byte data){ |
||||||
|
address -= 0x2000; |
||||||
|
switch(address){ |
||||||
|
case 0: //ppuctrl
|
||||||
|
|
||||||
|
Ppu->control.full = data; |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
case 1: //ppumask
|
||||||
|
|
||||||
|
Ppu->mask.full = data; |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
case 2: //ppustatus
|
||||||
|
|
||||||
|
Ppu->status.full = data; |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
case 3: //oamAddress
|
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
case 4: //oamData
|
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
case 5: //ppuScroll
|
||||||
|
|
||||||
|
if(Ppu->scroll.expectingY){ |
||||||
|
Ppu->scroll.y = data; |
||||||
|
}else{ |
||||||
|
Ppu->scroll.x = data; |
||||||
|
} |
||||||
|
|
||||||
|
Ppu->scroll.expectingY = !Ppu->scroll.expectingY; |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
case 6: //ppuAddr
|
||||||
|
|
||||||
|
if(Ppu->address.expectLsb){ |
||||||
|
Ppu->address.lsb = data; |
||||||
|
}else{ |
||||||
|
Ppu->address.msb = data; |
||||||
|
} |
||||||
|
|
||||||
|
Ppu->address.expectLsb = !Ppu->address.expectLsb; |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
case 7: //ppuData
|
||||||
|
|
||||||
|
Ppu->bus[Ppu->address.complete] = data; |
||||||
|
|
||||||
|
if(Ppu->control.vramIncrement) Ppu->address.complete += 32; |
||||||
|
else Ppu->address.complete++; |
||||||
|
|
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
byte ppuRead(PPU * Ppu, word address){ //send the registers to the bus so the components can read them
|
||||||
|
|
||||||
|
address -= 0x2000; |
||||||
|
switch(address){ |
||||||
|
case 0: //ppuctrl
|
||||||
|
|
||||||
|
return Ppu->control.full; |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
case 1: //ppumask
|
||||||
|
|
||||||
|
return Ppu->mask.full; |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
case 2: //ppustatus
|
||||||
|
|
||||||
|
return Ppu->status.full; |
||||||
|
Ppu->status.vblank = 0; |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
case 3: //oamAddress
|
||||||
|
|
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
case 4: //oamData
|
||||||
|
|
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
case 5: //ppuScroll
|
||||||
|
|
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
case 6: //ppuAddr
|
||||||
|
|
||||||
|
//makes no sense
|
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
case 7: //ppuData
|
||||||
|
|
||||||
|
//delayed by one cycle
|
||||||
|
|
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
void initPpu(PPU * Ppu){ |
||||||
|
Ppu->address.expectLsb = 0; |
||||||
|
Ppu->scroll.expectingY = 0; |
||||||
|
Ppu->control.full = 0; |
||||||
|
Ppu->dataByteBuffer = 0; |
||||||
|
Ppu->mask.full = 0; |
||||||
|
Ppu->status.full = 0; |
||||||
|
} |
||||||
@ -0,0 +1,108 @@ |
|||||||
|
#include "bus.h" |
||||||
|
|
||||||
|
//Credits to wiki.nesdev.com for these register graphs
|
||||||
|
//https://wiki.nesdev.com/w/index.php?title=PPU_registers
|
||||||
|
|
||||||
|
|
||||||
|
//Start of PPU struct
|
||||||
|
typedef struct{ |
||||||
|
/*
|
||||||
|
7 bit 0 |
||||||
|
---- ---- |
||||||
|
VPHB SINN |
||||||
|
|||| |||| |
||||||
|
|||| ||++- Base nametable address |
||||||
|
|||| || (0 = $2000; 1 = $2400; 2 = $2800; 3 = $2C00) |
||||||
|
|||| |+--- VRAM address increment per CPU read/write of PPUDATA |
||||||
|
|||| | (0: add 1, going across; 1: add 32, going down) |
||||||
|
|||| +---- Sprite pattern table address for 8x8 sprites |
||||||
|
|||| (0: $0000; 1: $1000; ignored in 8x16 mode) |
||||||
|
|||+------ Background pattern table address (0: $0000; 1: $1000) |
||||||
|
||+------- Sprite size (0: 8x8 pixels; 1: 8x16 pixels) |
||||||
|
|+-------- PPU master/slave select |
||||||
|
| (0: read backdrop from EXT pins; 1: output color on EXT pins) |
||||||
|
+--------- Generate an NMI at the start of the |
||||||
|
vertical blanking interval (0: off; 1: on) |
||||||
|
*/ |
||||||
|
union { |
||||||
|
struct { |
||||||
|
byte nametable : 2; |
||||||
|
byte vramIncrement : 1; |
||||||
|
byte spritePatternTable : 1; |
||||||
|
byte backgroundPatternTable : 1; |
||||||
|
byte spriteSize: 1; |
||||||
|
byte nmiVerticalBlank : 1; |
||||||
|
}; |
||||||
|
byte full; |
||||||
|
}control; |
||||||
|
|
||||||
|
struct { |
||||||
|
byte x; |
||||||
|
byte y; |
||||||
|
byte expectingY : 1; |
||||||
|
}scroll; |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
7 bit 0 |
||||||
|
---- ---- |
||||||
|
VSO. .... |
||||||
|
|||| |||| |
||||||
|
|||+-++++- Least significant bits previously written into a PPU register |
||||||
|
||| (due to register not being updated for this address) |
||||||
|
||+------- Sprite overflow. The intent was for this flag to be set |
||||||
|
|| whenever more than eight sprites appear on a scanline, but a |
||||||
|
|| hardware bug causes the actual behavior to be more complicated |
||||||
|
|| and generate false positives as well as false negatives; see |
||||||
|
|| PPU sprite evaluation. This flag is set during sprite |
||||||
|
|| evaluation and cleared at dot 1 (the second dot) of the |
||||||
|
|| pre-render line. |
||||||
|
|+-------- Sprite 0 Hit. Set when a nonzero pixel of sprite 0 overlaps |
||||||
|
| a nonzero background pixel; cleared at dot 1 of the pre-render |
||||||
|
| line. Used for raster timing. |
||||||
|
+--------- Vertical blank has started (0: not in vblank; 1: in vblank). |
||||||
|
Set at dot 1 of line 241 (the line *after* the post-render |
||||||
|
line); cleared after reading $2002 and at dot 1 of the |
||||||
|
pre-render line. |
||||||
|
*/ |
||||||
|
union{ |
||||||
|
struct { |
||||||
|
byte unused : 5; |
||||||
|
byte spriteOverflow : 1; |
||||||
|
byte sprite0Hit : 1; |
||||||
|
byte vblank : 1; |
||||||
|
}; |
||||||
|
byte full; |
||||||
|
}status; |
||||||
|
|
||||||
|
union{ |
||||||
|
struct{ |
||||||
|
byte greyscale : 1; |
||||||
|
byte showBackdropDebug : 1; |
||||||
|
byte showSpritesDebug : 1; |
||||||
|
byte unused : 5; |
||||||
|
}; |
||||||
|
byte full; |
||||||
|
}mask; |
||||||
|
|
||||||
|
struct{ |
||||||
|
union{ |
||||||
|
struct{ |
||||||
|
byte lsb; |
||||||
|
byte msb; |
||||||
|
}; |
||||||
|
|
||||||
|
word complete; |
||||||
|
}; |
||||||
|
byte expectLsb : 1; |
||||||
|
}address; |
||||||
|
|
||||||
|
byte bus[0x3FFF]; |
||||||
|
|
||||||
|
byte dataByteBuffer; |
||||||
|
}PPU; |
||||||
|
|
||||||
|
void initPpu(); |
||||||
|
|
||||||
|
void ppuWrite(word address, byte data); |
||||||
|
byte ppuRead(word address); |
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue