├── roms └── test │ ├── test.com │ ├── 8080EXER.COM │ ├── 8080EXM.COM │ ├── 8080PRE.COM │ ├── CPUTEST.COM │ └── TST8080.COM ├── SpaceInvadersJIT ├── SDL2.dll ├── ShiftRegister.cs ├── Properties │ └── launchSettings.json ├── SpaceInvadersJIT.csproj ├── Program.cs ├── SpaceInvadersKey.cs ├── SpaceInvadersInterruptUtils.cs └── SpaceInvadersApplication.cs ├── JIT8080 ├── 8080 │ ├── Register.cs │ ├── IRenderer.cs │ ├── IIOHandler.cs │ ├── IMemoryBus8080.cs │ ├── IInterruptUtils.cs │ └── Opcodes8080.cs ├── JIT8080.csproj └── Generator │ ├── Cpu8080.cs │ ├── CpuInternalBuilders.cs │ ├── FlagUtilities.cs │ ├── ILGeneratorExtensions.cs │ ├── StackUtilities.cs │ ├── General8BitALUEmitter.cs │ └── Emulator.cs ├── JIT8080.Tests ├── Mocks │ ├── TestRenderer.cs │ ├── TestIOHandler.cs │ ├── TestInterruptUtils.cs │ └── TestMemoryBus.cs ├── Opcodes │ ├── LoadTests.cs │ ├── JumpTests.cs │ ├── ExchangeTests.cs │ ├── RotateOperationTests.cs │ ├── StackTests.cs │ └── ArithmeticTests.cs ├── JIT8080.Tests.csproj ├── ValidOpcodeTests.cs ├── RegisterPairTests.cs └── FlagRegisterTests.cs ├── CPMEmulator ├── CPMEmulator.csproj ├── Properties │ └── launchSettings.json ├── Program.cs └── CPMApplication.cs ├── SpaceInvadersJIT.Tests ├── MemoryBusTests.cs ├── ShiftRegisterTests.cs └── SpaceInvadersJIT.Tests.csproj ├── LICENSE ├── azure-pipelines.yml ├── README.md ├── .gitattributes ├── SpaceInvadersJIT.sln ├── .editorconfig ├── .gitignore └── SpaceInvadersJIT.sln.DotSettings /roms/test/test.com: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaveTCode/8080JIT/HEAD/roms/test/test.com -------------------------------------------------------------------------------- /roms/test/8080EXER.COM: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaveTCode/8080JIT/HEAD/roms/test/8080EXER.COM -------------------------------------------------------------------------------- /roms/test/8080EXM.COM: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaveTCode/8080JIT/HEAD/roms/test/8080EXM.COM -------------------------------------------------------------------------------- /roms/test/8080PRE.COM: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaveTCode/8080JIT/HEAD/roms/test/8080PRE.COM -------------------------------------------------------------------------------- /roms/test/CPUTEST.COM: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaveTCode/8080JIT/HEAD/roms/test/CPUTEST.COM -------------------------------------------------------------------------------- /roms/test/TST8080.COM: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaveTCode/8080JIT/HEAD/roms/test/TST8080.COM -------------------------------------------------------------------------------- /SpaceInvadersJIT/SDL2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaveTCode/8080JIT/HEAD/SpaceInvadersJIT/SDL2.dll -------------------------------------------------------------------------------- /JIT8080/JIT8080.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /JIT8080.Tests/Mocks/TestRenderer.cs: -------------------------------------------------------------------------------- 1 | using JIT8080._8080; 2 | 3 | namespace JIT8080.Tests.Mocks 4 | { 5 | public class TestRenderer : IRenderer 6 | { 7 | public void VBlank() 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SpaceInvadersJIT/ShiftRegister.cs: -------------------------------------------------------------------------------- 1 | namespace SpaceInvadersJIT 2 | { 3 | internal struct ShiftRegister 4 | { 5 | internal ushort Register; 6 | internal byte Offset; 7 | 8 | internal byte Value() => (byte) (Register >> (8 - Offset)); 9 | } 10 | } -------------------------------------------------------------------------------- /SpaceInvadersJIT/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "SpaceInvadersJIT": { 4 | "commandName": "Project" 5 | }, 6 | "Space Invaders (Rom)": { 7 | "commandName": "Project", 8 | "commandLineArgs": "..\\..\\..\\..\\roms\\real\\invaders.rom" 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /CPMEmulator/CPMEmulator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /JIT8080/8080/Register.cs: -------------------------------------------------------------------------------- 1 | namespace JIT8080._8080 2 | { 3 | internal enum Register 4 | { 5 | A = 0b111, 6 | B = 0b000, 7 | C = 0b001, 8 | D = 0b010, 9 | E = 0b011, 10 | H = 0b100, 11 | L = 0b101, 12 | M = 0b110 // Memory address referred to by contents of HL 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /JIT8080.Tests/Mocks/TestIOHandler.cs: -------------------------------------------------------------------------------- 1 | using JIT8080._8080; 2 | 3 | namespace JIT8080.Tests.Mocks 4 | { 5 | internal class TestIOHandler : IIOHandler 6 | { 7 | public void Out(byte port, byte value) 8 | { 9 | 10 | } 11 | 12 | public byte In(byte port) 13 | { 14 | return 0x0; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /JIT8080/8080/IRenderer.cs: -------------------------------------------------------------------------------- 1 | namespace JIT8080._8080 2 | { 3 | /// 4 | /// This interface is passed to the CPU emulator and is fired on each 5 | /// VBlank interrupt allowing the calling code to perform draw (or other) 6 | /// functions whilst the CPU is halted 7 | /// 8 | public interface IRenderer 9 | { 10 | public void VBlank(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /JIT8080.Tests/Mocks/TestInterruptUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection.Emit; 2 | using JIT8080._8080; 3 | using JIT8080.Generator; 4 | 5 | namespace JIT8080.Tests.Mocks 6 | { 7 | public class TestInterruptUtils : IInterruptUtils 8 | { 9 | public void PreProgramEmit(ILGenerator methodIL) 10 | { 11 | } 12 | 13 | public void PostInstructionEmit(ILGenerator methodIL, CpuInternalBuilders internals, ushort programCounter) 14 | { 15 | 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /JIT8080/8080/IIOHandler.cs: -------------------------------------------------------------------------------- 1 | namespace JIT8080._8080 2 | { 3 | /// 4 | /// The 8080 uses two instructions (OUT d8 & IN d8) to talk directly to up 5 | /// to 255 connected ports. 6 | /// 7 | /// This class allows definition of those ports in managed code where the 8 | /// dynamically generated CLR IL can call the In/Out functions directly. 9 | /// 10 | public interface IIOHandler 11 | { 12 | public void Out(byte port, byte value); 13 | 14 | public byte In(byte port); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /CPMEmulator/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "General": { 4 | "commandName": "Project" 5 | }, 6 | "8080PRE.COM": { 7 | "commandName": "Project", 8 | "commandLineArgs": "..\\..\\..\\..\\roms\\test\\8080PRE.COM" 9 | }, 10 | "TST8080.COM": { 11 | "commandName": "Project", 12 | "commandLineArgs": "..\\..\\..\\..\\roms\\test\\TST8080.COM" 13 | }, 14 | "test.com": { 15 | "commandName": "Project", 16 | "commandLineArgs": "..\\..\\..\\..\\roms\\test\\test.com" 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /JIT8080.Tests/Mocks/TestMemoryBus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JIT8080._8080; 3 | 4 | namespace JIT8080.Tests.Mocks 5 | { 6 | internal class TestMemoryBus : IMemoryBus8080 7 | { 8 | private readonly byte[] _memory = new byte[0x10000]; 9 | 10 | internal TestMemoryBus(byte[] rom) 11 | { 12 | Array.Copy(rom, _memory, Math.Min(_memory.Length, rom.Length)); 13 | } 14 | 15 | public byte ReadByte(ushort address) => _memory[address]; 16 | 17 | public void WriteByte(byte value, ushort address) 18 | { 19 | _memory[address] = value; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SpaceInvadersJIT/SpaceInvadersJIT.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | 7 | 8 | 9 | true 10 | 11 | 12 | 13 | true 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | PreserveNewest 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /JIT8080/8080/IMemoryBus8080.cs: -------------------------------------------------------------------------------- 1 | namespace JIT8080._8080 2 | { 3 | /// 4 | /// Provides an interface for the CPU to access whatever is connected to 5 | /// it's address bus 6 | /// 7 | /// 8 | /// Due to the simplicity of space invaders (no MMIO like GB, NES etc) this 9 | /// _could_ really just be a bare array in the generated IL and access to 10 | /// it would not require the function call. 11 | /// 12 | /// However, this architecture better demonstrates the emulation technique 13 | /// and would allow for it to be used in MMIO based architectures. 14 | /// 15 | public interface IMemoryBus8080 16 | { 17 | public byte ReadByte(ushort address); 18 | 19 | public void WriteByte(byte value, ushort address); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SpaceInvadersJIT.Tests/MemoryBusTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace SpaceInvadersJIT.Tests 4 | { 5 | public class MemoryBusTests 6 | { 7 | [Fact] 8 | public void TestMemoryBusReadWrite() 9 | { 10 | var program = new byte[0x2000]; 11 | var memoryBus = new SpaceInvadersApplication(program); 12 | 13 | for (ushort ii = 0; ii < ushort.MaxValue; ii++) 14 | { 15 | memoryBus.WriteByte((byte)(ii + 1), ii); 16 | if (ii < 0x2000) 17 | { 18 | Assert.Equal(0, memoryBus.ReadByte(ii)); 19 | } 20 | else 21 | { 22 | Assert.Equal((byte)(ii + 1), memoryBus.ReadByte(ii)); 23 | } 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /CPMEmulator/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using JIT8080.Generator; 4 | 5 | namespace CPMEmulator 6 | { 7 | internal static class Program 8 | { 9 | private static void Main(string[] args) 10 | { 11 | var rom = args.Length switch 12 | { 13 | > 0 => File.ReadAllBytes(args[0]), 14 | _ => new byte[] { 0x04, 0x00, 0x00, 0x76 } 15 | }; 16 | var application = new CPMApplication(rom); 17 | application.Emulator = Emulator.CreateEmulator(application.CompleteProgram(), application, application, application, application, 0x100); 18 | var runDelegate = (Action) application.Emulator.Run.CreateDelegate(typeof(Action), application.Emulator.Emulator); 19 | 20 | runDelegate(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /JIT8080/8080/IInterruptUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Reflection.Emit; 3 | using JIT8080.Generator; 4 | 5 | namespace JIT8080._8080 6 | { 7 | /// 8 | /// Interrupts are handled in different ways on different systems, 9 | /// to support firing interrupts at the right moment this interface can 10 | /// be overridden to inject appropriate IL stream commands. 11 | /// 12 | public interface IInterruptUtils 13 | { 14 | /// 15 | /// This is typically used to declare locals and set their values and 16 | /// occurs at the start of the method 17 | /// 18 | public void PreProgramEmit(ILGenerator methodIL); 19 | 20 | /// 21 | /// This is called after each instruction executes and is used to 22 | /// check whether an interrupt should be fired. 23 | /// 24 | public void PostInstructionEmit(ILGenerator methodIL, CpuInternalBuilders internals, ushort programCounter); 25 | } 26 | } -------------------------------------------------------------------------------- /JIT8080.Tests/Opcodes/LoadTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JIT8080.Generator; 3 | using JIT8080.Tests.Mocks; 4 | using Xunit; 5 | 6 | namespace JIT8080.Tests.Opcodes 7 | { 8 | /// 9 | /// Test simple instructions to read/write registers to memory 10 | /// 11 | public class LoadTests 12 | { 13 | [Fact] 14 | public void TestLDAAndSTA() 15 | { 16 | var rom = new byte[] {0x32, 0x00, 0x01, 0xAF, 0x3A, 0x00, 0x01, 0x76}; 17 | var memoryBus = new TestMemoryBus(rom); 18 | var emulator = 19 | Emulator.CreateEmulator(rom, memoryBus, new TestIOHandler(), new TestRenderer(), new TestInterruptUtils()); 20 | emulator.Internals.A.SetValue(emulator.Emulator, (byte)0x10); 21 | 22 | emulator.Run.Invoke(emulator.Emulator, Array.Empty()); 23 | 24 | Assert.Equal((byte)0x10, emulator.Internals.A.GetValue(emulator.Emulator)); 25 | Assert.Equal((byte)0x10, memoryBus.ReadByte(0x100)); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /SpaceInvadersJIT.Tests/ShiftRegisterTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace SpaceInvadersJIT.Tests 4 | { 5 | public class ShiftRegisterTests 6 | { 7 | [Theory] 8 | [InlineData(0, 0, 0, 0)] 9 | [InlineData(0, 0xFF, 0, 0xFF)] 10 | [InlineData(0xFF, 0x0, 0, 0x0)] 11 | [InlineData(0b1111_1111, 0, 1, 0b1)] 12 | [InlineData(0b1111_1111, 0, 2, 0b11)] 13 | [InlineData(0b1111_1111, 0, 3, 0b111)] 14 | [InlineData(0b1111_1111, 0, 4, 0b1111)] 15 | [InlineData(0b1111_1111, 0, 5, 0b1111_1)] 16 | [InlineData(0b1111_1111, 0, 6, 0b1111_11)] 17 | [InlineData(0b1111_1111, 0, 7, 0b1111_111)] 18 | [InlineData(0b1111_1111, 0, 8, 0)] 19 | public void TestShiftRegister(byte lowByte, byte highByte, byte offset, byte expectedResult) 20 | { 21 | var app = new SpaceInvadersApplication(System.Array.Empty()); 22 | app.Out(2, offset); 23 | app.Out(4, lowByte); 24 | app.Out(4, highByte); 25 | Assert.Equal(expectedResult, app.In(3)); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /SpaceInvadersJIT/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.CompilerServices; 4 | using JIT8080.Generator; 5 | 6 | [assembly:InternalsVisibleTo("SpaceInvadersJIT.Tests")] 7 | namespace SpaceInvadersJIT 8 | { 9 | internal static class Program 10 | { 11 | private static void Main(string[] args) 12 | { 13 | var rom = args.Length switch 14 | { 15 | > 0 => File.ReadAllBytes(args[0]), 16 | _ => new byte[] { 0x04, 0x00, 0x00, 0x76 }//File.ReadAllBytes(Path.Combine("..", "..", "..", "..", "roms", "test", "intro.bin")).AsSpan(0, 0x2000).ToArray(), 17 | }; 18 | var application = new SpaceInvadersApplication(rom); 19 | var interruptUtils = new SpaceInvadersInterruptUtils(); 20 | application.InitialiseWindow(); 21 | var emulator = Emulator.CreateEmulator(rom, application, application, application, interruptUtils); 22 | var runDelegate = (Action) emulator.Run.CreateDelegate(typeof(Action), emulator.Emulator); 23 | runDelegate(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 David Tyler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /JIT8080/Generator/Cpu8080.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace JIT8080.Generator 4 | { 5 | /// 6 | /// Represents the internals of an 8080 that do not 1-1 map to CLR IL concepts 7 | /// 8 | public class Cpu8080 9 | { 10 | public object Emulator; 11 | 12 | public MethodInfo Run; 13 | 14 | public Cpu8080Internals Internals; 15 | } 16 | 17 | public class Cpu8080Internals 18 | { 19 | public FieldInfo A; 20 | public FieldInfo B; 21 | public FieldInfo C; 22 | public FieldInfo D; 23 | public FieldInfo E; 24 | public FieldInfo H; 25 | public FieldInfo L; 26 | 27 | public MethodInfo BC; 28 | public MethodInfo DE; 29 | public MethodInfo HL; 30 | 31 | public FieldInfo StackPointer; 32 | 33 | public FieldInfo SignFlag; 34 | public FieldInfo ZeroFlag; 35 | public FieldInfo AuxCarryFlag; 36 | public FieldInfo ParityFlag; 37 | public FieldInfo CarryFlag; 38 | 39 | public MethodInfo GetFlagRegister; 40 | public MethodInfo SetFlagRegister; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /JIT8080.Tests/JIT8080.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | all 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | 14 | 15 | 16 | 17 | 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | all 20 | 21 | 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | all 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /SpaceInvadersJIT.Tests/SpaceInvadersJIT.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | all 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | 14 | 15 | 16 | 17 | 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | all 20 | 21 | 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | all 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /JIT8080.Tests/ValidOpcodeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using JIT8080.Generator; 5 | using JIT8080.Tests.Mocks; 6 | using Xunit; 7 | 8 | namespace JIT8080.Tests 9 | { 10 | /// 11 | /// This class ensures that all possible combinations of opcode generate 12 | /// valid (but not necessarily correct) IL 13 | /// 14 | public class ValidOpcodeTests 15 | { 16 | [Theory] 17 | [MemberData(nameof(AllBytes))] 18 | public void TestAllOpcodesGenerateValidIL(byte opcode) 19 | { 20 | var program = new byte[0x50]; 21 | program[0] = 0x21; 22 | program[1] = 0x0A; 23 | program[2] = 0x00; 24 | program[3] = opcode; 25 | program[4] = 0x6; 26 | program[0x49] = 0x76; // HLT 27 | var emulator = Emulator.CreateEmulator(program, new TestMemoryBus(program), new TestIOHandler(), new TestRenderer(), new TestInterruptUtils()); 28 | emulator.Run.Invoke(emulator.Emulator, Array.Empty()); 29 | } 30 | 31 | public static IEnumerable AllBytes => 32 | Enumerable.Range(0, byte.MaxValue) 33 | .Where(b => b != 0xC7) // Don't test RST 0 as it causes an infinite loop 34 | .Select(b => new object[] { b }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | pool: 2 | name: Hosted Windows 2019 with VS2019 3 | variables: 4 | BuildPlatform: 'any cpu' 5 | BuildConfiguration: 'debug' 6 | 7 | steps: 8 | - task: UseDotNet@2 9 | displayName: 'Install .NET 5 sdk' 10 | inputs: 11 | packageType: sdk 12 | version: 5.x 13 | installationPath: $(Agent.ToolsDirectory)/dotnet 14 | 15 | - task: DotNetCoreCLI@2 16 | displayName: 'Restore Packages' 17 | inputs: 18 | command: 'restore' 19 | projects: '**/*.csproj' 20 | feedsToUse: 'select' 21 | verbosityRestore: Minimal 22 | 23 | - task: DotNetCoreCLI@2 24 | displayName: 'Build test projects' 25 | inputs: 26 | command: 'build' 27 | projects: '*Tests/*.csproj' 28 | 29 | - task: DotNetCoreCLI@2 30 | displayName: 'Run all integration tests' 31 | inputs: 32 | command: 'test' 33 | projects: '*Tests/*.csproj' 34 | arguments: /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura /p:CoverletOutput=$(Build.SourcesDirectory)\TestResults\Coverage\ 35 | 36 | - task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4 37 | displayName: ReportGenerator 38 | inputs: 39 | reports: '$(Build.SourcesDirectory)/TestResults/Coverage/coverage.cobertura.xml' 40 | targetdir: '$(Build.SourcesDirectory)/TestResults/Coverage' 41 | sourcedirs: '$(Build.SourcesDirectory)' 42 | 43 | - task: PublishCodeCoverageResults@1 44 | displayName: 'Publish code coverage results' 45 | inputs: 46 | codeCoverageTool: Cobertura 47 | summaryFileLocation: '$(Build.SourcesDirectory)/TestResults/Coverage/*cobertura.xml' 48 | reportDirectory: '$(Build.SourcesDirectory)/TestResults/Coverage' -------------------------------------------------------------------------------- /SpaceInvadersJIT/SpaceInvadersKey.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SpaceInvadersJIT 4 | { 5 | internal enum SpaceInvadersKey 6 | { 7 | Credit, 8 | P1Fire, 9 | P1Left, 10 | P1Right, 11 | P1Start, 12 | P2Fire, 13 | P2Left, 14 | P2Right, 15 | P2Start 16 | } 17 | 18 | internal static class SpaceInvadersKeyExtensions 19 | { 20 | internal static int PortIndex(this SpaceInvadersKey key) => key switch 21 | { 22 | SpaceInvadersKey.Credit => 0, 23 | SpaceInvadersKey.P1Fire => 0, 24 | SpaceInvadersKey.P1Left => 0, 25 | SpaceInvadersKey.P1Right => 0, 26 | SpaceInvadersKey.P1Start => 0, 27 | SpaceInvadersKey.P2Fire => 1, 28 | SpaceInvadersKey.P2Left => 1, 29 | SpaceInvadersKey.P2Right => 1, 30 | SpaceInvadersKey.P2Start => 0, 31 | _ => throw new ArgumentOutOfRangeException(nameof(key), key, null) 32 | }; 33 | 34 | internal static byte KeyDownMask(this SpaceInvadersKey key) => key switch 35 | { 36 | SpaceInvadersKey.Credit => 0b0000_0001, 37 | SpaceInvadersKey.P1Fire => 0b0001_0000, 38 | SpaceInvadersKey.P1Left => 0b0010_0000, 39 | SpaceInvadersKey.P1Right => 0b0100_0000, 40 | SpaceInvadersKey.P1Start => 0b0000_0100, 41 | SpaceInvadersKey.P2Fire => 0b0001_0000, 42 | SpaceInvadersKey.P2Left => 0b0010_0000, 43 | SpaceInvadersKey.P2Right => 0b0100_0000, 44 | SpaceInvadersKey.P2Start => 0b0000_0010, 45 | _ => throw new ArgumentOutOfRangeException(nameof(key), key, null) 46 | }; 47 | 48 | internal static byte KeyUpMask(this SpaceInvadersKey key) => (byte) ~key.KeyDownMask(); 49 | } 50 | } -------------------------------------------------------------------------------- /JIT8080/Generator/CpuInternalBuilders.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection.Emit; 2 | 3 | namespace JIT8080.Generator 4 | { 5 | public class CpuInternalBuilders 6 | { 7 | internal FieldBuilder A; 8 | internal FieldBuilder B; 9 | internal FieldBuilder C; 10 | internal FieldBuilder D; 11 | internal FieldBuilder E; 12 | internal FieldBuilder H; 13 | internal FieldBuilder L; 14 | internal FieldBuilder StackPointer; 15 | internal FieldBuilder SignFlag; 16 | internal FieldBuilder ZeroFlag; 17 | internal FieldBuilder AuxCarryFlag; 18 | internal FieldBuilder ParityFlag; 19 | internal FieldBuilder CarryFlag; 20 | internal MethodBuilder HL; 21 | internal MethodBuilder BC; 22 | internal MethodBuilder DE; 23 | internal MethodBuilder SetHL; 24 | internal MethodBuilder SetBC; 25 | internal MethodBuilder SetDE; 26 | internal MethodBuilder GetFlagRegister; 27 | internal MethodBuilder SetFlagRegister; 28 | public FieldBuilder InterruptEnable; 29 | public FieldBuilder CycleCounter; 30 | 31 | public FieldBuilder MemoryBusField; 32 | public FieldBuilder IOHandlerField; 33 | public FieldBuilder RendererField; 34 | 35 | /// 36 | /// We emit a label for each 8080 opcode to make it possible to jump 37 | /// to arbitrary addresses 38 | /// 39 | public Label[] ProgramLabels; 40 | 41 | /// 42 | /// Common variable to hold the destination of a runtime jump (e.g. PCHL) 43 | /// 44 | /// Used by the jump table to determine where to load 45 | /// 46 | internal LocalBuilder DestinationAddress; 47 | 48 | /// 49 | /// The jump table is a static area of code at the start of the main method to 50 | /// avoid reproducing a large block on each jump operation. 51 | /// 52 | internal Label JumpTableStart; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Space Invaders Emulator - CLR IL AOT/JIT Implementation 2 | 3 | This project contains an 8080 emulator implemented by AOT recompiling the 4 | provided rom directly to C# IL. 5 | 6 | To validate the cpu implementation it provides two architectures which take advantage of it: 7 | 8 | 1. CP/M - The CP/M machine is implemented in it's most trivial form purely to validate the CPU using the test roms found on the internet 9 | 2. Space Invaders - It also fully implements the space invaders arcade machine architecture (w/ SDL2 for rendering and shift register etc) 10 | 11 | ## Usage 12 | 13 | _Note: This requires a space invaders rom file (which can be acquired on the internet)_ 14 | 15 | ``` 16 | dotnet run -c Release --project SpaceInvadersJIT -- roms/real/invaders.rom 17 | ``` 18 | 19 | Likewise test roms can be executed with 20 | ``` 21 | dotnet run -c Release --project CPMEmulator -- roms/test/8080PRE.COM 22 | ``` 23 | 24 | ## Explanation 25 | 26 | c.f. https://blog.davetcode.co.uk/post/jit-8080/ 27 | 28 | ## Development 29 | 30 | ### Pre-requisites 31 | 32 | - Dotnet 5.0 SDK - https://dotnet.microsoft.com/download 33 | - Suitable text editor/IDE (VS2019 or vscode suggested) 34 | 35 | ### Testing 36 | 37 | There is a growing suite of simple per-opcode tests verifying the functionality of all individual opcodes, these can be run with `dotnet test` 38 | 39 | Additionally there are a small set of validation roms for the 8080 which require a basic CP/M style machine. These can be run like this (note the correct response below the command): 40 | 41 | ``` 42 | dotnet run -c Release --project CPMEmulator -- roms/test/8080PRE.COM 43 | 8080 Preliminary tests complete 44 | ``` 45 | 46 | Note that the 8080TST.COM rom doesn't pass because DAA/AuxCarry is not implemented 47 | 48 | ## References 49 | 50 | - [Space Invaders Architecture](https://computerarcheology.com/Arcade/SpaceInvaders/) 51 | - [CP/M BDOS functions](https://www.seasip.info/Cpm/bdos.html) 52 | - [8080 Programmers manual](https://altairclone.com/downloads/manuals/8080%20Programmers%20Manual.pdf) 53 | - [CLR IL Reference](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes?view=net-5.0) 54 | 55 | ## Missing function 56 | 57 | - Aux Carry Flag & DAA instruction (not implemented because it's not very important and DAA in JIT IL will be very tedious) 58 | - Audio -------------------------------------------------------------------------------- /JIT8080.Tests/RegisterPairTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JIT8080.Generator; 3 | using JIT8080.Tests.Mocks; 4 | using Xunit; 5 | 6 | namespace JIT8080.Tests 7 | { 8 | public class RegisterPairTests 9 | { 10 | [Fact] 11 | public void TestHL() 12 | { 13 | var rom = new byte[] { 0x76 }; 14 | var emulator = Emulator.CreateEmulator(rom, new TestMemoryBus(rom), new TestIOHandler(), new TestRenderer(), new TestInterruptUtils()); 15 | 16 | Assert.Equal((ushort)0, emulator.Internals.HL.Invoke(emulator.Emulator, Array.Empty())); 17 | emulator.Internals.H.SetValue(emulator.Emulator, (byte)1); 18 | Assert.Equal((ushort)256, emulator.Internals.HL.Invoke(emulator.Emulator, Array.Empty())); 19 | emulator.Internals.L.SetValue(emulator.Emulator, (byte)1); 20 | Assert.Equal((ushort)257, emulator.Internals.HL.Invoke(emulator.Emulator, Array.Empty())); 21 | } 22 | 23 | [Fact] 24 | public void TestBC() 25 | { 26 | var rom = new byte[] { 0x76 }; 27 | var emulator = Emulator.CreateEmulator(rom, new TestMemoryBus(rom), new TestIOHandler(), new TestRenderer(), new TestInterruptUtils()); 28 | 29 | Assert.Equal((ushort)0, emulator.Internals.BC.Invoke(emulator.Emulator, Array.Empty())); 30 | emulator.Internals.B.SetValue(emulator.Emulator, (byte)1); 31 | Assert.Equal((ushort)256, emulator.Internals.BC.Invoke(emulator.Emulator, Array.Empty())); 32 | emulator.Internals.C.SetValue(emulator.Emulator, (byte)1); 33 | Assert.Equal((ushort)257, emulator.Internals.BC.Invoke(emulator.Emulator, Array.Empty())); 34 | } 35 | 36 | [Fact] 37 | public void TestDE() 38 | { 39 | var rom = new byte[] { 0x76 }; 40 | var emulator = Emulator.CreateEmulator(rom, new TestMemoryBus(rom), new TestIOHandler(), new TestRenderer(), new TestInterruptUtils()); 41 | 42 | Assert.Equal((ushort)0, emulator.Internals.DE.Invoke(emulator.Emulator, Array.Empty())); 43 | emulator.Internals.D.SetValue(emulator.Emulator, (byte)1); 44 | Assert.Equal((ushort)256, emulator.Internals.DE.Invoke(emulator.Emulator, Array.Empty())); 45 | emulator.Internals.E.SetValue(emulator.Emulator, (byte)1); 46 | Assert.Equal((ushort)257, emulator.Internals.DE.Invoke(emulator.Emulator, Array.Empty())); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /JIT8080/Generator/FlagUtilities.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection.Emit; 2 | 3 | namespace JIT8080.Generator 4 | { 5 | /// 6 | /// Contains static functions which provide macro like expansion of flag 7 | /// operations (e.g. Set Zero flag based on accumulator) 8 | /// 9 | internal static class FlagUtilities 10 | { 11 | internal static void SetZeroFlagFromLocal(ILGenerator methodIL, FieldBuilder zeroFlagField, LocalBuilder local) 12 | { 13 | methodIL.Emit(OpCodes.Ldarg_0); 14 | methodIL.Emit(OpCodes.Ldloc, local); 15 | methodIL.Emit(OpCodes.Conv_U1); 16 | methodIL.Emit(OpCodes.Ldc_I4_0); 17 | methodIL.Emit(OpCodes.Ceq); 18 | methodIL.Emit(OpCodes.Stfld, zeroFlagField); 19 | } 20 | 21 | internal static void SetSignFlagFromLocal(ILGenerator methodIL, FieldBuilder signFlagField, LocalBuilder local) 22 | { 23 | methodIL.Emit(OpCodes.Ldarg_0); 24 | methodIL.Emit(OpCodes.Ldloc, local); 25 | methodIL.Emit(OpCodes.Ldc_I4, 0b1000_0000); 26 | methodIL.Emit(OpCodes.And); 27 | methodIL.Emit(OpCodes.Ldc_I4_0); 28 | methodIL.Emit(OpCodes.Cgt); 29 | methodIL.Emit(OpCodes.Stfld, signFlagField); 30 | } 31 | 32 | /// 33 | /// This function performs the calculation as 34 | /// parity = result 35 | /// parity ^= (parity >> 4) 36 | /// parity ^= (parity >> 2) 37 | /// parity ^= (parity >> 1) 38 | /// 39 | internal static void SetParityFlagFromLocal(ILGenerator methodIL, FieldBuilder parityFlagBuilder, LocalBuilder result) 40 | { 41 | methodIL.Emit(OpCodes.Ldarg_0); 42 | methodIL.Emit(OpCodes.Ldloc, result.LocalIndex); 43 | methodIL.Emit(OpCodes.Dup); 44 | methodIL.Emit(OpCodes.Ldc_I4_4); 45 | methodIL.Emit(OpCodes.Shr_Un); 46 | methodIL.Emit(OpCodes.Xor); 47 | methodIL.Emit(OpCodes.Dup); 48 | methodIL.Emit(OpCodes.Ldc_I4_2); 49 | methodIL.Emit(OpCodes.Shr_Un); 50 | methodIL.Emit(OpCodes.Xor); 51 | methodIL.Emit(OpCodes.Dup); 52 | methodIL.Emit(OpCodes.Ldc_I4_1); 53 | methodIL.Emit(OpCodes.Shr_Un); 54 | methodIL.Emit(OpCodes.Xor); 55 | methodIL.Emit(OpCodes.Not); 56 | methodIL.Emit(OpCodes.Ldc_I4_1); 57 | methodIL.Emit(OpCodes.And); 58 | methodIL.Emit(OpCodes.Stfld, parityFlagBuilder); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /JIT8080.Tests/Opcodes/JumpTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JIT8080.Generator; 3 | using JIT8080.Tests.Mocks; 4 | using Xunit; 5 | 6 | namespace JIT8080.Tests.Opcodes 7 | { 8 | public class JumpTests 9 | { 10 | [Theory] 11 | [InlineData(0xC3)] 12 | [InlineData(0xCB)] 13 | public void TestJMP(byte opcode) 14 | { 15 | var rom = new byte[] {opcode, 0x04, 0x00, 0x04, 0x76}; 16 | var emulator = 17 | Emulator.CreateEmulator(rom, new TestMemoryBus(rom), new TestIOHandler(), new TestRenderer(), new TestInterruptUtils()); 18 | 19 | emulator.Run.Invoke(emulator.Emulator, Array.Empty()); 20 | 21 | Assert.Equal((byte)0, emulator.Internals.B.GetValue(emulator.Emulator)); 22 | } 23 | 24 | [Theory] 25 | [InlineData(0xC2, false, 0)] // JNZ 26 | [InlineData(0xC2, true, 1)] // JNZ 27 | [InlineData(0xCA, false, 1)] // JZ 28 | [InlineData(0xCA, true, 0)] // JZ 29 | 30 | [InlineData(0xD2, false, 0)] // JNC 31 | [InlineData(0xD2, true, 1)] // JNC 32 | [InlineData(0xDA, false, 1)] // JC 33 | [InlineData(0xDA, true, 0)] // JC 34 | 35 | [InlineData(0xE2, false, 0)] // JPO 36 | [InlineData(0xE2, true, 1)] // JPO 37 | [InlineData(0xEA, false, 1)] // JPE 38 | [InlineData(0xEA, true, 0)] // JPE 39 | 40 | [InlineData(0xF2, false, 0)] // JP 41 | [InlineData(0xF2, true, 1)] // JP 42 | [InlineData(0xFA, false, 1)] // JM 43 | [InlineData(0xFA, true, 0)] // JM 44 | public void TestJwFlag(byte opcode, bool flagValue, byte expectedResult) 45 | { 46 | var rom = new byte[] {opcode, 0x04, 0x00, 0x04, 0x76}; 47 | var emulator = 48 | Emulator.CreateEmulator(rom, new TestMemoryBus(rom), new TestIOHandler(), new TestRenderer(), new TestInterruptUtils()); 49 | switch (opcode) 50 | { 51 | case 0xC2: 52 | case 0xCA: 53 | emulator.Internals.ZeroFlag.SetValue(emulator.Emulator, flagValue); 54 | break; 55 | case 0xD2: 56 | case 0xDA: 57 | emulator.Internals.CarryFlag.SetValue(emulator.Emulator, flagValue); 58 | break; 59 | case 0xE2: 60 | case 0xEA: 61 | emulator.Internals.ParityFlag.SetValue(emulator.Emulator, flagValue); 62 | break; 63 | case 0xF2: 64 | case 0xFA: 65 | emulator.Internals.SignFlag.SetValue(emulator.Emulator, flagValue); 66 | break; 67 | } 68 | 69 | emulator.Run.Invoke(emulator.Emulator, Array.Empty()); 70 | 71 | Assert.Equal(expectedResult, emulator.Internals.B.GetValue(emulator.Emulator)); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /JIT8080.Tests/Opcodes/ExchangeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JIT8080.Generator; 3 | using JIT8080.Tests.Mocks; 4 | using Xunit; 5 | 6 | namespace JIT8080.Tests.Opcodes 7 | { 8 | public class ExchangeTests 9 | { 10 | [Theory] 11 | [InlineData(1, 2, 3, 4)] 12 | [InlineData(255, 254, 253, 252)] 13 | public void TestXCHGOpcode(byte h, byte l, byte d, byte e) 14 | { 15 | var rom = new byte[] {0xEB, 0x76}; 16 | var emulator = Emulator.CreateEmulator(rom, new TestMemoryBus(rom), new TestIOHandler(), new TestRenderer(), new TestInterruptUtils()); 17 | emulator.Internals.H.SetValue(emulator.Emulator, h); 18 | emulator.Internals.L.SetValue(emulator.Emulator, l); 19 | emulator.Internals.D.SetValue(emulator.Emulator, d); 20 | emulator.Internals.E.SetValue(emulator.Emulator, e); 21 | 22 | emulator.Run.Invoke(emulator.Emulator, Array.Empty()); 23 | 24 | Assert.Equal(h, emulator.Internals.D.GetValue(emulator.Emulator)); 25 | Assert.Equal(l, emulator.Internals.E.GetValue(emulator.Emulator)); 26 | Assert.Equal(d, emulator.Internals.H.GetValue(emulator.Emulator)); 27 | Assert.Equal(e, emulator.Internals.L.GetValue(emulator.Emulator)); 28 | } 29 | 30 | [Theory] 31 | [InlineData(1, 2, 3, 4)] 32 | [InlineData(255, 254, 253, 252)] 33 | public void TestXTHLOpcode(byte h, byte l, byte mem1, byte mem2) 34 | { 35 | var rom = new byte[] {0xE3, 0x76}; 36 | var memoryBus = new TestMemoryBus(rom); 37 | memoryBus.WriteByte(mem1, 0x3500); 38 | memoryBus.WriteByte(mem2, 0x3501); 39 | var emulator = Emulator.CreateEmulator(rom, memoryBus, new TestIOHandler(), new TestRenderer(), new TestInterruptUtils()); 40 | emulator.Internals.H.SetValue(emulator.Emulator, h); 41 | emulator.Internals.L.SetValue(emulator.Emulator, l); 42 | emulator.Internals.StackPointer.SetValue(emulator.Emulator, (ushort)0x3500); 43 | 44 | emulator.Run.Invoke(emulator.Emulator, Array.Empty()); 45 | 46 | Assert.Equal(h, memoryBus.ReadByte(0x3501)); 47 | Assert.Equal(l, memoryBus.ReadByte(0x3500)); 48 | Assert.Equal(mem2, emulator.Internals.H.GetValue(emulator.Emulator)); 49 | Assert.Equal(mem1, emulator.Internals.L.GetValue(emulator.Emulator)); 50 | } 51 | 52 | [Theory] 53 | [InlineData(0, 0, 0)] 54 | [InlineData(0xFF, 0xFF, 0xFFFF)] 55 | [InlineData(0x50, 0x6C, 0x506C)] 56 | public void TestSPHLOpcode(byte h, byte l, ushort result) 57 | { 58 | var rom = new byte[] {0xF9, 0x76}; 59 | var memoryBus = new TestMemoryBus(rom); 60 | var emulator = Emulator.CreateEmulator(rom, memoryBus, new TestIOHandler(), new TestRenderer(), new TestInterruptUtils()); 61 | emulator.Internals.H.SetValue(emulator.Emulator, h); 62 | emulator.Internals.L.SetValue(emulator.Emulator, l); 63 | emulator.Internals.StackPointer.SetValue(emulator.Emulator, (ushort)0x1234); 64 | 65 | emulator.Run.Invoke(emulator.Emulator, Array.Empty()); 66 | 67 | Assert.Equal(h, emulator.Internals.H.GetValue(emulator.Emulator)); 68 | Assert.Equal(l, emulator.Internals.L.GetValue(emulator.Emulator)); 69 | 70 | Assert.Equal(result, emulator.Internals.StackPointer.GetValue(emulator.Emulator)); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /SpaceInvadersJIT.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30804.86 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpaceInvadersJIT", "SpaceInvadersJIT\SpaceInvadersJIT.csproj", "{911EDFF8-63AE-4832-A80A-F049B42262AA}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpaceInvadersJIT.Tests", "SpaceInvadersJIT.Tests\SpaceInvadersJIT.Tests.csproj", "{D8F6BE24-9893-44C6-A87A-CB040224BBEA}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{664CAC5D-F6D8-4057-893C-6A203F4DDC1F}" 11 | ProjectSection(SolutionItems) = preProject 12 | .editorconfig = .editorconfig 13 | README.md = README.md 14 | SpaceInvadersJIT.sln.DotSettings = SpaceInvadersJIT.sln.DotSettings 15 | azure-pipelines.yml = azure-pipelines.yml 16 | EndProjectSection 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JIT8080", "JIT8080\JIT8080.csproj", "{E6BAB05F-FEC1-4C43-A3F1-E23EB93513CA}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JIT8080.Tests", "JIT8080.Tests\JIT8080.Tests.csproj", "{453E06AA-A285-430B-8596-4199745429FE}" 21 | EndProject 22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CPMEmulator", "CPMEmulator\CPMEmulator.csproj", "{294BB960-F1E8-4145-B817-C5F42AA9B166}" 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | Release|Any CPU = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {911EDFF8-63AE-4832-A80A-F049B42262AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {911EDFF8-63AE-4832-A80A-F049B42262AA}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {911EDFF8-63AE-4832-A80A-F049B42262AA}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {911EDFF8-63AE-4832-A80A-F049B42262AA}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {D8F6BE24-9893-44C6-A87A-CB040224BBEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {D8F6BE24-9893-44C6-A87A-CB040224BBEA}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {D8F6BE24-9893-44C6-A87A-CB040224BBEA}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {D8F6BE24-9893-44C6-A87A-CB040224BBEA}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {E6BAB05F-FEC1-4C43-A3F1-E23EB93513CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {E6BAB05F-FEC1-4C43-A3F1-E23EB93513CA}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {E6BAB05F-FEC1-4C43-A3F1-E23EB93513CA}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {E6BAB05F-FEC1-4C43-A3F1-E23EB93513CA}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {453E06AA-A285-430B-8596-4199745429FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {453E06AA-A285-430B-8596-4199745429FE}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {453E06AA-A285-430B-8596-4199745429FE}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {453E06AA-A285-430B-8596-4199745429FE}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {294BB960-F1E8-4145-B817-C5F42AA9B166}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {294BB960-F1E8-4145-B817-C5F42AA9B166}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {294BB960-F1E8-4145-B817-C5F42AA9B166}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {294BB960-F1E8-4145-B817-C5F42AA9B166}.Release|Any CPU.Build.0 = Release|Any CPU 50 | EndGlobalSection 51 | GlobalSection(SolutionProperties) = preSolution 52 | HideSolutionNode = FALSE 53 | EndGlobalSection 54 | GlobalSection(ExtensibilityGlobals) = postSolution 55 | SolutionGuid = {464FAB31-F288-432B-936F-26B37664399B} 56 | EndGlobalSection 57 | EndGlobal 58 | -------------------------------------------------------------------------------- /JIT8080.Tests/Opcodes/RotateOperationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JIT8080.Generator; 3 | using JIT8080.Tests.Mocks; 4 | using Xunit; 5 | 6 | namespace JIT8080.Tests.Opcodes 7 | { 8 | public class RotateOperationTests 9 | { 10 | [Theory] 11 | [InlineData(0, 0, false)] 12 | [InlineData(1, 2, false)] 13 | [InlineData(0b1000_0000, 1, true)] 14 | public void TestRLCOpcode(byte original, byte expected, bool carryFlag) 15 | { 16 | var rom = new byte[] {0x3E, original, 0x07, 0x76}; 17 | var emulator = Emulator.CreateEmulator(rom, new TestMemoryBus(rom), new TestIOHandler(), new TestRenderer(), new TestInterruptUtils()); 18 | emulator.Run.Invoke(emulator.Emulator, Array.Empty()); 19 | 20 | Assert.Equal(expected, emulator.Internals.A.GetValue(emulator.Emulator)); 21 | Assert.Equal(carryFlag, emulator.Internals.CarryFlag.GetValue(emulator.Emulator)); 22 | } 23 | 24 | [Theory] 25 | [InlineData(0, 0, false, false)] 26 | [InlineData(0, 1, true, false)] 27 | [InlineData(1, 2, false, false)] 28 | [InlineData(0b1000_0000, 0, false, true)] 29 | [InlineData(0b1000_0000, 1, true, true)] 30 | public void TestRALOpcode(byte original, byte expected, bool originalCarryFlag, bool carryFlag) 31 | { 32 | var rom = new byte[] {0x3E, original, 0x17, 0x76}; 33 | var emulator = Emulator.CreateEmulator(rom, new TestMemoryBus(rom), new TestIOHandler(), new TestRenderer(), new TestInterruptUtils()); 34 | emulator.Internals.CarryFlag.SetValue(emulator.Emulator, originalCarryFlag); 35 | emulator.Run.Invoke(emulator.Emulator, Array.Empty()); 36 | 37 | Assert.Equal(expected, emulator.Internals.A.GetValue(emulator.Emulator)); 38 | Assert.Equal(carryFlag, emulator.Internals.CarryFlag.GetValue(emulator.Emulator)); 39 | } 40 | 41 | [Theory] 42 | [InlineData(0, 0, false)] 43 | [InlineData(1, 0b1000_0000, true)] 44 | [InlineData(0b1000_0000, 0b0100_0000, false)] 45 | public void TestRRCOpcode(byte original, byte expected, bool carryFlag) 46 | { 47 | var rom = new byte[] {0x3E, original, 0x0F, 0x76}; 48 | var emulator = Emulator.CreateEmulator(rom, new TestMemoryBus(rom), new TestIOHandler(), new TestRenderer(), new TestInterruptUtils()); 49 | emulator.Run.Invoke(emulator.Emulator, Array.Empty()); 50 | 51 | Assert.Equal(expected, emulator.Internals.A.GetValue(emulator.Emulator)); 52 | Assert.Equal(carryFlag, emulator.Internals.CarryFlag.GetValue(emulator.Emulator)); 53 | } 54 | 55 | [Theory] 56 | [InlineData(0, 0, false, false)] 57 | [InlineData(1, 0, false, true)] 58 | [InlineData(1, 0b1000_0000, true, true)] 59 | [InlineData(0b1000_0000, 0b0100_0000, false, false)] 60 | [InlineData(0b1000_0000, 0b1100_0000, true, false)] 61 | public void TestRAROpcode(byte original, byte expected, bool originalCarryFlag, bool carryFlag) 62 | { 63 | var rom = new byte[] {0x3E, original, 0x1F, 0x76}; 64 | var emulator = Emulator.CreateEmulator(rom, new TestMemoryBus(rom), new TestIOHandler(), new TestRenderer(), new TestInterruptUtils()); 65 | emulator.Internals.CarryFlag.SetValue(emulator.Emulator, originalCarryFlag); 66 | emulator.Run.Invoke(emulator.Emulator, Array.Empty()); 67 | 68 | Assert.Equal(expected, emulator.Internals.A.GetValue(emulator.Emulator)); 69 | Assert.Equal(carryFlag, emulator.Internals.CarryFlag.GetValue(emulator.Emulator)); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /CPMEmulator/CPMApplication.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection.Emit; 3 | using System.Text; 4 | using JIT8080._8080; 5 | using JIT8080.Generator; 6 | 7 | namespace CPMEmulator 8 | { 9 | internal class CPMApplication : IMemoryBus8080, IIOHandler, IRenderer, IInterruptUtils 10 | { 11 | private static readonly byte[] Bios = { 12 | 0x76, 0x76, 0x76, 0x76, 0x76, // 00-04 all just HLT machine 13 | 0xD3, 0x00, 0x00, 0xC9 // Treat BDOS entrypoint as OUT (0), RET 14 | }; 15 | 16 | internal Cpu8080 Emulator { get; set; } 17 | 18 | private readonly byte[] _memory = new byte[0x10000]; 19 | private readonly int _romLength; 20 | 21 | public CPMApplication(byte[] rom) 22 | { 23 | if (rom.Length > 0xFFFF - 0x100) 24 | { 25 | throw new ArgumentOutOfRangeException(nameof(rom), rom, "Rom too large"); 26 | } 27 | 28 | Array.Copy(Bios, _memory, Bios.Length); 29 | for (var ii = Bios.Length; ii < 0x100; ii++) 30 | { 31 | _memory[ii] = 0x76; // Fill the rest of the bios with HLT so we find out about instructions that shouldn't be called 32 | } 33 | Array.Copy(rom, 0, _memory, 0x100, rom.Length); 34 | _romLength = rom.Length; 35 | } 36 | 37 | internal Span CompleteProgram() => _memory.AsSpan(0, Bios.Length + _romLength); 38 | 39 | public byte ReadByte(ushort address) 40 | { 41 | #if DEBUG 42 | Console.WriteLine($" READ {address:X4}={_memory[address]:X2}"); 43 | #endif 44 | return _memory[address]; 45 | } 46 | 47 | public void WriteByte(byte value, ushort address) 48 | { 49 | #if DEBUG 50 | Console.WriteLine($" WRITE {address:X4}={value:X2}"); 51 | #endif 52 | _memory[address] = value; 53 | } 54 | 55 | public void Out(byte port, byte _) 56 | { 57 | if (port != 0) return; 58 | 59 | var operation = (byte)Emulator.Internals.C.GetValue(Emulator.Emulator)!; 60 | // Value here is the BDOS function to call (that is, register C at point of call) 61 | switch (operation) 62 | { 63 | case 0: // System Reset 64 | Environment.Exit(0); 65 | break; 66 | case 1: // C_READ 67 | var key = Console.ReadKey(false); 68 | Emulator.Internals.A.SetValue(Emulator.Emulator, (byte)key.KeyChar); 69 | break; 70 | case 2: // C_WRITE 71 | Console.Write(Convert.ToChar(Emulator.Internals.E.GetValue(Emulator.Emulator)!)); 72 | break; 73 | case 9: // C_WRITESTR 74 | var startIndex = (ushort)Emulator.Internals.DE.Invoke(Emulator.Emulator, Array.Empty())!; 75 | var length = 1; 76 | while (startIndex + length < _memory.Length) 77 | { 78 | if (_memory[startIndex + length] == (byte) '$') break; 79 | length++; 80 | } 81 | 82 | var stringInMemory = Encoding.ASCII.GetString(_memory.AsSpan(startIndex, length).ToArray()); 83 | Console.Write(stringInMemory); 84 | break; 85 | } 86 | } 87 | 88 | public byte In(byte port) => 0x0; 89 | 90 | public void VBlank() 91 | { 92 | throw new NotImplementedException(); 93 | } 94 | 95 | public void PreProgramEmit(ILGenerator methodIL) 96 | { 97 | } 98 | 99 | public void PostInstructionEmit(ILGenerator methodIL, CpuInternalBuilders internals, ushort programCounter) 100 | { 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /JIT8080.Tests/Opcodes/StackTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JIT8080.Generator; 3 | using JIT8080.Tests.Mocks; 4 | using Xunit; 5 | 6 | namespace JIT8080.Tests.Opcodes 7 | { 8 | public class StackTests 9 | { 10 | [Theory] 11 | [InlineData(0x06, 0x0E, 0xC5, 0xC1)] // BC 12 | [InlineData(0x16, 0x1E, 0xD5, 0xD1)] // DE 13 | [InlineData(0x26, 0x2E, 0xE5, 0xE1)] // HL 14 | public void TestPushPopRegisterPair(byte firstRegOpcode, byte secondRegisterOpcode, byte pushOpcode, byte popOpcode) 15 | { 16 | var setupRom = new byte[] 17 | { 18 | 0x31, 0x00, 0x01, // SP to 0x100 19 | firstRegOpcode, 0x01, // MVI B, 0x01 20 | secondRegisterOpcode, 0x02, // MVI C, 0x02 21 | pushOpcode, 22 | popOpcode, 23 | 0x76 // HLT 24 | }; 25 | var testMemoryBus = new TestMemoryBus(setupRom); 26 | var emulator = 27 | Emulator.CreateEmulator(setupRom, testMemoryBus, new TestIOHandler(), new TestRenderer(), new TestInterruptUtils()); 28 | 29 | emulator.Run.Invoke(emulator.Emulator, Array.Empty()); 30 | 31 | // All registers should still have their original values but the values pushed to the stack 32 | // should still be at the locations they were written. 33 | Assert.Equal((ushort)0x100, emulator.Internals.StackPointer.GetValue(emulator.Emulator)); 34 | Assert.Equal((byte)0x01, testMemoryBus.ReadByte(0xFF)); 35 | Assert.Equal((byte)0x02, testMemoryBus.ReadByte(0xFE)); 36 | switch (firstRegOpcode) 37 | { 38 | case 0x06: 39 | Assert.Equal((byte)0x01, emulator.Internals.B.GetValue(emulator.Emulator)); 40 | Assert.Equal((byte)0x02, emulator.Internals.C.GetValue(emulator.Emulator)); 41 | break; 42 | case 0x16: 43 | Assert.Equal((byte)0x01, emulator.Internals.D.GetValue(emulator.Emulator)); 44 | Assert.Equal((byte)0x02, emulator.Internals.E.GetValue(emulator.Emulator)); 45 | break; 46 | case 0x26: 47 | Assert.Equal((byte)0x01, emulator.Internals.H.GetValue(emulator.Emulator)); 48 | Assert.Equal((byte)0x02, emulator.Internals.L.GetValue(emulator.Emulator)); 49 | break; 50 | } 51 | } 52 | 53 | [Fact] 54 | public void TestPushPopPSW() 55 | { 56 | var setupRom = new byte[] 57 | { 58 | 0x31, 0x00, 0x01, // SP to 0x100 59 | 0x3E, 0x01, // MVI A, 0x01 60 | 0x37, // STC 61 | 0xF5, // PUSH PSW 62 | 0xF1, // POP PSW 63 | 0x76 // HLT 64 | }; 65 | var testMemoryBus = new TestMemoryBus(setupRom); 66 | var emulator = 67 | Emulator.CreateEmulator(setupRom, testMemoryBus, new TestIOHandler(), new TestRenderer(), new TestInterruptUtils()); 68 | 69 | emulator.Run.Invoke(emulator.Emulator, Array.Empty()); 70 | 71 | // All registers should still have their original values but the values pushed to the stack 72 | // should still be at the locations they were written. 73 | Assert.Equal((ushort)0x100, emulator.Internals.StackPointer.GetValue(emulator.Emulator)); 74 | Assert.Equal((byte)0x01, testMemoryBus.ReadByte(0xFF)); 75 | Assert.Equal((byte)0b0000_0011, testMemoryBus.ReadByte(0xFE)); 76 | Assert.Equal((byte)0x01, emulator.Internals.A.GetValue(emulator.Emulator)); 77 | Assert.Equal(true, emulator.Internals.CarryFlag.GetValue(emulator.Emulator)); 78 | Assert.Equal(false, emulator.Internals.SignFlag.GetValue(emulator.Emulator)); 79 | Assert.Equal(false, emulator.Internals.ZeroFlag.GetValue(emulator.Emulator)); 80 | Assert.Equal(false, emulator.Internals.AuxCarryFlag.GetValue(emulator.Emulator)); 81 | Assert.Equal(false, emulator.Internals.ParityFlag.GetValue(emulator.Emulator)); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /JIT8080/Generator/ILGeneratorExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection.Emit; 3 | 4 | namespace JIT8080.Generator 5 | { 6 | internal static class ILGeneratorExtensions 7 | { 8 | internal static void EmitDebugString(this ILGenerator ilGenerator, CpuInternalBuilders internals) 9 | { 10 | 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}"; 11 | var wlParams = new[] 12 | { 13 | typeof(string), 14 | typeof(object[]) 15 | }; 16 | 17 | var writeLineMethodInfo = typeof(Console).GetMethod("WriteLine", wlParams); 18 | ilGenerator.Emit(OpCodes.Ldstr, formatString); 19 | ilGenerator.Emit(OpCodes.Ldc_I4, 13); 20 | ilGenerator.Emit(OpCodes.Newarr, typeof(object)); 21 | 22 | foreach (var (field, ix) in new[] 23 | { 24 | (internals.A, OpCodes.Ldc_I4_0), 25 | (internals.B, OpCodes.Ldc_I4_1), 26 | (internals.C, OpCodes.Ldc_I4_2), 27 | (internals.D, OpCodes.Ldc_I4_3), 28 | (internals.E, OpCodes.Ldc_I4_4), 29 | (internals.H, OpCodes.Ldc_I4_5), 30 | (internals.L, OpCodes.Ldc_I4_6) 31 | }) 32 | { 33 | ilGenerator.Emit(OpCodes.Dup); 34 | ilGenerator.Emit(ix); 35 | ilGenerator.Emit(OpCodes.Ldarg_0); 36 | ilGenerator.Emit(OpCodes.Ldfld, field); 37 | ilGenerator.Emit(OpCodes.Box, typeof(byte)); 38 | ilGenerator.Emit(OpCodes.Stelem_Ref); 39 | } 40 | 41 | ilGenerator.Emit(OpCodes.Dup); 42 | ilGenerator.Emit(OpCodes.Ldc_I4_7); 43 | ilGenerator.Emit(OpCodes.Ldarg_0); 44 | ilGenerator.Emit(OpCodes.Ldfld, internals.StackPointer); 45 | ilGenerator.Emit(OpCodes.Box, typeof(ushort)); 46 | ilGenerator.Emit(OpCodes.Stelem_Ref); 47 | 48 | foreach (var (field, ix) in new[] 49 | { 50 | (internals.SignFlag, 8), 51 | (internals.CarryFlag, 9), 52 | (internals.AuxCarryFlag, 10), 53 | (internals.ParityFlag, 11), 54 | (internals.ZeroFlag, 12) 55 | }) 56 | { 57 | ilGenerator.Emit(OpCodes.Dup); 58 | ilGenerator.Emit(OpCodes.Ldc_I4, ix); 59 | ilGenerator.Emit(OpCodes.Ldarg_0); 60 | ilGenerator.Emit(OpCodes.Ldfld, field); 61 | ilGenerator.Emit(OpCodes.Box, typeof(bool)); 62 | ilGenerator.Emit(OpCodes.Stelem_Ref); 63 | } 64 | 65 | ilGenerator.EmitCall(OpCodes.Call, writeLineMethodInfo!, null); 66 | } 67 | 68 | internal static void EmitLd8Immediate(this ILGenerator ilGenerator, byte operand) 69 | { 70 | switch (operand) 71 | { 72 | case 0: 73 | ilGenerator.Emit(OpCodes.Ldc_I4_0); 74 | break; 75 | case 1: 76 | ilGenerator.Emit(OpCodes.Ldc_I4_1); 77 | break; 78 | case 2: 79 | ilGenerator.Emit(OpCodes.Ldc_I4_2); 80 | break; 81 | case 3: 82 | ilGenerator.Emit(OpCodes.Ldc_I4_3); 83 | break; 84 | case 4: 85 | ilGenerator.Emit(OpCodes.Ldc_I4_4); 86 | break; 87 | case 5: 88 | ilGenerator.Emit(OpCodes.Ldc_I4_5); 89 | break; 90 | case 6: 91 | ilGenerator.Emit(OpCodes.Ldc_I4_6); 92 | break; 93 | case 7: 94 | ilGenerator.Emit(OpCodes.Ldc_I4_7); 95 | break; 96 | case 8: 97 | ilGenerator.Emit(OpCodes.Ldc_I4_8); 98 | break; 99 | case { } x when x < 128: 100 | ilGenerator.Emit(OpCodes.Ldc_I4_S, operand); 101 | break; 102 | default: 103 | ilGenerator.Emit(OpCodes.Ldc_I4, (int)operand); 104 | break; 105 | } 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /SpaceInvadersJIT/SpaceInvadersInterruptUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection.Emit; 2 | using JIT8080._8080; 3 | using JIT8080.Generator; 4 | 5 | namespace SpaceInvadersJIT 6 | { 7 | public class SpaceInvadersInterruptUtils : IInterruptUtils 8 | { 9 | private LocalBuilder _interruptDownCounter; 10 | private LocalBuilder _oldInterruptDownCounter; 11 | 12 | public void PreProgramEmit(ILGenerator methodIL) 13 | { 14 | // Track half screen and full screen interrupts using cycle down counters 15 | _interruptDownCounter = methodIL.DeclareLocal(typeof(long)); 16 | _oldInterruptDownCounter = methodIL.DeclareLocal(typeof(long)); 17 | methodIL.Emit(OpCodes.Ldc_I8, SpaceInvadersApplication.CyclesPerScreen); 18 | methodIL.Emit(OpCodes.Stloc, _interruptDownCounter.LocalIndex); 19 | } 20 | 21 | public void PostInstructionEmit(ILGenerator methodIL, CpuInternalBuilders internals, ushort programCounter) 22 | { 23 | // Check whether to fire half screen interrupt 24 | var skipInterrupt = methodIL.DefineLabel(); 25 | var skipHalfScreen = methodIL.DefineLabel(); 26 | 27 | // Always decrement cycles to interrupt 28 | methodIL.Emit(OpCodes.Ldloc, _interruptDownCounter.LocalIndex); 29 | methodIL.Emit(OpCodes.Stloc, _oldInterruptDownCounter.LocalIndex); 30 | methodIL.Emit(OpCodes.Ldloc, _interruptDownCounter.LocalIndex); 31 | methodIL.Emit(OpCodes.Ldarg_0); 32 | methodIL.Emit(OpCodes.Ldfld, internals.CycleCounter); 33 | methodIL.Emit(OpCodes.Sub); 34 | methodIL.Emit(OpCodes.Stloc, _interruptDownCounter.LocalIndex); 35 | 36 | // Check if we've just crossed the half screen boundary 37 | methodIL.Emit(OpCodes.Ldloc, _interruptDownCounter.LocalIndex); 38 | methodIL.Emit(OpCodes.Ldc_I8, SpaceInvadersApplication.HalfCyclesPerScreen + 1); 39 | methodIL.Emit(OpCodes.Clt); 40 | methodIL.Emit(OpCodes.Ldloc, _oldInterruptDownCounter.LocalIndex); 41 | methodIL.Emit(OpCodes.Ldc_I8, SpaceInvadersApplication.HalfCyclesPerScreen); 42 | methodIL.Emit(OpCodes.Cgt); 43 | methodIL.Emit(OpCodes.And); 44 | methodIL.Emit(OpCodes.Brfalse, skipHalfScreen); 45 | #if DEBUG 46 | methodIL.EmitWriteLine("Half screen interrupt"); 47 | #endif 48 | // Actually _do_ the half screen interrupt (RST 0) 49 | // If IE flag set then skip interrupt check 50 | methodIL.Emit(OpCodes.Ldarg_0); 51 | methodIL.Emit(OpCodes.Ldfld, internals.InterruptEnable); 52 | methodIL.Emit(OpCodes.Brfalse, skipInterrupt); 53 | #if DEBUG 54 | methodIL.EmitWriteLine("Half screen interrupt EI enabled"); 55 | #endif 56 | var halfEmitter = new CallEmitter(0xCF, programCounter, internals.ProgramLabels[0x08], 0x08); 57 | halfEmitter.Emit(methodIL, internals); 58 | methodIL.Emit(OpCodes.Br, skipInterrupt); 59 | 60 | // Skipped the half screen so now check for full screen interrupt 61 | methodIL.MarkLabel(skipHalfScreen); 62 | 63 | methodIL.Emit(OpCodes.Ldc_I8, 0L); 64 | methodIL.Emit(OpCodes.Ldloc, _interruptDownCounter); 65 | methodIL.Emit(OpCodes.Blt, skipInterrupt); 66 | 67 | #if DEBUG 68 | methodIL.EmitWriteLine("Full screen interrupt"); 69 | #endif 70 | 71 | // Fire the vblank routine on the renderer 72 | methodIL.Emit(OpCodes.Ldarg_0); 73 | methodIL.Emit(OpCodes.Ldfld, internals.MemoryBusField); 74 | methodIL.Emit(OpCodes.Callvirt, internals.RendererField.FieldType.GetMethod("VBlank")!); 75 | 76 | // Reset the interrupt down counter 77 | methodIL.Emit(OpCodes.Ldc_I8, SpaceInvadersApplication.CyclesPerScreen); 78 | methodIL.Emit(OpCodes.Stloc, _interruptDownCounter.LocalIndex); 79 | 80 | // Fire the vblank interrupt routine 81 | // If IE flag not set then skip interrupt fire (but still do vblank call) 82 | methodIL.Emit(OpCodes.Ldarg_0); 83 | methodIL.Emit(OpCodes.Ldfld, internals.InterruptEnable); 84 | methodIL.Emit(OpCodes.Brfalse, skipInterrupt); 85 | var fullEmitter = new CallEmitter(0xD7, programCounter, internals.ProgramLabels[0x10], 0x10); 86 | fullEmitter.Emit(methodIL, internals); 87 | 88 | methodIL.MarkLabel(skipInterrupt); 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /JIT8080.Tests/FlagRegisterTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JIT8080.Generator; 3 | using JIT8080.Tests.Mocks; 4 | using Xunit; 5 | 6 | namespace JIT8080.Tests 7 | { 8 | public class FlagRegisterTests 9 | { 10 | [Theory] 11 | [InlineData(0b0000_0010, false, false, false, false, false)] 12 | [InlineData(0b1101_0111, true, true, true, true, true)] 13 | [InlineData(0b0000_0011, false, false, false, false, true)] 14 | [InlineData(0b0000_0110, false, false, false, true, false)] 15 | [InlineData(0b0001_0010, false, false, true, false, false)] 16 | [InlineData(0b0100_0010, false, true, false, false, false)] 17 | [InlineData(0b1000_0010, true, false, false, false, false)] 18 | public void TestGetFlagRegister(byte expected, bool sign, bool zero, bool aux, bool parity, bool carry) 19 | { 20 | var rom = new byte[] {0}; 21 | var emulator = Emulator.CreateEmulator(rom, new TestMemoryBus(rom), new TestIOHandler(), new TestRenderer(), new TestInterruptUtils()); 22 | emulator.Internals.SignFlag.SetValue(emulator.Emulator, sign); 23 | emulator.Internals.ZeroFlag.SetValue(emulator.Emulator, zero); 24 | emulator.Internals.AuxCarryFlag.SetValue(emulator.Emulator, aux); 25 | emulator.Internals.ParityFlag.SetValue(emulator.Emulator, parity); 26 | emulator.Internals.CarryFlag.SetValue(emulator.Emulator, carry); 27 | 28 | Assert.Equal(expected, emulator.Internals.GetFlagRegister.Invoke(emulator.Emulator, Array.Empty())); 29 | } 30 | 31 | [Theory] 32 | [InlineData(0b0000_0010, false, false, false, false, false)] 33 | [InlineData(0b1101_0111, true, true, true, true, true)] 34 | [InlineData(0b0000_0011, false, false, false, false, true)] 35 | [InlineData(0b0000_0110, false, false, false, true, false)] 36 | [InlineData(0b0001_0010, false, false, true, false, false)] 37 | [InlineData(0b0100_0010, false, true, false, false, false)] 38 | [InlineData(0b1000_0010, true, false, false, false, false)] 39 | public void TestSetFlagRegister(byte initial, bool sign, bool zero, bool aux, bool parity, bool carry) 40 | { 41 | var rom = new byte[] {0}; 42 | var emulator = Emulator.CreateEmulator(rom, new TestMemoryBus(rom), new TestIOHandler(), new TestRenderer(), new TestInterruptUtils()); 43 | emulator.Internals.SetFlagRegister.Invoke(emulator.Emulator, new object[] {initial}); 44 | 45 | Assert.Equal(sign, emulator.Internals.SignFlag.GetValue(emulator.Emulator)); 46 | Assert.Equal(zero, emulator.Internals.ZeroFlag.GetValue(emulator.Emulator)); 47 | Assert.Equal(aux, emulator.Internals.AuxCarryFlag.GetValue(emulator.Emulator)); 48 | Assert.Equal(parity, emulator.Internals.ParityFlag.GetValue(emulator.Emulator)); 49 | Assert.Equal(carry, emulator.Internals.CarryFlag.GetValue(emulator.Emulator)); 50 | } 51 | 52 | [Fact] 53 | public void TestParityFlagImplementation() 54 | { 55 | var parityLookupTable = new[] 56 | { 57 | true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, 58 | false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, 59 | false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, 60 | true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, 61 | false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, 62 | true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, 63 | true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, 64 | false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, 65 | false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, 66 | true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, 67 | true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, 68 | false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, 69 | true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, 70 | false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, 71 | false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, 72 | true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true 73 | }; 74 | 75 | var rom = new byte[] {0x04, 0x76}; // INR B -> HLT 76 | var emulator = Emulator.CreateEmulator(rom, new TestMemoryBus(rom), new TestIOHandler(), new TestRenderer(), new TestInterruptUtils()); 77 | emulator.Internals.B.SetValue(emulator.Emulator, (byte)0xFF); 78 | 79 | for (var ii = 0; ii < 0xFF; ii++) 80 | { 81 | emulator.Run.Invoke(emulator.Emulator, Array.Empty()); 82 | Assert.Equal(parityLookupTable[ii], emulator.Internals.ParityFlag.GetValue(emulator.Emulator)); 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | #Core editorconfig formatting - indentation 3 | #use soft tabs (spaces) for indentation 4 | indent_style = space 5 | 6 | #Formatting - new line options 7 | 8 | #require members of object initializers to be on the same line 9 | csharp_new_line_before_members_in_object_initializers = false 10 | #require braces to be on a new line for object_collection_array_initializers, control_blocks, types, and methods (also known as "Allman" style) 11 | 12 | #Formatting - organize using options 13 | 14 | #sort System.* using directives alphabetically, and place them before other usings 15 | dotnet_sort_system_directives_first = true 16 | 17 | #Formatting - spacing options 18 | 19 | #require NO space between a cast and the value 20 | #require a space after a keyword in a control flow statement such as a for loop 21 | csharp_space_after_keywords_in_control_flow_statements = true 22 | #remove space within empty argument list parentheses 23 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 24 | #remove space between method call name and opening parenthesis 25 | csharp_space_between_method_call_name_and_opening_parenthesis = false 26 | #do not place space characters after the opening parenthesis and before the closing parenthesis of a method call 27 | csharp_space_between_method_call_parameter_list_parentheses = false 28 | #place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list. 29 | csharp_space_between_method_declaration_parameter_list_parentheses = false 30 | 31 | #Style - Code block preferences 32 | 33 | #prefer curly braces even for one line of code 34 | csharp_prefer_braces = true:suggestion 35 | 36 | #Style - expression bodied member options 37 | 38 | #prefer expression-bodied members for properties 39 | csharp_style_expression_bodied_properties = true:suggestion 40 | 41 | #Style - expression level options 42 | 43 | #prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them 44 | dotnet_style_predefined_type_for_member_access = true:suggestion 45 | 46 | #Style - Expression-level preferences 47 | 48 | #prefer objects to be initialized using object initializers when possible 49 | dotnet_style_object_initializer = true:suggestion 50 | #prefer inferred tuple element names 51 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 52 | 53 | #Style - implicit and explicit types 54 | 55 | #prefer var over explicit type in all cases, unless overridden by another code style rule 56 | csharp_style_var_elsewhere = true:suggestion 57 | #prefer var is used to declare variables with built-in system types such as int 58 | csharp_style_var_for_built_in_types = true:suggestion 59 | #prefer var when the type is already mentioned on the right-hand side of a declaration expression 60 | csharp_style_var_when_type_is_apparent = true:suggestion 61 | 62 | #Style - language keyword and framework type options 63 | 64 | #prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them 65 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 66 | 67 | #Style - modifier options 68 | 69 | #prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods. 70 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion 71 | 72 | #Style - Modifier preferences 73 | 74 | #when this rule is set to a list of modifiers, prefer the specified ordering. 75 | csharp_preferred_modifier_order = public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion 76 | 77 | #Style - qualification options 78 | 79 | #prefer fields not to be prefaced with this. or Me. in Visual Basic 80 | dotnet_style_qualification_for_field = false:suggestion 81 | 82 | # Microsoft .NET properties 83 | dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none 84 | dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none 85 | dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none 86 | dotnet_style_qualification_for_event = false:suggestion 87 | dotnet_style_qualification_for_method = false:suggestion 88 | dotnet_style_qualification_for_property = false:suggestion 89 | 90 | # ReSharper properties 91 | resharper_braces_for_for = not_required 92 | resharper_braces_for_foreach = not_required 93 | resharper_braces_for_ifelse = not_required_for_both 94 | resharper_braces_for_while = not_required 95 | resharper_braces_redundant = true 96 | resharper_method_or_operator_body = block_body 97 | resharper_use_heuristics_for_body_style = true 98 | 99 | # ReSharper inspection severities 100 | resharper_arrange_accessor_owner_body_highlighting = suggestion 101 | resharper_arrange_method_or_operator_body_highlighting = none 102 | resharper_arrange_redundant_parentheses_highlighting = hint 103 | resharper_arrange_this_qualifier_highlighting = hint 104 | resharper_arrange_type_member_modifiers_highlighting = hint 105 | resharper_arrange_type_modifiers_highlighting = hint 106 | resharper_built_in_type_reference_style_for_member_access_highlighting = hint 107 | resharper_built_in_type_reference_style_highlighting = hint 108 | resharper_enforce_do_while_statement_braces_highlighting = none 109 | resharper_enforce_fixed_statement_braces_highlighting = none 110 | resharper_enforce_foreach_statement_braces_highlighting = none 111 | resharper_enforce_for_statement_braces_highlighting = none 112 | resharper_enforce_if_statement_braces_highlighting = none 113 | resharper_enforce_lock_statement_braces_highlighting = none 114 | resharper_enforce_using_statement_braces_highlighting = none 115 | resharper_enforce_while_statement_braces_highlighting = none 116 | resharper_redundant_base_qualifier_highlighting = warning 117 | resharper_remove_redundant_braces_highlighting = none 118 | resharper_suggest_var_or_type_built_in_types_highlighting = hint 119 | resharper_suggest_var_or_type_elsewhere_highlighting = hint 120 | resharper_suggest_var_or_type_simple_types_highlighting = hint 121 | 122 | [*.proto] 123 | indent_style = tab 124 | indent_size = tab 125 | tab_width = 4 126 | 127 | [*.{asax,ascx,aspx,axaml,c,c++,cc,cginc,compute,cp,cpp,cs,css,cu,cuh,cxx,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,master,mpp,mq4,mq5,mqh,paml,skin,tpp,usf,ush,vb,xaml,xamlx,xoml}] 128 | indent_style = space 129 | indent_size = 4 130 | tab_width = 4 131 | 132 | [*.{appxmanifest,axml,build,config,cshtml,csproj,dbml,discomap,dtd,htm,html,js,json,jsproj,jsx,lsproj,njsproj,nuspec,proj,props,razor,resjson,resw,resx,StyleCop,targets,tasks,ts,tsx,vbproj,xml,xsd}] 133 | indent_style = space 134 | indent_size = 2 135 | tab_width = 2 136 | -------------------------------------------------------------------------------- /JIT8080/Generator/StackUtilities.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection.Emit; 2 | 3 | namespace JIT8080.Generator 4 | { 5 | internal static class StackUtilities 6 | { 7 | private static void IncDecStackPointer(ILGenerator methodIL, FieldBuilder stackPointer, OpCode incOrDecOpCode) 8 | { 9 | methodIL.Emit(OpCodes.Ldarg_0); 10 | methodIL.Emit(OpCodes.Ldarg_0); 11 | methodIL.Emit(OpCodes.Ldfld, stackPointer); 12 | methodIL.Emit(OpCodes.Ldc_I4_1); 13 | methodIL.Emit(incOrDecOpCode); 14 | methodIL.Emit(OpCodes.Stfld, stackPointer); 15 | } 16 | 17 | private static void ReadByte(ILGenerator methodIL, FieldBuilder stackPointer, FieldBuilder memoryBusField) 18 | { 19 | methodIL.Emit(OpCodes.Ldarg_0); 20 | methodIL.Emit(OpCodes.Ldfld, memoryBusField); 21 | methodIL.Emit(OpCodes.Ldarg_0); 22 | methodIL.Emit(OpCodes.Ldfld, stackPointer); 23 | methodIL.Emit(OpCodes.Callvirt, memoryBusField.FieldType.GetMethod("ReadByte")!); 24 | } 25 | 26 | internal static void PopPairFromStack(ILGenerator methodIL, LocalBuilder returnLocal, FieldBuilder stackPointer, 27 | FieldBuilder memoryBusField) 28 | { 29 | ReadByte(methodIL, stackPointer, memoryBusField); 30 | methodIL.Emit(OpCodes.Stloc, returnLocal.LocalIndex); 31 | 32 | IncDecStackPointer(methodIL, stackPointer, OpCodes.Add); 33 | 34 | ReadByte(methodIL, stackPointer, memoryBusField); 35 | methodIL.Emit(OpCodes.Ldc_I4_8); 36 | methodIL.Emit(OpCodes.Shl); 37 | methodIL.Emit(OpCodes.Ldloc, returnLocal.LocalIndex); 38 | methodIL.Emit(OpCodes.Or); 39 | methodIL.Emit(OpCodes.Stloc, returnLocal.LocalIndex); 40 | 41 | IncDecStackPointer(methodIL, stackPointer, OpCodes.Add); 42 | } 43 | 44 | internal static void PopPairFromStack(ILGenerator methodIL, FieldBuilder msb, FieldBuilder lsb, 45 | FieldBuilder stackPointer, FieldBuilder memoryBusField) 46 | { 47 | foreach (var b in new[] {lsb, msb}) 48 | { 49 | methodIL.Emit(OpCodes.Ldarg_0); 50 | ReadByte(methodIL, stackPointer, memoryBusField); 51 | methodIL.Emit(OpCodes.Stfld, b); 52 | 53 | IncDecStackPointer(methodIL, stackPointer, OpCodes.Add); 54 | } 55 | } 56 | 57 | /// 58 | /// Used for popping PSW (A, Flags) from the stack 59 | /// 60 | internal static void PopPairFromStack(ILGenerator methodIL, FieldBuilder msb, MethodBuilder lsb, 61 | FieldBuilder stackPointer, FieldBuilder memoryBusField) 62 | { 63 | methodIL.Emit(OpCodes.Ldarg_0); 64 | ReadByte(methodIL, stackPointer, memoryBusField); 65 | methodIL.Emit(OpCodes.Call, lsb); 66 | 67 | IncDecStackPointer(methodIL, stackPointer, OpCodes.Add); 68 | 69 | methodIL.Emit(OpCodes.Ldarg_0); 70 | ReadByte(methodIL, stackPointer, memoryBusField); 71 | methodIL.Emit(OpCodes.Stfld, msb); 72 | 73 | IncDecStackPointer(methodIL, stackPointer, OpCodes.Add); 74 | } 75 | 76 | internal static void PushPairToStack(ILGenerator methodIL, FieldBuilder msb, FieldBuilder lsb, 77 | FieldBuilder stackPointer, FieldBuilder memoryBusField) 78 | { 79 | foreach (var b in new[] {msb, lsb}) 80 | { 81 | IncDecStackPointer(methodIL, stackPointer, OpCodes.Sub); 82 | 83 | methodIL.Emit(OpCodes.Ldarg_0); 84 | methodIL.Emit(OpCodes.Ldfld, memoryBusField); 85 | methodIL.Emit(OpCodes.Ldarg_0); 86 | methodIL.Emit(OpCodes.Ldfld, b); 87 | methodIL.Emit(OpCodes.Ldarg_0); 88 | methodIL.Emit(OpCodes.Ldfld, stackPointer); 89 | methodIL.Emit(OpCodes.Callvirt, memoryBusField.FieldType.GetMethod("WriteByte")!); 90 | } 91 | } 92 | 93 | /// 94 | /// Used for pushing PSW (A, Flags) to the stack 95 | /// 96 | internal static void PushPairToStack(ILGenerator methodIL, FieldBuilder msb, MethodBuilder lsb, 97 | FieldBuilder stackPointer, FieldBuilder memoryBusField) 98 | { 99 | IncDecStackPointer(methodIL, stackPointer, OpCodes.Sub); 100 | 101 | methodIL.Emit(OpCodes.Ldarg_0); 102 | methodIL.Emit(OpCodes.Ldfld, memoryBusField); 103 | methodIL.Emit(OpCodes.Ldarg_0); 104 | methodIL.Emit(OpCodes.Ldfld, msb); 105 | methodIL.Emit(OpCodes.Ldarg_0); 106 | methodIL.Emit(OpCodes.Ldfld, stackPointer); 107 | methodIL.Emit(OpCodes.Callvirt, memoryBusField.FieldType.GetMethod("WriteByte")!); 108 | 109 | IncDecStackPointer(methodIL, stackPointer, OpCodes.Sub); 110 | 111 | methodIL.Emit(OpCodes.Ldarg_0); 112 | methodIL.Emit(OpCodes.Ldfld, memoryBusField); 113 | methodIL.Emit(OpCodes.Ldarg_0); 114 | methodIL.Emit(OpCodes.Call, lsb); 115 | methodIL.Emit(OpCodes.Ldarg_0); 116 | methodIL.Emit(OpCodes.Ldfld, stackPointer); 117 | methodIL.Emit(OpCodes.Callvirt, memoryBusField.FieldType.GetMethod("WriteByte")!); 118 | } 119 | 120 | /// 121 | /// Push a word defined at compile time to the stack rather than values 122 | /// derived from register values. 123 | /// 124 | /// 125 | /// 126 | /// The IL generator for the main method 127 | /// 128 | /// 129 | /// 130 | /// The current stack pointer 131 | /// 132 | /// 133 | /// 134 | /// The value to push 135 | /// 136 | /// 137 | /// A field on the main class providing WriteByte functionality 139 | /// 140 | internal static void PushWordToStack(ILGenerator methodIL, FieldBuilder stackPointer, ushort word, 141 | FieldBuilder memoryBusField) 142 | { 143 | var msb = (byte) (word >> 8); 144 | var lsb = (byte) (word & 0b1111_1111); 145 | 146 | IncDecStackPointer(methodIL, stackPointer, OpCodes.Sub); 147 | 148 | methodIL.Emit(OpCodes.Ldarg_0); 149 | methodIL.Emit(OpCodes.Ldfld, memoryBusField); 150 | methodIL.EmitLd8Immediate( msb); 151 | methodIL.Emit(OpCodes.Ldarg_0); 152 | methodIL.Emit(OpCodes.Ldfld, stackPointer); 153 | methodIL.Emit(OpCodes.Callvirt, memoryBusField.FieldType.GetMethod("WriteByte")!); 154 | 155 | IncDecStackPointer(methodIL, stackPointer, OpCodes.Sub); 156 | 157 | methodIL.Emit(OpCodes.Ldarg_0); 158 | methodIL.Emit(OpCodes.Ldfld, memoryBusField); 159 | methodIL.EmitLd8Immediate( lsb); 160 | methodIL.Emit(OpCodes.Ldarg_0); 161 | methodIL.Emit(OpCodes.Ldfld, stackPointer); 162 | methodIL.Emit(OpCodes.Callvirt, memoryBusField.FieldType.GetMethod("WriteByte")!); 163 | } 164 | } 165 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | [Aa][Rr][Mm]/ 24 | [Aa][Rr][Mm]64/ 25 | bld/ 26 | [Bb]in/ 27 | [Oo]bj/ 28 | [Ll]og/ 29 | 30 | # Visual Studio 2015/2017 cache/options directory 31 | .vs/ 32 | # Uncomment if you have tasks that create the project's static files in wwwroot 33 | #wwwroot/ 34 | 35 | # Visual Studio 2017 auto generated files 36 | Generated\ Files/ 37 | 38 | # MSTest test Results 39 | [Tt]est[Rr]esult*/ 40 | [Bb]uild[Ll]og.* 41 | 42 | # NUNIT 43 | *.VisualState.xml 44 | TestResult.xml 45 | 46 | # Build Results of an ATL Project 47 | [Dd]ebugPS/ 48 | [Rr]eleasePS/ 49 | dlldata.c 50 | 51 | # Benchmark Results 52 | BenchmarkDotNet.Artifacts/ 53 | 54 | # .NET Core 55 | project.lock.json 56 | project.fragment.lock.json 57 | artifacts/ 58 | 59 | # StyleCop 60 | StyleCopReport.xml 61 | 62 | # Files built by Visual Studio 63 | *_i.c 64 | *_p.c 65 | *_h.h 66 | *.ilk 67 | *.meta 68 | *.obj 69 | *.iobj 70 | *.pch 71 | *.pdb 72 | *.ipdb 73 | *.pgc 74 | *.pgd 75 | *.rsp 76 | *.sbr 77 | *.tlb 78 | *.tli 79 | *.tlh 80 | *.tmp 81 | *.tmp_proj 82 | *_wpftmp.csproj 83 | *.log 84 | *.vspscc 85 | *.vssscc 86 | .builds 87 | *.pidb 88 | *.svclog 89 | *.scc 90 | 91 | # Chutzpah Test files 92 | _Chutzpah* 93 | 94 | # Visual C++ cache files 95 | ipch/ 96 | *.aps 97 | *.ncb 98 | *.opendb 99 | *.opensdf 100 | *.sdf 101 | *.cachefile 102 | *.VC.db 103 | *.VC.VC.opendb 104 | 105 | # Visual Studio profiler 106 | *.psess 107 | *.vsp 108 | *.vspx 109 | *.sap 110 | 111 | # Visual Studio Trace Files 112 | *.e2e 113 | 114 | # TFS 2012 Local Workspace 115 | $tf/ 116 | 117 | # Guidance Automation Toolkit 118 | *.gpState 119 | 120 | # ReSharper is a .NET coding add-in 121 | _ReSharper*/ 122 | *.[Rr]e[Ss]harper 123 | *.DotSettings.user 124 | 125 | # JustCode is a .NET coding add-in 126 | .JustCode 127 | 128 | # TeamCity is a build add-in 129 | _TeamCity* 130 | 131 | # DotCover is a Code Coverage Tool 132 | *.dotCover 133 | 134 | # AxoCover is a Code Coverage Tool 135 | .axoCover/* 136 | !.axoCover/settings.json 137 | 138 | # Visual Studio code coverage results 139 | *.coverage 140 | *.coveragexml 141 | 142 | # NCrunch 143 | _NCrunch_* 144 | .*crunch*.local.xml 145 | nCrunchTemp_* 146 | 147 | # MightyMoose 148 | *.mm.* 149 | AutoTest.Net/ 150 | 151 | # Web workbench (sass) 152 | .sass-cache/ 153 | 154 | # Installshield output folder 155 | [Ee]xpress/ 156 | 157 | # DocProject is a documentation generator add-in 158 | DocProject/buildhelp/ 159 | DocProject/Help/*.HxT 160 | DocProject/Help/*.HxC 161 | DocProject/Help/*.hhc 162 | DocProject/Help/*.hhk 163 | DocProject/Help/*.hhp 164 | DocProject/Help/Html2 165 | DocProject/Help/html 166 | 167 | # Click-Once directory 168 | publish/ 169 | 170 | # Publish Web Output 171 | *.[Pp]ublish.xml 172 | *.azurePubxml 173 | # Note: Comment the next line if you want to checkin your web deploy settings, 174 | # but database connection strings (with potential passwords) will be unencrypted 175 | *.pubxml 176 | *.publishproj 177 | 178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 179 | # checkin your Azure Web App publish settings, but sensitive information contained 180 | # in these scripts will be unencrypted 181 | PublishScripts/ 182 | 183 | # NuGet Packages 184 | *.nupkg 185 | # The packages folder can be ignored because of Package Restore 186 | **/[Pp]ackages/* 187 | # except build/, which is used as an MSBuild target. 188 | !**/[Pp]ackages/build/ 189 | # Uncomment if necessary however generally it will be regenerated when needed 190 | #!**/[Pp]ackages/repositories.config 191 | # NuGet v3's project.json files produces more ignorable files 192 | *.nuget.props 193 | *.nuget.targets 194 | 195 | # Microsoft Azure Build Output 196 | csx/ 197 | *.build.csdef 198 | 199 | # Microsoft Azure Emulator 200 | ecf/ 201 | rcf/ 202 | 203 | # Windows Store app package directories and files 204 | AppPackages/ 205 | BundleArtifacts/ 206 | Package.StoreAssociation.xml 207 | _pkginfo.txt 208 | *.appx 209 | 210 | # Visual Studio cache files 211 | # files ending in .cache can be ignored 212 | *.[Cc]ache 213 | # but keep track of directories ending in .cache 214 | !?*.[Cc]ache/ 215 | 216 | # Others 217 | ClientBin/ 218 | ~$* 219 | *~ 220 | *.dbmdl 221 | *.dbproj.schemaview 222 | *.jfm 223 | *.pfx 224 | *.publishsettings 225 | orleans.codegen.cs 226 | 227 | # Including strong name files can present a security risk 228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 229 | #*.snk 230 | 231 | # Since there are multiple workflows, uncomment next line to ignore bower_components 232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 233 | #bower_components/ 234 | 235 | # RIA/Silverlight projects 236 | Generated_Code/ 237 | 238 | # Backup & report files from converting an old project file 239 | # to a newer Visual Studio version. Backup files are not needed, 240 | # because we have git ;-) 241 | _UpgradeReport_Files/ 242 | Backup*/ 243 | UpgradeLog*.XML 244 | UpgradeLog*.htm 245 | ServiceFabricBackup/ 246 | *.rptproj.bak 247 | 248 | # SQL Server files 249 | *.mdf 250 | *.ldf 251 | *.ndf 252 | 253 | # Business Intelligence projects 254 | *.rdl.data 255 | *.bim.layout 256 | *.bim_*.settings 257 | *.rptproj.rsuser 258 | *- Backup*.rdl 259 | 260 | # Microsoft Fakes 261 | FakesAssemblies/ 262 | 263 | # GhostDoc plugin setting file 264 | *.GhostDoc.xml 265 | 266 | # Node.js Tools for Visual Studio 267 | .ntvs_analysis.dat 268 | node_modules/ 269 | 270 | # Visual Studio 6 build log 271 | *.plg 272 | 273 | # Visual Studio 6 workspace options file 274 | *.opt 275 | 276 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 277 | *.vbw 278 | 279 | # Visual Studio LightSwitch build output 280 | **/*.HTMLClient/GeneratedArtifacts 281 | **/*.DesktopClient/GeneratedArtifacts 282 | **/*.DesktopClient/ModelManifest.xml 283 | **/*.Server/GeneratedArtifacts 284 | **/*.Server/ModelManifest.xml 285 | _Pvt_Extensions 286 | 287 | # Paket dependency manager 288 | .paket/paket.exe 289 | paket-files/ 290 | 291 | # FAKE - F# Make 292 | .fake/ 293 | 294 | # JetBrains Rider 295 | .idea/ 296 | *.sln.iml 297 | 298 | # CodeRush personal settings 299 | .cr/personal 300 | 301 | # Python Tools for Visual Studio (PTVS) 302 | __pycache__/ 303 | *.pyc 304 | 305 | # Cake - Uncomment if you are using it 306 | # tools/** 307 | # !tools/packages.config 308 | 309 | # Tabs Studio 310 | *.tss 311 | 312 | # Telerik's JustMock configuration file 313 | *.jmconfig 314 | 315 | # BizTalk build output 316 | *.btp.cs 317 | *.btm.cs 318 | *.odx.cs 319 | *.xsd.cs 320 | 321 | # OpenCover UI analysis results 322 | OpenCover/ 323 | 324 | # Azure Stream Analytics local run output 325 | ASALocalRun/ 326 | 327 | # MSBuild Binary and Structured Log 328 | *.binlog 329 | 330 | # NVidia Nsight GPU debugger configuration file 331 | *.nvuser 332 | 333 | # MFractors (Xamarin productivity tool) working folder 334 | .mfractor/ 335 | 336 | # Local History for Visual Studio 337 | .localhistory/ 338 | 339 | # BeatPulse healthcheck temp database 340 | healthchecksdb 341 | 342 | roms/real -------------------------------------------------------------------------------- /SpaceInvadersJIT.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | ADC 3 | ADD 4 | ALU 5 | ANA 6 | CMA 7 | CMC 8 | CMP 9 | CPM 10 | DAA 11 | DAD 12 | DCR 13 | DCX 14 | HLT 15 | INR 16 | INRDCR 17 | INX 18 | INXDCX 19 | IO 20 | JMP 21 | LDA 22 | LDAX 23 | LHLD 24 | LXI 25 | MOV 26 | MVI 27 | NOP 28 | ORA 29 | PCHL 30 | POP 31 | PSW 32 | PUSH 33 | RAL 34 | RAR 35 | RLC 36 | RNZ 37 | RRC 38 | SBB 39 | SDL 40 | SHLD 41 | SPHL 42 | STA 43 | STAX 44 | SUB 45 | XCHG 46 | XHCG 47 | XRA 48 | XTHL 49 | True 50 | True 51 | True 52 | True 53 | True 54 | True 55 | True 56 | True 57 | True 58 | IL 59 | HL 60 | BC 61 | DE 62 | True 63 | True 64 | True 65 | True 66 | True 67 | True 68 | True -------------------------------------------------------------------------------- /SpaceInvadersJIT/SpaceInvadersApplication.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using JIT8080._8080; 5 | 6 | namespace SpaceInvadersJIT 7 | { 8 | internal class SpaceInvadersApplication : IIOHandler, IRenderer, IDisposable, IMemoryBus8080 9 | { 10 | internal const long HalfCyclesPerScreen = 17066L; 11 | internal const long CyclesPerScreen = HalfCyclesPerScreen * 2; 12 | private const int ScreenWidth = 224; 13 | private const int ScreenHeight = 256; 14 | private const double MsPerFrame = 1000.0 / 60.0; 15 | 16 | private IntPtr _window; 17 | private IntPtr _renderer; 18 | private IntPtr _texture; 19 | private ShiftRegister _shiftRegister; 20 | private readonly byte[] _rom = new byte[0x2000]; 21 | private readonly byte[] _ram = new byte[0x2000]; 22 | 23 | private readonly byte[,,] _screenBuffer = new byte[ScreenHeight,ScreenWidth,4]; 24 | 25 | private readonly Stopwatch _stopwatch = new (); 26 | 27 | private readonly byte[] _portStatus = { 0b0000_1000, 0b0000_0000 }; 28 | 29 | private readonly Dictionary _keyMap = new() 30 | { 31 | {SDL2.SDL_Keycode.SDLK_RIGHT, SpaceInvadersKey.P1Right}, 32 | {SDL2.SDL_Keycode.SDLK_LEFT, SpaceInvadersKey.P1Left}, 33 | {SDL2.SDL_Keycode.SDLK_UP, SpaceInvadersKey.P1Fire}, 34 | {SDL2.SDL_Keycode.SDLK_a, SpaceInvadersKey.P2Left}, 35 | {SDL2.SDL_Keycode.SDLK_d, SpaceInvadersKey.P2Right}, 36 | {SDL2.SDL_Keycode.SDLK_w, SpaceInvadersKey.P2Fire}, 37 | {SDL2.SDL_Keycode.SDLK_RSHIFT, SpaceInvadersKey.P1Start}, 38 | {SDL2.SDL_Keycode.SDLK_LSHIFT, SpaceInvadersKey.P2Start}, 39 | {SDL2.SDL_Keycode.SDLK_RETURN, SpaceInvadersKey.Credit} 40 | }; 41 | 42 | internal SpaceInvadersApplication(byte[] program) 43 | { 44 | Array.Copy(program, _rom, Math.Min(0x2000, program.Length)); 45 | } 46 | 47 | internal void InitialiseWindow() 48 | { 49 | _ = SDL2.SDL_Init(SDL2.SDL_INIT_VIDEO); 50 | _ = SDL2.SDL_CreateWindowAndRenderer( 51 | 224 * 2, 52 | 256 * 2, 53 | 0, 54 | out _window, 55 | out _renderer); 56 | _ = SDL2.SDL_SetRenderDrawColor(_renderer, 0, 0, 0, 255); 57 | _ = SDL2.SDL_RenderClear(_renderer); 58 | SDL2.SDL_SetWindowTitle(_window, "Space Invaders"); 59 | _ = SDL2.SDL_SetHint(SDL2.SDL_HINT_RENDER_SCALE_QUALITY, "0"); 60 | 61 | _texture = SDL2.SDL_CreateTexture( 62 | renderer: _renderer, 63 | format: SDL2.SDL_PIXELFORMAT_ARGB8888, 64 | access: (int)SDL2.SDL_TextureAccess.SDL_TEXTUREACCESS_STREAMING, 65 | w: ScreenWidth, 66 | h: ScreenHeight); 67 | 68 | _stopwatch.Start(); 69 | } 70 | 71 | public void Out(byte port, byte value) 72 | { 73 | switch (port) 74 | { 75 | case 2: 76 | _shiftRegister.Offset = (byte) (value & 0b111); 77 | break; 78 | case 3: 79 | // TODO SOUND1 port 80 | break; 81 | case 4: 82 | _shiftRegister.Register = (ushort) ((_shiftRegister.Register >> 8) | (value << 8)); 83 | break; 84 | case 5: 85 | // TODO SOUND2 port 86 | break; 87 | case 6: 88 | // TODO WATCHDOG PORT 89 | break; 90 | } 91 | } 92 | 93 | public byte In(byte port) 94 | { 95 | return port switch 96 | { 97 | 0 => 0x0, // TODO INP0 98 | 1 => _portStatus[0], 99 | 2 => _portStatus[1], 100 | 3 => _shiftRegister.Value(), 101 | _ => 0x0 // TODO - Other ports 102 | }; 103 | } 104 | 105 | public void Dispose() 106 | { 107 | SDL2.SDL_DestroyRenderer(_renderer); 108 | SDL2.SDL_DestroyWindow(_window); 109 | SDL2.SDL_Quit(); 110 | } 111 | 112 | public void VBlank() 113 | { 114 | for (var ii = 0; ii < 0x1C00; ii++) 115 | { 116 | var b = _ram[ii + 0x400]; // vram starts at 0x400 into RAM address space 117 | var y = ii * 8 / 256; 118 | var x = (ii * 8) % 256; 119 | for (var pixel = 0; pixel < 8; pixel++) 120 | { 121 | var rotatedY = ScreenHeight - (x + pixel) - 1; 122 | var rotatedX = y; 123 | byte value = ((b >> pixel) & 1) == 1 ? 0xFF : 0x0; 124 | _screenBuffer[rotatedY, rotatedX, 3] = 0xFF; // Alpha channel 125 | _screenBuffer[rotatedY, rotatedX, 1] = value; 126 | _screenBuffer[rotatedY, rotatedX, 2] = value; 127 | _screenBuffer[rotatedY, rotatedX, 0] = value; 128 | } 129 | } 130 | 131 | unsafe 132 | { 133 | fixed (byte* p = _screenBuffer) 134 | { 135 | _ = SDL2.SDL_UpdateTexture(_texture, IntPtr.Zero, (IntPtr)p, ScreenWidth * 4); 136 | } 137 | } 138 | 139 | _ = SDL2.SDL_RenderCopy(_renderer, _texture, IntPtr.Zero, IntPtr.Zero); 140 | SDL2.SDL_RenderPresent(_renderer); 141 | 142 | CheckForInputs(); 143 | 144 | // Pause until we're running at 60fps 145 | var msToSleep = MsPerFrame - (_stopwatch.ElapsedTicks / (double)Stopwatch.Frequency) * 1000; 146 | if (msToSleep > 0) 147 | { 148 | SDL2.SDL_Delay((uint)msToSleep); 149 | } 150 | _stopwatch.Restart(); 151 | } 152 | 153 | private void CheckForInputs() 154 | { 155 | // TODO - Checking for input once per frame seems a bit sketchy 156 | while (SDL2.SDL_PollEvent(out var e) != 0) 157 | { 158 | switch (e.type) 159 | { 160 | case SDL2.SDL_EventType.SDL_QUIT: 161 | Environment.Exit(0); 162 | break; 163 | case SDL2.SDL_EventType.SDL_KEYUP: 164 | if (e.key.keysym.sym == SDL2.SDL_Keycode.SDLK_ESCAPE) 165 | { 166 | Environment.Exit(0); 167 | } 168 | else if (_keyMap.ContainsKey(e.key.keysym.sym)) 169 | { 170 | var key = _keyMap[e.key.keysym.sym]; 171 | _portStatus[key.PortIndex()] &= key.KeyUpMask(); 172 | } 173 | break; 174 | case SDL2.SDL_EventType.SDL_KEYDOWN: 175 | if (_keyMap.ContainsKey(e.key.keysym.sym)) 176 | { 177 | var key = _keyMap[e.key.keysym.sym]; 178 | _portStatus[key.PortIndex()] |= key.KeyDownMask(); 179 | } 180 | break; 181 | } 182 | } 183 | } 184 | 185 | public byte ReadByte(ushort address) => 186 | address switch 187 | { 188 | < 0x2000 => _rom[address], 189 | _ => _ram[(address - 0x2000) & 0x1FFF] 190 | }; 191 | 192 | public void WriteByte(byte value, ushort address) 193 | { 194 | #if DEBUG 195 | Console.WriteLine($"WRITE {address:X4}={value:X2}"); 196 | #endif 197 | if (address < 0x2000) 198 | { 199 | return; 200 | } 201 | 202 | _ram[(address - 0x2000) & 0x1FFF] = value; 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /JIT8080.Tests/Opcodes/ArithmeticTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JIT8080.Generator; 3 | using JIT8080.Tests.Mocks; 4 | using Xunit; 5 | 6 | namespace JIT8080.Tests.Opcodes 7 | { 8 | public class ArithmeticTests 9 | { 10 | [Theory] 11 | [InlineData(0x09, 0x33, 0x9F, 0xA1, 0x7B, 0xD51A, false)] 12 | [InlineData(0x09, 0xFF, 0xFF, 0x00, 0x01, 0, true)] 13 | public void TestDADOpcode(byte opcode, byte highByte, byte lowByte, byte h, byte l, ushort expected, 14 | bool carryFlag) 15 | { 16 | var rom = new byte[] {opcode, 0x76}; 17 | var emulator = 18 | Emulator.CreateEmulator(rom, new TestMemoryBus(rom), new TestIOHandler(), new TestRenderer(), new TestInterruptUtils()); 19 | switch (opcode) 20 | { 21 | case 0x09: 22 | emulator.Internals.B.SetValue(emulator.Emulator, highByte); 23 | emulator.Internals.C.SetValue(emulator.Emulator, lowByte); 24 | break; 25 | case 0x19: 26 | emulator.Internals.D.SetValue(emulator.Emulator, highByte); 27 | emulator.Internals.E.SetValue(emulator.Emulator, lowByte); 28 | break; 29 | case 0x29: 30 | emulator.Internals.H.SetValue(emulator.Emulator, highByte); 31 | emulator.Internals.L.SetValue(emulator.Emulator, lowByte); 32 | break; 33 | case 0x39: 34 | emulator.Internals.StackPointer.SetValue(emulator.Emulator, (ushort) ((highByte << 8) | lowByte)); 35 | break; 36 | } 37 | 38 | emulator.Internals.H.SetValue(emulator.Emulator, h); 39 | emulator.Internals.L.SetValue(emulator.Emulator, l); 40 | emulator.Run.Invoke(emulator.Emulator, Array.Empty()); 41 | 42 | Assert.Equal(expected, emulator.Internals.HL.Invoke(emulator.Emulator, Array.Empty())); 43 | Assert.Equal(carryFlag, emulator.Internals.CarryFlag.GetValue(emulator.Emulator)); 44 | } 45 | 46 | [Theory] 47 | [InlineData(0x88, 0x42, 0x3D, false, 0x7F, false, false, false, false, false)] // ADC 48 | [InlineData(0x88, 0x42, 0x3D, true, 0x80, true, false, false, false, true)] // ADC (uses existing carry) 49 | [InlineData(0x80, 0x42, 0x3D, false, 0x7F, false, false, false, false, false)] // ADD 50 | [InlineData(0x80, 0x42, 0x3D, true, 0x7F, false, false, false, false, true)] // ADD (ignores existing carry) 51 | [InlineData(0x90, 0x42, 0x3D, false, 0x05, false, false, false, true, false)] // SUB 52 | [InlineData(0x90, 0x42, 0x3D, true, 0x05, false, false, false, true, true)] // SUB (ignores existing carry) 53 | [InlineData(0x90, 0x00, 0x01, true, 0xFF, true, false, true, true, true)] // SUB crossing byte boundary (0->FF) 54 | [InlineData(0xA0, 0xFF, 0x00, false, 0x00, false, true, false, true, false)] // ANA 55 | [InlineData(0xA0, 0xFF, 0xFF, false, 0xFF, true, false, false, true, false)] // ANA 56 | public void Test8BitArithmeticOpcode(byte opcode, byte a, byte operand, bool carryFlag, byte result, bool expectedSignFlag, 57 | bool expectedZeroFlag, bool expectedCarryFlag, bool expectedParityFlag, bool expectedAuxCarryFlag) 58 | { 59 | var rom = new byte[] {opcode, 0x76}; 60 | var emulator = 61 | Emulator.CreateEmulator(rom, new TestMemoryBus(rom), new TestIOHandler(), new TestRenderer(), new TestInterruptUtils()); 62 | emulator.Internals.A.SetValue(emulator.Emulator, a); 63 | emulator.Internals.B.SetValue(emulator.Emulator, operand); 64 | emulator.Internals.CarryFlag.SetValue(emulator.Emulator, carryFlag); 65 | 66 | emulator.Run.Invoke(emulator.Emulator, Array.Empty()); 67 | Assert.Equal(result, emulator.Internals.A.GetValue(emulator.Emulator)); 68 | Assert.Equal(expectedSignFlag, emulator.Internals.SignFlag.GetValue(emulator.Emulator)); 69 | Assert.Equal(expectedZeroFlag, emulator.Internals.ZeroFlag.GetValue(emulator.Emulator)); 70 | Assert.Equal(expectedCarryFlag, emulator.Internals.CarryFlag.GetValue(emulator.Emulator)); 71 | Assert.Equal(expectedParityFlag, emulator.Internals.ParityFlag.GetValue(emulator.Emulator)); 72 | //Assert.Equal(expectedAuxCarryFlag, emulator.Internals.AuxCarryFlag.GetValue(emulator.Emulator)); 73 | } 74 | 75 | [Theory] 76 | [InlineData(0xC6, 0x00, 0x00, 0x00, false, false, false, true, true, false)] // ADI 77 | [InlineData(0xC6, 0xFF, 0x00, 0xFF, false, false, true, false, true, false)] // ADI 78 | [InlineData(0xC6, 0x14, 0x42, 0x56, false, false, false, false, true, false)] // ADI 79 | [InlineData(0xC6, 0x56, 0xBE, 0x14, false, true, false, false, true, true)] // ADI (check carry) 80 | [InlineData(0xE6, 0x00, 0x00, 0x00, false, false, false, true, true, false)] // ANI 81 | [InlineData(0xE6, 0xFF, 0x00, 0x00, false, false, false, true, true, false)] // ANI 82 | [InlineData(0xE6, 0x3A, 0x0F, 0x0A, false, false, false, false, true, false)] // ANI 83 | [InlineData(0xD6, 0x0, 0x0, 0x0, false, false, false, true, true, false)] // SUI 84 | [InlineData(0xD6, 0x1, 0x0, 0x1, false, false, false, false, false, false)] // SUI 85 | [InlineData(0xD6, 0x0, 0x1, 0xFF, false, true, true, false, true, false)] // SUI (across boundary) 86 | [InlineData(0xFE, 0x0, 0x0, 0x0, false, false, false, true, true, false)] // CPI 87 | [InlineData(0xFE, 0x1, 0x0, 0x1, false, false, false, false, false, false)] // CPI 88 | [InlineData(0xFE, 0x0, 0x1, 0x0, false, true, true, false, true, false)] // CPI (across boundary) 89 | public void Test8BitImmediateOpcodes(byte opcode, byte a, byte operand, byte expected, bool prevCarry, 90 | bool carry, bool sign, bool zero, bool parity, bool auxCarry) 91 | { 92 | var rom = new byte[] {opcode, operand, 0x76}; 93 | var emulator = 94 | Emulator.CreateEmulator(rom, new TestMemoryBus(rom), new TestIOHandler(), new TestRenderer(), new TestInterruptUtils()); 95 | emulator.Internals.A.SetValue(emulator.Emulator, a); 96 | emulator.Internals.CarryFlag.SetValue(emulator.Emulator, prevCarry); 97 | 98 | emulator.Run.Invoke(emulator.Emulator, Array.Empty()); 99 | 100 | Assert.Equal(expected, emulator.Internals.A.GetValue(emulator.Emulator)); 101 | Assert.Equal(sign, emulator.Internals.SignFlag.GetValue(emulator.Emulator)); 102 | Assert.Equal(zero, emulator.Internals.ZeroFlag.GetValue(emulator.Emulator)); 103 | Assert.Equal(carry, emulator.Internals.CarryFlag.GetValue(emulator.Emulator)); 104 | Assert.Equal(parity, emulator.Internals.ParityFlag.GetValue(emulator.Emulator)); 105 | //Assert.Equal(auxCarry, emulator.Internals.AuxCarryFlag.GetValue(emulator.Emulator)); 106 | } 107 | 108 | [Fact] 109 | public void TestChainArithmeticOperations() 110 | { 111 | var rom = new byte[] 112 | { 113 | 0xC6, 0x5, 114 | 0xC6, 0x5, 115 | 0xC6, 0x5, 116 | 0xC6, 0x5, 117 | 0xC6, 0x5, 118 | 0x76 119 | }; 120 | var emulator = 121 | Emulator.CreateEmulator(rom, new TestMemoryBus(rom), new TestIOHandler(), new TestRenderer(), new TestInterruptUtils()); 122 | 123 | emulator.Run.Invoke(emulator.Emulator, Array.Empty()); 124 | 125 | Assert.Equal((byte)25, emulator.Internals.A.GetValue(emulator.Emulator)); 126 | Assert.Equal(false, emulator.Internals.SignFlag.GetValue(emulator.Emulator)); 127 | Assert.Equal(false, emulator.Internals.ZeroFlag.GetValue(emulator.Emulator)); 128 | Assert.Equal(false, emulator.Internals.CarryFlag.GetValue(emulator.Emulator)); 129 | Assert.Equal(false, emulator.Internals.ParityFlag.GetValue(emulator.Emulator)); 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /JIT8080/Generator/General8BitALUEmitter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection.Emit; 3 | using JIT8080._8080; 4 | 5 | namespace JIT8080.Generator 6 | { 7 | /// 8 | /// This emitter is used for all 8 bit ALU operations and is within it's 9 | /// own file due to it's higher complexity 10 | /// 11 | internal class General8BitALUEmitter : IEmitter 12 | { 13 | private readonly Register _register; 14 | private readonly OpCode _arithmeticOpCode; 15 | private readonly bool _useCarryInOperation; 16 | private readonly byte _opcodeByte; 17 | private readonly Opcodes8080 _opcode; 18 | private readonly byte _operand; 19 | 20 | internal General8BitALUEmitter(byte opcodeByte, Opcodes8080 opcode) 21 | { 22 | _opcodeByte = opcodeByte; 23 | _opcode = opcode; 24 | 25 | _register = DetermineRegister(opcodeByte); 26 | _arithmeticOpCode = ArithmeticOpcode(_opcode); 27 | _useCarryInOperation = UseCarryInOperation(_opcode); 28 | } 29 | 30 | internal General8BitALUEmitter(byte opcodeByte, Opcodes8080 opcode, byte operand) 31 | : this(opcodeByte, opcode) 32 | { 33 | _operand = operand; 34 | } 35 | 36 | private static Register DetermineRegister(byte opcodeByte) 37 | { 38 | var register = opcodeByte > 0xC0 ? (opcodeByte & 0b0011_1000) >> 3 : opcodeByte & 0b0000_0111; 39 | return register switch 40 | { 41 | 0b000 => Register.B, 42 | 0b001 => Register.C, 43 | 0b010 => Register.D, 44 | 0b011 => Register.E, 45 | 0b100 => Register.H, 46 | 0b101 => Register.L, 47 | 0b110 => Register.M, 48 | 0b111 => Register.A, 49 | _ => throw new ArgumentOutOfRangeException(nameof(opcodeByte), opcodeByte, "Invalid program") 50 | }; 51 | } 52 | 53 | private static OpCode ArithmeticOpcode(Opcodes8080 opcode) => opcode switch 54 | { 55 | Opcodes8080.ADD => OpCodes.Add, 56 | Opcodes8080.ADC => OpCodes.Add, 57 | Opcodes8080.ADI => OpCodes.Add, 58 | Opcodes8080.ACI => OpCodes.Add, 59 | Opcodes8080.SUB => OpCodes.Sub, 60 | Opcodes8080.SBB => OpCodes.Sub, 61 | Opcodes8080.SBI => OpCodes.Sub, 62 | Opcodes8080.SUI => OpCodes.Sub, 63 | Opcodes8080.ANA => OpCodes.And, 64 | Opcodes8080.ANI => OpCodes.And, 65 | Opcodes8080.XRA => OpCodes.Xor, 66 | Opcodes8080.XRI => OpCodes.Xor, 67 | Opcodes8080.ORA => OpCodes.Or, 68 | Opcodes8080.ORI => OpCodes.Or, 69 | Opcodes8080.CMP => OpCodes.Sub, 70 | Opcodes8080.CPI => OpCodes.Sub, 71 | _ => throw new ArgumentOutOfRangeException(nameof(opcode), opcode, "Invalid opcode for 8 bit arithmetic") 72 | }; 73 | 74 | private static bool UseCarryInOperation(Opcodes8080 opcode) => opcode switch 75 | { 76 | Opcodes8080.ADC => true, 77 | Opcodes8080.SBB => true, 78 | Opcodes8080.ACI => true, 79 | Opcodes8080.SBI => true, 80 | _ => false 81 | }; 82 | 83 | private static bool WriteResult(Opcodes8080 opcode) => opcode != Opcodes8080.CMP && opcode != Opcodes8080.CPI; 84 | 85 | private void LoadSource(ILGenerator methodIL, CpuInternalBuilders internals, FieldBuilder memoryBusField) 86 | { 87 | if (_opcodeByte > 0xC0) // Immediate 8 bit arithmetic 88 | { 89 | methodIL.EmitLd8Immediate( _operand); 90 | } 91 | else 92 | { 93 | switch (_register) 94 | { 95 | case Register.A: 96 | methodIL.Emit(OpCodes.Ldarg_0); 97 | methodIL.Emit(OpCodes.Ldfld, internals.A); 98 | break; 99 | case Register.B: 100 | methodIL.Emit(OpCodes.Ldarg_0); 101 | methodIL.Emit(OpCodes.Ldfld, internals.B); 102 | break; 103 | case Register.C: 104 | methodIL.Emit(OpCodes.Ldarg_0); 105 | methodIL.Emit(OpCodes.Ldfld, internals.C); 106 | break; 107 | case Register.D: 108 | methodIL.Emit(OpCodes.Ldarg_0); 109 | methodIL.Emit(OpCodes.Ldfld, internals.D); 110 | break; 111 | case Register.E: 112 | methodIL.Emit(OpCodes.Ldarg_0); 113 | methodIL.Emit(OpCodes.Ldfld, internals.E); 114 | break; 115 | case Register.H: 116 | methodIL.Emit(OpCodes.Ldarg_0); 117 | methodIL.Emit(OpCodes.Ldfld, internals.H); 118 | break; 119 | case Register.L: 120 | methodIL.Emit(OpCodes.Ldarg_0); 121 | methodIL.Emit(OpCodes.Ldfld, internals.L); 122 | break; 123 | case Register.M: 124 | methodIL.Emit(OpCodes.Ldarg_0); 125 | methodIL.Emit(OpCodes.Ldfld, memoryBusField); 126 | methodIL.Emit(OpCodes.Ldarg_0); 127 | methodIL.Emit(OpCodes.Call, internals.HL); 128 | methodIL.Emit(OpCodes.Callvirt, memoryBusField.FieldType.GetMethod("ReadByte")!); 129 | break; 130 | default: 131 | throw new ArgumentOutOfRangeException(); 132 | } 133 | } 134 | } 135 | 136 | void IEmitter.Emit(ILGenerator methodIL, CpuInternalBuilders internals) 137 | { 138 | var result = methodIL.DeclareLocal(typeof(int)); 139 | 140 | methodIL.Emit(OpCodes.Ldarg_0); 141 | methodIL.Emit(OpCodes.Ldarg_0); 142 | methodIL.Emit(OpCodes.Ldfld, internals.A); 143 | LoadSource(methodIL, internals, internals.MemoryBusField); 144 | methodIL.Emit(_arithmeticOpCode); 145 | if (_useCarryInOperation) 146 | { 147 | methodIL.Emit(OpCodes.Ldarg_0); 148 | methodIL.Emit(OpCodes.Ldfld, internals.CarryFlag); 149 | methodIL.Emit(_arithmeticOpCode); 150 | } 151 | methodIL.Emit(OpCodes.Stloc, result.LocalIndex); // Cache off the result as a local variable to allow setting flags 152 | 153 | // Set the carry flag (different for different operations) 154 | methodIL.Emit(OpCodes.Ldarg_0); 155 | switch (_opcode) 156 | { 157 | case Opcodes8080.ADD: 158 | case Opcodes8080.ADC: 159 | case Opcodes8080.ACI: 160 | case Opcodes8080.ADI: 161 | methodIL.Emit(OpCodes.Ldloc, result.LocalIndex); 162 | methodIL.Emit(OpCodes.Ldc_I4, 255); 163 | methodIL.Emit(OpCodes.Cgt); 164 | methodIL.Emit(OpCodes.Stfld, internals.CarryFlag); 165 | break; 166 | case Opcodes8080.SUB: 167 | case Opcodes8080.CMP: 168 | case Opcodes8080.CPI: 169 | case Opcodes8080.SUI: 170 | LoadSource(methodIL, internals, internals.MemoryBusField); 171 | methodIL.Emit(OpCodes.Ldarg_0); 172 | methodIL.Emit(OpCodes.Ldfld, internals.A); 173 | methodIL.Emit(OpCodes.Cgt); 174 | methodIL.Emit(OpCodes.Stfld, internals.CarryFlag); 175 | break; 176 | case Opcodes8080.SBB: 177 | case Opcodes8080.SBI: 178 | LoadSource(methodIL, internals, internals.MemoryBusField); 179 | methodIL.Emit(OpCodes.Ldarg_0); 180 | methodIL.Emit(OpCodes.Ldfld, internals.CarryFlag); 181 | methodIL.Emit(OpCodes.Add); 182 | methodIL.Emit(OpCodes.Ldarg_0); 183 | methodIL.Emit(OpCodes.Ldfld, internals.A); 184 | methodIL.Emit(OpCodes.Cgt); 185 | methodIL.Emit(OpCodes.Stfld, internals.CarryFlag); 186 | break; 187 | case Opcodes8080.ANA: 188 | case Opcodes8080.ANI: 189 | case Opcodes8080.XRA: 190 | case Opcodes8080.XRI: 191 | case Opcodes8080.ORA: 192 | case Opcodes8080.ORI: 193 | methodIL.Emit(OpCodes.Ldc_I4_0); 194 | methodIL.Emit(OpCodes.Stfld, internals.CarryFlag); 195 | break; 196 | default: 197 | throw new ArgumentException(nameof(_opcode)); 198 | } 199 | 200 | if (WriteResult(_opcode)) 201 | { 202 | methodIL.Emit(OpCodes.Ldloc, result.LocalIndex); 203 | methodIL.Emit(OpCodes.Stfld, internals.A); 204 | } 205 | else 206 | { 207 | methodIL.Emit(OpCodes.Pop); 208 | } 209 | 210 | // Handle flags based on value of accumulator/local 211 | FlagUtilities.SetZeroFlagFromLocal(methodIL, internals.ZeroFlag, result); 212 | FlagUtilities.SetSignFlagFromLocal(methodIL, internals.SignFlag, result); 213 | FlagUtilities.SetParityFlagFromLocal(methodIL, internals.ParityFlag, result); 214 | 215 | // TODO - Handle AuxCarry flag 216 | } 217 | 218 | public override string ToString() => _opcode switch 219 | { 220 | Opcodes8080.ADD => $"{_opcode} {_register}", 221 | Opcodes8080.ADC => $"{_opcode} {_register}", 222 | Opcodes8080.ADI => $"{_opcode} {_operand:X2}", 223 | Opcodes8080.ACI => $"{_opcode} {_operand:X2}", 224 | Opcodes8080.SUB => $"{_opcode} {_register}", 225 | Opcodes8080.SBB => $"{_opcode} {_register}", 226 | Opcodes8080.SBI => $"{_opcode} {_operand:X2}", 227 | Opcodes8080.SUI => $"{_opcode} {_operand:X2}", 228 | Opcodes8080.ANA => $"{_opcode} {_register}", 229 | Opcodes8080.ANI => $"{_opcode} {_operand:X2}", 230 | Opcodes8080.XRA => $"{_opcode} {_register}", 231 | Opcodes8080.XRI => $"{_opcode} {_operand:X2}", 232 | Opcodes8080.ORA => $"{_opcode} {_register}", 233 | Opcodes8080.ORI => $"{_opcode} {_operand:X2}", 234 | Opcodes8080.CMP => $"{_opcode} {_register}", 235 | Opcodes8080.CPI => $"{_opcode} {_operand:X2}", 236 | _ => throw new ArgumentOutOfRangeException(nameof(_opcode), _opcode, "Invalid 8 bit ALU opcode") 237 | }; 238 | } 239 | } -------------------------------------------------------------------------------- /JIT8080/8080/Opcodes8080.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JIT8080._8080 4 | { 5 | /// 6 | /// All the different opcodes that are used in an 8080 processor 7 | /// 8 | internal enum Opcodes8080 9 | { 10 | NOP, 11 | LXI, 12 | STAX, 13 | INX, 14 | INR, 15 | DCR, 16 | MVI, 17 | RLC, 18 | DAD, 19 | LDAX, 20 | DCX, 21 | RRC, 22 | RAL, 23 | RAR, 24 | SHLD, 25 | DAA, 26 | LHLD, 27 | CMA, 28 | STA, 29 | STC, 30 | LDA, 31 | CMC, 32 | MOV, 33 | HLT, 34 | ADD, 35 | ADC, 36 | SUB, 37 | SBB, 38 | ANA, 39 | XRA, 40 | ORA, 41 | CMP, 42 | RNZ, 43 | POP, 44 | JNZ, 45 | JMP, 46 | CNZ, 47 | PUSH, 48 | ADI, 49 | RST, 50 | RZ, 51 | RET, 52 | JZ, 53 | CZ, 54 | CALL, 55 | ACI, 56 | RNC, 57 | JNC, 58 | OUT, 59 | CNC, 60 | SUI, 61 | RC, 62 | JC, 63 | IN, 64 | CC, 65 | SBI, 66 | RPO, 67 | JPO, 68 | XTHL, 69 | CPO, 70 | ANI, 71 | RPE, 72 | PCHL, 73 | JPE, 74 | XCHG, 75 | CPE, 76 | XRI, 77 | RP, 78 | JP, 79 | DI, 80 | CP, 81 | ORI, 82 | RM, 83 | SPHL, 84 | JM, 85 | EI, 86 | CM, 87 | CPI 88 | } 89 | 90 | internal static class Opcodes8080Extensions 91 | { 92 | /// 93 | /// Represents the number of bytes that this opcode takes up in the 94 | /// program. 95 | /// 96 | /// 97 | /// 98 | /// The decoded opcode 99 | /// 100 | /// 101 | /// 102 | /// The number of consecutive bytes in memory that correspond 103 | /// to this opcode. 104 | /// 105 | internal static byte Length(this Opcodes8080 opcode) => opcode switch 106 | { 107 | Opcodes8080.NOP => 1, 108 | Opcodes8080.LXI => 3, 109 | Opcodes8080.STAX => 1, 110 | Opcodes8080.INX => 1, 111 | Opcodes8080.INR => 1, 112 | Opcodes8080.DCR => 1, 113 | Opcodes8080.MVI => 2, 114 | Opcodes8080.RLC => 1, 115 | Opcodes8080.DAD => 1, 116 | Opcodes8080.LDAX => 1, 117 | Opcodes8080.DCX => 1, 118 | Opcodes8080.RRC => 1, 119 | Opcodes8080.RAL => 1, 120 | Opcodes8080.RAR => 1, 121 | Opcodes8080.SHLD => 3, 122 | Opcodes8080.DAA => 1, 123 | Opcodes8080.LHLD => 3, 124 | Opcodes8080.CMA => 1, 125 | Opcodes8080.STA => 3, 126 | Opcodes8080.STC => 1, 127 | Opcodes8080.LDA => 3, 128 | Opcodes8080.CMC => 1, 129 | Opcodes8080.MOV => 1, 130 | Opcodes8080.HLT => 1, 131 | Opcodes8080.ADD => 1, 132 | Opcodes8080.ADC => 1, 133 | Opcodes8080.SUB => 1, 134 | Opcodes8080.SBB => 1, 135 | Opcodes8080.ANA => 1, 136 | Opcodes8080.XRA => 1, 137 | Opcodes8080.ORA => 1, 138 | Opcodes8080.CMP => 1, 139 | Opcodes8080.RNZ => 1, 140 | Opcodes8080.POP => 1, 141 | Opcodes8080.JNZ => 3, 142 | Opcodes8080.JMP => 3, 143 | Opcodes8080.CNZ => 3, 144 | Opcodes8080.PUSH => 1, 145 | Opcodes8080.ADI => 2, 146 | Opcodes8080.RST => 1, 147 | Opcodes8080.RZ => 1, 148 | Opcodes8080.RET => 1, 149 | Opcodes8080.JZ => 3, 150 | Opcodes8080.CZ => 3, 151 | Opcodes8080.CALL => 3, 152 | Opcodes8080.ACI => 2, 153 | Opcodes8080.RNC => 1, 154 | Opcodes8080.JNC => 3, 155 | Opcodes8080.OUT => 2, 156 | Opcodes8080.CNC => 3, 157 | Opcodes8080.SUI => 2, 158 | Opcodes8080.RC => 1, 159 | Opcodes8080.JC => 3, 160 | Opcodes8080.IN => 2, 161 | Opcodes8080.CC => 3, 162 | Opcodes8080.SBI => 2, 163 | Opcodes8080.RPO => 1, 164 | Opcodes8080.JPO => 3, 165 | Opcodes8080.XTHL => 1, 166 | Opcodes8080.CPO => 3, 167 | Opcodes8080.ANI => 2, 168 | Opcodes8080.RPE => 1, 169 | Opcodes8080.PCHL => 1, 170 | Opcodes8080.JPE => 3, 171 | Opcodes8080.XCHG => 1, 172 | Opcodes8080.CPE => 3, 173 | Opcodes8080.XRI => 2, 174 | Opcodes8080.RP => 1, 175 | Opcodes8080.JP => 3, 176 | Opcodes8080.DI => 1, 177 | Opcodes8080.CP => 3, 178 | Opcodes8080.ORI => 2, 179 | Opcodes8080.RM => 1, 180 | Opcodes8080.SPHL => 1, 181 | Opcodes8080.JM => 3, 182 | Opcodes8080.EI => 1, 183 | Opcodes8080.CM => 3, 184 | Opcodes8080.CPI => 2, 185 | _ => throw new NotImplementedException() 186 | }; 187 | 188 | /// 189 | /// The number of cpu cycles for the base version of this opcode. 190 | /// 191 | /// Note that conditional branching operations take _more_ cycles if 192 | /// they branch. This is handled by the emit logic itself. 193 | /// 194 | /// 195 | /// 196 | /// The decoded opcode 197 | /// 198 | /// 199 | /// 200 | /// The byte corresponding to the opcode. This is needed to 201 | /// disambiguate between operations on memory addresses as opposed 202 | /// to registers (memory addresses take more cycles to access) 203 | /// 204 | /// 205 | /// 206 | /// The number of cpu cycles for the base version of this opcode 207 | /// 208 | internal static long Cycles(this Opcodes8080 opcode, byte opcodeByte) => opcode switch 209 | { 210 | Opcodes8080.NOP => 4, 211 | Opcodes8080.LXI => 10, 212 | Opcodes8080.STAX => 7, 213 | Opcodes8080.INX => 5, 214 | Opcodes8080.INR => (opcodeByte == 0x34) ? 7 : 5, 215 | Opcodes8080.DCR => (opcodeByte == 0x35) ? 7 : 5, 216 | Opcodes8080.MVI => (opcodeByte == 0x36) ? 10 : 7, 217 | Opcodes8080.RLC => 4, 218 | Opcodes8080.DAD => 10, 219 | Opcodes8080.LDAX => 7, 220 | Opcodes8080.DCX => 5, 221 | Opcodes8080.RRC => 4, 222 | Opcodes8080.RAL => 4, 223 | Opcodes8080.RAR => 4, 224 | Opcodes8080.SHLD => 16, 225 | Opcodes8080.DAA => 4, 226 | Opcodes8080.LHLD => 16, 227 | Opcodes8080.CMA => 4, 228 | Opcodes8080.STA => 13, 229 | Opcodes8080.STC => 4, 230 | Opcodes8080.LDA => 13, 231 | Opcodes8080.CMC => 4, 232 | Opcodes8080.MOV => (opcodeByte >= 0x70 && opcodeByte < 0x78) ? 7 : 5, 233 | Opcodes8080.HLT => 7, 234 | Opcodes8080.ADD => (opcodeByte == 0x86) ? 7 : 4, 235 | Opcodes8080.ADC => (opcodeByte == 0x8E) ? 7 : 4, 236 | Opcodes8080.SUB => (opcodeByte == 0x96) ? 7 : 4, 237 | Opcodes8080.SBB => (opcodeByte == 0x9E) ? 7 : 4, 238 | Opcodes8080.ANA => (opcodeByte == 0xA6) ? 7 : 4, 239 | Opcodes8080.XRA => (opcodeByte == 0xAE) ? 7 : 4, 240 | Opcodes8080.ORA => (opcodeByte == 0xB6) ? 7 : 4, 241 | Opcodes8080.CMP => (opcodeByte == 0xBE) ? 7 : 4, 242 | Opcodes8080.RNZ => 5, 243 | Opcodes8080.POP => 10, 244 | Opcodes8080.JNZ => 10, 245 | Opcodes8080.JMP => 10, 246 | Opcodes8080.CNZ => 11, 247 | Opcodes8080.PUSH => 11, 248 | Opcodes8080.ADI => 7, 249 | Opcodes8080.RST => 11, 250 | Opcodes8080.RZ => 5, 251 | Opcodes8080.RET => 10, 252 | Opcodes8080.JZ => 10, 253 | Opcodes8080.CZ => 11, 254 | Opcodes8080.CALL => 17, 255 | Opcodes8080.ACI => 7, 256 | Opcodes8080.RNC => 5, 257 | Opcodes8080.JNC => 10, 258 | Opcodes8080.OUT => 10, 259 | Opcodes8080.CNC => 11, 260 | Opcodes8080.SUI => 7, 261 | Opcodes8080.RC => 5, 262 | Opcodes8080.JC => 10, 263 | Opcodes8080.IN => 10, 264 | Opcodes8080.CC => 11, 265 | Opcodes8080.SBI => 7, 266 | Opcodes8080.RPO => 5, 267 | Opcodes8080.JPO => 10, 268 | Opcodes8080.XTHL => 18, 269 | Opcodes8080.CPO => 11, 270 | Opcodes8080.ANI => 7, 271 | Opcodes8080.RPE => 5, 272 | Opcodes8080.PCHL => 5, 273 | Opcodes8080.JPE => 10, 274 | Opcodes8080.XCHG => 5, 275 | Opcodes8080.CPE => 11, 276 | Opcodes8080.XRI => 7, 277 | Opcodes8080.RP => 5, 278 | Opcodes8080.JP => 10, 279 | Opcodes8080.DI => 4, 280 | Opcodes8080.CP => 11, 281 | Opcodes8080.ORI => 7, 282 | Opcodes8080.RM => 5, 283 | Opcodes8080.SPHL => 5, 284 | Opcodes8080.JM => 10, 285 | Opcodes8080.EI => 4, 286 | Opcodes8080.CM => 11, 287 | Opcodes8080.CPI => 7, 288 | _ => throw new ArgumentOutOfRangeException(nameof(opcode), opcode, null) 289 | }; 290 | } 291 | 292 | internal static class Opcodes8080Decoder 293 | { 294 | internal static Opcodes8080 Decode(byte opcode) => opcode switch 295 | { 296 | // 0x00-0x0F 297 | 0x00 => Opcodes8080.NOP, 298 | 0x01 => Opcodes8080.LXI, 299 | 0x02 => Opcodes8080.STAX, 300 | 0x03 => Opcodes8080.INX, 301 | 0x04 => Opcodes8080.INR, 302 | 0x05 => Opcodes8080.DCR, 303 | 0x06 => Opcodes8080.MVI, 304 | 0x07 => Opcodes8080.RLC, 305 | 0x08 => Opcodes8080.NOP, 306 | 0x09 => Opcodes8080.DAD, 307 | 0x0A => Opcodes8080.LDAX, 308 | 0x0B => Opcodes8080.DCX, 309 | 0x0C => Opcodes8080.INR, 310 | 0x0D => Opcodes8080.DCR, 311 | 0x0E => Opcodes8080.MVI, 312 | 0x0F => Opcodes8080.RRC, 313 | // 0x10-0x1F 314 | 0x10 => Opcodes8080.NOP, 315 | 0x11 => Opcodes8080.LXI, 316 | 0x12 => Opcodes8080.STAX, 317 | 0x13 => Opcodes8080.INX, 318 | 0x14 => Opcodes8080.INR, 319 | 0x15 => Opcodes8080.DCR, 320 | 0x16 => Opcodes8080.MVI, 321 | 0x17 => Opcodes8080.RAL, 322 | 0x18 => Opcodes8080.NOP, 323 | 0x19 => Opcodes8080.DAD, 324 | 0x1A => Opcodes8080.LDAX, 325 | 0x1B => Opcodes8080.DCX, 326 | 0x1C => Opcodes8080.INR, 327 | 0x1D => Opcodes8080.DCR, 328 | 0x1E => Opcodes8080.MVI, 329 | 0x1F => Opcodes8080.RAR, 330 | // 0x20-0x2F 331 | 0x20 => Opcodes8080.NOP, 332 | 0x21 => Opcodes8080.LXI, 333 | 0x22 => Opcodes8080.SHLD, 334 | 0x23 => Opcodes8080.INX, 335 | 0x24 => Opcodes8080.INR, 336 | 0x25 => Opcodes8080.DCR, 337 | 0x26 => Opcodes8080.MVI, 338 | 0x27 => Opcodes8080.DAA, 339 | 0x28 => Opcodes8080.NOP, 340 | 0x29 => Opcodes8080.DAD, 341 | 0x2A => Opcodes8080.LHLD, 342 | 0x2B => Opcodes8080.DCX, 343 | 0x2C => Opcodes8080.INR, 344 | 0x2D => Opcodes8080.DCR, 345 | 0x2E => Opcodes8080.MVI, 346 | 0x2F => Opcodes8080.CMA, 347 | // 0x30-0x3F 348 | 0x30 => Opcodes8080.NOP, 349 | 0x31 => Opcodes8080.LXI, 350 | 0x32 => Opcodes8080.STA, 351 | 0x33 => Opcodes8080.INX, 352 | 0x34 => Opcodes8080.INR, 353 | 0x35 => Opcodes8080.DCR, 354 | 0x36 => Opcodes8080.MVI, 355 | 0x37 => Opcodes8080.STC, 356 | 0x38 => Opcodes8080.NOP, 357 | 0x39 => Opcodes8080.DAD, 358 | 0x3A => Opcodes8080.LDA, 359 | 0x3B => Opcodes8080.DCX, 360 | 0x3C => Opcodes8080.INR, 361 | 0x3D => Opcodes8080.DCR, 362 | 0x3E => Opcodes8080.MVI, 363 | 0x3F => Opcodes8080.CMC, 364 | // 0x40-0x7F 365 | 0x76 => Opcodes8080.HLT, 366 | < 0x80 => Opcodes8080.MOV, 367 | // 0x80-0xBF 368 | < 0x88 => Opcodes8080.ADD, 369 | < 0x90 => Opcodes8080.ADC, 370 | < 0x98 => Opcodes8080.SUB, 371 | < 0xA0 => Opcodes8080.SBB, 372 | < 0xA8 => Opcodes8080.ANA, 373 | < 0xB0 => Opcodes8080.XRA, 374 | < 0xB8 => Opcodes8080.ORA, 375 | < 0xC0 => Opcodes8080.CMP, 376 | // 0xC0-0xCF 377 | 0xC0 => Opcodes8080.RNZ, 378 | 0xC1 => Opcodes8080.POP, 379 | 0xC2 => Opcodes8080.JNZ, 380 | 0xC3 => Opcodes8080.JMP, 381 | 0xC4 => Opcodes8080.CNZ, 382 | 0xC5 => Opcodes8080.PUSH, 383 | 0xC6 => Opcodes8080.ADI, 384 | 0xC7 => Opcodes8080.RST, 385 | 0xC8 => Opcodes8080.RZ, 386 | 0xC9 => Opcodes8080.RET, 387 | 0xCA => Opcodes8080.JZ, 388 | 0xCB => Opcodes8080.JMP, 389 | 0xCC => Opcodes8080.CZ, 390 | 0xCD => Opcodes8080.CALL, 391 | 0xCE => Opcodes8080.ACI, 392 | 0xCF => Opcodes8080.RST, 393 | // 0xD0-0xDF 394 | 0xD0 => Opcodes8080.RNC, 395 | 0xD1 => Opcodes8080.POP, 396 | 0xD2 => Opcodes8080.JNC, 397 | 0xD3 => Opcodes8080.OUT, 398 | 0xD4 => Opcodes8080.CNC, 399 | 0xD5 => Opcodes8080.PUSH, 400 | 0xD6 => Opcodes8080.SUI, 401 | 0xD7 => Opcodes8080.RST, 402 | 0xD8 => Opcodes8080.RC, 403 | 0xD9 => Opcodes8080.RET, 404 | 0xDA => Opcodes8080.JC, 405 | 0xDB => Opcodes8080.IN, 406 | 0xDC => Opcodes8080.CC, 407 | 0xDD => Opcodes8080.CALL, 408 | 0xDE => Opcodes8080.SBI, 409 | 0xDF => Opcodes8080.RST, 410 | // 0xE0-0xEF 411 | 0xE0 => Opcodes8080.RPO, 412 | 0xE1 => Opcodes8080.POP, 413 | 0xE2 => Opcodes8080.JPO, 414 | 0xE3 => Opcodes8080.XTHL, 415 | 0xE4 => Opcodes8080.CPO, 416 | 0xE5 => Opcodes8080.PUSH, 417 | 0xE6 => Opcodes8080.ANI, 418 | 0xE7 => Opcodes8080.RST, 419 | 0xE8 => Opcodes8080.RPE, 420 | 0xE9 => Opcodes8080.PCHL, 421 | 0xEA => Opcodes8080.JPE, 422 | 0xEB => Opcodes8080.XCHG, 423 | 0xEC => Opcodes8080.CPE, 424 | 0xED => Opcodes8080.CALL, 425 | 0xEE => Opcodes8080.XRI, 426 | 0xEF => Opcodes8080.RST, 427 | // 0xF0-0xFF 428 | 0xF0 => Opcodes8080.RP, 429 | 0xF1 => Opcodes8080.POP, 430 | 0xF2 => Opcodes8080.JP, 431 | 0xF3 => Opcodes8080.DI, 432 | 0xF4 => Opcodes8080.CP, 433 | 0xF5 => Opcodes8080.PUSH, 434 | 0xF6 => Opcodes8080.ORI, 435 | 0xF7 => Opcodes8080.RST, 436 | 0xF8 => Opcodes8080.RM, 437 | 0xF9 => Opcodes8080.SPHL, 438 | 0xFA => Opcodes8080.JM, 439 | 0xFB => Opcodes8080.EI, 440 | 0xFC => Opcodes8080.CM, 441 | 0xFD => Opcodes8080.CALL, 442 | 0xFE => Opcodes8080.CPI, 443 | 0xFF => Opcodes8080.RST 444 | }; 445 | } 446 | } 447 | -------------------------------------------------------------------------------- /JIT8080/Generator/Emulator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Reflection.Emit; 6 | using JIT8080._8080; 7 | 8 | namespace JIT8080.Generator 9 | { 10 | public static class Emulator 11 | { 12 | /// 13 | /// Generates methods which return the 16 bit value from two 8 bit 14 | /// register pairs (BC, DE, HL) 15 | /// 16 | /// Access to the type to add the method 17 | /// The name of the function 18 | /// The high byte (H in HL) 19 | /// The low byte (L in HL) 20 | /// A method info which constitutes the simple function 21 | private static MethodBuilder CreateRegisterPairAccess(TypeBuilder typeBuilder, string name, 22 | FieldInfo regHighByte, FieldInfo regLowByte) 23 | { 24 | var method = typeBuilder.DefineMethod(name, MethodAttributes.Public, CallingConventions.Standard, 25 | typeof(ushort), Array.Empty()); 26 | method.SetImplementationFlags(MethodImplAttributes.IL | MethodImplAttributes.AggressiveInlining); 27 | var methodIL = method.GetILGenerator(); 28 | methodIL.Emit(OpCodes.Ldarg_0); 29 | methodIL.Emit(OpCodes.Ldfld, regHighByte); 30 | methodIL.Emit(OpCodes.Ldc_I4_8); 31 | methodIL.Emit(OpCodes.Shl); 32 | methodIL.Emit(OpCodes.Ldarg_0); 33 | methodIL.Emit(OpCodes.Ldfld, regLowByte); 34 | methodIL.Emit(OpCodes.Add); 35 | methodIL.Emit(OpCodes.Ret); 36 | 37 | return method; 38 | } 39 | 40 | private static MethodBuilder CreateRegisterPairSet(TypeBuilder typeBuilder, string name, FieldInfo regHighByte, 41 | FieldInfo regLowByte) 42 | { 43 | var method = typeBuilder.DefineMethod(name, MethodAttributes.Public, CallingConventions.Standard, 44 | null, new[] {typeof(ushort)}); 45 | method.SetImplementationFlags(MethodImplAttributes.IL | MethodImplAttributes.AggressiveInlining); 46 | var methodIL = method.GetILGenerator(); 47 | methodIL.Emit(OpCodes.Ldarg_0); 48 | methodIL.Emit(OpCodes.Ldarg_1); 49 | methodIL.Emit(OpCodes.Conv_U1); 50 | methodIL.Emit(OpCodes.Stfld, regLowByte); 51 | methodIL.Emit(OpCodes.Ldarg_0); 52 | methodIL.Emit(OpCodes.Ldarg_1); 53 | methodIL.Emit(OpCodes.Ldc_I4_8); 54 | methodIL.Emit(OpCodes.Shr); 55 | methodIL.Emit(OpCodes.Conv_U1); 56 | methodIL.Emit(OpCodes.Stfld, regHighByte); 57 | methodIL.Emit(OpCodes.Ret); 58 | 59 | return method; 60 | } 61 | 62 | private static MethodBuilder CreateFlagRegister(TypeBuilder typeBuilder, CpuInternalBuilders internals) 63 | { 64 | var method = typeBuilder.DefineMethod("GetFlagRegister", MethodAttributes.Public, 65 | CallingConventions.Standard, 66 | typeof(byte), Type.EmptyTypes); 67 | method.SetImplementationFlags(MethodImplAttributes.IL | MethodImplAttributes.AggressiveInlining); 68 | var methodIL = method.GetILGenerator(); 69 | methodIL.Emit(OpCodes.Ldarg_0); 70 | methodIL.Emit(OpCodes.Ldfld, internals.SignFlag); 71 | methodIL.Emit(OpCodes.Ldc_I4_7); 72 | methodIL.Emit(OpCodes.Shl); 73 | methodIL.Emit(OpCodes.Ldarg_0); 74 | methodIL.Emit(OpCodes.Ldfld, internals.ZeroFlag); 75 | methodIL.Emit(OpCodes.Ldc_I4_6); 76 | methodIL.Emit(OpCodes.Shl); 77 | methodIL.Emit(OpCodes.Or); 78 | methodIL.Emit(OpCodes.Ldarg_0); 79 | methodIL.Emit(OpCodes.Ldfld, internals.AuxCarryFlag); 80 | methodIL.Emit(OpCodes.Ldc_I4_4); 81 | methodIL.Emit(OpCodes.Shl); 82 | methodIL.Emit(OpCodes.Or); 83 | methodIL.Emit(OpCodes.Ldarg_0); 84 | methodIL.Emit(OpCodes.Ldfld, internals.ParityFlag); 85 | methodIL.Emit(OpCodes.Ldc_I4_2); 86 | methodIL.Emit(OpCodes.Shl); 87 | methodIL.Emit(OpCodes.Or); 88 | methodIL.Emit(OpCodes.Ldc_I4_2); 89 | methodIL.Emit(OpCodes.Or); 90 | methodIL.Emit(OpCodes.Ldarg_0); 91 | methodIL.Emit(OpCodes.Ldfld, internals.CarryFlag); 92 | methodIL.Emit(OpCodes.Or); 93 | methodIL.Emit(OpCodes.Ret); 94 | 95 | return method; 96 | } 97 | 98 | private static MethodBuilder CreateSetFlagRegister(TypeBuilder typeBuilder, CpuInternalBuilders internals) 99 | { 100 | var method = typeBuilder.DefineMethod("SetFlagRegister", MethodAttributes.Public, 101 | CallingConventions.Standard, null, new[] {typeof(byte)}); 102 | method.SetImplementationFlags(MethodImplAttributes.IL | MethodImplAttributes.AggressiveInlining); 103 | 104 | var methodIL = method.GetILGenerator(); 105 | methodIL.Emit(OpCodes.Ldarg_0); 106 | methodIL.Emit(OpCodes.Ldarg_1); 107 | methodIL.Emit(OpCodes.Ldc_I4_1); 108 | methodIL.Emit(OpCodes.And); 109 | methodIL.Emit(OpCodes.Stfld, internals.CarryFlag); 110 | 111 | methodIL.Emit(OpCodes.Ldarg_0); 112 | methodIL.Emit(OpCodes.Ldarg_1); 113 | methodIL.Emit(OpCodes.Ldc_I4_4); 114 | methodIL.Emit(OpCodes.And); 115 | methodIL.Emit(OpCodes.Ldc_I4_2); 116 | methodIL.Emit(OpCodes.Shr_Un); 117 | methodIL.Emit(OpCodes.Stfld, internals.ParityFlag); 118 | 119 | methodIL.Emit(OpCodes.Ldarg_0); 120 | methodIL.Emit(OpCodes.Ldarg_1); 121 | methodIL.EmitLd8Immediate( 0b0001_0000); 122 | methodIL.Emit(OpCodes.And); 123 | methodIL.Emit(OpCodes.Ldc_I4_4); 124 | methodIL.Emit(OpCodes.Shr_Un); 125 | methodIL.Emit(OpCodes.Stfld, internals.AuxCarryFlag); 126 | 127 | methodIL.Emit(OpCodes.Ldarg_0); 128 | methodIL.Emit(OpCodes.Ldarg_1); 129 | methodIL.EmitLd8Immediate( 0b0100_0000); 130 | methodIL.Emit(OpCodes.And); 131 | methodIL.Emit(OpCodes.Ldc_I4_6); 132 | methodIL.Emit(OpCodes.Shr_Un); 133 | methodIL.Emit(OpCodes.Stfld, internals.ZeroFlag); 134 | 135 | methodIL.Emit(OpCodes.Ldarg_0); 136 | methodIL.Emit(OpCodes.Ldarg_1); 137 | methodIL.Emit(OpCodes.Ldc_I4, 0b1000_0000); 138 | methodIL.Emit(OpCodes.And); 139 | methodIL.Emit(OpCodes.Ldc_I4_7); 140 | methodIL.Emit(OpCodes.Shr_Un); 141 | methodIL.Emit(OpCodes.Stfld, internals.SignFlag); 142 | 143 | methodIL.Emit(OpCodes.Ret); 144 | 145 | return method; 146 | } 147 | 148 | /// 149 | /// For any given offset into the program we can generate the requisite 150 | /// IL based upon the opcode (first byte of the span) and any operands 151 | /// (further bytes from the span) 152 | /// 153 | /// This returns the opcodes to emit (with args) and the number of bytes 154 | /// consumed 155 | /// 156 | /// 157 | /// The full ROM (not just the portion we're looking at) 158 | /// 159 | /// 160 | /// Indicates what index into the ROM we are generating an instruction for 161 | /// 162 | /// 163 | /// Provides references to all internal fields/methods on this class 164 | /// for e.g. register access 165 | /// 166 | /// 167 | /// Provides labels for every address in the ROM for jump/call commands 168 | /// 169 | /// 170 | /// A tuple containing: 171 | /// 1. The emitter which can be called to put instructions on an IL stream 172 | /// 2. The number of bytes consumed by this operation 173 | /// 3. The number of cycles taken by the operation (non-branching) 174 | /// 175 | private static (IEmitter, byte, long) GenerateILForOpcode(Span program, int programCounter, 176 | CpuInternalBuilders internals, Label[] jumpLabels) 177 | { 178 | var opcodeByte = program[programCounter]; 179 | var operand1 = program[(programCounter + 1) % program.Length]; 180 | var operand2 = program[(programCounter + 2) % program.Length]; 181 | var operandWord = (ushort) ((operand2 << 8) + operand1); 182 | var opcode = Opcodes8080Decoder.Decode(opcodeByte); 183 | 184 | IEmitter emitter = opcode switch 185 | { 186 | Opcodes8080.NOP => new NOPEmitter(), 187 | Opcodes8080.LXI => new LXIEmitter(opcodeByte, operand1, operand2, operandWord), 188 | Opcodes8080.STAX => new STAXEmitter(opcodeByte), 189 | Opcodes8080.INX => new INXDCXEmitter(opcodeByte), 190 | Opcodes8080.INR => new INRDCREmitter(opcodeByte), 191 | Opcodes8080.DCR => new INRDCREmitter(opcodeByte), 192 | Opcodes8080.MVI => new MVIEmitter(opcodeByte, operand1), 193 | Opcodes8080.RLC => new RLCEmitter(), 194 | Opcodes8080.DAD => new DADEmitter(opcodeByte), 195 | Opcodes8080.LDAX => new LDAXEmitter(opcodeByte), 196 | Opcodes8080.DCX => new INXDCXEmitter(opcodeByte), 197 | Opcodes8080.RRC => new RRCEmitter(), 198 | Opcodes8080.RAL => new RALEmitter(), 199 | Opcodes8080.RAR => new RAREmitter(), 200 | Opcodes8080.SHLD => new SHLDEmitter(operandWord), 201 | Opcodes8080.DAA => new NOPEmitter(), // TODO - Actually implement DAA after handling aux carry flag 202 | Opcodes8080.LHLD => new LHLDEmitter(operandWord), 203 | Opcodes8080.CMA => new CMAEmitter(), 204 | Opcodes8080.STA => new STAEmitter(operandWord), 205 | Opcodes8080.STC => new STCEmitter(), 206 | Opcodes8080.LDA => new LDAEmitter(operandWord), 207 | Opcodes8080.CMC => new CMCEmitter(), 208 | Opcodes8080.MOV => new MOVEmitter(opcodeByte), 209 | Opcodes8080.HLT => new HLTEmitter(), 210 | Opcodes8080.ADD => new General8BitALUEmitter(opcodeByte, opcode), 211 | Opcodes8080.ADC => new General8BitALUEmitter(opcodeByte, opcode), 212 | Opcodes8080.SUB => new General8BitALUEmitter(opcodeByte, opcode), 213 | Opcodes8080.SBB => new General8BitALUEmitter(opcodeByte, opcode), 214 | Opcodes8080.ANA => new General8BitALUEmitter(opcodeByte, opcode), 215 | Opcodes8080.XRA => new General8BitALUEmitter(opcodeByte, opcode), 216 | Opcodes8080.ORA => new General8BitALUEmitter(opcodeByte, opcode), 217 | Opcodes8080.CMP => new General8BitALUEmitter(opcodeByte, opcode), 218 | Opcodes8080.RNZ => new RetEmitter(opcodeByte, internals.ZeroFlag, false), 219 | Opcodes8080.POP => new POPEmitter(opcodeByte), 220 | Opcodes8080.JNZ => new JumpOnFlagEmitter(opcodeByte, internals.ZeroFlag, false, 221 | jumpLabels[operandWord % program.Length], 222 | operandWord), 223 | Opcodes8080.JMP => new JumpOnFlagEmitter(opcodeByte, jumpLabels[operandWord % program.Length], 224 | operandWord), 225 | Opcodes8080.CNZ => new CallEmitter(opcodeByte, (ushort) (programCounter + opcode.Length()), 226 | internals.ZeroFlag, false, jumpLabels[operandWord % program.Length], (ushort) (operandWord % program.Length)), 227 | Opcodes8080.PUSH => new PUSHEmitter(opcodeByte), 228 | Opcodes8080.ADI => new General8BitALUEmitter(opcodeByte, opcode, operand1), 229 | Opcodes8080.RST => new CallEmitter(opcodeByte, (ushort) (programCounter + opcode.Length()), 230 | jumpLabels[opcodeByte & 0b0011_1000], (ushort) (opcodeByte & 0b0011_1000)), 231 | Opcodes8080.RZ => new RetEmitter(opcodeByte, internals.ZeroFlag, true), 232 | Opcodes8080.RET => new RetEmitter(opcodeByte), 233 | Opcodes8080.JZ => new JumpOnFlagEmitter(opcodeByte, internals.ZeroFlag, true, 234 | jumpLabels[operandWord % program.Length], 235 | operandWord), 236 | Opcodes8080.CZ => new CallEmitter(opcodeByte, (ushort) (programCounter + opcode.Length()), 237 | internals.ZeroFlag, true, jumpLabels[operandWord % program.Length], (ushort) (operandWord % program.Length)), 238 | Opcodes8080.CALL => new CallEmitter(opcodeByte, (ushort) (programCounter + opcode.Length()), 239 | jumpLabels[operandWord % program.Length], (ushort) (operandWord % program.Length)), 240 | Opcodes8080.ACI => new General8BitALUEmitter(opcodeByte, opcode, operand1), 241 | Opcodes8080.RNC => new RetEmitter(opcodeByte, internals.CarryFlag, false), 242 | Opcodes8080.JNC => new JumpOnFlagEmitter(opcodeByte, internals.CarryFlag, false, 243 | jumpLabels[operandWord % program.Length], operandWord), 244 | Opcodes8080.OUT => new OutEmitter(operand1), 245 | Opcodes8080.CNC => new CallEmitter(opcodeByte, (ushort) (programCounter + opcode.Length()), 246 | internals.CarryFlag, false, jumpLabels[operandWord % program.Length], (ushort) (operandWord % program.Length)), 247 | Opcodes8080.SUI => new General8BitALUEmitter(opcodeByte, opcode, operand1), 248 | Opcodes8080.RC => new RetEmitter(opcodeByte, internals.CarryFlag, true), 249 | Opcodes8080.JC => new JumpOnFlagEmitter(opcodeByte, internals.CarryFlag, true, 250 | jumpLabels[operandWord % program.Length], 251 | operandWord), 252 | Opcodes8080.IN => new InEmitter(operand1), 253 | Opcodes8080.CC => new CallEmitter(opcodeByte, (ushort) (programCounter + opcode.Length()), 254 | internals.CarryFlag, true, jumpLabels[operandWord % program.Length], (ushort) (operandWord % program.Length)), 255 | Opcodes8080.SBI => new General8BitALUEmitter(opcodeByte, opcode, operand1), 256 | Opcodes8080.RPO => new RetEmitter(opcodeByte, internals.ParityFlag, false), 257 | Opcodes8080.JPO => new JumpOnFlagEmitter(opcodeByte, internals.ParityFlag, false, 258 | jumpLabels[operandWord % program.Length], operandWord), 259 | Opcodes8080.XTHL => new XTHLEmitter(), 260 | Opcodes8080.CPO => new CallEmitter(opcodeByte, (ushort) (programCounter + opcode.Length()), 261 | internals.ParityFlag, false, jumpLabels[operandWord % program.Length], (ushort) (operandWord % program.Length)), 262 | Opcodes8080.ANI => new General8BitALUEmitter(opcodeByte, opcode, operand1), 263 | Opcodes8080.RPE => new RetEmitter(opcodeByte, internals.ParityFlag, true), 264 | Opcodes8080.PCHL => new PCHLEmitter(), 265 | Opcodes8080.JPE => new JumpOnFlagEmitter(opcodeByte, internals.ParityFlag, true, 266 | jumpLabels[operandWord % program.Length], operandWord), 267 | Opcodes8080.XCHG => new XCHGEmitter(), 268 | Opcodes8080.CPE => new CallEmitter(opcodeByte, (ushort) (programCounter + opcode.Length()), 269 | internals.ParityFlag, true, jumpLabels[operandWord % program.Length], (ushort) (operandWord % program.Length)), 270 | Opcodes8080.XRI => new General8BitALUEmitter(opcodeByte, opcode, operand1), 271 | Opcodes8080.RP => new RetEmitter(opcodeByte, internals.SignFlag, false), 272 | Opcodes8080.JP => new JumpOnFlagEmitter(opcodeByte, internals.SignFlag, false, 273 | jumpLabels[operandWord % program.Length], 274 | operandWord), 275 | Opcodes8080.DI => new UpdateInterruptEnableEmitter(false), 276 | Opcodes8080.CP => new CallEmitter(opcodeByte, (ushort) (programCounter + opcode.Length()), 277 | internals.SignFlag, false, jumpLabels[operandWord % program.Length], (ushort) (operandWord % program.Length)), 278 | Opcodes8080.ORI => new General8BitALUEmitter(opcodeByte, opcode, operand1), 279 | Opcodes8080.RM => new RetEmitter(opcodeByte, internals.SignFlag, true), 280 | Opcodes8080.SPHL => new SPHLEmitter(), 281 | Opcodes8080.JM => new JumpOnFlagEmitter(opcodeByte, internals.SignFlag, true, 282 | jumpLabels[operandWord % program.Length], 283 | operandWord), 284 | Opcodes8080.EI => new UpdateInterruptEnableEmitter(true), 285 | Opcodes8080.CM => new CallEmitter(opcodeByte, (ushort) (programCounter + opcode.Length()), 286 | internals.SignFlag, true, jumpLabels[operandWord % program.Length], (ushort) (operandWord % program.Length)), 287 | Opcodes8080.CPI => new General8BitALUEmitter(opcodeByte, opcode, operand1), 288 | _ => throw new ArgumentOutOfRangeException(nameof(opcode), opcode, "Invalid 8080 opcode") 289 | }; 290 | 291 | return (emitter, opcode.Length(), opcode.Cycles(opcodeByte)); 292 | } 293 | 294 | private static void CreateConstructor(TypeBuilder typeBuilder, FieldBuilder memoryBusField, 295 | FieldBuilder ioHandlerField, FieldBuilder rendererField) 296 | { 297 | var constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, 298 | new[] {typeof(IMemoryBus8080), typeof(IIOHandler), typeof(IRenderer)}); 299 | var methodIL = constructorBuilder.GetILGenerator(); 300 | methodIL.Emit(OpCodes.Ldarg_0); 301 | methodIL.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes)!); 302 | methodIL.Emit(OpCodes.Ldarg_0); 303 | methodIL.Emit(OpCodes.Ldarg_1); 304 | methodIL.Emit(OpCodes.Stfld, memoryBusField); 305 | methodIL.Emit(OpCodes.Ldarg_0); 306 | methodIL.Emit(OpCodes.Ldarg_2); 307 | methodIL.Emit(OpCodes.Stfld, ioHandlerField); 308 | methodIL.Emit(OpCodes.Ldarg_0); 309 | methodIL.Emit(OpCodes.Ldarg_2); 310 | methodIL.Emit(OpCodes.Stfld, rendererField); 311 | methodIL.Emit(OpCodes.Ret); 312 | } 313 | 314 | /// 315 | /// Given a ROM loaded as an array of bytes this constructs an emulator with 316 | /// a "Run" function and all relevant flags & registers. 317 | /// 318 | /// TODO - More explanation about how we turn the program into IL 319 | /// 320 | /// 321 | /// 322 | /// The loaded ROM 323 | /// 324 | /// 325 | /// 326 | /// This will provide ReadByte & WriteByte functionality for the 327 | /// entire 16 bit address space. 328 | /// 329 | /// 330 | /// 331 | /// This will provide IO/OUT function for the whole 8 bit port space 332 | /// 333 | /// 334 | /// 335 | /// This will get called during VBlank interrupts to trigger a redraw 336 | /// 337 | /// 338 | /// 339 | /// Allowance for the owning computer to register code which runs at 340 | /// various points during execution and can fire interrupts. 341 | /// 342 | /// 343 | /// 344 | /// Defines the first instruction which will be executed (e.g. on CP/M 345 | /// machines 0x100 is the first operation executed) 346 | /// 347 | /// 348 | /// 349 | /// An object which contains references to all the field info and 350 | /// method info required to make use of (and inspect the running 351 | /// state) of the emulator. 352 | /// 353 | public static Cpu8080 CreateEmulator(Span program, IMemoryBus8080 memoryBus, IIOHandler ioHandler, 354 | IRenderer renderer, IInterruptUtils interruptUtils, ushort initialProgramCounter = 0x0) 355 | { 356 | var asmName = new AssemblyName("Emulator"); 357 | var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run); 358 | var moduleBuilder = assemblyBuilder.DefineDynamicModule("Execute"); 359 | var typeBuilder = moduleBuilder.DefineType("Emulator", TypeAttributes.Public); 360 | 361 | var methodBuilder = typeBuilder.DefineMethod("Run", MethodAttributes.Public, CallingConventions.Standard); 362 | var methodIL = methodBuilder.GetILGenerator(); 363 | 364 | var cpuInternal = new CpuInternalBuilders 365 | { 366 | A = typeBuilder.DefineField("A", typeof(byte), FieldAttributes.Public), 367 | B = typeBuilder.DefineField("B", typeof(byte), FieldAttributes.Public), 368 | C = typeBuilder.DefineField("C", typeof(byte), FieldAttributes.Public), 369 | D = typeBuilder.DefineField("D", typeof(byte), FieldAttributes.Public), 370 | E = typeBuilder.DefineField("E", typeof(byte), FieldAttributes.Public), 371 | H = typeBuilder.DefineField("H", typeof(byte), FieldAttributes.Public), 372 | L = typeBuilder.DefineField("L", typeof(byte), FieldAttributes.Public), 373 | SignFlag = typeBuilder.DefineField("SignFlag", typeof(bool), FieldAttributes.Public), 374 | ZeroFlag = typeBuilder.DefineField("ZeroFlag", typeof(bool), FieldAttributes.Public), 375 | AuxCarryFlag = typeBuilder.DefineField("AuxCarryFlag", typeof(bool), FieldAttributes.Public), 376 | CarryFlag = typeBuilder.DefineField("CarryFlag", typeof(bool), FieldAttributes.Public), 377 | ParityFlag = typeBuilder.DefineField("ParityFlag", typeof(bool), FieldAttributes.Public), 378 | StackPointer = typeBuilder.DefineField("StackPointer", typeof(ushort), FieldAttributes.Public), 379 | InterruptEnable = typeBuilder.DefineField("InterruptEnable", typeof(bool), FieldAttributes.Public), 380 | CycleCounter = typeBuilder.DefineField("CycleCounter", typeof(long), FieldAttributes.Public), 381 | MemoryBusField = typeBuilder.DefineField("_memoryBus", typeof(IMemoryBus8080), FieldAttributes.Private), 382 | IOHandlerField = typeBuilder.DefineField("_ioHandler", typeof(IIOHandler), FieldAttributes.Private), 383 | RendererField = typeBuilder.DefineField("_renderer", typeof(IRenderer), FieldAttributes.Private), 384 | ProgramLabels = Enumerable.Range(0, program.Length).Select(_ => methodIL.DefineLabel()).ToArray(), 385 | DestinationAddress = methodIL.DeclareLocal(typeof(ushort)), 386 | JumpTableStart = methodIL.DefineLabel() 387 | }; 388 | cpuInternal.HL = CreateRegisterPairAccess(typeBuilder, "HL", cpuInternal.H, cpuInternal.L); 389 | cpuInternal.BC = CreateRegisterPairAccess(typeBuilder, "BC", cpuInternal.B, cpuInternal.C); 390 | cpuInternal.DE = CreateRegisterPairAccess(typeBuilder, "DE", cpuInternal.D, cpuInternal.E); 391 | cpuInternal.SetHL = CreateRegisterPairSet(typeBuilder, "SetHL", cpuInternal.H, cpuInternal.L); 392 | cpuInternal.SetBC = CreateRegisterPairSet(typeBuilder, "SetHL", cpuInternal.B, cpuInternal.C); 393 | cpuInternal.SetDE = CreateRegisterPairSet(typeBuilder, "SetHL", cpuInternal.D, cpuInternal.E); 394 | cpuInternal.GetFlagRegister = CreateFlagRegister(typeBuilder, cpuInternal); 395 | cpuInternal.SetFlagRegister = CreateSetFlagRegister(typeBuilder, cpuInternal); 396 | 397 | CreateConstructor(typeBuilder, cpuInternal.MemoryBusField, cpuInternal.IOHandlerField, cpuInternal.RendererField); 398 | 399 | // Then we create the IL for every position in the program, whether 400 | // that position will ever be considered code or not 401 | var operationsAtIndex = new (IEmitter, byte, long)[program.Length]; 402 | for (var pc = 0; pc < program.Length; pc++) 403 | { 404 | operationsAtIndex[pc] = GenerateILForOpcode(program, pc, cpuInternal, cpuInternal.ProgramLabels); 405 | } 406 | var seenRomIndexes = new HashSet(program.Length); 407 | 408 | // Allow for definition of locals and other emits by an interrupt util handler 409 | interruptUtils.PreProgramEmit(methodIL); 410 | 411 | // Skip the jump table and start at the defined initial PC (defaults to 0) 412 | methodIL.Emit(OpCodes.Br, cpuInternal.ProgramLabels[initialProgramCounter]); 413 | 414 | // Place the jump table 415 | GenerateDynamicJumpTable(methodIL, cpuInternal); 416 | 417 | // Finally we actually generate a program, this amounts to the following steps: 418 | // 1. Find the next program counter position which has not yet been processed 419 | // 2. Emit a label for that program counter position 420 | // 3. Emit the CLR IL commands for that operation 421 | // 4. Increment the program counter to the next opcode 422 | // 5. Continue that process until a previously processed opcode is found and then emit a branch to that label (looping the program) 423 | // 6. Repeat 1-5 until there are no program counter values left to process 424 | var programCounter = 0; 425 | while (seenRomIndexes.Count != program.Length) 426 | { 427 | var hasLooped = false; 428 | while (!hasLooped) 429 | { 430 | var (instructions, instructionLength, cyclesTaken) = operationsAtIndex[programCounter]; 431 | methodIL.MarkLabel(cpuInternal.ProgramLabels[programCounter]); 432 | 433 | // Check for interrupts 434 | interruptUtils.PostInstructionEmit(methodIL, cpuInternal, (ushort)programCounter); 435 | 436 | // Clear the cycle counter so that we know the exact number of cycles that 437 | // the operation took 438 | methodIL.Emit(OpCodes.Ldarg_0); 439 | methodIL.Emit(OpCodes.Ldc_I8, 0L); 440 | methodIL.Emit(OpCodes.Stfld, cpuInternal.CycleCounter); 441 | 442 | #if DEBUG 443 | methodIL.EmitWriteLine($"{programCounter:X4} - {instructions}"); 444 | methodIL.EmitDebugString(cpuInternal); 445 | #endif 446 | instructions.Emit(methodIL, cpuInternal); 447 | 448 | // Store off the number of cycles in the last instruction 449 | methodIL.Emit(OpCodes.Ldarg_0); 450 | methodIL.Emit(OpCodes.Ldarg_0); 451 | methodIL.Emit(OpCodes.Ldfld, cpuInternal.CycleCounter); 452 | methodIL.Emit(OpCodes.Ldc_I8, cyclesTaken); 453 | methodIL.Emit(OpCodes.Add); 454 | methodIL.Emit(OpCodes.Stfld, cpuInternal.CycleCounter); 455 | 456 | seenRomIndexes.Add(programCounter); 457 | programCounter = (programCounter + instructionLength) % program.Length; 458 | 459 | // If this segment of the program rejoins the main program 460 | // then it should branch back into it and we can start on 461 | // the next segment 462 | if (seenRomIndexes.Contains(programCounter)) 463 | { 464 | methodIL.Emit(OpCodes.Br, cpuInternal.ProgramLabels[programCounter]); 465 | hasLooped = true; 466 | } 467 | } 468 | 469 | // Find the next segment which has not yet been processed 470 | programCounter = Enumerable.Range(0, program.Length).FirstOrDefault(ix => !seenRomIndexes.Contains(ix)); 471 | } 472 | 473 | var t = typeBuilder.CreateType(); 474 | return new Cpu8080 475 | { 476 | Internals = new Cpu8080Internals 477 | { 478 | A = t.GetField("A"), 479 | B = t.GetField("B"), 480 | C = t.GetField("C"), 481 | D = t.GetField("D"), 482 | E = t.GetField("E"), 483 | H = t.GetField("H"), 484 | L = t.GetField("L"), 485 | BC = t.GetMethod("BC"), 486 | DE = t.GetMethod("DE"), 487 | HL = t.GetMethod("HL"), 488 | StackPointer = t.GetField("StackPointer"), 489 | SignFlag = t.GetField("SignFlag"), 490 | ZeroFlag = t.GetField("ZeroFlag"), 491 | AuxCarryFlag = t.GetField("AuxCarryFlag"), 492 | ParityFlag = t.GetField("ParityFlag"), 493 | CarryFlag = t.GetField("CarryFlag"), 494 | GetFlagRegister = t.GetMethod("GetFlagRegister"), 495 | SetFlagRegister = t.GetMethod("SetFlagRegister"), 496 | }, 497 | Emulator = Activator.CreateInstance(t, memoryBus, ioHandler, renderer), 498 | Run = t.GetMethod("Run") 499 | }; 500 | } 501 | 502 | /// 503 | /// Various operations in an 8080 allow for jumping to an arbitrary 504 | /// address (which may be in ROM, RAM, VRAM, MMIO etc). 505 | /// 506 | /// This function supports that with a very naive linear search 507 | /// through a jump table. 508 | /// 509 | /// 510 | /// 511 | /// The IL generator in which to place this macro 512 | /// 513 | /// 514 | /// 515 | /// Contains references to the various locals and program labels 516 | /// required to build the table. 517 | /// 518 | private static void GenerateDynamicJumpTable(ILGenerator methodIL, CpuInternalBuilders cpuInternal) 519 | { 520 | methodIL.MarkLabel(cpuInternal.JumpTableStart); 521 | foreach (var (ix, programLabel) in cpuInternal.ProgramLabels.Select((l, ix) => (ix, l))) 522 | { 523 | methodIL.Emit(OpCodes.Ldc_I4, ix); 524 | methodIL.Emit(OpCodes.Ldloc, cpuInternal.DestinationAddress); 525 | methodIL.Emit(OpCodes.Beq, programLabel); 526 | } 527 | 528 | methodIL.EmitWriteLine("Failed to perform dynamic jump"); 529 | methodIL.Emit(OpCodes.Ret); 530 | } 531 | } 532 | } --------------------------------------------------------------------------------