blob: 03841bd0dec4d2916804629695d9dc3d545c1704 [file] [log] [blame]
//
// Copyright (c) 2010-2021 Antmicro
//
// This file is licensed under the MIT License.
// Full license text is available in 'licenses/MIT.txt'.
//
using System.Collections.Generic;
using Antmicro.Renode.Core;
using Antmicro.Renode.Core.Structure.Registers;
using Antmicro.Renode.Logging;
using Antmicro.Renode.Peripherals.Bus;
namespace Antmicro.Renode.Peripherals.UART
{
// This model currently does not support timeout feature, rx break detection and software tx pin override
public class OpenTitan2_UART : UARTBase, IDoubleWordPeripheral, IKnownSize
{
public OpenTitan2_UART(Machine machine) : base(machine)
{
TxWatermarkIRQ = new GPIO();
RxWatermarkIRQ = new GPIO();
TxEmptyIRQ = new GPIO();
RxOverflowIRQ = new GPIO();
RxFrameErrorIRQ = new GPIO();
RxBreakErrorIRQ = new GPIO();
RxTimeoutIRQ = new GPIO();
RxParityErrorIRQ = new GPIO();
registers = new DoubleWordRegisterCollection(this, BuildRegisterMap());
txQueue = new Queue<byte>();
}
public uint ReadDoubleWord(long offset)
{
return registers.Read(offset);
}
public void WriteDoubleWord(long offset, uint value)
{
registers.Write(offset, value);
}
public override void WriteChar(byte value)
{
if(lineLoopbackEnabled.Value)
{
txOngoing = true;
TransmitCharacter(value);
this.Log(LogLevel.Noisy, "Line Loopback Enabled, byte echoed by hardware.");
}
if(systemLoopbackEnabled.Value)
{
this.Log(LogLevel.Warning, "Sytem Loopback Enabled, incoming byte not queued.");
return;
}
if(!rxEnabled.Value)
{
this.Log(LogLevel.Warning, "CTRL.RX is unset, incoming byte not queued.");
return;
}
if(Count < rxFIFOCapacity)
{
rxOverflowPending.Value = false;
base.WriteChar(value);
}
else
{
rxOverflowPending.Value = true;
this.Log(LogLevel.Noisy, "RX FIFO overflowed, queueing incoming byte anyway.");
base.WriteChar(value);
}
UpdateInterrupts();
}
public override void Reset()
{
base.Reset();
registers.Reset();
txQueue.Clear();
UpdateInterrupts();
txOngoing = false;
txWatermarkCrossed = false;
}
public long Size => 0x30;
public GPIO TxWatermarkIRQ { get; }
public GPIO RxWatermarkIRQ { get; }
public GPIO TxEmptyIRQ { get; }
public GPIO RxOverflowIRQ { get; }
public GPIO RxFrameErrorIRQ { get; }
public GPIO RxBreakErrorIRQ { get; }
public GPIO RxTimeoutIRQ { get; }
public GPIO RxParityErrorIRQ { get; }
public override Bits StopBits => Bits.One;
public override Parity ParityBit => parityTypeField.Value == ParityType.Odd ? Parity.Odd : Parity.Even;
public override uint BaudRate => (uint)((baudClockRate.Value * fixedClockFrequency) >> 20);
protected override void CharWritten()
{
// intentionally left empty
}
protected override void QueueEmptied()
{
// intentionally left empty
}
private Dictionary<long, DoubleWordRegister> BuildRegisterMap()
{
return new Dictionary<long, DoubleWordRegister>
{
{(long)Registers.InterruptState, new DoubleWordRegister(this)
.WithFlag(0, out txWatermarkPending, FieldMode.Read | FieldMode.WriteOneToClear, name: "INTR_STATE.tx_watermark")
.WithFlag(1, out rxWatermarkPending, FieldMode.Read | FieldMode.WriteOneToClear, name: "INTR_STATE.rx_watermark")
.WithFlag(2, out txEmptyPending, FieldMode.Read | FieldMode.WriteOneToClear, name: "INTR_STATE.tx_empty")
.WithFlag(3, out rxOverflowPending, FieldMode.Read | FieldMode.WriteOneToClear, name: "INTR_STATE.rx_overflow")
.WithFlag(4, out rxFrameErrorPending, FieldMode.Read | FieldMode.WriteOneToClear, name: "INTR_STATE.rx_frame_err")
.WithFlag(5, out rxBreakErrorPending, FieldMode.Read | FieldMode.WriteOneToClear, name: "INTR_STATE.rx_break_err")
.WithFlag(6, out rxTimeoutPending, FieldMode.Read | FieldMode.WriteOneToClear, name: "INTR_STATE.rx_timeout")
.WithFlag(7, out rxParityErrorPending, FieldMode.Read | FieldMode.WriteOneToClear, name: "INTR_STATE.rx_parity_err")
.WithReservedBits(8, 24)
.WithWriteCallback((_, __) => UpdateInterrupts())
},
{(long)Registers.InterruptEnable, new DoubleWordRegister(this)
.WithFlag(0, out txWatermarkEnabled, name: "INTR_ENABLE.tx_watermark")
.WithFlag(1, out rxWatermarkEnabled, name: "INTR_ENABLE.rx_watermark")
.WithFlag(2, out txEmptyEnabled, name: "INTR_ENABLE.tx_empty")
.WithFlag(3, out rxOverflowEnabled, name: "INTR_ENABLE.rx_overflow")
.WithFlag(4, out rxFrameErrorEnabled, name: "INTR_ENABLE.rx_frame_err")
.WithFlag(5, out rxBreakErrorEnabled, name: "INTR_ENABLE.rx_break_err")
.WithFlag(6, out rxTimeoutEnabled, name: "INTR_ENABLE.rx_timeout")
.WithFlag(7, out rxParityErrorEnabled, name: "INTR_ENABLE.rx_parity_err")
.WithReservedBits(8, 24)
.WithWriteCallback((_, __) => UpdateInterrupts())
},
{(long)Registers.InterruptTest, new DoubleWordRegister(this)
.WithFlag(0, FieldMode.Write, writeCallback: (_, val) => { txWatermarkPending.Value |= val; }, name: "INTR_TEST.tx_watermark")
.WithFlag(1, FieldMode.Write, writeCallback: (_, val) => { rxWatermarkPending.Value |= val; }, name: "INTR_TEST.rx_watermark")
.WithFlag(2, FieldMode.Write, writeCallback: (_, val) => { txEmptyPending.Value |= val; }, name: "INTR_TEST.tx_empty")
.WithFlag(3, FieldMode.Write, writeCallback: (_, val) => { rxOverflowPending.Value |= val; }, name: "INTR_TEST.rx_overflow")
.WithFlag(4, FieldMode.Write, writeCallback: (_, val) => { rxFrameErrorPending.Value |= val; }, name: "INTR_TEST.rx_frame_err")
.WithFlag(5, FieldMode.Write, writeCallback: (_, val) => { rxBreakErrorPending.Value |= val; }, name: "INTR_TEST.rx_break_err")
.WithFlag(6, FieldMode.Write, writeCallback: (_, val) => { rxTimeoutPending.Value |= val; }, name: "INTR_TEST.rx_timeout")
.WithFlag(7, FieldMode.Write, writeCallback: (_, val) => { rxParityErrorPending.Value |= val; }, name: "INTR_TEST.rx_parity_err")
.WithReservedBits(8, 24)
.WithWriteCallback((_, __) => UpdateInterrupts())
},
{(long)Registers.Control, new DoubleWordRegister(this)
.WithFlag(0, out txEnabled, name: "CTRL.TX", writeCallback: (_, val) =>
{
if(val)
{
if(!lineLoopbackEnabled.Value)
{
foreach(byte value in txQueue)
{
txOngoing = true;
TransmitCharacter(value);
}
}
txQueue.Clear();
UpdateInterrupts();
}
})
.WithFlag(1, out rxEnabled, name: "CTRL.RX")
.WithFlag(2, out noiseFilterEnabled, name: "CTRL.NF")
.WithReservedBits(3, 1)
.WithFlag(4, out systemLoopbackEnabled, name: "CTRL.SLPBK")
.WithFlag(5, out lineLoopbackEnabled, name: "CTRL.LLPBK")
.WithFlag(6, out parityEnabled, name: "CTRL.PARITY_EN")
.WithEnumField(7, 1, out parityTypeField, name: "CTRL.PARITY_ODD")
.WithTag("CTRL.RXBLVL", 8, 2)
.WithReservedBits(10, 6)
.WithValueField(16, 16, out baudClockRate, name: "CTRL.NCO")
},
{(long)Registers.LiveStatus, new DoubleWordRegister(this)
.WithFlag(0, FieldMode.Read, valueProviderCallback: _ => txQueue.Count == txFIFOCapacity, name: "STATUS.TXFULL")
.WithFlag(1, FieldMode.Read, valueProviderCallback: _ => Count == rxFIFOCapacity, name: "STATUS.RXFULL")
.WithFlag(2, FieldMode.Read, valueProviderCallback: _ => txQueue.Count == 0, name: "STATUS.TXEMPTY")
.WithFlag(3, FieldMode.Read, valueProviderCallback: _ => txQueue.Count == 0, name: "STATUS.TXIDLE")
.WithFlag(4, FieldMode.Read, valueProviderCallback: _ => true, name: "STATUS.RXIDLE")
.WithFlag(5, FieldMode.Read, valueProviderCallback: _ => Count == 0, name: "STATUS.RXEMPTY")
.WithReservedBits(6, 26)
},
{(long)Registers.ReadData, new DoubleWordRegister(this)
.WithValueField(0, 8, FieldMode.Read, name: "RDATA", valueProviderCallback: _ =>
{
if(!TryGetCharacter(out var character))
{
this.Log(LogLevel.Warning, "Trying to read from an empty Rx FIFO.");
}
return character;
})
.WithReservedBits(8, 24)
.WithWriteCallback((_, __) => UpdateInterrupts())
},
{(long)Registers.WriteData, new DoubleWordRegister(this)
.WithValueField(0, 8, FieldMode.Write, name: "WDATA", writeCallback: (_, val) =>
{
if(systemLoopbackEnabled.Value)
{
base.WriteChar((byte)val);
return;
}
if(txEnabled.Value)
{
txOngoing = true;
TransmitCharacter((byte)val);
}
else if(txQueue.Count < txFIFOCapacity)
{
txQueue.Enqueue((byte)val);
if(txQueue.Count >= TxWatermarkValue) {
txWatermarkCrossed = true;
}
}
else
{
this.Log(LogLevel.Warning, "Trying to write to a full Tx FIFO.");
}
})
.WithReservedBits(8, 24)
.WithWriteCallback((_, __) => UpdateInterrupts())
},
{(long)Registers.FIFOControl, new DoubleWordRegister(this)
.WithFlag(0, FieldMode.Write, name: "FIFO_CTRL.RXRST", writeCallback: (_, val) =>
{
if(val)
{
ClearBuffer();
}
})
.WithFlag(1, FieldMode.Write, name: "FIFO_CTRL.TXRST", writeCallback: (_, val) =>
{
if(val)
{
txQueue.Clear();
txWatermarkCrossed = false;
}
})
.WithEnumField(2, 3, out rxWatermarkField, name: "FIFO_CTRL.RXILVL")
.WithEnumField(5, 2, out txWatermarkField, name: "FIFO_CTRL.TXILVL")
.WithReservedBits(7, 25)
.WithWriteCallback((_, __) => UpdateInterrupts())
},
{(long)Registers.FIFOStatus, new DoubleWordRegister(this)
.WithValueField(0, 6, FieldMode.Read, valueProviderCallback: _ => (uint)txQueue.Count, name: "FIFO_STATUS.TXLVL")
.WithReservedBits(6, 10)
.WithValueField(16, 6, FieldMode.Read, valueProviderCallback: _ => (uint)Count, name: "FIFO_STATUS.RXLVL")
.WithReservedBits(22, 10)
},
{(long)Registers.TxPinOverrideControl, new DoubleWordRegister(this)
.WithTaggedFlag("OVRD.TXEN", 0)
.WithTaggedFlag("OVRD.TXVAL", 1)
.WithReservedBits(2, 30)
},
{(long)Registers.OversampledValues, new DoubleWordRegister(this)
.WithTag("VAL.RX", 0, 16)
.WithReservedBits(16, 16)
},
{(long)Registers.RxTimeoutControl, new DoubleWordRegister(this)
.WithTag("TIMEOUT_CTRL.VAL", 0, 24)
.WithReservedBits(24, 7)
.WithTaggedFlag("TIMEOUT_CTRL.EN", 31)
}
};
}
private void UpdateInterrupts()
{
rxWatermarkPending.Value |= RxWatermarkValue <= Count;
if (txWatermarkCrossed && txQueue.Count < TxWatermarkValue) {
txWatermarkPending.Value = true;
txWatermarkCrossed = false;
}
if(txOngoing && txQueue.Count == 0)
{
txOngoing = false;
txEmptyPending.Value = true;
}
TxWatermarkIRQ.Set(txWatermarkPending.Value && txWatermarkEnabled.Value);
RxWatermarkIRQ.Set(rxWatermarkPending.Value && rxWatermarkEnabled.Value);
TxEmptyIRQ.Set(txEmptyPending.Value && txEmptyEnabled.Value);
RxOverflowIRQ.Set(rxOverflowPending.Value && rxOverflowEnabled.Value);
RxFrameErrorIRQ.Set(rxFrameErrorPending.Value && rxFrameErrorEnabled.Value);
RxBreakErrorIRQ.Set(rxBreakErrorPending.Value && rxBreakErrorEnabled.Value);
RxTimeoutIRQ.Set(rxTimeoutPending.Value && rxTimeoutEnabled.Value);
RxParityErrorIRQ.Set(rxParityErrorPending.Value && rxParityErrorEnabled.Value);
}
private int RxWatermarkValue
{
get
{
switch(rxWatermarkField.Value)
{
default:
this.Log(LogLevel.Error, "Unexpected state of rxWatermarkField ({0})", rxWatermarkField.Value);
return 1;
case RxWatermarkLevel.Level1:
return 1;
case RxWatermarkLevel.Level4:
return 4;
case RxWatermarkLevel.Level8:
return 8;
case RxWatermarkLevel.Level16:
return 16;
case RxWatermarkLevel.Level30:
return 30;
}
}
}
private int TxWatermarkValue
{
get
{
switch(txWatermarkField.Value)
{
default:
this.Log(LogLevel.Error, "Unexpected state of txWatermarkField ({0})", txWatermarkField.Value);
return 2;
case TxWatermarkLevel.Level1:
return 2;
case TxWatermarkLevel.Level4:
return 4;
case TxWatermarkLevel.Level8:
return 8;
case TxWatermarkLevel.Level16:
return 16;
}
}
}
private readonly DoubleWordRegisterCollection registers;
private readonly Queue<byte> txQueue;
// InterruptState
private IFlagRegisterField txWatermarkPending;
private IFlagRegisterField rxWatermarkPending;
private IFlagRegisterField txEmptyPending;
private IFlagRegisterField rxOverflowPending;
private IFlagRegisterField rxFrameErrorPending;
private IFlagRegisterField rxBreakErrorPending;
private IFlagRegisterField rxTimeoutPending;
private IFlagRegisterField rxParityErrorPending;
// InterruptEnable
private IFlagRegisterField txWatermarkEnabled;
private IFlagRegisterField rxWatermarkEnabled;
private IFlagRegisterField txEmptyEnabled;
private IFlagRegisterField rxOverflowEnabled;
private IFlagRegisterField rxFrameErrorEnabled;
private IFlagRegisterField rxBreakErrorEnabled;
private IFlagRegisterField rxTimeoutEnabled;
private IFlagRegisterField rxParityErrorEnabled;
// Control
private IFlagRegisterField txEnabled;
private IFlagRegisterField rxEnabled;
private IFlagRegisterField noiseFilterEnabled;
private IFlagRegisterField systemLoopbackEnabled;
private IFlagRegisterField lineLoopbackEnabled;
private IFlagRegisterField parityEnabled;
private IEnumRegisterField<ParityType> parityTypeField;
private IValueRegisterField baudClockRate;
// FIFOControl
private IEnumRegisterField<RxWatermarkLevel> rxWatermarkField;
private IEnumRegisterField<TxWatermarkLevel> txWatermarkField;
private bool txOngoing;
private bool txWatermarkCrossed;
private const int rxFIFOCapacity = 32;
private const int txFIFOCapacity = 32;
private const ulong fixedClockFrequency = 50000000;
private enum ParityType
{
Even = 0,
Odd = 1
}
private enum RxWatermarkLevel
{
Level1 = 0,
Level4 = 1,
Level8 = 2,
Level16 = 3,
Level30 = 4
}
private enum TxWatermarkLevel
{
Level1 = 0,
Level4 = 1,
Level8 = 2,
Level16 = 3
}
private enum Registers : long
{
InterruptState = 0x0,
InterruptEnable = 0x4,
InterruptTest = 0x8,
Control = 0xC,
LiveStatus = 0x10,
ReadData = 0x14,
WriteData = 0x18,
FIFOControl = 0x1C,
FIFOStatus = 0x20,
TxPinOverrideControl = 0x24,
OversampledValues = 0x28,
RxTimeoutControl = 0x2C
}
}
}