Add ml_top controlblock on single-core kelvin resc and repl

Change-Id: I26951636601f14975c5515169a62351c44169f54
diff --git a/kelvin.resc b/kelvin.resc
index 52c043a..68ecce9 100644
--- a/kelvin.resc
+++ b/kelvin.resc
@@ -12,35 +12,36 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Renode script for testing the Kelvin Vector Core
+# Renode script for testing the Kelvin core using kelvin_sim external CPU library
+
 
 mach create "kelvin"
 
-EnsureTypeIsLoaded "Antmicro.Renode.Peripherals.CPU.RiscV32"
-include @sim/config/shodan_infrastructure/KelvinRiscV32.cs
-EnsureTypeIsLoaded "Antmicro.Renode.Peripherals.CPU.KelvinRiscV32"
-EnsureTypeIsLoaded "Antmicro.Renode.Peripherals.CPU.KelvinRiscV32_ControlBlock"
-$platformfile?=@sim/config/platforms/kelvin.repl
+include @sim/config/shodan_infrastructure/KelvinCPU.cs
+EnsureTypeIsLoaded "Antmicro.Renode.Peripherals.CPU.KelvinCPU"
+EnsureTypeIsLoaded "Antmicro.Renode.Peripherals.CPU.MlTopControlBlock"
+
+$platformfile?=@sim/config/platforms/kelvin_ml_core_external_cpu.repl
 
 machine LoadPlatformDescription $platformfile
 
 $bin?=@out/kelvin/sw/bazel_out/hello_world.bin
+$cpuLibrary?=@out/kelvin/sim/librenode_kelvin.so
 
-sysbus.cpu2 EnableRiscvOpcodesCounting
+sysbus.cpu2 IsHalted true
+
+sysbus.cpu2 CpuLibraryPath $cpuLibrary
+sysbus.cpu2 MemoryOffset 0
 
 macro reset
 """
-    sysbus LoadBinary $bin 0x5A000000
+    # TODO(hcindyl): Replace it with LoadBinary `sysbus LoadBinary $bin 0x5A000000`
+    sysbus.cpu2 ExecutableFile $bin
     # Start the vector core at address 0 of its TCM and halt / reset it.
-    sysbus.vec_controlblock WriteDoubleWord 12 3
+    sysbus.ml_top_controlblock WriteDoubleWord 0xc 3
 """
 runMacro $reset
 
-# Note: GDB doesn't seem to like the way we're setting the program counter in
-# the vec_controlblock. Breakpoints set to the exact reset address don't seem to
-# get reliably triggered. We should probably root cause this later when we have
-# time, but as a workaround setting the breakpoint to reset_addr+4 instead seems
-# to work fine.
 
-# Enable vec_controlblock verbose logging
-logLevel -1 sysbus.vec_controlblock
+logLevel 0 sysbus.cpu2
+logLevel -1 sysbus.ml_top_controlblock
diff --git a/kelvin_external_cpu.resc b/kelvin_external_cpu.resc
deleted file mode 100644
index 0f78c60..0000000
--- a/kelvin_external_cpu.resc
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copyright 2023 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http:#www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Renode script for testing the Kelvin core using kelvin_sim external CPU library
-
-
-mach create "kelvin"
-
-include @sim/config/shodan_infrastructure/KelvinCPU.cs
-EnsureTypeIsLoaded "Antmicro.Renode.Peripherals.CPU.KelvinCPU"
-
-$platformfile?=@sim/config/platforms/kelvin_ml_core_external_cpu.repl
-
-machine LoadPlatformDescription $platformfile
-
-$bin?=@out/kelvin/sw/bazel_out/hello_world.bin
-$cpuLibrary?=@out/kelvin/sim/librenode_kelvin.so
-
-sysbus.cpu2 CpuLibraryPath $cpuLibrary
-sysbus.cpu2 ExecutableFile $bin
-sysbus.cpu2 MemoryOffset 0
-
-logLevel -1 sysbus.cpu2
diff --git a/platforms/kelvin_ml_core_external_cpu.repl b/platforms/kelvin_ml_core_external_cpu.repl
index f83ecdd..a2f3b11 100644
--- a/platforms/kelvin_ml_core_external_cpu.repl
+++ b/platforms/kelvin_ml_core_external_cpu.repl
@@ -19,3 +19,11 @@
 cpu2: CPU.KelvinCPU @ sysbus
     id: 1
     cpuType: "Kelvin"
+
+// RAM_ML_TOP_MEM      [‘h5A00_0000 - ‘h5A3F_FFFF)   4MB RAM for ML core
+ram_ml_top_dmem: Memory.MappedMemory @ sysbus 0x5A000000
+    size: 0x00400000
+
+
+ml_top_controlblock : CPU.MlTopControlBlock @ sysbus 0x5C000000
+    core: cpu2
diff --git a/shodan_infrastructure/KelvinCPU.cs b/shodan_infrastructure/KelvinCPU.cs
index b136900..d9df2ef 100644
--- a/shodan_infrastructure/KelvinCPU.cs
+++ b/shodan_infrastructure/KelvinCPU.cs
@@ -20,6 +20,7 @@
 using System.Collections.Generic;
 using System.Collections.Concurrent;
 using Antmicro.Renode.Core;
+using Antmicro.Renode.Core.Structure.Registers;
 using Antmicro.Renode.Exceptions;
 using Antmicro.Renode.Logging;
 using Antmicro.Renode.Peripherals.Bus;
@@ -88,11 +89,42 @@
             }
             this.NoisyLog("Load executable image " + executableFile);
 
-            // Start with halted CPU
-            IsHalted = true;
             PC = 0;
         }
 
+        public void RegisterControlBlock(MlTopControlBlock controlBlock)
+        {
+            ControlBlock = controlBlock;
+            ControlBlockRegistered = true;
+        }
+
+        private MlTopControlBlock ControlBlock;
+        private bool ControlBlockRegistered = false;
+
+        private void HandleKelvinEBreak()
+        {
+            if (ControlBlockRegistered)
+            {
+                ControlBlock.ExecEBreak();
+            }
+            else
+            {
+                IsHalted = true;
+            }
+        }
+
+        private void HandleKelvinMPause()
+        {
+            if (ControlBlockRegistered)
+            {
+                ControlBlock.ExecMPause();
+            }
+            else
+            {
+                IsHalted = true;
+            }
+        }
+
         public override void Reset()
         {
             base.Reset();
@@ -165,7 +197,8 @@
             }
         }
 
-        protected override ExecutionResult ExecuteInstructions(ulong numberOfInstructionsToExecute,
+        protected override ExecutionResult ExecuteInstructions(
+            ulong numberOfInstructionsToExecute,
             out ulong numberOfExecutedInstructions)
         {
             UInt64 instructionsExecutedThisRound = 0UL;
@@ -173,7 +206,8 @@
             try
             {
                 // Invoke simulator for the number of instructions.
-                instructionsExecutedThisRound = step(kelvin_id, numberOfInstructionsToExecute, error_ptr);
+                instructionsExecutedThisRound = step(kelvin_id, numberOfInstructionsToExecute,
+                                                     error_ptr);
                 // Parse the result.
                 Int32 step_result = Marshal.ReadInt32(error_ptr);
                 switch (step_result)
@@ -219,11 +253,17 @@
             if (numberOfInstructionsToExecute > instructionsExecutedThisRound &&
                 result == ExecutionResult.Ok)
             {
-                // TODO(hcindyl): Change to control block callbacks.
-                this.NoisyLog("CPU finish excution.");
-                this.NoisyLog("Total instruction count: {0}",
+                this.DebugLog("CPU finish execution.");
+                this.DebugLog("Total instruction count: {0}",
                               totalExecutedInstructions);
-                IsHalted = true;
+                HandleKelvinMPause();
+            }
+
+            if (numberOfInstructionsToExecute > instructionsExecutedThisRound &&
+                result == ExecutionResult.Aborted)
+            {
+                this.DebugLog("CPU finish execution.");
+                HandleKelvinEBreak();
             }
 
             return result;
@@ -264,7 +304,9 @@
                         continue;
                     }
                     var reg_info = Marshal.PtrToStructure<RegInfo>(reg_info_ptr);
-                    var cpu_reg = new CPURegister(reg_info.index, reg_info.width, reg_info.isGeneral, reg_info.isReadOnly);
+                    var cpu_reg = new CPURegister(reg_info.index, reg_info.width,
+                                                  reg_info.isGeneral,
+                                                  reg_info.isReadOnly);
                     var reg_name = Marshal.PtrToStringAuto(string_ptr);
                     registerMap.Add(reg_info.index, cpu_reg);
                     registerNamesMap.Add(reg_name, reg_info.index);
@@ -383,23 +425,32 @@
         private IntPtr string_ptr { get; set; }
 #pragma warning disable 649
         [Import(UseExceptionWrapper = false)]
-        private FuncInt32Int32 construct;  // Int32(Int32 max_string_lenth)
+        // Int32(Int32 max_string_lenth)
+        private FuncInt32Int32 construct;
         [Import(UseExceptionWrapper = false)]
-        private FuncInt32Int32 destruct;  // Int32(Int32 id);
+        // Int32(Int32 id);
+        private FuncInt32Int32 destruct;
         [Import(UseExceptionWrapper = false)]
-        private FuncInt32Int32 reset;  // Int32(Int32 id);
+        // Int32(Int32 id);
+        private FuncInt32Int32 reset;
         [Import(UseExceptionWrapper = false)]
-        private FuncInt32Int32 get_reg_info_size; // Int32(Int32 id)
+        // Int32(Int32 id)
+        private FuncInt32Int32 get_reg_info_size;
         [Import(UseExceptionWrapper = false)]
-        private FuncInt32Int32Int32IntPtrIntPtr get_reg_info; // Int32(Int32 id, Int32 index, char *name, IntPtr *info);
+        // Int32(Int32 id, Int32 index, char *name, IntPtr *info);
+        private FuncInt32Int32Int32IntPtrIntPtr get_reg_info;
         [Import(UseExceptionWrapper = false)]
-        private FuncInt32Int32StringUInt64 load_image;  // Int32(Int32 id, String file_name, Int64 address);
+        // Int32(Int32 id, String file_name, Int64 address);
+        private FuncInt32Int32StringUInt64 load_image;
         [Import(UseExceptionWrapper = false)]
-        private FuncUInt64Int32UInt64IntPtr step;  // UInt64(Int32 id, UInt64 step, IntPtr *status);
+        // UInt64(Int32 id, UInt64 step, IntPtr *status);
+        private FuncUInt64Int32UInt64IntPtr step;
         [Import(UseExceptionWrapper = false)]
-        private FuncInt32Int32UInt32IntPtr read_register;  // Int32(Int32 id, UInt32 reg_id, IntPtr *value);
+        // Int32(Int32 id, UInt32 reg_id, IntPtr *value);
+        private FuncInt32Int32UInt32IntPtr read_register;
         [Import(UseExceptionWrapper = false)]
-        private FuncInt32Int32Int32UInt64 write_register;  // Int32(Int32 id, Int32 reg_id, UInt64 value);
+        // Int32(Int32 id, Int32 reg_id, UInt64 value);
+        private FuncInt32Int32Int32UInt64 write_register;
 #pragma warning restore 649
 
         private Int32 kelvin_id { get; set; }
@@ -407,4 +458,252 @@
         private ulong totalExecutedInstructions { get; set; }
         private const int PC_ID = 0x07b1;  // PC magic ID
     }
+
+
+    public class MlTopControlBlock :
+            IDoubleWordPeripheral,
+            IProvidesRegisterCollection<DoubleWordRegisterCollection>,
+            IKnownSize
+    {
+
+        public MlTopControlBlock(Machine machine,
+                                 KelvinCPU core)
+        {
+            Machine = machine;
+            Core = core;
+
+            HostReqIRQ = new GPIO();
+            FinishIRQ = new GPIO();
+            InstructionFaultIRQ = new GPIO();
+
+            Core.RegisterControlBlock(this);
+
+            RegistersCollection = new DoubleWordRegisterCollection(this);
+            DefineRegisters();
+
+            Reset();
+        }
+
+        public void Reset()
+        {
+            mode = Mode.Freeze | Mode.SwReset;
+            RegistersCollection.Reset();
+        }
+
+        private void DefineRegisters()
+        {
+            Registers.IntrState.Define32(this)
+                .WithValueField(0, 4,
+                                writeCallback: (_, value) =>
+                                {
+                                    this.NoisyLog("Got {0} to clear IRQ pending bits", value);
+                                    irqsPending = irqsPending & ~(InterruptBits)value;
+                                    IrqUpdate();
+                                },
+                                valueProviderCallback: (_) =>
+                                {
+                                    return (uint)irqsPending;
+                                });
+
+            Registers.IntrEnable.Define32(this)
+                .WithValueField(0, 4,
+                                writeCallback: (_, value) =>
+                                {
+                                    this.NoisyLog("Got {0} to write IRQ enable bits", value);
+                                    irqsEnabled = (InterruptBits)value & InterruptBits.Mask;
+                                    IrqUpdate();
+                                },
+                                valueProviderCallback: (_) =>
+                                {
+                                    return (uint)irqsEnabled;
+                                });
+
+            Registers.IntrTest.Define32(this)
+                .WithValueField(0, 4,
+                                writeCallback: (_, value) =>
+                                {
+                                    this.NoisyLog("Got {0} to set IRQ pending bits", value);
+                                    irqsPending |= ((InterruptBits)value & InterruptBits.Mask);
+                                    IrqUpdate();
+                                });
+
+            Registers.Control.Define32(this, resetValue: 0x00000002)
+                .WithValueField(0, 24, name: "FREEZE_RESET_PC",
+                                writeCallback: (_, val) =>
+                                {
+                                    Mode newMode = (Mode)val & Mode.Mask;
+                                    // Pause the core when either freeze or swreset is asserted.
+                                    if ((mode == Mode.Run) && (newMode != Mode.Run))
+                                    {
+                                        this.NoisyLog("Pausing core.");
+                                        Core.IsHalted = true;
+                                    }
+
+                                    // Trigger the core's reset when SwReset is deasserted.
+                                    if (((mode & Mode.SwReset) != 0) &&
+                                        ((newMode & Mode.SwReset) == 0))
+                                    {
+                                        this.NoisyLog("Resetting core.");
+                                        // Reset PC
+                                        ulong startAddress = (val >> (int)Mode.NumBits);
+                                        if (Core.PC != startAddress)
+                                        {
+                                            this.DebugLog("Setting PC to 0x{0:X8}.",
+                                                          startAddress);
+                                            Core.PC = startAddress;
+                                        }
+                                        Core.Reset();
+                                    }
+
+                                    // Raise reset bit, but the core will not be
+                                    // reset until SwReset is deasserted.
+                                    if ((mode & Mode.SwReset) == 0 &&
+                                        ((newMode & Mode.SwReset) != 0))
+                                    {
+                                        this.NoisyLog("Raise reset bit");
+                                    }
+
+                                    // Unpause the core when both freeze and
+                                    // SwReset are deasserted.
+                                    if ((mode != Mode.Run) && (newMode == Mode.Run))
+                                    {
+                                        this.NoisyLog("Resuming core.");
+                                        Core.IsHalted = false;
+
+                                        Core.Resume();
+                                    }
+
+                                    this.mode = newMode;
+                                })
+                .WithTaggedFlag("VOLT_SEL", 24)
+                .WithIgnoredBits(25, 32 - 25);
+
+            // TODO: No implementation to memory bank control.
+            Registers.MemoryBankControl.Define32(this)
+                .WithTag("MEM_ENABLE", 0, 16)
+                .WithIgnoredBits(16, 32 - 16);
+
+            // TODO: No implementation of memory access error.
+            Registers.ErrorStatus.Define32(this)
+                .WithTaggedFlag("MEM_OUT_OF_RANGE", 0)
+                .WithTag("MEM_DISABLE_ACCESS", 1, 8)
+                .WithIgnoredBits(9, 32 - 9);
+
+            Registers.InitStart.Define32(this)
+                .WithTag("ADDRESS", 0, 22)
+                .WithIgnoredBits(22, 32 - 22);
+
+            Registers.InitEnd.Define32(this)
+                .WithTag("ADDRESS", 0, 22)
+                .WithTaggedFlag("VALID", 22)
+                .WithIgnoredBits(23, 32 - 23);
+
+            Registers.InitStatus.Define32(this)
+                .WithTaggedFlag("INIT_PENDING", 0)
+                .WithTaggedFlag("INIT_DONE", 1)
+                .WithIgnoredBits(2, 32 - 2);
+
+        }
+
+        public virtual uint ReadDoubleWord(long offset)
+        {
+            return RegistersCollection.Read(offset);
+        }
+
+        public virtual void WriteDoubleWord(long offset, uint value)
+        {
+            RegistersCollection.Write(offset, value);
+        }
+
+        public void ExecMPause()
+        {
+            // Pause and trigger a host interrupt indicating completion
+            if (mode == Mode.Run)
+            {
+                this.NoisyLog("Pausing the core for host completion notification.");
+            }
+            else
+            {
+                this.ErrorLog("Pausing the core for host completion notification, " +
+                              "but core was not expected to be running. Did you " +
+                              "clear IsHalted manually?");
+            }
+            Core.IsHalted = true;
+            mode = Mode.Freeze;
+            irqsPending |= InterruptBits.Finish;
+            IrqUpdate();
+        }
+
+        public void ExecEBreak()
+        {
+            // Pause and trigger a host interrupt indicating completion with
+            // fault
+            if (mode == Mode.Run)
+            {
+                this.NoisyLog("Core executed ebreak.");
+            }
+            else
+            {
+                this.ErrorLog("Core executed ebreak, but core was not expected " +
+                              "to be running. Did you clear IsHalted manually?");
+            }
+            Core.IsHalted = true;
+            mode = Mode.Freeze;
+            irqsPending |= InterruptBits.Finish | InterruptBits.InstructionFault;
+            IrqUpdate();
+        }
+
+        public DoubleWordRegisterCollection RegistersCollection { get; private set; }
+
+        public GPIO HostReqIRQ { get; }
+        public GPIO FinishIRQ { get; }
+        public GPIO InstructionFaultIRQ { get; }
+
+        private InterruptBits irqsEnabled;
+        private InterruptBits irqsPending;
+
+        private void IrqUpdate()
+        {
+            InterruptBits irqsPassed = irqsEnabled & irqsPending;
+            HostReqIRQ.Set((irqsPassed & InterruptBits.HostReq) != 0);
+            FinishIRQ.Set((irqsPassed & InterruptBits.Finish) != 0);
+            InstructionFaultIRQ.Set((irqsPassed & InterruptBits.InstructionFault) != 0);
+        }
+
+        private Mode mode;
+        private readonly Machine Machine;
+        private readonly KelvinCPU Core;
+        // Length of register space. Required by IKnowSize.
+        public long Size => 0x1000;
+        private enum Registers
+        {
+            IntrState = 0x00,
+            IntrEnable = 0x04,
+            IntrTest = 0x08,
+            Control = 0x0C,
+            MemoryBankControl = 0x10,
+            ErrorStatus = 0x14,
+            InitStart = 0x18,
+            InitEnd = 0x1C,
+            InitStatus = 0x20,
+        };
+        [Flags]
+        private enum Mode
+        {
+            Run = 0x00,
+            Freeze = 0x01,
+            SwReset = 0x02,
+            Mask = 0x03,
+            NumBits = 2,
+        };
+        [Flags]
+        private enum InterruptBits
+        {
+            HostReq = 1,
+            Finish = 2,
+            InstructionFault = 4,
+            Mask = 7,
+        };
+    }
+
 }