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