renode: Initial implementation of Matcha I2S driver

Change-Id: Id1f04d2b8bd64cddc08531d40ccc2903b0fa719b
diff --git a/platforms/shodan.repl b/platforms/shodan.repl
index 07c8135..b848959 100644
--- a/platforms/shodan.repl
+++ b/platforms/shodan.repl
@@ -41,3 +41,12 @@
 // ISP               [‘h4200_0000 - ‘h4200_FFFF)  64KB ISP registers
 // DMA Ctrl          [‘h4201_0000 - ‘h4201_FFFF)  64KB DMA control interface
 // DSP Ctrl          [‘h4202_0000 - ‘h4202_FFFF)  64KB Audio DSP control interface
+
+// TOP_MATCHA_I2S0_BASE_ADDR @ top_matcha.h
+// I2S0              ['h5410_0000 - 'h5410_0040)  64B registers
+i2s0 : Sound.MatchaI2S @ sysbus 0x54100000
+    TxWatermarkIRQ -> smc_plic@39       // kTopMatchaPlicIrqIdI2s0TxWatermark @ top_matcha.h
+    RxWatermarkIRQ -> smc_plic@40       // kTopMatchaPlicIrqIdI2s0RxWatermark @ top_matcha.h
+    TxEmptyIRQ -> smc_plic@41              // kTopMatchaPlicIrqIdI2s0TxEmpty @ top_matcha.h
+    RxOverflowIRQ -> smc_plic@42           // kTopMatchaPlicIrqIdI2s0RxOverflow @ top_matcha.h
+
diff --git a/platforms/smc.repl b/platforms/smc.repl
index e28f711..c9835f9 100644
--- a/platforms/smc.repl
+++ b/platforms/smc.repl
@@ -43,8 +43,8 @@
     // Bit 11 corresponds MEIP and bit 9 to SEIP.
     0 -> cpu1@11
     1 -> cpu1@9
-    // Must be >= kTopMatchaPlicIrqIdLastSmc in top_matcha.h
-    numberOfSources: 34
+    // Must be kTopMatchaPlicIrqIdLastSmc + 1 from top_matcha.h
+    numberOfSources: 43
     numberOfContexts: 2  // MEIP and SEIP
 
 uart5: UART.OpenTitan_UART @ sysbus 0x50000000
diff --git a/shodan.resc b/shodan.resc
index 1c9a0c7..4892d82 100644
--- a/shodan.resc
+++ b/shodan.resc
@@ -16,6 +16,8 @@
 include @sim/config/shodan_infrastructure/Mailbox.cs
 include @sim/renode/tools/sel4_extensions/seL4Extensions.cs
 include @sim/config/shodan_infrastructure/AddressRangeStub.cs
+include @sim/config/shodan_infrastructure/MatchaI2S.cs
+EnsureTypeIsLoaded "Antmicro.Renode.Peripherals.Sound.MatchaI2S"
 
 $repl_file ?= @sim/config/platforms/shodan.repl
 
@@ -58,6 +60,16 @@
 sysbus LoadBinary $sc_bin $eflash_address
 sysbus LoadSymbolsFrom $kernel
 
+# If we have a I2S peripheral, setup an audio file that we can use
+# for sampling data. Note that the format for these files is raw
+# sample data, left channel followed by right.
+$i2s_mic_audio_file     ?= @sim/config/shodan_infrastructure/test.raw
+$i2s_speaker_audio_file ?= @out/speaker.raw
+
+# Uncomment these lines to enable audio
+# i2s0 InputFile $i2s_mic_audio_file
+# i2s0 OutputFile $i2s_speaker_audio_file
+
 # Start cpu0 at the bootrom reset vector, which is stored immediately after the
 # bootrom interrupt vector table at 0x8080.
 # (see https://ibex-core.readthedocs.io/en/latest/03_reference/exception_interrupts.html for details)
diff --git a/shodan_infrastructure/MatchaI2S.cs b/shodan_infrastructure/MatchaI2S.cs
new file mode 100644
index 0000000..56def9d
--- /dev/null
+++ b/shodan_infrastructure/MatchaI2S.cs
@@ -0,0 +1,330 @@
+//
+// Copyright (c) 2010-2023 Google
+//
+// This file is licensed under the MIT License.
+// Full license text is available in 'licenses/MIT.txt'.
+//
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+
+using Antmicro.Renode.Core;
+using Antmicro.Renode.Core.Structure.Registers;
+using Antmicro.Renode.Exceptions;
+using Antmicro.Renode.Logging;
+using Antmicro.Renode.Peripherals.Miscellaneous;
+using Antmicro.Renode.Peripherals.Bus;
+using Antmicro.Renode.Utilities;
+using Antmicro.Renode.Debugging;
+using Antmicro.Renode.Sound;
+
+namespace Antmicro.Renode.Peripherals.Sound
+{
+    public class Fifo
+    {
+        public void Write(ulong value)
+        {
+            lock (synclock)
+            {
+                if (!Full())
+                {
+                    fifo[cursor_w] = value;
+                    cursor_w = (cursor_w + 1) & FIFO_MASK;
+                    count++;
+                }
+            }
+        }
+
+        public ulong Read()
+        {
+            ulong result = 0;
+
+            lock (synclock)
+            {
+                if (!Empty())
+                {
+                    result = fifo[cursor_r];
+                    cursor_r = (cursor_r + 1) & FIFO_MASK;
+                    count--;
+                }
+
+                return result;
+            }
+        }
+
+        public bool Empty()
+        {
+            return count == 0;
+        }
+
+        public bool Full()
+        {
+            return count == FIFO_SIZE;
+        }
+
+        public void Flush()
+        {
+            lock (synclock)
+            {
+                cursor_r = 0;
+                cursor_w = 0;
+                count = 0;
+                for (int i = 0; i < FIFO_SIZE; i++)
+                    fifo[i] = 0;
+            }
+        }
+
+        const uint FIFO_SIZE = 64 * 2;
+        const uint FIFO_MASK = FIFO_SIZE - 1;
+
+        public uint cursor_r = 0;
+        public uint cursor_w = 0;
+        public uint count = 0;
+        public ulong[] fifo = new ulong[FIFO_SIZE];
+
+        private readonly object synclock = new object();
+    }
+
+    public class MatchaI2S : BasicDoubleWordPeripheral, IDisposable, IKnownSize, INumberedGPIOOutput
+    {
+        public MatchaI2S(Machine machine) : base(machine)
+        {
+            CreateRegisters();
+
+            TxWatermarkIRQ = new GPIO();
+            RxWatermarkIRQ = new GPIO();
+            TxEmptyIRQ = new GPIO();
+            RxOverflowIRQ = new GPIO();
+
+            var irqs = new Dictionary<int, IGPIO>();
+            irqs[(int)Events.TxWatermarkIRQ] = TxWatermarkIRQ;
+            irqs[(int)Events.RxWatermarkIRQ] = RxWatermarkIRQ;
+            irqs[(int)Events.TxEmptyIRQ] = TxEmptyIRQ;
+            irqs[(int)Events.RxOverflowIRQ] = RxOverflowIRQ;
+            Connections = new ReadOnlyDictionary<int, IGPIO>(irqs);
+
+            Reset();
+        }
+
+        public override void Reset()
+        {
+            base.Reset();
+            InputFile = "";
+            OutputFile = "";
+
+            ResetTx();
+            ResetRx();
+        }
+
+        private void ResetTx()
+        {
+            encoder?.FlushBuffer();
+            txThread = null;
+            txBuffer = new Fifo();
+        }
+
+        private void ResetRx()
+        {
+            decoder?.Reset();
+            rxThread = null;
+            rxBuffer = new Fifo();
+
+            rxTriggerLevel.Value = 0b010;
+            mappedRxTriggerLevel = 8;
+        }
+
+        public void Dispose()
+        {
+            encoder?.Dispose();
+        }
+
+        public string InputFile { get; set; }
+        public string OutputFile { get; set; }
+
+        public const uint SampleFrequencyHz = 16_000;
+        public const uint SampleSizeBits = 16;
+        public const uint NumChannels = 2;
+        public const uint BufferSizeWords = 64;
+
+        private Fifo rxBuffer;
+        private Fifo txBuffer;
+
+        public IReadOnlyDictionary<int, IGPIO> Connections { get; }
+
+        private void StartRx()
+        {
+            if (InputFile == "") {
+                this.Log(LogLevel.Error, "Starting recording without an input file!");
+                return;
+            }
+
+            decoder = new PCMDecoder(SampleSizeBits, SampleFrequencyHz, NumChannels, false, this);
+            decoder.LoadFile(InputFile);
+
+            this.Log(LogLevel.Info, "Starting recording.");
+            rxThread = machine.ObtainManagedThread(InputFrames, SampleFrequencyHz);
+            rxThread.Start();
+        }
+
+        private void StartTx()
+        {
+        }
+
+        private void StopThread(ref IManagedThread thread)
+        {
+            thread?.Stop();
+            thread = null;
+        }
+
+        private void OutputFrames()
+        {
+        }
+
+        private void InputFrames()
+        {
+            while (!rxBuffer.Full())
+            {
+                // Decode left on the upper half of the sample
+                ulong sample = (decoder.GetSingleSample() << 16);
+                sample |= decoder.GetSingleSample();
+
+                if (!rxBuffer.Full()) {
+                    rxBuffer.Write(sample);
+
+                    if (rxBuffer.count == mappedRxTriggerLevel)
+                    {
+                        this.Log(LogLevel.Debug, "I2S RX FIFO Watermark.");
+                        Connections[(int)Events.RxWatermarkIRQ].Blink();
+                    }
+                }
+            }
+
+            Connections[(int)Events.RxOverflowIRQ].Blink();
+        }
+
+        private void CreateRegisters()
+        {
+            Registers.Control.Define(this)
+                .WithFlag(0, out txEnable, name: "TX",
+                    writeCallback: (_, val) => {
+                        if (val) {
+                            StartTx();
+                        } else {
+                            StopThread(ref txThread);
+                        }
+                    })
+                .WithFlag(1, out rxEnable, name: "RX",
+                    writeCallback: (_, val) => {
+                        if (val) {
+                            StartRx();
+                        } else {
+                            StopThread(ref rxThread);
+                        }
+                    })
+                .WithFlag(2, out loopbackEnable, name: "SLPBK")
+                .WithReservedBits(3, 15)
+                .WithValueField(18, 6, out rxClkDivide, name: "NCO_RX")
+                .WithValueField(25, 6, out txClkDivide, name: "NCO_TX");
+            Registers.Status.Define32(this)
+                .WithFlag(0, name: "TXFULL", valueProviderCallback: _ => { return txBuffer.Full(); })
+                .WithFlag(1, name: "RXFULL", valueProviderCallback: _ => { return rxBuffer.Full(); })
+                .WithFlag(2, name: "TXEMPTY", valueProviderCallback: _ => { return txBuffer.Empty(); })
+                .WithFlag(3, name: "RXEMPTY", valueProviderCallback: _ => { return rxBuffer.Empty(); })
+                .WithReservedBits(4, 27);
+            Registers.ReadData.Define32(this)
+                .WithValueField(0, 31, FieldMode.Read, name: "RDATA",
+                    valueProviderCallback: _ => { return rxBuffer.Read(); });
+            Registers.WriteData.Define32(this)
+                .WithValueField(0, 31, name: "WDATA",
+                writeCallback: (_, val) => { txBuffer.Write(val); });
+            Registers.FifoControl.Define32(this)
+                .WithFlag(0, name: "RXRST", writeCallback: (_, val) => { if (val) ResetRx(); })
+                .WithFlag(1, name: "TXRST", writeCallback: (_, val) => { if (val) ResetTx(); })
+                .WithValueField(2, 3, out rxTriggerLevel,
+                    writeCallback: (_, val) => {
+                        switch (val) {
+                            case 0b000: mappedRxTriggerLevel = 1; break;
+                            case 0b001: mappedRxTriggerLevel = 4; break;
+                            case 0b010: mappedRxTriggerLevel = 8; break;
+                            case 0b011: mappedRxTriggerLevel = 16; break;
+                            case 0b100: mappedRxTriggerLevel = 30; break;
+                            default:
+                                this.Log(LogLevel.Error, "Trying to set invalid RX trigger level. Setting to default");
+                                val = 0b010;
+                                mappedRxTriggerLevel = 8;
+                                break;
+                        }
+                    },
+                    name: "RXILVL")
+                .WithValueField(5, 2, out txTriggerLevel,
+                    writeCallback: (_, val) => {
+                        val = 0b01;
+                    },
+                    name: "TXILVL")
+                .WithReservedBits(7, 24);
+            Registers.FifoStatus.Define32(this)
+                .WithValueField(0, 6, name: "TXLVL",
+            valueProviderCallback: _ => { return txBuffer.count; })
+                .WithReservedBits(7, 9)
+                .WithValueField(16, 6, name: "RXLVL",
+            valueProviderCallback: _ => { return rxBuffer.count; })
+                .WithReservedBits(22, 10);
+        }
+
+        private IFlagRegisterField txEnable;
+        private IFlagRegisterField rxEnable;
+        private IFlagRegisterField loopbackEnable;
+
+        private IValueRegisterField rxClkDivide;
+        private IValueRegisterField txClkDivide;
+        private IValueRegisterField rxTriggerLevel;
+        private IValueRegisterField txTriggerLevel;
+
+        private int mappedRxTriggerLevel;
+        private int mappedTxTriggerLevel;
+
+        private IManagedThread rxThread;
+        private IManagedThread txThread;
+        private PCMDecoder decoder;
+        private PCMEncoder encoder;
+
+        public GPIO TxWatermarkIRQ { get; }
+        public GPIO RxWatermarkIRQ { get; }
+        public GPIO TxEmptyIRQ { get; }
+        public GPIO RxOverflowIRQ { get; }
+
+        private enum Events
+        {
+            TxWatermarkIRQ = 0,  // raised if the xmit FIFO is below the high watermark
+            RxWatermarkIRQ = 1,  // raised if the recv FIFO is above the high watermark
+            TxEmptyIRQ     = 2,  // raised if the xmit FIFO is empty
+            RxOverflowIRQ  = 3,  // raised if the recv FIFO has overflowed
+        }
+
+        private enum Registers
+        {
+            Control     = 0x0,  // Control register
+            Status      = 0x1,  // I2S Live Status register
+                                //   bit 0: TX buffer full
+                                //   bit 1: RX buffer full
+                                //   bit 2: TX FIFO empty
+                                //   bit 3: RX FIFO empty
+            ReadData    = 0x2,  // I2S read data
+            WriteData   = 0x3,  // I2S write data
+            FifoControl = 0x4,  // I2S FIFO control register
+                                //   bit 0: RX FIFO reset (write 1 to reset)
+                                //   bit 1: TX FIFO reset (write 1 to reset)
+                                //   bit 4-2: Trigger level for RX interrupts
+                                //   bit 6-5: Trigger level for TX interrupts
+            FifoStatus  = 0x5,  // I2S FIFO status register
+                                //   bit 5:0: Current fill level of TX fifo
+                                //   bit 21:16: Current fill level of RX fifo.
+        }
+
+        public long Size => 0x40;
+    }
+}
diff --git a/shodan_infrastructure/test.raw b/shodan_infrastructure/test.raw
new file mode 100644
index 0000000..d868cec
--- /dev/null
+++ b/shodan_infrastructure/test.raw
Binary files differ