diff options
| author | Melodi <62381002+Melodi17@users.noreply.github.com> | 2026-03-18 23:08:58 +1000 |
|---|---|---|
| committer | Melodi <62381002+Melodi17@users.noreply.github.com> | 2026-03-18 23:08:58 +1000 |
| commit | 7d20ff2a8e89e646fa79be69c8d07a512081daba (patch) | |
| tree | 67433c74d55f778065363010a593c44a0f65296b /Q1.tests/Core/Instructions | |
| parent | 0bb62fc65526a0981cd328dea1b09aba8ce0a576 (diff) | |
test: Add tests for all control instructions and base CPU implementation
Diffstat (limited to 'Q1.tests/Core/Instructions')
| -rw-r--r-- | Q1.tests/Core/Instructions/ControlInstructionTests.cs | 451 | ||||
| -rw-r--r-- | Q1.tests/Core/Instructions/InstructionTestBase.cs | 6 |
2 files changed, 457 insertions, 0 deletions
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 |
