blob: 33cf1907a51371f0c3b2360c98f90496d6b59bc0 [file] [log] [blame]
//
// Copyright (c) 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
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()
{
lock (synclock)
{
return count == 0;
}
}
public bool Full()
{
lock (synclock)
{
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;
}
}
public override string ToString()
{
string result = "[";
for (uint i = 0; i < FIFO_SIZE; i++) {
result += (i == cursor_r) ? "r" : " ";
result += (i == cursor_w) ? "w" : " ";
result += String.Format("{0:X8} ", fifo[i]);
}
return result + "]";
}
public uint Count { get { lock(synclock) { return count; } } }
const uint FIFO_SIZE = 32;
const uint FIFO_MASK = FIFO_SIZE - 1;
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();
}
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(ReceiveFrame, SampleFrequencyHz, name: "i2s rx");
rxThread.Start();
}
private void StartTx()
{
if (OutputFile == "") {
this.Log(LogLevel.Error, "Starting playback without an output file!");
return;
}
this.Log(LogLevel.Info, "Starting playback.");
encoder = new PCMEncoder(SampleSizeBits, SampleFrequencyHz, NumChannels, false);
encoder.Output = OutputFile + String.Format(".{0}.{1:d4}",
Process.GetCurrentProcess().Id, outputFileIdx++);
encoder.SetBufferingBySamplesCount(1);
txThread = machine.ObtainManagedThread(TransmitFrame, SampleFrequencyHz, name: "i2s tx");
txThread.Start();
}
private void StopThread(ref IManagedThread thread)
{
thread?.Stop();
thread = null;
}
// 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()
{
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;
}
ulong sample = txBuffer.Read();
encoder.AcceptSample((uint)((sample & 0xFFFF0000) >> 16)); // left
encoder.AcceptSample((uint)(sample & 0x0000FFFF)); // right
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 ({1:X}). Level: {0}", rxBuffer.Count, sample);
this.Log(LogLevel.Noisy, rxBuffer.ToString());
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; },
writeCallback: (_, val) => { if (val) TxWatermarkIRQ.Set(false); })
.WithFlag(1, name: "RX_WATERMARK",
valueProviderCallback: _ => { return rxBuffer.Count >= mappedRxTriggerLevel; },
writeCallback: (_, val) => { if (val) RxWatermarkIRQ.Set(false); })
.WithFlag(2, name: "TX_EMPTY",
valueProviderCallback: _ => { return txBuffer.Empty(); },
writeCallback: (_, val) => { if (val) TxEmptyIRQ.Set(false); })
.WithFlag(3, name: "RX_OVERFLOW",
valueProviderCallback: _ => { return rxBuffer.Full(); },
writeCallback: (_, val) => { if (val) RxOverflowIRQ.Set(false); });
Registers.IrqEnable.Define(this)
.WithFlag(0, out txWatermarkIrqEnabled, name: "ENABLE_TX_WATERMARK")
.WithFlag(1, out rxWatermarkIrqEnabled, name: "ENABLE_RX_WATERMARK")
.WithFlag(2, out txEmptyIrqEnabled, name: "ENABLE_TX_EMPTY")
.WithFlag(3, out rxOverflowIrqEnabled, name: "ENABLE_RX_OVERFLOW");
Registers.IrqTest.Define(this)
.WithFlag(0, name: "TEST_TX_WATERMARK",
writeCallback: (_, val) => { TxWatermarkIRQ.Set(val); })
.WithFlag(1, name: "TEST_RX_WATERMARK",
writeCallback: (_, val) => { RxWatermarkIRQ.Set(val); })
.WithFlag(2, name: "TEST_TX_EMPTY",
writeCallback: (_, val) => { TxEmptyIRQ.Set(val); })
.WithFlag(3, name: "TEST_RX_OVERFLOW",
writeCallback: (_, val) => { RxOverflowIRQ.Set(val); });
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, 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 ({1:X}). Level: {0}", rxBuffer.Count, sample);
return sample;
});
Registers.WriteData.Define32(this)
.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 ({1:X}). Level: {0}", txBuffer.Count, 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) => {
switch (val) {
case 0b00: mappedTxTriggerLevel = 1; break;
case 0b01: mappedTxTriggerLevel = 4; break;
case 0b10: mappedTxTriggerLevel = 8; break;
case 0b11: mappedTxTriggerLevel = 16; break;
default:
this.Log(LogLevel.Error, "Trying to set invalid TX trigger level. Setting to default");
val = 0b00;
mappedTxTriggerLevel = 8;
break;
}
},
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 IFlagRegisterField txWatermarkIrqEnabled;
private IFlagRegisterField rxWatermarkIrqEnabled;
private IFlagRegisterField txEmptyIrqEnabled;
private IFlagRegisterField rxOverflowIrqEnabled;
private IValueRegisterField rxClkDivide;
private IValueRegisterField txClkDivide;
private IValueRegisterField rxTriggerLevel;
private IValueRegisterField txTriggerLevel;
private int mappedRxTriggerLevel;
private int mappedTxTriggerLevel;
private int outputFileIdx;
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; }
public string DumpFifos() {
return rxBuffer.ToString();
}
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
{
IrqState = 0x00, // Current interrupt states
// bit 0: TX watermark
// bit 1: RX watermark
// bit 2: TX empty
// bit 3: RX overflow
IrqEnable = 0x04, // Whether or not interrupts are enabled
// bit 0: TX watermark
// bit 1: RX watermark
// bit 2: TX empty
// bit 3: RX overflow
IrqTest = 0x08, // IRQ testing
// bit 0: TX watermark
// bit 1: RX watermark
// bit 2: TX empty
// bit 3: RX overflow
Control = 0x0c, // Control register
Status = 0x10, // 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 = 0x14, // I2S read data
WriteData = 0x18, // I2S write data
FifoControl = 0x1c, // 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 = 0x20, // 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;
}
}