Ejemplo n.º 1
0
        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);
            }
        }
Ejemplo n.º 2
0
        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);
            }
        }