Add 1-window WindowMMU to Kelvin to support TCM offset

This CL adds an untouchable WindowMMU to Kelvin with a single window
translating addresses 0x00000000 : MemorySize to MemoryRangeStart :
MemoryRangeStart + MemorySize. This is needed in simulation because
Kelvin's software expects memory to begin at address 0x00000000 but
Renode doesn't support multiple address spaces without the use of
MMUs.

Change-Id: I08b7baf071f916f29f62470224e43ab7e7c221d8
diff --git a/kelvin.resc b/kelvin.resc
index b3ee710..4a8aead 100644
--- a/kelvin.resc
+++ b/kelvin.resc
@@ -30,9 +30,18 @@
 
 macro reset
 """
-    sysbus LoadBIN $bin 0x5A000000
-    # Start the vector core at address 0 of its TCM.
-    sysbus.cpu2 IsHalted true
-    sysbus.cpu2 PC 0x5A000000
+    sysbus LoadBinary $bin 0x5A000000
+    # Start the vector core at address 0 of its TCM and halt / reset it.
+    sysbus.vec_controlblock WriteDoubleWord 12 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.
+$gdb_port?=3333
+machine StartGdbServer $gdb_port false sysbus.cpu2
+
+start
diff --git a/platforms/kelvin.repl b/platforms/kelvin.repl
index 0aa30ae..00469f3 100644
--- a/platforms/kelvin.repl
+++ b/platforms/kelvin.repl
@@ -16,8 +16,18 @@
 // Kelvin single test core
 // ***************************************************
 
-using "sim/config/platforms/ml_core.repl"
+// TODO(jonathantate): Move the cpu and dmem blocks back into ml_core.repl and
+// uncomment this 'using ".../ml_core.repl"' line.
+// using "sim/config/platforms/ml_core.repl"
+
+cpu2: CPU.KelvinRiscV32 @ sysbus
+    hartId: 2
+
+//RAM_VEC_MEM      [‘h5A00_0000 - ‘h5A3F_FFFF)   4MB RAM for Vector core
+ram_vec_dmem: Memory.MappedMemory @ sysbus 0x5A000000
+    size: 0x00400000
 
 vec_controlblock : CPU.KelvinRiscV32_ControlBlock @ sysbus 0x5C000000
     core: cpu2
-    memoryRangeStart: 0x5A000000
+    tcmSize:       0x00400000
+    tcmRangeStart: 0x5A000000
diff --git a/shodan_infrastructure/KelvinRiscV32.cs b/shodan_infrastructure/KelvinRiscV32.cs
index 1b7772d..8b912c1 100644
--- a/shodan_infrastructure/KelvinRiscV32.cs
+++ b/shodan_infrastructure/KelvinRiscV32.cs
@@ -30,8 +30,17 @@
                              string cpuType = "rv32im")
             : base(null, cpuType, machine, hartId, privilegeArchitecture, endianness)
         {
-            InstallCustomInstruction(pattern: "00001000000000000000000001110011", handler: HandleKelvinMPause);
             InstallCustomInstruction(pattern: "00000000000100000000000001110011", handler: HandleKelvinEBreak); // Kelvin doesn't implement rv32i ebreak correctly
+            InstallCustomInstruction(pattern: "00000010000000000000000001110011", handler: HandleKelvinEExit);
+            InstallCustomInstruction(pattern: "00000100000000000000000001110011", handler: HandleKelvinEYield);
+            InstallCustomInstruction(pattern: "00000110000000000000000001110011", handler: HandleKelvinECtxSw);
+            InstallCustomInstruction(pattern: "00001000000000000000000001110011", handler: HandleKelvinMPause);
+            InstallCustomInstruction(pattern: "011110000000-----000000001110111", handler: HandleKelvinFLog);
+            InstallCustomInstruction(pattern: "011110000000-----001000001110111", handler: HandleKelvinSLog);
+            InstallCustomInstruction(pattern: "011110000000-----010000001110111", handler: HandleKelvinCLog);
+            InstallCustomInstruction(pattern: "011110000000-----011000001110111", handler: HandleKelvinKLog);
+            InstallCustomInstruction(pattern: "00100110000000000000000001110111", handler: HandleKelvinFlushAll);
+            InstallCustomInstruction(pattern: "001001100000-----000000001110111", handler: HandleKelvinFlushAt);
 
             Reset();
         }
@@ -58,14 +67,65 @@
         private KelvinRiscV32_ControlBlock ControlBlock;
         private bool ControlBlockRegistered = false;
 
+        private void HandleKelvinEBreak(UInt64 opcode)
+        {
+            ControlBlock.ExecEBreak();
+        }
+
+        private void HandleKelvinEExit(UInt64 opcode)
+        {
+            ControlBlock.ExecEBreak();
+        }
+
+        private void HandleKelvinEYield(UInt64 opcode)
+        {
+            // To be implemented
+            ControlBlock.ExecEBreak();
+        }
+
+        private void HandleKelvinECtxSw(UInt64 opcode)
+        {
+            // To be implemented
+            ControlBlock.ExecEBreak();
+        }
+
         private void HandleKelvinMPause(UInt64 opcode)
         {
             ControlBlock.ExecMPause();
         }
 
-        private void HandleKelvinEBreak(UInt64 opcode)
+        private void HandleKelvinFLog(UInt64 opcode)
         {
-            ControlBlock.ExecEBreak();
+            this.Log(LogLevel.Noisy, "FLog dropped (unimplemented)");
+            // To be implemented
+        }
+
+        private void HandleKelvinSLog(UInt64 opcode)
+        {
+            this.Log(LogLevel.Noisy, "SLog dropped (unimplemented)");
+            // To be implemented
+        }
+
+        private void HandleKelvinCLog(UInt64 opcode)
+        {
+            this.Log(LogLevel.Noisy, "CLog dropped (unimplemented)");
+            // To be implemented
+        }
+
+        private void HandleKelvinKLog(UInt64 opcode)
+        {
+            this.Log(LogLevel.Noisy, "KLog dropped (unimplemented)");
+            // To be implemented
+        }
+
+        private void HandleKelvinFlushAll(UInt64 opcode)
+        {
+            // No-op in simulator
+        }
+
+        private void HandleKelvinFlushAt(UInt64 opcode)
+        {
+            // No-op in simulator
         }
     }
 
@@ -77,12 +137,16 @@
 
         public KelvinRiscV32_ControlBlock(Machine machine,
                                           KelvinRiscV32 core,
-                                          ulong memoryRangeStart)
+                                          ulong tcmSize,
+                                          ulong tcmRangeStart)
         {
             Machine = machine;
             Core = core;
 
-            this.memoryRangestart = memoryRangeStart;
+            this.tcmRangeStart = tcmRangeStart;
+            this.tcmSize = tcmSize;
+
+            Mmu = InitMmu(tcmRangeStart, tcmSize);
 
             HostReqIRQ = new GPIO();
             FinishIRQ = new GPIO();
@@ -94,6 +158,8 @@
             RegistersCollection = new DoubleWordRegisterCollection(this);
             DefineRegisters();
 
+            Core.AddHookOnMmuFault(HandleFault);
+
             Reset();
         }
 
@@ -103,6 +169,18 @@
             RegistersCollection.Reset();
         }
 
+        private ExternalMmuBase InitMmu(ulong rangeStart, ulong tcmSize)
+        {
+            var mmu = new ExternalMmuBase(this.Core, 1);
+
+            mmu.SetWindowStart(0, 0);
+            mmu.SetWindowEnd(0, tcmSize);
+            mmu.SetWindowAddend(0, tcmRangeStart);
+            mmu.SetWindowPrivileges(0, 7); // Read / write / execute
+
+            return mmu;
+        }
+
         private void DefineRegisters()
         {
             Registers.IntrState.Define32(this)
@@ -161,9 +239,17 @@
                                         {
                                             this.Log(LogLevel.Noisy, "Resetting core.");
                                             Core.Reset();
-                                            ulong startAddress = (val >> (ulong)Mode.NumBits) + memoryRangeStart;
-                                            this.Log(LogLevel.Noisy, "Setting PC to 0x{0:X}.", startAddress);
-                                            Core.PC = startAddress;
+                                        }
+
+                                        if ((mode & Mode.SwReset) != 0)
+                                        {
+                                            // Always set the PC on assignment to help with GDB.
+                                            ulong startAddress = (val >> (int)Mode.NumBits);
+                                            if ((Core.PC != startAddress))
+                                            {
+                                                this.Log(LogLevel.Noisy, "Setting PC to 0x{0:X}.", startAddress);
+                                                Core.PC = startAddress;
+                                            }
                                         }
 
                                         // Unpause the core when both freeze and SwReset are deasserted.
@@ -182,20 +268,20 @@
 
             // To-do: Not sure how to implement disablable memory banks.
             Registers.MemoryBankControl.Define32(this)
-                    .WithValueField(0, 16, out MemoryEnable, name: "MEM_ENABLE")
+                    .WithValueField(0, 4, out MemoryEnable, name: "MEM_ENABLE")
                     .WithIgnoredBits(16, 32 - 16)
             ;
 
             // To-do: Not sure how to implement memory access range checks.
             Registers.ErrorStatus.Define32(this)
                     .WithFlag(0, name: "MEM_OUT_OF_RANGE")
-                    .WithValueField(1, 9, out MemoryDisableAccess, name: "MEM_DISABLE_ACCESS")
-                    .WithIgnoredBits(9, 32 - 9)
+                    .WithValueField(2, 4, out MemoryDisableAccess, name: "MEM_DISABLE_ACCESS")
+                    .WithIgnoredBits(16, 32 - 16)
             ;
 
             Registers.InitStart.Define32(this)
                     .WithValueField(0, 22, out InitStartAddress, name: "ADDRESS")
-                    .WithIgnoredBits(22, 32 - 22)
+                    .WithIgnoredBits(24, 32 - 24)
             ;
 
             Registers.InitEnd.Define32(this)
@@ -216,7 +302,6 @@
                                               writeAddress += DataPageSize)
                                           {
                                               Machine.SystemBus.WriteBytes(DataErasePattern,
-                                                                           memoryRangeStart +
                                                                            (ulong)writeAddress,
                                                                            (uint)DataPageSize, true);
                                           }
@@ -282,6 +367,45 @@
 
         public DoubleWordRegisterCollection RegistersCollection { get; private set; }
 
+
+        private void HandleFault(ulong faultAddress, AccessType accessType, int windowIndex)
+        {
+            LogFaultType(Mmu, faultAddress, accessType);
+            ExecEBreak();
+        }
+
+        private void LogFaultType(ExternalMmuBase mmu, ulong faultAddress, AccessType accessType)
+        {
+            var mmuName = mmu.GetType().Name;
+            var virtualWindowIndex = (uint)(faultAddress / tcmSize);
+
+            if(virtualWindowIndex >= 1)
+            {
+                this.Log(LogLevel.Error, "{0}: Translation request for {1} at 0x{2:X}: failure, no window at address", mmuName, accessType, faultAddress);
+            }
+            else
+            {
+                var windowEnd = mmu.GetWindowEnd(virtualWindowIndex);
+                var windowPrivilege = mmu.GetWindowPrivileges(virtualWindowIndex);
+
+                if(faultAddress > windowEnd)
+                {
+                    this.Log(LogLevel.Error, "{0}: Translation request for {1} at 0x{2:X}: failure on window {3}, out of range, window ends at 0x{4:X}",
+                             mmuName, accessType, faultAddress, virtualWindowIndex, windowEnd);
+                }
+                else if((windowPrivilege & (uint)accessType) == 0)
+                {
+                    this.Log(LogLevel.Error, "{0}: Translation request for {1} at 0x{2:X}: failure on window {3}, no permission, window permissions are 0x{4:X}",
+                             mmuName, accessType, faultAddress, virtualWindowIndex, windowPrivilege);
+                }
+                else
+                {
+                    this.Log(LogLevel.Error, "{0}: Translation request for {1} at 0x{2:X}: failed for unknown reason. This should not occur",
+                             mmuName, accessType, faultAddress);
+                }
+            }
+        }
+
         public GPIO HostReqIRQ { get; }
         public GPIO FinishIRQ { get; }
         public GPIO InstructionFaultIRQ { get; }
@@ -318,7 +442,9 @@
         private Mode mode;
         private readonly Machine Machine;
         private readonly KelvinRiscV32 Core;
-        private readonly ulong memoryRangeStart;
+        private readonly ExternalMmuBase Mmu;
+        private readonly ulong tcmSize;
+        private readonly ulong tcmRangeStart;
 
         // Length of register space.
         public long Size => 0x1000;