i2s: Fill out I2S peripheral simulation

Renode managed "threads" are not actually threads. They're more
like real-time processing functions, or coroutines, than threads.
The prior behavior was executing so fast that the FIFO could never
be emptied fast enough to trigger an empty IRQ.

This change alters the behavior such that a single frame is
added to the FIFO each time the function is called.

Change-Id: I7bbff4f7cec7e8b7e0e3fede17512bc3f49b7179
diff --git a/shodan_infrastructure/MatchaI2S.cs b/shodan_infrastructure/MatchaI2S.cs
index 037ab35..d6fc044 100644
--- a/shodan_infrastructure/MatchaI2S.cs
+++ b/shodan_infrastructure/MatchaI2S.cs
@@ -67,12 +67,18 @@
 
         public bool Empty()
         {
-            return count == 0;
+            lock (synclock)
+            {
+                return count == 0;
+            }
         }
 
         public bool Full()
         {
-            return count == FIFO_SIZE;
+            lock (synclock)
+            {
+                return count == FIFO_SIZE;
+            }
         }
 
         public void Flush()
@@ -87,13 +93,15 @@
             }
         }
 
-        const uint FIFO_SIZE = 64 * 2;
+        public uint Count { get { lock(synclock) { return count; } } }
+
+        const uint FIFO_SIZE = 63;
         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 uint cursor_r = 0;
+        private uint cursor_w = 0;
+        private uint count = 0;
+        private ulong[] fifo = new ulong[FIFO_SIZE];
 
         private readonly object synclock = new object();
     }
@@ -175,12 +183,15 @@
             decoder.LoadFile(InputFile);
 
             this.Log(LogLevel.Info, "Starting recording.");
-            rxThread = machine.ObtainManagedThread(InputFrames, SampleFrequencyHz);
+            rxThread = machine.ObtainManagedThread(ReceiveFrame, SampleFrequencyHz);
             rxThread.Start();
         }
 
         private void StartTx()
         {
+            this.Log(LogLevel.Info, "Starting playback.");
+            txThread = machine.ObtainManagedThread(TransmitFrame, SampleFrequencyHz);
+            txThread.Start();
         }
 
         private void StopThread(ref IManagedThread thread)
@@ -189,39 +200,65 @@
             thread = null;
         }
 
-        private void OutputFrames()
+        // Note: this is not a real thread! This is simply a function that is
+        // called at a known frequency from the machine simulation. It must
+        // always complete and never loop!
+        private void TransmitFrame()
         {
-        }
-
-        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) && (rxWatermarkIrqEnabled.Value))
-                    {
-                        this.Log(LogLevel.Debug, "I2S RX FIFO Watermark.");
-                        Connections[(int)Events.RxWatermarkIRQ].Blink();
-                    }
-                }
+            if (txBuffer.Empty()) {
+                // FIFO is empty -- nothing to do except set the IRQ and return.
+                this.Log(LogLevel.Debug, "I2S TX FIFO Empty.", txBuffer.Count, mappedTxTriggerLevel);
+                TxEmptyIRQ.Set(txEmptyIrqEnabled.Value);
+                return;
             }
 
-            if (rxOverflowIrqEnabled.Value) {
-                Connections[(int)Events.RxOverflowIRQ].Blink();
+            txBuffer.Read();
+            this.Log(LogLevel.Noisy, "Removed sample. Level: {0}", txBuffer.Count);
+
+            if (txBuffer.Count < mappedTxTriggerLevel)
+            {
+                this.Log(LogLevel.Debug, "I2S TX FIFO Watermark (count {0} < {1}).", txBuffer.Count, mappedTxTriggerLevel);
+                TxWatermarkIRQ.Set(txWatermarkIrqEnabled.Value);
+            }
+        }
+
+        // Note: this is not a real thread! This is simply a function that is
+        // called at a known frequency from the machine simulation. It must
+        // always complete and never loop!
+        private void ReceiveFrame()
+        {
+            // Decode left on the upper half of the sample
+            ulong sample = (decoder.GetSingleSample() << 16);
+            sample |= decoder.GetSingleSample();
+
+            if (rxBuffer.Full())
+            {
+                this.Log(LogLevel.Noisy, "RX buffer full");
+                if (rxOverflowIrqEnabled.Value)
+                {
+                    this.Log(LogLevel.Debug, "I2S RX FIFO Overflow (count {0}).", rxBuffer.Count);
+                    RxOverflowIRQ.Set(rxOverflowIrqEnabled.Value);
+                }
+
+                // FIFO is full -- drop the sample
+                return;
+            }
+
+            rxBuffer.Write(sample);
+            this.Log(LogLevel.Noisy, "Added sample. Level: {0}", rxBuffer.Count);
+
+            if (rxBuffer.Count >= mappedRxTriggerLevel)
+            {
+                this.Log(LogLevel.Debug, "I2S RX FIFO Watermark (count {0} == {1}).", rxBuffer.Count, mappedRxTriggerLevel);
+                RxWatermarkIRQ.Set(rxWatermarkIrqEnabled.Value);
             }
         }
 
         private void CreateRegisters()
         {
             Registers.IrqState.Define(this)
-                .WithFlag(0, name: "TX_WATERMARK", valueProviderCallback: _ => { return txBuffer.count >= mappedTxTriggerLevel; })
-                .WithFlag(1, name: "RX_WATERMARK", valueProviderCallback: _ => { return rxBuffer.count >= mappedRxTriggerLevel; })
+                .WithFlag(0, name: "TX_WATERMARK", valueProviderCallback: _ => { return txBuffer.Count >= mappedTxTriggerLevel; })
+                .WithFlag(1, name: "RX_WATERMARK", valueProviderCallback: _ => { return rxBuffer.Count >= mappedRxTriggerLevel; })
                 .WithFlag(2, name: "TX_EMPTY", valueProviderCallback: _ => { return txBuffer.Empty(); })
                 .WithFlag(3, name: "RX_OVERFLOW", valueProviderCallback: _ => { return rxBuffer.Full(); });
 
@@ -233,13 +270,13 @@
 
             Registers.IrqTest.Define(this)
                 .WithFlag(0, name: "TEST_TX_WATERMARK",
-                        writeCallback: (_, __) => { Connections[(int)Events.TxWatermarkIRQ].Blink(); })
+                        writeCallback: (_, val) => { TxWatermarkIRQ.Set(val); })
                 .WithFlag(1, name: "TEST_RX_WATERMARK",
-                        writeCallback: (_, __) => { Connections[(int)Events.RxWatermarkIRQ].Blink(); })
+                        writeCallback: (_, val) => { RxWatermarkIRQ.Set(val); })
                 .WithFlag(2, name: "TEST_TX_EMPTY",
-                        writeCallback: (_, __) => { Connections[(int)Events.TxEmptyIRQ].Blink(); })
+                        writeCallback: (_, val) => { TxEmptyIRQ.Set(val); })
                 .WithFlag(3, name: "TEST_RX_OVERFLOW",
-                        writeCallback: (_, __) => { Connections[(int)Events.RxOverflowIRQ].Blink(); });
+                        writeCallback: (_, val) => { RxOverflowIRQ.Set(val); });
 
             Registers.Control.Define(this)
                 .WithFlag(0, out txEnable, name: "TX",
@@ -271,12 +308,30 @@
                 .WithReservedBits(4, 27);
 
             Registers.ReadData.Define32(this)
-                .WithValueField(0, 31, FieldMode.Read, name: "RDATA",
-                    valueProviderCallback: _ => { return rxBuffer.Read(); });
+                .WithValueField(0, 32, FieldMode.Read, name: "RDATA",
+                    valueProviderCallback: _ => { 
+                        ulong sample = rxBuffer.Read();
+                        if (rxBuffer.Count < mappedRxTriggerLevel) {
+                            RxWatermarkIRQ.Set(false);
+                            this.Log(LogLevel.Noisy, "RX Watermark cleared");
+                        }
+                        RxOverflowIRQ.Set(false);
+                        this.Log(LogLevel.Noisy, "Removed sample. Level: {0}", rxBuffer.Count);
+
+                        return sample;
+                    });
 
             Registers.WriteData.Define32(this)
-                .WithValueField(0, 31, name: "WDATA",
-                writeCallback: (_, val) => { txBuffer.Write(val); });
+                .WithValueField(0, 32, name: "WDATA",
+                    writeCallback: (_, val) => {
+                        txBuffer.Write(val);
+                        if (txBuffer.Count > mappedTxTriggerLevel) {
+                            TxWatermarkIRQ.Set(false);
+                            this.Log(LogLevel.Noisy, "TX Watermark cleared");
+                        } 
+                        TxEmptyIRQ.Set(false);
+                        this.Log(LogLevel.Noisy, "Added sample. Level: {0}", txBuffer.Count);
+                    });
 
             Registers.FifoControl.Define32(this)
                 .WithFlag(0, name: "RXRST", writeCallback: (_, val) => { if (val) ResetRx(); })
@@ -316,10 +371,10 @@
 
             Registers.FifoStatus.Define32(this)
                 .WithValueField(0, 6, name: "TXLVL",
-            valueProviderCallback: _ => { return txBuffer.count; })
+                    valueProviderCallback: _ => { return txBuffer.Count; })
                 .WithReservedBits(7, 9)
                 .WithValueField(16, 6, name: "RXLVL",
-            valueProviderCallback: _ => { return rxBuffer.count; })
+                    valueProviderCallback: _ => { return rxBuffer.Count; })
                 .WithReservedBits(22, 10);
         }