From b427fac800a144766875dbe1680fc446ff283532 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= <argmaster.world@gmail.com> Date: Mon, 4 Nov 2024 13:00:56 +0100 Subject: [PATCH] Add MicroOp Mov tests --- src/Bytom.Hardware.Tests/CPU/CoreTests.cs | 6 + src/Bytom.Hardware.Tests/CPU/MicroOpTests.cs | 191 +++++++++++++++++++ src/Bytom.Hardware/CPU/Core.cs | 149 +++++++-------- src/Bytom.Hardware/CPU/MicoOp.cs | 15 +- src/Bytom.Hardware/CPU/Register.cs | 34 +++- src/Bytom.Hardware/IoController.cs | 2 +- src/Bytom.Tools/Itertools.cs | 10 + 7 files changed, 312 insertions(+), 95 deletions(-) create mode 100644 src/Bytom.Hardware.Tests/CPU/CoreTests.cs create mode 100644 src/Bytom.Hardware.Tests/CPU/MicroOpTests.cs diff --git a/src/Bytom.Hardware.Tests/CPU/CoreTests.cs b/src/Bytom.Hardware.Tests/CPU/CoreTests.cs new file mode 100644 index 0000000..6f44f4a --- /dev/null +++ b/src/Bytom.Hardware.Tests/CPU/CoreTests.cs @@ -0,0 +1,6 @@ +namespace Bytom.Hardware.Tests +{ + public class CoreTests + { + } +} \ No newline at end of file diff --git a/src/Bytom.Hardware.Tests/CPU/MicroOpTests.cs b/src/Bytom.Hardware.Tests/CPU/MicroOpTests.cs new file mode 100644 index 0000000..e54cd80 --- /dev/null +++ b/src/Bytom.Hardware.Tests/CPU/MicroOpTests.cs @@ -0,0 +1,191 @@ +using Bytom.Assembler.Nodes; +using Bytom.Assembler.Operands; +using Bytom.Tools; + +namespace Bytom.Hardware.CPU.Tests +{ + public class InstructionDecodeTests + { + Core? core; + + [SetUp] + public void Setup() + { + core = new Core(0, 1000); + } + + [Test] + public void TestDecodeNop() + { + var machine_code = new Nop().ToMachineCode(); + var instruction = new Register32(Serialization.UInt32FromBytesBigEndian(machine_code)); + var instruction_decode = new InstructionDecode(instruction); + + Itertools.exhaust(instruction_decode.execute(core!)); + + Assert.That(core!.pipeline.Count, Is.EqualTo(4)); + checkUpdateInstructionPointer(); + checkContinueExecution(); + } + + private void checkUpdateInstructionPointer(int offset = 0) + { + Assert.That(core!.pipeline[offset], Is.TypeOf<WriteRegister>()); + var write_register = (WriteRegister)core.pipeline[offset]; + Assert.That(write_register.destination.name, Is.EqualTo(core.vInstructionSize.name)); + Assert.That(write_register.source, Is.EqualTo(Serialization.UInt32ToBytesBigEndian(4))); + + Assert.That(core.pipeline[offset + 1], Is.TypeOf<ALUOperation32>()); + var alu_operation = (ALUOperation32)core.pipeline[offset + 1]; + Assert.That(alu_operation.operation, Is.EqualTo(ALUOperationType.ADD)); + Assert.That(alu_operation.left.name, Is.EqualTo(core.IP.name)); + Assert.That(alu_operation.right.name, Is.EqualTo(core.vInstructionSize.name)); + } + + private void checkContinueExecution() + { + var read_memory_index = core!.pipeline.Count - 2; + Assert.That(core.pipeline[read_memory_index], Is.TypeOf<ReadMemory>()); + var read_memory = (ReadMemory)core.pipeline[read_memory_index]; + Assert.That(read_memory.storage.name, Is.EqualTo(core.vInstruction.name)); + Assert.That(read_memory.address.name, Is.EqualTo(core.IP.name)); + + + var instruction_decode_index = core.pipeline.Count - 1; + Assert.That(core.pipeline[instruction_decode_index], Is.TypeOf<InstructionDecode>()); + var instruction_decode_next = (InstructionDecode)core.pipeline[instruction_decode_index]; + Assert.That(instruction_decode_next.source.name, Is.EqualTo(core.vInstruction.name)); + } + + [Test] + public void TestDecodeMovRegReg() + { + var machine_code = new MovRegReg( + new OpRegister(RegisterID.RD0), + new OpRegister(RegisterID.RD1) + ).ToMachineCode(); + var instruction = new Register32(Serialization.UInt32FromBytesBigEndian(machine_code)); + var instruction_decode = new InstructionDecode(instruction); + + Itertools.exhaust(instruction_decode.execute(core!)); + + Assert.That(core!.pipeline.Count, Is.EqualTo(5)); + checkUpdateInstructionPointer(); + + Assert.That(core.pipeline[2], Is.TypeOf<CopyRegister>()); + var copy_register = (CopyRegister)core.pipeline[2]; + Assert.That(copy_register.destination.name, Is.EqualTo(core.RD0.name)); + Assert.That(copy_register.source.name, Is.EqualTo(core.RD1.name)); + + checkContinueExecution(); + } + + [Test] + public void TestMovRegMem() + { + var machine_code = new MovRegMem( + new OpRegister(RegisterID.RD0), + new OpMemoryAddress(RegisterID.RD1) + ).ToMachineCode(); + var instruction = new Register32(Serialization.UInt32FromBytesBigEndian(machine_code)); + var instruction_decode = new InstructionDecode(instruction); + + Itertools.exhaust(instruction_decode.execute(core!)); + + Assert.That(core!.pipeline.Count, Is.EqualTo(5)); + checkUpdateInstructionPointer(); + + Assert.That(core.pipeline[2], Is.TypeOf<ReadMemory>()); + var read_memory = (ReadMemory)core.pipeline[2]; + Assert.That(read_memory.storage, Is.EqualTo(core.RD0)); + Assert.That(read_memory.address, Is.EqualTo(core.RD1)); + + checkContinueExecution(); + } + + [Test] + public void TestMovMemReg() + { + var machine_code = new MovMemReg( + new OpMemoryAddress(RegisterID.RD0), + new OpRegister(RegisterID.RD1) + ).ToMachineCode(); + var instruction = new Register32(Serialization.UInt32FromBytesBigEndian(machine_code)); + var instruction_decode = new InstructionDecode(instruction); + + Itertools.exhaust(instruction_decode.execute(core!)); + + Assert.That(core!.pipeline.Count, Is.EqualTo(5)); + checkUpdateInstructionPointer(); + + Assert.That(core.pipeline[2], Is.TypeOf<WriteMemory>()); + var write_memory = (WriteMemory)core.pipeline[2]; + Assert.That(write_memory.address, Is.EqualTo(core.RD0)); + Assert.That(write_memory.storage, Is.EqualTo(core.RD1)); + + checkContinueExecution(); + } + + [Test] + public void TestMovRegCon() + { + var machine_code = new MovRegCon( + new OpRegister(RegisterID.RD0), + new OpConstantInt(1) + ).ToMachineCode(); + var instruction = new Register32(Serialization.UInt32FromBytesBigEndian( + machine_code.Take(4).ToArray() + )); + var instruction_decode = new InstructionDecode(instruction); + + Itertools.exhaust(instruction_decode.execute(core!)); + + Assert.That(core!.pipeline.Count, Is.EqualTo(8)); + checkUpdateInstructionPointer(); + checkReadInstructionConstant(); + + Assert.That(core.pipeline[5], Is.TypeOf<CopyRegister>()); + var copy_register = (CopyRegister)core.pipeline[5]; + Assert.That(copy_register.destination.name, Is.EqualTo(core.RD0.name)); + Assert.That(copy_register.source.name, Is.EqualTo(core.vRD0.name)); + + checkContinueExecution(); + } + + private void checkReadInstructionConstant(int offset = 2) + { + Assert.That(core!.pipeline[offset], Is.TypeOf<ReadMemory>()); + var read_memory = (ReadMemory)core.pipeline[offset]; + Assert.That(read_memory.storage.name, Is.EqualTo(core.vRD0.name)); + Assert.That(read_memory.address.name, Is.EqualTo(core.IP.name)); + + checkUpdateInstructionPointer(offset + 1); + } + + [Test] + public void TestMovMemCon() + { + var machine_code = new MovMemCon( + new OpMemoryAddress(RegisterID.RD0), + new OpConstantInt(1) + ).ToMachineCode(); + var instruction = new Register32(Serialization.UInt32FromBytesBigEndian( + machine_code.Take(4).ToArray() + )); + var instruction_decode = new InstructionDecode(instruction); + + Itertools.exhaust(instruction_decode.execute(core!)); + + Assert.That(core!.pipeline.Count, Is.EqualTo(8)); + checkUpdateInstructionPointer(); + checkReadInstructionConstant(); + + Assert.That(core.pipeline[5], Is.TypeOf<WriteMemory>()); + var write_memory = (WriteMemory)core.pipeline[5]; + Assert.That(write_memory.address.name, Is.EqualTo(core.RD0.name)); + Assert.That(write_memory.storage.name, Is.EqualTo(core.vRD0.name)); + + checkContinueExecution(); + } + } +} \ No newline at end of file diff --git a/src/Bytom.Hardware/CPU/Core.cs b/src/Bytom.Hardware/CPU/Core.cs index 4ad765b..411532c 100644 --- a/src/Bytom.Hardware/CPU/Core.cs +++ b/src/Bytom.Hardware/CPU/Core.cs @@ -52,36 +52,57 @@ public class Core public Package? package; public Thread? thread; - public Register32 RD0; - public Register32 RD1; - public Register32 RD2; - public Register32 RD3; - public Register32 RD4; - public Register32 RD5; - public Register32 RD6; - public Register32 RD7; - public Register32 RD8; - public Register32 RD9; - public Register32 RDA; - public Register32 RDB; - public Register32 RDC; - public Register32 RDD; - public Register32 RDE; - public Register32 RDF; + public Register32 RD0 = new Register32(0, name: "RD0"); + public Register32 RD1 = new Register32(0, name: "RD1"); + public Register32 RD2 = new Register32(0, name: "RD2"); + public Register32 RD3 = new Register32(0, name: "RD3"); + public Register32 RD4 = new Register32(0, name: "RD4"); + public Register32 RD5 = new Register32(0, name: "RD5"); + public Register32 RD6 = new Register32(0, name: "RD6"); + public Register32 RD7 = new Register32(0, name: "RD7"); + public Register32 RD8 = new Register32(0, name: "RD8"); + public Register32 RD9 = new Register32(0, name: "RD9"); + public Register32 RDA = new Register32(0, name: "RDA"); + public Register32 RDB = new Register32(0, name: "RDB"); + public Register32 RDC = new Register32(0, name: "RDC"); + public Register32 RDD = new Register32(0, name: "RDD"); + public Register32 RDE = new Register32(0, name: "RDE"); + public Register32 RDF = new Register32(0, name: "RDF"); - public ConditionCodeRegister CCR; - public Register32 CR0; - public Register32 STP; - public Register32 FBP; - public Register32 VATTA; - public Register32 IDT; - public Register32 IRA; - public Register32 IP; - public Register32 TRA; - public Register32 TDTA; - public Register32 KERNEL_STP; - public Register32 KERNEL_FBP; - public Register32 KERNEL_IP; + public ConditionCodeRegister CCR = new ConditionCodeRegister(name: "CCR"); + public Register32 CR0 = new Register32(0b10000000_00000000_00000000_00000000, name: "CR0"); + public Register32 STP = new Register32(0, name: "STP"); + public Register32 FBP = new Register32(0, name: "FBP"); + public Register32 VATTA = new Register32(0, name: "VATTA"); + public Register32 IDT = new Register32(0, name: "IDT"); + public Register32 IRA = new Register32(0, name: "IRA"); + public Register32 IP = new Register32(0, name: "IP"); + public Register32 TRA = new Register32(0, name: "TRA"); + public Register32 TDTA = new Register32(0, name: "TDTA"); + public Register32 KERNEL_STP = new Register32(0, name: "KERNEL_STP"); + public Register32 KERNEL_FBP = new Register32(0, name: "KERNEL_FBP"); + public Register32 KERNEL_IP = new Register32(0, name: "KERNEL_IP"); + // Register containing the instruction to be executed after it was fetched from + // memory. + public Register32 vInstruction = new Register32(0, name: "vInstruction"); + // Register containing the size of the instruction to be executed, used + // to offset the instruction pointer. + public Register32 vInstructionSize = new Register32(0, name: "vInstructionSize"); + // Hidden intermediate register 0 used by micro-ops. + public Register32 vRD0 = new Register32(0, name: "vRD0"); + // Hidden intermediate register 1 used by micro-ops. + public Register32 vRD1 = new Register32(0, name: "vRD1"); + // Hidden intermediate register 2 used by micro-ops. + public Register32 vRD2 = new Register32(0, name: "vRD2"); + // Hidden intermediate register 3 used by micro-ops. + public Register32 vRD3 = new Register32(0, name: "vRD3"); + // Pipeline containing scheduled micro-ops. + public List<MicroOp> pipeline = new List<MicroOp>(); + // Micro-op that was primed and currently is being executed. + // Execution of micro-op can take multiple cycles, for example if this is + // a blocking memory read operation. + IEnumerator? currentMicroOp; + List<uint> interrupt_queue = new List<uint>(); public Dictionary<RegisterID, Register32> registers; @@ -96,38 +117,6 @@ uint clock_speed_hz power_status = PowerStatus.OFF; clock = new Clock(clock_speed_hz); - RD0 = new Register32(0); - RD1 = new Register32(0); - RD2 = new Register32(0); - RD3 = new Register32(0); - RD4 = new Register32(0); - RD5 = new Register32(0); - RD6 = new Register32(0); - RD7 = new Register32(0); - RD8 = new Register32(0); - RD9 = new Register32(0); - RDA = new Register32(0); - RDB = new Register32(0); - RDC = new Register32(0); - RDD = new Register32(0); - RDE = new Register32(0); - RDF = new Register32(0); - - CCR = new ConditionCodeRegister(); - CR0 = new Register32(0b10000000_00000000_00000000_00000000); - STP = new Register32(0); - FBP = new Register32(0); - VATTA = new Register32(0); - IDT = new Register32(0); - IRA = new Register32(0); - IP = new Register32(0); - TRA = new Register32(0); - TDTA = new Register32(0); - KERNEL_STP = new Register32(0); - KERNEL_FBP = new Register32(0); - KERNEL_IP = new Register32(0); - - registers = new Dictionary<RegisterID, Register32>{ { RegisterID.RD0, RD0 }, { RegisterID.RD1, RD1 }, @@ -160,21 +149,14 @@ uint clock_speed_hz { RegisterID.KERNEL_IP, KERNEL_IP }, }; } - public Register32 vInstruction = new Register32(0); - public Register32 vInstructionSize = new Register32(0); - public Register32 vRD0 = new Register32(0); - public Register32 vRD1 = new Register32(0); - public Register32 vRD2 = new Register32(0); - public Register32 vRD3 = new Register32(0); - public List<MicroOp> pipeline = new List<MicroOp>(); - IEnumerator? currentMicroOp; - List<uint> interrupt_queue = new List<uint>(); public void primeMicroOpDecoding() { pushMicroOp(new ReadMemory(IP, vInstruction)); pushMicroOp(new InstructionDecode(vInstruction)); } + // Execute the next tick on the current micro-op or if it's finished then + // start execution of the next micro-op in the pipeline. public void executeMicroOp() { if (currentMicroOp?.MoveNext() ?? false) @@ -187,43 +169,45 @@ public void executeMicroOp() pipeline.RemoveAt(0); } } + // Push a micro-op to the pipeline. public void pushMicroOp(MicroOp microOp) { pipeline.Add(microOp); } - + // Get the kernel mode bit from the control register 0. public bool isKernelMode() { return CR0.readBit(31); } - + // Set the kernel mode bit in the control register 0. public void setKernelModeBit(bool value) { CR0.writeBit(31, value); } - + // Get the virtual memory enabled bit from the control register 0. public bool isVirtualMemoryEnabled() { return CR0.readBit(0); } - + // Set the virtual memory enabled bit in the control register 0. public void setVirtualMemoryEnabledBit(bool value) { CR0.writeBit(0, value); } - + // Method for starting the core and initializing it's execution loop. public virtual void powerOn(Package package) { if (power_status == PowerStatus.ON) { - throw new System.Exception("Core is already powered on"); + throw new Exception("Core is already powered on"); } power_status = PowerStatus.STARTING; this.package = package; powerOnInit(); power_status = PowerStatus.ON; } - + // Function invoked during initialization phase of core after the signal to + // power on is received. public virtual void powerOnInit() { foreach (var reg in registers.Values) @@ -237,12 +221,12 @@ public virtual void powerOnInit() thread = new Thread(executionLoop); thread.Start(); } - + // Get reference to the memory controller of the motherboard or throw an exception. public IoController GetMemoryController() { return package!.motherboard!.controller; } - + // Function used as main execution loop of the thread representing running core. public virtual void executionLoop() { primeMicroOpDecoding(); @@ -257,7 +241,8 @@ public virtual void executionLoop() throw new System.Exception("Incorrect core power state"); } } - + // Method for stopping the core. It will wait for the current instruction to finish + // executing and then stop the core. public virtual void powerOff() { if (power_status == PowerStatus.OFF) @@ -268,7 +253,7 @@ public virtual void powerOff() powerOffTeardown(); power_status = PowerStatus.OFF; } - + // Perform cleanup of the core after power off signal was received. public virtual void powerOffTeardown() { requested_power_off = true; @@ -281,7 +266,7 @@ public virtual void powerOffTeardown() thread = null; package = null; } - + // Get the current power status of the core. public PowerStatus getPowerStatus() { return power_status; diff --git a/src/Bytom.Hardware/CPU/MicoOp.cs b/src/Bytom.Hardware/CPU/MicoOp.cs index 2ee70a6..4b47eea 100644 --- a/src/Bytom.Hardware/CPU/MicoOp.cs +++ b/src/Bytom.Hardware/CPU/MicoOp.cs @@ -3,7 +3,6 @@ using System; using System.Collections; using System.Collections.Concurrent; -using System.Collections.Generic; using Bytom.Tools; namespace Bytom.Hardware.CPU @@ -372,6 +371,9 @@ public override IEnumerable execute(Core core) popStackValue(core, core.IP); pushContinueExecution(core); break; + + default: + throw new NotImplementedException(decoder.GetOpCode().ToString()); } yield break; } @@ -421,13 +423,14 @@ private static void popStackValue(Core core, Register destination) ) ); } - + // Push ReadMemory operation to the core pipeline followed by update of the + // instruction pointer. private static void readInstructionConstant(Core core, Register32 destination) { core.pushMicroOp(new ReadMemory(destination: destination, address: core.IP)); updateInstructionPointer(core, 4u); } - + // Add `size` to the instruction pointer. private static void updateInstructionPointer(Core core, uint size) { core.pushMicroOp( @@ -479,7 +482,8 @@ public CopyRegister(Register destination, Register source) } public override IEnumerable execute(Core core) { - yield return null; + destination.writeBytes(source.readBytes()); + yield break; } } public class WriteRegister : MicroOp @@ -493,7 +497,8 @@ public WriteRegister(Register destination, byte[] source) } public override IEnumerable execute(Core core) { - yield return null; + destination.writeBytes(source); + yield break; } } public class JmpIfCondition : MicroOp diff --git a/src/Bytom.Hardware/CPU/Register.cs b/src/Bytom.Hardware/CPU/Register.cs index 71a87a8..2b11fc7 100644 --- a/src/Bytom.Hardware/CPU/Register.cs +++ b/src/Bytom.Hardware/CPU/Register.cs @@ -5,14 +5,19 @@ namespace Bytom.Hardware.CPU { public abstract class Register { + // Current value of the register as array of bytes big-endian. public byte[] value { get; set; } - public Register(byte[] value) + // Human readable name of the register used for debugging purposes. + public string name { get; } + + public Register(byte[] value, string name = "Register") { if (value.Length != getSizeBytes()) { throw new Exception("Invalid number of bytes"); } this.value = value; + this.name = name; } public abstract void reset(); public abstract uint getSizeBytes(); @@ -29,6 +34,11 @@ public byte[] readBytes() { return value; } + + public override string ToString() + { + return $"Register::{name}({BitConverter.ToString(value)})"; + } } public class Register32 : Register @@ -43,8 +53,9 @@ public Register32( uint initial_value, bool write_kernel_only = false, bool read_kernel_only = false, - bool no_move_write = false - ) : base(new byte[4] { 0, 0, 0, 0 }) + bool no_move_write = false, + string name = "Register32" + ) : base(new byte[4] { 0, 0, 0, 0 }, name) { this.initial_value = initial_value; this.write_kernel_only = write_kernel_only; @@ -53,17 +64,17 @@ public Register32( reset(); } - + // Reset the register to its initial value. public override void reset() { writeUInt32(initial_value); } - + // Get size of register in bytes. public override uint getSizeBytes() { return 4; } - + // Overwrite value of register with unsigned 32-bit integer value. public void writeUInt32(uint value) { Serialization.UInt32ToBytesBigEndian(value).CopyTo(this.value, 0); @@ -98,6 +109,7 @@ public bool readBit(int offset) { return (readUInt32() & (1u << offset)) != 0; } + public void writeBit(int offset, bool set) { if (set) @@ -109,19 +121,27 @@ public void writeBit(int offset, bool set) writeUInt32(readUInt32() & ~(1u << offset)); } } + public Address readAddress() { return new Address(readUInt32()); } + public void writeAddress(Address address) { writeUInt32(address.ToUInt32()); } + + public override string ToString() + { + return $"Register32::{name}({BitConverter.ToString(value)})"; + } } public class ConditionCodeRegister : Register32 { - public ConditionCodeRegister() : base(0) + public ConditionCodeRegister(string name = "ConditionCodeRegister") + : base(0, name: name, no_move_write: true) { } diff --git a/src/Bytom.Hardware/IoController.cs b/src/Bytom.Hardware/IoController.cs index 3336056..30f5167 100644 --- a/src/Bytom.Hardware/IoController.cs +++ b/src/Bytom.Hardware/IoController.cs @@ -118,7 +118,7 @@ public PowerStatus getPowerStatus() public AddressRange allocateAddressRange(long size) { Debug.Assert(power_status != PowerStatus.OFF, "MemoryController is powered off"); - Debug.Assert(ram_address_range != null, "RAM address range not allocated"); + Debug.Assert(!(ram_address_range is null), "RAM address range not allocated"); var new_start_address = backward_allocation_address - size; Debug.Assert(new_start_address >= ram_address_range!.end_address, "Out of address space"); diff --git a/src/Bytom.Tools/Itertools.cs b/src/Bytom.Tools/Itertools.cs index bbc2653..0fa742c 100644 --- a/src/Bytom.Tools/Itertools.cs +++ b/src/Bytom.Tools/Itertools.cs @@ -11,5 +11,15 @@ public static IEnumerable yieldVoidTimes(long count) yield return null; } } + public static void exhaust(IEnumerable enumerable) + { + foreach (var _ in enumerable) + { } + } + public static void saturate(IEnumerator enumerable) + { + while (enumerable.MoveNext()) + { } + } } } \ No newline at end of file