Merge "testing: Unify the platform names to be more programmatic."
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);
         }