1 // 2 // Copyright (c) 2010-2024 Antmicro 3 // 4 // This file is licensed under the MIT License. 5 // Full license text is available in 'licenses/MIT.txt'. 6 // 7 using System; 8 using System.Collections.Generic; 9 using Antmicro.Renode.Core; 10 using Antmicro.Renode.Core.Structure; 11 using Antmicro.Renode.Core.Structure.Registers; 12 using Antmicro.Renode.Exceptions; 13 using Antmicro.Renode.Logging; 14 using Antmicro.Renode.Peripherals.Bus; 15 using Antmicro.Renode.Utilities; 16 17 namespace Antmicro.Renode.Peripherals.SPI 18 { 19 public class IMXRT_LPSPI : SimpleContainer<ISPIPeripheral>, IDoubleWordPeripheral, IProvidesRegisterCollection<DoubleWordRegisterCollection>, IKnownSize 20 { IMXRT_LPSPI(IMachine machine, uint fifoSize = 4)21 public IMXRT_LPSPI(IMachine machine, uint fifoSize = 4) : base(machine) 22 { 23 if(!Misc.IsPowerOfTwo(fifoSize)) 24 { 25 throw new ConstructionException($"Invalid fifoSize! It has to be a power of 2 but the fifoSize provided was: {fifoSize}"); 26 } 27 this.fifoSize = fifoSize; 28 29 dataMatcher = new DataMatcher(); 30 IRQ = new GPIO(); 31 receiveFifo = new Queue<uint>(); 32 transmitFifo = new Queue<TCFifoEntry>(); 33 transmitFifoRestore = new Queue<TCFifoEntry>(); 34 RegistersCollection = new DoubleWordRegisterCollection(this); 35 DefineRegisters(); 36 37 Reset(); 38 } 39 Reset()40 public override void Reset() 41 { 42 continuousTransferInProgress = false; 43 selectedDevice = null; 44 sizeLeft = 0; 45 currentCommand = null; 46 dataMatcher.Reset(); 47 receiveFifo.Clear(); 48 transmitFifo.Clear(); 49 transmitFifoRestore.Clear(); 50 51 RegistersCollection.Reset(); 52 UpdateInterrupts(); 53 } 54 ReadDoubleWord(long offset)55 public uint ReadDoubleWord(long offset) 56 { 57 return RegistersCollection.Read(offset); 58 } 59 WriteDoubleWord(long offset, uint value)60 public void WriteDoubleWord(long offset, uint value) 61 { 62 RegistersCollection.Write(offset, value); 63 } 64 65 public long Size => 0x1000; 66 67 public GPIO IRQ { get; } 68 69 public DoubleWordRegisterCollection RegistersCollection { get; } 70 DefineRegisters()71 private void DefineRegisters() 72 { 73 Registers.Status.Define(this) 74 .WithFlag(0, FieldMode.Read, name: "TDF - Transmit Data Flag", valueProviderCallback: _ => true) // TX fifo is always empty, so this bit is always active 75 .WithFlag(1, FieldMode.Read, name: "RDF - Receive Data Flag", 76 valueProviderCallback: _ => receiveFifo.Count > (int)rxWatermark.Value 77 ) 78 .WithReservedBits(2, 6) 79 // b8-9, b11-13: Unused but don't warn if they're cleared. 80 .WithFlag(8, FieldMode.WriteOneToClear, name: "WCF - Word Complete Flag") 81 .WithFlag(9, FieldMode.WriteOneToClear, name: "FCF - Frame Complete Flag") 82 .WithFlag(10, out transferComplete, FieldMode.Read | FieldMode.WriteOneToClear, name: "TCF - Transfer Complete Flag") 83 .WithFlag(11, FieldMode.WriteOneToClear, name: "TEF - Transmit Error Flag") 84 .WithFlag(12, FieldMode.WriteOneToClear, name: "REF - Receive Error Flag") 85 .WithFlag(13, out dataMatch, FieldMode.Read | FieldMode.WriteOneToClear, name: "DMF - Data Match Flag") 86 .WithReservedBits(14, 10) 87 .WithTaggedFlag("MBF - Module Busy Flag", 24) 88 .WithReservedBits(25, 7); 89 90 Registers.TransmitCommand.Define(this) 91 .WithValueField(0, 12, out frameSize, name: "FRAMESZ - Frame Size") 92 .WithReservedBits(12, 4) 93 .WithTag("WIDTH - Transfer Width", 16, 2) // enum 94 .WithFlag(18, out transmitDataMask, name: "TXMSK - Transmit Data Mask", valueProviderCallback: _ => currentCommand?.TxMask ?? false) 95 .WithFlag(19, out receiveDataMask, name: "RXMSK - Receive Data Mask", valueProviderCallback: _ => currentCommand?.RxMask ?? false) 96 .WithFlag(20, out continuingCommand, name: "CONTC - Continuing Command", valueProviderCallback: _ => currentCommand?.ContinuingCommand ?? false) 97 .WithFlag(21, out continuousTransfer, name: "CONT - Continuous Transfer", valueProviderCallback: _ => currentCommand?.Continuous ?? false) 98 .WithTaggedFlag("BYSW - Byte Swap", 22) 99 .WithTaggedFlag("LSBF - LSB First", 23) 100 .WithValueField(24, 2, name: "PCS - Peripheral Chip Select", writeCallback: (_, value) => 101 { 102 if(!TryGetByAddress((int)value, out selectedDevice)) 103 { 104 this.Log(LogLevel.Error, "No device is connected to LPSPI_PCS[{0}]!", value); 105 } 106 }) 107 .WithReservedBits(26, 1) 108 .WithTag("PRESCALE - Prescaler Value", 27, 3) // enum 109 // Unused but don't warn when it's set. 110 .WithFlag(30, name: "CPHA - Clock Phase") 111 .WithTaggedFlag("CPOL - Clock Polarity", 31) 112 .WithWriteCallback((_, cmd) => 113 { 114 this.Log(LogLevel.Debug, "Pushing a command: 0x{0:X8}", cmd); 115 transmitFifo.Enqueue(new TCFifoCmd 116 { 117 ByteSwap = false, 118 TxMask = transmitDataMask.Value, 119 RxMask = receiveDataMask.Value, 120 Continuous = continuousTransfer.Value, 121 ContinuingCommand = continuingCommand.Value 122 }); 123 UpdateTransmitter(); 124 }); 125 126 Registers.TransmitData.Define(this) 127 .WithValueField(0, 32, FieldMode.Write, writeCallback: (_, val) => 128 { 129 this.Log(LogLevel.Debug, "Pushing data: 0x{0:X8}", val); 130 transmitFifo.Enqueue(new TCFifoData 131 { 132 Data = (uint)val 133 }); 134 UpdateTransmitter(); 135 }); 136 137 Registers.FIFOStatus.Define(this) 138 .WithValueField(0, 5, FieldMode.Read, name: "TXCOUNT - Transmit FIFO Count", valueProviderCallback: _ => 0) 139 .WithReservedBits(5, 11) 140 .WithValueField(16, 5, FieldMode.Read, name: "RXCOUNT - Receive FIFO Count", valueProviderCallback: _ => (ulong)receiveFifo.Count) 141 .WithReservedBits(21, 11); 142 143 Registers.FIFOControl.Define(this) 144 .WithTag("TXWATER - Transmit FIFO Watermark", 0, 2) 145 .WithReservedBits(2, 14) 146 .WithValueField(16, 2, out rxWatermark, name: "RXWATER - Receive FIFO Watermark") 147 .WithReservedBits(18, 14); 148 149 Registers.ReceiveData.Define(this) 150 .WithValueField(0, 32, FieldMode.Read, name: "RDR - Receive Data", 151 valueProviderCallback: _ => receiveFifo.Count != 0 ? receiveFifo.Peek() : 0, 152 readCallback: (_, __) => 153 { 154 if(!receiveFifo.TryDequeue(out var result)) 155 { 156 this.Log(LogLevel.Warning, "Receive FIFO underflow"); 157 return; 158 } 159 UpdateInterrupts(); 160 } 161 ); 162 163 Registers.DataMatch0.Define(this) 164 .WithValueField(0, 32, out match0, name: "MATCH0 - Data match 0", changeCallback: (_, val) => 165 { 166 if(rxDataMatchOnly.Value) 167 { 168 // This is undefined behaviour. The manual says you should not do that. 169 this.Log(LogLevel.Warning, "Changing MATCH0 while CFGR0[RDMO] == 1 is prohibited"); 170 } 171 }); 172 173 Registers.DataMatch1.Define(this) 174 .WithValueField(0, 32, out match1, name: "MATCH1 - Data match 1", changeCallback: (_, val) => 175 { 176 if(rxDataMatchOnly.Value) 177 { 178 // This is undefined behaviour. The manual says you should not do that. 179 this.Log(LogLevel.Warning, "Changing MATCH1 while CFGR0[RDMO] == 1 is prohibited"); 180 } 181 }); 182 183 Registers.Control.Define(this) 184 .WithFlag(0, out moduleEnable, name: "MEN - Module Enable", changeCallback: (_, val) => UpdateTransmitter()) 185 .WithFlag(1, name: "RST - Software Reset", changeCallback: (_, val) => 186 { 187 if(val) 188 { 189 this.Log(LogLevel.Debug, "Software Reset requested by writing RST to the Control Register"); 190 // TODO: The Control Register shouldn't be cleared. RST should remain set until cleared by software. 191 Reset(); 192 } 193 }) 194 .WithTaggedFlag("DOZEN - Doze mode enable", 2) 195 .WithTaggedFlag("DBGEN - Debug Enable", 3) 196 .WithReservedBits(4, 4) 197 .WithFlag(8, FieldMode.Write, name: "RTF - Reset Transmit FIFO", writeCallback: (_, val) => 198 { 199 if(val) 200 { 201 transmitFifo.Clear(); 202 transmitFifoRestore.Clear(); 203 } 204 }) 205 .WithFlag(9, FieldMode.Write, name: "RRF - Reset Receive FIFO", writeCallback: (_, val) => { if(val) receiveFifo.Clear(); }) 206 .WithReservedBits(10, 22) 207 .WithWriteCallback((_, __) => UpdateInterrupts()); 208 209 Registers.InterruptEnable.Define(this) 210 // Transmit Data Flag is always set so the Transmit Interrupt is never triggered. 211 .WithFlag(0, name: "TDIE - Transmit Data Interrupt Enable") 212 .WithFlag(1, out receiveDataInterruptEnable, name: "RDIE - Receive Data Interrupt Enable") 213 .WithReservedBits(2, 6) 214 .WithTaggedFlag("WCIE - Word Complete Interrupt Enable", 8) 215 .WithTaggedFlag("FCIE - Frame Complete Interrupt Enable", 9) 216 .WithFlag(10, out transferCompleteInterruptEnable, name: "TCIE - Transfer Complete Interrupt Enable") 217 // b11-12: These interrupts are never used but allow them to be enabled. 218 .WithFlag(11, name: "TEIE - Transmit Error Interrupt Enable") 219 .WithFlag(12, name: "REIE - Receive Error Interrupt Enable") 220 .WithFlag(13, out dataMatchInterruptEnable, name: "DMIE - Data Match Interrupt Enable") 221 .WithReservedBits(14, 18) 222 .WithWriteCallback((_, __) => UpdateInterrupts()); 223 224 Registers.Parameter.Define(this) 225 .WithValueField(0, 8, FieldMode.Read, name: "TXFIFO - Transmit FIFO Size (in words; size = 2^TXFIFO)", 226 valueProviderCallback: _ => (ulong)Misc.Logarithm2((int)fifoSize) 227 ) 228 .WithValueField(8, 8, FieldMode.Read, name: "RXFIFO - Receive FIFO Size (in words; size = 2^RXFIFO)", 229 valueProviderCallback: _ => (ulong)Misc.Logarithm2((int)fifoSize) 230 ) 231 .WithValueField(16, 8, FieldMode.Read, name: "PCSNUM - PCS Number (Indicates the number of PCS pins supported)", 232 valueProviderCallback: _ => (ulong)ChildCollection.Count 233 ) 234 .WithReservedBits(24, 8); 235 236 Registers.Configuration0.Define(this) 237 .WithReservedBits(10, 22) 238 .WithFlag(9, out rxDataMatchOnly, name: "RDMO - Receive Data Match Only", writeCallback: (_, val) => 239 { 240 if(dataMatch.Value && val) 241 { 242 this.Log(LogLevel.Warning, "Writing 1 to CFGR0[RDMO] while SR[DMF] is set to 1"); 243 } 244 }) 245 .WithFlag(8, out circularFifoEnabled, name: "CIRFIFO - Circular FIFO Enable", changeCallback: (_, val) => 246 { 247 transmitFifoRestore.Clear(); 248 if(!val) 249 { 250 return; 251 } 252 253 bool contXferWithCirFifo = false; 254 this.Log(LogLevel.Debug, "Enabling CIRFIFO"); 255 foreach(var word in transmitFifo) 256 { 257 transmitFifoRestore.Enqueue(word); 258 259 if(word is TCFifoCmd cmd) 260 { 261 contXferWithCirFifo |= cmd.Continuous; 262 } 263 } 264 if(contXferWithCirFifo) 265 { 266 this.Log(LogLevel.Warning, "Continuous transfer command enabled/queued when CFGR0[CIRFIFO] is on, this can cause an infinie transfer loop"); 267 } 268 }) 269 .WithReservedBits(4, 4) 270 .WithTaggedFlag("HRDIR - HostRequest Direction", 3) 271 .WithTaggedFlag("HRSEL - Host Request Select", 2) 272 .WithTaggedFlag("HRPOL - Host Request Polarity", 1) 273 .WithTaggedFlag("HREN - Host Request Enable", 0); 274 275 Registers.Configuration1.Define(this) 276 .WithFlag(0, out masterMode, name: "MASTER - Master Mode", changeCallback: (_, val) => 277 { 278 this.Log(LogLevel.Debug, "Switching to the {0} Mode", val ? "Master" : "Slave"); 279 if(moduleEnable.Value) 280 { 281 this.Log(LogLevel.Warning, "The LPSPI module should be disabled when changing the Configuration Mode"); 282 } 283 }) 284 // Unused but don't warn when it's set. 285 .WithFlag(1, name: "SAMPLE - Sample Point") 286 .WithTaggedFlag("AUTOPCS - Automatic PCS", 2) 287 .WithTaggedFlag("NOSTALL - No Stall", 3) 288 .WithReservedBits(4, 4) 289 .WithTag("PCSPOL - Peripheral Chip Select Polarity", 8, 4) 290 .WithReservedBits(12, 4) 291 .WithValueField(16, 3, out matchConfig, name: "MATCFG - Match Configuration") 292 .WithReservedBits(19, 5) 293 .WithTag("PINCFG - Pin Configuration", 24, 2) 294 .WithTaggedFlag("OUTCFG - Output Config", 26) 295 .WithTag("PCSCFG - Peripheral Chip Select Configuration", 27, 2) 296 .WithReservedBits(29, 3); 297 298 // Unused but don't warn when it's read from or written to. 299 Registers.ClockConfiguration.Define(this) 300 .WithValueField(0, 8, name: "SCKDIV - SCK Divider") 301 .WithValueField(8, 8, name: "DBT - Delay Between Transfers") 302 .WithValueField(16, 8, name: "PCSSCK - PCS-to-SCK Delay") 303 .WithValueField(24, 8, name: "SCKPCS - SCK-to-PCS Delay"); 304 } 305 GetFrameSize()306 private uint GetFrameSize() 307 { 308 // frameSize keeps value substracted by 1 309 var sizeLeft = (uint)frameSize.Value + 1; 310 if(sizeLeft % 8 != 0) 311 { 312 sizeLeft += 8 - (sizeLeft % 8); 313 this.Log(LogLevel.Warning, "Only 8-bit-aligned transfers are currently supported, but frame size is set to {0}, adjusting it to: {1}", frameSize.Value, sizeLeft); 314 } 315 316 return sizeLeft; 317 } 318 CanTransfer(out ISPIPeripheral device)319 private bool CanTransfer(out ISPIPeripheral device) 320 { 321 device = null; 322 if(!moduleEnable.Value) 323 { 324 this.Log(LogLevel.Warning, "Trying to send data, but the LPSPI module is disabled"); 325 return false; 326 } 327 328 if(!masterMode.Value) 329 { 330 this.Log(LogLevel.Error, "The Slave Mode is not supported"); 331 return false; 332 } 333 334 return TryGetDevice(out device); 335 } 336 DoDummyTransfer()337 private void DoDummyTransfer() 338 { 339 if(!CanTransfer(out var device)) 340 { 341 return; 342 } 343 344 // send dummy bytes 345 while(!TrySendDataInner(0, device)) { } 346 } 347 TrySendData(uint value)348 private bool TrySendData(uint value) 349 { 350 if(!CanTransfer(out var device)) 351 { 352 return false; 353 } 354 355 return TrySendDataInner(value, device); 356 } 357 TrySendDataInner(uint value, ISPIPeripheral device)358 private bool TrySendDataInner(uint value, ISPIPeripheral device) 359 { 360 if(sizeLeft == 0) 361 { 362 // let's assume this is a new transfer 363 this.Log(LogLevel.Debug, "Starting a new SPI xfer, frame size: {0} bytes", GetFrameSize() / 8); 364 sizeLeft = GetFrameSize(); 365 dataMatcher.Configure((MatchMode)matchConfig.Value); 366 dataMatcher.Match0 = (uint)match0.Value; 367 dataMatcher.Match1 = (uint)match1.Value; 368 } 369 370 // we can read up to 4 bytes at a time 371 var byteIdx = 0; 372 uint receivedWord = 0; 373 374 this.Log(LogLevel.Debug, "Sending 0x{0:X} to the device", value); 375 while(sizeLeft != 0 && byteIdx < 4) 376 { 377 var resp = device.Transmit((byte)value); 378 379 receivedWord |= (uint)resp << (byteIdx * 8); 380 381 value >>= 8; 382 sizeLeft -= 8; 383 byteIdx++; 384 } 385 this.Log(LogLevel.Debug, "Received response 0x{0:X} from the device", receivedWord); 386 387 if(!receiveDataMask.Value && dataMatcher.MatchAndPush(receiveFifo, receivedWord, byteIdx * 8, rxDataMatchOnly.Value)) 388 { 389 dataMatch.Value = true; 390 } 391 392 if(!currentCommand.ContinuingCommand) 393 { 394 transferComplete.Value = true; 395 UpdateInterrupts(); 396 } 397 398 if(sizeLeft == 0 && !currentCommand.ContinuingCommand) 399 { 400 if(!continuousTransfer.Value) 401 { 402 device.FinishTransmission(); 403 } 404 else 405 { 406 continuousTransferInProgress = true; 407 } 408 } 409 410 return true; 411 } 412 TryGetDevice(out ISPIPeripheral device)413 private bool TryGetDevice(out ISPIPeripheral device) 414 { 415 device = selectedDevice; 416 if(device == null) 417 { 418 this.Log(LogLevel.Warning, "No device connected!"); 419 return false; 420 } 421 return true; 422 } 423 UpdateInterrupts()424 private void UpdateInterrupts() 425 { 426 var flag = false; 427 428 flag |= receiveDataInterruptEnable.Value && receiveFifo.Count > (int)rxWatermark.Value; 429 flag |= transferComplete.Value && transferCompleteInterruptEnable.Value; 430 flag |= dataMatch.Value | dataMatchInterruptEnable.Value; 431 432 this.Log(LogLevel.Debug, "Setting IRQ flag to {0}", flag); 433 IRQ.Set(flag); 434 } 435 TryTransmitFifoDequeue(out uint? poppedData)436 private void TryTransmitFifoDequeue(out uint? poppedData) 437 { 438 bool restored = false; 439 poppedData = null; 440 441 if(transmitFifo.TryDequeue(out var tcEntry)) 442 { 443 if(tcEntry is TCFifoCmd cmd) 444 { 445 currentCommand = cmd; 446 } 447 else if(tcEntry is TCFifoData data) 448 { 449 poppedData = data.Data; 450 } 451 return; 452 } 453 454 // Restoration procedure for circular FIFO 455 foreach(var word in transmitFifoRestore) 456 { 457 transmitFifo.Enqueue(word); 458 restored = true; 459 } 460 461 if(restored) 462 { 463 this.Log(LogLevel.Debug, "Restored Tx FIFO"); 464 TryTransmitFifoDequeue(out poppedData); 465 } 466 } 467 UpdateTransmitter(TCFifoEntry transmitEntry = null)468 private void UpdateTransmitter(TCFifoEntry transmitEntry = null) 469 { 470 if(!moduleEnable.Value) 471 { 472 return; 473 } 474 475 if(transmitEntry != null) 476 { 477 if(transmitFifo.Count > fifoSize) 478 { 479 this.Log(LogLevel.Warning, "Trying to enqueue entry to the transmit FIFO which is full, data ignored"); 480 return; 481 } 482 transmitFifo.Enqueue(transmitEntry); 483 484 if(circularFifoEnabled.Value) 485 { 486 if(transmitFifoRestore.Count >= fifoSize) 487 { 488 // Avoid storing more data in the restore FIFO rather than in the regular FIFO 489 transmitFifoRestore.Dequeue(); 490 } 491 transmitFifoRestore.Enqueue(transmitEntry); 492 } 493 } 494 495 do 496 { 497 TryTransmitFifoDequeue(out uint? poppedData); 498 499 // Note: Section 71.3.1.1.1 describes one of the conditions as "data is written to transmit FIFO". This can be understood as either anything being present in the FIFO 500 // or as transmission data being present there. 501 // Note: The manual does not mention that a command has to be loaded first, but it makes no sense to process data otherwise. It's likely an undefined behaviour. 502 bool initSpiXfer = currentCommand != null && poppedData.HasValue && moduleEnable.Value; 503 if(!initSpiXfer) 504 { 505 this.Log(LogLevel.Debug, "SPI transfer not initialized"); 506 // Nothing more to do 507 break; 508 } 509 510 if(!currentCommand.ContinuingCommand && continuousTransferInProgress && (sizeLeft == 0)) 511 { 512 continuousTransferInProgress = false; 513 // Finish last transmission in case with are not in continuing mode. 514 if(TryGetDevice(out var device)) 515 { 516 device.FinishTransmission(); 517 } 518 } 519 520 // When Transmit Data Mask is set, transmit data is masked (no data is loaded from transmit FIFO and output pin is tristated). 521 // In master mode, the Transmit Data Mask bit will initiate a new transfer which cannot be aborted by another command word; 522 if(currentCommand.TxMask) 523 { 524 DoDummyTransfer(); 525 // according to the documentation: 526 // "the Transmit Data Mask bit will be cleared by hardware at the end of the transfer" 527 // It is unclear what should be done in case of continuous transfers, but it's assumed that the TxMask should stay as it was set. 528 if(!currentCommand.Continuous) 529 { 530 currentCommand.TxMask = false; 531 } 532 } 533 else if(poppedData.HasValue) 534 { 535 if(!TrySendData(poppedData.Value)) 536 { 537 this.Log(LogLevel.Error, "Couldn't send data"); 538 } 539 } 540 else 541 { 542 // We need to wait for more data 543 this.Log(LogLevel.Debug, "No more data on FIFO"); 544 break; 545 } 546 } while(currentCommand.Continuous || sizeLeft != 0); 547 } 548 549 private IFlagRegisterField masterMode; 550 private IFlagRegisterField moduleEnable; 551 private IValueRegisterField frameSize; 552 private IFlagRegisterField receiveDataMask; 553 private IFlagRegisterField transmitDataMask; 554 private IFlagRegisterField transferComplete; 555 private IValueRegisterField rxWatermark; 556 private IFlagRegisterField continuingCommand; 557 private IFlagRegisterField continuousTransfer; 558 private IFlagRegisterField circularFifoEnabled; 559 560 private IFlagRegisterField receiveDataInterruptEnable; 561 private IFlagRegisterField transferCompleteInterruptEnable; 562 private IFlagRegisterField dataMatchInterruptEnable; 563 564 private bool continuousTransferInProgress; 565 private readonly uint fifoSize; 566 private readonly Queue<uint> receiveFifo; 567 private ISPIPeripheral selectedDevice; 568 private readonly Queue<TCFifoEntry> transmitFifo; 569 private readonly Queue<TCFifoEntry> transmitFifoRestore; 570 private TCFifoCmd currentCommand; 571 private uint sizeLeft; 572 573 private IValueRegisterField match0; 574 private IValueRegisterField match1; 575 private IValueRegisterField matchConfig; 576 577 private IFlagRegisterField rxDataMatchOnly; 578 private IFlagRegisterField dataMatch; 579 580 private readonly DataMatcher dataMatcher; 581 582 private abstract class TCFifoEntry { } 583 584 private class TCFifoCmd : TCFifoEntry 585 { 586 public byte XferWidth { get; set; } 587 public bool TxMask { get; set; } 588 public bool RxMask { get; set; } 589 public ISPIPeripheral Pcs { get; set; } 590 public bool Continuous { get; set; } 591 public bool ContinuingCommand { get; set; } 592 public bool ByteSwap { get; set; } 593 } 594 595 private class TCFifoData : TCFifoEntry 596 { 597 public uint Data { get; set; } 598 } 599 600 private class DataMatcher 601 { Reset()602 public void Reset() 603 { 604 lastWord = null; 605 Configure(MatchMode.Disabled); 606 Match0 = default; 607 Match1 = default; 608 Array.Clear(matchBuffer, 0, matchBuffer.Length); 609 Array.Clear(compareBuffer, 0, compareBuffer.Length); 610 } 611 Configure(MatchMode mode)612 public void Configure(MatchMode mode) 613 { 614 lastWord = null; 615 matchMode = mode; 616 Active = mode != MatchMode.Disabled; 617 matchFirstOnly = ((int)mode & 0b001) == 0; 618 switch(mode) 619 { 620 case MatchMode.SeqFirst: 621 case MatchMode.SeqAny: 622 match1Mode = Match1Mode.Seq; 623 break; 624 case MatchMode.FirstCompareMasked: 625 case MatchMode.AnyCompareMasked: 626 match1Mode = Match1Mode.Mask; 627 break; 628 default: 629 match1Mode = Match1Mode.None; 630 break; 631 } 632 } 633 MatchAndPush(Queue<uint> fifo, uint value, int width, bool dataMatchOnly = false)634 public bool MatchAndPush(Queue<uint> fifo, uint value, int width, bool dataMatchOnly = false) 635 { 636 if(!Active) 637 { 638 if(!dataMatchOnly) 639 { 640 // Pass-through 641 fifo.Enqueue(value); 642 } 643 return false; 644 } 645 646 matchBuffer[0] = value; 647 int wordCount = 1; 648 649 // In sequential mode we need to read two words 650 if(match1Mode == Match1Mode.Seq) 651 { 652 if(!lastWord.HasValue) 653 { 654 lastWord = value; 655 if(!dataMatchOnly) 656 { 657 fifo.Enqueue(value); 658 } 659 return false; 660 } 661 matchBuffer[1] = matchBuffer[0]; 662 matchBuffer[0] = lastWord.Value; 663 ++wordCount; 664 665 // In sequential mode we need to wait for at least two words 666 if(matchFirstOnly) 667 { 668 Active = false; 669 } 670 } 671 else 672 { 673 if(matchFirstOnly) 674 { 675 Active = false; 676 } 677 } 678 679 // Prepare match buffers 680 uint lastWordCmpMask = WordSizeMask(width); 681 if(match1Mode == Match1Mode.Mask) 682 { 683 lastWordCmpMask &= Match1; 684 } 685 686 compareBuffer[1] = Match1; 687 // The two "compare" modes just OR MATCH0 and MATCH0 to create the comparison word 688 compareBuffer[0] = Match0 | (match1Mode == Match1Mode.None ? Match1 : 0); 689 // Zero-out irrelevant bits (masked or not part of the word given its width) 690 compareBuffer[wordCount - 1] &= lastWordCmpMask; 691 matchBuffer[wordCount - 1] &= lastWordCmpMask; 692 693 // Perform comparisons 694 bool match = true; 695 696 if((compareBuffer[0] != matchBuffer[0]) || (wordCount == 2 && compareBuffer[1] != matchBuffer[1])) 697 { 698 match = false; 699 } 700 701 // Manage the FIFO 702 if(!dataMatchOnly || match) 703 { 704 if(match1Mode == Match1Mode.Seq && dataMatchOnly) 705 { 706 fifo.Enqueue(lastWord.Value); 707 } 708 fifo.Enqueue(value); 709 } 710 711 lastWord = value; 712 return match; 713 } 714 715 public bool Active { get; set; } 716 public MatchMode Mode => matchMode; 717 public uint Match0 { get; set; } 718 public uint Match1 { get; set; } 719 WordSizeMask(int wordSizeInBits)720 private static uint WordSizeMask(int wordSizeInBits) 721 { 722 if(wordSizeInBits >= 32) 723 { 724 return uint.MaxValue; 725 } 726 return (1U << wordSizeInBits) - 1; 727 } 728 729 private bool matchFirstOnly; 730 private MatchMode matchMode; 731 private Match1Mode match1Mode; 732 733 private uint? lastWord; 734 735 // Two-word buffer of inputs to compare. The second word is relevant only in sequential mode. 736 private readonly uint[] matchBuffer = new uint[2]; 737 // Two-word buffer of reference words to compare inputs with. The second word is relevant only in sequential mode. 738 // Those words are based on the contents of `Match0` and `Match1`. 739 private readonly uint[] compareBuffer = new uint[2]; 740 741 private enum Match1Mode 742 { 743 None, 744 Seq, 745 Mask, 746 } 747 } 748 749 private enum MatchMode : long 750 { 751 Disabled = 0b000, 752 FirstCompare = 0b010, 753 AnyCompare = 0b011, 754 SeqFirst = 0b100, 755 SeqAny = 0b101, 756 FirstCompareMasked = 0b110, 757 AnyCompareMasked = 0b111, 758 } 759 760 private enum Registers : long 761 { 762 VersionID = 0x0, 763 Parameter = 0x4, 764 Control = 0x10, 765 Status = 0x14, 766 InterruptEnable = 0x18, 767 DMAEnable = 0x1C, 768 Configuration0 = 0x20, 769 Configuration1 = 0x24, 770 DataMatch0 = 0x30, 771 DataMatch1 = 0x34, 772 ClockConfiguration = 0x40, 773 FIFOControl = 0x58, 774 FIFOStatus = 0x5C, 775 TransmitCommand = 0x60, 776 TransmitData = 0x64, 777 ReceiveStatus = 0x70, 778 ReceiveData = 0x74 779 } 780 } 781 } 782