1 //
2 // Copyright (c) 2010-2025 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.Linq;
9 using System.Threading;
10 using System.Collections.Generic;
11 using Antmicro.Renode.Core;
12 using Antmicro.Renode.Exceptions;
13 using Antmicro.Renode.Logging;
14 using Antmicro.Renode.Peripherals;
15 using Antmicro.Renode.Peripherals.Bus;
16 using Antmicro.Renode.Peripherals.CPU;
17 using Antmicro.Renode.Peripherals.CoSimulated;
18 using Antmicro.Renode.Peripherals.Timers;
19 using Antmicro.Renode.Plugins.CoSimulationPlugin.Connection.Protocols;
20 using Range = Antmicro.Renode.Core.Range;
21 
22 namespace Antmicro.Renode.Plugins.CoSimulationPlugin.Connection
23 {
24     public static class CoSimulationConnectionExtensions {
ConnectToCoSimulation(this Emulation emulation, string machineName, string name = null, long frequency = DefaultTimeunitFrequency, string simulationFilePathLinux = null, string simulationFilePathWindows = null, string simulationFilePathMacOS = null, string simulationContextLinux = null, string simulationContextWindows = null, string simulationContextMacOS = null, ulong limitBuffer = DefaultLimitBuffer, int timeout = DefaultTimeout, string address = null )25         public static void ConnectToCoSimulation(this Emulation emulation,
26                                                  string machineName,
27                                                  string name = null,
28                                                  long frequency = DefaultTimeunitFrequency,
29                                                  string simulationFilePathLinux = null,
30                                                  string simulationFilePathWindows = null,
31                                                  string simulationFilePathMacOS = null,
32                                                  string simulationContextLinux = null,
33                                                  string simulationContextWindows = null,
34                                                  string simulationContextMacOS = null,
35                                                  ulong limitBuffer = DefaultLimitBuffer,
36                                                  int timeout = DefaultTimeout,
37                                                  string address = null
38                                                  )
39         {
40             EmulationManager.Instance.CurrentEmulation.TryGetMachine(machineName, out var machine);
41             if(machine == null)
42             {
43                 throw new ConstructionException($"Machine {machineName} does not exist.");
44             }
45 
46             var cosimConnection = new CoSimulationConnection(machine, name, frequency,
47                     simulationFilePathLinux, simulationFilePathWindows, simulationFilePathMacOS,
48                     simulationContextLinux, simulationContextWindows, simulationContextMacOS,
49                     limitBuffer, timeout, address);
50         }
51 
52         public const ulong DefaultLimitBuffer = 1000000;
53         public const long DefaultTimeunitFrequency = 1000000000;
54         public const int DefaultTimeout = 3000;
55     }
56 
57     public partial class CoSimulationConnection : IHostMachineElement, IConnectable<ICoSimulationConnectible>, IDisposable {
CoSimulationConnection(IMachine machine, string name, long frequency, string simulationFilePathLinux, string simulationFilePathWindows, string simulationFilePathMacOS, string simulationContextLinux, string simulationContextWindows, string simulationContextMacOS, ulong limitBuffer, int timeout, string address)58         public CoSimulationConnection(IMachine machine,
59                 string name,
60                 long frequency,
61                 string simulationFilePathLinux,
62                 string simulationFilePathWindows,
63                 string simulationFilePathMacOS,
64                 string simulationContextLinux,
65                 string simulationContextWindows,
66                 string simulationContextMacOS,
67                 ulong limitBuffer,
68                 int timeout,
69                 string address)
70         {
71             this.machine = machine;
72             this.gpioEntries = new List<GPIOEntry>();
73 
74             RegisterInHostMachine(name);
75             cosimConnection = SetupConnection(address, timeout, frequency, limitBuffer);
76 
77             cosimIdxToPeripheral = new Dictionary<int, ICoSimulationConnectible>();
78         }
79 
AttachTo(ICoSimulationConnectible peripheral)80         public void AttachTo(ICoSimulationConnectible peripheral)
81         {
82             if(cosimIdxToPeripheral.ContainsKey(peripheral.CosimToRenodeIndex))
83             {
84                 throw new RecoverableException($"Failed to add a peripheral to co-simulated connection: Duplicate CosimToRenode index {peripheral.CosimToRenodeIndex}. Make sure all connected peripherals have unique cosimToRenodeIndex and renodeToCosimIndex parameters in platform definition.");
85             }
86             cosimIdxToPeripheral.Add(peripheral.CosimToRenodeIndex, peripheral);
87             peripheral.OnConnectionAttached(this);
88         }
89 
DetachFrom(ICoSimulationConnectible peripheral)90         public void DetachFrom(ICoSimulationConnectible peripheral)
91         {
92             peripheral.OnConnectionDetached(this);
93             cosimIdxToPeripheral.Remove(peripheral.CosimToRenodeIndex);
94         }
95 
Dispose()96         public void Dispose()
97         {
98             disposeInitiated = true;
99             cosimConnection.Dispose();
100         }
101 
102         public string SimulationContextLinux
103         {
104             get => SimulationContext;
105             set
106             {
107 #if PLATFORM_LINUX
108                 SimulationContext = value;
109 #endif
110             }
111         }
112 
113         public string SimulationContextWindows
114         {
115             get => SimulationContext;
116             set
117             {
118 #if PLATFORM_WINDOWS
119                 SimulationContext = value;
120 #endif
121             }
122         }
123 
124         public string SimulationContextMacOS
125         {
126             get => SimulationContext;
127             set
128             {
129 #if PLATFORM_OSX
130                 SimulationContext = value;
131 #endif
132             }
133         }
134 
135         public string SimulationContext
136         {
137             get => cosimConnection.Context;
138             set
139             {
140                 cosimConnection.Context = value;
141             }
142         }
143 
144         public string SimulationFilePathLinux
145         {
146             get => simulationFilePath;
147             set
148             {
149 #if PLATFORM_LINUX
150                 SimulationFilePath = value;
151 #endif
152             }
153         }
154 
155         public string SimulationFilePathWindows
156         {
157             get => simulationFilePath;
158             set
159             {
160 #if PLATFORM_WINDOWS
161                 SimulationFilePath = value;
162 #endif
163             }
164         }
165 
166         public string SimulationFilePathMacOS
167         {
168             get => simulationFilePath;
169             set
170             {
171 #if PLATFORM_OSX
172                 SimulationFilePath = value;
173 #endif
174             }
175         }
176 
177         public string SimulationFilePath
178         {
179             get => simulationFilePath;
180             set
181             {
182                 if(String.IsNullOrWhiteSpace(value))
183                 {
184                     return;
185                 }
186                 if(!String.IsNullOrWhiteSpace(simulationFilePath))
187                 {
188                     var message = $"Co-simulated peripheral already connected to \"{simulationFilePath}\", cannot change the file name!";
189                     this.Log(LogLevel.Error, message);
190                     throw new RecoverableException(message);
191                 }
192 
193                 if(!String.IsNullOrWhiteSpace(value))
194                 {
195                     cosimConnection.SimulationFilePath = value;
196                     simulationFilePath = value;
197                     Connect();
198                 }
199             }
200         }
201 
HandleMessage()202         public void HandleMessage()
203         {
204             cosimConnection.HandleMessage();
205         }
206 
Connect()207         public void Connect()
208         {
209             if(cosimConnection.IsConnected)
210             {
211                 this.Log(LogLevel.Warning, "The co-simulated peripheral is already connected.");
212                 return;
213             }
214             cosimConnection.Connect();
215         }
216 
Send(ICoSimulationConnectible connectible, ActionType actionId, ulong offset, ulong value)217         public void Send(ICoSimulationConnectible connectible, ActionType actionId, ulong offset, ulong value)
218         {
219             int renodeToCosimIndex = connectible != null ? connectible.RenodeToCosimIndex : ProtocolMessage.NoPeripheralIndex;
220             var message = new ProtocolMessage(actionId, offset, value, renodeToCosimIndex);
221             if(!cosimConnection.TrySendMessage(message))
222             {
223                 AbortAndLogError($"Failed to send message: {message}");
224             }
225         }
226 
Respond(ActionType actionId, ulong offset, ulong value, int peripheralIdx)227         public void Respond(ActionType actionId, ulong offset, ulong value, int peripheralIdx)
228         {
229             if(!cosimConnection.TryRespond(new ProtocolMessage(actionId, offset, value, peripheralIdx)))
230             {
231                 AbortAndLogError("Respond error!");
232             }
233         }
234 
235         public bool IsConnected => cosimConnection.IsConnected;
236 
Reset()237         public void Reset()
238         {
239             // We currently have no way to tell the simulation that a particular peripheral is resetting.
240             // Reset is treated as a global event.
241             if(timer != null)
242             {
243                 timer.Reset();
244             }
245             Send(null, ActionType.ResetPeripheral, 0, 0);
246         }
247 
SendGPIO(int number, bool value)248         public void SendGPIO(int number, bool value)
249         {
250             Write(null, ActionType.Interrupt, number, value ? 1ul : 0ul);
251         }
252 
253         public string ConnectionParameters => (cosimConnection as SocketConnection)?.ConnectionParameters ?? "";
254 
OnReceiveDelegate(ProtocolMessage message)255         public delegate bool OnReceiveDelegate(ProtocolMessage message);
256         public OnReceiveDelegate OnReceive { get; set; }
257 
RegisterOnGPIOReceive(Action<int, bool> callback, Range translationRange)258         public void RegisterOnGPIOReceive(Action<int, bool> callback, Range translationRange)
259         {
260             foreach(GPIOEntry entry in gpioEntries)
261             {
262                 if(entry.range.Intersects(translationRange))
263                 {
264                     throw new ConfigurationException($"Cannot register cosimulation GPIO receive callback on range [{translationRange.StartAddress}, {translationRange.EndAddress}] - there already is a callback registered on intersecting range [{entry.range.StartAddress}, {entry.range.EndAddress}]");
265                 }
266             }
267             gpioEntries.Add(new GPIOEntry(translationRange, callback));
268         }
269 
UnregisterOnGPIOReceive(Range translationRange)270         public void UnregisterOnGPIOReceive(Range translationRange)
271         {
272             gpioEntries.RemoveAll(entry => entry.range.Equals(translationRange));
273         }
274 
Write(ICoSimulationConnectible connectible, ActionType type, long offset, ulong value)275         public void Write(ICoSimulationConnectible connectible, ActionType type, long offset, ulong value)
276         {
277             if(!IsConnected)
278             {
279                 this.Log(LogLevel.Warning, "Cannot write to peripheral. Set SimulationFilePath or connect to a simulator first!");
280                 return;
281             }
282             Send(connectible, type, (ulong)offset, value);
283             ValidateResponse(Receive());
284         }
285 
Read(ICoSimulationConnectible connectible, ActionType type, long offset)286         public ulong Read(ICoSimulationConnectible connectible, ActionType type, long offset)
287         {
288             if(!IsConnected)
289             {
290                 this.Log(LogLevel.Warning, "Cannot read from peripheral. Set SimulationFilePath or connect to a simulator first!");
291                 return 0;
292             }
293             Send(connectible, type, (ulong)offset, 0);
294             var result = Receive();
295             ValidateResponse(result);
296 
297             return result.Data;
298         }
299 
AbortAndLogError(string message)300         private void AbortAndLogError(string message)
301         {
302             // It's safe to call AbortAndLogError from any thread.
303             // Calling it from many threads may cause throwing more than one exception.
304             if(disposeInitiated)
305             {
306                 return;
307             }
308             this.Log(LogLevel.Error, message);
309             cosimConnection.Abort();
310 
311             // Due to deadlock, we need to abort CPU instead of pausing emulation.
312             throw new CpuAbortException();
313         }
314 
SetupConnection(string address, int timeout, long frequency, ulong limitBuffer)315         private ICoSimulationConnection SetupConnection(string address, int timeout, long frequency, ulong limitBuffer)
316         {
317             ICoSimulationConnection cosimConnection = null;
318             if(address != null)
319             {
320                 cosimConnection = new SocketConnection(this, timeout, HandleReceivedMessage, address);
321             }
322             else
323             {
324                 cosimConnection = new LibraryConnection(this, timeout, HandleReceivedMessage);
325             }
326 
327             // Setup time synchronization
328             // Frequency 0 means we never sync the time
329             if(frequency != 0)
330             {
331                 allTicksProcessedARE = new AutoResetEvent(initialState: false);
332                 timer = new LimitTimer(machine.ClockSource, frequency, null, LimitTimerName, limitBuffer, enabled: true, eventEnabled: true, autoUpdate: true);
333                 timer.LimitReached += () =>
334                 {
335                     if(!cosimConnection.TrySendMessage(new ProtocolMessage(ActionType.TickClock, 0, limitBuffer, ProtocolMessage.NoPeripheralIndex)))
336                     {
337                         AbortAndLogError("Failed to send or didn't receive TickClock action response.");
338                     }
339                     this.NoisyLog("Tick: TickClock sent, waiting for the verilated peripheral...");
340                     if(!allTicksProcessedARE.WaitOne(timeout))
341                     {
342                         AbortAndLogError("Timeout reached while waiting for a tick response.");
343                     }
344                     this.NoisyLog("Tick: Co-simulation peripheral finished evaluating the model.");
345                 };
346             }
347 
348             return cosimConnection;
349         }
350 
ValidateResponse(ProtocolMessage message)351         private void ValidateResponse(ProtocolMessage message)
352         {
353             if(message.ActionId == ActionType.Error)
354             {
355                 this.Log(LogLevel.Warning, "Operation error reported by the co-simulation!");
356             }
357         }
358 
Receive()359         private ProtocolMessage Receive()
360         {
361             if(!cosimConnection.TryReceiveMessage(out var message))
362             {
363                 AbortAndLogError("Receive error!");
364             }
365 
366             return message;
367         }
368 
HandleReceivedMessage(ProtocolMessage message)369         private void HandleReceivedMessage(ProtocolMessage message)
370         {
371             ICoSimulationConnectible peripheral = null;
372             if(message.PeripheralIndex != ProtocolMessage.NoPeripheralIndex && !cosimIdxToPeripheral.TryGetValue(message.PeripheralIndex, out peripheral))
373             {
374                 this.Log(LogLevel.Error, "Received co-simulation message {} to a peripheral with index {}, not registered in Renode. Make sure \"cosimToRenodeIndex\" property is provided in platform definition. Message will be ignored.", message.ActionId, message.PeripheralIndex);
375                 return;
376             }
377 
378             if(OnReceive != null)
379             {
380                 foreach(OnReceiveDelegate or in OnReceive.GetInvocationList())
381                 {
382                     if(or(message))
383                     {
384                         return;
385                     }
386                 }
387             }
388 
389             IBusController systemBus = machine.SystemBus;
390             var busPeripheral = peripheral as IBusPeripheral;
391             if(busPeripheral != null)
392             {
393                 systemBus = machine.GetSystemBus(busPeripheral);
394             }
395 
396             switch(message.ActionId)
397             {
398                 case ActionType.InvalidAction:
399                     this.Log(LogLevel.Warning, "Invalid action received");
400                     break;
401                 case ActionType.Interrupt:
402                     HandleGPIO(message);
403                     break;
404                 case ActionType.PushByte:
405                     this.Log(LogLevel.Noisy, "Writing byte: 0x{0:X} to address: 0x{1:X}", message.Data, message.Address);
406                     systemBus.WriteByte(message.Address, (byte)message.Data);
407                     Respond(ActionType.PushConfirmation, 0, 0, message.PeripheralIndex);
408                     break;
409                 case ActionType.PushWord:
410                     this.Log(LogLevel.Noisy, "Writing word: 0x{0:X} to address: 0x{1:X}", message.Data, message.Address);
411                     systemBus.WriteWord(message.Address, (ushort)message.Data);
412                     Respond(ActionType.PushConfirmation, 0, 0, message.PeripheralIndex);
413                     break;
414                 case ActionType.PushDoubleWord:
415                     this.Log(LogLevel.Noisy, "Writing double word: 0x{0:X} to address: 0x{1:X}", message.Data, message.Address);
416                     systemBus.WriteDoubleWord(message.Address, (uint)message.Data);
417                     Respond(ActionType.PushConfirmation, 0, 0, message.PeripheralIndex);
418                     break;
419                 case ActionType.PushQuadWord:
420                     this.Log(LogLevel.Noisy, "Writing quad word: 0x{0:X} to address: 0x{1:X}", message.Data, message.Address);
421                     systemBus.WriteQuadWord(message.Address, message.Data);
422                     Respond(ActionType.PushConfirmation, 0, 0, message.PeripheralIndex);
423                     break;
424                 case ActionType.GetByte:
425                     this.Log(LogLevel.Noisy, "Requested byte from address: 0x{0:X}", message.Address);
426                     Respond(ActionType.WriteToBus, 0, systemBus.ReadByte(message.Address), message.PeripheralIndex);
427                     break;
428                 case ActionType.GetWord:
429                     this.Log(LogLevel.Noisy, "Requested word from address: 0x{0:X}", message.Address);
430                     Respond(ActionType.WriteToBus, 0, systemBus.ReadWord(message.Address), message.PeripheralIndex);
431                     break;
432                 case ActionType.GetDoubleWord:
433                     this.Log(LogLevel.Noisy, "Requested double word from address: 0x{0:X}", message.Address);
434                     Respond(ActionType.WriteToBus, 0, systemBus.ReadDoubleWord(message.Address), message.PeripheralIndex);
435                     break;
436                 case ActionType.GetQuadWord:
437                     this.Log(LogLevel.Noisy, "Requested quad word from address: 0x{0:X}", message.Address);
438                     Respond(ActionType.WriteToBus, 0, systemBus.ReadQuadWord(message.Address), message.PeripheralIndex);
439                     break;
440                 case ActionType.TickClock:
441                     allTicksProcessedARE.Set();
442                     break;
443                 case ActionType.Error:
444                     AbortAndLogError("Fatal error message received from a co-simulation");
445                     break;
446                 default:
447                     this.Log(LogLevel.Warning, "Unhandled message: ActionId = {0}; Address: 0x{1:X}; Data: 0x{2:X}!",
448                         message.ActionId, message.Address, message.Data);
449                     break;
450             }
451         }
452 
HandleGPIO(ProtocolMessage message)453         private void HandleGPIO(ProtocolMessage message)
454         {
455             var gpioNumber = message.Address;
456             bool newValue = message.Data != 0;
457             foreach(GPIOEntry entry in gpioEntries)
458             {
459                 if(entry.range.Contains(gpioNumber))
460                 {
461                     // NOTE: Callback is responsible for translating into local interrupt offsets using
462                     // the range it registered with - local GPIO number is (gpioNumber - range.StartAddress).
463                     entry.callback((int)gpioNumber, newValue);
464                     return;
465                 }
466             }
467         }
468 
RegisterInHostMachine(string name)469         private void RegisterInHostMachine(string name)
470         {
471             if(name == null)
472             {
473                 name = "cosimulation_connection";
474             }
475 
476             var hostMachineElementNames = EmulationManager.Instance.CurrentEmulation.HostMachine.GetNames();
477 
478             // Assure the name is unique inside of the HostMachine by appending a number at the end.
479             if(hostMachineElementNames.Contains(name))
480             {
481                 var uniqueNo = 0;
482                 while(hostMachineElementNames.Contains($"{name}{uniqueNo}"))
483                 {
484                     uniqueNo += 1;
485                 }
486                 name = $"{name}{uniqueNo}";
487             }
488             EmulationManager.Instance.CurrentEmulation.HostMachine.AddHostMachineElement(this, name);
489         }
490 
491         private struct GPIOEntry
492         {
GPIOEntryAntmicro.Renode.Plugins.CoSimulationPlugin.Connection.CoSimulationConnection.GPIOEntry493             public GPIOEntry(Range range, Action<int, bool> callback)
494             {
495                 this.range = range;
496                 this.callback = callback;
497             }
498 
499             public readonly Range range;
500             public readonly Action<int, bool> callback;
501         };
502 
503 
504         private readonly ICoSimulationConnection cosimConnection;
505 
506         private const int DefaultTimeout = 3000;
507 
508         private readonly Dictionary<int, ICoSimulationConnectible> cosimIdxToPeripheral;
509         private string simulationFilePath;
510         private IMachine machine;
511         private volatile bool disposeInitiated;
512         private const string LimitTimerName = "CoSimulationClock";
513         private LimitTimer timer;
514         private AutoResetEvent allTicksProcessedARE;
515         private List<GPIOEntry> gpioEntries;
516     }
517 }
518