#include "nes.h" //================================================================================================= // // NES operations // This file includes all the nes functions possible to be called from the nes dictionary. // // See description of the commands contained here in shared/shared_dictionaries.h // //================================================================================================= /* Desc:Function takes an opcode which was transmitted via USB * then decodes it to call designated function. * shared_dict_nes.h is used in both host and fw to ensure opcodes/names align * Pre: Macros must be defined in firmware pinport.h * opcode must be defined in shared_dict_nes.h * Post:function call complete. * Rtn: SUCCESS if opcode found and completed, error if opcode not present or other problem. */ uint8_t nes_call( uint8_t opcode, uint8_t miscdata, uint16_t operand, uint8_t *rdata ) { #define RD_LEN 0 #define RD0 1 #define RD1 2 #define BYTE_LEN 1 #define HWORD_LEN 2 switch (opcode) { // //no return value: case DISCRETE_EXP0_PRGROM_WR: discrete_exp0_prgrom_wr( operand, miscdata ); break; case DISC_PUSH_EXP0_PRGROM_WR: disc_push_exp0_prgrom_wr( operand, miscdata ); break; case NES_PPU_WR: nes_ppu_wr( operand, miscdata ); break; case NES_CPU_WR: nes_cpu_wr( operand, miscdata ); break; case NES_DUALPORT_WR: nes_dualport_wr( operand, miscdata ); break; // case DISCRETE_EXP0_MAPPER_WR: // discrete_exp0_mapper_wr( operand, miscdata ); // break; case NES_MMC1_WR: mmc1_wr( operand, miscdata, 0 ); break; //8bit return values: case EMULATE_NES_CPU_RD: rdata[RD_LEN] = BYTE_LEN; rdata[RD0] = emulate_nes_cpu_rd( operand ); break; case NES_CPU_RD: rdata[RD_LEN] = BYTE_LEN; rdata[RD0] = nes_cpu_rd( operand ); break; case NES_PPU_RD: rdata[RD_LEN] = BYTE_LEN; rdata[RD0] = nes_ppu_rd( operand ); break; case NES_DUALPORT_RD: rdata[RD_LEN] = BYTE_LEN; rdata[RD0] = nes_dualport_rd( operand ); break; case CIRAM_A10_MIRROR: rdata[RD_LEN] = BYTE_LEN; rdata[RD0] = ciram_a10_mirroring( ); break; default: //macro doesn't exist return ERR_UNKN_NES_OPCODE; } return SUCCESS; } /* Desc: Discrete board PRG-ROM only write, does not write to mapper * PRG-ROM /WE <- EXP0 w/PU * PRG-ROM /OE <- /ROMSEL * PRG-ROM /CE <- GND * PRG-ROM write: /WE & /CE low, /OE high * mapper '161 CLK <- /ROMSEL * mapper '161 /LOAD <- PRG R/W * mapper '161 /LOAD must be low on rising edge of CLK to latch data * This is a /WE controlled write. Address latched on falling edge, * and data latched on rising edge EXP0 * Note:addrH bit7 has no effect (ends up on PPU /A13) * /ROMSEL, M2, & PRG R/W signals untouched * Pre: nes_init() setup of io pins * Post:data latched by PRG-ROM, mapper register unaffected * address left on bus * data left on bus, but pullup only * EXP0 left pulled up * Rtn: None */ void discrete_exp0_prgrom_wr( uint16_t addr, uint8_t data ) { ADDR_SET(addr); DATA_OP(); DATA_SET(data); EXP0_OP(); //Tas = 0ns, Tah = 30ns EXP0_LO(); EXP0_IP_PU(); //Twp = 40ns, Tds = 40ns, Tdh = 0ns //16Mhz avr clk = 62.5ns period guarantees timing reqts DATA_IP(); } //like above, but push on EXP0 instead of pullup void disc_push_exp0_prgrom_wr( uint16_t addr, uint8_t data ) { ADDR_SET(addr); DATA_OP(); DATA_SET(data); EXP0_OP(); //Tas = 0ns, Tah = 30ns EXP0_LO(); //EXP0_IP_PU(); //Twp = 40ns, Tds = 40ns, Tdh = 0ns EXP0_HI(); //Twp = 40ns, Tds = 40ns, Tdh = 0ns //16Mhz avr clk = 62.5ns period guarantees timing reqts DATA_IP(); } /* Desc: Discrete board MAPPER write without bus conflicts * will also write to PRG-ROM, but PRG-ROM shouldn't output * data while writting to mapper. Thus removing need for bank table. * NOTE: I think it would be possible to write one value to mapper * and another value to PRG-ROM. * PRG-ROM /WE <- EXP0 w/PU * PRG-ROM /OE <- /ROMSEL * PRG-ROM /CE <- GND * PRG-ROM write: /WE & /CE low, /OE high * mapper '161 CLK <- /ROMSEL * mapper '161 /LOAD <- PRG R/W * mapper '161 /LOAD must be low on rising edge of CLK to latch data * Note:addrH bit7 has no effect (ends up on PPU /A13) * M2 signal untouched * Pre: nes_init() setup of io pins * Post:data latched by MAPPER, will also be written to PRG-ROM afterwards * address left on bus * data left on bus, but pullup only * EXP0 left pulled up * Rtn: None */ //void discrete_exp0_mapper_wr( uint16_t addr, uint8_t data ) //{ // //Float EXP0 as it should be in NES // EXP0_IP_FL(); // //EXP0_OP(); //tas = 0ns, tah = 30ns // //EXP0_LO(); // // //need for whole function // //_DATA_OP(); // // //set addrL // //ADDR_OUT = addrL; // //latch addrH // //DATA_OUT = addrH; // //_AHL_CLK(); // ADDR_SET(addr); // // //PRG R/W LO // PRGRW_LO(); // // //put data on bus // DATA_OP(); // DATA_SET(data); // // //set M2 and /ROMSEL // M2_HI(); // if( addr >= 0x8000 ) { //addressing cart rom space // ROMSEL_LO(); //romsel trails M2 during CPU operations // } // // //give some time // NOP(); // NOP(); // // //latch data to cart memory/mapper // M2_LO(); // ROMSEL_HI(); // // //retore PRG R/W to default // PRGRW_HI(); // // EXP0_IP_PU(); //Twp = 40ns, Tds = 40ns, Tdh = 0ns // //Free data bus // DATA_IP(); // // return; // // /* // ADDR_SET(addr); // // DATA_OP(); // DATA_SET(data); // // //start write to PRG-ROM (latch address) // exp0_op(); //tas = 0ns, tah = 30ns // exp0_lo(); // // //enable write to mapper PRG R/W LO // PRGRW_LO(); // ROMSEL_LO(); //fact that it's low for such a short time might also if PRG-ROM does output data // // NOP(); //AVR didn't need this delay // NOP(); //AVR didn't need this delay // NOP(); //AVR didn't need this delay // NOP(); //AVR didn't need this delay // NOP(); //AVR didn't need this delay // NOP(); //AVR didn't need this delay // //clock mapper register, should not enable PRG-ROM output since /WE low // NOP(); //AVR didn't need this delay // NOP(); //AVR didn't need this delay // ROMSEL_HI(); //data latched on rising edge // // //Could output other data here that would like to be written to PRG-ROM // //I'm not certain an actual write gets applied to PRG-ROM as /OE is supposed to be high whole time.. // // NOP(); //AVR didn't need this delay // //return to default // PRGRW_HI(); // // EXP0_IP_PU(); //Twp = 40ns, Tds = 40ns, Tdh = 0ns // //16Mhz avr clk = 62.5ns period guarantees timing reqts // DATA_IP(); // */ //} /* Desc:Emulate NES CPU Read as best possible * decode A15 from addrH to set /ROMSEL as expected * float EXP0 * toggle M2 as NES would * insert some NOP's in to be slow like NES * Note:not the fastest read operation * Pre: nes_init() setup of io pins * Post:address left on bus * data bus left clear * EXP0 left floating * Rtn: Byte read from PRG-ROM at addrHL */ uint8_t emulate_nes_cpu_rd( uint16_t addr ) { uint8_t read; //return value //m2 should be low as it aids in disabling WRAM //this is also m2 state at beginging of CPU cycle //all these pins should already be in this state, but //go ahead and setup just to be sure since we're trying //to be as accurate as possible EXP0_IP_FL(); //this could have been left pulled up M2_LO(); //start of CPU cycle ROMSEL_HI(); //trails M2 PRGRW_HI(); //happens just after M2 //set address bus ADDR_SET(addr); //couple NOP's to wait a bit NOP(); NOP(); //set M2 and /ROMSEL if( addr >= 0x8000 ) { //addressing cart rom space M2_HI(); ROMSEL_LO(); //romsel trails M2 during CPU operations } else { M2_HI(); } //couple more NOP's waiting for data NOP(); NOP(); NOP(); NOP(); NOP(); NOP(); //latch data DATA_RD(read); //return bus to default M2_LO(); ROMSEL_HI(); return read; } /* Desc:NES CPU Read without being so slow * decode A15 from addrH to set /ROMSEL as expected * float EXP0 * toggle M2 as NES would * Pre: nes_init() setup of io pins * Post:address left on bus * data bus left clear * EXP0 left floating * Rtn: Byte read from PRG-ROM at addrHL */ uint8_t nes_cpu_rd( uint16_t addr ) { uint8_t read; //return value //set address bus ADDR_SET(addr); //set M2 and /ROMSEL M2_HI(); if( addr >= 0x8000 ) { //addressing cart rom space ROMSEL_LO(); //romsel trails M2 during CPU operations } //couple more NOP's waiting for data //zero nop's returned previous databus value NOP(); //one nop got most of the bits right NOP(); //two nop got all the bits right NOP(); //add third nop for some extra NOP(); //one more can't hurt //might need to wait longer for some carts... //latch data DATA_RD(read); //return bus to default M2_LO(); ROMSEL_HI(); return read; } /* Desc:NES CPU Write * Just as you would expect NES's CPU to perform * A15 decoded to enable /ROMSEL * This ends up as a M2 and/or /ROMSEL controlled write * Note:addrH bit7 has no effect (ends up on PPU /A13) * EXP0 floating * Pre: nes_init() setup of io pins * Post:data latched by anything listening on the bus * address left on bus * data left on bus, but pullup only * Rtn: None */ void nes_cpu_wr( uint16_t addr, uint8_t data ) { //Float EXP0 as it should be in NES EXP0_IP_FL(); //need for whole function //_DATA_OP(); //set addrL //ADDR_OUT = addrL; //latch addrH //DATA_OUT = addrH; //_AHL_CLK(); ADDR_SET(addr); //PRG R/W LO PRGRW_LO(); //put data on bus DATA_OP(); DATA_SET(data); //set M2 and /ROMSEL M2_HI(); if( addr >= 0x8000 ) { //addressing cart rom space ROMSEL_LO(); //romsel trails M2 during CPU operations } //give some time NOP(); NOP(); //latch data to cart memory/mapper M2_LO(); ROMSEL_HI(); //retore PRG R/W to default PRGRW_HI(); //Free data bus DATA_IP(); } /* Desc:NES PPU Read * decode A13 from addrH to set /A13 as expected * Pre: nes_init() setup of io pins * Post:address left on bus * data bus left clear * Rtn: Byte read from CHR-ROM/RAM at addrHL */ uint8_t nes_ppu_rd( uint16_t addr ) { uint8_t read; //return value //addr with PPU /A13 if (addr < 0x2000) { //below $2000 A13 clear, /A13 set addr |= PPU_A13N_WORD; } //above PPU $1FFF, A13 set, /A13 clear ADDR_SET( addr ); //set CHR /RD and /WR CSRD_LO(); //couple more NOP's waiting for data //zero nop's returned previous databus value NOP(); //one nop got most of the bits right NOP(); //two nop got all the bits right NOP(); //add third nop for some extra NOP(); //one more can't hurt //might need to wait longer for some carts... //latch data DATA_RD(read); //return bus to default CSRD_HI(); return read; } /* Desc:NES PPU Write * decode A13 from addrH to set /A13 as expected * flash: address clocked falling edge, data rising edge of /WE * Pre: nes_init() setup of io pins * Post:data written to addrHL * address left on bus * data bus left clear * Rtn: None */ void nes_ppu_wr( uint16_t addr, uint8_t data ) { //addr with PPU /A13 if (addr < 0x2000) { //below $2000 A13 clear, /A13 set addr |= PPU_A13N_WORD; } //above PPU $1FFF, A13 set, /A13 clear ADDR_SET( addr ); //put data on bus DATA_OP(); DATA_SET(data); NOP(); //set CHR /RD and /WR CSWR_LO(); //might need to wait longer for some carts... NOP(); //one can't hurt //latch data to memory CSWR_HI(); //clear data bus DATA_IP(); } /* Desc:NES dual port Read from the PPU * /A13 as ignored * Pre: nes_init() setup of io pins * Post:address left on bus * data bus left clear * Rtn: Byte read from CHR-ROM/RAM at addrHL */ uint8_t nes_dualport_rd( uint16_t addr ) { uint8_t read; //return value ADDR_SET( addr ); //enable data path M2_HI(); //M2 is kinda like R/W setting direction ROMSEL_LO(); //enable data buffers //data should now be driven on the bus but invalid //set CHR /RD and /WR CSRD_LO(); //couple more NOP's waiting for data //zero nop's returned previous databus value NOP(); //one nop got most of the bits right NOP(); //two nop got all the bits right NOP(); //add third nop for some extra //latch data DATA_RD(read); //return bus to default CSRD_HI(); M2_LO(); ROMSEL_HI(); return read; } /* Desc:NES DUALPORT Write * /A13 ignored * Pre: nes_init() setup of io pins * Post:data written to addrHL * address left on bus * data bus left clear * Rtn: None */ void nes_dualport_wr( uint16_t addr, uint8_t data ) { ADDR_SET( addr ); //enable data path M2_LO(); //M2 is kinda like R/W setting direction ROMSEL_LO(); //enable data buffers //data should now be driven on the bus but invalid //put data on bus DATA_OP(); DATA_SET(data); NOP(); //set CHR /RD and /WR CSWR_LO(); //might need to wait longer for some carts... NOP(); //one can't hurt //latch data to memory CSWR_HI(); //clear data bus DATA_IP(); ROMSEL_HI(); } /* Desc:PPU CIRAM A10 NT arrangement sense * Toggle A11 and A10 and read back CIRAM A10 * report back if vert/horiz/1scnA/1scnB * reports nesdev defined mirroring * does not report Nintendo's "Name Table Arrangement" * Pre: nes_init() setup of io pins * Post:address left on bus * Rtn: MIR_VERT, MIR_HORIZ, MIR_1SCNA, MIR_1SCNB * errors not really possible since all combinations * of CIRAM A10 level designate something valid */ uint8_t ciram_a10_mirroring( void ) { uint16_t readV, readH; //set A11, clear A10 //ADDRH(A11_BYTE); setting A11 in this manner doesn't work for some reason.. ADDR_SET(0x0800); CIA10_RD(readH); //set A10, clear A11 ADDRH(A10_BYTE); //ADDR_SET(0x0400); CIA10_RD(readV); //if CIRAM A10 was always low -> 1 screen A if ((readV==0) & (readH==0)) return MIR_1SCNA; //if CIRAM A10 was always high -> 1 screen B if ((readV!=0) & (readH!=0)) return MIR_1SCNB; //if CIRAM A10 toggled with A10 -> Vertical mirroring, horizontal arrangement if ((readV!=0) & (readH==0)) return MIR_VERT; //if CIRAM A10 toggled with A11 -> Horizontal mirroring, vertical arrangement if ((readV==0) & (readH!=0)) return MIR_HORZ; //shouldn't be here... return GEN_FAIL; } /* Desc:NES CPU Page Read with optional USB polling * decode A15 from addrH to set /ROMSEL as expected * float EXP0 * toggle M2 as NES would * if poll is true calls usbdrv.h usbPoll fuction * this is needed to keep from timing out when double buffering usb data * Pre: nes_init() setup of io pins * num_bytes can't exceed 256B page boundary * Post:address left on bus * data bus left clear * EXP0 left floating * data buffer filled starting at first to last * Rtn: Index of last byte read */ uint8_t nes_cpu_page_rd_poll( uint8_t *data, uint8_t addrH, uint8_t first, uint8_t len, uint8_t poll ) { uint8_t i; //set address bus ADDRH(addrH); //set M2 and /ROMSEL M2_HI(); if( addrH >= 0x80 ) { //addressing cart rom space ROMSEL_LO(); //romsel trails M2 during CPU operations } //set lower address bits ADDRL(first); //doing this prior to entry and right after latching //extra NOP was needed on stm6 as address hadn't settled in time for the very first read NOP(); //gives longest delay between address out and latching data for( i=0; i<=len; i++ ) { //testing shows that having this if statement doesn't affect overall dumping speed if ( poll == FALSE ) { NOP(); //couple more NOP's waiting for data NOP(); //one prob good enough considering the if/else } else { usbPoll(); //Call usbdrv.h usb polling while waiting for data } //latch data DATA_RD(data[i]); //set lower address bits //ADDRL(++first); THIS broke things, on stm adapter because macro expands it twice! first++; ADDRL(first); } //return bus to default M2_LO(); ROMSEL_HI(); //return index of last byte read return i; } /* Desc:NES PPU Page Read with optional USB polling * decode A13 from addrH to set /A13 as expected * if poll is true calls usbdrv.h usbPoll fuction * this is needed to keep from timing out when double buffering usb data * Pre: nes_init() setup of io pins * num_bytes can't exceed 256B page boundary * Post:address left on bus * data bus left clear * data buffer filled starting at first for len number of bytes * Rtn: Index of last byte read */ uint8_t nes_ppu_page_rd_poll( uint8_t *data, uint8_t addrH, uint8_t first, uint8_t len, uint8_t poll ) { uint8_t i; if (addrH < 0x20) { //below $2000 A13 clear, /A13 set //ADDRH(addrH | PPU_A13N_BYTE); //Don't do weird stuff like above! logic inside macro expansions can have weird effects!! addrH |= PPU_A13N_BYTE; ADDRH(addrH); } else { //above PPU $1FFF, A13 set, /A13 clear ADDRH(addrH); } //set CHR /RD and /WR CSRD_LO(); //set lower address bits ADDRL(first); //doing this prior to entry and right after latching NOP(); //adding extra NOP as it was needed on PRG //gives longest delay between address out and latching data for( i=0; i<=len; i++ ) { //couple more NOP's waiting for data if ( poll == FALSE ) { NOP(); //one prob good enough considering the if/else NOP(); } else { usbPoll(); } //latch data DATA_RD(data[i]); //set lower address bits first ++; ADDRL(first); } //return bus to default CSRD_HI(); //return index of last byte read return i; } /* Desc:NES DUAL PORT PPU Page Read with optional USB polling * /A13 ignored * if poll is true calls usbdrv.h usbPoll fuction * this is needed to keep from timing out when double buffering usb data * Pre: nes_init() setup of io pins * num_bytes can't exceed 256B page boundary * Post:address left on bus * data bus left clear * data buffer filled starting at first for len number of bytes * Rtn: Index of last byte read */ uint8_t nes_dualport_page_rd_poll( uint8_t *data, uint8_t addrH, uint8_t first, uint8_t len, uint8_t poll ) { uint8_t i; //ignore /A13, board doesn't see it anyway ADDRH(addrH); //now that data bus is no longer needed, //can enable data path out of cart M2_HI(); ROMSEL_LO(); //set CHR /RD and /WR CSRD_LO(); //set lower address bits ADDRL(first); //doing this prior to entry and right after latching NOP(); //adding extra NOP as it was needed on PRG //gives longest delay between address out and latching data for( i=0; i<=len; i++ ) { //couple more NOP's waiting for data if ( poll == FALSE ) { NOP(); //one prob good enough considering the if/else NOP(); } else { usbPoll(); } //latch data DATA_RD(data[i]); //set lower address bits first ++; ADDRL(first); } //return bus to default CSRD_HI(); M2_LO(); ROMSEL_HI(); //return index of last byte read return i; } /* Desc:NES MMC1 Write * write to entirety of MMC1 register * address selects register that's written to * address must be >= $8000 where registers are located * Pre: nes_init() setup of io pins * MMC1 shift register has been reset by writting with D7 set * bit7 must be clear, else the shift register will be reset * Post:MMC1 register contains value provided * address left on bus * data left on bus, but pullup only * Rtn: None */ void mmc1_wr( uint16_t addr, uint8_t data, uint8_t reset ) { uint8_t i; //reset shift register if requested if( reset ) { nes_cpu_rd(0x8000); nes_cpu_wr(0x8000, 0x80); } //5 bits in register D0-4, so 5 total writes through D0 for( i=0; i<5; i++) { //MMC1 ignores all but the first write, so perform a read first nes_cpu_rd(addr); nes_cpu_wr(addr, data); data = data >> 1; } return; }