using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using BizHawk.Common; using BizHawk.Common.NumberExtensions; using BizHawk.Emulation.Common; namespace BizHawk.Emulation.Cores.Consoles.ChannelF { /// /// Fairchild F3850 (F8) CPU (Channel F-specific implementation) /// /// The F8 microprocessor is made up of separate interchangeable devices /// The Channel F has: /// * x1 F3850 CPU (central processing unit) /// * x2 F3851 PSU (program storage unit) /// The CPU does not have its own data counters or program counters, rather each F8 component connected to the CPU /// holds their own PCs and SPs and are all connected to the ROMC (ROM control) pins that are serviced by the CPU. /// Every device must respond to changes in the CPU ROMC pins output and they each update their PCs and DCs in the same way. /// e.g. SPs and PCs should always be identical /// Each device has a factory ROM mask applied and with every ROMC change observed is able to know whether it should respond (via the shared data bus) /// or not based on the value within its counters. /// /// For this reason we will hold the PCs and SPs within the F3850 implementation. /// /// We are currently also *not* using a separate F3851 implementation. In reality the F3851 chip has/does: /// * 1024 byte masked ROM /// * x2 16-bit program counters /// * x1 16-bit data counter /// * Programmable timer /// * Interrupt logic /// /// However, the Channel F does not use the timer or interrupt logic at all (as far as I can see) so we can hopefully just /// maintain the PC and DC here in the CPU and move the ROMs into the core. /// public sealed partial class F3850 { // operations that can take place in an instruction public const byte ROMC_01 = 1; public const byte ROMC_02 = 2; public const byte ROMC_03_S = 3; public const byte ROMC_04 = 4; public const byte ROMC_05 = 5; public const byte ROMC_06 = 6; public const byte ROMC_07 = 7; public const byte ROMC_08 = 8; public const byte ROMC_09 = 9; public const byte ROMC_0A = 10; public const byte ROMC_0B = 11; public const byte ROMC_0C = 12; public const byte ROMC_0D = 13; public const byte ROMC_0E = 14; public const byte ROMC_0F = 15; public const byte ROMC_10 = 16; public const byte ROMC_11 = 17; public const byte ROMC_12 = 18; public const byte ROMC_13 = 19; public const byte ROMC_14 = 20; public const byte ROMC_15 = 21; public const byte ROMC_16 = 22; public const byte ROMC_17 = 23; public const byte ROMC_18 = 24; public const byte ROMC_19 = 25; public const byte ROMC_1A = 26; public const byte ROMC_1B = 27; public const byte ROMC_1C_S = 28; public const byte ROMC_1D = 29; public const byte ROMC_1E = 30; public const byte ROMC_1F = 31; public const byte ROMC_00_S = 32; public const byte ROMC_00_L = 33; public const byte ROMC_03_L = 34; public const byte ROMC_1C_L = 35; public const byte IDLE = 0; public const byte END = 51; public const byte OP_LR8 = 100; public const byte OP_SHFT_R = 101; public const byte OP_SHFT_L = 102; public const byte OP_LNK = 103; public const byte OP_DI = 104; public const byte OP_EI = 105; public const byte OP_INC8 = 106; public const byte OP_AND8 = 107; public const byte OP_OR8 = 108; public const byte OP_XOR8 = 109; //public const byte OP_COM = 110; public const byte OP_SUB8 = 110; public const byte OP_ADD8 = 111; public const byte OP_CI = 112; public const byte OP_IS_INC = 113; public const byte OP_IS_DEC = 114; public const byte OP_LISU = 115; public const byte OP_LISL = 116; public const byte OP_BT = 117; public const byte OP_ADD8D = 118; public const byte OP_BR7 = 119; public const byte OP_BF = 120; public const byte OP_IN = 121; public const byte OP_OUT = 122; //public const byte OP_AS_IS = 123; //public const byte OP_XS_IS = 124; //public const byte OP_NS_IS = 125; public const byte OP_LR_A_DB_IO = 126; public const byte OP_DS = 127; //public const byte OP_CLEAR_FLAGS = 126; //public const byte OP_SET_FLAGS_SZ = 127; public const byte OP_LIS = 128; public F3850() { Reset(); } public void Reset() { ResetRegisters(); TotalExecutedCycles = 0; instr_pntr = 0; PopulateCURINSTR( ROMC_1C_S, // S IDLE, IDLE, IDLE, ROMC_08, // L IDLE, IDLE, IDLE, IDLE, IDLE, ROMC_00_S, // S IDLE, IDLE, END); ClearFlags_Func(); FlagICB = false; } public IMemoryCallbackSystem MemoryCallbacks { get; set; } // Memory Access public Func ReadMemory; public Action WriteMemory; public Func PeekMemory; public Func DummyReadMemory; // Hardware I/O Port Access public Func ReadHardware; public Action WriteHardware; public Action OnExecFetch; public void SetCallbacks ( Func ReadMemory, Func DummyReadMemory, Func PeekMemory, Action WriteMemory, Func ReadHardware, Action WriteHardware ) { this.ReadMemory = ReadMemory; this.DummyReadMemory = DummyReadMemory; this.PeekMemory = PeekMemory; this.WriteMemory = WriteMemory; this.ReadHardware = ReadHardware; this.WriteHardware = WriteHardware; } /// /// Runs a single CPU clock cycle /// public void ExecuteOne() { switch (cur_instr[instr_pntr++]) { // always the last tick within an opcode instruction cycle case END: OnExecFetch?.Invoke(RegPC0); TraceCallback?.Invoke(State()); opcode = (byte)Regs[DB]; instr_pntr = 0; FetchInstruction(); break; // used as timing 'padding' case IDLE: break; // load one register into another (or databus) case OP_LR8: LR_Func(cur_instr[instr_pntr++], cur_instr[instr_pntr++]); break; // load DB into A (as a part of an IN or INS instruction) case OP_LR_A_DB_IO: LR_A_IO_Func(cur_instr[instr_pntr++], cur_instr[instr_pntr++]); break; // loads supplied index value into register case OP_LIS: Regs[ALU1] = (byte)(cur_instr[instr_pntr++] & 0x0F); LR_Func(A, ALU1); break; // Shift register n bit positions to the right (zero fill) case OP_SHFT_R: SR_Func(cur_instr[instr_pntr++], cur_instr[instr_pntr++]); break; // Shift register n bit positions to the left (zero fill) case OP_SHFT_L: SL_Func(cur_instr[instr_pntr++], cur_instr[instr_pntr++]); break; // x <- (x) ADD y case OP_ADD8: ADD_Func(cur_instr[instr_pntr++], cur_instr[instr_pntr++]); break; // x <- (x) MINUS y case OP_SUB8: SUB_Func(cur_instr[instr_pntr++], cur_instr[instr_pntr++]); break; // x <- (x) ADD y (decimal) case OP_ADD8D: ADDD_Func(cur_instr[instr_pntr++], cur_instr[instr_pntr++]); break; // A <- (A) + (C) case OP_LNK: Regs[ALU0] = (byte)(FlagC ? 1 : 0); ADD_Func(A, ALU0); break; // Clear ICB status bit case OP_DI: FlagICB = false; break; // Set ICB status bit case OP_EI: FlagICB = true; break; // x <- (y) XOR DB case OP_XOR8: XOR_Func(cur_instr[instr_pntr++], cur_instr[instr_pntr++]); break; // x <- (x) + 1 case OP_INC8: ADD_Func(cur_instr[instr_pntr++], cur_instr[instr_pntr++]); break; // x <- (y) & DB case OP_AND8: AND_Func(cur_instr[instr_pntr++], cur_instr[instr_pntr++]); break; // x <- (y) | DB case OP_OR8: OR_Func(cur_instr[instr_pntr++], cur_instr[instr_pntr++]); break; // DB + (x) + 1 (modify flags without saving result) case OP_CI: CI_Func(); break; // ISAR is incremented case OP_IS_INC: Regs[ISAR] = (byte)((Regs[ISAR]& 0x38) | ((Regs[ISAR] + 1) & 0x07)); break; // ISAR is decremented case OP_IS_DEC: Regs[ISAR] = (byte)((Regs[ISAR] & 0x38) | ((Regs[ISAR] - 1) & 0x07)); break; // set the upper octal ISAR bits (b3,b4,b5) case OP_LISU: Regs[ISAR] = (byte)((((Regs[ISAR] & 0x07) | (cur_instr[instr_pntr++] & 0x07) << 3)) & 0x3F); break; // set the lower octal ISAR bits (b0,b1,b2) case OP_LISL: Regs[ISAR] = (byte) (((Regs[ISAR] & 0x38) | (cur_instr[instr_pntr++] & 0x07)) & 0x3F); break; // decrement scratchpad byte //case OP_DS: //SUB_Func(cur_instr[instr_pntr++], ONE); //break; // Branch on TRUE case OP_BT: bool branchBT = false; switch (cur_instr[instr_pntr++]) { case 0: // do not branch break; case 1: // branch if positive (sign bit is set) if (FlagS) branchBT = true; break; case 2: // branch on carry (carry bit is set) if (FlagC) branchBT = true; break; case 3: // branch if positive or on carry if (FlagS || FlagC) branchBT = true; break; case 4: // branch if zero (zero bit is set) if (FlagZ) branchBT = true; break; case 5: // branch if positive and zero if (FlagS || FlagZ) branchBT = true; break; case 6: // branch if zero or on carry if (FlagZ || FlagC) branchBT = true; break; case 7: // branch if positive or on carry or zero if (FlagS || FlagC || FlagZ) branchBT = true; break; } instr_pntr = 0; if (branchBT) DO_BRANCH(); else DONT_BRANCH(); break; // Branch on ISARL case OP_BR7: instr_pntr = 1; // lose a cycle if (Regs[ISAR].Bit(0) && Regs[ISAR].Bit(1) && Regs[ISAR].Bit(2)) { DONT_BRANCH(); } else { DO_BRANCH(); } break; // Branch on FALSE case OP_BF: bool branchBF = false; switch (cur_instr[instr_pntr++]) { case 0: // unconditional branch relative branchBF = true; break; case 1: // branch on negative (sign bit is reset) if (!FlagS) branchBF = true; break; case 2: // branch if no carry (carry bit is reset) if (!FlagC) branchBF = true; break; case 3: // branch if no carry and negative if (!FlagC && !FlagS) branchBF = true; break; case 4: // branch if not zero (zero bit is reset) if (!FlagZ) branchBF = true; break; case 5: // branch if not zero and negative if (!FlagS && !FlagZ) branchBF = true; break; case 6: // branch if no carry and result is no zero if (!FlagC && !FlagZ) branchBF = true; break; case 7: // branch if not zero, carry and sign if (!FlagS && !FlagC && !FlagZ) branchBF = true; break; case 8: // branch if there is no overflow (OVF bit is reset) if (!FlagO) branchBF = true; break; case 9: // branch if negative and no overflow if (!FlagS && !FlagO) branchBF = true; break; case 0xA: // branch if no overflow and no carry if (!FlagO && !FlagC) branchBF = true; break; case 0xB: // branch if no overflow, no carry & negative if (!FlagO && !FlagC && !FlagS) branchBF = true; break; case 0xC: // branch if no overflow and not zero if (!FlagO && !FlagZ) branchBF = true; break; case 0xD: // branch if no overflow, not zero and neg if (!FlagS && !FlagO && !FlagZ) branchBF = true; break; case 0xE: // branch if no overflow, no carry & not zero if (!FlagO && !FlagC && !FlagZ) branchBF = true; break; case 0xF: // all neg if (!FlagO && !FlagC && !FlagS && FlagZ) branchBF = true; break; } instr_pntr = 0; if (branchBF) DO_BRANCH(); else DONT_BRANCH(); break; // A <- (I/O Port 0 or 1) case OP_IN: instr_pntr++; // dest == A Regs[ALU0] = cur_instr[instr_pntr++]; // src IN_Func(A, ALU0); break; // I/O Port 0 or 1 <- (A) case OP_OUT: WriteHardware(cur_instr[instr_pntr++], (byte)Regs[cur_instr[instr_pntr++]]); break; // instruction fetch // The device whose address space includes the contents of the PC0 register must place on the data bus the op code addressed by PC0; // then all devices increments the content of PC0. // CYCLE LENGTH: S case ROMC_00_S: Read_Func(DB, PC0l, PC0h); RegPC0++; break; // instruction fetch // The device whose address space includes the contents of the PC0 register must place on the data bus the op code addressed by PC0; // then all devices increments the content of PC0. // CYCLE LENGTH: L case ROMC_00_L: Read_Func(DB, PC0l, PC0h); RegPC0++; break; // The device whose address space includes the contents of the PC0 register must place on the data bus the contents of the memory location // addressed by by PC0; then all devices add the 8-bit value on the data bus, as a signed binary number, to PC0 // CYCLE LENGTH: L case ROMC_01: Read_Func(DB, PC0l, PC0h); RegPC0 += (ushort)((SByte) Regs[DB]); break; // The device whose DC0 address addresses a memory word within the address space of that device must place on the data bus the contents // of the memory location addressed by DC0; then all devices increment DC0 // CYCLE LENGTH: L case ROMC_02: Read_Func(DB, DC0l, DC0h); RegDC0++; break; // Similar to 0x00, except that it is used for Immediate Operand fetches (using PC0) instead of instruction fetches // CYCLE LENGTH: S case ROMC_03_S: Read_Func(DB, PC0l, PC0h); RegPC0++; Regs[IO] = Regs[DB]; break; // Similar to 0x00, except that it is used for Immediate Operand fetches (using PC0) instead of instruction fetches // CYCLE LENGTH: L case ROMC_03_L: Read_Func(DB, PC0l, PC0h); RegPC0++; Regs[IO] = Regs[DB]; break; // Copy the contents of PC1 into PC0 // CYCLE LENGTH: S case ROMC_04: RegPC0 = RegPC1; break; // Store the data bus contents into the memory location pointed to by DC0; increment DC0 // CYCLE LENGTH: L case ROMC_05: Write_Func(DC0l, DC0h, DB); break; // Place the high order byte of DC0 on the data bus // CYCLE LENGTH: L case ROMC_06: Regs[DB] = (byte)Regs[DC0h]; break; // Place the high order byte of PC1 on the data bus // CYCLE LENGTH: L case ROMC_07: Regs[DB] = (byte)Regs[PC1h]; break; // All devices copy the contents of PC0 into PC1. The CPU outputs zero on the data bus in this ROMC state. // Load the data bus into both halves of PC0, this clearing the register. // CYCLE LENGTH: L case ROMC_08: RegPC1 = RegPC0; Regs[DB] = 0; Regs[PC0h] = 0; Regs[PC0l] = 0; break; // The device whose address space includes the contents of the DC0 register must place the low order byte of DC0 onto the data bus // CYCLE LENGTH: L case ROMC_09: Regs[DB] = (byte)Regs[DC0l]; break; // All devices add the 8-bit value on the data bus, treated as a signed binary number, to the data counter // CYCLE LENGTH: L case ROMC_0A: RegDC0 += (ushort) ((sbyte) Regs[DB]); break; // The device whose address space includes the value in PC1 must place the low order byte of PC1 on the data bus // CYCLE LENGTH: L case ROMC_0B: Regs[DB] = (byte)Regs[PC1l]; break; // The device whose address space includes the contents of the PC0 register must place the contents of the memory word addressed by PC0 // onto the data bus; then all devices move the value that has just been placed on the data bus into the low order byte of PC0 // CYCLE LENGTH: L case ROMC_0C: Read_Func(DB, PC0l, PC0h); Regs[PC0l] = Regs[DB]; break; // All devices store in PC1 the current contents of PC0, incremented by 1; PC0 is unaltered // CYCLE LENGTH: S case ROMC_0D: RegPC1 = (ushort)(RegPC0 + 1); break; // The device whose address space includes the contents of PC0 must place the contents of the word addressed by PC0 onto the data bus. // The value on the data bus is then moved to the low order byte of DC0 by all devices // CYCLE LENGTH: L case ROMC_0E: Read_Func(DB, PC0l, PC0h); Regs[DC0l] = Regs[DB]; break; // The interrupting device with the highest priority must place the low order byte of the interrupt vector on the data bus. // All devices must copy the contents of PC0 into PC1. All devices must move the contents of the data bus into the low order byte of PC0 // CYCLE LENGTH: L case ROMC_0F: throw new NotImplementedException("ROMC 0x0F not implemented"); // Inhibit any modification to the interrupt priority logic // CYCLE LENGTH: L case ROMC_10: throw new NotImplementedException("ROMC 0x10 not implemented"); // The device whose memory space includes the contents of PC0 must place the contents of the addressed memory word on the data bus. // All devices must then move the contents of the data bus to the upper byte of DC0 // CYCLE LENGTH: L case ROMC_11: Read_Func(DB, PC0l, PC0h); Regs[DC0h] = Regs[DB]; break; // All devices copy the contents of PC0 into PC1. All devices then move the contents of the data bus into the low order byte of PC0 // CYCLE LENGTH: L case ROMC_12: RegPC1 = RegPC0; Regs[PC0l] = Regs[DB]; break; // The interrupting device with the highest priority must move the high order half of the interrupt vector onto the data bus. // All devices must move the conetnts of the data bus into the high order byte of of PC0. The interrupting device resets its // interrupt circuitry (so that it is no longer requesting CPU servicing and can respond to another interrupt) // CYCLE LENGTH: L case ROMC_13: throw new NotImplementedException("ROMC 0x13 not implemented"); // All devices move the contents of the data bus into the high order byte of PC0 // CYCLE LENGTH: L case ROMC_14: Regs[PC0h] = Regs[DB]; break; // All devices move the contents of the data bus into the high order byte of PC1 // CYCLE LENGTH: L case ROMC_15: Regs[PC1h] = Regs[DB]; break; // All devices move the contents of the data bus into the high order byte of DC0 // CYCLE LENGTH: L case ROMC_16: Regs[DC0h] = Regs[DB]; break; // All devices move the contents of the data bus into the low order byte of PC0 // CYCLE LENGTH: L case ROMC_17: Regs[PC0l] = Regs[DB]; break; // All devices move the contents of the data bus into the low order byte of PC1 // CYCLE LENGTH: L case ROMC_18: Regs[PC1l] = Regs[DB]; break; // All devices move the contents of the data bus into the low order byte of DC0 // CYCLE LENGTH: L case ROMC_19: Regs[DC0l] = Regs[DB]; break; // During the prior cycle, an I/O port timer or interrupt control register was addressed; the device containing the addressed // port must move the current contents of the data bus into the addressed port // CYCLE LENGTH: L case ROMC_1A: WriteHardware(Regs[IO], (byte)Regs[DB]); break; // During the prior cycle, the data bus specified the address of an I/O port. The device containing the addressed I/O port // must place the contents of the I/O port on the data bus. (Note that the contents of the timer and interrupt control // registers cannot be read back onto the data bus) // CYCLE LENGTH: L case ROMC_1B: IN_Func(DB, IO); //Regs[DB] = ReadHardware(Regs[IO]); break; // None // CYCLE LENGTH: S case ROMC_1C_S: break; // None // CYCLE LENGTH: L case ROMC_1C_L: break; // Devices with DC0 and DC1 registers must switch registers. Devices without a DC1 register perform no operation // CYCLE LENGTH: S case ROMC_1D: // we have no DC1 in this implementation break; // The device whose address space includes the contents of PC0 must place the low order byte of PC0 onto the data bus // CYCLE LENGTH: L case ROMC_1E: Regs[DB] = (byte)Regs[PC0l]; break; // The device whose address space includes the contents of PC0 must place the high order byte of PC0 onto the data bus // CYCLE LENGTH: L case ROMC_1F: Regs[DB] = (byte)Regs[PC0h]; break; } TotalExecutedCycles++; } public Action TraceCallback; public string TraceHeader => "F3850: PC, machine code, mnemonic, operands, flags (IOZCS), registers (PC1, DC0, A, ISAR, DB, IO, J, H, K, Q, R00-R63), Cycles"; public TraceInfo State(bool disassemble = true) { int bytes_read = 0; ushort pc = (ushort)(RegPC0 - 1); string disasm = disassemble ? Disassemble(pc, ReadMemory, out bytes_read) : "---"; string byte_code = null; for (ushort i = 0; i < bytes_read; i++) { byte_code += ReadMemory((ushort)(pc + i)).ToString("X2"); if (i < (bytes_read - 1)) { byte_code += " "; } } return new TraceInfo { Disassembly = string.Format( "{0:X4}: {1} {2}", pc, byte_code.PadRight(12), disasm.PadRight(26)), RegisterInfo = string.Format( "Flags:{75}{76}{77}{78}{79} " + "PC1:{0:X4} DC0:{1:X4} A:{2:X2} ISAR:{3:X2} DB:{4:X2} IO:{5:X2} J:{6:X2} H:{7:X4} K:{8:X4} Q:{9:X4} " + "R0:{10:X2} R1:{11:X2} R2:{12:X2} R3:{13:X2} R4:{14:X2} R5:{15:X2} R6:{16:X2} R7:{17:X2} R8:{18:X2} R9:{19:X2} " + "R10:{20:X2} R11:{21:X2} R12:{22:X2} R13:{23:X2} R14:{24:X2} R15:{25:X2} R16:{26:X2} R17:{27:X2} R18:{28:X2} R19:{29:X2} " + "R20:{30:X2} R21:{31:X2} R22:{32:X2} R23:{33:X2} R24:{34:X2} R25:{35:X2} R26:{36:X2} R27:{37:X2} R28:{38:X2} R29:{39:X2} " + "R30:{40:X2} R31:{41:X2} R32:{42:X2} R33:{43:X2} R34:{44:X2} R35:{45:X2} R36:{46:X2} R37:{47:X2} R38:{48:X2} R39:{49:X2} " + "R40:{50:X2} R41:{51:X2} R42:{52:X2} R43:{53:X2} R44:{54:X2} R45:{55:X2} R46:{56:X2} R47:{57:X2} R48:{58:X2} R49:{59:X2} " + "R50:{60:X2} R51:{61:X2} R52:{62:X2} R53:{63:X2} R54:{64:X2} R55:{65:X2} R56:{66:X2} R57:{67:X2} R58:{68:X2} R59:{69:X2} " + "R60:{70:X2} R61:{71:X2} R62:{72:X2} R63:{73:X2} " + "Cy:{74}", RegPC1, RegDC0, Regs[A], Regs[ISAR], Regs[DB], Regs[IO], Regs[J], (ushort)(Regs[Hl] | (Regs[Hh] << 8)), (ushort)(Regs[Kl] | (Regs[Kh] << 8)), (ushort)(Regs[Ql] | (Regs[Qh] << 8)), Regs[0], Regs[1], Regs[2], Regs[3], Regs[4], Regs[5], Regs[6], Regs[7], Regs[8], Regs[9], Regs[10], Regs[11], Regs[12], Regs[13], Regs[14], Regs[15], Regs[16], Regs[17], Regs[18], Regs[19], Regs[20], Regs[21], Regs[22], Regs[23], Regs[24], Regs[25], Regs[26], Regs[27], Regs[28], Regs[29], Regs[30], Regs[31], Regs[32], Regs[33], Regs[34], Regs[35], Regs[36], Regs[37], Regs[38], Regs[39], Regs[40], Regs[41], Regs[42], Regs[43], Regs[44], Regs[45], Regs[46], Regs[47], Regs[48], Regs[49], Regs[50], Regs[51], Regs[52], Regs[53], Regs[54], Regs[55], Regs[56], Regs[57], Regs[58], Regs[59], Regs[60], Regs[61], Regs[62], Regs[63], TotalExecutedCycles, FlagICB ? "I" : "i", FlagO ? "O" : "o", FlagZ ? "Z" : "z", FlagC ? "C" : "c", FlagS ? "S" : "s"), }; } /// /// Optimization method to set cur_instr /// private void PopulateCURINSTR(byte d0 = 0, byte d1 = 0, byte d2 = 0, byte d3 = 0, byte d4 = 0, byte d5 = 0, byte d6 = 0, byte d7 = 0, byte d8 = 0, byte d9 = 0, byte d10 = 0, byte d11 = 0, byte d12 = 0, byte d13 = 0, byte d14 = 0, byte d15 = 0, byte d16 = 0, byte d17 = 0, byte d18 = 0, byte d19 = 0, byte d20 = 0, byte d21 = 0, byte d22 = 0, byte d23 = 0, byte d24 = 0, byte d25 = 0, byte d26 = 0, byte d27 = 0, byte d28 = 0, byte d29 = 0, byte d30 = 0, byte d31 = 0, byte d32 = 0, byte d33 = 0, byte d34 = 0, byte d35 = 0, byte d36 = 0, byte d37 = 0) { cur_instr[0] = d0; cur_instr[1] = d1; cur_instr[2] = d2; cur_instr[3] = d3; cur_instr[4] = d4; cur_instr[5] = d5; cur_instr[6] = d6; cur_instr[7] = d7; cur_instr[8] = d8; cur_instr[9] = d9; cur_instr[10] = d10; cur_instr[11] = d11; cur_instr[12] = d12; cur_instr[13] = d13; cur_instr[14] = d14; cur_instr[15] = d15; cur_instr[16] = d16; cur_instr[17] = d17; cur_instr[18] = d18; cur_instr[19] = d19; cur_instr[20] = d20; cur_instr[21] = d21; cur_instr[22] = d22; cur_instr[23] = d23; cur_instr[24] = d24; cur_instr[25] = d25; cur_instr[26] = d26; cur_instr[27] = d27; cur_instr[28] = d28; cur_instr[29] = d29; cur_instr[30] = d30; cur_instr[31] = d31; cur_instr[32] = d32; cur_instr[33] = d33; cur_instr[34] = d34; cur_instr[35] = d35; cur_instr[36] = d36; cur_instr[37] = d37; } public void SyncState(Serializer ser) { ser.BeginSection(nameof(F3850)); ser.Sync(nameof(Regs), ref Regs, false); ser.Sync(nameof(cur_instr), ref cur_instr, false); ser.Sync(nameof(instr_pntr), ref instr_pntr); ser.EndSection(); } } }