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