diff --git a/src/Emulator/Peripherals/Peripherals/Analog/MH1903_ADC.cs b/src/Emulator/Peripherals/Peripherals/Analog/MH1903_ADC.cs new file mode 100644 index 000000000..959f85e6c --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/Analog/MH1903_ADC.cs @@ -0,0 +1,63 @@ +using Antmicro.Renode.Core; +using Antmicro.Renode.Core.Structure.Registers; + +namespace Antmicro.Renode.Peripherals.Analog +{ + public class MH1903_ADC : BasicDoubleWordPeripheral, IKnownSize + { + public MH1903_ADC(IMachine machine) : base(machine) + { + DefineRegisters(); + } + + public GPIO IRQ { get; } = new GPIO(); + + public long Size => 0x100; + + private void DefineRegisters() + { + // Control1 at 0x00 + Registers.Control1.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "Control1"); + + // Status at 0x04 + Registers.Status.Define(this, resetValue: 0x00000001) + .WithFlag(0, FieldMode.Read, name: "Ready", valueProviderCallback: _ => true) + .WithReservedBits(1, 31); + + // Fifo at 0x08 + Registers.Fifo.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "Fifo"); + + // Data at 0x0C + Registers.Data.Define(this, resetValue: 0x00000D70) // 3440 = ~4.2V + .WithValueField(0, 16, FieldMode.Read, name: "Data", + valueProviderCallback: _ => adcData); + + // FifoLevel at 0x10 + Registers.FifoLevel.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "FifoLevel"); + + // FifoThreshold at 0x14 + Registers.FifoThreshold.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "FifoThreshold"); + + // Control2 at 0x18 + Registers.Control2.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "Control2"); + } + + private readonly ushort adcData = 0x0D70; // 3440 = ~4.2V + + private enum Registers : long + { + Control1 = 0x00, + Status = 0x04, + Fifo = 0x08, + Data = 0x0C, + FifoLevel = 0x10, + FifoThreshold = 0x14, + Control2 = 0x18, + } + } +} diff --git a/src/Emulator/Peripherals/Peripherals/Cache/MH1903_CACHE.cs b/src/Emulator/Peripherals/Peripherals/Cache/MH1903_CACHE.cs new file mode 100644 index 000000000..6ba6b96b6 --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/Cache/MH1903_CACHE.cs @@ -0,0 +1,148 @@ +using Antmicro.Renode.Core; +using Antmicro.Renode.Core.Structure.Registers; + +namespace Antmicro.Renode.Peripherals.Cache +{ + public class MH1903_CACHE : BasicDoubleWordPeripheral, IKnownSize + { + public MH1903_CACHE(IMachine machine) : base(machine) + { + DefineRegisters(); + } + + public long Size => 0x80; + + private void DefineRegisters() + { + // InstructionData0 at offset 0x00 + Registers.InstructionData0.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "InstructionData0"); + + // InstructionData1 at offset 0x04 + Registers.InstructionData1.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "InstructionData1"); + + // InstructionData2 at offset 0x08 + Registers.InstructionData2.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "InstructionData2"); + + // InstructionData3 at offset 0x0C + Registers.InstructionData3.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "InstructionData3"); + + // KeyData0 at offset 0x10 + Registers.KeyData0.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "KeyData0"); + + // KeyData1 at offset 0x14 + Registers.KeyData1.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "KeyData1"); + + // KeyData2 at offset 0x18 + Registers.KeyData2.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "KeyData2"); + + // KeyData3 at offset 0x1C + Registers.KeyData3.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "KeyData3"); + + // ChipSelect at offset 0x20 + Registers.ChipSelect.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "ChipSelect"); + + // Reference at offset 0x24 - when written, gets cleared to 0 + Registers.Reference.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Write, name: "Reference", + writeCallback: (_, __) => { /* Writing clears it, do nothing */ }); + + // Reserved area 0x28-0x3F (6 words) + Registers.Reserved28.Define(this) + .WithReservedBits(0, 32); + Registers.Reserved2C.Define(this) + .WithReservedBits(0, 32); + Registers.Reserved30.Define(this) + .WithReservedBits(0, 32); + Registers.Reserved34.Define(this) + .WithReservedBits(0, 32); + Registers.Reserved38.Define(this) + .WithReservedBits(0, 32); + Registers.Reserved3C.Define(this) + .WithReservedBits(0, 32); + + // Configuration at offset 0x40 + Registers.Configuration.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "Configuration"); + + // Reserved area 0x44-0x73 (12 words) + Registers.Reserved44.Define(this) + .WithReservedBits(0, 32); + Registers.Reserved48.Define(this) + .WithReservedBits(0, 32); + Registers.Reserved4C.Define(this) + .WithReservedBits(0, 32); + Registers.Reserved50.Define(this) + .WithReservedBits(0, 32); + Registers.Reserved54.Define(this) + .WithReservedBits(0, 32); + Registers.Reserved58.Define(this) + .WithReservedBits(0, 32); + Registers.Reserved5C.Define(this) + .WithReservedBits(0, 32); + Registers.Reserved60.Define(this) + .WithReservedBits(0, 32); + Registers.Reserved64.Define(this) + .WithReservedBits(0, 32); + Registers.Reserved68.Define(this) + .WithReservedBits(0, 32); + Registers.Reserved6C.Define(this) + .WithReservedBits(0, 32); + Registers.Reserved70.Define(this) + .WithReservedBits(0, 32); + + // StartAddress at offset 0x74 + Registers.StartAddress.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "StartAddress"); + + // EndAddress at offset 0x78 + Registers.EndAddress.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "EndAddress"); + } + + private enum Registers : long + { + InstructionData0 = 0x00, + InstructionData1 = 0x04, + InstructionData2 = 0x08, + InstructionData3 = 0x0C, + KeyData0 = 0x10, + KeyData1 = 0x14, + KeyData2 = 0x18, + KeyData3 = 0x1C, + ChipSelect = 0x20, + Reference = 0x24, + // Reserved 0x28-0x3F (6 words) + Reserved28 = 0x28, + Reserved2C = 0x2C, + Reserved30 = 0x30, + Reserved34 = 0x34, + Reserved38 = 0x38, + Reserved3C = 0x3C, + Configuration = 0x40, + // Reserved 0x44-0x70 (12 words) + Reserved44 = 0x44, + Reserved48 = 0x48, + Reserved4C = 0x4C, + Reserved50 = 0x50, + Reserved54 = 0x54, + Reserved58 = 0x58, + Reserved5C = 0x5C, + Reserved60 = 0x60, + Reserved64 = 0x64, + Reserved68 = 0x68, + Reserved6C = 0x6C, + Reserved70 = 0x70, + StartAddress = 0x74, + EndAddress = 0x78, + } + } +} diff --git a/src/Emulator/Peripherals/Peripherals/DMA/MH1903_DMA.cs b/src/Emulator/Peripherals/Peripherals/DMA/MH1903_DMA.cs new file mode 100644 index 000000000..496242247 --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/DMA/MH1903_DMA.cs @@ -0,0 +1,391 @@ +using Antmicro.Renode.Core; +using Antmicro.Renode.Core.Structure.Registers; + +namespace Antmicro.Renode.Peripherals.DMA +{ + public class MH1903_DMA : BasicDoubleWordPeripheral, IKnownSize + { + public MH1903_DMA(IMachine machine) : base(machine) + { + DefineRegisters(); + } + + public long Size => 0x400; + + public GPIO IRQ { get; set; } = new GPIO(); + + private void DefineRegisters() + { + // DMA Channels 0-7 (each channel is 0x58 bytes = 22 DWORDs) + for(int ch = 0; ch < 8; ch++) + { + var baseOffset = (Registers)((long)Registers.Channel0SourceAddressLow + (ch * 0x58)); + + // Source Address Register Low/High + ((Registers)((long)baseOffset + 0x00)).Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: $"Channel{ch}SourceAddressLow"); + ((Registers)((long)baseOffset + 0x04)).Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: $"Channel{ch}SourceAddressHigh"); + + // Destination Address Register Low/High + ((Registers)((long)baseOffset + 0x08)).Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: $"Channel{ch}DestinationAddressLow"); + ((Registers)((long)baseOffset + 0x0C)).Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: $"Channel{ch}DestinationAddressHigh"); + + // Linked List Pointer Low/High + ((Registers)((long)baseOffset + 0x10)).Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: $"Channel{ch}LinkedListPointerLow"); + ((Registers)((long)baseOffset + 0x14)).Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: $"Channel{ch}LinkedListPointerHigh"); + + // Control Register Low/High + ((Registers)((long)baseOffset + 0x18)).Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: $"Channel{ch}ControlLow"); + ((Registers)((long)baseOffset + 0x1C)).Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: $"Channel{ch}ControlHigh"); + + // Source Status Low/High + ((Registers)((long)baseOffset + 0x20)).Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: $"Channel{ch}SourceStatusLow"); + ((Registers)((long)baseOffset + 0x24)).Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: $"Channel{ch}SourceStatusHigh"); + + // Destination Status Low/High + ((Registers)((long)baseOffset + 0x28)).Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: $"Channel{ch}DestinationStatusLow"); + ((Registers)((long)baseOffset + 0x2C)).Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: $"Channel{ch}DestinationStatusHigh"); + + // Source Status Address Low/High + ((Registers)((long)baseOffset + 0x30)).Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: $"Channel{ch}SourceStatusAddressLow"); + ((Registers)((long)baseOffset + 0x34)).Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: $"Channel{ch}SourceStatusAddressHigh"); + + // Destination Status Address Low/High + ((Registers)((long)baseOffset + 0x38)).Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: $"Channel{ch}DestinationStatusAddressLow"); + ((Registers)((long)baseOffset + 0x3C)).Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: $"Channel{ch}DestinationStatusAddressHigh"); + + // Configuration Register Low/High + ((Registers)((long)baseOffset + 0x40)).Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: $"Channel{ch}ConfigurationLow"); + ((Registers)((long)baseOffset + 0x44)).Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: $"Channel{ch}ConfigurationHigh"); + + // Source Gather Register Low/High + ((Registers)((long)baseOffset + 0x48)).Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: $"Channel{ch}SourceGatherLow"); + ((Registers)((long)baseOffset + 0x4C)).Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: $"Channel{ch}SourceGatherHigh"); + + // Destination Scatter Register Low/High + ((Registers)((long)baseOffset + 0x50)).Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: $"Channel{ch}DestinationScatterLow"); + ((Registers)((long)baseOffset + 0x54)).Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: $"Channel{ch}DestinationScatterHigh"); + } + + // Interrupt Registers - Raw Status + Registers.RawTransferLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "RawTransferLow"); + Registers.RawTransferHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "RawTransferHigh"); + Registers.RawBlockLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "RawBlockLow"); + Registers.RawBlockHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "RawBlockHigh"); + Registers.RawSourceTransferLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "RawSourceTransferLow"); + Registers.RawSourceTransferHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "RawSourceTransferHigh"); + Registers.RawDestinationTransferLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "RawDestinationTransferLow"); + Registers.RawDestinationTransferHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "RawDestinationTransferHigh"); + Registers.RawErrorLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "RawErrorLow"); + Registers.RawErrorHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "RawErrorHigh"); + + // Interrupt Registers - Status + Registers.StatusTransferLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "StatusTransferLow"); + Registers.StatusTransferHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "StatusTransferHigh"); + Registers.StatusBlockLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "StatusBlockLow"); + Registers.StatusBlockHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "StatusBlockHigh"); + Registers.StatusSourceTransferLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "StatusSourceTransferLow"); + Registers.StatusSourceTransferHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "StatusSourceTransferHigh"); + Registers.StatusDestinationTransferLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "StatusDestinationTransferLow"); + Registers.StatusDestinationTransferHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "StatusDestinationTransferHigh"); + Registers.StatusErrorLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "StatusErrorLow"); + Registers.StatusErrorHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "StatusErrorHigh"); + + // Interrupt Registers - Mask + Registers.MaskTransferLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "MaskTransferLow"); + Registers.MaskTransferHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "MaskTransferHigh"); + Registers.MaskBlockLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "MaskBlockLow"); + Registers.MaskBlockHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "MaskBlockHigh"); + Registers.MaskSourceTransferLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "MaskSourceTransferLow"); + Registers.MaskSourceTransferHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "MaskSourceTransferHigh"); + Registers.MaskDestinationTransferLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "MaskDestinationTransferLow"); + Registers.MaskDestinationTransferHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "MaskDestinationTransferHigh"); + Registers.MaskErrorLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "MaskErrorLow"); + Registers.MaskErrorHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "MaskErrorHigh"); + + // Interrupt Registers - Clear + Registers.ClearTransferLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Write, name: "ClearTransferLow"); + Registers.ClearTransferHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Write, name: "ClearTransferHigh"); + Registers.ClearBlockLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Write, name: "ClearBlockLow"); + Registers.ClearBlockHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Write, name: "ClearBlockHigh"); + Registers.ClearSourceTransferLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Write, name: "ClearSourceTransferLow"); + Registers.ClearSourceTransferHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Write, name: "ClearSourceTransferHigh"); + Registers.ClearDestinationTransferLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Write, name: "ClearDestinationTransferLow"); + Registers.ClearDestinationTransferHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Write, name: "ClearDestinationTransferHigh"); + Registers.ClearErrorLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Write, name: "ClearErrorLow"); + Registers.ClearErrorHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Write, name: "ClearErrorHigh"); + + // Combined Interrupt Status + Registers.StatusInterruptLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "StatusInterruptLow"); + Registers.StatusInterruptHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "StatusInterruptHigh"); + + // Software handshaking + Registers.RequestSourceRegisterLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "RequestSourceRegisterLow"); + Registers.RequestSourceRegisterHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "RequestSourceRegisterHigh"); + Registers.RequestDestinationRegisterLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "RequestDestinationRegisterLow"); + Registers.RequestDestinationRegisterHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "RequestDestinationRegisterHigh"); + Registers.SingleRequestSourceRegisterLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "SingleRequestSourceRegisterLow"); + Registers.SingleRequestSourceRegisterHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "SingleRequestSourceRegisterHigh"); + Registers.SingleRequestDestinationRegisterLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "SingleRequestDestinationRegisterLow"); + Registers.SingleRequestDestinationRegisterHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "SingleRequestDestinationRegisterHigh"); + Registers.LastSourceRegisterLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "LastSourceRegisterLow"); + Registers.LastSourceRegisterHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "LastSourceRegisterHigh"); + Registers.LastDestinationRegisterLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "LastDestinationRegisterLow"); + Registers.LastDestinationRegisterHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "LastDestinationRegisterHigh"); + + // DMA Configuration registers + Registers.ConfigurationRegisterLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "ConfigurationRegisterLow"); + Registers.ConfigurationRegisterHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "ConfigurationRegisterHigh"); + Registers.ChannelEnableRegisterLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "ChannelEnableRegisterLow"); + Registers.ChannelEnableRegisterHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "ChannelEnableRegisterHigh"); + Registers.DmaIdRegisterLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "DmaIdRegisterLow"); + Registers.DmaIdRegisterHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "DmaIdRegisterHigh"); + Registers.DmaTestRegisterLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "DmaTestRegisterLow"); + Registers.DmaTestRegisterHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "DmaTestRegisterHigh"); + + // Reserved 0x3B8-0x3BF + Registers.Reserved3B8.Define(this) + .WithReservedBits(0, 32); + Registers.Reserved3BC.Define(this) + .WithReservedBits(0, 32); + + // Component parameter registers (read-only) + Registers.ComponentParameters6Low.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "ComponentParameters6Low"); + Registers.ComponentParameters6High.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "ComponentParameters6High"); + Registers.ComponentParameters5Low.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "ComponentParameters5Low"); + Registers.ComponentParameters5High.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "ComponentParameters5High"); + Registers.ComponentParameters4Low.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "ComponentParameters4Low"); + Registers.ComponentParameters4High.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "ComponentParameters4High"); + Registers.ComponentParameters3Low.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "ComponentParameters3Low"); + Registers.ComponentParameters3High.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "ComponentParameters3High"); + Registers.ComponentParameters2Low.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "ComponentParameters2Low"); + Registers.ComponentParameters2High.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "ComponentParameters2High"); + Registers.ComponentParameters1Low.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "ComponentParameters1Low"); + Registers.ComponentParameters1High.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "ComponentParameters1High"); + Registers.ComponentIdRegisterLow.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "ComponentIdRegisterLow"); + Registers.ComponentIdRegisterHigh.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "ComponentIdRegisterHigh"); + } + + private enum Registers : long + { + // Channel 0 registers (0x000 - 0x057) + Channel0SourceAddressLow = 0x000, + Channel0SourceAddressHigh = 0x004, + Channel0DestinationAddressLow = 0x008, + Channel0DestinationAddressHigh = 0x00C, + Channel0LinkedListPointerLow = 0x010, + Channel0LinkedListPointerHigh = 0x014, + Channel0ControlLow = 0x018, + Channel0ControlHigh = 0x01C, + Channel0SourceStatusLow = 0x020, + Channel0SourceStatusHigh = 0x024, + Channel0DestinationStatusLow = 0x028, + Channel0DestinationStatusHigh = 0x02C, + Channel0SourceStatusAddressLow = 0x030, + Channel0SourceStatusAddressHigh = 0x034, + Channel0DestinationStatusAddressLow = 0x038, + Channel0DestinationStatusAddressHigh = 0x03C, + Channel0ConfigurationLow = 0x040, + Channel0ConfigurationHigh = 0x044, + Channel0SourceGatherLow = 0x048, + Channel0SourceGatherHigh = 0x04C, + Channel0DestinationScatterLow = 0x050, + Channel0DestinationScatterHigh = 0x054, + + // Channels 1-7 follow same pattern at offsets 0x58, 0xB0, 0x108, 0x160, 0x1B8, 0x210, 0x268 + + // Interrupt raw status registers (0x2C0) + RawTransferLow = 0x2C0, + RawTransferHigh = 0x2C4, + RawBlockLow = 0x2C8, + RawBlockHigh = 0x2CC, + RawSourceTransferLow = 0x2D0, + RawSourceTransferHigh = 0x2D4, + RawDestinationTransferLow = 0x2D8, + RawDestinationTransferHigh = 0x2DC, + RawErrorLow = 0x2E0, + RawErrorHigh = 0x2E4, + + // Interrupt status registers (0x2E8) + StatusTransferLow = 0x2E8, + StatusTransferHigh = 0x2EC, + StatusBlockLow = 0x2F0, + StatusBlockHigh = 0x2F4, + StatusSourceTransferLow = 0x2F8, + StatusSourceTransferHigh = 0x2FC, + StatusDestinationTransferLow = 0x300, + StatusDestinationTransferHigh = 0x304, + StatusErrorLow = 0x308, + StatusErrorHigh = 0x30C, + + // Interrupt mask registers (0x310) + MaskTransferLow = 0x310, + MaskTransferHigh = 0x314, + MaskBlockLow = 0x318, + MaskBlockHigh = 0x31C, + MaskSourceTransferLow = 0x320, + MaskSourceTransferHigh = 0x324, + MaskDestinationTransferLow = 0x328, + MaskDestinationTransferHigh = 0x32C, + MaskErrorLow = 0x330, + MaskErrorHigh = 0x334, + + // Interrupt clear registers (0x338) + ClearTransferLow = 0x338, + ClearTransferHigh = 0x33C, + ClearBlockLow = 0x340, + ClearBlockHigh = 0x344, + ClearSourceTransferLow = 0x348, + ClearSourceTransferHigh = 0x34C, + ClearDestinationTransferLow = 0x350, + ClearDestinationTransferHigh = 0x354, + ClearErrorLow = 0x358, + ClearErrorHigh = 0x35C, + + // Combined interrupt status (0x360) + StatusInterruptLow = 0x360, + StatusInterruptHigh = 0x364, + + // Software handshaking (0x368) + RequestSourceRegisterLow = 0x368, + RequestSourceRegisterHigh = 0x36C, + RequestDestinationRegisterLow = 0x370, + RequestDestinationRegisterHigh = 0x374, + SingleRequestSourceRegisterLow = 0x378, + SingleRequestSourceRegisterHigh = 0x37C, + SingleRequestDestinationRegisterLow = 0x380, + SingleRequestDestinationRegisterHigh = 0x384, + LastSourceRegisterLow = 0x388, + LastSourceRegisterHigh = 0x38C, + LastDestinationRegisterLow = 0x390, + LastDestinationRegisterHigh = 0x394, + + // DMA Configuration (0x398) + ConfigurationRegisterLow = 0x398, + ConfigurationRegisterHigh = 0x39C, + ChannelEnableRegisterLow = 0x3A0, + ChannelEnableRegisterHigh = 0x3A4, + DmaIdRegisterLow = 0x3A8, + DmaIdRegisterHigh = 0x3AC, + DmaTestRegisterLow = 0x3B0, + DmaTestRegisterHigh = 0x3B4, + + // Reserved (0x3B8) + Reserved3B8 = 0x3B8, + Reserved3BC = 0x3BC, + + // Component parameters (0x3C0) + ComponentParameters6Low = 0x3C0, + ComponentParameters6High = 0x3C4, + ComponentParameters5Low = 0x3C8, + ComponentParameters5High = 0x3CC, + ComponentParameters4Low = 0x3D0, + ComponentParameters4High = 0x3D4, + ComponentParameters3Low = 0x3D8, + ComponentParameters3High = 0x3DC, + ComponentParameters2Low = 0x3E0, + ComponentParameters2High = 0x3E4, + ComponentParameters1Low = 0x3E8, + ComponentParameters1High = 0x3EC, + ComponentIdRegisterLow = 0x3F0, + ComponentIdRegisterHigh = 0x3F4, + } + } +} diff --git a/src/Emulator/Peripherals/Peripherals/GPIOPort/MH1903_GPIO.cs b/src/Emulator/Peripherals/Peripherals/GPIOPort/MH1903_GPIO.cs new file mode 100644 index 000000000..9181267b4 --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/GPIOPort/MH1903_GPIO.cs @@ -0,0 +1,99 @@ +using Antmicro.Renode.Core; +using Antmicro.Renode.Core.Structure.Registers; +using Antmicro.Renode.Peripherals.Bus; + +namespace Antmicro.Renode.Peripherals.GPIOPort +{ + public class MH1903_GPIO : BaseGPIOPort, IDoubleWordPeripheral, IProvidesRegisterCollection, IKnownSize + { + public MH1903_GPIO(IMachine machine) : base(machine, PinsPerPort) + { + RegistersCollection = new DoubleWordRegisterCollection(this); + DefineRegisters(); + } + + public uint ReadDoubleWord(long offset) + { + return RegistersCollection.Read(offset); + } + + public void WriteDoubleWord(long offset, uint value) + { + RegistersCollection.Write(offset, value); + } + + public override void Reset() + { + base.Reset(); + RegistersCollection.Reset(); + } + + public DoubleWordRegisterCollection RegistersCollection { get; } + + public long Size => 0x10; + + private void OnBSSRChange(uint newValue) + { + // BSRR register layout: + // Bits [15:0] - Set bits (BS): writing 1 sets the corresponding GPIO pin + // Bits [31:16] - Reset bits (BR): writing 1 clears the corresponding GPIO pin + var resetPins = (newValue >> 16) & 0xFFFF; + var setPins = newValue & 0xFFFF; + + // Update State[] array to reflect the pin changes + for(int pin = 0; pin < PinsPerPort; pin++) + { + // Check if this pin was set + if((setPins & (1 << pin)) != 0) + { + WritePin(pin, true); + } + // Check if this pin was reset + if((resetPins & (1 << pin)) != 0) + { + WritePin(pin, false); + } + } + } + + private void WritePin(int number, bool value) + { + State[number] = value; + Connections[number].Set(value); + } + + private void DefineRegisters() + { + // InputOutputDataRegister - Input/Output Data Register + Registers.InputOutputDataRegister.Define(this, resetValue: 0xFFFF0000) + .WithValueField(0, 16, name: "OutputDataRegister") // Output Data Register + .WithValueField(16, 16, name: "InputDataRegister"); // Input Data Register + + // BitSetResetRegister - Bit Set/Reset Register + Registers.BitSetResetRegister.Define(this, resetValue: 0x00000000) + .WithValueField(0, 16, name: "BitSet") // Bit Set + .WithValueField(16, 16, name: "BitReset") // Bit Reset + .WithChangeCallback((_, newValue) => OnBSSRChange(newValue)); + + // OutputEnableRegister - Output Enable Register + Registers.OutputEnableRegister.Define(this, resetValue: 0x0000FFFF) + .WithValueField(0, 16, name: "OutputEnable") // Output Enable + .WithReservedBits(16, 16); + + // PullUpEnableRegister - Pull-Up Enable Register + Registers.PullUpEnableRegister.Define(this, resetValue: 0x0000FFFF) + .WithValueField(0, 16, name: "PullUpEnable") // Pull-Up Enable + .WithReservedBits(16, 16); + } + + private const int PinsPerPort = 16; + + private enum Registers : ulong + { + InputOutputDataRegister = 0x00, + BitSetResetRegister = 0x04, + OutputEnableRegister = 0x08, + PullUpEnableRegister = 0x0C, + } + } +} diff --git a/src/Emulator/Peripherals/Peripherals/GPIOPort/MH1903_GPIO_Control.cs b/src/Emulator/Peripherals/Peripherals/GPIOPort/MH1903_GPIO_Control.cs new file mode 100644 index 000000000..7af6bbccc --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/GPIOPort/MH1903_GPIO_Control.cs @@ -0,0 +1,176 @@ +using Antmicro.Renode.Core; +using Antmicro.Renode.Core.Structure.Registers; +using Antmicro.Renode.Peripherals.Bus; + +namespace Antmicro.Renode.Peripherals.GPIOPort +{ + public class MH1903_GPIO_Control : IDoubleWordPeripheral, IProvidesRegisterCollection, IKnownSize + { + public MH1903_GPIO_Control(IMachine machine) + { + RegistersCollection = new DoubleWordRegisterCollection(this); + DefineRegisters(); + } + + public uint ReadDoubleWord(long offset) + { + return RegistersCollection.Read(offset); + } + + public void WriteDoubleWord(long offset, uint value) + { + RegistersCollection.Write(offset, value); + } + + public void Reset() + { + RegistersCollection.Reset(); + } + + public DoubleWordRegisterCollection RegistersCollection { get; } + + public long Size => 0x840; + + private void DefineRegisters() + { + // Interrupt status registers for external interrupt inputs + DefineINTPStatus(Registers.ExternalInterrupt5Status); + DefineINTPStatus(Registers.ExternalInterrupt4Status); + DefineINTPStatus(Registers.ExternalInterrupt3Status); + DefineINTPStatus(Registers.ExternalInterrupt2Status); + DefineINTPStatus(Registers.ExternalInterrupt1Status); + DefineINTPStatus(Registers.ExternalInterrupt0Status); + + // Alternate function registers for all GPIO ports (A-H) + DefineALTRegister(Registers.PortAAlternateFunction, "PA"); + DefineALTRegister(Registers.PortBAlternateFunction, "PB"); + DefineALTRegister(Registers.PortCAlternateFunction, "PC"); + DefineALTRegister(Registers.PortDAlternateFunction, "PD"); + DefineALTRegister(Registers.PortEAlternateFunction, "PE"); + DefineALTRegister(Registers.PortFAlternateFunction, "PF"); + DefineALTRegister(Registers.PortGAlternateFunction, "PG"); + DefineALTRegister(Registers.PortHAlternateFunction, "PH"); + + // System control register + Registers.SystemControl1.Define(this, resetValue: 0xFFFF0000) + .WithValueField(0, 32, name: "SystemControl1"); + + // Wakeup configuration registers + Registers.WakeupTypeEnable.Define(this) + .WithReservedBits(15, 17) + .WithFlag(14, name: "SensorWakeupEnable") + .WithFlag(13, name: "MeasurementWakeupEnable") + .WithFlag(12, name: "RealTimeClockWakeupEnable") + .WithFlag(11, name: "KeyboardWakeupEnable") + .WithReservedBits(1, 10) + .WithFlag(0, name: "GpioWakeupType"); + + Registers.WakeupPort0Enable.Define(this) + .WithValueField(16, 16, name: "PortBWakeupEnable") + .WithValueField(0, 16, name: "PortAWakeupEnable"); + + Registers.WakeupPort1Enable.Define(this) + .WithValueField(16, 16, name: "PortDWakeupEnable") + .WithValueField(0, 16, name: "PortCWakeupEnable"); + + Registers.WakeupPort2Enable.Define(this) + .WithReservedBits(24, 8) + .WithValueField(16, 8, name: "PortFWakeupEnable") + .WithValueField(0, 16, name: "PortEWakeupEnable"); + + // Interrupt type and status registers for GPIO ports + DefineINTPTypeAndStatus(Registers.PortAInterruptType, Registers.PortAInterruptStatus, "PA"); + DefineINTPTypeAndStatus(Registers.PortBInterruptType, Registers.PortBInterruptStatus, "PB"); + DefineINTPTypeAndStatus(Registers.PortCInterruptType, Registers.PortCInterruptStatus, "PC"); + DefineINTPTypeAndStatus(Registers.PortDInterruptType, Registers.PortDInterruptStatus, "PD"); + DefineINTPTypeAndStatus(Registers.PortEInterruptType, Registers.PortEInterruptStatus, "PE"); + DefineINTPTypeAndStatus(Registers.PortFInterruptType, Registers.PortFInterruptStatus, "PF"); + DefineINTPTypeAndStatus(Registers.PortGInterruptType, Registers.PortGInterruptStatus, "PG"); + DefineINTPTypeAndStatus(Registers.PortHInterruptType, Registers.PortHInterruptStatus, "PH"); + } + + private void DefineINTPStatus(Registers register) + { + register.Define(this, resetValue: 0x00000000) + .WithFlag(0, mode: FieldMode.ReadToClear, name: "InterruptState") + .WithReservedBits(1, 30); + } + + private void DefineALTRegister(Registers register, string portName) + { + var reg = register.Define(this, 0x55555555); + // Alternate function: 2 bits per pin, pins ordered from 15 to 0 + for(int pin = 15; pin >= 0; pin--) + { + reg.WithValueField(pin * 2, 2, name: $"{portName}{pin}AlternateFunction"); + } + } + + private void DefineINTPTypeAndStatus(Registers typeRegister, Registers staRegister, string portName) + { + // INTP_TYPE - Interrupt Type Register + // 00: No interrupt generated + // 01: Rising edge interrupt + // 10: Falling edge interrupt + // 11: Both edges interrupt (rising and falling) + var typeReg = typeRegister.Define(this); + for(int pin = 15; pin >= 0; pin--) + { + typeReg.WithValueField(pin * 2, 2, name: $"{portName}{pin}InterruptType"); + } + + // INTP_STA - Interrupt Status Register (WriteToClear) + staRegister.Define(this) + .WithReservedBits(16, 16) + .WithValueField(0, 16, mode: FieldMode.WriteToClear, name: $"{portName}InterruptStatus"); + } + + private enum Registers : ulong + { + // External interrupt status (offsets relative to control base 0x4001d100) + ExternalInterrupt5Status = 0x14, + ExternalInterrupt4Status = 0x18, + ExternalInterrupt3Status = 0x1C, + ExternalInterrupt2Status = 0x20, + ExternalInterrupt1Status = 0x24, + ExternalInterrupt0Status = 0x28, + + // Alternate function registers + PortAAlternateFunction = 0x80, + PortBAlternateFunction = 0x84, + PortCAlternateFunction = 0x88, + PortDAlternateFunction = 0x8C, + PortEAlternateFunction = 0x90, + PortFAlternateFunction = 0x94, + PortGAlternateFunction = 0x98, + PortHAlternateFunction = 0x9C, + + // System control + SystemControl1 = 0x100, + + // Wakeup control registers + WakeupTypeEnable = 0x120, + WakeupPort0Enable = 0x124, + WakeupPort1Enable = 0x128, + WakeupPort2Enable = 0x12C, + + // GPIO Port Interrupt Type and Status + PortAInterruptType = 0x700, + PortAInterruptStatus = 0x704, + PortBInterruptType = 0x708, + PortBInterruptStatus = 0x70C, + PortCInterruptType = 0x710, + PortCInterruptStatus = 0x714, + PortDInterruptType = 0x718, + PortDInterruptStatus = 0x71C, + PortEInterruptType = 0x720, + PortEInterruptStatus = 0x724, + PortFInterruptType = 0x728, + PortFInterruptStatus = 0x72C, + PortGInterruptType = 0x730, + PortGInterruptStatus = 0x734, + PortHInterruptType = 0x738, + PortHInterruptStatus = 0x73C, + } + } +} diff --git a/src/Emulator/Peripherals/Peripherals/Miscellaneous/MH1903_BPU.cs b/src/Emulator/Peripherals/Peripherals/Miscellaneous/MH1903_BPU.cs new file mode 100644 index 000000000..cd35c3478 --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/Miscellaneous/MH1903_BPU.cs @@ -0,0 +1,353 @@ +using System.Collections.Generic; + +using Antmicro.Renode.Core; +using Antmicro.Renode.Core.Structure.Registers; +using Antmicro.Renode.Logging; + +namespace Antmicro.Renode.Peripherals.Miscellaneous +{ + public class MH1903_BPU : BasicDoubleWordPeripheral, IKnownSize + { + public MH1903_BPU(IMachine machine) : base(machine) + { + BuildRegisterNameMap(); + DefineRegisters(); + } + + public long Size => 0x600; + + public override uint ReadDoubleWord(long offset) + { + var value = base.ReadDoubleWord(offset); + var regName = GetRegisterName(offset); + this.Log(LogLevel.Info, "BPU read at {0}: 0x{1:X8}", regName, value); + return value; + } + + public override void WriteDoubleWord(long offset, uint value) + { + var regName = GetRegisterName(offset); + this.Log(LogLevel.Info, "BPU write at {0}: 0x{1:X8}", regName, value); + base.WriteDoubleWord(offset, value); + } + + private string GetRegisterName(long offset) + { + if(registerNames.TryGetValue(offset, out var name)) + { + return name; + } + return $"0x{offset:X3}"; + } + + private void BuildRegisterNameMap() + { + registerNames = new Dictionary(); + + // KEY registers + for(int i = 0; i < 16; i++) + { + registerNames[(long)Registers.EncryptionKey0 + (i * 4)] = $"EncryptionKey{i}"; + } + + // BPK registers + registerNames[(long)Registers.BpkReady] = "BpkReady"; + registerNames[(long)Registers.BpkClear] = "BpkClear"; + registerNames[(long)Registers.BpkLastReadAddress] = "BpkLastReadAddress"; + registerNames[(long)Registers.BpkLastWriteAddress] = "BpkLastWriteAddress"; + registerNames[(long)Registers.BpkLockRegister] = "BpkLockRegister"; + registerNames[(long)Registers.BpkStatusControlRegister] = "BpkStatusControlRegister"; + registerNames[(long)Registers.BpkPowerRegister] = "BpkPowerRegister"; + + // RTC registers + registerNames[(long)Registers.RealTimeClockControlStatus] = "RealTimeClockControlStatus"; + registerNames[(long)Registers.RealTimeClockReference] = "RealTimeClockReference"; + registerNames[(long)Registers.RealTimeClockAlarmRegister] = "RealTimeClockAlarmRegister"; + registerNames[(long)Registers.RealTimeClockTimer] = "RealTimeClockTimer"; + registerNames[(long)Registers.RealTimeClockInterruptClear] = "RealTimeClockInterruptClear"; + registerNames[(long)Registers.Oscillator32KHz] = "Oscillator32KHz"; + registerNames[(long)Registers.RealTimeClockAttachmentTimer] = "RealTimeClockAttachmentTimer"; + registerNames[(long)Registers.BpkResetRegister] = "BpkResetRegister"; + + // SEN registers + registerNames[(long)Registers.SensorExternalType] = "SensorExternalType"; + registerNames[(long)Registers.SensorExternalConfig] = "SensorExternalConfig"; + registerNames[(long)Registers.SensorSoftwareEnable] = "SensorSoftwareEnable"; + registerNames[(long)Registers.SensorState] = "SensorState"; + registerNames[(long)Registers.SensorBridge] = "SensorBridge"; + registerNames[(long)Registers.SensorSoftwareAttack] = "SensorSoftwareAttack"; + registerNames[(long)Registers.SensorSoftwareLock] = "SensorSoftwareLock"; + registerNames[(long)Registers.SensorAttackCounter] = "SensorAttackCounter"; + registerNames[(long)Registers.SensorAttackType] = "SensorAttackType"; + registerNames[(long)Registers.SensorVoltageGlitchDetect] = "SensorVoltageGlitchDetect"; + registerNames[(long)Registers.SensorRandomNumberGeneratorInitialization] = "SensorRandomNumberGeneratorInitialization"; + + // SEN_EN registers + for(int i = 0; i < 19; i++) + { + registerNames[(long)Registers.SensorEnable0 + (i * 4)] = $"SensorEnable{i}"; + } + + // Additional sensor registers + registerNames[(long)Registers.SensorExtendedStart] = "SensorExtendedStart"; + registerNames[(long)Registers.SensorLock] = "SensorLock"; + registerNames[(long)Registers.SensorAnalog0] = "SensorAnalog0"; + registerNames[(long)Registers.SensorAnalog1] = "SensorAnalog1"; + registerNames[(long)Registers.SensorAttackClear] = "SensorAttackClear"; + registerNames[(long)Registers.SensorPullUpPullDownConfig] = "SensorPullUpPullDownConfig"; + + // BPK_RAM + for(int i = 0; i < 256; i++) + { + registerNames[(long)Registers.BreakpointRam0 + (i * 4)] = $"BreakpointRam{i}"; + } + } + + private void DefineRegisters() + { + // KEY registers 0x00-0x3C (16 words) + for(int i = 0; i < 16; i++) + { + ((Registers)((long)Registers.EncryptionKey0 + (i * 4))).Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: $"EncryptionKey{i}"); + } + + // Reserved 0x40-0x7C + for(int i = 0; i < 16; i++) + { + ((Registers)((long)Registers.BpuReserved0 + (i * 4))).Define(this) + .WithReservedBits(0, 32); + } + + // BPK registers + Registers.BpkReady.Define(this) + .WithValueField(0, 32, FieldMode.Read, valueProviderCallback: _ => 0x00000001, name: "BpkReady"); + Registers.BpkClear.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "BpkClear"); + Registers.BpkLastReadAddress.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "BpkLastReadAddress"); + Registers.BpkLastWriteAddress.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "BpkLastWriteAddress"); + Registers.BpuReserved1.Define(this) + .WithReservedBits(0, 32); + Registers.BpkLockRegister.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "BpkLockRegister"); + Registers.BpkStatusControlRegister.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "BpkStatusControlRegister"); + Registers.BpkPowerRegister.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "BpkPowerRegister"); + + // RTC (Real-Time Clock) registers + Registers.RealTimeClockControlStatus.Define(this, resetValue: 0x00000008) // Default: 1 << 3 + .WithValueField(0, 32, name: "RealTimeClockControlStatus"); + Registers.RealTimeClockReference.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "RealTimeClockReference"); + Registers.RealTimeClockAlarmRegister.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "RealTimeClockAlarmRegister"); + Registers.RealTimeClockTimer.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "RealTimeClockTimer"); + Registers.RealTimeClockInterruptClear.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Write, name: "RealTimeClockInterruptClear"); + Registers.Oscillator32KHz.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "Oscillator32KHz"); + Registers.RealTimeClockAttachmentTimer.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "RealTimeClockAttachmentTimer"); + Registers.BpkResetRegister.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "BpkResetRegister"); + + // SEN (Sensor) registers + Registers.SensorExternalType.Define(this, resetValue: 0x000FF000) + .WithValueField(0, 32, name: "SensorExternalType"); + Registers.SensorExternalConfig.Define(this, resetValue: 0x00A5A000) + .WithValueField(0, 32, name: "SensorExternalConfig"); + Registers.SensorSoftwareEnable.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "SensorSoftwareEnable"); + Registers.SensorState.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "SensorState"); + Registers.SensorBridge.Define(this, resetValue: 0x000000F0) + .WithValueField(0, 32, name: "SensorBridge"); + Registers.SensorSoftwareAttack.Define(this, resetValue: 0x80000000) + .WithValueField(0, 32, name: "SensorSoftwareAttack"); + Registers.SensorSoftwareLock.Define(this, resetValue: 0x0000000F) + .WithValueField(0, 32, name: "SensorSoftwareLock"); + Registers.SensorAttackCounter.Define(this, resetValue: 0x0000000F) + .WithValueField(0, 32, FieldMode.Read, name: "SensorAttackCounter"); + Registers.SensorAttackType.Define(this, resetValue: 0x00000001) + .WithValueField(0, 32, FieldMode.Read, name: "SensorAttackType"); + Registers.SensorVoltageGlitchDetect.Define(this, resetValue: 0x00000003) + .WithValueField(0, 32, name: "SensorVoltageGlitchDetect"); + Registers.SensorRandomNumberGeneratorInitialization.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "SensorRandomNumberGeneratorInitialization"); + + // Reserved 0xEC-0x100 + for(int i = 0; i < 6; i++) + { + ((Registers)((long)Registers.Reserved3 + (i * 4))).Define(this) + .WithReservedBits(0, 32); + } + + // SEN_EN registers 0x104-0x14C + // SensorExternal0Enable through SensorExternal7Enable (0x104-0x120): 0x000000AA + for(int i = 0; i < 8; i++) + { + ((Registers)((long)Registers.SensorEnable0 + (i * 4))).Define(this, resetValue: 0x000000AA) + .WithValueField(0, 32, name: $"SensorExternal{i}Enable"); + } + + // SensorReserved (0x124-0x130): 0x00000000 + for(int i = 8; i < 11; i++) + { + ((Registers)((long)Registers.SensorEnable0 + (i * 4))).Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: $"SensorReserved{i}"); + } + + // SensorVoltageHighEnable (0x134): 0x000000AA + ((Registers)((long)Registers.SensorEnable0 + (11 * 4))).Define(this, resetValue: 0x000000AA) + .WithValueField(0, 32, name: "SensorVoltageHighEnable"); + + // SensorVoltageLowEnable (0x138): 0x000000AA + ((Registers)((long)Registers.SensorEnable0 + (12 * 4))).Define(this, resetValue: 0x000000AA) + .WithValueField(0, 32, name: "SensorVoltageLowEnable"); + + // SensorTemperatureHighEnable (0x13C): 0x000000AA + ((Registers)((long)Registers.SensorEnable0 + (13 * 4))).Define(this, resetValue: 0x000000AA) + .WithValueField(0, 32, name: "SensorTemperatureHighEnable"); + + // SensorTemperatureLowEnable (0x140): 0x000000AA + ((Registers)((long)Registers.SensorEnable0 + (14 * 4))).Define(this, resetValue: 0x000000AA) + .WithValueField(0, 32, name: "SensorTemperatureLowEnable"); + + // SensorCrystal32KhzEnable (0x144): 0x00000055 + ((Registers)((long)Registers.SensorEnable0 + (15 * 4))).Define(this, resetValue: 0x00000055) + .WithValueField(0, 32, name: "SensorCrystal32KhzEnable"); + + // SensorMessageEnable (0x148): 0x000000AA + ((Registers)((long)Registers.SensorEnable0 + (16 * 4))).Define(this, resetValue: 0x000000AA) + .WithValueField(0, 32, name: "SensorMessageEnable"); + + // SensorVoltageGlitchLatchEnable (0x14C): 0x000000AA + ((Registers)((long)Registers.SensorEnable0 + (17 * 4))).Define(this, resetValue: 0x000000AA) + .WithValueField(0, 32, name: "SensorVoltageGlitchLatchEnable"); + + // Additional sensor registers + Registers.SensorExtendedStart.Define(this, resetValue: 0x800000AA) + .WithValueField(0, 32, name: "SensorExtendedStart"); + Registers.SensorLock.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "SensorLock"); + Registers.SensorAnalog0.Define(this, resetValue: 0x02350220) + .WithValueField(0, 32, name: "SensorAnalog0"); + Registers.SensorAnalog1.Define(this, resetValue: 0x00000024) + .WithValueField(0, 32, name: "SensorAnalog1"); + + // SensorAttackClear 0x160 + Registers.SensorAttackClear.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Write, name: "SensorAttackClear"); + + // Reserved 0x164-0x170 + for(int i = 0; i < 4; i++) + { + ((Registers)(0x164 + (i * 4))).Define(this, resetValue: 0x00000000) + .WithReservedBits(0, 32); + } + + // SensorPullUpPullDownConfig 0x174 + Registers.SensorPullUpPullDownConfig.Define(this, resetValue: 0xFF0000FF) + .WithValueField(0, 32, name: "SensorPullUpPullDownConfig"); + + // Reserved 0x178-0x1FC + for(int i = 0; i < 34; i++) + { + ((Registers)((long)Registers.BpuReserved4 + (i * 4))).Define(this) + .WithReservedBits(0, 32); + } + + // BreakpointRam 0x200-0x5FC (256 words) + for(int i = 0; i < 256; i++) + { + ((Registers)((long)Registers.BreakpointRam0 + (i * 4))).Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: $"BreakpointRam{i}"); + } + } + + private Dictionary registerNames; + + private enum Registers : long + { + // KEY registers 0x00-0x3C + EncryptionKey0 = 0x000, + EncryptionKey1 = 0x004, + EncryptionKey2 = 0x008, + EncryptionKey3 = 0x00C, + EncryptionKey4 = 0x010, + EncryptionKey5 = 0x014, + EncryptionKey6 = 0x018, + EncryptionKey7 = 0x01C, + EncryptionKey8 = 0x020, + EncryptionKey9 = 0x024, + EncryptionKey10 = 0x028, + EncryptionKey11 = 0x02C, + EncryptionKey12 = 0x030, + EncryptionKey13 = 0x034, + EncryptionKey14 = 0x038, + EncryptionKey15 = 0x03C, + + // Reserved 0x40-0x7C + BpuReserved0 = 0x040, + + // BPK registers 0x80-0x9C + BpkReady = 0x080, + BpkClear = 0x084, + BpkLastReadAddress = 0x088, + BpkLastWriteAddress = 0x08C, + BpuReserved1 = 0x090, + BpkLockRegister = 0x094, + BpkStatusControlRegister = 0x098, + BpkPowerRegister = 0x09C, + + // RTC registers 0xA0-0xBC + RealTimeClockControlStatus = 0x0A0, + RealTimeClockReference = 0x0A4, + RealTimeClockAlarmRegister = 0x0A8, + RealTimeClockTimer = 0x0AC, + RealTimeClockInterruptClear = 0x0B0, + Oscillator32KHz = 0x0B4, + RealTimeClockAttachmentTimer = 0x0B8, + BpkResetRegister = 0x0BC, + + // SEN registers 0xC0-0xE8 + SensorExternalType = 0x0C0, + SensorExternalConfig = 0x0C4, + SensorSoftwareEnable = 0x0C8, + SensorState = 0x0CC, + SensorBridge = 0x0D0, + SensorSoftwareAttack = 0x0D4, + SensorSoftwareLock = 0x0D8, + SensorAttackCounter = 0x0DC, + SensorAttackType = 0x0E0, + SensorVoltageGlitchDetect = 0x0E4, + SensorRandomNumberGeneratorInitialization = 0x0E8, + + // Reserved 0xEC-0x100 + Reserved3 = 0x0EC, + + // SEN_EN registers 0x104-0x14C + SensorEnable0 = 0x104, + + // Additional sensor registers 0x150-0x160 + SensorExtendedStart = 0x150, + SensorLock = 0x154, + SensorAnalog0 = 0x158, + SensorAnalog1 = 0x15C, + SensorAttackClear = 0x160, + + // SEN_PUPU_CFG 0x174 + SensorPullUpPullDownConfig = 0x174, + + // Reserved 0x178-0x1FC + BpuReserved4 = 0x178, + + // BPK_RAM 0x200-0x5FC + BreakpointRam0 = 0x200, + } + } +} diff --git a/src/Emulator/Peripherals/Peripherals/Miscellaneous/MH1903_CRC.cs b/src/Emulator/Peripherals/Peripherals/Miscellaneous/MH1903_CRC.cs new file mode 100644 index 000000000..2321114ee --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/Miscellaneous/MH1903_CRC.cs @@ -0,0 +1,247 @@ +using Antmicro.Renode.Core; +using Antmicro.Renode.Core.Structure.Registers; +using Antmicro.Renode.Logging; +using Antmicro.Renode.Peripherals.Bus; + +namespace Antmicro.Renode.Peripherals.Miscellaneous +{ + public class MH1903_CRC : BasicDoubleWordPeripheral, IBytePeripheral, IKnownSize + { + public MH1903_CRC(IMachine machine) : base(machine) + { + DefineRegisters(); + Reset(); + } + + public override void Reset() + { + base.Reset(); + crcValue = 0; + initialValue = 0; + polynomialSelect = false; + typeSelect = false; + reverseInput = false; + reverseOutput = false; + xorOutput = false; + } + + public void WriteByte(long offset, byte value) + { + // CrcDataRegister (offset 0x08) accepts byte writes for feeding data + if(offset == (long)Registers.CrcDataRegister) + { + ProcessByte(value); + return; + } + + // For other registers, use read-modify-write + long alignedOffset = offset & ~3; + int byteOffset = (int)(offset & 3); + uint currentValue = (uint)RegistersCollection.Read(alignedOffset); + uint mask = (uint)(0xFF << (byteOffset * 8)); + uint newValue = (currentValue & ~mask) | ((uint)value << (byteOffset * 8)); + RegistersCollection.Write(alignedOffset, newValue); + } + + public byte ReadByte(long offset) + { + // Read the doubleword and extract the appropriate byte + uint value = (uint)RegistersCollection.Read(offset & ~3); + int byteOffset = (int)(offset & 3); + return (byte)((value >> (byteOffset * 8)) & 0xFF); + } + + public long Size => 0x100; + + private static byte ReverseBits(byte b) + { + b = (byte)((b & 0xF0) >> 4 | (b & 0x0F) << 4); + b = (byte)((b & 0xCC) >> 2 | (b & 0x33) << 2); + b = (byte)((b & 0xAA) >> 1 | (b & 0x55) << 1); + return b; + } + + private static ushort ReverseBits16(ushort value) + { + ushort result = 0; + for(int i = 0; i < 16; i++) + { + result = (ushort)((result << 1) | (value & 1)); + value >>= 1; + } + return result; + } + + private static uint ReverseBits32(uint value) + { + uint result = 0; + for(int i = 0; i < 32; i++) + { + result = (result << 1) | (value & 1); + value >>= 1; + } + return result; + } + + private void DefineRegisters() + { + Registers.CrcControlStatusRegister.Define(this, resetValue: 0x00000000) + .WithFlag(0, name: "PolynomialSelect", + writeCallback: (_, value) => polynomialSelect = value, + valueProviderCallback: _ => polynomialSelect) + .WithFlag(1, name: "TypeSelect", + writeCallback: (_, value) => + { + typeSelect = value; + // When switching modes, reinitialize CRC + crcValue = initialValue; + }, + valueProviderCallback: _ => typeSelect) + .WithFlag(2, name: "ReverseInputSelect", + writeCallback: (_, value) => reverseInput = value, + valueProviderCallback: _ => reverseInput) + .WithFlag(3, name: "ReverseOutputSelect", + writeCallback: (_, value) => reverseOutput = value, + valueProviderCallback: _ => reverseOutput) + .WithFlag(4, name: "XorOutputSelect", + writeCallback: (_, value) => xorOutput = value, + valueProviderCallback: _ => xorOutput) + .WithReservedBits(5, 27); + + Registers.CrcInitializationValueRegister.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Write, name: "CrcInitializationValue", + writeCallback: (_, value) => + { + initialValue = (uint)value; + crcValue = initialValue; + this.Log(LogLevel.Debug, "CRC initialized to 0x{0:X}", initialValue); + }); + + // CrcDataRegister at 0x08 + // Write (byte): DataInput - Feed data byte into CRC + // Read (32-bit): DataOutput - Read CRC result + RegistersCollection.DefineRegister((long)Registers.CrcDataRegister) + .WithValueField(0, 32, + writeCallback: (_, value) => + { + // Only lowest byte is used for input + byte inputByte = (byte)(value & 0xFF); + ProcessByte(inputByte); + }, + valueProviderCallback: _ => GetCRCResult()); + } + + private void ProcessByte(byte data) + { + byte processedByte = data; + + // Reverse input bits if needed + if(reverseInput) + { + processedByte = ReverseBits(processedByte); + } + + if(typeSelect) + { + // CRC32 mode + ProcessByteCRC32(processedByte); + } + else + { + // CRC16 mode + ProcessByteCRC16(processedByte); + } + } + + private void ProcessByteCRC16(byte data) + { + uint polynomial = polynomialSelect ? (uint)0x1021 : (uint)0x8005; + + // CRC16 uses only lower 16 bits + ushort crc16 = (ushort)(crcValue & 0xFFFF); + + crc16 ^= (ushort)(data << 8); + + for(int i = 0; i < 8; i++) + { + if((crc16 & 0x8000) != 0) + { + crc16 = (ushort)((crc16 << 1) ^ polynomial); + } + else + { + crc16 = (ushort)(crc16 << 1); + } + } + + crcValue = crc16; + } + + private void ProcessByteCRC32(byte data) + { + const uint polynomial = 0x04C11DB7; + + crcValue ^= (uint)(data << 24); + + for(int i = 0; i < 8; i++) + { + if((crcValue & 0x80000000) != 0) + { + crcValue = (crcValue << 1) ^ polynomial; + } + else + { + crcValue = crcValue << 1; + } + } + } + + private uint GetCRCResult() + { + uint result = crcValue; + + if(typeSelect) + { + // CRC32 - use full 32 bits + if(reverseOutput) + { + result = ReverseBits32(result); + } + + if(xorOutput) + { + result ^= 0xFFFFFFFF; + } + } + else + { + // CRC16 - use only lower 16 bits + ushort result16 = (ushort)(result & 0xFFFF); + + if(reverseOutput) + { + result16 = ReverseBits16(result16); + } + + result = result16; + } + + return result; + } + + private uint crcValue; + private uint initialValue; + private bool polynomialSelect; // false=0x8005, true=0x1021 + private bool typeSelect; // false=CRC16, true=CRC32 + private bool reverseInput; + private bool reverseOutput; + private bool xorOutput; + + private enum Registers : long + { + CrcControlStatusRegister = 0x00, + CrcInitializationValueRegister = 0x04, + CrcDataRegister = 0x08, + } + } +} diff --git a/src/Emulator/Peripherals/Peripherals/Miscellaneous/MH1903_OTP.cs b/src/Emulator/Peripherals/Peripherals/Miscellaneous/MH1903_OTP.cs new file mode 100644 index 000000000..c53761b46 --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/Miscellaneous/MH1903_OTP.cs @@ -0,0 +1,292 @@ +using System; +using System.IO; + +using Antmicro.Renode.Core; +using Antmicro.Renode.Core.Structure.Registers; +using Antmicro.Renode.Logging; +using Antmicro.Renode.Peripherals.Bus; + +namespace Antmicro.Renode.Peripherals.Miscellaneous +{ + public class MH1903_OTP : BasicDoubleWordPeripheral, IBytePeripheral, IKnownSize + { + public MH1903_OTP(IMachine machine) : base(machine) + { + // OTP Data area: 0x2000 bytes (8KB) + otpData = new byte[OtpDataSize]; + // Initialize to 0xFF (unprogrammed OTP state) + for(int i = 0; i < otpData.Length; i++) + { + otpData[i] = 0xFF; + } + + DefineRegisters(); + } + + public void LoadFromFile(string path) + { + if(!File.Exists(path)) + { + this.Log(LogLevel.Warning, "OTP file not found: {0}", path); + return; + } + + try + { + var fileData = File.ReadAllBytes(path); + var bytesToCopy = Math.Min(fileData.Length, otpData.Length); + Array.Copy(fileData, otpData, bytesToCopy); + this.Log(LogLevel.Info, "Loaded {0} bytes of OTP data from {1}", bytesToCopy, path); + } + catch(Exception e) + { + this.Log(LogLevel.Error, "Failed to load OTP file {0}: {1}", path, e.Message); + } + } + + public void SaveToFile(string path) + { + try + { + File.WriteAllBytes(path, otpData); + this.Log(LogLevel.Info, "Saved OTP data to {0}", path); + } + catch(Exception e) + { + this.Log(LogLevel.Error, "Failed to save OTP file {0}: {1}", path, e.Message); + } + } + + public void SetRSASignKey(string modulusHex, string exponentHex) + { + if(string.IsNullOrEmpty(modulusHex) || string.IsNullOrEmpty(exponentHex)) + { + this.Log(LogLevel.Error, "SetRSASignKey: modulus or exponent is null or empty"); + return; + } + + // Parse hex strings to byte arrays + byte[] modulus; + byte[] exponent; + + try + { + modulus = ParseHexString(modulusHex); + exponent = ParseHexString(exponentHex); + } + catch(Exception e) + { + this.Log(LogLevel.Error, "SetRSASignKey: failed to parse hex strings: {0}", e.Message); + return; + } + + if(modulus.Length != 256) + { + this.Log(LogLevel.Error, "SetRSASignKey: modulus must be 256 bytes (512 hex chars), got {0} bytes", modulus.Length); + return; + } + + if(exponent.Length > 8) + { + this.Log(LogLevel.Error, "SetRSASignKey: exponent must be 8 bytes or less (16 hex chars max), got {0} bytes", exponent.Length); + return; + } + + // RSA Key Slot 1 (Secondary) - Offset 0x307 (exponent), 0x309 (modulus) + this.Log(LogLevel.Info, "Writing RSA key to Slot 1 (Secondary) at offset 0x307"); + + // Write exponent (8 bytes) - pad with zeros if needed + for(int i = 0; i < 8; i++) + { + otpData[0x307 + i] = (i < exponent.Length) ? exponent[i] : (byte)0; + } + + // Write modulus (256 bytes) + Array.Copy(modulus, 0, otpData, 0x309, 256); + + // RSA Key Slot 2 (Primary) - Offset 0x407 (exponent), 0x409 (modulus) + this.Log(LogLevel.Info, "Writing RSA key to Slot 2 (Primary) at offset 0x407"); + + // Write exponent (8 bytes) - pad with zeros if needed + for(int i = 0; i < 8; i++) + { + otpData[0x407 + i] = (i < exponent.Length) ? exponent[i] : (byte)0; + } + + // Write modulus (256 bytes) + Array.Copy(modulus, 0, otpData, 0x409, 256); + + this.Log(LogLevel.Info, "RSA keys written to both slots"); + } + + public override uint ReadDoubleWord(long offset) + { + // OTP data area (0x0000-0x1FFC) + if(offset < OtpDataSize) + { + // Ensure 4-byte aligned access + if(offset % 4 != 0) + { + this.Log(LogLevel.Warning, "Unaligned OTP read at offset 0x{0:X}", offset); + return 0; + } + + // Read 32-bit value from OTP data (little-endian) + uint value = (uint)(otpData[offset] | + (otpData[offset + 1] << 8) | + (otpData[offset + 2] << 16) | + (otpData[offset + 3] << 24)); + return value; + } + + // Control registers (0x2000+) + return RegistersCollection.Read(offset); + } + + public override void WriteDoubleWord(long offset, uint value) + { + // OTP data area (0x0000-0x1FFC) + if(offset < OtpDataSize) + { + // OTP is typically write-once (one-time programmable) + // We'll allow writes but log them + if(offset % 4 != 0) + { + this.Log(LogLevel.Warning, "Unaligned OTP write at offset 0x{0:X}", offset); + return; + } + + this.Log(LogLevel.Debug, "OTP write at offset 0x{0:X}: 0x{1:X}", offset, value); + + // Write 32-bit value to OTP data (little-endian) + // In real OTP, you can only change bits from 1 to 0 (programming) + // For emulation, we'll just allow direct writes + otpData[offset] = (byte)(value & 0xFF); + otpData[offset + 1] = (byte)((value >> 8) & 0xFF); + otpData[offset + 2] = (byte)((value >> 16) & 0xFF); + otpData[offset + 3] = (byte)((value >> 24) & 0xFF); + return; + } + + // Control registers (0x2000+) + RegistersCollection.Write(offset, value); + } + + public byte ReadByte(long offset) + { + // OTP data area (0x0000-0x1FFF) + if(offset < OtpDataSize) + { + return otpData[offset]; + } + + // Control registers (0x2000+) + // Read the doubleword and extract the appropriate byte + var value = RegistersCollection.Read(offset & ~3); + var byteOffset = (int)(offset & 3); + return (byte)((value >> (byteOffset * 8)) & 0xFF); + } + + public void WriteByte(long offset, byte value) + { + // OTP data area (0x0000-0x1FFF) + if(offset < OtpDataSize) + { + this.Log(LogLevel.Debug, "OTP byte write at offset 0x{0:X}: 0x{1:X}", offset, value); + otpData[offset] = value; + return; + } + + // Control registers (0x2000+) + // Read-modify-write for register area + var alignedOffset = offset & ~3; + var byteOffset = (int)(offset & 3); + var currentValue = RegistersCollection.Read(alignedOffset); + var mask = (uint)(0xFF << (byteOffset * 8)); + var newValue = (currentValue & ~mask) | ((uint)value << (byteOffset * 8)); + RegistersCollection.Write(alignedOffset, newValue); + } + + public long Size => 0x4000; + + private byte[] ParseHexString(string hex) + { + // Remove common prefixes and whitespace + hex = hex.Replace("0x", "").Replace("0X", "").Replace(" ", "").Replace("\n", "").Replace("\r", "").Replace("\t", ""); + + if(hex.Length % 2 != 0) + { + throw new ArgumentException("Hex string must have an even number of characters"); + } + + byte[] bytes = new byte[hex.Length / 2]; + for(int i = 0; i < bytes.Length; i++) + { + bytes[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16); + } + + return bytes; + } + + private void DefineRegisters() + { + // Configuration Register at 0x2000 + Registers.ConfigurationRegister.Define(this, resetValue: 0x00000008) + .WithValueField(0, 32, name: "ConfigurationData"); + + // Control/Status Register at 0x2004 + Registers.ControlStatusRegister.Define(this, resetValue: 0x80000000) + .WithFlag(31, name: "OtpReady", mode: FieldMode.Read, valueProviderCallback: _ => true) + .WithReservedBits(0, 31); + + // Protection Register at 0x2008 + Registers.ProtectionRegister.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "ProtectionData"); + + // Address Register at 0x200C + Registers.AddressRegister.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "OtpAddress"); + + // Program Data Register at 0x2010 + Registers.ProgramDataRegister.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "ProgramData"); + + // Read-Only Control Register at 0x2014 + Registers.ReadOnlyControlRegister.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "ReadOnlyControl"); + + // Read-Only Lock Enable Register at 0x2018 + Registers.ReadOnlyLockEnableRegister.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "ReadOnlyLockLength"); + + // Reserved Register at 0x201C + Registers.ReservedRegister.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "Reserved"); + + // Timing Register at 0x2020 + Registers.TimingRegister.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "Timing"); + + // Timing Enable Register at 0x2024 + Registers.TimingEnableRegister.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "TimingEnable"); + } + + private readonly byte[] otpData; + private const int OtpDataSize = 0x2000; // 8KB (0x0000-0x1FFC) + + private enum Registers : long + { + ConfigurationRegister = 0x2000, + ControlStatusRegister = 0x2004, + ProtectionRegister = 0x2008, + AddressRegister = 0x200c, + ProgramDataRegister = 0x2010, + ReadOnlyControlRegister = 0x2014, + ReadOnlyLockEnableRegister = 0x2018, + ReservedRegister = 0x201c, + TimingRegister = 0x2020, + TimingEnableRegister = 0x2024, + } + } +} diff --git a/src/Emulator/Peripherals/Peripherals/Miscellaneous/MH1903_SSC.cs b/src/Emulator/Peripherals/Peripherals/Miscellaneous/MH1903_SSC.cs new file mode 100644 index 000000000..bb4ad401c --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/Miscellaneous/MH1903_SSC.cs @@ -0,0 +1,161 @@ +using System.Collections.Generic; + +using Antmicro.Renode.Core; +using Antmicro.Renode.Core.Structure.Registers; +using Antmicro.Renode.Logging; + +namespace Antmicro.Renode.Peripherals.Miscellaneous +{ + public class MH1903_SSC : BasicDoubleWordPeripheral, IKnownSize + { + public MH1903_SSC(IMachine machine) : base(machine) + { + BuildRegisterNameMap(); + DefineRegisters(); + } + + public long Size => 0x400; + + public GPIO IRQ { get; set; } = new GPIO(); + + public override uint ReadDoubleWord(long offset) + { + var value = base.ReadDoubleWord(offset); + var regName = GetRegisterName(offset); + this.Log(LogLevel.Info, "SSC read at {0}: 0x{1:X8}", regName, value); + return value; + } + + public override void WriteDoubleWord(long offset, uint value) + { + var regName = GetRegisterName(offset); + this.Log(LogLevel.Info, "SSC write at {0}: 0x{1:X8}", regName, value); + base.WriteDoubleWord(offset, value); + } + + private string GetRegisterName(long offset) + { + if(registerNames.TryGetValue(offset, out var name)) + { + return name; + } + return $"0x{offset:X3}"; + } + + private void BuildRegisterNameMap() + { + registerNames = new Dictionary(); + + registerNames[(long)Registers.Control3] = "Control3"; + registerNames[(long)Registers.Status] = "Status"; + registerNames[(long)Registers.StatusClear] = "StatusClear"; + registerNames[(long)Registers.Acknowledge] = "Acknowledge"; + registerNames[(long)Registers.DataRamSecure] = "DataRamSecure"; + registerNames[(long)Registers.BpuReadWriteControl] = "BpuReadWriteControl"; + registerNames[(long)Registers.MainSensorLock] = "MainSensorLock"; + registerNames[(long)Registers.MainSensorEnable] = "MainSensorEnable"; + } + + private void DefineRegisters() + { + // Reserved 0x00-0x04 + Registers.Reserved00.Define(this) + .WithReservedBits(0, 32); + Registers.Reserved04.Define(this) + .WithReservedBits(0, 32); + + // Control3 at 0x08 + Registers.Control3.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "Control"); + + // Reserved 0x0C-0x100 (62 words) + for(int i = 0; i < 62; i++) + { + ((Registers)((long)Registers.Reserved0C + (i * 4))).Define(this) + .WithReservedBits(0, 32); + } + + // Status and Control registers 0x104-0x10C + Registers.Status.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "Status"); + Registers.StatusClear.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Write, name: "StatusClear"); + Registers.Acknowledge.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "Acknowledge"); + + // Reserved 0x110-0x180 (29 words) + for(int i = 0; i < 29; i++) + { + ((Registers)((long)Registers.Reserved110 + (i * 4))).Define(this) + .WithReservedBits(0, 32); + } + + // DataRamSecure at 0x184 + Registers.DataRamSecure.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "DataRamSecure"); + + // Reserved 0x188-0x1F8 (29 words) + for(int i = 0; i < 29; i++) + { + ((Registers)((long)Registers.Reserved188 + (i * 4))).Define(this) + .WithReservedBits(0, 32); + } + + // BpuReadWriteControl at 0x1FC + Registers.BpuReadWriteControl.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "BpuReadWriteControl"); + + // Reserved 0x200-0x3E8 (123 words) + for(int i = 0; i < 123; i++) + { + ((Registers)((long)Registers.Reserved200 + (i * 4))).Define(this) + .WithReservedBits(0, 32); + } + + // Main sensor lock and enable at 0x3EC-0x3F0 + Registers.MainSensorLock.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "MainSensorLock"); + Registers.MainSensorEnable.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "MainSensorEnable"); + } + + private Dictionary registerNames; + + private enum Registers : long + { + // Reserved 0x00-0x04 + Reserved00 = 0x000, + Reserved04 = 0x004, + + // Control3 at 0x08 + Control3 = 0x008, + + // Reserved 0x0C-0x100 + Reserved0C = 0x00C, + + // Status and Control 0x104-0x10C + Status = 0x104, + StatusClear = 0x108, + Acknowledge = 0x10C, + + // Reserved 0x110-0x180 + Reserved110 = 0x110, + + // DataRamSecure 0x184 + DataRamSecure = 0x184, + + // Reserved 0x188-0x1F8 + Reserved188 = 0x188, + + // BpuReadWriteControl 0x1FC + BpuReadWriteControl = 0x1FC, + + // Reserved 0x200-0x3E8 + Reserved200 = 0x200, + + // Main sensor lock and enable 0x3EC-0x3F0 + MainSensorLock = 0x3EC, + MainSensorEnable = 0x3F0, + } + } +} diff --git a/src/Emulator/Peripherals/Peripherals/Miscellaneous/MH1903_SYSCTRL.cs b/src/Emulator/Peripherals/Peripherals/Miscellaneous/MH1903_SYSCTRL.cs new file mode 100644 index 000000000..5fe464b56 --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/Miscellaneous/MH1903_SYSCTRL.cs @@ -0,0 +1,331 @@ +using Antmicro.Renode.Core; +using Antmicro.Renode.Core.Structure.Registers; +using Antmicro.Renode.Logging; + +namespace Antmicro.Renode.Peripherals.Miscellaneous +{ + public class MH1903_SYSCTRL : BasicDoubleWordPeripheral, IKnownSize + { + public MH1903_SYSCTRL(IMachine machine) : base(machine) + { + DefineRegisters(); + } + + public long Size => 1020; + + public override uint ReadDoubleWord(long offset) + { + return RegistersCollection.Read(offset); + } + + public override void WriteDoubleWord(long offset, uint value) + { + RegistersCollection.Write(offset, value); + } + + private void DefineRegisters() + { + Registers.FreqSel.Define(this, resetValue: 0x200D395A) + .WithFlag(0, name: "pclk_freq_sel") + .WithReservedBits(1, 3) + .WithFlag(4, name: "hclk_freq_sel") + .WithReservedBits(5, 3) + .WithValueField(8, 2, name: "cpu_freq_sel") + .WithReservedBits(10, 2) + .WithFlag(12, name: "ck12m_src") + .WithFlag(13, name: "sys_ck_src") + .WithReservedBits(14, 2) + .WithValueField(16, 5, name: "xtal_sel") + .WithReservedBits(21, 3) + .WithValueField(24, 3, name: "power_mode") + .WithReservedBits(27, 2) + .WithValueField(29, 3, name: "pix_clk_freq"); + + Registers.CgCtrl1.Define(this, resetValue: 0x04100000) + .WithFlag(0, name: "uart2_cg_en") + .WithFlag(1, name: "uart2_cg_en") + .WithFlag(2, name: "uart2_cg_en") + .WithFlag(3, name: "uart3_cg_en") + .WithReservedBits(4, 4) + + .WithFlag(8, name: "spi0_cg_en") + .WithFlag(9, name: "spi1_cg_en") + .WithFlag(10, name: "spi2_cg_en") + .WithReservedBits(11, 2) + .WithFlag(13, name: "spi5_cg_en") + .WithFlag(14, name: "sci0_cg_en") + .WithReservedBits(15, 1) + + .WithFlag(16, name: "sci2_cg_en") + .WithReservedBits(17, 1) + .WithFlag(18, name: "i2c0_cg_en") + .WithReservedBits(19, 1) + .WithFlag(20, name: "gpio_cg_en") + .WithFlag(21, name: "timer_cg_en") + .WithFlag(22, name: "csi2_cg_en") + .WithFlag(23, name: "dcmis_cg_en") + + .WithReservedBits(24, 2) + .WithFlag(26, name: "bpu_cg_en") + .WithFlag(27, name: "kbd_cg_en") + .WithReservedBits(28, 1) + .WithFlag(29, name: "crc_cg_en") + .WithFlag(30, name: "adc_cg_en") + .WithFlag(31, name: "trng_cg_en"); + + Registers.CgCtrl2.Define(this, resetValue: 0x00000000) + .WithFlag(0, name: "crypt_cg_en") + .WithFlag(1, name: "lcd_cg_en") + .WithFlag(2, name: "gpu_cg_en") + .WithFlag(3, name: "otp_cg_en") + .WithReservedBits(4, 1) + .WithFlag(5, name: "qr_cg_en") + .WithReservedBits(6, 2) + .WithReservedBits(8, 8) + .WithReservedBits(16, 8) + .WithReservedBits(24, 4) + .WithFlag(28, name: "usb_cg_en") + .WithFlag(29, name: "dma_cg_en") + .WithReservedBits(30, 2); + + Registers.SoftRst1.Define(this, resetValue: 0x00000000) + .WithFlag(0, name: "srst_uart0") + .WithFlag(1, name: "srst_uart1") + .WithFlag(2, name: "srst_uart2") + .WithFlag(3, name: "srst_uart3") + .WithReservedBits(4, 3) + .WithFlag(8, name: "srst_spi0") + .WithFlag(9, name: "srst_spi1") + .WithFlag(10, name: "srst_spi2") + .WithReservedBits(11, 2) + .WithFlag(13, name: "srst_spi5") + .WithFlag(14, name: "srst_sci0") + .WithReservedBits(15, 1) + .WithFlag(16, name: "srst_sci2") + .WithReservedBits(17, 1) + .WithFlag(18, name: "srst_i2c0") + .WithReservedBits(19, 1) + .WithFlag(20, name: "srst_gpio") + .WithFlag(21, name: "srst_timer0") + .WithReservedBits(22, 1) + .WithFlag(23, name: "srst_dcmis") + .WithReservedBits(24, 3) + .WithFlag(27, name: "srst_kbd") + .WithReservedBits(28, 1) + .WithFlag(29, name: "srst_crc") + .WithFlag(30, name: "srst_adc") + .WithFlag(31, name: "srst_trng"); + + Registers.SoftRst2.Define(this, resetValue: 0x00000000) + .WithFlag(0, name: "srst_crypt") + .WithFlag(1, name: "srst_lcd") + .WithFlag(2, name: "srst_gpu") + .WithReservedBits(3, 2) + .WithFlag(5, name: "srst_qr") + .WithReservedBits(6, 18) + .WithReservedBits(24, 3) + .WithFlag(27, name: "srst_cache") + .WithFlag(28, name: "srst_usb") + .WithFlag(29, name: "srst_dma") + .WithFlag(30, name: "srst_cm3") + .WithFlag(31, name: "srst_glb") + .WithWriteCallback((offset, value) => OnSoftRst2Write(offset, value)); + + Registers.LockR.Define(this, resetValue: 0xF0000000) + .WithReservedBits(0, 28) + .WithFlag(28, name: "srst_usb_lock") + .WithFlag(29, name: "srst_dma_lock") + .WithFlag(30, name: "srst_cm3_lock") + .WithFlag(31, name: "srst_glb_lock"); + + Registers.PherCtrl.Define(this, resetValue: 0x00000000) + .WithReservedBits(0, 16) + .WithFlag(16, name: "sci0_cdet_inv") + .WithReservedBits(17, 1) + .WithFlag(18, name: "sci2_cdet_inv") + .WithReservedBits(19, 1) + .WithFlag(20, name: "sci0_vccen_inv") + .WithReservedBits(21, 1) + .WithFlag(22, name: "sci2_vccen_inv") + .WithReservedBits(23, 1) + .WithFlag(24, name: "spi0_slv_sel") + .WithReservedBits(25, 7); + + Registers.Hclk1MsVal.Define(this, resetValue: 0x0000BB80) + .WithValueField(0, 17, mode: FieldMode.Read, name: "val_1ms_hclk", valueProviderCallback: _ => 0xBB80) + .WithReservedBits(17, 15); + Registers.Pclk1MsVal.Define(this, resetValue: 0x00005DC0) + .WithValueField(0, 17, mode: FieldMode.Read, name: "val_1ms_hclk", valueProviderCallback: _ => 0x5DC0) + .WithReservedBits(17, 15); + Registers.AnaCtrl.Define(this, resetValue: 0x0002C601) + .WithReservedBits(0, 4) + .WithFlag(4, name: "usb12_pd_en") + .WithFlag(5, name: "usb33_pd_en") + .WithReservedBits(6, 1) + .WithFlag(7, name: "rom_pd_en") + .WithReservedBits(8, 24); + + Registers.DmaChan.Define(this, resetValue: 0x0A0B0203) + .WithValueField(0, 4, name: "dma_ch0_if") + .WithReservedBits(4, 4) + .WithValueField(8, 4, name: "dma_ch1_if") + .WithReservedBits(12, 4) + .WithValueField(16, 4, name: "dma_ch2_if") + .WithReservedBits(20, 4) + .WithValueField(24, 4, name: "dma_ch3_if") + .WithReservedBits(28, 4); + + Registers.Sci0Glf.Define(this, resetValue: 0x20060000) + .WithValueField(0, 20, name: "card_normal_glf") + .WithReservedBits(20, 9) + .WithFlag(29, name: "card_normal_bypass") + .WithFlag(30, name: "card_normal_glf_bypass") + .WithReservedBits(31, 1); + + Registers.CardRsvd.Define(this, resetValue: 0x00000000); + + Registers.Ldo25Cr.Define(this, resetValue: 0x00000028) + .WithReservedBits(0, 4) + .WithFlag(4, name: "ldo25_pd_en") + .WithFlag(5, name: "otp_pd_en") + .WithReservedBits(6, 26); + + Registers.DmaChan1.Define(this, resetValue: 0x0A0B0203) + .WithValueField(0, 4, name: "dma_ch4_if") + .WithReservedBits(4, 4) + .WithValueField(8, 4, name: "dma_ch5_if") + .WithReservedBits(12, 4) + .WithValueField(16, 4, name: "dma_ch6_if") + .WithReservedBits(20, 4) + .WithValueField(24, 4, name: "dma_ch7_if") + .WithReservedBits(28, 4); + + Registers.MsrCr1.Define(this, resetValue: 0x07F88800) + .WithReservedBits(0, 26) + .WithFlag(27, name: "pd_msr") + .WithReservedBits(28, 4); + + Registers.MsrCr2.Define(this, resetValue: 0x0000000C) + .WithReservedBits(0, 32); + + Registers.UsbPhyCr1.Define(this, resetValue: 0x204921AE) + .WithReservedBits(0, 28) + .WithFlag(29, name: "stop_ck_for_suspend") + .WithReservedBits(30, 2); + + Registers.UsbPhyCr2.Define(this, resetValue: 0x40000000) + .WithReservedBits(0, 32); + + Registers.UsbPhyCr3.Define(this, resetValue: 0x00000000) + .WithFlag(0, name: "idpullup") + .WithFlag(1, name: "iddig") + .WithReservedBits(2, 29); + + Registers.Iso7816Cr.Define(this, resetValue: 0x00000080) + .WithReservedBits(0, 6) + .WithValueField(6, 2, name: "7816_vsel") + .WithReservedBits(8, 24); + + Registers.LdoCr.Define(this, resetValue: 0x00000000) + .WithReservedBits(0, 32); + Registers.ChgCsr.Define(this, resetValue: 0x00000000) + .WithReservedBits(0, 28) + .WithValueField(28, 2, name: "chg_state") + .WithReservedBits(30, 2); + + Registers.Pm2WkFlag.Define(this, resetValue: 0x00000000); + + Registers.CalibCsr.Define(this, resetValue: 0xAB000080) + .WithReservedBits(0, 8) + .WithFlag(8, name: "osc12m_cal_en") + .WithFlag(9, name: "osc12m_cal_done") + .WithFlag(10, name: "osc12m_usb_cal_en") + .WithReservedBits(11, 4) + .WithFlag(15, name: "32k_cal_sel") + .WithReservedBits(16, 16); + + Registers.DbgCr.Define(this, resetValue: 0x00000000) + .WithReservedBits(0, 32); + + Registers.ChipId.Define(this, resetValue: 0x03070000) + .WithValueField(0, 32, name: "chip_id", mode: FieldMode.Read); + } + + private void OnSoftRst2Write(uint offset, uint value) + { + // Check if global soft reset bit (bit 31) is set + bool globalResetRequested = (value & 0x80000000) != 0; + + if(globalResetRequested) + { + // Read LOCK_R register to check if global reset is locked + var lockRegister = RegistersCollection.Read((long)Registers.LockR); + bool globalResetLocked = (lockRegister & 0x80000000) != 0; + + if(globalResetLocked) + { + this.Log(LogLevel.Warning, "Global soft reset requested but locked (LOCK_R bit 31 is set)"); + } + else + { + this.Log(LogLevel.Info, "Global soft reset triggered via SOFT_RST2"); + machine.RequestReset(); + } + } + + // Check if CPU reset bit (bit 30) is set + bool cpuResetRequested = (value & 0x40000000) != 0; + + if(cpuResetRequested) + { + var lockRegister = RegistersCollection.Read((long)Registers.LockR); + bool cpuResetLocked = (lockRegister & 0x40000000) != 0; + + if(cpuResetLocked) + { + this.Log(LogLevel.Warning, "CPU soft reset requested but locked (LOCK_R bit 30 is set)"); + } + else + { + this.Log(LogLevel.Info, "CPU soft reset triggered via SOFT_RST2"); + machine.RequestReset(); + } + } + } + + private enum Registers : long + { + FreqSel = 0x0, + CgCtrl1 = 0x4, + CgCtrl2 = 0x8, + SoftRst1 = 0xC, + SoftRst2 = 0x10, + LockR = 0x14, + PherCtrl = 0x18, + + Hclk1MsVal = 0x2C, + Pclk1MsVal = 0x30, + AnaCtrl = 0x34, + DmaChan = 0x38, + Sci0Glf = 0x3C, + + CardRsvd = 0x48, + Ldo25Cr = 0x4C, + DmaChan1 = 0x50, + + MsrCr1 = 0x100, + MsrCr2 = 0x104, + UsbPhyCr1 = 0x108, + UsbPhyCr2 = 0x10C, + UsbPhyCr3 = 0x110, + Iso7816Cr = 0x114, + LdoCr = 0x118, + ChgCsr = 0x11c, + + Pm2WkFlag = 0x3ec, + CalibCsr = 0x3f0, + DbgCr = 0x3f4, + ChipId = 0x3f8, + } + } +} \ No newline at end of file diff --git a/src/Emulator/Peripherals/Peripherals/Miscellaneous/MH1903_TRNG.cs b/src/Emulator/Peripherals/Peripherals/Miscellaneous/MH1903_TRNG.cs new file mode 100644 index 000000000..918c31a3e --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/Miscellaneous/MH1903_TRNG.cs @@ -0,0 +1,120 @@ +using Antmicro.Renode.Core; +using Antmicro.Renode.Core.Structure.Registers; +using Antmicro.Renode.Logging; +using Antmicro.Renode.Peripherals.Timers; +using Antmicro.Renode.Time; + +namespace Antmicro.Renode.Peripherals.Miscellaneous +{ + public class MH1903_TRNG : BasicDoubleWordPeripheral, IKnownSize + { + public MH1903_TRNG(IMachine machine) : base(machine) + { + DefineRegisters(); + ticker = new LimitTimer(machine.ClockSource, 32768, this, nameof(ticker), 32, direction: Direction.Descending, eventEnabled: true, autoUpdate: true); + ticker.LimitReached += UpdateState; + ticker.Enabled = true; + } + + public GPIO IRQ { get; } = new GPIO(); + + public long Size => 0x100; + + public override void Reset() + { + base.Reset(); + ticker.Reset(); + IRQ.Unset(); + } + + private void DefineRegisters() + { + Registers.RngControlStatusRegister.Define(this, resetValue: 0x00000020) + .WithFlag(0, name: "RNG0Sample128", mode: FieldMode.Read, valueProviderCallback: _ => true) + .WithReservedBits(1, 1) + .WithFlag(2, name: "RNG0Attack", mode: FieldMode.Read, valueProviderCallback: _ => false) + .WithReservedBits(3, 1) + .WithFlag(4, name: "RngInterruptEnable") + .WithReservedBits(5, 27); + + Registers.RngDataRegister.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "RngData", mode: FieldMode.Read, + valueProviderCallback: _ => + { + // Increment RNG_INDEX + var index = RegistersCollection.Read((long)Registers.RngIndexRegister); + index = (index + 1); + // If overflow, set the overflow flag + if((index & 0x3) == 0) + { + index = 3; + index |= (uint)RNG_FIFO.RNG0FifoOverflow; + } + + RegistersCollection.Write((long)Registers.RngIndexRegister, index); + return (uint)rng.Next(); + }); + + Registers.ReservedRegister.Define(this, resetValue: 0x00000000) + .WithReservedBits(0, 32); + + Registers.RngAnalogMeasurementRegister.Define(this, resetValue: 0x000FF486) + .WithValueField(0, 32, name: "RngAnalogMeasurement"); + + Registers.RngPseudoNoiseRegister.Define(this, resetValue: 0x69D84C18) + .WithValueField(0, 32, name: "RngPseudoNoise"); + + Registers.RngIndexRegister.Define(this, resetValue: 0x00000000) + .WithValueField(0, 2, name: "RngIndex") + .WithReservedBits(2, 29) + .WithFlag(31, name: "RngFifoOverflow"); + } + + private void UpdateState() + { + // Set RNG_CSR to RNG0_S128 + var csr = RegistersCollection.Read((long)Registers.RngControlStatusRegister); + csr |= (uint)RNGCSRBit.RNG0Sample128; + RegistersCollection.Write((long)Registers.RngControlStatusRegister, csr); + // if interrupt is enabled, trigger it + if((csr & (uint)RNGCSRBit.RngInterruptEnable) != 0 && !IRQ.IsSet) + { + this.Log(LogLevel.Debug, "TRNG interrupt triggered."); + IRQ.Set(); + } + } + + private readonly PseudorandomNumberGenerator rng = EmulationManager.Instance.CurrentEmulation.RandomGenerator; + + private readonly LimitTimer ticker; + + public enum RNGCSRBit : ulong + { + RNG0Sample128 = 1u << 0, + RNG0Attack = 1u << 2, + RngInterruptEnable = 1u << 4, + } + + public enum RNG_FIFO : ulong + { + // FIFO Depth + RNG0Index = 0x00000003, + + // RNG0FifoOverflow RNG FIFO overflow bit + // FIFO read overflow indicator, when a newly generated 128-bit random number is read, + // and then a read operation is performed on the FIFO, this is set to 1, and a write + // operation clears it to 0. + RNG0FifoOverflow = 1u << 31, + } + + private enum Registers : long + { + RngControlStatusRegister = 0x00, + RngDataRegister = 0x04, + ReservedRegister = 0x08, + RngAnalogMeasurementRegister = 0x0C, + RngPseudoNoiseRegister = 0x10, + RngIndexRegister = 0x14, + } + } +} diff --git a/src/Emulator/Peripherals/Peripherals/SPI/MH1903_QSPI.cs b/src/Emulator/Peripherals/Peripherals/SPI/MH1903_QSPI.cs new file mode 100644 index 000000000..0040b7a14 --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/SPI/MH1903_QSPI.cs @@ -0,0 +1,372 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using Antmicro.Renode.Core; +using Antmicro.Renode.Core.Structure; +using Antmicro.Renode.Core.Structure.Registers; +using Antmicro.Renode.Logging; +using Antmicro.Renode.Peripherals.Bus; + +namespace Antmicro.Renode.Peripherals.SPI +{ + public class MH1903_QSPI : SimpleContainer, IDoubleWordPeripheral, IProvidesRegisterCollection, IKnownSize + { + public MH1903_QSPI(IMachine machine) : base(machine) + { + RegistersCollection = new DoubleWordRegisterCollection(this); + DefineRegisters(); + } + + public uint ReadDoubleWord(long offset) + { + return RegistersCollection.Read(offset); + } + + public void WriteDoubleWord(long offset, uint value) + { + RegistersCollection.Write(offset, value); + } + + public DoubleWordRegisterCollection RegistersCollection { get; } + + public long Size => 0x1000; + + private void DefineRegisters() + { + Registers.FlashControlCommand.Define(this, resetValue: 0x00000000) + .WithValueField(24, 8, out commandCode, name: "CommandCode") + .WithReservedBits(10, 14) + .WithValueField(8, 2, out busMode, name: "BusMode") + .WithValueField(4, 4, out cmdFormat, name: "CommandFormat") + .WithFlag(3, out commandDone, name: "CommandDone", mode: FieldMode.Read) + .WithFlag(2, out commandBusy, name: "CommandBusy", mode: FieldMode.Read) + .WithFlag(1, out accessAck, name: "AccessAcknowledge", mode: FieldMode.Read) + .WithFlag(0, name: "AccessRequest", writeCallback: (_, value) => OnAccessRequest(value)); + + Registers.Address.Define(this, resetValue: 0x00000000) + .WithValueField(8, 24, out addressField, name: "Address") + .WithValueField(0, 8, out m7_0, name: "AddressLower8Bits"); + + Registers.ByteNumber.Define(this, resetValue: 0x00000000) + .WithReservedBits(29, 3) + .WithValueField(16, 13, out writeByteCount, name: "WriteByteNumber") + .WithReservedBits(13, 3) + .WithValueField(0, 13, out readByteCount, name: "ReadByteNumber"); + + Registers.WriteDataFifo.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "WriteData", mode: FieldMode.Write, writeCallback: (_, value) => OnWriteFifo((uint)value)); + + Registers.ReadDataFifo.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "ReadData", mode: FieldMode.Read, valueProviderCallback: _ => OnReadFifo()); + + Registers.DeviceParameters.Define(this, resetValue: 0x00A80283) + .WithValueField(16, 16, name: "OneUsCount") + .WithValueField(15, 1, name: "SampleDelay") + .WithValueField(14, 1, name: "SamplePhase") + .WithReservedBits(9, 5) + .WithValueField(8, 1, name: "Protocol") + .WithValueField(4, 4, name: "DummyCycles") + .WithValueField(3, 1, name: "FlashReady") + .WithReservedBits(2, 1) + .WithValueField(0, 2, name: "FrequencySelect"); + + Registers.RegisterWriteData.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, out regWdata, name: "RegisterWriteData"); + + Registers.RegisterReadData.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, out regRdata, name: "RegisterReadData", mode: FieldMode.Read); + + Registers.InterruptMask.Define(this) + .WithReservedBits(7, 25) + .WithFlag(6, name: "TransmitFifoDataMask") + .WithFlag(5, name: "ReceiveFifoDataMask") + .WithFlag(4, name: "TransmitFifoOverflowMask") + .WithFlag(3, name: "TransmitFifoUnderflowMask") + .WithFlag(2, name: "ReceiveFifoOverflowMask") + .WithFlag(1, name: "ReceiveFifoUnderflowMask") + .WithFlag(0, name: "CommandDoneInterruptMask"); + + Registers.InterruptUnmask.Define(this) + .WithReservedBits(7, 25) + .WithFlag(6, name: "TransmitFifoDataUnmask") + .WithFlag(5, name: "ReceiveFifoDataUnmask") + .WithFlag(4, name: "TransmitFifoOverflowUnmask") + .WithFlag(3, name: "TransmitFifoUnderflowUnmask") + .WithFlag(2, name: "ReceiveFifoOverflowUnmask") + .WithFlag(1, name: "ReceiveFifoUnderflowUnmask") + .WithFlag(0, name: "CommandDoneInterruptUnmask"); + + Registers.InterruptMaskStatus.Define(this) + .WithReservedBits(7, 25) + .WithFlag(6, FieldMode.Read, name: "TransmitFifoDataMask") + .WithFlag(5, FieldMode.Read, name: "ReceiveFifoDataMask") + .WithFlag(4, FieldMode.Read, name: "TransmitFifoOverflowMask") + .WithFlag(3, FieldMode.Read, name: "TransmitFifoUnderflowMask") + .WithFlag(2, FieldMode.Read, name: "ReceiveFifoOverflowMask") + .WithFlag(1, FieldMode.Read, name: "ReceiveFifoUnderflowMask") + .WithFlag(0, FieldMode.Read, name: "CommandDoneInterruptMask"); + + Registers.InterruptStatus.Define(this) + .WithReservedBits(7, 25) // Bits 31:7 are reserved (RO) + .WithFlag(6, FieldMode.Read, name: "TransmitFifoDataStatus") + .WithFlag(5, FieldMode.Read, name: "ReceiveFifoDataStatus") + .WithFlag(4, FieldMode.Read, name: "TransmitFifoOverflowStatus") + .WithFlag(3, FieldMode.Read, name: "TransmitFifoUnderflowStatus") + .WithFlag(2, FieldMode.Read, name: "ReceiveFifoOverflowStatus") + .WithFlag(1, FieldMode.Read, name: "ReceiveFifoUnderflowStatus") + .WithFlag(0, FieldMode.Read, name: "CommandDoneInterruptStatus"); + + Registers.InterruptRawStatus.Define(this) + .WithReservedBits(7, 25) // Bits 31:7 are reserved (RO) + .WithFlag(6, FieldMode.Read, name: "TransmitFifoDataRawStatus") + .WithFlag(5, FieldMode.Read, name: "ReceiveFifoDataRawStatus") + .WithFlag(4, FieldMode.Read, name: "TransmitFifoOverflowRawStatus") + .WithFlag(3, FieldMode.Read, name: "TransmitFifoUnderflowRawStatus") + .WithFlag(2, FieldMode.Read, name: "ReceiveFifoOverflowRawStatus") + .WithFlag(1, FieldMode.Read, name: "ReceiveFifoUnderflowRawStatus") + .WithFlag(0, FieldMode.Read, name: "CommandDoneInterruptRawStatus"); + + Registers.InterruptClear.Define(this, resetValue: 0x00000000) + .WithReservedBits(7, 25) + .WithFlag(6, FieldMode.Read, name: "ClearTransmitFifoData") // Clear TX FIFO data interrupt + .WithFlag(5, FieldMode.Read, name: "ClearReceiveFifoData") // Clear RX FIFO data interrupt + .WithFlag(4, FieldMode.Read, name: "ClearTransmitFifoOverflow") // Clear TX FIFO overflow interrupt + .WithFlag(3, FieldMode.Read, name: "ClearTransmitFifoUnderflow") // Clear TX FIFO underflow interrupt + .WithFlag(2, FieldMode.Read, name: "ClearReceiveFifoOverflow") // Clear RX FIFO overflow interrupt + .WithFlag(1, FieldMode.Read, name: "ClearReceiveFifoUnderflow") // Clear RX FIFO underflow interrupt + .WithFlag(0, FieldMode.Read, name: "ClearCommandDoneInterrupt"); // Clear command completion interrupt + + Registers.CacheInterfaceCommand.Define(this, resetValue: 0xABB92BEB) + .WithValueField(24, 8, + name: "CacheInterfaceReleaseDisableCommand") // Bits 31:24 - Cache interface release/disable command + .WithValueField(16, 8, name: "CacheInterfaceDisableCommand") // Bits 23:16 - Cache interface disable command + .WithReservedBits(14, 2) + .WithValueField(12, 2, + name: "CacheInterfaceReadCommandBusMode") // Bits 13:12 - Cache interface read command bus mode + .WithValueField(8, 4, + name: "CacheInterfaceReadCommandFormat") // Bits 11:8 - Cache interface read command format + .WithValueField(0, 8, name: "CacheInterfaceReadCommand"); // Bits 7:0 - Cache interface read command + + Registers.DmaControl.Define(this, resetValue: 0x00000000) + .WithReservedBits(1, 31) + .WithFlag(0, name: "TransmitDmaEnable"); // Bit 0 - TX DMA Enable + + Registers.FifoControl.Define(this, resetValue: 0x00000000) + .WithFlag(31, name: "TransmitFifoFlush", writeCallback: (_, value) => { if(value) txFifo.Clear(); }) // Bit 31 - TX FIFO Flush (RW) + .WithReservedBits(22, 9) // Bits 30:22 - Reserved + .WithFlag(21, FieldMode.Read, name: "TransmitFifoEmpty", valueProviderCallback: _ => txFifo.Count == 0) // Bit 21 - TX FIFO Empty (RO) + .WithFlag(20, FieldMode.Read, name: "TransmitFifoFull", valueProviderCallback: _ => txFifo.Count >= MaxFifoSize) // Bit 20 - TX FIFO Full (RO) + .WithValueField(16, 4, FieldMode.Read, name: "TransmitFifoLevel", valueProviderCallback: _ => (uint)txFifo.Count) // Bits 19:16 - TX FIFO Level (RO) + .WithFlag(15, name: "ReceiveFifoFlush", writeCallback: (_, value) => { if(value) rxFifo.Clear(); }) // Bit 15 - RX FIFO Flush (RW) + .WithReservedBits(6, 9) // Bits 14:6 - Reserved + .WithFlag(5, FieldMode.Read, name: "ReceiveFifoEmpty", valueProviderCallback: _ => rxFifo.Count == 0) // Bit 5 - RX FIFO Empty (RO) + .WithFlag(4, FieldMode.Read, name: "ReceiveFifoFull", valueProviderCallback: _ => rxFifo.Count >= MaxFifoSize) // Bit 4 - RX FIFO Full (RO) + .WithValueField(0, 4, FieldMode.Read, name: "ReceiveFifoLevel", valueProviderCallback: _ => (uint)rxFifo.Count); // Bits 3:0 - RX FIFO Level (RO) + } + + private void OnAccessRequest(bool enabled) + { + if(!enabled) + return; + + commandBusy.Value = true; + accessAck.Value = true; + try + { + ExecuteFcuCommand(); + } + finally + { + commandBusy.Value = false; + commandDone.Value = true; + accessAck.Value = false; + } + } + + private void ExecuteFcuCommand() + { + ISPIPeripheral slave = null; + if(ChildCollection.Count > 0) + { + slave = ChildCollection.Values.First(); + } + + if(slave == null) + { + this.Log(LogLevel.Warning, "QSPI: No SPI slave connected"); + return; + } + + var cmd = (byte)commandCode.Value; + var format = (uint)cmdFormat.Value; + var addr = (uint)addressField.Value; + var wdata = (uint)regWdata.Value; + + this.Log(LogLevel.Debug, "FCU Command: cmd=0x{0:X2}, format={1}, addr=0x{2:X6}", cmd, format, addr); + + // Send command byte + slave.Transmit(cmd); + + // Handle different command formats according to datasheet + switch(format) + { + case 0x0: // 8bit cmd only + break; + + case 0x1: // 8bit cmd + 8bit read reg data + regRdata.Value = slave.Transmit(0x00); + break; + + case 0x2: // 8bit cmd + 16bit read reg data + { + uint data = (uint)(slave.Transmit(0x00) << 8); + data |= slave.Transmit(0x00); + regRdata.Value = data; + } + break; + + case 0x3: // 8bit cmd + 24bit read reg data + { + uint data = (uint)(slave.Transmit(0x00) << 16); + data |= (uint)(slave.Transmit(0x00) << 8); + data |= slave.Transmit(0x00); + regRdata.Value = data; + this.Log(LogLevel.Debug, "Read 24-bit data: 0x{0:X6}", data); + } + break; + + case 0x7: // 8bit cmd + 8bit write reg data + slave.Transmit((byte)(wdata & 0xFF)); + break; + + case 0x8: // 8bit cmd + 16bit write reg data + slave.Transmit((byte)((wdata >> 8) & 0xFF)); + slave.Transmit((byte)(wdata & 0xFF)); + break; + + case 0x9: // 8bit cmd + 24bit address + slave.Transmit((byte)((addr >> 16) & 0xFF)); + slave.Transmit((byte)((addr >> 8) & 0xFF)); + slave.Transmit((byte)(addr & 0xFF)); + break; + + case 0xA: // 8bit cmd + 24bit addr + read data (use FIFO) + case 0xB: // 8bit cmd + 24bit addr + dummy + read data (use FIFO) + case 0xD: // 8bit cmd + 24bit addr + program data (use FIFO) + // Send address + slave.Transmit((byte)((addr >> 16) & 0xFF)); + slave.Transmit((byte)((addr >> 8) & 0xFF)); + slave.Transmit((byte)(addr & 0xFF)); + + // Use FIFO for data transfer + ExecuteFifoTransaction(slave); + break; + + default: + this.Log(LogLevel.Warning, "Unsupported FCU command format: 0x{0:X}", format); + break; + } + + slave.FinishTransmission(); + } + + private void ExecuteFifoTransaction(ISPIPeripheral slave) + { + uint writeCount = (uint)writeByteCount.Value; + uint readCount = (uint)readByteCount.Value; + + for(uint i = 0; i < Math.Max(writeCount, readCount); i++) + { + byte writeData = 0; + if(i < writeCount && txFifo.Count > 0) + { + writeData = txFifo.Dequeue(); + } + + byte readData = slave.Transmit(writeData); + + if(i < readCount) + { + if(rxFifo.Count < MaxFifoSize) + { + rxFifo.Enqueue(readData); + } + } + } + } + + private void OnWriteFifo(uint value) + { + if(txFifo.Count < MaxFifoSize) + { + txFifo.Enqueue((byte)((value >> 24) & 0xFF)); + if(txFifo.Count < MaxFifoSize) + txFifo.Enqueue((byte)((value >> 16) & 0xFF)); + if(txFifo.Count < MaxFifoSize) + txFifo.Enqueue((byte)((value >> 8) & 0xFF)); + if(txFifo.Count < MaxFifoSize) + txFifo.Enqueue((byte)(value & 0xFF)); + } + } + + private uint OnReadFifo() + { + uint value = 0; + for(int i = 0; i < 4 && rxFifo.Count > 0; i++) + { + byte data = rxFifo.Dequeue(); + value = (value << 8) | data; + } + return value; + } + + public override void Reset() + { + RegistersCollection.Reset(); + txFifo.Clear(); + rxFifo.Clear(); + commandBusy.Value = false; + commandDone.Value = false; + accessAck.Value = false; + } + + private IFlagRegisterField commandDone; + private IFlagRegisterField commandBusy; + private IFlagRegisterField accessAck; + private IValueRegisterField writeByteCount; + private IValueRegisterField readByteCount; + private IValueRegisterField commandCode; + private IValueRegisterField busMode; + private IValueRegisterField cmdFormat; + private IValueRegisterField addressField; + private IValueRegisterField m7_0; + private IValueRegisterField regWdata; + private IValueRegisterField regRdata; + + private readonly Queue txFifo = new Queue(); + private readonly Queue rxFifo = new Queue(); + private const int MaxFifoSize = 16; + + private enum Registers : long + { + FlashControlCommand = 0x0, + Address = 0x4, + ByteNumber = 0x8, + WriteDataFifo = 0xC, + ReadDataFifo = 0x10, + DeviceParameters = 0x14, + RegisterWriteData = 0x18, + RegisterReadData = 0x1C, + InterruptMask = 0x20, + InterruptUnmask = 0x24, + InterruptMaskStatus = 0x28, + InterruptStatus = 0x2C, + InterruptRawStatus = 0x30, + InterruptClear = 0x34, + CacheInterfaceCommand = 0x38, + DmaControl = 0x3C, + FifoControl = 0x40 + } + } +} \ No newline at end of file diff --git a/src/Emulator/Peripherals/Peripherals/SPI/MH1903_SPIM.cs b/src/Emulator/Peripherals/Peripherals/SPI/MH1903_SPIM.cs new file mode 100644 index 000000000..2c76a30ac --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/SPI/MH1903_SPIM.cs @@ -0,0 +1,297 @@ +using System.Collections.Generic; +using System.Linq; + +using Antmicro.Renode.Core; +using Antmicro.Renode.Core.Structure; +using Antmicro.Renode.Core.Structure.Registers; +using Antmicro.Renode.Peripherals.Bus; + +namespace Antmicro.Renode.Peripherals.SPI +{ + public class MH1903_SPIM : SimpleContainer, IDoubleWordPeripheral, IWordPeripheral, IProvidesRegisterCollection, IKnownSize + { + + public MH1903_SPIM(IMachine machine) : base(machine) + { + RegistersCollection = new DoubleWordRegisterCollection(this); + DefineRegisters(); + } + + public uint ReadDoubleWord(long offset) + { + return RegistersCollection.Read(offset); + } + + public void WriteDoubleWord(long offset, uint value) + { + RegistersCollection.Write(offset, value); + } + + public ushort ReadWord(long offset) + { + var alignedOffset = offset & ~0x3; + var existingValue = RegistersCollection.Read(alignedOffset); + int shift = (int)(offset & 0x3) * 8; + uint mask = 0xFFFFu << shift; + return (ushort)((existingValue & mask) >> shift); + } + + public void WriteWord(long offset, ushort value) + { + var alignedOffset = offset & ~0x3; + var existingValue = RegistersCollection.Read(alignedOffset); + int shift = (int)(offset & 0x3) * 8; + uint mask = 0xFFFFu << shift; + uint newValue = (existingValue & ~mask) | ((uint)value << shift); + RegistersCollection.Write(alignedOffset, newValue); + } + + public DoubleWordRegisterCollection RegistersCollection { get; } + + public GPIO IRQ { get; } = new GPIO(); + + public long Size => 0x100; + + private void DefineRegisters() + { + // Control Register 0 at 0x00 + Registers.Control0.Define(this, resetValue: 0x00000000) + .WithValueField(0, 4, name: "DataFrameSize", writeCallback: (_, value) => dfs = (uint)value) + .WithValueField(4, 2, name: "FrameFormat") + .WithValueField(6, 2, name: "ClockPhase") + .WithValueField(8, 2, name: "ClockPolarity") + .WithValueField(10, 4, name: "TransferMode") + .WithValueField(14, 2, name: "ShiftRegisterLoop") + .WithValueField(16, 5, name: "NumberOfDataFrames") + .WithReservedBits(21, 11); + + // Control Register 1 at 0x04 + Registers.Control1.Define(this, resetValue: 0x00000000) + .WithValueField(0, 16, name: "NumberOfDataFramesOrReceiveFrames"); + + // SPI Enable Register at 0x08 + Registers.SpiEnable.Define(this, resetValue: 0x00000000) + .WithFlag(0, name: "Enable", + writeCallback: (_, value) => spiEnabled = value, + valueProviderCallback: _ => spiEnabled); + + // Master and Slave Select Register at 0x0C + Registers.MasterAndSlaveSelect.Define(this, resetValue: 0x00000000) + .WithFlag(0, name: "MasterWriteMode") + .WithFlag(1, name: "MasterDataDirection") + .WithFlag(2, name: "MasterHandshake"); + + // Slave Enable Register at 0x10 + Registers.SlaveEnable.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "SlaveEnable"); + + // Baud Rate Divisor at 0x14 + Registers.BaudRateDivisor.Define(this, resetValue: 0x00000000) + .WithValueField(0, 16, name: "SerialClockDivisor"); + + // Transmit FIFO Threshold Level at 0x18 + Registers.TransmitFifoThreshold.Define(this, resetValue: 0x00000000) + .WithValueField(0, 8, name: "TransmitFifoThreshold"); + + // Receive FIFO Threshold Level at 0x1C + Registers.ReceiveFifoThreshold.Define(this, resetValue: 0x00000000) + .WithValueField(0, 8, name: "ReceiveFifoThreshold"); + + // Transmit FIFO Level Register at 0x20 + Registers.TransmitFifoLevel.Define(this, resetValue: 0x00000000) + .WithValueField(0, 9, FieldMode.Read, name: "TransmitFifoLevel", + valueProviderCallback: _ => (uint)txFifo.Count); + + // Receive FIFO Level Register at 0x24 + Registers.ReceiveFifoLevel.Define(this, resetValue: 0x00000000) + .WithValueField(0, 9, FieldMode.Read, name: "ReceiveFifoLevel", + valueProviderCallback: _ => (uint)rxFifo.Count); + + // Status Register at 0x28 + Registers.Status.Define(this, resetValue: 0x00000006) + .WithFlag(0, FieldMode.Read, name: "Busy", valueProviderCallback: _ => false) + .WithFlag(1, FieldMode.Read, name: "TransmitFifoNotFull", valueProviderCallback: _ => txFifo.Count < FifoSize) + .WithFlag(2, FieldMode.Read, name: "TransmitFifoEmpty", valueProviderCallback: _ => txFifo.Count == 0) + .WithFlag(3, FieldMode.Read, name: "ReceiveFifoNotEmpty", valueProviderCallback: _ => rxFifo.Count > 0) + .WithFlag(4, FieldMode.Read, name: "ReceiveFifoFull", valueProviderCallback: _ => rxFifo.Count >= FifoSize) + .WithFlag(5, FieldMode.Read, name: "TransmissionError", valueProviderCallback: _ => false) + .WithFlag(6, FieldMode.Read, name: "DataCollision", valueProviderCallback: _ => false) + .WithReservedBits(7, 25); + + // Interrupt Mask Register at 0x2C + Registers.InterruptMask.Define(this, resetValue: 0x00000000) + .WithFlag(0, name: "TransmitEmptyInterruptMask") + .WithFlag(1, name: "TransmitOverflowInterruptMask") + .WithFlag(2, name: "ReceiveUnderflowInterruptMask") + .WithFlag(3, name: "ReceiveOverflowInterruptMask") + .WithFlag(4, name: "ReceiveFifoFullInterruptMask") + .WithFlag(5, name: "MultiMasterInterruptMask") + .WithReservedBits(6, 26); + + // Interrupt Status Register at 0x30 + Registers.InterruptStatus.Define(this, resetValue: 0x00000000) + .WithFlag(0, FieldMode.Read, name: "TransmitEmptyInterrupt", valueProviderCallback: _ => false) + .WithFlag(1, FieldMode.Read, name: "TransmitOverflowInterrupt", valueProviderCallback: _ => false) + .WithFlag(2, FieldMode.Read, name: "ReceiveUnderflowInterrupt", valueProviderCallback: _ => false) + .WithFlag(3, FieldMode.Read, name: "ReceiveOverflowInterrupt", valueProviderCallback: _ => false) + .WithFlag(4, FieldMode.Read, name: "ReceiveFifoFullInterrupt", valueProviderCallback: _ => false) + .WithFlag(5, FieldMode.Read, name: "MultiMasterInterrupt", valueProviderCallback: _ => false) + .WithReservedBits(6, 26); + + // Raw Interrupt Status Register at 0x34 + Registers.RawInterruptStatus.Define(this, resetValue: 0x00000000) + .WithFlag(0, FieldMode.Read, name: "TransmitEmptyRawInterrupt") + .WithFlag(1, FieldMode.Read, name: "TransmitOverflowRawInterrupt") + .WithFlag(2, FieldMode.Read, name: "ReceiveUnderflowRawInterrupt") + .WithFlag(3, FieldMode.Read, name: "ReceiveOverflowRawInterrupt") + .WithFlag(4, FieldMode.Read, name: "ReceiveFifoFullRawInterrupt") + .WithFlag(5, FieldMode.Read, name: "MultiMasterRawInterrupt") + .WithReservedBits(6, 26); + + // Transmit FIFO Overflow Interrupt Clear at 0x38 + Registers.TransmitOverflowInterruptClear.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "TransmitOverflowInterruptClear"); + + // Receive FIFO Overflow Interrupt Clear at 0x3C + Registers.ReceiveOverflowInterruptClear.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "ReceiveOverflowInterruptClear"); + + // Receive FIFO Underflow Interrupt Clear at 0x40 + Registers.ReceiveUnderflowInterruptClear.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "ReceiveUnderflowInterruptClear"); + + // Master Sync Interrupt Clear at 0x44 + Registers.MasterSyncInterruptClear.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "MasterSyncInterruptClear"); + + // Interrupt Clear Register at 0x48 + Registers.InterruptClear.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, FieldMode.Read, name: "InterruptClear"); + + // DMA Control Register at 0x4C + Registers.DmaControl.Define(this, resetValue: 0x00000000) + .WithFlag(0, name: "ReceiveDmaEnable") + .WithFlag(1, name: "TransmitDmaEnable"); + + // DMA TX Data Level at 0x50 + Registers.DmaTransmitDataLevel.Define(this, resetValue: 0x00000000) + .WithValueField(0, 8, name: "DmaTransmitDataLevel"); + + // DMA RX Data Level at 0x54 + Registers.DmaReceiveDataLevel.Define(this, resetValue: 0x00000000) + .WithValueField(0, 8, name: "DmaReceiveDataLevel"); + + // Identification Register at 0x58 + Registers.Identification.Define(this, resetValue: 0x6117A000) + .WithValueField(0, 32, FieldMode.Read, name: "Identification"); + + // Version Register at 0x5C + Registers.Version.Define(this, resetValue: 0x3230302A) + .WithValueField(0, 32, FieldMode.Read, name: "Version"); + + // Data Register at 0x60 + Registers.Data.Define(this, resetValue: 0x00000000) + .WithValueField(0, 32, name: "Data", + writeCallback: (_, value) => OnDataWrite((uint)value), + valueProviderCallback: _ => OnDataRead()); + + // RX Sample Delay at 0xF0 + Registers.RxSampleDelay.Define(this, resetValue: 0x00000000) + .WithValueField(0, 8, name: "RxSampleDelay"); + } + + private void OnDataWrite(uint value) + { + if(txFifo.Count < FifoSize) + { + txFifo.Enqueue((byte)value); + ExecuteSpiTransaction(); + } + } + + private uint OnDataRead() + { + if(rxFifo.Count > 0) + { + return rxFifo.Dequeue(); + } + return 0; + } + + private void ExecuteSpiTransaction() + { + if(!spiEnabled || txFifo.Count == 0) + return; + + if(ChildCollection.Count == 0) + { + // Act like we send and nothing answered + txFifo.Dequeue(); + rxFifo.Enqueue(0xFF); + return; + } + + var slave = ChildCollection.Values.First(); + + // Transmit one byte from TX FIFO and get response + byte txByte = txFifo.Dequeue(); + byte rxByte = slave.Transmit(txByte); + rxFifo.Enqueue(rxByte); + + // If more data in TX FIFO, continue + if(txFifo.Count > 0) + { + ExecuteSpiTransaction(); + } + else + { + //slave.FinishTransmission(); + } + } + private bool spiEnabled = false; + private uint dfs = 0; + + private readonly Queue txFifo = new Queue(); + private readonly Queue rxFifo = new Queue(); + private const int FifoSize = 256; + + public override void Reset() + { + RegistersCollection.Reset(); + txFifo.Clear(); + rxFifo.Clear(); + spiEnabled = false; + dfs = 0; + } + + private enum Registers : long + { + Control0 = 0x00, + Control1 = 0x04, + SpiEnable = 0x08, + MasterAndSlaveSelect = 0x0C, + SlaveEnable = 0x10, + BaudRateDivisor = 0x14, + TransmitFifoThreshold = 0x18, + ReceiveFifoThreshold = 0x1C, + TransmitFifoLevel = 0x20, + ReceiveFifoLevel = 0x24, + Status = 0x28, + InterruptMask = 0x2C, + InterruptStatus = 0x30, + RawInterruptStatus = 0x34, + TransmitOverflowInterruptClear = 0x38, + ReceiveOverflowInterruptClear = 0x3C, + ReceiveUnderflowInterruptClear = 0x40, + MasterSyncInterruptClear = 0x44, + InterruptClear = 0x48, + DmaControl = 0x4C, + DmaTransmitDataLevel = 0x50, + DmaReceiveDataLevel = 0x54, + Identification = 0x58, + Version = 0x5C, + Data = 0x60, + RxSampleDelay = 0xF0, + } + } +} diff --git a/src/Emulator/Peripherals/Peripherals/Timers/MH1903_Timer.cs b/src/Emulator/Peripherals/Peripherals/Timers/MH1903_Timer.cs new file mode 100644 index 000000000..d5ea220b9 --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/Timers/MH1903_Timer.cs @@ -0,0 +1,471 @@ +// +// Copyright (c) 2010-2025 Antmicro +// +// This file is licensed under the MIT License. +// Full license text is available in 'licenses/MIT.txt'. +// +using System; +using System.Collections.Generic; + +using Antmicro.Renode.Core; +using Antmicro.Renode.Core.Structure.Registers; +using Antmicro.Renode.Logging; +using Antmicro.Renode.Time; + +namespace Antmicro.Renode.Peripherals.Timers +{ + /// + /// MH1903 Timer Block - Contains all 8 timers + /// + /// This peripheral implements the complete MH1903 timer block with 8 independent timers. + /// Each timer counts down from LoadCount to 0. + /// + /// Register Layout (base 0x40013000): + /// 0x00-0x0F: Timer0 (LoadCount, CurrentValue, ControlReg, EOI, IntStatus) + /// 0x14-0x23: Timer1 (same registers) + /// 0x28-0x37: Timer2 (same registers) + /// 0x3C-0x4B: Timer3 (same registers) + /// 0x50-0x5F: Timer4 (same registers) + /// 0x64-0x73: Timer5 (same registers) + /// 0x78-0x87: Timer6 (same registers) + /// 0x8C-0x9B: Timer7 (same registers) + /// 0xA0: TimersIntStatus - Combined interrupt status for all timers + /// 0xA4: TimersEOI - Global End of Interrupt (clears all interrupts) + /// 0xA8: TimersRawIntStatus - Raw interrupt status (before masking) + /// 0xB0-0xCC: Timer0-7 LoadCount2 registers (for PWM mode) + /// + /// Each timer operates in count-down mode: + /// - When enabled, CurrentValue starts at LoadCount (or 0xFFFFFFFF in free-running) + /// - Decrements on each timer tick + /// - When reaching 0, triggers interrupt if not masked + /// - In free-running mode (mode=0): counts from 0xFFFFFFFF and reloads 0xFFFFFFFF + /// - In user-defined mode (mode=1): counts from LoadCount and reloads LoadCount + /// + /// PWM Mode: + /// - When PWM bit is set, timer alternates between LoadCount and LoadCount2 + /// - LoadCount sets LOW period: (LoadCount + 1) * PCLK_Period + /// - LoadCount2 sets HIGH period: (LoadCount2 + 1) * PCLK_Period + /// + public class MH1903_Timer : BasicDoubleWordPeripheral, IKnownSize, INumberedGPIOOutput + { + public MH1903_Timer(IMachine machine, ulong frequency = 72000000) : base(machine) + { + var irqs = new Dictionary(); + timers = new TimerUnit[8]; + + for(int i = 0; i < 8; i++) + { + irqs[i] = new GPIO(); + var timerIndex = i; // Capture for lambda + timers[i] = new TimerUnit(machine, frequency, this, i, irqs[i], () => UpdateInterrupt(timerIndex)); + } + + Connections = irqs; + DefineRegisters(); + } + + public override void Reset() + { + base.Reset(); + foreach(var timer in timers) + { + timer.Reset(); + } + } + + public IReadOnlyDictionary Connections { get; } + + public long Size => 0x100; + + private void DefineRegisters() + { + // Define registers for each of the 8 timers + for(int i = 0; i < 8; i++) + { + var timerIndex = i; + var baseOffset = i * 0x14; // Each timer block is 0x14 bytes apart + + // LoadCount register (offset 0x00 from timer base) + ((Registers)(baseOffset + 0x00)).Define(this) + .WithValueField(0, 32, name: $"Timer{i}LoadCount", + writeCallback: (_, value) => + { + timers[timerIndex].LoadCount = (uint)value; + this.Log(LogLevel.Noisy, "Timer{0}: LoadCount = 0x{1:X}", timerIndex, value); + }, + valueProviderCallback: _ => timers[timerIndex].LoadCount); + + // CurrentValue register (offset 0x04 from timer base) + ((Registers)(baseOffset + 0x04)).Define(this) + .WithValueField(0, 32, FieldMode.Read, name: $"Timer{i}CurrentValue", + valueProviderCallback: _ => timers[timerIndex].GetCurrentValue()); + + // ControlReg register (offset 0x08 from timer base) + ((Registers)(baseOffset + 0x08)).Define(this) + .WithFlag(0, name: $"Timer{i}_Enable", + writeCallback: (_, value) => timers[timerIndex].Enabled = value, + valueProviderCallback: _ => timers[timerIndex].Enabled) + .WithFlag(1, name: $"Timer{i}_Mode", + writeCallback: (_, value) => timers[timerIndex].Mode = value, + valueProviderCallback: _ => timers[timerIndex].Mode) + .WithFlag(2, name: $"Timer{i}_IntMask", + writeCallback: (_, value) => timers[timerIndex].InterruptMask = value, + valueProviderCallback: _ => timers[timerIndex].InterruptMask) + .WithFlag(3, name: $"Timer{i}_PWM", + writeCallback: (_, value) => timers[timerIndex].PwmMode = value, + valueProviderCallback: _ => timers[timerIndex].PwmMode) + .WithFlag(4, name: $"Timer{i}_PWM_Oneshot", + writeCallback: (_, value) => timers[timerIndex].PwmOneshot = value, + valueProviderCallback: _ => timers[timerIndex].PwmOneshot) + .WithFlag(5, name: $"Timer{i}_TIM_Reload", + writeCallback: (_, value) => timers[timerIndex].TimReload = value, + valueProviderCallback: _ => timers[timerIndex].TimReload) + .WithReservedBits(6, 26) + .WithWriteCallback((_, value) => + { + this.Log(LogLevel.Noisy, "Timer{0}: ControlReg written = 0x{1:X} (Enable={2}, Mode={3}, IntMask={4}, PWM={5}, PWM_Oneshot={6}, TIM_Reload={7})", + timerIndex, value, + timers[timerIndex].Enabled, + timers[timerIndex].Mode, + timers[timerIndex].InterruptMask, + timers[timerIndex].PwmMode, + timers[timerIndex].PwmOneshot, + timers[timerIndex].TimReload); + }); + + // EOI register (offset 0x0C from timer base) + ((Registers)(baseOffset + 0x0C)).Define(this) + .WithValueField(0, 32, FieldMode.Read, name: $"Timer{i}EOI", + valueProviderCallback: _ => + { + var status = timers[timerIndex].InterruptStatus ? 1u : 0u; + this.Log(LogLevel.Noisy, "Timer{0}: EOI read, status was {1}, clearing interrupt", timerIndex, status); + timers[timerIndex].ClearInterrupt(); + return status; + }); + + // IntStatus register (offset 0x10 from timer base) + ((Registers)(baseOffset + 0x10)).Define(this) + .WithFlag(0, FieldMode.Read, name: $"Timer{i}IntStatus", + valueProviderCallback: _ => timers[timerIndex].InterruptStatus) + .WithReservedBits(1, 31); + } + + // Global registers + Registers.TimersIntStatus.Define(this) + .WithFlags(0, 8, FieldMode.Read, name: "TimersIntStatus", + valueProviderCallback: (i, _) => timers[i].InterruptStatus && !timers[i].InterruptMask) + .WithReservedBits(8, 24); + + Registers.TimersEOI.Define(this) + .WithValueField(0, 32, FieldMode.Read, name: "TimersEOI", + valueProviderCallback: _ => + { + uint status = 0; + for(int i = 0; i < 8; i++) + { + if(timers[i].InterruptStatus) + { + status |= (1u << i); + timers[i].ClearInterrupt(); + } + } + return status; + }); + + Registers.TimersRawIntStatus.Define(this) + .WithFlags(0, 8, FieldMode.Read, name: "TimersRawIntStatus", + valueProviderCallback: (i, _) => timers[i].InterruptStatus) + .WithReservedBits(8, 24); + + // LoadCount2 registers (0xB0-0xCC, 4 bytes apart) + for(var i = 0; i < 8; i++) + { + var timerIndex = i; + ((Registers)(0xB0 + i * 4)).Define(this) + .WithValueField(0, 32, name: $"Timer{i}LoadCount2", + writeCallback: (_, value) => + { + timers[timerIndex].LoadCount2 = (uint)value; + this.Log(LogLevel.Noisy, "Timer{0}: LoadCount2 = 0x{1:X}", timerIndex, value); + }, + valueProviderCallback: _ => timers[timerIndex].LoadCount2); + } + } + + private void UpdateInterrupt(int timerIndex) + { + var timer = timers[timerIndex]; + var irqState = timer.InterruptStatus && !timer.InterruptMask; + this.Log(LogLevel.Noisy, "Timer{0}: UpdateInterrupt - intStatus={1}, intMask={2}, irqState={3}", + timerIndex, timer.InterruptStatus, timer.InterruptMask, irqState); + timer.IRQ.Set(irqState); + } + + private readonly TimerUnit[] timers; + + private class TimerUnit + { + public TimerUnit(IMachine machine, ulong frequency, MH1903_Timer parent, int index, IGPIO irq, Action onInterruptChanged) + { + this.parent = parent; + this.index = index; + this.IRQ = irq; + this.onInterruptChanged = onInterruptChanged; + + innerTimer = new LimitTimer( + machine.ClockSource, + frequency, + parent, + $"timer{index}", + 0xFFFFFFFF, + Direction.Ascending, + eventEnabled: true, + autoUpdate: true + ); + + innerTimer.LimitReached += OnLimitReached; + } + + public void Reset() + { + innerTimer.Reset(); + loadCount = 0x00000000; // Reset value from table + loadCount2 = 0x00000000; + interruptStatus = false; + pwmCurrentHigh = false; + enabled = false; + mode = false; + interruptMask = false; + pwmMode = false; + pwmOneshot = false; + timReload = false; + onInterruptChanged?.Invoke(); + } + + public uint GetCurrentValue() + { + if(!innerTimer.Enabled) + { + return loadCount; + } + + // Sync time before reading + if(parent.machine.SystemBus.TryGetCurrentCPU(out var cpu)) + { + cpu.SyncTime(); + } + + return loadCount - (uint)innerTimer.Value; + } + + public void ClearInterrupt() + { + interruptStatus = false; + parent.Log(LogLevel.Noisy, "Timer{0}: interrupt cleared, calling UpdateInterrupt", index); + onInterruptChanged?.Invoke(); + } + + public IGPIO IRQ { get; } + + public uint LoadCount + { + get => loadCount; + set + { + loadCount = value; + innerTimer.Limit = loadCount; + parent.Log(LogLevel.Noisy, "Timer{0}: LoadCount set to 0x{1:X}", index, loadCount); + if(innerTimer.Enabled) + { + innerTimer.Value = 0; + } + } + } + + public uint LoadCount2 + { + get => loadCount2; + set + { + loadCount2 = value; + parent.Log(LogLevel.Noisy, "Timer{0}: LoadCount2 set to 0x{1:X}", index, loadCount2); + } + } + + public bool Enabled + { + get => enabled; + set + { + if(value && !enabled) + { + // Clear interrupt status when starting timer + interruptStatus = false; + pwmCurrentHigh = false; + innerTimer.Value = 0; + innerTimer.Limit = loadCount; + innerTimer.Enabled = true; + onInterruptChanged?.Invoke(); + parent.Log(LogLevel.Noisy, "Timer{0}: enabled, counting down from 0x{1:X}", index, loadCount); + } + else if(!value && enabled) + { + innerTimer.Enabled = false; + parent.Log(LogLevel.Noisy, "Timer{0}: disabled", index); + } + enabled = value; + } + } + + public bool Mode + { + get => mode; + set + { + mode = value; + parent.Log(LogLevel.Noisy, "Timer{0}: mode set to {1}", index, value ? "user-defined" : "free-running"); + } + } + + public bool InterruptMask + { + get => interruptMask; + set + { + interruptMask = value; + parent.Log(LogLevel.Debug, "Timer{0}: interrupt mask set to {1}", index, value ? "masked" : "unmasked"); + onInterruptChanged?.Invoke(); + } + } + + public bool PwmMode + { + get => pwmMode; + set + { + pwmMode = value; + parent.Log(LogLevel.Debug, "Timer{0}: PWM mode {1}", index, value ? "enabled" : "disabled"); + } + } + + public bool PwmOneshot + { + get => pwmOneshot; + set + { + pwmOneshot = value; + parent.Log(LogLevel.Debug, "Timer{0}: PWM oneshot {1}", index, value ? "enabled" : "disabled"); + } + } + + public bool TimReload + { + get => timReload; + set + { + timReload = value; + parent.Log(LogLevel.Debug, "Timer{0}: TIM_Reload {1}", index, value ? "enabled" : "disabled"); + } + } + + public bool InterruptStatus => interruptStatus; + + private void OnLimitReached() + { + parent.Log(LogLevel.Noisy, "Timer{0}: reached 0", index); + + interruptStatus = true; + onInterruptChanged?.Invoke(); + + // Handle PWM mode + if(pwmMode) + { + if(pwmCurrentHigh) + { + // Was in HIGH period, now go to LOW + pwmCurrentHigh = false; + innerTimer.Limit = loadCount; + innerTimer.Value = 0; + parent.Log(LogLevel.Noisy, "Timer{0}: PWM switching to LOW period (LoadCount=0x{1:X})", index, loadCount); + } + else + { + // Was in LOW period, now go to HIGH + pwmCurrentHigh = true; + innerTimer.Limit = loadCount2; + innerTimer.Value = 0; + parent.Log(LogLevel.Noisy, "Timer{0}: PWM switching to HIGH period (LoadCount2=0x{1:X})", index, loadCount2); + } + + // Check PWM oneshot mode + if(pwmOneshot && pwmCurrentHigh) + { + innerTimer.Enabled = false; + enabled = false; + parent.Log(LogLevel.Noisy, "Timer{0}: PWM oneshot completed", index); + } + } + // Check mode: mode=0 is free-running, mode=1 is user-defined + else if(!mode) + { + // Free-running mode (mode=0): reload from 0xFFFFFFFF + innerTimer.Limit = 0xFFFFFFFF; + innerTimer.Value = 0; + parent.Log(LogLevel.Noisy, "Timer{0}: free-running mode, reloading from 0xFFFFFFFF", index); + } + else + { + // User-defined mode (mode=1): always reload from LoadCount + innerTimer.Value = 0; + innerTimer.Limit = loadCount; + parent.Log(LogLevel.Noisy, "Timer{0}: user-defined mode, reloading from LoadCount 0x{1:X}", index, loadCount); + } + } + + private uint loadCount; + private uint loadCount2; + private bool interruptStatus; + private bool pwmCurrentHigh; + private bool enabled; + private bool mode; + private bool interruptMask; + private bool pwmMode; + private bool pwmOneshot; + private bool timReload; + + private readonly LimitTimer innerTimer; + private readonly MH1903_Timer parent; + private readonly int index; + private readonly Action onInterruptChanged; + } + + private enum Registers : long + { + // Timer 0-7 base offsets (each timer occupies 0x14 bytes, but only uses 5 registers) + Timer0Base = 0x00, + Timer1Base = 0x14, + Timer2Base = 0x28, + Timer3Base = 0x3C, + Timer4Base = 0x50, + Timer5Base = 0x64, + Timer6Base = 0x78, + Timer7Base = 0x8C, + + // Global registers + TimersIntStatus = 0xA0, + TimersEOI = 0xA4, + TimersRawIntStatus = 0xA8, + + // LoadCount2 registers (0xB0-0xCC, 4 bytes apart) + Timer0LoadCount2 = 0xB0, + Timer1LoadCount2 = 0xB4, + Timer2LoadCount2 = 0xB8, + Timer3LoadCount2 = 0xBC, + Timer4LoadCount2 = 0xC0, + Timer5LoadCount2 = 0xC4, + Timer6LoadCount2 = 0xC8, + Timer7LoadCount2 = 0xCC, + } + } +} diff --git a/src/Emulator/Peripherals/Peripherals/Timers/MH1903_Watchdog.cs b/src/Emulator/Peripherals/Peripherals/Timers/MH1903_Watchdog.cs new file mode 100644 index 000000000..edd81864c --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/Timers/MH1903_Watchdog.cs @@ -0,0 +1,199 @@ +// +// Copyright (c) 2010-2025 Antmicro +// +// This file is licensed under the MIT License. +// Full license text is available in 'licenses/MIT.txt'. +// + +using Antmicro.Renode.Core; +using Antmicro.Renode.Core.Structure.Registers; +using Antmicro.Renode.Logging; +using Antmicro.Renode.Time; + +namespace Antmicro.Renode.Peripherals.Timers +{ + public class MH1903_Watchdog : BasicDoubleWordPeripheral, IKnownSize + { + public MH1903_Watchdog(IMachine machine, ulong frequency) : base(machine) + { + // Create timer with default reload value (0xFFFFFFFF) + // Timer counts down from reload value to 0 + watchdogTimer = new LimitTimer(machine.ClockSource, frequency, this, "MH1903_WDT", + DefaultReloadValue, workMode: WorkMode.Periodic, enabled: false, eventEnabled: true, autoUpdate: true); + watchdogTimer.LimitReached += TimerLimitReachedCallback; + + DefineRegisters(); + } + + public override void Reset() + { + base.Reset(); + watchdogTimer.Reset(); + watchdogTimer.Limit = DefaultReloadValue; + enabled = false; + responseMode = false; + interruptTriggered = false; + interruptStatus = false; + reloadValue = DefaultReloadValue; + } + + public long Size => 0x1000; + + private void DefineRegisters() + { + // Control Register (0x00) + Registers.Control.Define(this, resetValue: 0x00000000) + .WithFlag(0, name: "Enable", + writeCallback: (_, value) => + { + if(value && !enabled) + { + // Once enabled, cannot be disabled (only by system reset) + enabled = true; + watchdogTimer.Enabled = true; + this.Log(LogLevel.Info, "Watchdog enabled - cannot be disabled until system reset"); + } + else if(!value && enabled) + { + this.Log(LogLevel.Warning, "Attempted to disable watchdog - once enabled it cannot be disabled"); + } + }, + valueProviderCallback: _ => enabled) + .WithFlag(1, name: "ResponseMode", + writeCallback: (_, value) => + { + responseMode = value; + this.Log(LogLevel.Debug, "Response mode set to: {0}", value ? "Interrupt then Reset" : "Direct Reset"); + }, + valueProviderCallback: _ => responseMode) + .WithReservedBits(2, 30); + + // Reserved register at 0x04 + Registers.Reserved04.Define(this, resetValue: 0x00000000) + .WithReservedBits(0, 32); + + // Current Counter Value Register (0x08) - Read Only + Registers.CurrentCounterValue.Define(this, resetValue: 0x0000FFFF) + .WithValueField(0, 32, FieldMode.Read, name: "CurrentCounterValue", + valueProviderCallback: _ => (uint)watchdogTimer.Value); + + // Counter Restart Register (0x0C) - Write Only + Registers.CounterRestart.Define(this, resetValue: 0x00000000) + .WithValueField(0, 8, FieldMode.Write, name: "CounterRestart", + writeCallback: (_, value) => + { + if(value == RestartKey) + { + this.Log(LogLevel.Debug, "Watchdog counter restarted (kicked)"); + watchdogTimer.Value = reloadValue; + + // Clear interrupt if in response mode + if(responseMode && interruptStatus) + { + interruptStatus = false; + interruptTriggered = false; + this.Log(LogLevel.Debug, "Watchdog interrupt cleared by restart"); + } + } + else + { + this.Log(LogLevel.Warning, "Invalid watchdog restart key: 0x{0:X2} (expected 0x76)", value); + } + }) + .WithReservedBits(8, 24); + + // Interrupt Status Register (0x10) - Read Only + Registers.InterruptStatus.Define(this, resetValue: 0x00000000) + .WithFlag(0, FieldMode.Read, name: "InterruptStatus", + valueProviderCallback: _ => interruptStatus) + .WithReservedBits(1, 31); + + // End of Interrupt Register (0x14) - Read Only (clears on read) + Registers.EndOfInterrupt.Define(this, resetValue: 0x00000000) + .WithFlag(0, FieldMode.Read, name: "EndOfInterrupt", + valueProviderCallback: _ => + { + // Reading this register clears the interrupt and resets counter + if(interruptStatus) + { + this.Log(LogLevel.Debug, "Watchdog interrupt cleared via EndOfInterrupt read"); + interruptStatus = false; + interruptTriggered = false; + watchdogTimer.Value = reloadValue; + } + return false; + }) + .WithReservedBits(1, 31); + + // Reserved register at 0x18 + Registers.Reserved18.Define(this, resetValue: 0x00000000) + .WithReservedBits(0, 32); + + // Reload Value Register (0x1C) + Registers.ReloadValue.Define(this, resetValue: DefaultReloadValue) + .WithValueField(0, 32, name: "ReloadValue", + writeCallback: (_, value) => + { + reloadValue = (uint)value; + watchdogTimer.Limit = reloadValue; + this.Log(LogLevel.Debug, "Watchdog reload value set to: 0x{0:X}", value); + }, + valueProviderCallback: _ => reloadValue); + } + + private void TimerLimitReachedCallback() + { + if(!enabled) + { + return; + } + + if(responseMode) + { + // Response mode: First timeout -> interrupt, Second timeout -> reset + if(!interruptTriggered) + { + // First timeout - generate interrupt (note: no physical IRQ line in this SoC) + interruptTriggered = true; + interruptStatus = true; + this.Log(LogLevel.Warning, "Watchdog first timeout - interrupt status set"); + } + else + { + // Second timeout without clearing interrupt - system reset + this.Log(LogLevel.Warning, "Watchdog second timeout without interrupt clear - triggering system reset!"); + machine.RequestReset(); + } + } + else + { + // Direct reset mode - immediate system reset on timeout + this.Log(LogLevel.Warning, "Watchdog timeout - triggering system reset!"); + machine.RequestReset(); + } + } + + private bool enabled; + private bool responseMode; + private bool interruptTriggered; + private bool interruptStatus; + private uint reloadValue; + + private readonly LimitTimer watchdogTimer; + + private const uint DefaultReloadValue = 0xFFFFFFFF; + private const byte RestartKey = 0x76; + + private enum Registers : long + { + Control = 0x00, + Reserved04 = 0x04, + CurrentCounterValue = 0x08, + CounterRestart = 0x0C, + InterruptStatus = 0x10, + EndOfInterrupt = 0x14, + Reserved18 = 0x18, + ReloadValue = 0x1C, + } + } +} diff --git a/src/Emulator/Peripherals/Peripherals/UART/MH1903_UART.cs b/src/Emulator/Peripherals/Peripherals/UART/MH1903_UART.cs new file mode 100644 index 000000000..155c6e79c --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/UART/MH1903_UART.cs @@ -0,0 +1,361 @@ +using System; +using System.Collections.Generic; + +using Antmicro.Migrant; +using Antmicro.Renode.Core; +using Antmicro.Renode.Core.Structure.Registers; +using Antmicro.Renode.Peripherals.Bus; + +namespace Antmicro.Renode.Peripherals.UART +{ + [AllowedTranslations(AllowedTranslation.WordToDoubleWord | AllowedTranslation.ByteToDoubleWord)] + public class MH1903_UART : BasicDoubleWordPeripheral, IKnownSize, IUART + { + public MH1903_UART(IMachine machine, uint pclkFrequency = 8000000) : base(machine) + { + this.pclkFrequency = pclkFrequency; + DefineRegisters(); + } + + public void WriteChar(byte value) + { + receiveFifo.Enqueue(value); + DR.Value = true; + } + + public long Size => 256; + + public Parity ParityBit + { + get + { + if(!PEN.Value) + return Parity.None; + + if(EPS.Value && SP.Value) + return Parity.Forced0; + + if(SP.Value) + return Parity.Forced1; + + return EPS.Value ? Parity.Even : Parity.Odd; + } + } + + public Bits StopBits + { + get + { + var b = STOPBITS.Value ? Bits.OneAndAHalf : Bits.One; + if(DLS.Value == 0 && b == Bits.OneAndAHalf) + b = Bits.Two; + return b; + } + } + + public uint BaudRate + { + get + { + uint divisor = (uint)((DLH.Value << 8) | DLL.Value); + if(divisor == 0) + return 0; + + // Baud rate = PCLK / (16 * Divisor) + return pclkFrequency / (16 * divisor); + } + } + + public GPIO IRQ { get; } = new GPIO(); + + [field: Transient] + public event Action CharReceived; + + public override void Reset() + { + base.Reset(); + receiveFifo.Clear(); + IRQ.Set(false); + } + + private void DefineRegisters() + { + // Depends on DLAB + Registers.DivisorLatchLow_TransmitHoldingRegister_ReceiveBufferRegister.Define(this) + .WithValueField(0, 32, name: "DivisorLatchLow_TransmitHoldingRegister_ReceiveBufferRegister", + valueProviderCallback: ReadDLL_THR_RBR, + writeCallback: WriteDLL_THR_RBR); + + Registers.DivisorLatchHigh_InterruptEnableRegister.Define(this) + .WithValueField(0, 32, name: "DivisorLatchHigh_InterruptEnableRegister", + valueProviderCallback: ReadDLH_IER, + writeCallback: WriteDLH_IER); + + Registers.InterruptIdentificationRegister_FifoControlRegister.Define(this) + .WithValueField(0, 32, name: "InterruptIdentificationRegister_FifoControlRegister", + valueProviderCallback: ReadIIR_FCR, + writeCallback: WriteIIR_FCR); + + Registers.LineControlRegister.Define(this) + .WithReservedBits(8, 24) // Bits 31:8 are reserved + .WithFlag(7, out DLAB, name: "DivisorLatchAccessBit") // Divisor Latch Access Bit + .WithFlag(6, out BC, name: "BreakControl") // Break Control + .WithFlag(5, out SP, name: "StickParity") // Stick Parity + .WithFlag(4, out EPS, name: "EvenParitySelect") // Even Parity Select + .WithFlag(3, out PEN, name: "ParityEnable") // Parity Enable + .WithFlag(2, out STOPBITS, name: "StopBits") // Number of Stop Bits + .WithValueField(0, 2, out DLS, name: "DataLengthSelect"); // Data Length Select + + Registers.ModemControlRegister.Define(this) + .WithReservedBits(7, 25) // Bits 31:7 are reserved + .WithFlag(6, name: "SerialInfraredEnable") // SIR mode enable + .WithFlag(5, name: "AutoFlowControlEnable") // Auto Flow Control Enable + .WithFlag(4, name: "Loopback") // Loopback + .WithFlag(3, name: "Output2") // OUT2 + .WithFlag(2, name: "Output1") // OUT1 + .WithFlag(1, name: "RequestToSend") // Request to Send + .WithFlag(0, name: "DataTerminalReady"); // Data Terminal Ready + + Registers.LineStatusRegister.Define(this) + .WithReservedBits(8, 24) // Bits 31:8 are reserved + .WithFlag(7, name: "ReceiverFifoError") // Receiver FIFO Error + .WithFlag(6, name: "TransmitterEmpty", valueProviderCallback: b => true) // Transmitter Empty + .WithFlag(5, name: "TransmitterHoldingRegisterEmpty") // Transmitter Holding Register Empty + .WithFlag(4, name: "BreakInterrupt") // Break Interrupt + .WithFlag(3, name: "FramingError") // Framing Error + .WithFlag(2, name: "ParityError") // Parity Error + .WithFlag(1, name: "OverrunError") // Overrun Error + .WithFlag(0, out DR, name: "DataReady"); // Data Ready + + Registers.ModemStatusRegister.Define(this) + .WithReservedBits(8, 24) // Bits 31:8 are reserved + .WithFlag(7, name: "DataCarrierDetect") // Data Carrier Detect + .WithFlag(6, name: "RingIndicator") // Ring Indicator + .WithFlag(5, name: "DataSetReady") // Data Set Ready + .WithFlag(4, name: "ClearToSend") // Clear To Send + .WithFlag(3, name: "DeltaDataCarrierDetect", mode: FieldMode.ReadToClear) // Delta Data Carrier Detect + .WithFlag(2, name: "TrailingEdgeRingIndicator", mode: FieldMode.ReadToClear) // Trailing Edge Ring Indicator + .WithFlag(1, name: "DeltaDataSetReady", mode: FieldMode.ReadToClear) // Delta Data Set Ready + .WithFlag(0, name: "DeltaClearToSend", mode: FieldMode.ReadToClear); // Delta Clear To Send + + Registers.ScratchRegister.Define(this) + .WithValueField(0, 32, name: "ScratchRegister"); // Scratch Register + + // This bit is used for FIFO testing to control whether the FIFO can be accessed by the user. + // When enabled, the user can write to the receive FIFO and read from the transmit FIFO. When disabled, + // the user can only access the FIFO through RBR and THR. + // 0 = FIFO access disabled + // 1 = FIFO access enabled + Registers.FifoAccessRegister.Define(this) + .WithReservedBits(1, 31) // Bits 31:1 are reserved + .WithFlag(0, name: "FifoAccessRegister"); // FIFO Access Register + + Registers.TransmitFifoReadRegister.Define(this) + .WithReservedBits(8, 24) // Bits 31:8 are reserved + .WithValueField(0, 8, name: "TransmitFifoReadData", mode: FieldMode.Read); // Transmit FIFO Read Data + + Registers.ReceiveFifoWriteRegister.Define(this) + .WithReservedBits(10, 22) // Bits 31:10 are reserved + .WithFlag(9, name: "ReceiveFifoFramingError", mode: FieldMode.Write) // Receive FIFO Framing Error + .WithFlag(8, name: "ReceiveFifoParityError", mode: FieldMode.Write) // Receive FIFO Parity Error + .WithValueField(0, 8, name: "ReceiveFifoWriteData", mode: FieldMode.Write); // Receive FIFO Write Data + + Registers.UartStatusRegister.Define(this) + .WithReservedBits(5, 27) // Bits 31:5 are reserved + .WithFlag(4, name: "ReceiveFifoFull", mode: FieldMode.Read) // Receive FIFO Full + .WithFlag(3, name: "ReceiveFifoNotEmpty", mode: FieldMode.Read) // Receive FIFO Not Empty + .WithFlag(2, name: "TransmitFifoEmpty", mode: FieldMode.Read) // Transmit FIFO Empty + .WithFlag(1, name: "TransmitFifoNotFull", mode: FieldMode.Read) // Transmit FIFO Not Full + .WithFlag(0, name: "UartBusy", mode: FieldMode.Read); // UART Busy + + Registers.TransmitFifoLevel.Define(this) + .WithReservedBits(4, 28) // Bits 31:4 are reserved + .WithValueField(0, 4, name: "TransmitFifoLevel", mode: FieldMode.Read); // Transmit FIFO Level + + Registers.ReceiveFifoLevel.Define(this) + .WithReservedBits(4, 28) // Bits 31:4 are reserved + .WithValueField(0, 4, name: "ReceiveFifoLevel", mode: FieldMode.Read); // Receive FIFO Level + + Registers.SoftwareResetRegister.Define(this) + .WithReservedBits(3, 29) // Bits 31:3 are reserved + .WithFlag(2, name: "TransmitFifoReset", mode: FieldMode.Write) // XMIT FIFO Reset + .WithFlag(1, name: "ReceiverFifoReset", mode: FieldMode.Write) // RCVR FIFO Reset + .WithFlag(0, name: "UartReset", mode: FieldMode.Write); // UART Reset + + Registers.ShadowRequestToSend.Define(this) + .WithReservedBits(1, 31) // Bits 31:1 are reserved + .WithFlag(0, name: "ShadowRequestToSend"); // Shadow Request to Send + + Registers.ShadowBreakControlRegister.Define(this) + .WithReservedBits(1, 31) // Bits 31:1 are reserved + .WithFlag(0, name: "ShadowBreakControlBit"); // Shadow Break Control Bit + + Registers.ShadowDmaMode.Define(this) + .WithReservedBits(1, 31) // Bits 31:1 are reserved + .WithFlag(0, name: "ShadowDmaMode"); // Shadow DMA Mode + + Registers.ShadowFifoEnable.Define(this) + .WithReservedBits(1, 31) // Bits 31:1 are reserved + .WithFlag(0, name: "ShadowFifoEnable"); // Shadow FIFO Enable + + Registers.ShadowReceiverTrigger.Define(this) + .WithReservedBits(2, 30) // Bits 31:1 are reserved + .WithValueField(0, 2, name: "ShadowReceiverTrigger"); // Shadow RCVR Trigger + + Registers.ShadowTransmitterEmptyTrigger.Define(this) + .WithReservedBits(2, 30) // Bits 31:1 are reserved + .WithFlag(0, name: "ShadowFifoEnable"); // Shadow FIFO Enable + + Registers.HaltTransmit.Define(this) + .WithReservedBits(1, 31) // Bits 31:1 are reserved + .WithFlag(0, name: "HaltTransmit"); // Halt TX + + Registers.DmaSoftwareAcknowledge.Define(this) + .WithReservedBits(1, 31) // Bits 31:1 are reserved + .WithFlag(0, name: "DmaSoftwareAcknowledge", mode: FieldMode.Write); // DMA Software Acknowledge + + THR = new DoubleWordRegister(this) + .WithValueField(0, 8, mode: FieldMode.Write, name: "TransmitHoldingRegister", writeCallback: (offset, value) => + { + CharReceived?.Invoke((byte)value); + }) + .WithReservedBits(8, 24); + + RBR = new DoubleWordRegister(this) + .WithValueField(0, 8, mode: FieldMode.Read, name: "ReceiveBufferRegister", valueProviderCallback: (arg) => + { + uint value = 0; + if(receiveFifo.Count > 0) + value = receiveFifo.Dequeue(); + + DR.Value = receiveFifo.Count > 0; + return value; + }) + .WithReservedBits(8, 24); + + DLH = new DoubleWordRegister(this) + .WithValueField(0, 8, name: "DivisorLatchHigh") + .WithReservedBits(8, 24); + + DLL = new DoubleWordRegister(this) + .WithValueField(0, 8, name: "DivisorLatchLow") + .WithReservedBits(8, 24); + + IER = new DoubleWordRegister(this) + .WithFlag(7, name: "ProgrammableThreInterruptModeEnable") // Programmable THRE Interrupt Mode Enable + .WithReservedBits(4, 3) // Bits 6:4 are reserved + .WithFlag(3, name: "EnableModemStatusInterrupt") // Enable Modem Status Interrupt + .WithFlag(2, name: "EnableReceiverLineStatusInterrupt") // Enable Receiver Line Status Interrupt + .WithFlag(1, name: "EnableTransmitHoldingRegisterEmptyInterrupt") // Enable Transmit Holding Register Empty Interrupt + .WithFlag(0, name: "EnableReceivedDataAvailableInterrupt"); // Enable Received Data Available Interrupt + + FCR = new DoubleWordRegister(this) + .WithReservedBits(8, 24) // Bits 31:8 are reserved + .WithValueField(6, 2, name: "ReceiverTriggerLevel") // Receiver trigger level + .WithValueField(4, 2, name: "TransmitterEmptyTrigger") // TX Empty trigger + .WithFlag(3, name: "DmaMode") // DMA Mode + .WithFlag(2, name: "TransmitFifoReset") // XMIT FIFO Reset + .WithFlag(1, name: "ReceiveFifoReset") // RCVR FIFO Reset + .WithFlag(0, name: "FifoEnable"); // FIFO Enable + + IIR = new DoubleWordRegister(this, resetValue: 0x00000001) + .WithFlag(0, name: "InterruptIdentificationRegister") + .WithReservedBits(1, 31); + } + + private void WriteIIR_FCR(ulong offset, ulong value) + { + if(DLAB.Value) + FCR.Write((uint)offset, (uint)value); + else + IIR.Write((uint)offset, (uint)value); + } + + private ulong ReadIIR_FCR(ulong offset) + { + return DLAB.Value ? FCR.Read() : IIR.Read(); + } + + private void WriteDLH_IER(ulong offset, ulong value) + { + if(DLAB.Value) + DLH.Write((uint)offset, (uint)value); + else + IER.Write((uint)offset, (uint)value); + } + + private ulong ReadDLH_IER(ulong offset) + { + return DLAB.Value ? DLH.Read() : IER.Read(); + } + + private void WriteDLL_THR_RBR(ulong offset, ulong value) + { + if(DLAB.Value) + DLL.Write((uint)offset, (uint)value); + else + THR.Write((uint)offset, (uint)value); + } + + private ulong ReadDLL_THR_RBR(ulong offset) + { + return DLAB.Value ? DLL.Read() : RBR.Read(); + } + + private DoubleWordRegister THR; + private DoubleWordRegister RBR; + private DoubleWordRegister DLH; + private DoubleWordRegister DLL; + private DoubleWordRegister IER; + private DoubleWordRegister IIR; + private DoubleWordRegister FCR; + + private IValueRegisterField DLS; + private IFlagRegisterField DLAB; + private IFlagRegisterField BC; + private IFlagRegisterField SP; + private IFlagRegisterField EPS; + private IFlagRegisterField PEN; + private IFlagRegisterField STOPBITS; + private IFlagRegisterField DR; + + private readonly Queue receiveFifo = new Queue(); + + private readonly uint pclkFrequency; + + private enum Registers : long + { + // Main registers + DivisorLatchLow_TransmitHoldingRegister_ReceiveBufferRegister = 0x00, // Divisor Latch Low / Transmit Holding Register / Receive Buffer Register + DivisorLatchHigh_InterruptEnableRegister = 0x04, // Divisor Latch High / Interrupt Enable Register + InterruptIdentificationRegister_FifoControlRegister = 0x08, // Interrupt Identification Register / FIFO Control Register + LineControlRegister = 0x0C, // Line Control Register + ModemControlRegister = 0x10, // Modem Control Register + LineStatusRegister = 0x14, // Line Status Register + ModemStatusRegister = 0x18, // Modem Status Register + ScratchRegister = 0x1C, // Scratch Register + + // Reserved registers at 0x20, 0x24, 0x28, 0x2C + + // Shadow registers - just defining the first one (rest will be array-indexed) + // SRBR_STHR = 0x30, // Shadow Receive/Transmit Buffer Register + + // Advanced feature registers + FifoAccessRegister = 0x70, // FIFO Access Register + TransmitFifoReadRegister = 0x74, // Transmit FIFO Read + ReceiveFifoWriteRegister = 0x78, // Receive FIFO Write + UartStatusRegister = 0x7C, // UART Status Register + TransmitFifoLevel = 0x80, // Transmit FIFO Level + ReceiveFifoLevel = 0x84, // Receive FIFO Level + SoftwareResetRegister = 0x88, // Software Reset Register + ShadowRequestToSend = 0x8C, // Shadow Request to Send + ShadowBreakControlRegister = 0x90, // Shadow Break Control Register + ShadowDmaMode = 0x94, // Shadow DMA Mode + ShadowFifoEnable = 0x98, // Shadow FIFO Enable + ShadowReceiverTrigger = 0x9C, // Shadow RCVR Trigger + ShadowTransmitterEmptyTrigger = 0xA0, // Shadow TX Empty Trigger + HaltTransmit = 0xA4, // Halt TX FIFO Trigger + DmaSoftwareAcknowledge = 0xA8, // DMA Software Acknowledge Register + } + } +} \ No newline at end of file diff --git a/src/Emulator/Peripherals/Peripherals/Video/ST7735_SPI.cs b/src/Emulator/Peripherals/Peripherals/Video/ST7735_SPI.cs new file mode 100644 index 000000000..459892bb1 --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/Video/ST7735_SPI.cs @@ -0,0 +1,274 @@ +// +// Copyright (c) 2010-2024 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.Backends.Display; +using Antmicro.Renode.Core; +using Antmicro.Renode.Logging; +using Antmicro.Renode.Peripherals.SPI; + +namespace Antmicro.Renode.Peripherals.Video +{ + public class ST7735_SPI : AutoRepaintingVideo, ISPIPeripheral, IGPIOReceiver + { + public ST7735_SPI(IMachine machine, int width = 160, int height = 128) + : base(machine) + { + this.width = width; + this.height = height; + + Reconfigure(width, height, PixelFormat.RGB565); + + framebuffer = new uint[width * height]; + commandBuffer = new List(); + isCommandMode = false; + xStart = 0; + xEnd = (ushort)(width - 1); + yStart = 0; + yEnd = (ushort)(height - 1); + displayOn = false; + chipSelectAsserted = false; + dataCommandPin = false; + } + + public void OnGPIO(int number, bool value) + { + // GPIO number 0 is Chip Select (CS) + // GPIO number 1 is Command/Data pin (CMD) + // When CMD is high: command mode + // When CMD is low: data mode + + if(number == 0) + { + // Chip Select pin + if(value && chipSelectAsserted) + { + // CS transition from low to high (deasserted) - finish transmission + this.Log(LogLevel.Noisy, "Chip Select deasserted"); + FinishCommand(); + } + + if(chipSelectAsserted != !value) + { + this.Log(LogLevel.Info, $"LCD CS changed from {chipSelectAsserted} to {!value}"); + } + chipSelectAsserted = !value; // CS is active low + } + else if(number == 1) + { + // Data/Command pin + dataCommandPin = value; + this.Log(LogLevel.Noisy, "CMD pin: {0} ({1})", value ? "high" : "low", value ? "data" : "command"); + } + } + + public override void Reset() + { + commandBuffer.Clear(); + isCommandMode = false; + xStart = 0; + xEnd = (ushort)(width - 1); + yStart = 0; + yEnd = (ushort)(height - 1); + displayOn = false; + chipSelectAsserted = false; + dataCommandPin = false; + for(int i = 0; i < framebuffer.Length; i++) + { + framebuffer[i] = 0; + } + } + + public void FinishTransmission() + { + this.Log(LogLevel.Noisy, "Finishing SPI transmission"); + // Called when chip select is deasserted - finish any pending command + FinishCommand(); + } + + public byte Transmit(byte data) + { + this.Log(LogLevel.Noisy, "SPI Transmit: 0x{0:X2}, CMD pin={1} ({2})", data, dataCommandPin ? "high" : "low", dataCommandPin ? "data" : "command"); + + if(!dataCommandPin) + { + // CMD pin is low - this is a command byte + if(isCommandMode) + { + // Already in command mode, finish previous command and start new one + FinishCommand(); + } + commandBuffer.Add(data); + isCommandMode = true; + } + else + { + // CMD pin is high - this is data bytes for current command + if(isCommandMode) + { + commandBuffer.Add(data); + } + } + + return 0; // ST7735 typically doesn't return meaningful data during writes + } + + protected override void Repaint() + { + if(!displayOn) + { + return; + } + + // Framebuffer is already in RGB565 format + // The display backend will handle rendering + int pixelIndex = 0; + for(int y = 0; y < height; y++) + { + for(int x = 0; x < width; x++) + { + uint pixel = framebuffer[pixelIndex++]; + buffer[(y * width + x) * 2 + 1] = (byte)(pixel >> 8); // High byte + buffer[(y * width + x) * 2] = (byte)(pixel & 0xFF); // Low byte + } + } + } + + private void FinishCommand() + { + if(commandBuffer.Count == 0) + { + return; + } + + byte cmd = commandBuffer[0]; + this.Log(LogLevel.Noisy, "Processing command: 0x{0:X2}", cmd); + + switch((ST7735Commands)cmd) + { + case ST7735Commands.CASET: + // Column Address Set - expects 4 bytes: XS[15:8], XS[7:0], XE[15:8], XE[7:0] + if(commandBuffer.Count >= 5) + { + xStart = (ushort)((commandBuffer[1] << 8) | commandBuffer[2]); + xEnd = (ushort)((commandBuffer[3] << 8) | commandBuffer[4]); + this.Log(LogLevel.Debug, "Column set: {0} to {1}", xStart, xEnd); + } + break; + + case ST7735Commands.RASET: + // Row Address Set - expects 4 bytes: YS[15:8], YS[7:0], YE[15:8], YE[7:0] + if(commandBuffer.Count >= 5) + { + yStart = (ushort)((commandBuffer[1] << 8) | commandBuffer[2]); + yEnd = (ushort)((commandBuffer[3] << 8) | commandBuffer[4]); + this.Log(LogLevel.Debug, "Row set: {0} to {1}", yStart, yEnd); + } + break; + + case ST7735Commands.RAMWR: + // RAM Write - remaining bytes are pixel data in RGB565 format + if(commandBuffer.Count >= 3) + { + WriteFrameBuffer(); + } + break; + + case ST7735Commands.DISPON: + displayOn = true; + this.Log(LogLevel.Info, "Display ON"); + break; + + case ST7735Commands.DISPOFF: + displayOn = false; + this.Log(LogLevel.Info, "Display OFF"); + break; + + case ST7735Commands.MADCTL: + // Memory Access Control - just log it + if(commandBuffer.Count >= 2) + { + this.Log(LogLevel.Debug, "MADCTL: 0x{0:X2}", commandBuffer[1]); + } + break; + + case ST7735Commands.COLMOD: + // Interface Pixel Format - just log it + if(commandBuffer.Count >= 2) + { + this.Log(LogLevel.Debug, "COLMOD: 0x{0:X2}", commandBuffer[1]); + } + break; + + default: + this.Log(LogLevel.Debug, "ST7735 Command: 0x{0:X2} with {1} bytes", cmd, commandBuffer.Count); + break; + } + + commandBuffer.Clear(); + isCommandMode = false; + } + + private void WriteFrameBuffer() + { + // RAMWR: commandBuffer[0] is the command, commandBuffer[1:] are pixel bytes + // Pixels are in RGB565 format (2 bytes per pixel) + int pixelIndex = 0; + int byteIndex = 1; + + // Calculate the number of pixels to write + int pixelsToWrite = (commandBuffer.Count - 1) / 2; + int pixelsPerLine = (int)(xEnd - xStart) + 1; + + for(int i = 0; i < pixelsToWrite && byteIndex + 1 < commandBuffer.Count; i++) + { + // Read two bytes in big-endian format + ushort rgb565 = (ushort)((commandBuffer[byteIndex] << 8) | commandBuffer[byteIndex + 1]); + byteIndex += 2; + + // Calculate position + int x = (int)xStart + (pixelIndex % pixelsPerLine); + int y = (int)yStart + (pixelIndex / pixelsPerLine); + + // Check bounds + if(x < width && y < height) + { + framebuffer[y * width + x] = rgb565; + } + + pixelIndex++; + } + + this.Log(LogLevel.Noisy, "Framebuffer write: {0} pixels at ({1},{2})", pixelsToWrite, xStart, yStart); + } + private bool isCommandMode; + private ushort xStart; + private ushort xEnd; + private ushort yStart; + private ushort yEnd; + private bool displayOn; + private bool chipSelectAsserted; + private bool dataCommandPin; + + private readonly uint[] framebuffer; + private readonly List commandBuffer; + private readonly int width; + private readonly int height; + + private enum ST7735Commands : byte + { + CASET = 0x2A, // Column Address Set + RASET = 0x2B, // Row Address Set + RAMWR = 0x2C, // RAM Write + MADCTL = 0x36, // Memory Access Control + COLMOD = 0x3A, // Interface Pixel Format + DISPON = 0x29, // Display ON + DISPOFF = 0x28, // Display OFF + } + } +}