[init] added files

main
KoNicks 4 years ago
commit 35b687b916
No known key found for this signature in database
GPG Key ID: 6D8132757F8B93E5
  1. 6
      .gitignore
  2. 8
      Makefile
  3. 143
      bus.c
  4. 17
      bus.h
  5. 71
      cartridge.c
  6. 77
      cartridge.h
  7. 5
      common.h
  8. 1838
      cpu.c
  9. 67
      cpu.h
  10. 130
      ppu.c
  11. 108
      ppu.h
  12. 8991
      sampleoutput

6
.gitignore vendored

@ -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

143
bus.c

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

17
bus.h

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

1838
cpu.c

File diff suppressed because it is too large Load Diff

67
cpu.h

@ -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

130
ppu.c

@ -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;
}

108
ppu.h

@ -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…
Cancel
Save