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.Threading;
9 using System.Collections.Generic;
10 using System.Collections.ObjectModel;
11 using Antmicro.Renode.Core;
12 using Antmicro.Renode.Exceptions;
13 using Antmicro.Renode.Logging;
14 using Antmicro.Renode.Peripherals.Bus;
15 using Antmicro.Renode.Peripherals.Timers;
16 using Antmicro.Renode.Plugins.CoSimulationPlugin.Connection;
17 using Antmicro.Renode.Plugins.CoSimulationPlugin.Connection.Protocols;
18 using Range = Antmicro.Renode.Core.Range;
19 
20 namespace Antmicro.Renode.Peripherals.CoSimulated
21 {
22     public class CoSimulatedPeripheral : ICoSimulationConnectible, IQuadWordPeripheral, IDoubleWordPeripheral, IWordPeripheral, IBytePeripheral, IBusPeripheral, IDisposable, INumberedGPIOOutput, IGPIOReceiver, IAbsoluteAddressAware
23     {
CoSimulatedPeripheral(Machine machine, int maxWidth = 64, bool useAbsoluteAddress = false, long frequency = VerilogTimeunitFrequency, string simulationFilePathLinux = null, string simulationFilePathWindows = null, string simulationFilePathMacOS = null, string simulationContextLinux = null, string simulationContextWindows = null, string simulationContextMacOS = null, ulong limitBuffer = LimitBuffer, int timeout = DefaultTimeout, string address = null, bool createConnection = true, ulong renodeToCosimSignalsOffset = 0, Range? cosimToRenodeSignalRange = null, int renodeToCosimIndex = 0, int cosimToRenodeIndex = 0)24         public CoSimulatedPeripheral(Machine machine, int maxWidth = 64, bool useAbsoluteAddress = false, long frequency = VerilogTimeunitFrequency,
25             string simulationFilePathLinux = null, string simulationFilePathWindows = null, string simulationFilePathMacOS = null,
26             string simulationContextLinux = null, string simulationContextWindows = null, string simulationContextMacOS = null,
27             ulong limitBuffer = LimitBuffer, int timeout = DefaultTimeout, string address = null,  bool createConnection = true,
28             ulong renodeToCosimSignalsOffset = 0, Range? cosimToRenodeSignalRange = null, int renodeToCosimIndex = 0, int cosimToRenodeIndex = 0)
29         {
30             UseAbsoluteAddress = useAbsoluteAddress;
31             this.maxWidth = maxWidth;
32             this.renodeToCosimSignalsOffset = renodeToCosimSignalsOffset;
33             this.cosimToRenodeSignalRange = cosimToRenodeSignalRange;
34             RenodeToCosimIndex = renodeToCosimIndex;
35             CosimToRenodeIndex = cosimToRenodeIndex;
36 
37             if(createConnection)
38             {
39                 connection = new CoSimulationConnection(machine, "cosimulation_connection", frequency,
40                         simulationFilePathLinux, simulationFilePathWindows, simulationFilePathMacOS,
41                         simulationContextLinux, simulationContextWindows, simulationContextMacOS,
42                         limitBuffer, timeout, address);
43                 connection.AttachTo(this);
44             }
45             else
46             {
47                 CheckNoEffectConstructorParam(nameof(frequency), frequency, VerilogTimeunitFrequency);
48                 CheckNoEffectConstructorParam(nameof(limitBuffer), limitBuffer, LimitBuffer);
49                 CheckNoEffectConstructorParam(nameof(timeout), timeout, DefaultTimeout);
50                 CheckNoEffectConstructorParam(nameof(address), address, null);
51                 CheckNoEffectConstructorParam(nameof(simulationFilePathLinux), simulationFilePathLinux, null);
52                 CheckNoEffectConstructorParam(nameof(simulationFilePathWindows), simulationFilePathWindows, null);
53                 CheckNoEffectConstructorParam(nameof(simulationFilePathMacOS), simulationFilePathMacOS, null);
54                 CheckNoEffectConstructorParam(nameof(simulationContextLinux), simulationContextLinux, null);
55                 CheckNoEffectConstructorParam(nameof(simulationContextWindows), simulationContextWindows, null);
56                 CheckNoEffectConstructorParam(nameof(simulationContextMacOS), simulationContextMacOS, null);
57             }
58 
59             var innerGPIOConnections = new Dictionary<int, IGPIO>();
60             if(this.cosimToRenodeSignalRange.HasValue)
61             {
62                 for(int i = 0; i < (int)this.cosimToRenodeSignalRange.Value.Size; i++)
63                 {
64                     innerGPIOConnections[i] = new GPIO();
65                 }
66             }
67 
68             Connections = new ReadOnlyDictionary<int, IGPIO>(innerGPIOConnections);
69         }
70 
Reset()71         public void Reset()
72         {
73             connection?.Reset();
74         }
75 
OnGPIO(int number, bool value)76         public void OnGPIO(int number, bool value)
77         {
78             // Connection can be null here because OnGPIO is called during initialization
79             // for each input connection
80             connection?.SendGPIO((int)renodeToCosimSignalsOffset + number, value);
81         }
82 
OnConnectionAttached(CoSimulationConnection connection)83         public virtual void OnConnectionAttached(CoSimulationConnection connection)
84         {
85             this.connection = connection;
86             if(cosimToRenodeSignalRange.HasValue)
87             {
88                 this.connection.RegisterOnGPIOReceive(ReceiveGPIOChange, cosimToRenodeSignalRange.Value);
89             }
90         }
91 
OnConnectionDetached(CoSimulationConnection connection)92         public virtual void OnConnectionDetached(CoSimulationConnection connection)
93         {
94             if(cosimToRenodeSignalRange.HasValue)
95             {
96                 this.connection.UnregisterOnGPIOReceive(cosimToRenodeSignalRange.Value);
97             }
98             this.connection = null;
99         }
100 
ReadByte(long offset)101         public byte ReadByte(long offset)
102         {
103             offset = UseAbsoluteAddress ? (long)absoluteAddress : offset;
104             if(!VerifyLength(8, offset))
105             {
106                 return 0;
107             }
108             return (byte)connection.Read(this, ActionType.ReadFromBusByte, offset);
109         }
110 
ReadWord(long offset)111         public ushort ReadWord(long offset)
112         {
113             offset = UseAbsoluteAddress ? (long)absoluteAddress : offset;
114             if(!VerifyLength(16, offset))
115             {
116                 return 0;
117             }
118             return (ushort)connection.Read(this, ActionType.ReadFromBusWord, offset);
119         }
120 
ReadDoubleWord(long offset)121         public uint ReadDoubleWord(long offset)
122         {
123             offset = UseAbsoluteAddress ? (long)absoluteAddress : offset;
124             if(!VerifyLength(32, offset))
125             {
126                 return 0;
127             }
128             return (uint)connection.Read(this, ActionType.ReadFromBusDoubleWord, offset);
129         }
130 
ReadQuadWord(long offset)131         public ulong ReadQuadWord(long offset)
132         {
133             offset = UseAbsoluteAddress ? (long)absoluteAddress : offset;
134             if(!VerifyLength(64, offset))
135             {
136                 return 0;
137             }
138             return connection.Read(this, ActionType.ReadFromBusQuadWord, offset);
139         }
140 
WriteByte(long offset, byte value)141         public void WriteByte(long offset, byte value)
142         {
143             offset = UseAbsoluteAddress ? (long)absoluteAddress : offset;
144             if(VerifyLength(8, offset, value))
145             {
146                 connection.Write(this, ActionType.WriteToBusByte, offset, value);
147             }
148         }
149 
WriteWord(long offset, ushort value)150         public void WriteWord(long offset, ushort value)
151         {
152             offset = UseAbsoluteAddress ? (long)absoluteAddress : offset;
153             if(VerifyLength(16, offset, value))
154             {
155                 connection.Write(this, ActionType.WriteToBusWord, offset, value);
156             }
157         }
158 
WriteDoubleWord(long offset, uint value)159         public void WriteDoubleWord(long offset, uint value)
160         {
161             offset = UseAbsoluteAddress ? (long)absoluteAddress : offset;
162             if(VerifyLength(32, offset, value))
163             {
164                 connection.Write(this, ActionType.WriteToBusDoubleWord, offset, value);
165             }
166         }
167 
WriteQuadWord(long offset, ulong value)168         public void WriteQuadWord(long offset, ulong value)
169         {
170             offset = UseAbsoluteAddress ? (long)absoluteAddress : offset;
171             if(VerifyLength(64, offset, value))
172             {
173                 connection.Write(this, ActionType.WriteToBusQuadWord, offset, value);
174             }
175         }
176 
ReceiveGPIOChange(int coSimNumber, bool value)177         public virtual void ReceiveGPIOChange(int coSimNumber, bool value)
178         {
179             if(!cosimToRenodeSignalRange.HasValue)
180             {
181                 this.Log(LogLevel.Warning, $"Received GPIO change from co-simulation, but no cosimToRenodeSignalRange is defined.");
182                 return;
183             }
184 
185             var localNumber = coSimNumber - (int)cosimToRenodeSignalRange.Value.StartAddress;
186             if (!Connections.TryGetValue(localNumber, out var gpioConnection))
187             {
188                  this.Log(LogLevel.Warning, "Unhandled interrupt: '{0}'", localNumber);
189                  return;
190             }
191 
192             gpioConnection.Set(value);
193         }
194 
Dispose()195         public void Dispose()
196         {
197             connection?.DetachFrom(this);
198         }
199 
200         public IReadOnlyDictionary<int, IGPIO> Connections { get; }
201 
202         public string ConnectionParameters => connection?.ConnectionParameters ?? "";
Connect()203         public void Connect()
204         {
205             AssureIsConnected();
206             connection.Connect();
207         }
208 
SetAbsoluteAddress(ulong address)209         public void SetAbsoluteAddress(ulong address)
210         {
211             absoluteAddress = address;
212         }
213 
214         public string SimulationContextLinux
215         {
216             get => connection.SimulationContextLinux;
217             set
218             {
219                 AssureIsConnected();
220                 connection.SimulationContextLinux = value;
221             }
222         }
223 
224         public string SimulationContextWindows
225         {
226             get => connection.SimulationContextWindows;
227             set
228             {
229                 AssureIsConnected();
230                 connection.SimulationContextWindows = value;
231             }
232         }
233 
234         public string SimulationContextMacOS
235         {
236             get => connection.SimulationContextMacOS;
237             set
238             {
239                 AssureIsConnected();
240                 connection.SimulationContextMacOS = value;
241             }
242         }
243 
244         public string SimulationContext
245         {
246             get => connection.SimulationContext;
247             set
248             {
249                 AssureIsConnected();
250                 connection.SimulationContext = value;
251             }
252         }
253 
254         public string SimulationFilePathLinux
255         {
256             get => connection.SimulationFilePathLinux;
257             set
258             {
259                 AssureIsConnected();
260                 connection.SimulationFilePathLinux = value;
261             }
262         }
263 
264         public string SimulationFilePathWindows
265         {
266             get => connection.SimulationFilePathWindows;
267             set
268             {
269                 AssureIsConnected();
270                 connection.SimulationFilePathWindows = value;
271             }
272         }
273 
274         public string SimulationFilePathMacOS
275         {
276             get => connection.SimulationFilePathMacOS;
277             set
278             {
279                 AssureIsConnected();
280                 connection.SimulationFilePathMacOS = value;
281             }
282         }
283 
284         // The following constant should be in sync with a time unit defined in the `renode` SystemVerilog module.
285         // It allows using simulation time instead of a number of clock ticks.
286         public const long VerilogTimeunitFrequency = 1000000000;
287         public bool UseAbsoluteAddress { get; set; }
288 
VerifyLength(int length, long offset, ulong? value = null)289         private bool VerifyLength(int length, long offset, ulong? value = null)
290         {
291             if(length > maxWidth)
292             {
293                 this.Log(LogLevel.Warning, "Trying to {0} {1} bits at offset 0x{2:X}{3}, but maximum length is {4}",
294                         value.HasValue ? "write" : "read",
295                         length,
296                         offset,
297                         value.HasValue ? $" (value 0x{value})" : String.Empty,
298                         maxWidth
299                 );
300                 return false;
301             }
302             return true;
303         }
304 
AssureIsConnected(string message = null)305         private void AssureIsConnected(string message = null)
306         {
307             if(connection == null)
308             {
309                 throw new RecoverableException("CoSimulatedPeripheral is not attached to a CoSimulationConnection.");
310             }
311         }
312 
CheckNoEffectConstructorParam(string name, T value, T defaultValue)313         private void CheckNoEffectConstructorParam<T>(string name, T value, T defaultValue)
314         {
315                 if(EqualityComparer<T>.Default.Equals(defaultValue, value) == false)
316                 {
317                     this.Log(LogLevel.Error, "CoSimulatedPeripheral: Parameter \"{0}\" set to {1} will be ignored, because this peripheral uses an external CoSimulationConnection (\"createConnection\" is set to false). Change the property in the relevant CoSimulationConnection instead.", name, value);
318                 }
319         }
320 
321         public int RenodeToCosimIndex { get; }
322         public int CosimToRenodeIndex { get; }
323 
324         protected CoSimulationConnection connection;
325         protected const ulong LimitBuffer = 1000000;
326         protected const int DefaultTimeout  = 3000;
327         readonly protected Range? cosimToRenodeSignalRange;
328 
329         private int maxWidth;
330         private ulong renodeToCosimSignalsOffset;
331         private ulong absoluteAddress = 0;
332         private const string LimitTimerName = "CoSimulationIntegrationClock";
333     }
334 }
335