diff options
| -rw-r--r-- | .gitignore | 4 | ||||
| -rw-r--r-- | Q1.core/Arch/Chip.cs | 75 | ||||
| -rw-r--r-- | Q1.core/Arch/Constants/ChipLayout.cs (renamed from Q1.core/Arch/ChipLayout.cs) | 2 | ||||
| -rw-r--r-- | Q1.core/Arch/Constants/ChipRegisters.cs | 19 | ||||
| -rw-r--r-- | Q1.core/Arch/InstructionEncoding.cs | 26 | ||||
| -rw-r--r-- | Q1.core/Arch/Lookups/AddressingModes.cs (renamed from Q1.core/Arch/AddressingModes.cs) | 15 | ||||
| -rw-r--r-- | Q1.core/Arch/Lookups/InstructionSet.cs (renamed from Q1.core/Arch/InstructionSet.cs) | 102 | ||||
| -rw-r--r-- | Q1.core/Exceptions.cs | 7 | ||||
| -rw-r--r-- | Q1.tests/Core/AddressingModesTests.cs | 4 | ||||
| -rw-r--r-- | Q1.tests/Core/ChipTestBase.cs | 20 | ||||
| -rw-r--r-- | Q1.tests/Core/ChipTests.cs | 87 | ||||
| -rw-r--r-- | Q1.tests/Core/InstructionEncodingTests.cs | 28 | ||||
| -rw-r--r-- | Q1.tests/Core/InstructionShapeTests.cs | 63 | ||||
| -rw-r--r-- | Q1.tests/Core/Instructions/ControlInstructionTests.cs | 451 | ||||
| -rw-r--r-- | Q1.tests/Core/Instructions/InstructionTestBase.cs | 6 | ||||
| -rw-r--r-- | Q1.tests/Core/InstructionsTests.cs | 20 | ||||
| -rw-r--r-- | Q1.tests/GlobalUsings.cs | 12 |
17 files changed, 844 insertions, 97 deletions
@@ -4,4 +4,6 @@ obj/ riderModule.iml /_ReSharper.Caches/ .idea/ -.config/
\ No newline at end of file +.config/ +*.sln.DotSettings +*.sln.DotSettings.user
\ No newline at end of file diff --git a/Q1.core/Arch/Chip.cs b/Q1.core/Arch/Chip.cs index 9f5f109..fef9cea 100644 --- a/Q1.core/Arch/Chip.cs +++ b/Q1.core/Arch/Chip.cs @@ -1,6 +1,8 @@ namespace Q1.core.Arch; using Components; +using Constants; +using Lookups; // Big endian public class Chip @@ -10,10 +12,15 @@ public class Chip public u16 Ax, Dx, Lx; private u16[] _registers; + + public Chip() + { + this.Reset(); + } public void Reset() { - this._registers = new u16[64]; + this._registers = new u16[ChipRegisters.GENERIC_REGISTER_COUNT]; this.Pc = ChipLayout.PROGRAM_START; this.Sp = ChipLayout.STACK_START; } @@ -23,23 +30,25 @@ public class Chip this.Bus.Clock(); this.Fetch(out u8 opcode, out u8 m1, out u8 m2, out bool word); + + var instruction = InstructionSet.Lookup[opcode]; switch (m1, m2) { case (0x00, 0x00): - InstructionSet.Lookup[opcode].Implicit.Execute(this, word); + instruction.Implicit.Execute(this, word); break; case (_, 0x00): - InstructionSet.Lookup[opcode].SimpleAddressing.Execute(this, m1, word); + instruction.SimpleAddressing.Execute(this, m1, word); break; case (0x00, 0x01): - InstructionSet.Lookup[opcode].ExtendedImplicit.Execute(this, word); + instruction.ExtendedImplicit.Execute(this, word); break; case (_, _): - InstructionSet.Lookup[opcode].ExtendedAddressing.Execute(this, m1, m2, word); + instruction.ExtendedAddressing.Execute(this, m1, m2, word); break; } } @@ -49,12 +58,8 @@ public class Chip { u16 instruction = this.Bus.ReadWord(this.Pc); this.Pc += 2; - - opcode = (u8) ((instruction >> 8) & 0xFE); // 128 opcodes - word = ((instruction >> 8) & 0x1) != 0; // flag for word mode - m1 = (u8) ((instruction >> 4) & 0x0F); // 16 modes - m2 = (u8) (instruction & 0x0F); // 16 modes + InstructionEncoding.Decode(instruction, out opcode, out m1, out m2, out word); } public void SkipInstruction() @@ -105,43 +110,44 @@ public class Chip public u16 GetRegister(u8 index) { - if (index < 64) return this._registers[index]; + if (index < ChipRegisters.GENERIC_REGISTER_COUNT) return this._registers[index]; - if (index == 0x40) return this.Ax; - if (index == 0x41) return (u16) (this.Ax >> 8); // High byte of Ax - if (index == 0x42) return (u16) (this.Ax & 0xFF); // Low byte of Ax + if (index == ChipRegisters.AX) return this.Ax; + if (index == ChipRegisters.AH) return (u16) (this.Ax >> 8); // High byte of Ax + if (index == ChipRegisters.AL) return (u16) (this.Ax & 0xFF); // Low byte of Ax - if (index == 0x44) return this.Dx; - if (index == 0x45) return (u16) (this.Dx >> 8); // High byte of Dx - if (index == 0x46) return (u16) (this.Dx & 0xFF); // Low byte of Dx + if (index == ChipRegisters.DX) return this.Dx; + if (index == ChipRegisters.DH) return (u16) (this.Dx >> 8); // High byte of Dx + if (index == ChipRegisters.DL) return (u16) (this.Dx & 0xFF); // Low byte of Dx - if (index == 0x48) return this.Lx; + if (index == ChipRegisters.LX) return this.Lx; - if (index == 0x4C) return this.Pc; - if (index == 0x4D) return this.Sp; + if (index == ChipRegisters.PC) return this.Pc; + if (index == ChipRegisters.SP) return this.Sp; throw new InvalidOperationException($"Invalid register index: {index}"); } public void SetRegister(u8 index, u16 value) { - if (index < 64) this._registers[index] = value; + if (index < ChipRegisters.GENERIC_REGISTER_COUNT) this._registers[index] = value; - else if (index == 0x40) this.Ax = value; - else if (index == 0x41) this.Ax = (u16)((this.Ax & 0x00FF) | ((value & 0xFF) << 8)); // Set high byte of Ax - else if (index == 0x42) this.Ax = (u16)((this.Ax & 0xFF00) | (value & 0xFF)); // Set low byte of Ax + else if (index == ChipRegisters.AX) this.Ax = value; + else if (index == ChipRegisters.AH) this.Ax = (u16)((this.Ax & 0x00FF) | ((value & 0xFF) << 8)); // Set high byte of Ax + else if (index == ChipRegisters.AL) this.Ax = (u16)((this.Ax & 0xFF00) | (value & 0xFF)); // Set low byte of Ax - else if (index == 0x44) this.Dx = value; - else if (index == 0x45) this.Dx = (u16)((this.Dx & 0x00FF) | ((value & 0xFF) << 8)); // Set high byte of Dx - else if (index == 0x46) this.Dx = (u16)((this.Dx & 0xFF00) | (value & 0xFF)); // Set low byte of Dx + else if (index == ChipRegisters.DX) this.Dx = value; + else if (index == ChipRegisters.DH) this.Dx = (u16)((this.Dx & 0x00FF) | ((value & 0xFF) << 8)); // Set high byte of Dx + else if (index == ChipRegisters.DL) this.Dx = (u16)((this.Dx & 0xFF00) | (value & 0xFF)); // Set low byte of Dx - else if (index == 0x48) this.Lx = value; + else if (index == ChipRegisters.LX) this.Lx = value; - else if (index == 0x4C) this.Pc = value; - else if (index == 0x4D) this.Sp = value; + else if (index == ChipRegisters.PC) this.Pc = value; + else if (index == ChipRegisters.SP) this.Sp = value; else throw new InvalidOperationException($"Invalid register index: {index}"); } + public void Interrupt(u16 code) { if (code >= ChipLayout.IVT_COUNT) @@ -151,4 +157,13 @@ public class Chip u16 interruptVectorAddress = (u16) (ChipLayout.IVT_START + code * 2); this.Pc = this.Bus.ReadWord(interruptVectorAddress); } + + public void RegisterInterruptHandler(u16 code, u16 handlerAddress) + { + if (code >= ChipLayout.IVT_COUNT) + throw new InvalidOperationException($"Invalid interrupt code: {code}"); + + u16 interruptVectorAddress = (u16) (ChipLayout.IVT_START + code * 2); + this.Bus.WriteWord(interruptVectorAddress, handlerAddress); + } }
\ No newline at end of file diff --git a/Q1.core/Arch/ChipLayout.cs b/Q1.core/Arch/Constants/ChipLayout.cs index 6c7187b..6990789 100644 --- a/Q1.core/Arch/ChipLayout.cs +++ b/Q1.core/Arch/Constants/ChipLayout.cs @@ -1,4 +1,4 @@ -namespace Q1.core.Arch; +namespace Q1.core.Arch.Constants; public static class ChipLayout { diff --git a/Q1.core/Arch/Constants/ChipRegisters.cs b/Q1.core/Arch/Constants/ChipRegisters.cs new file mode 100644 index 0000000..042ce32 --- /dev/null +++ b/Q1.core/Arch/Constants/ChipRegisters.cs @@ -0,0 +1,19 @@ +namespace Q1.core.Arch.Constants; + +public static class ChipRegisters +{ + public const u8 GENERIC_REGISTER_COUNT = 64; + + public const u8 AX = 0x40; + public const u8 AH = 0x41; + public const u8 AL = 0x42; + + public const u8 DX = 0x44; + public const u8 DH = 0x45; + public const u8 DL = 0x46; + + public const u8 LX = 0x48; + + public const u8 PC = 0x4C; + public const u8 SP = 0x4D; +}
\ No newline at end of file diff --git a/Q1.core/Arch/InstructionEncoding.cs b/Q1.core/Arch/InstructionEncoding.cs new file mode 100644 index 0000000..479d61f --- /dev/null +++ b/Q1.core/Arch/InstructionEncoding.cs @@ -0,0 +1,26 @@ +namespace Q1.core.Arch; + +public static class InstructionEncoding +{ + public static void Decode(u16 instruction, out u8 opcode, out u8 m1, out u8 m2, out bool word) + { + word = ((instruction >> 15) & 0x1) != 0; // top bit + opcode = (u8)((instruction >> 8) & 0x7F); // 7 clean bits + + m1 = (u8)((instruction >> 4) & 0x0F); + m2 = (u8)(instruction & 0x0F); + } + + public static u16 Encode(u8 opcode, u8 m1, u8 m2, bool word) + { + u16 instruction = 0; + + instruction |= (u16)((opcode & 0x7F) << 8); // 7-bit opcode + instruction |= (u16)((word ? 1 : 0) << 15); // top bit flag + + instruction |= (u16)((m1 & 0x0F) << 4); + instruction |= (u16)(m2 & 0x0F); + + return instruction; + } +}
\ No newline at end of file diff --git a/Q1.core/Arch/AddressingModes.cs b/Q1.core/Arch/Lookups/AddressingModes.cs index 96b4475..95e798d 100644 --- a/Q1.core/Arch/AddressingModes.cs +++ b/Q1.core/Arch/Lookups/AddressingModes.cs @@ -1,11 +1,22 @@ -namespace Q1.core.Arch; +namespace Q1.core.Arch.Lookups; -using Components; +using Q1.core.Components; public static class AddressingModes { public static AddressingMode[] Lookup = new AddressingMode[8]; + public static u8 GetAddressingMode(string name) + { + for (u8 i = 0; i < AddressingModes.Lookup.Length; i++) + { + if (AddressingModes.Lookup[i].Name == name) + return i; + } + + throw new ArgumentException($"Invalid addressing mode: {name}"); + } + static AddressingModes() { AddressingModes.Lookup[0x00] = new AddressingMode diff --git a/Q1.core/Arch/InstructionSet.cs b/Q1.core/Arch/Lookups/InstructionSet.cs index 212e77e..2ea20fc 100644 --- a/Q1.core/Arch/InstructionSet.cs +++ b/Q1.core/Arch/Lookups/InstructionSet.cs @@ -1,9 +1,25 @@ -namespace Q1.core.Arch; +namespace Q1.core.Arch.Lookups; public static class InstructionSet { public static readonly InstructionGroup[] Lookup = new InstructionGroup[128]; + public static u8 GetOpcode(string instructionName) + { + for (u8 opcode = 0; opcode < InstructionSet.Lookup.Length; opcode++) + { + var group = InstructionSet.Lookup[opcode]; + if (group.Implicit.Name == instructionName || + group.SimpleAddressing.Name == instructionName || + group.ExtendedImplicit.Name == instructionName || + group.ExtendedAddressing.Name == instructionName) + { + return opcode; + } + } + throw new ArgumentException($"Instruction '{instructionName}' not found in the instruction set."); + } + static InstructionSet() { var nop = new ImplicitInstruction { Name = "NOP", Execute = InstructionSet.Nop }; @@ -173,85 +189,85 @@ public static class InstructionSet Implicit = nop, SimpleAddressing = nopSimple, ExtendedImplicit = nop, - ExtendedAddressing = new ExtendedInstruction { Name = "DIV", Execute = InstructionSet.Modulo } + ExtendedAddressing = new ExtendedInstruction { Name = "MOD", Execute = InstructionSet.Modulo } }; #endregion } - private static void Nop(Chip chip, bool word) { /* No operation */ } - private static void Nop(Chip chip, u8 m1, bool word) { /* No operation */ } - private static void Nop(Chip chip, u8 m1, u8 m2, bool word) { /* No operation */ } + public static void Nop(Chip chip, bool word) { /* No operation */ } + public static void Nop(Chip chip, u8 m1, bool word) { /* No operation */ } + public static void Nop(Chip chip, u8 m1, u8 m2, bool word) { /* No operation */ } #region Control Instructions - private static void Halt(Chip chip, bool word) + public static void Halt(Chip chip, bool word) { - throw new NotImplementedException("HALT instruction is not implemented yet."); + throw new HaltException(); } - private static void Suspend(Chip chip, bool word) + public static void Suspend(Chip chip, bool word) { throw new NotImplementedException("SUS instruction is not implemented yet."); } - private static void Interrupt(Chip chip, u8 m1, bool word) + public static void Interrupt(Chip chip, u8 m1, bool word) { u16 interruptNumber = chip.Load(m1, word); chip.Interrupt(interruptNumber); } - private static void Move(Chip chip, u8 m1, u8 m2, bool word) + public static void Move(Chip chip, u8 m1, u8 m2, bool word) { u16 value = chip.Load(m1, word); chip.Store(m2, value, word); } - private static void Jump(Chip chip, u8 m1, bool word) + public static void Jump(Chip chip, u8 m1, bool word) { u16 address = chip.Load(m1, word); chip.Pc = address; } - private static void Call(Chip chip, u8 m1, bool word) + public static void Call(Chip chip, u8 m1, bool word) { u16 targetAddress = chip.Load(m1, word); chip.Push(chip.Pc); chip.Pc = targetAddress; } - private static void Return(Chip chip, bool word) + public static void Return(Chip chip, bool word) { u16 returnAddress = chip.Pop(); chip.Pc = returnAddress; } - private static void BranchIfZero(Chip chip, u8 m1, bool word) + public static void BranchIfZero(Chip chip, u8 m1, bool word) { u16 value = chip.Load(m1, word); if (value == 0) chip.SkipInstruction(); } - private static void BranchIfZeroLx(Chip chip, bool word) + public static void BranchIfZeroLx(Chip chip, bool word) { if (chip.Lx == 0) chip.SkipInstruction(); } - private static void Branch(Chip chip, bool word) + public static void Branch(Chip chip, bool word) { chip.SkipInstruction(); } - private static void Push(Chip chip, u8 m1, bool word) + public static void Push(Chip chip, u8 m1, bool word) { u16 value = chip.Load(m1, word); chip.Push(value); } - private static void Pop(Chip chip, u8 m1, bool word) + public static void Pop(Chip chip, u8 m1, bool word) { u16 value = chip.Pop(); chip.Store(m1, value, word); @@ -261,7 +277,7 @@ public static class InstructionSet #region Data Instructions - private static void Inverse(Chip chip, u8 m1, bool word) + public static void Inverse(Chip chip, u8 m1, bool word) { u16 value = chip.Load(m1, word); value = (u16)~value; @@ -269,12 +285,12 @@ public static class InstructionSet chip.Dx = value; } - private static void InverseDx(Chip chip, bool word) + public static void InverseDx(Chip chip, bool word) { chip.Dx = (u16)~chip.Dx; } - private static void And(Chip chip, u8 m1, u8 m2, bool word) + public static void And(Chip chip, u8 m1, u8 m2, bool word) { u16 value1 = chip.Load(m1, word); u16 value2 = chip.Load(m2, word); @@ -283,7 +299,7 @@ public static class InstructionSet chip.Dx = result; } - private static void Or(Chip chip, u8 m1, u8 m2, bool word) + public static void Or(Chip chip, u8 m1, u8 m2, bool word) { u16 value1 = chip.Load(m1, word); u16 value2 = chip.Load(m2, word); @@ -292,7 +308,7 @@ public static class InstructionSet chip.Dx = result; } - private static void Xor(Chip chip, u8 m1, u8 m2, bool word) + public static void Xor(Chip chip, u8 m1, u8 m2, bool word) { u16 value1 = chip.Load(m1, word); u16 value2 = chip.Load(m2, word); @@ -301,7 +317,7 @@ public static class InstructionSet chip.Dx = result; } - private static void ShiftLeft(Chip chip, u8 m1, u8 m2, bool word) + public static void ShiftLeft(Chip chip, u8 m1, u8 m2, bool word) { u16 value = chip.Load(m1, word); u16 shiftAmount = (u16) (chip.Load(m2, word) & 0xF); // Limit shift to 0-15 @@ -310,7 +326,7 @@ public static class InstructionSet chip.Dx = result; } - private static void ShiftRight(Chip chip, u8 m1, u8 m2, bool word) + public static void ShiftRight(Chip chip, u8 m1, u8 m2, bool word) { u16 value = chip.Load(m1, word); u16 shiftAmount = (u16) (chip.Load(m2, word) & 0xF); // Limit shift to 0-15 @@ -319,7 +335,7 @@ public static class InstructionSet chip.Dx = result; } - private static void ShiftPlaceLeft(Chip chip, u8 m1, bool word) + public static void ShiftPlaceLeft(Chip chip, u8 m1, bool word) { u16 value = chip.Load(m1, word); u16 result = (u16) ((value << 1) | (value >> 15)); // Rotate left @@ -327,7 +343,7 @@ public static class InstructionSet chip.Dx = result; } - private static void ShiftPlaceRight(Chip chip, u8 m1, bool word) + public static void ShiftPlaceRight(Chip chip, u8 m1, bool word) { u16 value = chip.Load(m1, word); u16 result = (u16) ((value >> 1) | (value << 15)); // Rotate right @@ -335,7 +351,7 @@ public static class InstructionSet chip.Dx = result; } - private static void ShiftPlaceLeftDx(Chip chip, bool word) + public static void ShiftPlaceLeftDx(Chip chip, bool word) { u16 value = chip.Dx; u16 result = (u16) ((value << 1) | (value >> 15)); // Rotate left @@ -343,7 +359,7 @@ public static class InstructionSet chip.Dx = result; } - private static void ShiftPlaceRightDx(Chip chip, bool word) + public static void ShiftPlaceRightDx(Chip chip, bool word) { u16 value = chip.Dx; u16 result = (u16) ((value >> 1) | (value << 15)); // Rotate right @@ -355,32 +371,32 @@ public static class InstructionSet #region Logic Instructions - private static void Not(Chip chip, u8 m1, bool word) + public static void Not(Chip chip, u8 m1, bool word) { u16 value = chip.Load(m1, word); chip.Lx = value == 0 ? (u16)1 : (u16)0; } - private static void NotLx(Chip chip, bool word) + public static void NotLx(Chip chip, bool word) { chip.Lx = chip.Lx == 0 ? (u16)1 : (u16)0; } - private static void Compare(Chip chip, u8 m1, u8 m2, bool word) + public static void Compare(Chip chip, u8 m1, u8 m2, bool word) { u16 value1 = chip.Load(m1, word); u16 value2 = chip.Load(m2, word); chip.Lx = value1 == value2 ? (u16)1 : (u16)0; } - private static void LessThan(Chip chip, u8 m1, u8 m2, bool word) + public static void LessThan(Chip chip, u8 m1, u8 m2, bool word) { u16 value1 = chip.Load(m1, word); u16 value2 = chip.Load(m2, word); chip.Lx = value1 < value2 ? (u16)1 : (u16)0; } - private static void GreaterThan(Chip chip, u8 m1, u8 m2, bool word) + public static void GreaterThan(Chip chip, u8 m1, u8 m2, bool word) { u16 value1 = chip.Load(m1, word); u16 value2 = chip.Load(m2, word); @@ -391,31 +407,31 @@ public static class InstructionSet #region Arithmetic Instructions - private static void Increment(Chip chip, u8 m1, bool word) + public static void Increment(Chip chip, u8 m1, bool word) { u16 value = chip.Load(m1, word); value++; chip.Store(m1, value, word); } - private static void IncrementAx(Chip chip, bool word) + public static void IncrementAx(Chip chip, bool word) { chip.Ax++; } - private static void Decrement(Chip chip, u8 m1, bool word) + public static void Decrement(Chip chip, u8 m1, bool word) { u16 value = chip.Load(m1, word); value--; chip.Store(m1, value, word); } - private static void DecrementAx(Chip chip, bool word) + public static void DecrementAx(Chip chip, bool word) { chip.Ax--; } - private static void Add(Chip chip, u8 m1, u8 m2, bool word) + public static void Add(Chip chip, u8 m1, u8 m2, bool word) { u16 value1 = chip.Load(m1, word); u16 value2 = chip.Load(m2, word); @@ -424,7 +440,7 @@ public static class InstructionSet chip.Ax = result; } - private static void Subtract(Chip chip, u8 m1, u8 m2, bool word) + public static void Subtract(Chip chip, u8 m1, u8 m2, bool word) { u16 value1 = chip.Load(m1, word); u16 value2 = chip.Load(m2, word); @@ -433,7 +449,7 @@ public static class InstructionSet chip.Ax = result; } - private static void Multiply(Chip chip, u8 m1, u8 m2, bool word) + public static void Multiply(Chip chip, u8 m1, u8 m2, bool word) { u16 value1 = chip.Load(m1, word); u16 value2 = chip.Load(m2, word); @@ -442,7 +458,7 @@ public static class InstructionSet chip.Ax = result; } - private static void Divide(Chip chip, u8 m1, u8 m2, bool word) + public static void Divide(Chip chip, u8 m1, u8 m2, bool word) { u16 value1 = chip.Load(m1, word); u16 value2 = chip.Load(m2, word); @@ -453,7 +469,7 @@ public static class InstructionSet chip.Ax = result; } - private static void Modulo(Chip chip, u8 m1, u8 m2, bool word) + public static void Modulo(Chip chip, u8 m1, u8 m2, bool word) { u16 value1 = chip.Load(m1, word); u16 value2 = chip.Load(m2, word); diff --git a/Q1.core/Exceptions.cs b/Q1.core/Exceptions.cs new file mode 100644 index 0000000..fa5156f --- /dev/null +++ b/Q1.core/Exceptions.cs @@ -0,0 +1,7 @@ +namespace Q1.core; + +public class ChipException(string message) : Exception(message); + +public class InvalidInstructionException(u8 opcode) : ChipException($"Invalid instruction: 0x{opcode:X2}"); +public class InvalidAddressingModeException(u8 mode) : ChipException($"Invalid addressing mode: 0x{mode:X2}"); +public class HaltException() : ChipException("Execution halted");
\ No newline at end of file diff --git a/Q1.tests/Core/AddressingModesTests.cs b/Q1.tests/Core/AddressingModesTests.cs index 19be64d..058e373 100644 --- a/Q1.tests/Core/AddressingModesTests.cs +++ b/Q1.tests/Core/AddressingModesTests.cs @@ -2,6 +2,7 @@ namespace Q1.tests.Core; using core; using core.Arch; +using core.Arch.Lookups; [TestFixture] public class AddressingModesTests @@ -16,5 +17,8 @@ public class AddressingModesTests Assert.That(populatedModes, Has.Length.EqualTo(8), "Expected 8 addressing modes to be defined"); + + Assert.That(AddressingModes.Lookup.Length, Is.EqualTo(8), + "Expected addressing mode lookup to have 8 entries"); } }
\ No newline at end of file diff --git a/Q1.tests/Core/ChipTestBase.cs b/Q1.tests/Core/ChipTestBase.cs new file mode 100644 index 0000000..3540954 --- /dev/null +++ b/Q1.tests/Core/ChipTestBase.cs @@ -0,0 +1,20 @@ +namespace Q1.tests.Core; + +using Q1.core.Arch; +using Q1.core.Arch.Constants; +using Q1.core.Components; + +public abstract class ChipTestBase +{ + protected Chip CreateChip() + { + var chip = new Chip(); + + var memory = new Memory(ChipLayout.MEMORY_START, ChipLayout.MEMORY_SIZE); + var bus = new Bus(0x0, u16.MaxValue); + bus.MountDevice(memory); + + bus.AttachChip(chip); + return chip; + } +}
\ No newline at end of file diff --git a/Q1.tests/Core/ChipTests.cs b/Q1.tests/Core/ChipTests.cs new file mode 100644 index 0000000..2b0d20f --- /dev/null +++ b/Q1.tests/Core/ChipTests.cs @@ -0,0 +1,87 @@ +namespace Q1.tests.Core; + +using core.Arch; +using core.Arch.Constants; + +[TestFixture] +public class ChipTests : ChipTestBase +{ + [TestCase(ChipRegisters.AX)] + [TestCase(ChipRegisters.DX)] + [TestCase(ChipRegisters.LX)] + public void Register_ShouldRoundTrip(u8 register) + { + var chip = this.CreateChip(); + + u16 value = (u16) (register * 0x1111); // unique value for each register + chip.SetRegister(register, value); + u16 readValue = chip.GetRegister(register); + + Assert.That(readValue, Is.EqualTo(value), $"Register {register:X2} should round trip"); + } + + [Test] + public void PushPop_ShouldRoundTrip() + { + var chip = this.CreateChip(); + + u16 value = 0xABCD; + chip.Push(value); + u16 poppedValue = chip.Pop(); + + Assert.That(poppedValue, Is.EqualTo(value), "Popped value should match pushed value"); + } + + [Test] + public void StackOverflow_ShouldThrow() + { + var chip = this.CreateChip(); + + // Fill the stack to its limit + for (int i = 0; i < ChipLayout.STACK_COUNT; i++) + chip.Push((u16) i); + + // Next push should cause overflow + Assert.Throws<InvalidOperationException>(() => chip.Push(0xFFFF), "Expected stack overflow to throw"); + } + + [Test] + public void StackUnderflow_ShouldThrow() + { + var chip = this.CreateChip(); + + // Attempt to pop from an empty stack + Assert.Throws<InvalidOperationException>(() => chip.Pop(), "Expected stack underflow to throw"); + } + + [Test] + public void InvalidRegisterAccess_ShouldThrow() + { + var chip = this.CreateChip(); + + // Attempt to access an invalid register index + Assert.Throws<InvalidOperationException>(() => chip.GetRegister(0xFF), "Expected invalid register access to throw"); + Assert.Throws<InvalidOperationException>(() => chip.SetRegister(0xFF, 0x1234), "Expected invalid register access to throw"); + } + + [Test] + public void InterruptHandling_ShouldJumpToInterruptVector() + { + var chip = this.CreateChip(); + + var originalPc = chip.Pc; + + // Set up an interrupt vector at 0xFFFE + u16 interruptHandlerAddress = 0x1234; + chip.RegisterInterruptHandler(8, interruptHandlerAddress); // Register handler for interrupt 8 + + // Trigger the interrupt + chip.Interrupt(8); + + Assert.Multiple(() => + { + Assert.That(chip.Pc, Is.EqualTo(interruptHandlerAddress), "PC should jump to interrupt handler address"); + Assert.That(chip.Pop(), Is.EqualTo(originalPc), "PC should be pushed onto the stack during interrupt"); + }); + } +}
\ No newline at end of file diff --git a/Q1.tests/Core/InstructionEncodingTests.cs b/Q1.tests/Core/InstructionEncodingTests.cs new file mode 100644 index 0000000..7bb653f --- /dev/null +++ b/Q1.tests/Core/InstructionEncodingTests.cs @@ -0,0 +1,28 @@ +namespace Q1.tests.Core; + +using core.Arch; + +[TestFixture] +public class InstructionEncodingTests +{ + [Test] + public void EncodeDecode_ShouldRoundTrip() + { + u8 opcode = 0x01; + u8 m1 = 0x5; + u8 m2 = 0xC; + bool word = true; + + u16 instruction = InstructionEncoding.Encode(opcode, m1, m2, word); + + InstructionEncoding.Decode(instruction, out var op2, out var m1_2, out var m2_2, out var word2); + + Assert.Multiple(() => + { + Assert.That(op2, Is.EqualTo(opcode)); + Assert.That(m1_2, Is.EqualTo(m1)); + Assert.That(m2_2, Is.EqualTo(m2)); + Assert.That(word2, Is.EqualTo(word)); + }); + } +}
\ No newline at end of file diff --git a/Q1.tests/Core/InstructionShapeTests.cs b/Q1.tests/Core/InstructionShapeTests.cs new file mode 100644 index 0000000..969ba7e --- /dev/null +++ b/Q1.tests/Core/InstructionShapeTests.cs @@ -0,0 +1,63 @@ +namespace Q1.tests.Core; + +using core; +using core.Arch; +using core.Arch.Lookups; + +[TestFixture] +public class InstructionShapeTests +{ + + [Test] + public void LookupShouldHaveInstructions() + { + var populatedSets = InstructionSet.Lookup + .Where(x => x != default) + .ToArray(); + + Assert.That(populatedSets, Has.Length.EqualTo(19), + "Expected 19 instruction groups to be defined"); + + Assert.That(InstructionSet.Lookup.Length, Is.EqualTo(128), + "Expected instruction lookup to have 128 entries"); + } + + [TestCase(0x00, "NOP", "JMP", "HALT", "MOV")] + [TestCase(0x01, "RET", "CALL", "SUS", "NOP")] + [TestCase(0x02, "BZ(LX)", "BZ", "BR", "NOP")] + [TestCase(0x03, "NOP", "PUSH", "NOP", "NOP")] + [TestCase(0x04, "NOP", "POP", "NOP", "NOP")] + [TestCase(0x05, "NOP", "INT", "NOP", "NOP")] + + [TestCase(0x10, "INV(DX)", "INV", "NOP", "AND")] + [TestCase(0x11, "NOP", "NOP", "NOP", "OR")] + [TestCase(0x12, "NOP", "NOP", "NOP", "XOR")] + [TestCase(0x13, "SHPL(DX)", "SHPL", "NOP", "SHL")] + [TestCase(0x14, "SHPR(DX)", "SHPR", "NOP", "SHR")] + + [TestCase(0x20, "NOT(LX)", "NOT", "NOP", "CMP")] + [TestCase(0x21, "NOP", "NOP", "NOP", "LT")] + [TestCase(0x22, "NOP", "NOP", "NOP", "GT")] + + [TestCase(0x30, "INC(AX)", "INC", "NOP", "ADD")] + [TestCase(0x31, "DEC(AX)", "DEC", "NOP", "SUB")] + [TestCase(0x32, "NOP", "NOP", "NOP", "DIV")] + [TestCase(0x33, "NOP", "NOP", "NOP", "MUL")] + [TestCase(0x34, "NOP", "NOP", "NOP", "MOD")] + public void GroupLooksLike(i32 opcode, string impl, string simple, string extendedImpl, string extended) + { + var group = InstructionSet.Lookup[opcode]; + + Assert.That(group.Implicit.Name, Is.EqualTo(impl), + $"Expected implicit instruction for opcode {opcode:X2} to be {impl}"); + + Assert.That(group.SimpleAddressing.Name, Is.EqualTo(simple), + $"Expected simple addressing instruction for opcode {opcode:X2} to be {simple}"); + + Assert.That(group.ExtendedImplicit.Name, Is.EqualTo(extendedImpl), + $"Expected extended implicit instruction for opcode {opcode:X2} to be {extendedImpl}"); + + Assert.That(group.ExtendedAddressing.Name, Is.EqualTo(extended), + $"Expected extended addressing instruction for opcode {opcode:X2} to be {extended}"); + } +}
\ No newline at end of file diff --git a/Q1.tests/Core/Instructions/ControlInstructionTests.cs b/Q1.tests/Core/Instructions/ControlInstructionTests.cs new file mode 100644 index 0000000..00c9566 --- /dev/null +++ b/Q1.tests/Core/Instructions/ControlInstructionTests.cs @@ -0,0 +1,451 @@ +namespace Q1.tests.Core.Instructions; + +using core; +using core.Arch; +using core.Arch.Constants; +using core.Arch.Lookups; +using core.Components; + +public class ControlInstructionTests : InstructionTestBase +{ + [Test] + public void JumpInstruction_JumpsToAddress() + { + // Arrange + var chip = CreateChip(); + + var instruction = InstructionEncoding + .Encode( + InstructionSet.GetOpcode("JMP"), + AddressingModes.GetAddressingMode("Immediate"), + 0, + true); + + u16 jumpAddress = 0x1234; + + chip.Bus.WriteWord(chip.Pc, instruction); + chip.Bus.WriteWord((u16) (chip.Pc + 2), jumpAddress); + + // Act + chip.Clock(); + + // Assert + Assert.That( + chip.Pc, + Is.EqualTo(jumpAddress), + "PC should jump to the specified address"); + } + + [Test] + public void HaltInstruction_HaltsExecution() + { + // Arrange + var chip = CreateChip(); + + var instruction = InstructionEncoding + .Encode( + InstructionSet.GetOpcode("HALT"), + 0, + 1, + true); + + chip.Bus.WriteWord(chip.Pc, instruction); + + // Act + var clock = () => chip.Clock(); + + // Assert + Assert.That(clock, Throws.TypeOf<HaltException>()); + } + + + [Test] + public void ReturnInstruction_ReturnsFromSubroutine() + { + // Arrange + var chip = CreateChip(); + + // Simulate a call by pushing a return address onto the stack + u16 returnAddress = 0x5678; + chip.Push(returnAddress); + + var instruction = InstructionEncoding + .Encode( + InstructionSet.GetOpcode("RET"), + 0, + 0, + true); + + chip.Bus.WriteWord(chip.Pc, instruction); + + // Act + chip.Clock(); + + // Assert + Assert.That( + chip.Pc, + Is.EqualTo(returnAddress), + "PC should return to the address on top of the stack"); + } + + + [Test] + public void CallInstruction_CallsSubroutine() + { + // Arrange + var chip = CreateChip(); + + u16 pc = chip.Pc; + u16 subroutineAddress = 0x9ABC; + + var instruction = InstructionEncoding + .Encode( + InstructionSet.GetOpcode("CALL"), + AddressingModes.GetAddressingMode("Immediate"), + 0, + true); + + chip.Bus.WriteWord(pc, instruction); + chip.Bus.WriteWord((u16) (pc + 2), subroutineAddress); + + u16 expectedReturnAddress = (u16) (pc + 4); + + // Act + chip.Clock(); + + // Assert + Assert.Multiple(() => + { + Assert.That( + chip.Pc, + Is.EqualTo(subroutineAddress), + "PC should jump to the subroutine address"); + + Assert.That( + chip.Bus.ReadWord(ChipLayout.STACK_START), + Is.EqualTo(expectedReturnAddress), + "Return address should be pushed onto the stack"); + }); + } + + + [Test] + public void SuspendInstruction_Fails() + { + // Arrange + var chip = CreateChip(); + + var instruction = InstructionEncoding + .Encode( + InstructionSet.GetOpcode("SUS"), + 0, + 1, + true); + + chip.Bus.WriteWord(chip.Pc, instruction); + + // Act + var clock = () => chip.Clock(); + + // Assert + Assert.That(clock, Throws.TypeOf<NotImplementedException>()); + } + + + [TestCase(0, true)] + [TestCase(1, false)] + [TestCase(5, false)] + public void BranchIfZeroLx_BranchesCorrectly(i32 lxValue, bool shouldBranch) + { + // Arrange + var chip = CreateChip(); + + u16 pc = chip.Pc; + + var instruction = InstructionEncoding + .Encode( + InstructionSet.GetOpcode("BZ(LX)"), + 0, + 0, + true); + + chip.Bus.WriteWord(pc, instruction); + chip.Lx = (u16) lxValue; + + u16 nextInstructionAddress = (u16) (pc + 2); // Address of the next instruction + u16 branchAddress = (u16) (nextInstructionAddress + 2); // Finish current instruction and skip next one + + // Act + chip.Clock(); + + // Assert + if (shouldBranch) + { + Assert.That( + chip.Pc, + Is.EqualTo(branchAddress), + "PC should skip the next instruction when Lx is zero"); + } + else + { + Assert.That( + chip.Pc, + Is.EqualTo(nextInstructionAddress), + "PC should proceed to the next instruction when Lx is non-zero"); + } + } + + [TestCase(0, true)] + [TestCase(1, false)] + [TestCase(5, false)] + public void BranchIfZero_BranchesCorrectly(int value, bool shouldBranch) + { + // Arrange + var chip = CreateChip(); + + u16 pc = chip.Pc; + + var instruction = InstructionEncoding + .Encode( + InstructionSet.GetOpcode("BZ"), + AddressingModes.GetAddressingMode("Immediate"), + 0, + true); + + chip.Bus.WriteWord(pc, instruction); + chip.Bus.WriteWord((u16) (pc + 2), (u16) value); + + u16 nextInstructionAddress = (u16) (pc + 4); // Address of the instruction after the next one + u16 branchAddress = (u16) (nextInstructionAddress + 2); // Finish current instruction and skip next one + + // Act + chip.Clock(); + + // Assert + if (shouldBranch) + { + Assert.That( + chip.Pc, + Is.EqualTo(branchAddress), + "PC should skip the next instruction when the value is zero"); + } + else + { + Assert.That( + chip.Pc, + Is.EqualTo(nextInstructionAddress), + "PC should proceed to the next instruction when the value is non-zero"); + } + } + + [Test] + public void Branch_SkipsNextInstruction() + { + // Arrange + var chip = CreateChip(); + + u16 pc = chip.Pc; + + var instruction = InstructionEncoding + .Encode( + InstructionSet.GetOpcode("BR"), + 0, + 0, + true); + + chip.Bus.WriteWord(pc, instruction); + + u16 nextInstructionAddress = (u16) (pc + 2); // Address of the next instruction + u16 branchAddress = (u16) (nextInstructionAddress + 2); // Finish current instruction and skip next one + + // Act + chip.Clock(); + + // Assert + Assert.That( + chip.Pc, + Is.EqualTo(branchAddress), + "PC should skip the next instruction unconditionally"); + } + + [Test] + public void PushInstruction_PushesValueOntoStack() + { + // Arrange + var chip = CreateChip(); + + u16 pc = chip.Pc; + u16 valueToPush = 0xABCD; + + var instruction = InstructionEncoding + .Encode( + InstructionSet.GetOpcode("PUSH"), + AddressingModes.GetAddressingMode("Immediate"), + 0, + true); + + chip.Bus.WriteWord(pc, instruction); + chip.Bus.WriteWord((u16) (pc + 2), valueToPush); + + // Act + chip.Clock(); + + // Assert + Assert.Multiple(() => + { + Assert.That( + chip.Bus.ReadWord(ChipLayout.STACK_START), + Is.EqualTo(valueToPush), + "Value should be pushed onto the stack"); + + Assert.That( + chip.Sp, + Is.EqualTo((u16) (ChipLayout.STACK_START + 2)), + "Stack pointer should move up by 2 bytes after push"); + }); + } + + [Test] + public void PopInstruction_PopsValueFromStack() + { + // Arrange + var chip = CreateChip(); + + u16 pc = chip.Pc; + u16 valueToPop = 0xABCD; + + // Simulate a push by writing a value to the stack and moving the stack pointer + chip.Bus.WriteWord(ChipLayout.STACK_START, valueToPop); + chip.Sp = ChipLayout.STACK_START + 2; + + var instruction = InstructionEncoding + .Encode( + InstructionSet.GetOpcode("POP"), + AddressingModes.GetAddressingMode("Register"), + 0, + true); + + chip.Bus.WriteWord(pc, instruction); + chip.Bus.Write((u16) (pc + 2), ChipRegisters.DX); + + // Act + chip.Clock(); + + // Assert + Assert.Multiple(() => + { + Assert.That( + chip.Dx, + Is.EqualTo(valueToPop), + "Value should be popped from the stack into the register"); + + Assert.That( + chip.Sp, + Is.EqualTo(ChipLayout.STACK_START), + "Stack pointer should move down by 2 bytes after pop"); + }); + } + + [Test] + public void PushPopSequence_WorksCorrectly() + { + // Arrange + var chip = CreateChip(); + + u16[] valuesToPush = [0x1111, 0x2222, 0x3333]; + + void PushValue(u16 value) + { + var instruction = InstructionEncoding + .Encode( + InstructionSet.GetOpcode("PUSH"), + AddressingModes.GetAddressingMode("Immediate"), + 0, + true); + + chip.Bus.WriteWord(chip.Pc, instruction); + chip.Bus.WriteWord((u16) (chip.Pc + 2), value); + chip.Clock(); + } + + u16 PopValue() + { + var instruction = InstructionEncoding + .Encode( + InstructionSet.GetOpcode("POP"), + AddressingModes.GetAddressingMode("Register"), + 0, + true); + + chip.Bus.WriteWord(chip.Pc, instruction); + chip.Bus.Write((u16) (chip.Pc + 2), ChipRegisters.DX); + + chip.Clock(); + + return chip.Dx; + } + + // Act & Assert + // Push 1 item, then pop it, then push 2 items, then pop them + Assert.Multiple(() => + { + PushValue(valuesToPush[0]); + Assert.That( + PopValue(), + Is.EqualTo(valuesToPush[0]), + "First popped value should match the first pushed value"); + + PushValue(valuesToPush[1]); + PushValue(valuesToPush[2]); + Assert.That( + PopValue(), + Is.EqualTo(valuesToPush[2]), + "Second popped value should match the second pushed value"); + Assert.That( + PopValue(), + Is.EqualTo(valuesToPush[1]), + "Third popped value should match the first pushed value"); + }); + } + + [Test] + public void InterruptInstruction_TriggersInterrupt() + { + // Arrange + var chip = CreateChip(); + + var instruction = InstructionEncoding + .Encode( + InstructionSet.GetOpcode("INT"), + AddressingModes.GetAddressingMode("Immediate"), + 0, + true); + + u8 interruptNumber = 8; + + chip.Bus.WriteWord(chip.Pc, instruction); + chip.Bus.WriteWord((u16) (chip.Pc + 2), interruptNumber); + + u16 expectedReturnPc = (u16) (chip.Pc + 4); // After the INT instruction and its operand + u16 handlerAddress = 0x2345; + + chip.RegisterInterruptHandler(interruptNumber, handlerAddress); + + // Act + chip.Clock(); + + // Assert + Assert.Multiple(() => + { + Assert.That( + chip.Pc, + Is.EqualTo(handlerAddress), + "PC should jump to the interrupt handler address"); + + Assert.That( + chip.Bus.ReadWord(ChipLayout.STACK_START), + Is.EqualTo(expectedReturnPc), + "PC should be pushed onto the stack during interrupt"); + }); + } +}
\ No newline at end of file diff --git a/Q1.tests/Core/Instructions/InstructionTestBase.cs b/Q1.tests/Core/Instructions/InstructionTestBase.cs new file mode 100644 index 0000000..e0cdad4 --- /dev/null +++ b/Q1.tests/Core/Instructions/InstructionTestBase.cs @@ -0,0 +1,6 @@ +namespace Q1.tests.Core.Instructions; + +public abstract class InstructionTestBase : ChipTestBase +{ + +}
\ No newline at end of file diff --git a/Q1.tests/Core/InstructionsTests.cs b/Q1.tests/Core/InstructionsTests.cs deleted file mode 100644 index 45c85ee..0000000 --- a/Q1.tests/Core/InstructionsTests.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Q1.tests.Core; - -using core; -using core.Arch; - -[TestFixture] -public class InstructionsTests -{ - - [Test] - public void LookupShouldHaveInstructions() - { - var populatedSets = InstructionSet.Lookup - .Where(x => x != default) - .ToArray(); - - Assert.That(populatedSets, Has.Length.EqualTo(19), - "Expected 19 instruction groups to be defined"); - } -}
\ No newline at end of file diff --git a/Q1.tests/GlobalUsings.cs b/Q1.tests/GlobalUsings.cs new file mode 100644 index 0000000..aca802e --- /dev/null +++ b/Q1.tests/GlobalUsings.cs @@ -0,0 +1,12 @@ +// Integer type aliases +global using i8 = sbyte; // -128 to 127 +global using u8 = byte; // 0 to 255 +global using i16 = short; // -32,768 to 32,767 +global using u16 = ushort; // 0 to 65,535 +global using i32 = int; // -2,147,483,648 to 2,147,483,647 +global using u32 = uint; // 0 to 4,294,967,295 +global using i64 = long; // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 +global using u64 = ulong; // 0 to 18,446,744,073,709,551,615 + +global using ref8 = System.Span<byte>; +global using data8 = byte[];
\ No newline at end of file |
