| // | |
| // 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 | |
| } | |
| } | |
| } |