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