sim/config: Add ISP peripherals

This CL adds multiple peripherals to support the ISP functionality
necessary for the HPS demo.

CamCtrl handles the triggering of the HM01B0 camera, turning a register
write into a GPIO hi/low. It also handles the motion interupt (not
implemented yet).

Hm01b0Video augments the HiMaxHm01b0 peripheral with a circular buffer
of frames, rather than a single frame.

DoubleBufferDMA fakes the ISP, reducing it to just to handling the
CamCtrl trigger and doing the double buffering. Currently this
peripheral just does single buffering as the SW side isn't ready.

Change-Id: Ie8fea32e467142b35436d793d6b5529acc1ef5cf
diff --git a/hps.resc b/hps.resc
index 4eca830..9272f88 100644
--- a/hps.resc
+++ b/hps.resc
@@ -13,6 +13,9 @@
 include @sim/config/shodan_infrastructure/OpenTitan2_UART.cs
 include @sim/config/shodan_infrastructure/Mailbox.cs
 include @sim/config/shodan_infrastructure/AddressRangeStub.cs
+include @sim/config/shodan_infrastructure/CamCtrl.cs
+include @sim/config/shodan_infrastructure/DoubleBufferDMA.cs
+include @sim/config/shodan_infrastructure/Hm01b0Video.cs
 
 machine LoadPlatformDescription @sim/config/platforms/hps.repl
 
diff --git a/platforms/hps.repl b/platforms/hps.repl
index 6e7c4a9..1b28780 100644
--- a/platforms/hps.repl
+++ b/platforms/hps.repl
@@ -1,6 +1,12 @@
 using "sim/config/platforms/shodan.repl"
 
-camera: Sensors.HiMaxHM01B0 @ cam_i2c 0x24
+cam_ctrl: Sensors.CamCtrl @ sysbus 0x54050000
+    TriggerOut -> isp@0
+
+isp: Sensors.DoubleBufferDMA @ sysbus 0x54060000
+    IRQ -> smc_plic@17
+
+camera: Sensors.Hm01b0Video @ {isp; cam_i2c 0x24}
 
 // TODO(jesionowski): Temporary piece of flash for the finch app. Tock kernel
 // assumes that the app needs to be relocated from flash. This must be removed
diff --git a/shodan_infrastructure/CamCtrl.cs b/shodan_infrastructure/CamCtrl.cs
new file mode 100644
index 0000000..384265e
--- /dev/null
+++ b/shodan_infrastructure/CamCtrl.cs
@@ -0,0 +1,44 @@
+//
+// Copyright (c) 2022 Google LLC
+//
+// This file is licensed under the MIT License.
+// Full license text is available in 'licenses/MIT.txt'.
+//
+
+using Antmicro.Renode.Core;
+using Antmicro.Renode.Core.Structure;
+using Antmicro.Renode.Core.Structure.Registers;
+using Antmicro.Renode.Logging;
+using Antmicro.Renode.Peripherals.Bus;
+using Antmicro.Renode.Peripherals.Sensor;
+using System;
+
+namespace Antmicro.Renode.Peripherals.Sensors
+{
+    public class CamCtrl : IDoubleWordPeripheral, IKnownSize
+    {
+        public CamCtrl()
+        {
+            TriggerOut = new GPIO();
+        }
+
+        public void Reset()
+        {
+            TriggerOut.Unset();
+        }
+
+        public uint ReadDoubleWord(long offset)
+        {
+            return 0;
+        }
+
+        public void WriteDoubleWord(long offset, uint value)
+        {
+            TriggerOut.Set(Convert.ToBoolean(value));
+        }
+
+        public GPIO TriggerOut { get; }
+
+        public long Size => 0x4;
+    }
+}
diff --git a/shodan_infrastructure/DoubleBufferDMA.cs b/shodan_infrastructure/DoubleBufferDMA.cs
new file mode 100644
index 0000000..686e011
--- /dev/null
+++ b/shodan_infrastructure/DoubleBufferDMA.cs
@@ -0,0 +1,115 @@
+//
+// Copyright (c) 2022 Google LLC
+//
+// This file is licensed under the MIT License.
+// Full license text is available in 'licenses/MIT.txt'.
+//
+
+using Antmicro.Renode.Core;
+using Antmicro.Renode.Core.Structure;
+using Antmicro.Renode.Core.Structure.Registers;
+using Antmicro.Renode.Logging;
+using Antmicro.Renode.Peripherals.Bus;
+using Antmicro.Renode.Peripherals.Sensor;
+
+namespace Antmicro.Renode.Peripherals.Sensors
+{
+    public class DoubleBufferDMA: NullRegistrationPointPeripheralContainer<ICPIPeripheral>, IDoubleWordPeripheral, IKnownSize, IProvidesRegisterCollection<DoubleWordRegisterCollection>, IGPIOReceiver
+    {
+        public DoubleBufferDMA(Machine machine) : base(machine)
+        {
+            IRQ = new GPIO();
+            Trigger = new GPIO();
+
+            RegistersCollection = new DoubleWordRegisterCollection(this);
+            DefineRegisters();
+        }
+
+        public override void Reset()
+        {
+            IRQ.Unset();
+            Trigger.Unset();
+            RegistersCollection.Reset();
+        }
+
+        public uint ReadDoubleWord(long offset)
+        {
+            return RegistersCollection.Read(offset);
+        }
+
+        public void WriteDoubleWord(long offset, uint value)
+        {
+            RegistersCollection.Write(offset, value);
+        }
+
+        public void OnGPIO(int number, bool value) {
+            if (!value) {
+                return;
+            }
+
+            if(RegisteredPeripheral == null)
+            {
+                this.Log(LogLevel.Warning, "Tried to enable the controller, but there is no device connected");
+                return;
+            }
+
+            var data = RegisteredPeripheral.ReadFrame();
+
+            if(data.Length != rxBufferSize.Value)
+            {
+                this.Log(LogLevel.Warning, "Received {0} bytes from the device, but RX DMA stream is configured for {1} bytes. This might indicate problems in the driver", data.Length, rxBufferSize.Value);
+            }
+
+            Machine.SystemBus.WriteBytes(data, rxBuffer1Address.Value);
+
+            // TODO(jesionowski): re-enable double buffer once sw supports it
+            // if(use_buffer1) {
+            //     Machine.SystemBus.WriteBytes(data, rxBuffer1Address.Value);
+            // } else {
+            //     Machine.SystemBus.WriteBytes(data, rxBuffer2Address.Value);
+            // }
+
+            use_buffer1 = !use_buffer1;
+            IRQ.Blink();
+        }
+
+        public DoubleWordRegisterCollection RegistersCollection { get; }
+
+        public GPIO Trigger { get; }
+        public GPIO IRQ { get; }
+
+        public long Size => 0x100;
+
+        private void DefineRegisters()
+        {
+            Registers.RxBufferSize.Define(this)
+                .WithValueField(0, 32, out rxBufferSize, name: "RX_SIZE")
+            ;
+
+            Registers.RxBufferBaseAddress1.Define(this)
+                .WithValueField(0, 32, out rxBuffer1Address, name: "ADDR1")
+            ;
+
+            Registers.RxBufferBaseAddress2.Define(this)
+                .WithValueField(0, 32, out rxBuffer2Address, name: "ADDR2")
+            ;
+
+            Registers.Configuration.Define(this)
+                .WithReservedBits(0, 32)
+            ;
+        }
+
+        private bool use_buffer1 = true;
+        private IValueRegisterField rxBuffer1Address;
+        private IValueRegisterField rxBuffer2Address;
+        private IValueRegisterField rxBufferSize;
+
+        private enum Registers
+        {
+            Configuration = 0x0,
+            RxBufferSize = 0x4,
+            RxBufferBaseAddress1 = 0x8,
+            RxBufferBaseAddress2 = 0xc,
+        }
+    }
+}
diff --git a/shodan_infrastructure/Hm01b0Video.cs b/shodan_infrastructure/Hm01b0Video.cs
new file mode 100644
index 0000000..959e762
--- /dev/null
+++ b/shodan_infrastructure/Hm01b0Video.cs
@@ -0,0 +1,107 @@
+//
+// Copyright (c) 2022 Google LLC
+//
+// This file is licensed under the MIT License.
+// Full license text is available in 'licenses/MIT.txt'.
+//
+
+using System;
+using System.IO;
+using Antmicro.Renode.Exceptions;
+using Antmicro.Renode.Logging;
+using Antmicro.Renode.Peripherals.Sensor;
+using Antmicro.Renode.Peripherals.I2C;
+
+namespace Antmicro.Renode.Peripherals.Sensors
+{
+    public class Hm01b0Video : I2CPeripheralBase<HiMaxHM01B0.Registers>, ICPIPeripheral, ISensor
+    {
+        public Hm01b0Video() : base(16)
+        {
+            imageData = new byte[0];
+            imageIdx = 0;
+            LoadFrame();
+        }
+
+        public void Reset()
+        {
+            base.Reset();
+            imageIdx = 0;
+            LoadFrame();
+        }
+
+        void LoadFrame()
+        {
+            var filename = frames[imageIdx];
+            try
+            {
+                imageData = File.ReadAllBytes(filename);
+
+                this.NoisyLog("Loaded {0} bytes of image data to buffer", imageData.Length);
+            }
+            catch(Exception e)
+            {
+                throw new RecoverableException($"Could not load image {filename}: {(e.Message)}");
+            }
+        }
+
+        public byte[] ReadFrame()
+        {
+            var ret = imageData;
+
+            imageIdx += 1;
+            if(imageIdx == frames.Length) {
+                imageIdx = 0;
+            }
+            LoadFrame();
+
+            return ret;
+        }
+
+        protected override void DefineRegisters()
+        {
+        }
+
+        private byte[] imageData;
+        private uint imageIdx;
+        private string[] frames = {
+            "../../out/springbok_iree/quant_models/hps_0_quant_input",
+            "../../out/springbok_iree/quant_models/hps_1_quant_input",
+            "../../out/springbok_iree/quant_models/hps_2_quant_input",
+            "../../out/springbok_iree/quant_models/hps_3_quant_input",
+            "../../out/springbok_iree/quant_models/hps_4_quant_input",
+            "../../out/springbok_iree/quant_models/hps_5_quant_input",
+        };
+
+        // based on https://github.com/sparkfun/SparkFun_Apollo3_AmbiqSuite_BSPs/blob/master/common/third_party/hm01b0/HM01B0.h
+        public enum Registers
+        {
+            ModelIdHigh = 0x0000,
+            ModelIdLow = 0x0001,
+            SiliconRevision = 0x0002,
+            FrameCount = 0x0005,
+            PixelOrder = 0x0006,
+
+            ModeSelect = 0x0100,
+            ImageOrientation = 0x0101,
+            SoftwareReset = 0x0103,
+            GRP_ParamHold = 0x0104,
+
+            IntegrationHigh = 0x0202,
+            IntegrationLow = 0x0203,
+            AnalogGain = 0x0205,
+            DigitalGainHigh = 0x020E,
+            DigitalGainLow = 0x020F,
+
+            AE_TargetMean = 0x2101,
+            AE_MinMean = 0x2102,
+            ConvergeInThreshold = 0x2103,
+            ConvergeOutThreshold = 0x2104,
+
+            I2C_IdSelection = 0x3400,
+            I2C_Id = 0x3401,
+
+            PMU_ProgrammableFrameCount = 0x3020,
+        }
+    }
+}