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.Reflection; 9 using System.Collections.Generic; 10 using System.Linq; 11 using System.IO; 12 using Antmicro.Renode.Core; 13 using Antmicro.Renode.Peripherals; 14 using Antmicro.Renode.Time; 15 using Antmicro.Renode.Logging; 16 using Antmicro.Migrant; 17 using Antmicro.Migrant.Hooks; 18 19 namespace Antmicro.Renode.Utilities.RESD 20 { 21 public enum RESDStreamStatus 22 { 23 // Provided sample is from requested timestamp 24 OK = 0, 25 // There are no samples for given samples in current block yet 26 BeforeStream = -1, 27 // There are no more samples of given type in the block/file 28 AfterStream = -2, 29 } 30 31 public enum RESDStreamSampleOffset 32 { 33 // Use specified sample offset 34 Specified, 35 // User current virtual-time timestamp as offset 36 CurrentVirtualTime, 37 } 38 39 public static class RESDStreamExtension 40 { 41 public static RESDStream<T> CreateRESDStream<T>(this IPeripheral @this, ReadFilePath path, uint channel, 42 RESDStreamSampleOffset offsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0, 43 Predicate<DataBlock<T>> extraFilter = null) where T: RESDSample, new() 44 { 45 if(offsetType == RESDStreamSampleOffset.CurrentVirtualTime) 46 { 47 var machine = @this.GetMachine(); 48 if(machine.SystemBus.TryGetCurrentCPU(out var cpu)) 49 { 50 cpu.SyncTime(); 51 } 52 sampleOffsetTime += (long)machine.ClockSource.CurrentValue.TotalMicroseconds * -1000L; 53 } 54 55 var stream = new RESDStream<T>(path, channel, sampleOffsetTime, extraFilter); 56 stream.Owner = @this; 57 return stream; 58 } 59 60 public static IManagedThread StartSampleFeedThread<T>(this RESDStream<T> @this, IUnderstandRESD owner, uint frequency, 61 ulong startTime = 0, string domain = null, bool shouldStop = true) where T: RESDSample, new() 62 { 63 Action<T, TimeInterval> beforeCallback = FindCallback<T>(owner, @this.SampleType, RESDStreamStatus.BeforeStream, @this.Channel, domain); 64 Action<T, TimeInterval> currentCallback = FindCallback<T>(owner, @this.SampleType, RESDStreamStatus.OK, @this.Channel, domain); 65 Action<T, TimeInterval> afterCallback = FindCallback<T>(owner, @this.SampleType, RESDStreamStatus.AfterStream, @this.Channel, domain); 66 Action<T, TimeInterval, RESDStreamStatus> sampleCallback = (sample, ts, status) => 67 { 68 switch(status) 69 { 70 case RESDStreamStatus.BeforeStream: 71 beforeCallback(sample, ts); 72 break; 73 case RESDStreamStatus.OK: 74 currentCallback(sample, ts); 75 break; 76 case RESDStreamStatus.AfterStream: 77 afterCallback(sample, ts); 78 break; 79 } 80 }; 81 return @this.StartSampleFeedThread(owner, frequency, sampleCallback, startTime, shouldStop); 82 } 83 84 public static IManagedThread StartSampleFeedThread<T, Out>(this RESDStream<T, Out> @this, IUnderstandRESD owner, uint frequency, 85 ulong startTime = 0, string domain = null, bool shouldStop = true) where T: RESDSample, new() 86 { 87 Action<Out, TimeInterval> beforeCallback = FindCallback<Out>(owner, @this.SampleType, RESDStreamStatus.BeforeStream, @this.Channel, domain); 88 Action<Out, TimeInterval> currentCallback = FindCallback<Out>(owner, @this.SampleType, RESDStreamStatus.OK, @this.Channel, domain); 89 Action<Out, TimeInterval> afterCallback = FindCallback<Out>(owner, @this.SampleType, RESDStreamStatus.AfterStream, @this.Channel, domain); 90 Action<Out, TimeInterval, RESDStreamStatus> sampleCallback = (sample, ts, status) => 91 { 92 switch(status) 93 { 94 case RESDStreamStatus.BeforeStream: 95 beforeCallback(sample, ts); 96 break; 97 case RESDStreamStatus.OK: 98 currentCallback(sample, ts); 99 break; 100 case RESDStreamStatus.AfterStream: 101 afterCallback(sample, ts); 102 break; 103 } 104 }; 105 return @this.StartSampleFeedThread(owner, frequency, sampleCallback, startTime, shouldStop); 106 } 107 108 public static RESDStreamStatus TryGetCurrentSample<T, Out>(this RESDStream<T> @this, IPeripheral peripheral, Func<T, Out> transformer, 109 out Out sample, out TimeInterval timestamp) where T: RESDSample, new() 110 { 111 var result = @this.TryGetCurrentSample(peripheral, out var originalSample, out timestamp); 112 sample = transformer.TransformSample(originalSample); 113 return result; 114 } 115 116 public static Out TransformSample<T, Out>(this Func<T, Out> @this, T sample) 117 where T: RESDSample, new() 118 { 119 if(sample == null) 120 { 121 return default(Out); 122 } 123 return @this(sample); 124 } 125 FindCallback(IUnderstandRESD instance, SampleType sampleType, RESDStreamStatus status, uint channel, string domain)126 private static Action<Out, TimeInterval> FindCallback<Out>(IUnderstandRESD instance, SampleType sampleType, RESDStreamStatus status, uint channel, string domain) 127 { 128 Func<ParameterInfo[], bool> checkCorrectPrototype = parameters => 129 parameters.Length == 2 && 130 parameters[0].ParameterType == typeof(Out) && 131 parameters[1].ParameterType == typeof(TimeInterval); 132 133 var methodInfo = instance.GetType() 134 .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) 135 .Where(info => checkCorrectPrototype(info.GetParameters())) 136 .FirstOrDefault(info => 137 info.GetCustomAttributes(typeof(RESDSampleCallbackAttribute)) 138 .OfType<RESDSampleCallbackAttribute>() 139 .Any(attribute => 140 attribute.SampleType == sampleType && 141 attribute.Status == status && 142 attribute.ChannelId == channel && 143 attribute.Domain == domain)); 144 145 if(methodInfo == null) 146 { 147 return delegate {}; 148 } 149 150 return (sample, ts) => methodInfo.Invoke(instance, new object[] { sample, ts }); 151 } 152 } 153 154 public class RESDStream<T, Out> : RESDStream<T> where T: RESDSample, new() 155 { RESDStream(ReadFilePath path, uint channel, Func<T, Out> transformer, long sampleOffsetTime = 0)156 public RESDStream(ReadFilePath path, uint channel, Func<T, Out> transformer, long sampleOffsetTime = 0) : base(path, channel, sampleOffsetTime) 157 { 158 this.transformer = transformer; 159 } 160 TryGetSample(ulong timestamp, out Out sample, long? overrideSampleOffsetTime = null)161 public RESDStreamStatus TryGetSample(ulong timestamp, out Out sample, long? overrideSampleOffsetTime = null) 162 { 163 var result = TryGetSample(timestamp, out T originalSample, overrideSampleOffsetTime); 164 sample = transformer.TransformSample(originalSample); 165 return result; 166 } 167 TryGetCurrentSample(IPeripheral peripheral, out Out sample, out TimeInterval timestamp)168 public RESDStreamStatus TryGetCurrentSample(IPeripheral peripheral, out Out sample, out TimeInterval timestamp) 169 { 170 var result = TryGetCurrentSample(peripheral, out T originalSample, out timestamp); 171 sample = transformer.TransformSample(originalSample); 172 return result; 173 } 174 StartSampleFeedThread(IPeripheral owner, uint frequency, Action<Out, TimeInterval, RESDStreamStatus> newSampleCallback, ulong startTime = 0, bool shouldStop = true)175 public IManagedThread StartSampleFeedThread(IPeripheral owner, uint frequency, Action<Out, TimeInterval, RESDStreamStatus> newSampleCallback, ulong startTime = 0, bool shouldStop = true) 176 { 177 Action<T, TimeInterval, RESDStreamStatus> transformedCallback = 178 (sample, timestamp, status) => newSampleCallback(transformer.TransformSample(sample), timestamp, status); 179 return StartSampleFeedThread(owner, frequency, transformedCallback, startTime, shouldStop); 180 } 181 182 private readonly Func<T, Out> transformer; 183 } 184 185 public class RESDStream<T> : IDisposable where T : RESDSample, new() 186 { RESDStream(ReadFilePath path, uint channel, long sampleOffsetTime = 0, Predicate<DataBlock<T>> extraFilter = null)187 public RESDStream(ReadFilePath path, uint channel, long sampleOffsetTime = 0, Predicate<DataBlock<T>> extraFilter = null) 188 { 189 var sampleTypeAttribute = typeof(T).GetCustomAttributes(typeof(SampleTypeAttribute), true).FirstOrDefault() as SampleTypeAttribute; 190 if(sampleTypeAttribute == null) 191 { 192 throw new RESDException($"Unsupported RESD sample type: {typeof(T).Name}"); 193 } 194 195 SampleType = sampleTypeAttribute.SampleType; 196 Channel = channel; 197 198 this.sampleOffsetTime = sampleOffsetTime; 199 this.managedThreads = new List<IManagedThread>(); 200 this.parser = new LowLevelRESDParser(path); 201 this.parser.LogCallback += (logLevel, message) => Owner?.Log(logLevel, message); 202 this.blockEnumerator = parser.GetDataBlockEnumerator<T>().GetEnumerator(); 203 this.extraFilter = extraFilter; 204 205 PrereadFirstBlock(); 206 } 207 TryGetCurrentSample(IPeripheral peripheral, out T sample, out TimeInterval timestamp)208 public RESDStreamStatus TryGetCurrentSample(IPeripheral peripheral, out T sample, out TimeInterval timestamp) 209 { 210 var machine = peripheral.GetMachine(); 211 timestamp = machine.ClockSource.CurrentValue; 212 var timestampInNanoseconds = timestamp.TotalNanoseconds; 213 return TryGetSample(timestampInNanoseconds, out sample); 214 } 215 TryGetSample(ulong timestamp, out T sample, long? overrideSampleOffsetTime = null)216 public RESDStreamStatus TryGetSample(ulong timestamp, out T sample, long? overrideSampleOffsetTime = null) 217 { 218 currentTimestampInNanoseconds = timestamp; 219 var currentSampleOffsetTime = overrideSampleOffsetTime ?? sampleOffsetTime; 220 if(currentSampleOffsetTime < 0) 221 { 222 if(timestamp >= (ulong)(-currentSampleOffsetTime)) 223 { 224 timestamp = timestamp - (ulong)(-currentSampleOffsetTime); 225 } 226 else 227 { 228 Owner?.Log(LogLevel.Debug, "RESD: Tried getting sample at timestamp {0}ns, before the start time of the current block" 229 + " after applying the {1}ns offset", timestamp, currentSampleOffsetTime); 230 sample = null; 231 return RESDStreamStatus.BeforeStream; 232 } 233 } 234 else 235 { 236 timestamp = timestamp + (ulong)currentSampleOffsetTime; 237 } 238 239 while(blockEnumerator != null) 240 { 241 if(currentBlock == null) 242 { 243 if(!TryGetNextBlock(out currentBlock)) 244 { 245 break; 246 } 247 MetadataChanged?.Invoke(); 248 } 249 250 switch(currentBlock.TryGetSample(timestamp, out sample)) 251 { 252 case RESDStreamStatus.BeforeStream: 253 Owner?.Log(LogLevel.Debug, "RESD: Tried getting sample at timestamp {0}ns, before the first sample in the block", timestamp); 254 sample = null; 255 return RESDStreamStatus.BeforeStream; 256 case RESDStreamStatus.OK: 257 // Just return sample 258 Owner?.Log(LogLevel.Debug, "RESD: Getting sample at timestamp {0}ns: {1}", timestamp, sample); 259 return RESDStreamStatus.OK; 260 case RESDStreamStatus.AfterStream: 261 // Find next block 262 Owner?.Log(LogLevel.Debug, "RESD: Tried getting sample at timestamp {0}ns after the last sample of the current block", timestamp); 263 currentBlock = null; 264 continue; 265 } 266 267 return RESDStreamStatus.OK; 268 } 269 270 Owner?.Log(LogLevel.Debug, "RESD: That was the last block of the file"); 271 sample = null; 272 return RESDStreamStatus.AfterStream; 273 } 274 275 // If shouldStop is false, the thread will continue running after the end of the stream StartSampleFeedThread(IPeripheral owner, uint frequency, Action<T, TimeInterval, RESDStreamStatus> newSampleCallback, ulong startTime = 0, bool shouldStop = true)276 public IManagedThread StartSampleFeedThread(IPeripheral owner, uint frequency, Action<T, TimeInterval, RESDStreamStatus> newSampleCallback, ulong startTime = 0, bool shouldStop = true) 277 { 278 var machine = owner.GetMachine(); 279 Action feedSample = () => 280 { 281 var status = TryGetCurrentSample(owner, out var sample, out var timestamp); 282 newSampleCallback(sample, timestamp, status); 283 }; 284 285 Func<bool> stopCondition = () => 286 { 287 if(blockEnumerator == null) 288 { 289 if(shouldStop) 290 { 291 feedSample(); // invoke action to update timestamp and status before stopping thread 292 Owner?.Log(LogLevel.Debug, "RESD: End of sample feeding thread detected"); 293 } 294 return shouldStop; 295 } 296 return false; 297 }; 298 299 var thread = machine.ObtainManagedThread(feedSample, frequency, "RESD stream thread", owner, stopCondition); 300 var delayInterval = TimeInterval.FromMicroseconds(startTime / 1000); 301 Owner?.Log(LogLevel.Debug, "RESD: Starting samples feeding thread at frequency {0}Hz delayed by {1}us", frequency, delayInterval); 302 thread.StartDelayed(delayInterval); 303 managedThreads.Add(thread); 304 return thread; 305 } 306 Dispose()307 public void Dispose() 308 { 309 foreach(var thread in managedThreads) 310 { 311 thread.Dispose(); 312 } 313 managedThreads.Clear(); 314 blockEnumerator?.Dispose(); 315 } 316 317 // The `Owner` property is used to log detailed messages 318 // about the process of the RESD file parsing. 319 // If it's not set (set to `null`, by default) no log messages 320 // will be generated. 321 public IEmulationElement Owner { get; set; } 322 323 public T CurrentSample => currentBlock?.CurrentSample; 324 public DataBlock<T> CurrentBlock => currentBlock; 325 public long CurrentBlockNumber => currentBlockNumber; 326 public SampleType SampleType { get; } 327 public uint Channel { get; } 328 public Action MetadataChanged; 329 PrereadFirstBlock()330 private void PrereadFirstBlock() 331 { 332 if(!TryGetNextBlock(out currentBlock)) 333 { 334 throw new RESDException($"Provided RESD file doesn't contain data for {typeof(T)}"); 335 } 336 Owner?.Log(LogLevel.Debug, "RESD: First sample of the file has timestamp {0}ns", currentBlock.StartTime); 337 } 338 TryGetNextBlock(out DataBlock<T> block)339 private bool TryGetNextBlock(out DataBlock<T> block) 340 { 341 if(blockEnumerator == null) 342 { 343 block = null; 344 return false; 345 } 346 347 while(blockEnumerator.TryGetNext(out var nextBlock)) 348 { 349 currentBlockNumber++; 350 if(nextBlock.ChannelId != Channel || !(extraFilter?.Invoke(nextBlock) ?? true)) 351 { 352 Owner?.Log(LogLevel.Debug, "RESD: Skipping block of type {0} and size {1} bytes", nextBlock.BlockType, nextBlock.DataSize); 353 continue; 354 } 355 356 block = nextBlock; 357 return true; 358 } 359 360 block = null; 361 blockEnumerator = null; 362 return false; 363 } 364 365 [PreSerialization] BeforeSerialization()366 private void BeforeSerialization() 367 { 368 // After stream ended, current block is null so serialize timestamp as the last registered time 369 // so after serialization it will also return RESDStreamStatus.AfterStream 370 serializedTimestamp = currentBlock != null ? currentBlock.CurrentTimestamp : currentTimestampInNanoseconds; 371 } 372 373 [PostDeserialization] AfterDeserialization()374 private void AfterDeserialization() 375 { 376 PrereadFirstBlock(); 377 TryGetSample(serializedTimestamp, out _); 378 } 379 380 [Transient] 381 private DataBlock<T> currentBlock; 382 [Transient] 383 private IEnumerator<DataBlock<T>> blockEnumerator; 384 private ulong serializedTimestamp; 385 private ulong currentTimestampInNanoseconds; 386 private long currentBlockNumber; 387 private long sampleOffsetTime; 388 389 private readonly LowLevelRESDParser parser; 390 private readonly IList<IManagedThread> managedThreads; 391 private readonly Predicate<DataBlock<T>> extraFilter; 392 } 393 } 394