summaryrefslogtreecommitdiff
path: root/Emulator/Chip
diff options
context:
space:
mode:
Diffstat (limited to 'Emulator/Chip')
-rw-r--r--Emulator/Chip/Bus.cs37
-rw-r--r--Emulator/Chip/BusDevice.cs49
-rw-r--r--Emulator/Chip/DebugDevice.cs13
-rw-r--r--Emulator/Chip/DisplayDevice.cs76
-rw-r--r--Emulator/Chip/Q1Cpu.cs171
-rw-r--r--Emulator/Chip/Q1CpuAddressingModes.cs187
-rw-r--r--Emulator/Chip/Q1CpuInstructions.cs282
-rw-r--r--Emulator/Chip/RamDevice.cs33
8 files changed, 848 insertions, 0 deletions
diff --git a/Emulator/Chip/Bus.cs b/Emulator/Chip/Bus.cs
new file mode 100644
index 0000000..0907f61
--- /dev/null
+++ b/Emulator/Chip/Bus.cs
@@ -0,0 +1,37 @@
+namespace Q1.Emulator.Chip;
+
+using System.Collections.Generic;
+using System.Linq;
+
+public class Bus : BusDevice
+{
+ public List<BusDevice> Devices;
+
+ public Bus(u16 addressableStart, u16 addressableEnd) : base(addressableStart, addressableEnd)
+ {
+ this.Devices = [];
+ }
+ private BusDevice? GetDevice(u16 address)
+ {
+ BusDevice? device = this.Devices.FirstOrDefault(x => address >= x.AddressableStart && address < x.AddressableEnd);
+ return device;
+ }
+
+ public override u8 Read(u16 address)
+ {
+ BusDevice? device = this.GetDevice(address);
+ if (device == null)
+ return 0;
+
+ return device.Read(address);
+ }
+
+ public override void Write(u16 address, u8 value)
+ {
+ BusDevice? device = this.GetDevice(address);
+ if (device == null)
+ return;
+
+ device.Write(address, value);
+ }
+} \ No newline at end of file
diff --git a/Emulator/Chip/BusDevice.cs b/Emulator/Chip/BusDevice.cs
new file mode 100644
index 0000000..1e1e972
--- /dev/null
+++ b/Emulator/Chip/BusDevice.cs
@@ -0,0 +1,49 @@
+namespace Q1.Emulator.Chip;
+
+using System.Runtime.CompilerServices;
+
+public abstract class BusDevice
+{
+ public BusDevice(u16 addressableStart, u16 addressableEnd)
+ {
+ this.AddressableStart = addressableStart;
+ this.AddressableEnd = addressableEnd;
+ }
+
+ public Bus Bus { get; set; }
+ public abstract u8 Read(u16 address);
+ public abstract void Write(u16 address, u8 value);
+
+ public u16 ReadWord(u16 address)
+ {
+ u8 high = this.Read(address);
+ u8 low = this.Read((u16) (address + 1));
+ u16 value = Make.u16(high, low);
+ return value;
+ }
+
+ public void WriteWord(u16 address, u16 value)
+ {
+ ref8 data = Make.ref8(value);
+ this.Write(address, data[0]);
+ this.Write((u16) (address + 1), data[1]);
+ }
+
+ public virtual void Clock() { }
+ public virtual void Reset() { }
+
+ public void AttachToBus(Bus bus)
+ {
+ this.Bus = bus;
+ bus.Devices.Add(this);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected u16 GetRelativeAddress(u16 address)
+ {
+ return (u16) (address - this.AddressableStart);
+ }
+
+ public u16 AddressableStart { get; }
+ public u16 AddressableEnd { get; }
+} \ No newline at end of file
diff --git a/Emulator/Chip/DebugDevice.cs b/Emulator/Chip/DebugDevice.cs
new file mode 100644
index 0000000..4e869bb
--- /dev/null
+++ b/Emulator/Chip/DebugDevice.cs
@@ -0,0 +1,13 @@
+namespace Q1.Emulator.Chip;
+
+using System;
+
+public class DebugDevice(u16 addressPoint)
+ : BusDevice(addressPoint, (u16) (addressPoint + 2))
+{
+ public override u8 Read(u16 address) => throw new Exception("DebugDevice.Read not permitted");
+ public override void Write(u16 address, u8 value)
+ {
+ Console.WriteLine($"DebugDevice.Write: x{value:X4} b{value:b8}");
+ }
+} \ No newline at end of file
diff --git a/Emulator/Chip/DisplayDevice.cs b/Emulator/Chip/DisplayDevice.cs
new file mode 100644
index 0000000..a18dbd4
--- /dev/null
+++ b/Emulator/Chip/DisplayDevice.cs
@@ -0,0 +1,76 @@
+namespace Q1.Emulator.Chip;
+
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+public class DisplayDevice : RamDevice
+{
+ public Texture2D? OutputTexture;
+ private readonly u16 _paletteSize;
+ private readonly u16 _displayWidth;
+ private readonly u16 _displayHeight;
+ private Color[] textureData;
+
+ public DisplayDevice(u16 paletteSize, u16 displayWidth, u16 displayHeight, u16 addressableStart)
+ : base(addressableStart, DisplayDevice.DetermineSize(paletteSize, displayWidth, displayHeight))
+ {
+ this._paletteSize = paletteSize;
+ this._displayWidth = displayWidth;
+ this._displayHeight = displayHeight;
+ this.textureData = new Color[this._displayWidth * this._displayHeight];
+ }
+
+ private static u16 DetermineSize(u16 paletteSize, u16 displayWidth, u16 displayHeight)
+ {
+ return (u16) ((paletteSize * 3) + (displayWidth * displayHeight));
+ }
+
+ public override void Clock()
+ {
+ Color[] palette = this.ReadPalette();
+ ref8 pixelData = this.Memory.AsSpan((u16) (this._paletteSize * 3), (u16) (this._displayWidth * this._displayHeight));
+ // Console.WriteLine($"DisplayDevice.Clock: @{(this.AddressableStart + this._paletteSize * 3):X4} palette size={this._paletteSize} display size={this._displayWidth}x{this._displayHeight}");
+ for (u16 i = 0; i < this.textureData.Length; i++)
+ {
+ u8 pixelValue = pixelData[i];
+ this.textureData[i] = palette[pixelValue % this._paletteSize];
+ }
+
+ if (this.OutputTexture != null)
+ this.OutputTexture.SetData(this.textureData);
+ }
+
+ private Color[] ReadPalette()
+ {
+ ref8 paletteData = this.Memory.AsSpan(0, (u16) (this._paletteSize * 3));
+ Color[] colors = new Color[this._paletteSize + 1];
+ colors[0] = Color.Transparent;
+
+ for (u16 i = 0; i < this._paletteSize; i++)
+ {
+ u16 offset = (u16) (i * 3);
+ byte r = paletteData[offset];
+ byte g = paletteData[offset + 1];
+ byte b = paletteData[offset + 2];
+ colors[(u16) (i + 1)] = new Color(r, g, b);
+ }
+
+ return colors;
+ }
+
+ public void SetPalette(Color[] palette)
+ {
+ if (palette.Length != this._paletteSize)
+ throw new ArgumentException($"Palette must have {this._paletteSize} colors.");
+
+ ref8 paletteData = this.Memory.AsSpan(0, (u16) (this._paletteSize * 3));
+ for (u16 i = 0; i < this._paletteSize; i++)
+ {
+ u16 offset = (u16) (i * 3);
+ paletteData[offset] = palette[i].R;
+ paletteData[offset + 1] = palette[i].G;
+ paletteData[offset + 2] = palette[i].B;
+ }
+ }
+} \ No newline at end of file
diff --git a/Emulator/Chip/Q1Cpu.cs b/Emulator/Chip/Q1Cpu.cs
new file mode 100644
index 0000000..16e1e14
--- /dev/null
+++ b/Emulator/Chip/Q1Cpu.cs
@@ -0,0 +1,171 @@
+namespace Q1.Emulator.Chip;
+
+using System;
+using Assembler;
+
+public partial class Q1Cpu
+{
+ public const u16 StackStart = 0x2FFF;
+ public const u16 StackLength = 16 * 2; // 16 16-bit values
+
+ public Random Random;
+
+ public u16 AX = 0; // Accumulator register
+ public u16 DX = 0; // Data register
+ public u16 LX = 0; // Logical register
+
+ public u16 SP { get; set; } // Stack pointer
+ public u16 PC { get; set; } // Program counter
+
+ public u16[] V { get; set; } // Variable registers (64 total)
+ public u16 I { get; set; } // Index register
+ public u16 Cycles { get; set; } // Number of cycles
+
+ public Bus Bus { get; set; }
+ public bool AbortRequested = false;
+
+ public Q1Cpu()
+ {
+ this.LoadInstructionLookup();
+ }
+
+ public void Reset()
+ {
+ this.AbortRequested = false;
+
+ this.V = new u16[16];
+
+ this.SP = Q1Cpu.StackStart;
+ this.PushStack(0xFFFF);
+
+ this.I = 0;
+ }
+
+ public void Clock()
+ {
+ var pc = this.PC;
+ if (pc == 0xFFFF)
+ {
+ this.AbortRequested = true;
+ return;
+ }
+
+ u16 instruction = this.FetchInstruction(out u8 op, out u8 m1, out u8 m2, out _);
+ if (instruction != 0x0101)
+ Console.WriteLine($"CPU.Clock: {pc:X4} | {Disassembler.ParseInstruction(instruction)}");
+
+ bool ex = m2 > 0;
+ switch (m1, ex)
+ {
+ case (0x00, false):
+ this.InstructionLookup[op].Item1(); // Implicit
+ break;
+ case (> 0x00, false):
+ this.InstructionLookup[op].Item2(m1); // Simple addressing mode
+ break;
+ case (0x00, true):
+ this.InstructionLookup[op].Item3(); // Extended implicit
+ break;
+ case (> 0x00, true):
+ this.InstructionLookup[op].Item4(m1, m2); // Extended addressing mode
+ break;
+ }
+
+ this.Cycles++;
+ }
+ public u16 FetchInstruction(out u8 op, out u8 m1, out u8 m2, out u8 length)
+ {
+ u16 instruction = this.Bus.ReadWord(this.PC);
+ this.PC += 2;
+
+ op = (u8) ((instruction >> 8) & 0xFF); // 256 opcodes
+ m1 = (u8) ((instruction >> 4) & 0x0F); // 16 modes
+ m2 = (u8) (instruction & 0x0F); // 16 modes
+
+ length = (u8) (this.AddressingModeLength(m1) + this.AddressingModeLength(m2));
+ return instruction;
+ }
+
+ private void PushStack(u16 value)
+ {
+ if (this.SP + 2 >= Q1Cpu.StackStart + Q1Cpu.StackLength)
+ throw new InvalidOperationException("Stack overflow");
+
+ this.Bus.WriteWord(this.SP, value);
+ this.SP += 2;
+ }
+
+ private u16 PopStack()
+ {
+ if (this.SP - 2 < Q1Cpu.StackStart)
+ throw new InvalidOperationException("Stack underflow");
+
+ this.SP -= 2;
+ return this.Bus.ReadWord(this.SP);
+ }
+
+ private u16 FetchRegister(u8 regIndex)
+ {
+ return regIndex switch
+ {
+ <= 0x3F => this.V[regIndex],
+
+ 0x40 => this.AX,
+ 0x41 => (u16) (this.AX >> 8), // High byte of AX
+ 0x42 => (u16) (this.AX & 0xFF), // Low byte of AX
+
+ 0x43 => this.DX,
+ 0x44 => (u16) (this.DX >> 8), // High byte of DX
+ 0x45 => (u16) (this.DX & 0xFF), // Low byte of DX
+
+ 0x46 => this.LX,
+ 0x50 => this.PC,
+ 0x51 => this.SP,
+
+ _ => throw new InvalidOperationException($"Unknown register index: {regIndex:X2}"),
+ };
+ }
+
+ private void StoreRegister(u8 regIndex, u16 value)
+ {
+ switch (regIndex)
+ {
+ case <= 0x3F:
+ this.V[regIndex] = value;
+ break;
+
+ case 0x40:
+ this.AX = value;
+ break;
+ case 0x41:
+ this.AX = (u16) ((this.AX & 0x00FF) | (value << 8));
+ break; // Set high byte
+ case 0x42:
+ this.AX = (u16) ((this.AX & 0xFF00) | (value & 0x00FF));
+ break; // Set low byte
+
+ case 0x43:
+ this.DX = value;
+ break;
+ case 0x44:
+ this.DX = (u16) ((this.DX & 0x00FF) | (value << 8));
+ break; // Set high byte
+ case 0x45:
+ this.DX = (u16) ((this.DX & 0xFF00) | (value & 0x00FF));
+ break; // Set low byte
+
+ case 0x46:
+ this.LX = value;
+ break;
+ case 0x50:
+ this.PC = value;
+ break;
+ case 0x51:
+ this.SP = value;
+ break;
+
+ default:
+ throw new InvalidOperationException($"Unknown register index: {regIndex:X2}");
+ }
+ }
+} \ No newline at end of file
diff --git a/Emulator/Chip/Q1CpuAddressingModes.cs b/Emulator/Chip/Q1CpuAddressingModes.cs
new file mode 100644
index 0000000..40020f1
--- /dev/null
+++ b/Emulator/Chip/Q1CpuAddressingModes.cs
@@ -0,0 +1,187 @@
+namespace Q1.Emulator.Chip;
+
+using System;
+
+public partial class Q1Cpu
+{
+ public (Func<u16> read, Action<u16> write, Action<u8> writeByte) Address(u8 mode)
+ {
+ return mode switch
+ {
+ 0x0 => this.AddressingMode_Implicit(),
+ 0x1 => this.AddressingMode_Immediate(),
+ 0x2 => this.AddressingMode_Direct(),
+ 0x3 => this.AddressingMode_Indirect(),
+ 0x4 => this.AddressingMode_Relative(),
+ 0x5 => this.AddressingMode_Register(),
+ 0x6 => this.AddressingMode_RegisterIndirect(),
+ 0x7 => this.AddressingMode_RegisterIndirectOffset(),
+ _ => throw new InvalidOperationException($"Unknown addressing mode: {mode:X2}"),
+ };
+ }
+
+ private u8 AddressingModeLength(u8 mode)
+ {
+ return mode switch
+ {
+ 0x0 => 0, // Implicit
+ 0x1 => 2, // Immediate
+ 0x2 => 2, // Direct
+ 0x3 => 2, // Indirect
+ 0x4 => 2, // Relative
+ 0x5 => 1, // Register
+ 0x6 => 1, // Register indirect
+ 0x7 => 3, // Register indirect with offset
+ _ => throw new InvalidOperationException($"Unknown addressing mode: {mode:X2}"),
+ };
+ }
+
+ public (Func<u16> read, Action<u16> write, Action<u8> writeByte) AddressingMode_Implicit()
+ {
+ u16 Read() => throw new InvalidOperationException("Cannot read from implicit mode.");
+ void Write(u16 value) => throw new InvalidOperationException("Cannot write to implicit mode.");
+ void WriteByte(u8 value) => throw new InvalidOperationException("Cannot write byte to implicit mode.");
+
+ return (Read, Write, WriteByte);
+ }
+
+
+ public (Func<u16> read, Action<u16> write, Action<u8> writeByte) AddressingMode_Immediate()
+ {
+ u16 value = this.Bus.ReadWord(this.PC);
+ this.PC += 2;
+
+ u16 Read() => value;
+ void Write(u16 v) => throw new InvalidOperationException("Cannot write to immediate mode.");
+ void WriteByte(u8 v) => throw new InvalidOperationException("Cannot write byte to immediate mode.");
+
+ return (Read, Write, WriteByte);
+ }
+
+ public (Func<u16> read, Action<u16> write, Action<u8> writeByte) AddressingMode_Direct()
+ {
+ u16 address = this.Bus.ReadWord(this.PC);
+ this.PC += 2;
+
+ u16 Read() => this.Bus.ReadWord(address);
+ void Write(u16 value) => this.Bus.WriteWord(address, value);
+ void WriteByte(u8 value) => this.Bus.Write(address, value);
+
+ return (Read, Write, WriteByte);
+ }
+
+ public (Func<u16> read, Action<u16> write, Action<u8> writeByte) AddressingMode_Indirect()
+ {
+ u16 address = this.Bus.ReadWord(this.PC);
+ this.PC += 2;
+
+ u16 Read()
+ {
+ u16 indirectAddress = this.Bus.ReadWord(address);
+ u16 value = this.Bus.ReadWord(indirectAddress);
+ return value;
+ }
+
+ void Write(u16 value)
+ {
+ u16 indirectAddress = this.Bus.ReadWord(address);
+ this.Bus.WriteWord(indirectAddress, value);
+ }
+
+ void WriteByte(u8 value)
+ {
+ u16 indirectAddress = this.Bus.ReadWord(address);
+ this.Bus.Write(indirectAddress, value);
+ }
+
+ return (Read, Write, WriteByte);
+ }
+
+ public (Func<u16> read, Action<u16> write, Action<u8> writeByte) AddressingMode_Relative()
+ {
+ u16 offset = this.Bus.ReadWord(this.PC);
+ this.PC += 2;
+
+ u16 address = (u16) (this.PC + offset);
+
+ u16 Read() => address;
+ void Write(u16 value) => throw new InvalidOperationException("Cannot write to relative mode.");
+ void WriteByte(u8 value) => throw new InvalidOperationException("Cannot write byte to relative mode.");
+
+ return (Read, Write, WriteByte);
+ }
+
+ public (Func<u16> read, Action<u16> write, Action<u8> writeByte) AddressingMode_Register()
+ {
+ u8 regIndex = this.Bus.Read(this.PC);
+ this.PC += 1;
+
+ u16 Read() => this.FetchRegister(regIndex);
+ void Write(u16 value) => this.StoreRegister(regIndex, value);
+ void WriteByte(u8 value)
+ {
+ u16 currentValue = this.FetchRegister(regIndex);
+ u16 newValue = (u16) ((currentValue & 0xFF00) | (value & 0x00FF)); // Update low byte
+ this.StoreRegister(regIndex, newValue);
+ }
+
+ return (Read, Write, WriteByte);
+ }
+
+ public (Func<u16> read, Action<u16> write, Action<u8> writeByte) AddressingMode_RegisterIndirect()
+ {
+ u8 regIndex = this.Bus.Read(this.PC);
+ this.PC += 1;
+
+ u16 Read()
+ {
+ u16 address = this.FetchRegister(regIndex);
+ u16 value = this.Bus.ReadWord(address);
+ return value;
+ }
+
+ void Write(u16 value)
+ {
+ u16 address = this.FetchRegister(regIndex);
+ this.Bus.WriteWord(address, value);
+ }
+
+ void WriteByte(u8 value)
+ {
+ u16 address = this.FetchRegister(regIndex);
+ this.Bus.Write(address, value);
+ }
+
+ return (Read, Write, WriteByte);
+ }
+
+ public (Func<u16> read, Action<u16> write, Action<u8> writeByte) AddressingMode_RegisterIndirectOffset()
+ {
+ u8 regIndex = this.Bus.Read(this.PC);
+ this.PC += 1;
+
+ u16 offset = this.Bus.ReadWord(this.PC);
+ this.PC += 2;
+
+ u16 Read()
+ {
+ u16 address = (u16) (this.FetchRegister(regIndex) + offset);
+ u16 value = this.Bus.ReadWord(address);
+ return value;
+ }
+
+ void Write(u16 value)
+ {
+ u16 address = (u16) (this.FetchRegister(regIndex) + offset);
+ this.Bus.WriteWord(address, value);
+ }
+
+ void WriteByte(u8 value)
+ {
+ u16 address = (u16) (this.FetchRegister(regIndex) + offset);
+ this.Bus.Write(address, value);
+ }
+
+ return (Read, Write, WriteByte);
+ }
+} \ No newline at end of file
diff --git a/Emulator/Chip/Q1CpuInstructions.cs b/Emulator/Chip/Q1CpuInstructions.cs
new file mode 100644
index 0000000..2da8969
--- /dev/null
+++ b/Emulator/Chip/Q1CpuInstructions.cs
@@ -0,0 +1,282 @@
+namespace Q1.Emulator.Chip;
+
+using System;
+using System.Collections.Generic;
+
+public partial class Q1Cpu
+{
+ public static Dictionary<u8, string[]> InstructionLookupStrings = new()
+ {
+ { 0x00, ["NOP", "JMP", "HALT", "MOV"] },
+ { 0x01, ["RET", "CALL", "SUS", "MOVB"] },
+ { 0x02, ["BZ", "BZ", "BR", "NOP"] },
+ { 0x03, ["NOP", "PUSH", "NOP", "NOP"] },
+ { 0x04, ["NOP", "POP", "NOP", "NOP"] },
+
+ { 0x10, ["INV", "INV", "NOP", "AND"] },
+ { 0x11, ["SHL", "SHL", "NOP", "OR"] },
+ { 0x12, ["SHR", "SHR", "NOP", "XOR"] },
+
+ { 0x20, ["NOT", "NOT", "NOP", "CMP"] },
+ { 0x21, ["NOP", "NOP", "NOP", "LT"] },
+ { 0x22, ["NOP", "NOP", "NOP", "GT"] },
+
+ { 0x30, ["INC_AX", "INC", "NOP", "ADD"] },
+ { 0x31, ["DEC_AX", "DEC", "NOP", "SUB"] },
+ { 0x32, ["NOP", "NOP", "NOP", "DIV"] },
+ { 0x33, ["NOP", "NOP", "NOP", "MUL"] },
+ { 0x34, ["NOP", "NOP", "NOP", "MOD"] },
+ };
+
+ public Dictionary<u8, (Action, Action<u8>, Action, Action<u8, u8>)> InstructionLookup;
+ private void LoadInstructionLookup()
+ {
+ this.InstructionLookup = new Dictionary<u8, (Action, Action<u8>, Action, Action<u8, u8>)>
+ {
+ { 0x00, (this.Instruction_NOP, this.Instruction_JMP, this.Instruction_HALT, this.Instruction_MOV) },
+ { 0x01, (this.Instruction_RET, this.Instruction_CALL, this.Instruction_SUS, this.Instruction_MOVB) },
+ { 0x02, (this.Instruction_BZ_LX, this.Instruction_BZ, this.Instruction_BR, this.Instruction_NOP) },
+ { 0x03, (this.Instruction_NOP, this.Instruction_PUSH, this.Instruction_NOP, this.Instruction_NOP) },
+ { 0x04, (this.Instruction_NOP, this.Instruction_POP, this.Instruction_NOP, this.Instruction_NOP) },
+
+ { 0x10, (this.Instruction_INV_DX, this.Instruction_INV, this.Instruction_NOP, this.Instruction_AND) },
+ { 0x11, (this.Instruction_SHL_DX, this.Instruction_SHL, this.Instruction_NOP, this.Instruction_OR) },
+ { 0x12, (this.Instruction_SHR_DX, this.Instruction_SHR, this.Instruction_NOP, this.Instruction_XOR) },
+
+ { 0x20, (this.Instruction_NOT_LX, this.Instruction_NOT, this.Instruction_NOP, this.Instruction_CMP) },
+ { 0x21, (this.Instruction_NOP, this.Instruction_NOP, this.Instruction_NOP, this.Instruction_LT) },
+ { 0x22, (this.Instruction_NOP, this.Instruction_NOP, this.Instruction_NOP, this.Instruction_GT) },
+
+ { 0x30, (this.Instruction_INC_AX, this.Instruction_INC, this.Instruction_NOP, this.Instruction_ADD) },
+ { 0x31, (this.Instruction_DEC_AX, this.Instruction_DEC, this.Instruction_NOP, this.Instruction_SUB) },
+ { 0x32, (this.Instruction_NOP, this.Instruction_NOP, this.Instruction_NOP, this.Instruction_DIV) },
+ { 0x33, (this.Instruction_NOP, this.Instruction_NOP, this.Instruction_NOP, this.Instruction_MUL) },
+ { 0x34, (this.Instruction_NOP, this.Instruction_NOP, this.Instruction_NOP, this.Instruction_MOD) },
+ };
+ }
+ private void Instruction_ADD(u8 m1, u8 m2)
+ {
+ u16 v1 = this.Address(m1).read();
+ u16 v2 = this.Address(m2).read();
+
+ u16 result = (u16) (v1 + v2);
+ this.AX = result;
+ }
+ private void Instruction_SUB(u8 m1, u8 m2)
+ {
+ u16 v1 = this.Address(m1).read();
+ u16 v2 = this.Address(m2).read();
+
+ u16 result = (u16) (v1 - v2);
+ this.AX = result;
+ }
+ private void Instruction_MUL(u8 m1, u8 m2)
+ {
+ u16 v1 = this.Address(m1).read();
+ u16 v2 = this.Address(m2).read();
+
+ u16 result = (u16) (v1 * v2);
+ this.AX = result;
+ }
+ private void Instruction_DIV(u8 m1, u8 m2)
+ {
+ u16 v1 = this.Address(m1).read();
+ u16 v2 = this.Address(m2).read();
+
+ if (v2 == 0)
+ throw new DivideByZeroException("Cannot perform division by zero.");
+ u16 result = (u16) (v1 / v2);
+ this.AX = result;
+ }
+ private void Instruction_MOD(u8 m1, u8 m2)
+ {
+ u16 v1 = this.Address(m1).read();
+ u16 v2 = this.Address(m2).read();
+
+ if (v2 == 0)
+ throw new DivideByZeroException("Cannot perform modulo operation with divisor zero.");
+ u16 result = (u16) (v1 % v2);
+ this.AX = result;
+ }
+ private void Instruction_INC(u8 obj)
+ {
+ var address = this.Address(obj);
+ u16 value = address.read();
+ address.write((u16) (value + 1));
+ }
+ private void Instruction_DEC(u8 obj)
+ {
+ var address = this.Address(obj);
+ u16 value = address.read();
+ address.write((u16) (value - 1));
+ }
+ private void Instruction_INC_AX()
+ {
+ this.AX = (u16) (this.AX + 1);
+ }
+ private void Instruction_DEC_AX()
+ {
+ this.AX = (u16) (this.AX - 1);
+ }
+ private void Instruction_SHL(u8 obj)
+ {
+ var address = this.Address(obj);
+ u16 value = address.read();
+ u16 result = (u16) (value << 1);
+ address.write(result);
+ }
+ private void Instruction_SHR(u8 obj)
+ {
+ var address = this.Address(obj);
+ u16 value = address.read();
+ u16 result = (u16) (value >> 1);
+ address.write(result);
+ }
+ private void Instruction_SHL_DX()
+ {
+ this.DX = (u16) (this.DX << 1);
+ }
+ private void Instruction_SHR_DX()
+ {
+ this.DX = (u16) (this.DX >> 1);
+ }
+ private void Instruction_XOR(u8 m1, u8 m2)
+ {
+ u16 v1 = this.Address(m1).read();
+ u16 v2 = this.Address(m2).read();
+
+ u16 result = (u16) (v1 ^ v2);
+ this.DX = result;
+ }
+ private void Instruction_OR(u8 m1, u8 m2)
+ {
+ u16 v1 = this.Address(m1).read();
+ u16 v2 = this.Address(m2).read();
+
+ u16 result = (u16) (v1 | v2);
+ this.DX = result;
+ }
+ private void Instruction_INV(u8 obj)
+ {
+ var address = this.Address(obj);
+ u16 value = address.read();
+ u16 result = (u16) ~value;
+ this.DX = result;
+ }
+ private void Instruction_INV_DX()
+ {
+ this.DX = (u16) ~this.DX;
+ }
+ private void Instruction_AND(u8 m1, u8 m2)
+ {
+ u16 v1 = this.Address(m1).read();
+ u16 v2 = this.Address(m2).read();
+
+ u16 result = (u16) (v1 & v2);
+ this.DX = result;
+ }
+ private void Instruction_NOT(u8 obj)
+ {
+ u16 value = this.Address(obj).read();
+ u16 result = value == 0 ? (u16) 1 : (u16) 0;
+ this.LX = result;
+ }
+ private void Instruction_NOT_LX()
+ {
+ this.LX = this.LX == 0 ? (u16) 1 : (u16) 0;
+ }
+ private void Instruction_GT(u8 m1, u8 m2)
+ {
+ u16 v1 = this.Address(m1).read();
+ u16 v2 = this.Address(m2).read();
+
+ this.LX = (u16) (v1 > v2 ? 1 : 0);
+ }
+ private void Instruction_LT(u8 m1, u8 m2)
+ {
+ u16 v1 = this.Address(m1).read();
+ u16 v2 = this.Address(m2).read();
+
+ this.LX = (u16) (v1 < v2 ? 1 : 0);
+ }
+
+ private void Instruction_BR()
+ {
+ this.FetchInstruction(out _, out _, out _, out u8 length);
+ this.PC += length;
+ }
+ private void Instruction_BZ_LX()
+ {
+ if (this.LX == 0)
+ this.Instruction_BR();
+ }
+ private void Instruction_BZ(u8 obj)
+ {
+ u16 value = this.Address(obj).read();
+ if (value == 0)
+ this.Instruction_BR();
+ }
+ private void Instruction_CMP(u8 m1, u8 m2)
+ {
+ u16 v1 = this.Address(m1).read();
+ u16 v2 = this.Address(m2).read();
+
+ // Set LX based on comparison
+ this.LX = (u16) (v1 == v2 ? 1 : 0);
+ }
+ private void Instruction_MOV(u8 m1, u8 m2)
+ {
+ u16 value = this.Address(m1).read();
+ this.Address(m2).write(value);
+ }
+
+ private void Instruction_MOVB(u8 m1, u8 m2)
+ {
+ u16 value = this.Address(m1).read();
+ u8 low = Make.ref8(value)[1];
+ this.Address(m2).writeByte(low);
+ }
+
+ public void Instruction_NOP() { }
+ public void Instruction_NOP(u8 m1) { }
+ public void Instruction_NOP(u8 m1, u8 m2) { }
+
+ public void Instruction_JMP(u8 m1)
+ {
+ this.PC = this.Address(m1).read();
+ }
+
+ public void Instruction_RET()
+ {
+ this.PC = this.PopStack();
+ }
+
+ public void Instruction_CALL(u8 m1)
+ {
+ u16 address = this.Address(m1).read();
+ this.PushStack(this.PC);
+ this.PC = address;
+ }
+
+ public void Instruction_HALT()
+ {
+ this.AbortRequested = true;
+ }
+
+ public void Instruction_SUS()
+ {
+ this.PC -= 2;
+ }
+
+ public void Instruction_PUSH(u8 m1)
+ {
+ u16 value = this.Address(m1).read();
+ this.PushStack(value);
+ }
+
+ public void Instruction_POP(u8 m1)
+ {
+ u16 value = this.PopStack();
+ this.Address(m1).write(value);
+ }
+} \ No newline at end of file
diff --git a/Emulator/Chip/RamDevice.cs b/Emulator/Chip/RamDevice.cs
new file mode 100644
index 0000000..8d5b051
--- /dev/null
+++ b/Emulator/Chip/RamDevice.cs
@@ -0,0 +1,33 @@
+namespace Q1.Emulator.Chip;
+
+public class RamDevice : BusDevice
+{
+ public u8[] Memory;
+
+ public RamDevice(u16 start, u16 size) : base(start, (u16) (start + size))
+ {
+ this.Memory = new u8[size];
+ }
+
+ public override void Reset()
+ {
+ this.Memory = new u8[this.AddressableEnd - this.AddressableStart];
+ }
+
+ public override void Clock()
+ {
+ // No clocking needed for RAM
+ }
+
+ public override u8 Read(u16 address)
+ {
+ u16 relativeAddress = this.GetRelativeAddress(address);
+ return this.Memory[relativeAddress];
+ }
+
+ public override void Write(u16 address, u8 value)
+ {
+ u16 relativeAddress = this.GetRelativeAddress(address);
+ this.Memory[relativeAddress] = value;
+ }
+} \ No newline at end of file