private static void Write(int address, byte value) { BUS_RW_P = BUS_RW; BUS_ADDRESS = address; BUS_RW = false; ClockComponents(); if (address < 0x2000)// Internal 2K Work RAM (mirrored to 800h-1FFFh) { WRAM[address & 0x7FF] = value; } else if (address < 0x4000) { #region Internal PPU Registers (mirrored to 2008h-3FFFh) switch (address & 7) { case 0: // $2000 { vram_temp = (vram_temp & 0x73FF) | ((value & 0x3) << 10); vram_increament = ((value & 0x4) != 0) ? 32 : 1; spr_patternAddress = ((value & 0x8) != 0) ? 0x1000 : 0x0000; bkg_patternAddress = ((value & 0x10) != 0) ? 0x1000 : 0x0000; spr_size16 = (value & 0x20) != 0 ? 0x0010 : 0x0008; nmi_old = nmi_enabled; nmi_enabled = (value & 0x80) != 0; if (!nmi_enabled) // NMI disable effect only at vbl set period (HClock between 1 and 3) { if ((VClock == vbl_vclock_Start) && (HClock <= 3)) { NMI_Current = (vbl_flag_temp & nmi_enabled); } } else if (vbl_flag_temp & !nmi_old) // Special case ! NMI can be enabled anytime if vbl already set { NMI_Current = true; } break; } case 1: // $2001 { grayscale = (value & 0x01) != 0 ? 0x30 : 0x3F; emphasis = (value & 0xE0) << 1; bkg_clipped = (value & 0x02) == 0; spr_clipped = (value & 0x04) == 0; bkg_enabled = (value & 0x08) != 0; spr_enabled = (value & 0x10) != 0; break; } case 3: // $2003 { oam_address = value; break; } case 4: // $2004 { if (VClock < 240 && IsRenderingOn()) { value = 0xFF; } if ((oam_address & 0x03) == 0x02) { value &= 0xE3; } oam_ram[oam_address++] = value; break; } case 5: // $2005 { if (!vram_flipflop) { vram_temp = (vram_temp & 0x7FE0) | ((value & 0xF8) >> 3); vram_fine = (byte)(value & 0x07); } else { vram_temp = (vram_temp & 0x0C1F) | ((value & 0x7) << 12) | ((value & 0xF8) << 2); } vram_flipflop = !vram_flipflop; break; } case 6: // $2006 { if (!vram_flipflop) { vram_temp = (vram_temp & 0x00FF) | ((value & 0x3F) << 8); } else { vram_temp = (vram_temp & 0x7F00) | value; vram_address = vram_temp; board.OnPPUAddressUpdate(ref vram_address); } vram_flipflop = !vram_flipflop; break; } case 7: // $2007 { vram_address_temp_access = vram_address & 0x3FFF; if (vram_address_temp_access < 0x2000) { board.WriteCHR(ref vram_address_temp_access, ref value); } else if (vram_address_temp_access < 0x3F00) { board.WriteNMT(ref vram_address_temp_access, ref value); } else { palettes_bank[vram_address_temp_access & ((vram_address_temp_access & 0x03) == 0 ? 0x0C : 0x1F)] = value; } vram_address = (vram_address + vram_increament) & 0x7FFF; board.OnPPUAddressUpdate(ref vram_address); break; } } #endregion } else if (address < 0x4020) { #region Internal APU Registers switch (address) { /*Pulse 1*/ case 0x4000: { sq1_volume_decay_time = value & 0xF; sq1_duration_haltRequset = (value & 0x20) != 0; sq1_constant_volume_flag = (value & 0x10) != 0; sq1_envelope = sq1_constant_volume_flag ? sq1_volume_decay_time : sq1_env_counter; sq1_dutyForm = (value & 0xC0) >> 6; break; } case 0x4001: { sq1_sweepEnable = (value & 0x80) == 0x80; sq1_sweepDeviderPeriod = (value >> 4) & 7; sq1_sweepNegateFlag = (value & 0x8) == 0x8; sq1_sweepShiftCount = value & 7; sq1_sweepReload = true; break; } case 0x4002: { sq1_frequency = (sq1_frequency & 0x0700) | value; break; } case 0x4003: { sq1_duration_reload = DurationTable[value >> 3]; sq1_duration_reloadRequst = true; sq1_frequency = (sq1_frequency & 0x00FF) | ((value & 7) << 8); sq1_dutyStep = 0; sq1_env_startflag = true; break; } /*Pulse 2*/ case 0x4004: { sq2_volume_decay_time = value & 0xF; sq2_duration_haltRequset = (value & 0x20) != 0; sq2_constant_volume_flag = (value & 0x10) != 0; sq2_envelope = sq2_constant_volume_flag ? sq2_volume_decay_time : sq2_env_counter; sq2_dutyForm = (value & 0xC0) >> 6; break; } case 0x4005: { sq2_sweepEnable = (value & 0x80) == 0x80; sq2_sweepDeviderPeriod = (value >> 4) & 7; sq2_sweepNegateFlag = (value & 0x8) == 0x8; sq2_sweepShiftCount = value & 7; sq2_sweepReload = true; break; } case 0x4006: { sq2_frequency = (sq2_frequency & 0x0700) | value; break; } case 0x4007: { sq2_duration_reload = DurationTable[value >> 3]; sq2_duration_reloadRequst = true; sq2_frequency = (sq2_frequency & 0x00FF) | ((value & 7) << 8); sq2_dutyStep = 0; sq2_env_startflag = true; break; } /*Triangle*/ case 0x4008: { trl_linearCounterHalt = trl_duration_haltRequset = (value & 0x80) != 0; trl_linearCounterReload = (byte)(value & 0x7F); break; } case 0x400A: { trl_frequency = (trl_frequency & 0x700) | value; break; } case 0x400B: { trl_frequency = (trl_frequency & 0x00FF) | ((value & 7) << 8); trl_duration_reload = DurationTable[value >> 3]; trl_duration_reloadRequst = true; trl_halt = true; break; } /*Noise*/ case 0x400C: { noz_volume_decay_time = value & 0xF; noz_duration_haltRequset = (value & 0x20) != 0; noz_constant_volume_flag = (value & 0x10) != 0; noz_envelope = noz_constant_volume_flag ? noz_volume_decay_time : noz_env_counter; break; } case 0x400E: { noz_frequency = NozFrequencyTable[systemIndex][value & 0x0F]; noz_mode = (value & 0x80) == 0x80; break; } case 0x400F: { noz_duration_reload = DurationTable[value >> 3]; noz_duration_reloadRequst = true; noz_env_startflag = true; break; } /*DMC*/ case 0x4010: { DMCIrqEnabled = (value & 0x80) != 0; dmc_dmaLooping = (value & 0x40) != 0; if (!DMCIrqEnabled) { DeltaIrqOccur = false; IRQFlags &= ~IRQ_DMC; } dmc_freqTimer = value & 0x0F; break; } case 0x4011: { dmc_output = (byte)(value & 0x7F); break; } case 0x4012: { dmc_dmaAddrRefresh = (value << 6) | 0xC000; break; } case 0x4013: { dmc_dmaSizeRefresh = (value << 4) | 0x0001; break; } case 0x4014: { dmaOamaddress = value << 8; AssertOAMDMA(); break; } case 0x4015: { // SQ1 sq1_duration_reloadEnabled = (value & 0x01) != 0; if (!sq1_duration_reloadEnabled) { sq1_duration_counter = 0; } // SQ2 sq2_duration_reloadEnabled = (value & 0x02) != 0; if (!sq2_duration_reloadEnabled) { sq2_duration_counter = 0; } // TRL trl_duration_reloadEnabled = (value & 0x04) != 0; if (!trl_duration_reloadEnabled) { trl_duration_counter = 0; } // NOZ noz_duration_reloadEnabled = (value & 0x08) != 0; if (!noz_duration_reloadEnabled) { noz_duration_counter = 0; } // DMC if ((value & 0x10) != 0) { if (dmc_dmaSize == 0) { dmc_dmaSize = dmc_dmaSizeRefresh; dmc_dmaAddr = dmc_dmaAddrRefresh; } } else { dmc_dmaSize = 0; } // Disable DMC IRQ DeltaIrqOccur = false; IRQFlags &= ~IRQ_DMC; // RDY ? if (!dmc_bufferFull && dmc_dmaSize > 0) { AssertDMCDMA(); } break; } case 0x4016: { if (inputStrobe > (value & 0x01)) { if (IsFourPlayers) { PORT0 = joypad3.GetData() << 8 | joypad1.GetData() | 0x01010000; PORT1 = joypad4.GetData() << 8 | joypad2.GetData() | 0x02020000; } else { PORT0 = joypad1.GetData() | 0x01010100; // What is this ? see * PORT1 = joypad2.GetData() | 0x02020200; } } if (IsVSUnisystem) { board.VSUnisystem4016RW(ref value); } inputStrobe = value & 0x01; break; // * The data port is 24 bits length // Each 8 bits indicates device, if that device is connected, then device data set on it normally... // So we have 4 block of data on each register ([] indicate byte block here, let's call these blocks a SEQ) // SEQ: // [block 3] [block 2] [block 1] [block 0] // 0000 0000 0000 0000 0000 0000 0000 0000 // ^ bit 23 ^ bit 0 // Let's say we connect joypad 1 and joypad2, then: // In $4016: the data could be like this [00h][00h][00h][joy1] // In $4017: the data could be like this [00h][00h][00h][joy2] // Instead of having 00h value on other blocks, the read returns a bit set on each unused block // to indicate that there's no device (i.e. joypad) is connected : // In $4016 the first bit (i.e. bit 0) is set if no device connected on that block // Example: [01h][01h][01h][joy1] (we only have joypad 1 connected so other blocks are blocked) // In $4017 work the same but with second bit (i.e. bit 1) is set if no device connected on other blocks // Example: [02h][02h][02h][joy2] (when we have joypad 2 connected so other blocks are blocked) // If we connect 4 joypads then: // $4016 : [01h][01h][joy3][joy1] // $4017 : [02h][02h][joy4][joy2] } case 0x4017: { SequencingMode = (value & 0x80) != 0; FrameIrqEnabled = (value & 0x40) == 0; CurrentSeq = 0; if (!SequencingMode) { Cycles = SequenceMode0[systemIndex][0]; } else { Cycles = SequenceMode1[systemIndex][0]; } if (!oddCycle) { Cycles++; } else { Cycles += 2; } if (!FrameIrqEnabled) { FrameIrqFlag = false; IRQFlags &= ~IRQ_APU; } break; } } #endregion } else if (address < 0x6000)// Cartridge Expansion Area almost 8K { if (IsVSUnisystem && address == 0x4020) { VSUnisystemDIP.Write4020(ref value); } board.WriteEXP(ref address, ref value); } else if (address < 0x8000)// Cartridge SRAM Area 8K { board.WriteSRM(ref address, ref value); } else if (address <= 0xFFFF)// Cartridge PRG-ROM Area 32K { board.WritePRG(ref address, ref value); } }
private static byte Read(int address) { BUS_RW_P = BUS_RW; BUS_ADDRESS = address; BUS_RW = true; ClockComponents(); if (address < 0x2000)// Internal 2K Work RAM (mirrored to 800h-1FFFh) { return(WRAM[address & 0x7FF]); } else if (address < 0x4000) { #region Internal PPU Registers (mirrored to 2008h-3FFFh) switch (address & 7) { case 2: // $2002 { ppu_2002_temp = 0; if (vbl_flag) { ppu_2002_temp |= 0x80; } if (spr_0Hit) { ppu_2002_temp |= 0x40; } if (spr_overflow) { ppu_2002_temp |= 0x20; } vbl_flag_temp = false; vram_flipflop = false; // NMI disable effect only at vbl set period (HClock between 1 and 3) if ((VClock == vbl_vclock_Start) && (HClock <= 3)) { NMI_Current = (vbl_flag_temp & nmi_enabled); } return(ppu_2002_temp); } case 4: // $2004 { ppu_2004_temp = oam_ram[oam_address]; if (VClock < 240 && IsRenderingOn()) { if (HClock < 64) { ppu_2004_temp = 0xFF; } else if (HClock < 192) { ppu_2004_temp = oam_ram[((HClock - 64) << 1) & 0xFC]; } else if (HClock < 256) { ppu_2004_temp = ((HClock & 1) == 1) ? oam_ram[0xFC] : oam_ram[((HClock - 192) << 1) & 0xFC]; } else if (HClock < 320) { ppu_2004_temp = 0xFF; } else { ppu_2004_temp = oam_ram[0]; } } return(ppu_2004_temp); } case 7: // $2007 { ppu_2007_temp = 0; if ((vram_address & 0x3F00) == 0x3F00) { // The return value should be from the palettes bank ppu_2007_temp = (byte)(palettes_bank[vram_address & ((vram_address & 0x03) == 0 ? 0x0C : 0x1F)] & grayscale); // fill buffer from chr or nametables vram_address_temp_access1 = vram_address & 0x2FFF; if (vram_address_temp_access1 < 0x2000) { reg2007buffer = board.ReadCHR(ref vram_address_temp_access1, false); } else { reg2007buffer = board.ReadNMT(ref vram_address_temp_access1); } } else { ppu_2007_temp = reg2007buffer; // fill buffer vram_address_temp_access1 = vram_address & 0x3FFF; if (vram_address_temp_access1 < 0x2000) { reg2007buffer = board.ReadCHR(ref vram_address_temp_access1, false); } else if (vram_address_temp_access1 < 0x3F00) { reg2007buffer = board.ReadNMT(ref vram_address_temp_access1); } else { reg2007buffer = palettes_bank[vram_address_temp_access1 & ((vram_address_temp_access1 & 0x03) == 0 ? 0x0C : 0x1F)]; } } vram_address = (vram_address + vram_increament) & 0x7FFF; board.OnPPUAddressUpdate(ref vram_address); return(ppu_2007_temp); } } #endregion } else if (address < 0x4020) { #region Internal APU Registers switch (address) { case 0x4015: { temp_4015 = 0; // Channels enable if (sq1_duration_counter > 0) { temp_4015 |= 0x01; } if (sq2_duration_counter > 0) { temp_4015 |= 0x02; } if (trl_duration_counter > 0) { temp_4015 |= 0x04; } if (noz_duration_counter > 0) { temp_4015 |= 0x08; } if (dmc_dmaSize > 0) { temp_4015 |= 0x10; } // IRQ if (FrameIrqFlag) { temp_4015 |= 0x40; } if (DeltaIrqOccur) { temp_4015 |= 0x80; } FrameIrqFlag = false; IRQFlags &= ~IRQ_APU; return(temp_4015); } case 0x4016: { temp_4016 = (byte)(PORT0 & 1); PORT0 >>= 1; if (IsZapperConnected) { temp_4016 |= zapper.GetData(); } if (IsVSUnisystem) { temp_4016 |= VSUnisystemDIP.GetData4016(); } return(temp_4016); } case 0x4017: { temp_4017 = (byte)(PORT1 & 1); PORT1 >>= 1; if (IsZapperConnected) { temp_4017 |= zapper.GetData(); } if (IsVSUnisystem) { temp_4017 |= VSUnisystemDIP.GetData4017(); } return(temp_4017); } } #endregion } else if (address < 0x6000)// Cartridge Expansion Area almost 8K { return(board.ReadEXP(ref address)); } else if (address < 0x8000)// Cartridge SRAM Area 8K { return(board.ReadSRM(ref address)); } else if (address <= 0xFFFF)// Cartridge PRG-ROM Area 32K { return(board.ReadPRG(ref address)); } // Should not reach here ! return(0); }