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;
9 using System.Collections.Generic;
10 using System.Linq;
11 using Antmicro.Renode.Core;
12 using Antmicro.Renode.Time;
13 using Antmicro.Renode.Utilities;
14 
15 namespace Antmicro.Renode.Peripherals.SENT
16 {
17     public class Transmitter
18     {
Transmitter(IMachine machine, TimeInterval tickPeriod)19         public Transmitter(IMachine machine, TimeInterval tickPeriod)
20         {
21             this.machine = machine;
22             this.tickPeriod = tickPeriod;
23         }
24 
25         public event Func<FastMessage> ProvideFastMessage;
26         public event Func<SlowMessage> ProvideSlowMessage;
27         public event Action<SENTEdge> Edge;
28 
29         public bool Transmitting
30         {
31             get => sendThread != null;
32             set
33             {
34                 if(value == Transmitting)
35                 {
36                     return;
37                 }
38 
39                 sendThread?.Dispose();
40                 sendThread = null;
41 
42                 if(value)
43                 {
44                     var coroutine = TransmitterThread().GetEnumerator();
45                     sendThread = machine.ObtainManagedThread(delegate{}, tickPeriod, "SENT Transmitter", stopCondition: () => !coroutine.MoveNext());
46                     sendThread.Start();
47                 }
48             }
49         }
50 
TransmitterThread()51         private IEnumerable TransmitterThread()
52         {
53             IEnumerator<bool> slowMessageBits = null;
54 
55             while(true)
56             {
57                 // Generating pulses based on the SENT frame
58                 //
59                 // Description: | Sync pulse |       Each nibble      |   Pause pulse (optional)  | Sync pulse ...
60                 // Signal:      | Low | High | Low |        High      | Currently not implemented |
61                 // Ticks:       |  1  |  55  |  5  | 7 + nibble value |                           |
62                 //
63                 // Each `yield return null` is waiting until the time of the next tick
64 
65                 var frame = ConstructFrame(ref slowMessageBits);
66 
67                 // Sync pulse
68                 Edge?.Invoke(SENTEdge.Falling);
69                 yield return null;
70                 Edge?.Invoke(SENTEdge.Rising);
71 
72                 for(var i = 0; i < SynchPulseWidth - 1; i++)
73                 {
74                     yield return null;
75                 }
76 
77                 // Pulse for each nibble to send
78                 foreach(var nibble in frame)
79                 {
80                     Edge?.Invoke(SENTEdge.Falling);
81                     for(var i = 0; i < NibbleLowPulseWidth; i++)
82                     {
83                         yield return null;
84                     }
85                     Edge?.Invoke(SENTEdge.Rising);
86 
87                     var pulseWidth = (nibble & NibbleByteMask) + NibblePulseOffset;
88                     for(var i = 0; i < pulseWidth; i++)
89                     {
90                         yield return null;
91                     }
92                     // Next falling edge will be generated at the start of the inner or outer loop
93                 }
94             }
95         }
96 
ConstructFrame(ref IEnumerator<bool> slowMessageBits)97         private IEnumerable<byte> ConstructFrame(ref IEnumerator<bool> slowMessageBits)
98         {
99             // SENT frame structure (each segment is 4 bits long):
100             // | Status | | Data0 | ... | DataN | CRC |
101             //            <-    Max N = 6     ->
102             // Status:
103             //  Bit 3 - Serial sync. Should be 1 when starting a new slow message otherwise 0
104             //  Bit 2 - Serial data. One bit of the slow message
105             //  Bits 1, 0 - Currently reserved
106             // Data:
107             //  Data nibbles. Taken from the FastMessage class
108             // CRC:
109             //  CRC of data nibbles. Calculated using the x4 + x3 + x2 + 1 polynomial with a starting value of 5
110             //  Note that CRC is added already added by the `FastMessage` class
111 
112             var newSlowMessage = false;
113             if(slowMessageBits == null || !slowMessageBits.MoveNext())
114             {
115                 slowMessageBits = ProvideSlowMessage().Bits.GetEnumerator();
116                 slowMessageBits.MoveNext();
117                 newSlowMessage = true;
118             }
119 
120             byte statusNibble = 0x0;
121             BitHelper.SetBit(ref statusNibble, StatusSerialSyncBit, newSlowMessage);
122             BitHelper.SetBit(ref statusNibble, StatusSerialDataBit, slowMessageBits.Current);
123 
124             return ProvideFastMessage().Nibbles.Prepend(statusNibble);
125         }
126 
127         private IManagedThread sendThread;
128 
129         private readonly IMachine machine;
130         private readonly TimeInterval tickPeriod;
131 
132         private const int SynchPulseWidth = 56;
133         private const int NibbleLowPulseWidth = 5;
134         private const int NibblePulseOffset = 7;
135         private const byte NibbleByteMask = 0xF;
136 
137         private const int StatusSerialSyncBit = 3;
138         private const int StatusSerialDataBit = 2;
139     }
140 }
141