All pastes #2127807 Raw Edit

Famicom Disk System emul. WIP

public cpp v1 · immutable
#2127807 ·published 2012-03-13 13:33 UTC
rendered paste body
// Integer typestypedef unsigned char u8;// Bitfield utilitiestemplate<unsigned bitno, unsigned nbits=1, typename T=u8>struct RegBit{    T data;    enum { mask = (1u << nbits) - 1u };    template<typename T2>    RegBit& operator=(T2 val)    {        data = (data & ~(mask << bitno)) | ((nbits > 1 ? val & mask : !!val) << bitno);        return *this;    }    operator unsigned() const { return (data >> bitno) & mask; }};// FDS EMULATIONnamespace GamePak{    /**************************************************/    /*** FDS timer IRQ variables: ***/    /**************************************************/    // Are timer IRQs enabled?    bool FDS_TimerIRQ_Enable = false;    // Which value will be reloded upon write to $4022    unsigned FDS_IRQ_ReloadValue = 0;    // Number of CPU cycles remaining until next FDS timer IRQ    unsigned FDS_IRQ_Counter = 0;    /**************************************************/    /*** FDS disk drive variables ***/    /**************************************************/    // Current side of the disk. Used internally.    u8 FDS_CurrentDiskSide = 0;    // Current position on the current side of the disk (0 = first byte)    unsigned FDS_Position = 0;    // Number of CPU cycles remaining until our virtual disk drive    // manages to transfer the next byte to/from the disk.    unsigned FDS_Delay = 0;    // CRC sum.    unsigned FDS_CRC = 0;    // Last value written to $4021 (byte to be written to disk)    u8 FDS_WriteData = 0;    // Last value written to $4023 (register enable mask)    u8 FDS_RegEnable = 3;    // Last value written to $4025    union    {        u8 raw;        RegBit<0,1,u8> MotorOn;        // Bit 0 (&0x01)        RegBit<1,1,u8> Bit1;           // Bit 1 (&0x02)        RegBit<2,1,u8> Reading;        // Bit 2 (&0x04)        RegBit<4,1,u8> CRCmode;        // Bit 3 (&0x08)        RegBit<6,1,u8> Bit6;           // Bit 6 (&0x40)        RegBit<7,1,u8> DiskIRQ_Enable; // Bit 7 (&0x80)    } FDS_Reg4025;    // Last value written to $4026 (only used for bitmasking $4033)    u8 FDS_Reg4026 = 0xFF;    // Next value to be read from $4031 (byte read from disk)    u8 FDS_ReadData = 0;    // Whether the FDS has noticed that disk is inserted.    bool FDS_DiskReady = true;    //     bool FDS_TransferComplete = false;    bool FDS_GapFound = false;    /**************************************************/    /* FDS I/O function.     * Called whenever the CPU reads or writes $4020..$403F.     * For brevity of this code snipped, assume that no     * writes/reads outside that region ever happen.     * Sound emulation is omitted for brevity.     */    /**************************************************/    u8 FDS_Register_Access(unsigned addr, u8 value, bool write)    {        switch( (addr & 0xFF) | (write ? 0x100 : 0x000) )        {            /*** WRITE PORTS (4020..4026) ***/            case 0x120: // 4020: Write low 8 bits of timer IRQ reload value                CPU::intr.FDS_TIMER_IRQ = false; // Untrigger IRQ                FDS_IRQ_ReloadValue = (FDS_IRQ_ReloadValue & 0xFF00) | value;                break;            case 0x121: // 4021: Write high 8 bits of timer IRQ reload value                CPU::intr.FDS_TIMER_IRQ = false; // Untrigger IRQ                FDS_IRQ_ReloadValue = (FDS_IRQ_ReloadValue & 0x00FF) | (value << 8);                break;            case 0x122: // 4022: Reload and enable/disable timer IRQ                CPU::intr.FDS_TIMER_IRQ = false; // Untrigger IRQ                FDS_IRQ_Counter     = FDS_IRQ_ReloadValue;                FDS_TimerIRQ_Enable = value & 2;                break;            case 0x123: // 4023: Set register write protection (TODO: handle this)                FDS_RegEnable = value;                break;            case 0x124: // 4024: Set next byte to be written to disk.                FDS_WriteData = value;                FDS_TransferComplete = false;                // Confirm any pending disk IRQ.                CPU::intr.FDS_DISK_IRQ = false;                break;            case 0x125: // 4025: Control the drive.                if(!(value & 0x50) && FDS_Reg4025.Bit6)                {                    /* FCEU does this. I have no idea really why.                     * My intuition says it is WRONG, but removing this                     * makes disksys.rom unable to load games.                     */                    if(FDS_Position > 0) FDS_Position -= 2;                }                // Store the new flags.                FDS_Reg4025.raw = value;                // If Bit 6 was stored as "1", reset the CRC.                if(FDS_Reg4025.Bit6) FDS_CRC = 0;                // Reload the disk access delay. This is probably WRONG.                FDS_Delay = 150;                // Set PPU mirroring (for some reason, horiz/vert seems to be reversed)                SetMirroring( (value&8) ? 0x1100 : 0x1010 );                break;            case 0x126: // 4026: Write the bitmask that only controls what $4033 returns.                FDS_Reg4026 = value;                break;            /** READ PORTS (4030..4033) **/            case 0x030: // 4030: return status and confirm any IRQ.            {                unsigned v = (CPU::intr.FDS_TIMER_IRQ ? 1 : 0)                           | (FDS_TransferComplete    ? 2 : 0)                         //| (FDS_DiskReady ? 0x80 : 0x00)                           | (FDS_DiskReady ? 0x00 : 0x40)                           ;                // Confirm any IRQ (both timer and disk)                CPU::intr.FDS_TIMER_IRQ = false;                CPU::intr.FDS_DISK_IRQ = false;                // Reset $4030 bit 1 (& 0x02).                FDS_TransferComplete = false;                return v;            }            case 0x031: // 4031: Return what was read from the disk                // Confirm the FDS disk transfer IRQ                CPU::intr.FDS_DISK_IRQ = false;                // Reset $4030 bit 1 (& 0x02)                FDS_TransferComplete = false;                // Return the databyte last fetched from disk                return FDS_ReadData;            case 0x032: // 4032: return more status.                // If disk is not inserted,      assert bits 0 and 2                // If disk was not yet detected, assert bits 1 and 2                // If motor is not running,      assert bit 1                // If $4025 bit 1 is set,        assert bit 1                // If disk is write protected,   assert bit 2                // Always assert bit 6                return ((FDS_CurrentDiskSide != 0xFF) ? 0 : 5)                     | (FDS_DiskReady       ? 0 : 6)                     | (FDS_Reg4025.MotorOn ? 0 : 2)                     | (FDS_Reg4025.Bit1    ? 2 : 0)                    // |  0x04 // Write protected                     |  0x40 // constant                     ;            case 0x033: // 4033: Return battery status.                return 0x80 & FDS_Reg4026;        }        return 0; // dummy return    }    /******************************************************/    /* Tick(): This function is called at each CPU cycle. */    /******************************************************/    void tick()    {        /* If FDS timer IRQ is enabled and the counter is nonzero,         * decrement the counter. If it becomes zero,         * assert the IRQ line and disable the counter.         */        if(FDS_TimerIRQ_Enable && FDS_IRQ_Counter && !--FDS_IRQ_Counter)        {            FDS_TimerIRQ_Enable = false;            CPU::intr.FDS_TIMER_IRQ = true;        }        /* If there is no disk inserted, the disk system does no activity. */        if(FDS_CurrentDiskSide == 0xFF)        {            return;        }        /* So, disk is in. Have we detected it yet? */        if(!FDS_DiskReady)        {            // Nope. Can we detect it? Detect it when the delay becomes zero.            if(FDS_Delay > 0) --FDS_Delay;            if(!FDS_Delay) FDS_DiskReady = true;        }        /* If we have not detected it yet, the disk system does no activity. */        if(!FDS_DiskReady)            return;        /* So, disk is inside and detected. */        /* Is position-reset requested? ($4025 bit 1) */        if(FDS_Reg4025.Bit1)        {            // Reset position and do nothing else.            FDS_Position = 0;            FDS_GapFound = false;            return;        }        /* If the motor is not running, do nothing else. */        if(!FDS_Reg4025.MotorOn)        {            return;        }        /* Is the disk system ready with its next activity? */        if(FDS_Delay > 0) --FDS_Delay; // Elapse the cycle delay counter        if(FDS_Delay > 0) return;        /* It is ready. */        /* Which byte of the disk-image is the disk head currently over? */        unsigned pos = 65500 * FDS_CurrentDiskSide + FDS_Position;        bool     got = false;        /* If bit $4025 bit 6 is set and we have not yet signalled         * the first byte of the disk, do it now.         */        if(FDS_Reg4025.Bit6 && !FDS_GapFound)        {            FDS_GapFound = true;            /* Read data from disk. */            FDS_ReadData = DiskImage[pos];            got = true;            /* Advance the reading position only if IRQ is enabled.             * PROBABLY WRONG, but removing this condition makes             * disksys.rom print error codes.             */            if(FDS_Reg4025.DiskIRQ_Enable)                FDS_Position++;        }        else        {            /* So we're not _trying_ to find the first byte of the disk.             * If we have already found it, process the next data byte.             */            if(FDS_GapFound)            {                /* Transfer the data from/to disk. */                if(FDS_Reg4025.Reading)                    FDS_ReadData = DiskImage[pos];                else                    DiskImage[pos] = (FDS_Reg4025.Bit6 ? FDS_WriteData : 0x00);                got = true;                /* Advance the reading position only if IRQ is enabled.                 * PROBABLY WRONG, but removing this condition makes                 * disksys.rom print error codes.                 */                if(FDS_Reg4025.DiskIRQ_Enable)                    FDS_Position++;                /* If we reached the end of the disk side, set $4025 bit 1                 * so that we return to the beginning of the disk at next cycle.                 * Probably WRONG.                 */                if(FDS_Position >= 65500)                    FDS_Reg4025.Bit1 = 1;            }        }        /* Did we successfully transfer a byte from/to the disk? */        if(got)        {            /* We did. Set $4030 bit 1. */            FDS_TransferComplete = true;            /* Also assert the disk IRQ, if permitted. */            if(FDS_Reg4025.DiskIRQ_Enable) CPU::intr.FDS_DISK_IRQ = true;        }        /* After 150 CPU cycles, we'll consider the situation again */        FDS_Delay = 150;    }    /* This function "inserts" a disk. Called from User Interface. */    void FDS_InsertSide(unsigned side)    {        FDS_CurrentDiskSide = side;        FDS_Delay           = 1000;        // Random constant. About 1000 cycles to detect the disk is in.    }    /* This function "ejects" the disk. Called from User Interface. */    void FDS_Eject()    {        FDS_CurrentDiskSide = 0xFF;        FDS_DiskReady       = false;        FDS_Delay           = 0;        FDS_Reg4025.MotorOn = false;        FDS_GapFound     = false;    }}