1 //
2 // Copyright (c) 2010-2024 Antmicro
3 // Copyright (c) 2011-2015 Realtime Embedded
4 //
5 // This file is licensed under the MIT License.
6 // Full license text is available in 'licenses/MIT.txt'.
7 //
8 using System;
9 using Antmicro.Renode.Core;
10 using Antmicro.Renode.Time;
11 using Antmicro.Renode.Exceptions;
12 using Antmicro.Renode.Utilities;
13 using Antmicro.Renode.Peripherals.CPU;
14 
15 namespace Antmicro.Renode.Peripherals.Timers
16 {
17     public class ComparingTimer : ITimer, IPeripheral
18     {
ComparingTimer(IClockSource clockSource, long frequency, IPeripheral owner, string localName, ulong limit = ulong.MaxValue, Direction direction = Direction.Ascending, bool enabled = false, WorkMode workMode = WorkMode.OneShot, bool eventEnabled = false, ulong compare = ulong.MaxValue, uint divider = 1, uint step = 1)19         public ComparingTimer(IClockSource clockSource, long frequency, IPeripheral owner, string localName, ulong limit = ulong.MaxValue, Direction direction = Direction.Ascending, bool enabled = false, WorkMode workMode = WorkMode.OneShot, bool eventEnabled = false, ulong compare = ulong.MaxValue, uint divider = 1, uint step = 1)
20         {
21             if(compare > limit)
22             {
23                 throw new ConstructionException(string.Format(CompareHigherThanLimitMessage, compare, limit));
24             }
25             if(divider == 0)
26             {
27                 throw new ArgumentException("Divider cannot be zero.");
28             }
29             if(frequency == 0)
30             {
31                 throw new ArgumentException("Frequency cannot be zero.");
32             }
33             if(limit == 0)
34             {
35                 throw new ArgumentException("Limit cannot be zero.");
36             }
37 
38             this.clockSource = clockSource;
39 
40             initialDirection = direction;
41             initialFrequency = frequency;
42             initialLimit = limit;
43             initialCompare = compare;
44             initialEnabled = enabled;
45             initialWorkMode = workMode;
46             initialEventEnabled = eventEnabled;
47             initialDivider = divider;
48             initialStep = step;
49             this.owner = this is IPeripheral && owner == null ? this : owner;
50             this.localName = localName;
51             InternalReset();
52         }
53 
ComparingTimer(IClockSource clockSource, long frequency, ulong limit = ulong.MaxValue, Direction direction = Direction.Ascending, bool enabled = false, WorkMode workMode = WorkMode.OneShot, bool eventEnabled = false, ulong compare = ulong.MaxValue, uint divider = 1, uint step = 1)54         protected ComparingTimer(IClockSource clockSource, long frequency, ulong limit = ulong.MaxValue, Direction direction = Direction.Ascending, bool enabled = false, WorkMode workMode = WorkMode.OneShot, bool eventEnabled = false, ulong compare = ulong.MaxValue, uint divider = 1, uint step = 1)
55             : this(clockSource, frequency, null, null, limit, direction, enabled, workMode, eventEnabled, compare, divider, step)
56         {
57         }
58 
59         public bool Enabled
60         {
61             get
62             {
63                 return clockSource.GetClockEntry(CompareReachedInternal).Enabled;
64             }
65             set
66             {
67                 clockSource.ExchangeClockEntryWith(CompareReachedInternal, oldEntry => oldEntry.With(enabled: value));
68 
69                 RequestReturnOnCurrentCpu();
70             }
71         }
72 
73         public bool EventEnabled { get; set; }
74 
75         public long Frequency
76         {
77             get
78             {
79                 return frequency;
80             }
81             set
82             {
83                 if(value == 0)
84                 {
85                     throw new ArgumentException("Frequency cannot be zero.");
86                 }
87                 frequency = value;
88                 RecalculateFrequency();
89 
90                 RequestReturnOnCurrentCpu();
91             }
92         }
93 
94         public ulong Value
95         {
96             get
97             {
98                 var currentValue = 0UL;
99                 clockSource.GetClockEntryInLockContext(CompareReachedInternal, entry =>
100                 {
101                     currentValue = valueAccumulatedSoFar + entry.Value;
102                 });
103                 return currentValue;
104             }
105             set
106             {
107                 if(value > initialLimit)
108                 {
109                     throw new ArgumentException("Value cannot be larger than limit");
110                 }
111 
112                 clockSource.ExchangeClockEntryWith(CompareReachedInternal, entry =>
113                 {
114                     valueAccumulatedSoFar = value;
115                     return entry.With(period: CalculatePeriod(), value: 0);
116                 });
117 
118                 RequestReturnOnCurrentCpu();
119             }
120         }
121 
122         public ulong Compare
123         {
124             get
125             {
126                 return compareValue;
127             }
128             set
129             {
130                 if(value > initialLimit)
131                 {
132                     throw new InvalidOperationException(CompareHigherThanLimitMessage.FormatWith(value, initialLimit));
133                 }
134                 clockSource.ExchangeClockEntryWith(CompareReachedInternal, entry =>
135                 {
136                     compareValue = value;
137                     valueAccumulatedSoFar += entry.Value;
138                     return entry.With(period: CalculatePeriod(), value: 0);
139                 });
140 
141                 RequestReturnOnCurrentCpu();
142             }
143         }
144 
145         public uint Divider
146         {
147             get
148             {
149                 return divider;
150             }
151             set
152             {
153                 if(value == divider)
154                 {
155                     return;
156                 }
157                 if(value == 0)
158                 {
159                     throw new ArgumentException("Divider cannot be zero.");
160                 }
161                 divider = value;
162                 RecalculateFrequency();
163 
164                 RequestReturnOnCurrentCpu();
165             }
166         }
167 
168         public uint Step
169         {
170             get
171             {
172                 return step;
173             }
174             set
175             {
176                 if(value == step)
177                 {
178                     return;
179                 }
180                 step = value;
181                 clockSource.ExchangeClockEntryWith(CompareReachedInternal, oldEntry => oldEntry.With(step: step));
182 
183                 RequestReturnOnCurrentCpu();
184             }
185         }
186 
Reset()187         public virtual void Reset()
188         {
189             InternalReset();
190         }
191 
192         public event Action CompareReached;
193 
OnCompareReached()194         protected virtual void OnCompareReached()
195         {
196             if(!EventEnabled)
197             {
198                 return;
199             }
200 
201             CompareReached?.Invoke();
202         }
203 
RecalculateFrequency()204         private void RecalculateFrequency()
205         {
206             var effectiveFrequency = Frequency / Divider;
207             clockSource.ExchangeClockEntryWith(CompareReachedInternal, oldEntry => oldEntry.With(frequency: effectiveFrequency));
208         }
209 
CalculatePeriod()210         private ulong CalculatePeriod()
211         {
212             return ((compareValue > valueAccumulatedSoFar) ? compareValue : initialLimit) - valueAccumulatedSoFar;
213         }
214 
CompareReachedInternal()215         private void CompareReachedInternal()
216         {
217             // since we use OneShot, timer's value is already 0 and it is disabled now
218             // first we add old limit to accumulated value:
219             valueAccumulatedSoFar += clockSource.GetClockEntry(CompareReachedInternal).Period;
220             if(valueAccumulatedSoFar >= initialLimit && compareValue != initialLimit)
221             {
222                 // compare value wasn't actually reached, the timer reached its limit
223                 // we don't trigger an event in such case
224                 valueAccumulatedSoFar = 0;
225                 clockSource.ExchangeClockEntryWith(CompareReachedInternal, entry => entry.With(period: compareValue, enabled: true));
226                 return;
227             }
228             // real compare event - then we reenable the timer with the next event marked by limit
229             // which will probably be soon corrected by software
230             clockSource.ExchangeClockEntryWith(CompareReachedInternal, entry => entry.With(period: initialLimit - valueAccumulatedSoFar, enabled: true));
231             if(valueAccumulatedSoFar >= initialLimit)
232             {
233                 valueAccumulatedSoFar = 0;
234             }
235             OnCompareReached();
236         }
237 
InternalReset()238         private void InternalReset()
239         {
240             divider = initialDivider;
241             frequency = initialFrequency;
242             step = initialStep;
243 
244             var clockEntry = new ClockEntry(initialCompare, frequency / divider, CompareReachedInternal, owner, localName, initialEnabled, initialDirection, initialWorkMode, step)
245             { Value = initialDirection == Direction.Ascending ? 0 : initialLimit };
246             clockSource.ExchangeClockEntryWith(CompareReachedInternal, entry => clockEntry, () => clockEntry);
247             valueAccumulatedSoFar = 0;
248             compareValue = initialCompare;
249             EventEnabled = initialEventEnabled;
250         }
251 
RequestReturnOnCurrentCpu()252         private void RequestReturnOnCurrentCpu()
253         {
254             if(EmulationManager.Instance.CurrentEmulation.TryGetExecutionContext(out var machine, out var cpu))
255             {
256                 (cpu as IControllableCPU)?.RequestReturn();
257             }
258         }
259 
260         private ulong valueAccumulatedSoFar;
261         private ulong compareValue;
262         private uint divider;
263         private long frequency;
264         private uint step;
265 
266         private readonly uint initialStep;
267         private readonly uint initialDivider;
268         private readonly Direction initialDirection;
269         private readonly long initialFrequency;
270         private readonly IClockSource clockSource;
271         private readonly ulong initialLimit;
272         private readonly WorkMode initialWorkMode;
273         private readonly ulong initialCompare;
274         private readonly bool initialEnabled;
275         private readonly bool initialEventEnabled;
276         private readonly IPeripheral owner;
277         private readonly string localName;
278 
279         private const string CompareHigherThanLimitMessage = "Compare value ({0}) cannot be higher than limit ({1}).";
280     }
281 }
282 
283