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); } }
public void Write(int address, byte value) { BUS_RW_P = BUS_RW; BUS_ADDRESS = address; BUS_RW = false; #region Clock Components this.ppu.Clock(); /* * NMI edge detector polls the status of the NMI line during φ2 of each CPU cycle * (i.e., during the second half of each cycle) */ this.interrupts.PollInterruptStatus(); this.ppu.Clock(); this.ppu.Clock(); if (this.emulator.DoPalAdditionalClock) // In pal system .. { this.emulator.palCyc++; if (this.emulator.palCyc == 5) { this.ppu.Clock(); this.emulator.palCyc = 0; } } this.apu.Clock(); this.dma.Clock(); board.OnCPUClock(); #endregion 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 { this.ppu.vram_temp = (this.ppu.vram_temp & 0x73FF) | ((value & 0x3) << 10); this.ppu.vram_increament = ((value & 0x4) != 0) ? 32 : 1; this.ppu.spr_patternAddress = ((value & 0x8) != 0) ? 0x1000 : 0x0000; this.ppu.bkg_patternAddress = ((value & 0x10) != 0) ? 0x1000 : 0x0000; this.ppu.spr_size16 = (value & 0x20) != 0 ? 0x0010 : 0x0008; this.interrupts.nmi_old = this.interrupts.nmi_enabled; this.interrupts.nmi_enabled = (value & 0x80) != 0; if (!this.interrupts.nmi_enabled) // NMI disable effect only at vbl set period (HClock between 1 and 3) { this.interrupts.CheckNMI(); } else if (this.interrupts.vbl_flag_temp & !this.interrupts.nmi_old) // Special case ! NMI can be enabled anytime if vbl already set { this.interrupts.NMI_Current = true; } break; } case 1: // $2001 { this.ppu.grayscale = (value & 0x01) != 0 ? 0x30 : 0x3F; this.ppu.emphasis = (value & 0xE0) << 1; this.ppu.bkg_clipped = (value & 0x02) == 0; this.ppu.spr_clipped = (value & 0x04) == 0; this.ppu.bkg_enabled = (value & 0x08) != 0; this.ppu.spr_enabled = (value & 0x10) != 0; break; } case 3: // $2003 { this.ppu.oam_address = value; break; } case 4: // $2004 { if (this.ppu.VClock < 240 && this.ppu.IsRenderingOn()) { value = 0xFF; } if ((this.ppu.oam_address & 0x03) == 0x02) { value &= 0xE3; } oam_ram[this.ppu.oam_address++] = value; break; } case 5: // $2005 { if (!this.ppu.vram_flipflop) { this.ppu.vram_temp = (this.ppu.vram_temp & 0x7FE0) | ((value & 0xF8) >> 3); this.ppu.vram_fine = (byte)(value & 0x07); } else { this.ppu.vram_temp = (this.ppu.vram_temp & 0x0C1F) | ((value & 0x7) << 12) | ((value & 0xF8) << 2); } this.ppu.vram_flipflop = !this.ppu.vram_flipflop; break; } case 6: // $2006 { if (!this.ppu.vram_flipflop) { this.ppu.vram_temp = (this.ppu.vram_temp & 0x00FF) | ((value & 0x3F) << 8); } else { this.ppu.vram_temp = (this.ppu.vram_temp & 0x7F00) | value; this.ppu.vram_address = this.ppu.vram_temp; board.OnPPUAddressUpdate(ref this.ppu.vram_address); } this.ppu.vram_flipflop = !this.ppu.vram_flipflop; break; } case 7: // $2007 { this.ppu.vram_address_temp_access = this.ppu.vram_address & 0x3FFF; if (this.ppu.vram_address_temp_access < 0x2000) { board.WriteCHR(ref this.ppu.vram_address_temp_access, ref value); } else if (this.ppu.vram_address_temp_access < 0x3F00) { board.WriteNMT(ref this.ppu.vram_address_temp_access, ref value); } else { palettes_bank[this.ppu.vram_address_temp_access & ((this.ppu.vram_address_temp_access & 0x03) == 0 ? 0x0C : 0x1F)] = value; } this.ppu.vram_address = (this.ppu.vram_address + this.ppu.vram_increament) & 0x7FFF; board.OnPPUAddressUpdate(ref this.ppu.vram_address); break; } } #endregion } else if (address < 0x4020) { #region Internal APU Registers switch (address) { /*Pulse 1*/ case 0x4000: case 0x4001: case 0x4002: case 0x4003: { this.apu.pulse1Channel.WriteByte(address, value); break; } /*Pulse 2*/ case 0x4004: case 0x4005: case 0x4006: case 0x4007: { this.apu.pulse2Channel.WriteByte(address, value); break; } /*Triangle*/ case 0x4008: case 0x400A: case 0x400B: { this.apu.triangleChannel.WriteByte(address, value); break; } /*Noise*/ case 0x400C: case 0x400E: case 0x400F: { this.apu.noiseChannel.WriteByte(address, value); break; } /*DMC*/ case 0x4010: { this.apu.dmcChannel.DMCIrqEnabled = (value & 0x80) != 0; this.apu.dmcChannel.dmc_dmaLooping = (value & 0x40) != 0; if (!this.apu.dmcChannel.DMCIrqEnabled) { this.apu.dmcChannel.DeltaIrqOccur = false; Interrupts.IRQFlags &= ~Interrupts.IRQ_DMC; } this.apu.dmcChannel.dmc_freqTimer = value & 0x0F; break; } case 0x4011: { this.apu.dmcChannel.Output = (byte)(value & 0x7F); break; } case 0x4012: { this.apu.dmcChannel.dmc_dmaAddrRefresh = (value << 6) | 0xC000; break; } case 0x4013: { this.apu.dmcChannel.dmc_dmaSizeRefresh = (value << 4) | 0x0001; break; } case 0x4014: { this.dma.dmaOamaddress = value << 8; this.dma.AssertOAMDMA(); break; } case 0x4015: { // SQ1 this.apu.pulse1Channel.Duration_reloadEnabled = (value & 0x01) != 0; if (!this.apu.pulse1Channel.Duration_reloadEnabled) { this.apu.pulse1Channel.Duration_counter = 0; } // SQ2 this.apu.pulse2Channel.Duration_reloadEnabled = (value & 0x02) != 0; if (!this.apu.pulse2Channel.Duration_reloadEnabled) { this.apu.pulse2Channel.Duration_counter = 0; } // TRL this.apu.triangleChannel.Duration_reloadEnabled = (value & 0x04) != 0; if (!this.apu.triangleChannel.Duration_reloadEnabled) { this.apu.triangleChannel.Duration_counter = 0; } // NOZ this.apu.noiseChannel.Duration_reloadEnabled = (value & 0x08) != 0; if (!this.apu.noiseChannel.Duration_reloadEnabled) { this.apu.noiseChannel.Duration_counter = 0; } // DMC if ((value & 0x10) != 0) { if (this.apu.dmcChannel.dmc_dmaSize == 0) { this.apu.dmcChannel.dmc_dmaSize = this.apu.dmcChannel.dmc_dmaSizeRefresh; this.apu.dmcChannel.dmc_dmaAddr = this.apu.dmcChannel.dmc_dmaAddrRefresh; } } else { this.apu.dmcChannel.dmc_dmaSize = 0; } // Disable DMC IRQ this.apu.dmcChannel.DeltaIrqOccur = false; Interrupts.IRQFlags &= ~Interrupts.IRQ_DMC; // RDY ? if (!this.apu.dmcChannel.dmc_bufferFull && this.apu.dmcChannel.dmc_dmaSize > 0) { this.dma.AssertDMCDMA(); } break; } case 0x4016: { if (this.input.inputStrobe > (value & 0x01)) { if (this.input.IsFourPlayers) { this.input.PORT0 = this.input.joypad3.GetData() << 8 | this.input.joypad1.GetData() | 0x01010000; this.input.PORT1 = this.input.joypad4.GetData() << 8 | this.input.joypad2.GetData() | 0x02020000; } else { this.input.PORT0 = this.input.joypad1.GetData() | 0x01010100; // What is this ? see * this.input.PORT1 = this.input.joypad2.GetData() | 0x02020200; } } if (this.legacy.IsVSUnisystem) { board.VSUnisystem4016RW(ref value); } this.input.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: { this.apu.SequencingMode = (value & 0x80) != 0; this.apu.FrameIrqEnabled = (value & 0x40) == 0; this.apu.CurrentSeq = 0; if (!this.apu.SequencingMode) { this.apu.Cycles = Apu.SequenceMode0[this.apu.SystemIndex][0]; } else { this.apu.Cycles = Apu.SequenceMode1[this.apu.SystemIndex][0]; } if (!this.apu.oddCycle) { this.apu.Cycles++; } else { this.apu.Cycles += 2; } if (!this.apu.FrameIrqEnabled) { this.apu.FrameIrqFlag = false; Interrupts.IRQFlags &= ~Interrupts.IRQ_APU; } break; } } #endregion } else if (address < 0x6000) // Cartridge Expansion Area almost 8K { if (this.legacy.IsVSUnisystem && address == 0x4020) { this.input.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); } }