/// <summary> /// For any given offset into the program we can generate the requisite /// IL based upon the opcode (first byte of the span) and any operands /// (further bytes from the span) /// /// This returns the opcodes to emit (with args) and the number of bytes /// consumed /// </summary> /// <param name="program"> /// The full ROM (not just the portion we're looking at) /// </param> /// <param name="programCounter"> /// Indicates what index into the ROM we are generating an instruction for /// </param> /// <param name="internals"> /// Provides references to all internal fields/methods on this class /// for e.g. register access /// </param> /// <param name="jumpLabels"> /// Provides labels for every address in the ROM for jump/call commands /// </param> /// <returns> /// A tuple containing: /// 1. The emitter which can be called to put instructions on an IL stream /// 2. The number of bytes consumed by this operation /// 3. The number of cycles taken by the operation (non-branching) /// </returns> private static (IEmitter, byte, long) GenerateILForOpcode(Span <byte> program, int programCounter, CpuInternalBuilders internals, Label[] jumpLabels) { var opcodeByte = program[programCounter]; var operand1 = program[(programCounter + 1) % program.Length]; var operand2 = program[(programCounter + 2) % program.Length]; var operandWord = (ushort)((operand2 << 8) + operand1); var opcode = Opcodes8080Decoder.Decode(opcodeByte); IEmitter emitter = opcode switch { Opcodes8080.NOP => new NOPEmitter(), Opcodes8080.LXI => new LXIEmitter(opcodeByte, operand1, operand2, operandWord), Opcodes8080.STAX => new STAXEmitter(opcodeByte), Opcodes8080.INX => new INXDCXEmitter(opcodeByte), Opcodes8080.INR => new INRDCREmitter(opcodeByte), Opcodes8080.DCR => new INRDCREmitter(opcodeByte), Opcodes8080.MVI => new MVIEmitter(opcodeByte, operand1), Opcodes8080.RLC => new RLCEmitter(), Opcodes8080.DAD => new DADEmitter(opcodeByte), Opcodes8080.LDAX => new LDAXEmitter(opcodeByte), Opcodes8080.DCX => new INXDCXEmitter(opcodeByte), Opcodes8080.RRC => new RRCEmitter(), Opcodes8080.RAL => new RALEmitter(), Opcodes8080.RAR => new RAREmitter(), Opcodes8080.SHLD => new SHLDEmitter(operandWord), Opcodes8080.DAA => new NOPEmitter(), // TODO - Actually implement DAA after handling aux carry flag Opcodes8080.LHLD => new LHLDEmitter(operandWord), Opcodes8080.CMA => new CMAEmitter(), Opcodes8080.STA => new STAEmitter(operandWord), Opcodes8080.STC => new STCEmitter(), Opcodes8080.LDA => new LDAEmitter(operandWord), Opcodes8080.CMC => new CMCEmitter(), Opcodes8080.MOV => new MOVEmitter(opcodeByte), Opcodes8080.HLT => new HLTEmitter(), Opcodes8080.ADD => new General8BitALUEmitter(opcodeByte, opcode), Opcodes8080.ADC => new General8BitALUEmitter(opcodeByte, opcode), Opcodes8080.SUB => new General8BitALUEmitter(opcodeByte, opcode), Opcodes8080.SBB => new General8BitALUEmitter(opcodeByte, opcode), Opcodes8080.ANA => new General8BitALUEmitter(opcodeByte, opcode), Opcodes8080.XRA => new General8BitALUEmitter(opcodeByte, opcode), Opcodes8080.ORA => new General8BitALUEmitter(opcodeByte, opcode), Opcodes8080.CMP => new General8BitALUEmitter(opcodeByte, opcode), Opcodes8080.RNZ => new RetEmitter(opcodeByte, internals.ZeroFlag, false), Opcodes8080.POP => new POPEmitter(opcodeByte), Opcodes8080.JNZ => new JumpOnFlagEmitter(opcodeByte, internals.ZeroFlag, false, jumpLabels[operandWord % program.Length], operandWord), Opcodes8080.JMP => new JumpOnFlagEmitter(opcodeByte, jumpLabels[operandWord % program.Length], operandWord), Opcodes8080.CNZ => new CallEmitter(opcodeByte, (ushort)(programCounter + opcode.Length()), internals.ZeroFlag, false, jumpLabels[operandWord % program.Length], (ushort)(operandWord % program.Length)), Opcodes8080.PUSH => new PUSHEmitter(opcodeByte), Opcodes8080.ADI => new General8BitALUEmitter(opcodeByte, opcode, operand1), Opcodes8080.RST => new CallEmitter(opcodeByte, (ushort)(programCounter + opcode.Length()), jumpLabels[opcodeByte & 0b0011_1000], (ushort)(opcodeByte & 0b0011_1000)),
private static MethodBuilder CreateSetFlagRegister(TypeBuilder typeBuilder, CpuInternalBuilders internals) { var method = typeBuilder.DefineMethod("SetFlagRegister", MethodAttributes.Public, CallingConventions.Standard, null, new[] { typeof(byte) }); method.SetImplementationFlags(MethodImplAttributes.IL | MethodImplAttributes.AggressiveInlining); var methodIL = method.GetILGenerator(); methodIL.Emit(OpCodes.Ldarg_0); methodIL.Emit(OpCodes.Ldarg_1); methodIL.Emit(OpCodes.Ldc_I4_1); methodIL.Emit(OpCodes.And); methodIL.Emit(OpCodes.Stfld, internals.CarryFlag); methodIL.Emit(OpCodes.Ldarg_0); methodIL.Emit(OpCodes.Ldarg_1); methodIL.Emit(OpCodes.Ldc_I4_4); methodIL.Emit(OpCodes.And); methodIL.Emit(OpCodes.Ldc_I4_2); methodIL.Emit(OpCodes.Shr_Un); methodIL.Emit(OpCodes.Stfld, internals.ParityFlag); methodIL.Emit(OpCodes.Ldarg_0); methodIL.Emit(OpCodes.Ldarg_1); methodIL.EmitLd8Immediate(0b0001_0000); methodIL.Emit(OpCodes.And); methodIL.Emit(OpCodes.Ldc_I4_4); methodIL.Emit(OpCodes.Shr_Un); methodIL.Emit(OpCodes.Stfld, internals.AuxCarryFlag); methodIL.Emit(OpCodes.Ldarg_0); methodIL.Emit(OpCodes.Ldarg_1); methodIL.EmitLd8Immediate(0b0100_0000); methodIL.Emit(OpCodes.And); methodIL.Emit(OpCodes.Ldc_I4_6); methodIL.Emit(OpCodes.Shr_Un); methodIL.Emit(OpCodes.Stfld, internals.ZeroFlag); methodIL.Emit(OpCodes.Ldarg_0); methodIL.Emit(OpCodes.Ldarg_1); methodIL.Emit(OpCodes.Ldc_I4, 0b1000_0000); methodIL.Emit(OpCodes.And); methodIL.Emit(OpCodes.Ldc_I4_7); methodIL.Emit(OpCodes.Shr_Un); methodIL.Emit(OpCodes.Stfld, internals.SignFlag); methodIL.Emit(OpCodes.Ret); return(method); }
internal static void EmitDebugString(this ILGenerator ilGenerator, CpuInternalBuilders internals) { const string formatString = "A={0:X2} B={1:X2} C={2:X2} D={3:X2} E={4:X2} H={5:X2} L={6:X2} SP={7:X4} S={8} C={9} A={10} P={11} Z={12}"; var wlParams = new[] { typeof(string), typeof(object[]) }; var writeLineMethodInfo = typeof(Console).GetMethod("WriteLine", wlParams); ilGenerator.Emit(OpCodes.Ldstr, formatString); ilGenerator.Emit(OpCodes.Ldc_I4, 13); ilGenerator.Emit(OpCodes.Newarr, typeof(object)); foreach (var(field, ix) in new[]
void IEmitter.Emit(ILGenerator methodIL, CpuInternalBuilders internals) { switch (_opcode) { case 0x01: // LXI BC methodIL.Emit(OpCodes.Ldarg_0); methodIL.EmitLd8Immediate(_operand1); methodIL.Emit(OpCodes.Stfld, internals.C); methodIL.Emit(OpCodes.Ldarg_0); methodIL.EmitLd8Immediate(_operand2); methodIL.Emit(OpCodes.Stfld, internals.B); break; case 0x11: // LXI DE methodIL.Emit(OpCodes.Ldarg_0); methodIL.EmitLd8Immediate(_operand1); methodIL.Emit(OpCodes.Stfld, internals.E); methodIL.Emit(OpCodes.Ldarg_0); methodIL.EmitLd8Immediate(_operand2); methodIL.Emit(OpCodes.Stfld, internals.D); break; case 0x21: // LXI HL methodIL.Emit(OpCodes.Ldarg_0); methodIL.EmitLd8Immediate(_operand1); methodIL.Emit(OpCodes.Stfld, internals.L); methodIL.Emit(OpCodes.Ldarg_0); methodIL.EmitLd8Immediate(_operand2); methodIL.Emit(OpCodes.Stfld, internals.H); break; case 0x31: // LXI SP methodIL.Emit(OpCodes.Ldarg_0); methodIL.Emit(OpCodes.Ldc_I4, _operandWord); methodIL.Emit(OpCodes.Stfld, internals.StackPointer); break; default: throw new ArgumentOutOfRangeException(nameof(_opcode), $"LXI shouldn't be run for opcode byte {_opcode}"); } }
private static MethodBuilder CreateFlagRegister(TypeBuilder typeBuilder, CpuInternalBuilders internals) { var method = typeBuilder.DefineMethod("GetFlagRegister", MethodAttributes.Public, CallingConventions.Standard, typeof(byte), Type.EmptyTypes); method.SetImplementationFlags(MethodImplAttributes.IL | MethodImplAttributes.AggressiveInlining); var methodIL = method.GetILGenerator(); methodIL.Emit(OpCodes.Ldarg_0); methodIL.Emit(OpCodes.Ldfld, internals.SignFlag); methodIL.Emit(OpCodes.Ldc_I4_7); methodIL.Emit(OpCodes.Shl); methodIL.Emit(OpCodes.Ldarg_0); methodIL.Emit(OpCodes.Ldfld, internals.ZeroFlag); methodIL.Emit(OpCodes.Ldc_I4_6); methodIL.Emit(OpCodes.Shl); methodIL.Emit(OpCodes.Or); methodIL.Emit(OpCodes.Ldarg_0); methodIL.Emit(OpCodes.Ldfld, internals.AuxCarryFlag); methodIL.Emit(OpCodes.Ldc_I4_4); methodIL.Emit(OpCodes.Shl); methodIL.Emit(OpCodes.Or); methodIL.Emit(OpCodes.Ldarg_0); methodIL.Emit(OpCodes.Ldfld, internals.ParityFlag); methodIL.Emit(OpCodes.Ldc_I4_2); methodIL.Emit(OpCodes.Shl); methodIL.Emit(OpCodes.Or); methodIL.Emit(OpCodes.Ldc_I4_2); methodIL.Emit(OpCodes.Or); methodIL.Emit(OpCodes.Ldarg_0); methodIL.Emit(OpCodes.Ldfld, internals.CarryFlag); methodIL.Emit(OpCodes.Or); methodIL.Emit(OpCodes.Ret); return(method); }
void IEmitter.Emit(ILGenerator methodIL, CpuInternalBuilders internals) => methodIL.Emit(OpCodes.Nop);