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