[#59809] Add SystemC integration
diff --git a/Renode.sln b/Renode.sln
index 90eff76..5a56cf8 100644
--- a/Renode.sln
+++ b/Renode.sln
@@ -59,6 +59,8 @@
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TracePlugin", "src\Infrastructure\src\Plugins\TracePlugin\TracePlugin.csproj", "{887C6088-F483-466A-A671-06EEC42B8DC1}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SystemCPlugin", "src\Plugins\SystemCPlugin\SystemCPlugin.csproj", "{8882BDAF-FE52-4A39-B1F2-84C3F061D5A7}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WiresharkPlugin", "src\Plugins\WiresharkPlugin\WiresharkPlugin.csproj", "{66A9995A-13AE-4454-88A6-29EB2D6F5988}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VerilatorPlugin", "src\Plugins\VerilatorPlugin\VerilatorPlugin.csproj", "{45A27C6A-1831-4E0D-84C2-FB3893E7FBE5}"
@@ -413,6 +415,18 @@
{66A9995A-13AE-4454-88A6-29EB2D6F5988}.ReleaseWindows|Any CPU.Build.0 = Release|Any CPU
{66A9995A-13AE-4454-88A6-29EB2D6F5988}.DebugWindows|Any CPU.ActiveCfg = Debug|Any CPU
{66A9995A-13AE-4454-88A6-29EB2D6F5988}.DebugWindows|Any CPU.Build.0 = Debug|Any CPU
+ {8882BDAF-FE52-4A39-B1F2-84C3F061D5A7}.DebugHeadless|Any CPU.ActiveCfg = Debug|Any CPU
+ {8882BDAF-FE52-4A39-B1F2-84C3F061D5A7}.DebugHeadless|Any CPU.Build.0 = Debug|Any CPU
+ {8882BDAF-FE52-4A39-B1F2-84C3F061D5A7}.ReleaseHeadless|Any CPU.ActiveCfg = Release|Any CPU
+ {8882BDAF-FE52-4A39-B1F2-84C3F061D5A7}.ReleaseHeadless|Any CPU.Build.0 = Release|Any CPU
+ {8882BDAF-FE52-4A39-B1F2-84C3F061D5A7}.DebugMono|Any CPU.ActiveCfg = Debug|Any CPU
+ {8882BDAF-FE52-4A39-B1F2-84C3F061D5A7}.DebugMono|Any CPU.Build.0 = Debug|Any CPU
+ {8882BDAF-FE52-4A39-B1F2-84C3F061D5A7}.ReleaseMono|Any CPU.ActiveCfg = Release|Any CPU
+ {8882BDAF-FE52-4A39-B1F2-84C3F061D5A7}.ReleaseMono|Any CPU.Build.0 = Release|Any CPU
+ {8882BDAF-FE52-4A39-B1F2-84C3F061D5A7}.ReleaseWindows|Any CPU.ActiveCfg = Release|Any CPU
+ {8882BDAF-FE52-4A39-B1F2-84C3F061D5A7}.ReleaseWindows|Any CPU.Build.0 = Release|Any CPU
+ {8882BDAF-FE52-4A39-B1F2-84C3F061D5A7}.DebugWindows|Any CPU.ActiveCfg = Debug|Any CPU
+ {8882BDAF-FE52-4A39-B1F2-84C3F061D5A7}.DebugWindows|Any CPU.Build.0 = Debug|Any CPU
{45A27C6A-1831-4E0D-84C2-FB3893E7FBE5}.DebugHeadless|Any CPU.ActiveCfg = Debug|Any CPU
{45A27C6A-1831-4E0D-84C2-FB3893E7FBE5}.DebugHeadless|Any CPU.Build.0 = Debug|Any CPU
{45A27C6A-1831-4E0D-84C2-FB3893E7FBE5}.ReleaseHeadless|Any CPU.ActiveCfg = Release|Any CPU
@@ -575,6 +589,7 @@
{25FAECC1-55F0-4608-88BD-4207A7F993B0} = {CA585D43-7DF3-4486-B9B5-8DAA812716C3}
{C93D746E-1586-4D4F-B411-BF5A966E6A08} = {CA585D43-7DF3-4486-B9B5-8DAA812716C3}
{BB222124-707C-5EFD-8289-2728351FB7AA} = {4671F602-D5E2-4F4E-B942-3357C33C92E2}
+ {8882BDAF-FE52-4A39-B1F2-84C3F061D5A7} = {8BD709BF-8C11-4CBA-9A7D-CC2CC639BD4F}
{45A27C6A-1831-4E0D-84C2-FB3893E7FBE5} = {8BD709BF-8C11-4CBA-9A7D-CC2CC639BD4F}
{3A70B9B8-BBAB-47EA-8473-B7A0B4961D56} = {CA585D43-7DF3-4486-B9B5-8DAA812716C3}
{69B08E08-10DC-473C-BCB7-3B0C398F63CF} = {CD9D4CF9-BD98-4D57-A5EF-EA5C3C75CB0C}
diff --git a/Renode_NET.sln b/Renode_NET.sln
index e52b305..a61ad69 100644
--- a/Renode_NET.sln
+++ b/Renode_NET.sln
@@ -69,6 +69,8 @@
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WiresharkPlugin_NET", "src\Plugins\WiresharkPlugin\WiresharkPlugin_NET.csproj", "{1FBC3B41-D61C-4528-8BA5-20EBA17C9031}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SystemCPlugin_NET", "src\Plugins\SystemCPlugin\SystemCPlugin_NET.csproj", "{D7CEC81D-8BF1-4B3E-8B6A-5D7B3CA0C2F2}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleCommandPlugin_NET", "src\Infrastructure\src\Plugins\SampleCommandPlugin\SampleCommandPlugin_NET.csproj", "{78DBBFB9-7193-4ABC-BE9F-DA839693C23A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TracePlugin_NET", "src\Infrastructure\src\Plugins\TracePlugin\TracePlugin_NET.csproj", "{2DB2469A-9F2E-4BA8-A4A0-333E4A319AF0}"
@@ -463,6 +465,22 @@
{3B25FD1F-CEBA-4450-8893-DC330FDB56A7}.ReleaseMono|Any CPU.Build.0 = Release|Any CPU
{3B25FD1F-CEBA-4450-8893-DC330FDB56A7}.ReleaseWindows|Any CPU.ActiveCfg = Release|Any CPU
{3B25FD1F-CEBA-4450-8893-DC330FDB56A7}.ReleaseWindows|Any CPU.Build.0 = Release|Any CPU
+ {D7CEC81D-8BF1-4B3E-8B6A-5D7B3CA0C2F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D7CEC81D-8BF1-4B3E-8B6A-5D7B3CA0C2F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D7CEC81D-8BF1-4B3E-8B6A-5D7B3CA0C2F2}.DebugHeadless|Any CPU.ActiveCfg = Debug|Any CPU
+ {D7CEC81D-8BF1-4B3E-8B6A-5D7B3CA0C2F2}.DebugHeadless|Any CPU.Build.0 = Debug|Any CPU
+ {D7CEC81D-8BF1-4B3E-8B6A-5D7B3CA0C2F2}.DebugMono|Any CPU.ActiveCfg = Debug|Any CPU
+ {D7CEC81D-8BF1-4B3E-8B6A-5D7B3CA0C2F2}.DebugMono|Any CPU.Build.0 = Debug|Any CPU
+ {D7CEC81D-8BF1-4B3E-8B6A-5D7B3CA0C2F2}.DebugWindows|Any CPU.ActiveCfg = Debug|Any CPU
+ {D7CEC81D-8BF1-4B3E-8B6A-5D7B3CA0C2F2}.DebugWindows|Any CPU.Build.0 = Debug|Any CPU
+ {D7CEC81D-8BF1-4B3E-8B6A-5D7B3CA0C2F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D7CEC81D-8BF1-4B3E-8B6A-5D7B3CA0C2F2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D7CEC81D-8BF1-4B3E-8B6A-5D7B3CA0C2F2}.ReleaseHeadless|Any CPU.ActiveCfg = Release|Any CPU
+ {D7CEC81D-8BF1-4B3E-8B6A-5D7B3CA0C2F2}.ReleaseHeadless|Any CPU.Build.0 = Release|Any CPU
+ {D7CEC81D-8BF1-4B3E-8B6A-5D7B3CA0C2F2}.ReleaseMono|Any CPU.ActiveCfg = Release|Any CPU
+ {D7CEC81D-8BF1-4B3E-8B6A-5D7B3CA0C2F2}.ReleaseMono|Any CPU.Build.0 = Release|Any CPU
+ {D7CEC81D-8BF1-4B3E-8B6A-5D7B3CA0C2F2}.ReleaseWindows|Any CPU.ActiveCfg = Release|Any CPU
+ {D7CEC81D-8BF1-4B3E-8B6A-5D7B3CA0C2F2}.ReleaseWindows|Any CPU.Build.0 = Release|Any CPU
{A244A988-6ADC-4B90-912B-D66C680FA962}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A244A988-6ADC-4B90-912B-D66C680FA962}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A244A988-6ADC-4B90-912B-D66C680FA962}.DebugHeadless|Any CPU.ActiveCfg = Debug|Any CPU
@@ -724,6 +742,7 @@
{1FBC3B41-D61C-4528-8BA5-20EBA17C9031} = {8BD709BF-8C11-4CBA-9A7D-CC2CC639BD4F}
{78DBBFB9-7193-4ABC-BE9F-DA839693C23A} = {8BD709BF-8C11-4CBA-9A7D-CC2CC639BD4F}
{2DB2469A-9F2E-4BA8-A4A0-333E4A319AF0} = {8BD709BF-8C11-4CBA-9A7D-CC2CC639BD4F}
+ {D7CEC81D-8BF1-4B3E-8B6A-5D7B3CA0C2F2} = {8BD709BF-8C11-4CBA-9A7D-CC2CC639BD4F}
# {...} = Tests
{101B7A46-D5A6-41DE-ADC3-36939FF62942} = {CD9D4CF9-BD98-4D57-A5EF-EA5C3C75CB0C}
{16DBE3C1-83C5-4472-85AC-CF654E4559AD} = {CD9D4CF9-BD98-4D57-A5EF-EA5C3C75CB0C}
diff --git a/src/Plugins/SystemCPlugin/Peripheral/SystemCPeripheral.cs b/src/Plugins/SystemCPlugin/Peripheral/SystemCPeripheral.cs
new file mode 100644
index 0000000..cff412a
--- /dev/null
+++ b/src/Plugins/SystemCPlugin/Peripheral/SystemCPeripheral.cs
@@ -0,0 +1,499 @@
+//
+// Copyright (c) 2010-2024 Antmicro
+//
+// This file is licensed under the MIT License.
+// Full license text is available in 'licenses/MIT.txt'.
+//
+using Antmicro.Renode.Core;
+using Antmicro.Renode.Exceptions;
+using Antmicro.Renode.Logging;
+using Antmicro.Renode.Peripherals.Bus;
+using Antmicro.Renode.Peripherals.CPU;
+using Antmicro.Renode.Peripherals.Timers;
+using Antmicro.Renode.Time;
+using Antmicro.Renode.Utilities;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Net;
+using System.Net.Sockets;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace Antmicro.Renode.Peripherals.SystemC
+{
+ public enum RenodeAction : byte
+ {
+ Init = 0,
+ Read = 1,
+ Write = 2,
+ Timesync = 3,
+ GPIOWrite = 4,
+ Reset = 5,
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct RenodeMessage
+ {
+ public RenodeMessage(RenodeAction actionId, byte dataLength, byte connectionIndex, ulong address, ulong payload)
+ {
+ ActionId = actionId;
+ DataLength = dataLength;
+ ConnectionIndex = connectionIndex;
+ Address = address;
+ Payload = payload;
+ }
+
+ public byte[] Serialize()
+ {
+ var size = Marshal.SizeOf(this);
+ var result = new byte[size];
+ var handler = default(GCHandle);
+
+ try
+ {
+ handler = GCHandle.Alloc(result, GCHandleType.Pinned);
+ Marshal.StructureToPtr(this, handler.AddrOfPinnedObject(), false);
+ }
+ finally
+ {
+ if(handler.IsAllocated)
+ {
+ handler.Free();
+ }
+ }
+
+ return result;
+ }
+
+ public void Deserialize(byte[] message)
+ {
+ var handler = default(GCHandle);
+ try
+ {
+ handler = GCHandle.Alloc(message, GCHandleType.Pinned);
+ this = (RenodeMessage)Marshal.PtrToStructure(handler.AddrOfPinnedObject(), typeof(RenodeMessage));
+ }
+ finally
+ {
+ if(handler.IsAllocated)
+ {
+ handler.Free();
+ }
+ }
+ }
+
+ public override string ToString()
+ {
+ return $"RenodeMessage [{ActionId}@{ConnectionIndex}:{Address}] {Payload}";
+ }
+
+ public bool IsSystemBusConnection() => ConnectionIndex == MainSystemBusConnectionIndex;
+ public bool IsDirectConnection() => !IsSystemBusConnection();
+
+ public byte GetDirectConnectionIndex()
+ {
+ if(!IsDirectConnection())
+ {
+ Logger.Log(LogLevel.Error, "Message for main system bus connection does not have a direct connection index.");
+ return 0xff;
+ }
+ return (byte)(ConnectionIndex - 1);
+ }
+
+ private const byte MainSystemBusConnectionIndex = 0;
+
+ public readonly RenodeAction ActionId;
+ public readonly byte DataLength;
+ public readonly byte ConnectionIndex;
+ public readonly ulong Address;
+ public readonly ulong Payload;
+ }
+
+ public interface IDirectAccessPeripheral : IPeripheral
+ {
+ ulong ReadDirect(byte dataLength, long offset, byte connectionIndex);
+ void WriteDirect(byte dataLength, long offset, ulong value, byte connectionIndex);
+ };
+
+ public class SystemCPeripheral : IQuadWordPeripheral, IDoubleWordPeripheral, IWordPeripheral, IBytePeripheral, INumberedGPIOOutput, IGPIOReceiver, IDirectAccessPeripheral, IDisposable
+ {
+ public SystemCPeripheral(
+ IMachine machine,
+ string address,
+ int port,
+ int timeSyncPeriodUS = 1000
+ )
+ {
+ this.address = address;
+ this.port = port;
+ this.machine = machine;
+ this.timeSyncPeriodUS = timeSyncPeriodUS;
+ sysbus = machine.GetSystemBus(this);
+
+ directAccessPeripherals = new Dictionary<int, IDirectAccessPeripheral>();
+
+ messageLock = new object();
+
+ backwardThread = new Thread(BackwardConnectionLoop)
+ {
+ IsBackground = true,
+ Name = "SystemC.BackwardThread"
+ };
+
+ var innerConnections = new Dictionary<int, IGPIO>();
+ for(int i = 0; i < NumberOfGPIOPins; i++)
+ {
+ innerConnections[i] = new GPIO();
+ }
+ Connections = new ReadOnlyDictionary<int, IGPIO>(innerConnections);
+ }
+
+ public void AddDirectConnection(byte connectionIndex, IDirectAccessPeripheral target)
+ {
+ if(directAccessPeripherals.ContainsKey(connectionIndex))
+ {
+ this.Log(LogLevel.Error, "Failed to add Direct Connection #{0} - connection with this index is already present", connectionIndex);
+ return;
+ }
+
+ directAccessPeripherals.Add(connectionIndex, target);
+ }
+
+ public string SystemCExecutablePath
+ {
+ get => systemcExecutablePath;
+ set
+ {
+ try
+ {
+ systemcExecutablePath = value;
+ var connectionParams = $"{address} {port}";
+ StartSystemCProcess(systemcExecutablePath, connectionParams);
+ SetupConnection();
+ SetupTimesync();
+ }
+ catch(Exception e)
+ {
+ throw new RecoverableException($"Failed to start SystemC process: {e.Message}");
+ }
+ }
+ }
+
+ public ulong ReadQuadWord(long offset)
+ {
+ return Read(8, offset);
+ }
+
+ public void WriteQuadWord(long offset, ulong value)
+ {
+ Write(8, offset, value);
+ }
+
+ public uint ReadDoubleWord(long offset)
+ {
+ return (uint)Read(4, offset);
+ }
+
+ public void WriteDoubleWord(long offset, uint value)
+ {
+ Write(4, offset, value);
+ }
+
+ public ushort ReadWord(long offset)
+ {
+ return (ushort)Read(2, offset);
+ }
+
+ public void WriteWord(long offset, ushort value)
+ {
+ Write(2, offset, value);
+ }
+
+ public byte ReadByte(long offset)
+ {
+ return (byte)Read(1, offset);
+ }
+
+ public void WriteByte(long offset, byte value)
+ {
+ Write(1, offset, value);
+ }
+
+ public ulong ReadDirect(byte dataLength, long offset, byte connectionIndex)
+ {
+ return Read(dataLength, offset, connectionIndex);
+ }
+
+ public void WriteDirect(byte dataLength, long offset, ulong value, byte connectionIndex)
+ {
+ Write(dataLength, offset, value, connectionIndex);
+ }
+
+ public void OnGPIO(int number, bool value)
+ {
+ BitHelper.SetBit(ref outGPIOState, (byte)number, value);
+ var request = new RenodeMessage(RenodeAction.GPIOWrite, 0, 0, 0, outGPIOState);
+ SendRequest(request);
+ }
+
+ public void Reset()
+ {
+ outGPIOState = 0;
+ var request = new RenodeMessage(RenodeAction.Reset, 0, 0, 0, 0);
+ SendRequest(request);
+ }
+
+ public void Dispose()
+ {
+ if(systemcProcess != null && !systemcProcess.HasExited)
+ {
+ // Init message sent after connection has been established signifies Renode terminated and SystemC process
+ // should exit.
+ var request = new RenodeMessage(RenodeAction.Init, 0, 0, 0, 0);
+ SendRequest(request);
+
+ if(!systemcProcess.WaitForExit(500)) {
+ this.Log(LogLevel.Info, "SystemC process failed to exit gracefully - killing it.");
+ systemcProcess.Kill();
+ }
+ }
+ }
+
+ public IReadOnlyDictionary<int, IGPIO> Connections { get; }
+
+ private ulong Read(byte dataLength, long offset, byte connectionIndex = 0)
+ {
+ var request = new RenodeMessage(RenodeAction.Read, dataLength, connectionIndex, (ulong)offset, 0);
+ var response = SendRequest(request);
+
+ TryToSkipTransactionTime(response.Address);
+
+ return response.Payload;
+ }
+
+ private void Write(byte dataLength, long offset, ulong value, byte connectionIndex = 0)
+ {
+ var request = new RenodeMessage(RenodeAction.Write, dataLength, connectionIndex, (ulong)offset, value);
+ var response = SendRequest(request);
+
+ TryToSkipTransactionTime(response.Address);
+ }
+
+ private ulong GetCurrentVirtualTimeUS()
+ {
+ return machine.LocalTimeSource.ElapsedVirtualTime.TotalMicroseconds;
+ }
+
+ private void StartSystemCProcess(string systemcExecutablePath, string connectionParams)
+ {
+ try
+ {
+ systemcProcess = new Process
+ {
+ StartInfo = new ProcessStartInfo(systemcExecutablePath)
+ {
+ UseShellExecute = false,
+ Arguments = connectionParams
+ }
+ };
+
+ systemcProcess.Start();
+ }
+ catch(Exception e)
+ {
+ throw new RecoverableException(e.Message);
+ }
+ }
+
+ private void SetupConnection()
+ {
+ var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ listener.Bind(new IPEndPoint(IPAddress.Parse(address), port));
+ listener.Listen(2);
+
+ this.Log(LogLevel.Info, "SystemCPeripheral waiting for forward SystemC connection on {0}:{1}", address, port);
+ forwardSocket = listener.Accept();
+ forwardSocket.SendTimeout = 1000;
+ forwardSocket.ReceiveTimeout = 1000;
+
+ backwardSocket = listener.Accept();
+ backwardSocket.SendTimeout = 1000;
+ // No ReceiveTimeout for backwardSocket - it runs on a dedicated thread and by design blocks on Receive until a message arrives from SystemC process.
+
+ SendRequest(new RenodeMessage(RenodeAction.Init, 0, 0, 0, (ulong)timeSyncPeriodUS));
+
+ backwardThread.Start();
+ }
+
+ private void SetupTimesync()
+ {
+ // NOTE: This function blocks simulation while awaiting for the response.
+
+ // Timer unit is microseconds
+ var timesyncFrequency = 1000000;
+ var timesyncLimit = (ulong)timeSyncPeriodUS;
+ var timerName = "RenodeSystemCTimesyncTimer";
+ var timer = new LimitTimer(machine.ClockSource, timesyncFrequency, this, timerName, limit: timesyncLimit, enabled: true, eventEnabled: true, autoUpdate: true);
+ timer.LimitReached += () =>
+ {
+ var request = new RenodeMessage(RenodeAction.Timesync, 0, 0, 0, GetCurrentVirtualTimeUS());
+ var response = SendRequest(request);
+ };
+ }
+
+ private RenodeMessage SendRequest(RenodeMessage request)
+ {
+ lock (messageLock)
+ {
+ var messageSize = Marshal.SizeOf(typeof(RenodeMessage));
+ var recvBytes = new byte[messageSize];
+
+ forwardSocket.Send(request.Serialize(), SocketFlags.None);
+ forwardSocket.Receive(recvBytes, 0, messageSize, SocketFlags.None);
+
+ var responseMessage = new RenodeMessage();
+ responseMessage.Deserialize(recvBytes);
+
+ return responseMessage;
+ }
+ }
+
+ private void BackwardConnectionLoop()
+ {
+ while(true)
+ {
+ var messageSize = Marshal.SizeOf(typeof(RenodeMessage));
+ var recvBytes = new byte[messageSize];
+
+ var nbytes = backwardSocket.Receive(recvBytes, 0, messageSize, SocketFlags.None);
+ if(nbytes == 0) {
+ this.Log(LogLevel.Info, "Backward connection to SystemC process closed.");
+ return;
+ }
+
+ var message = new RenodeMessage();
+ message.Deserialize(recvBytes);
+
+ ulong payload = 0;
+ switch(message.ActionId)
+ {
+ case RenodeAction.GPIOWrite:
+ // We have to respond before GPIO state is changed, because SystemC is blocked until
+ // it receives the response. Setting the GPIO may require it to respond, e. g. when it
+ // is interracted with from an interrupt handler.
+ backwardSocket.Send(message.Serialize(), SocketFlags.None);
+ for(int pin = 0; pin < NumberOfGPIOPins; pin++)
+ {
+ bool irqval = (message.Payload & (1UL << pin)) != 0;
+ Connections[pin].Set(irqval);
+ }
+ break;
+ case RenodeAction.Write:
+ if(message.IsSystemBusConnection())
+ {
+ sysbus.TryGetCurrentCPU(out var icpu);
+ switch(message.DataLength)
+ {
+ case 1:
+ sysbus.WriteByte(message.Address, (byte)message.Payload, context: icpu);
+ break;
+ case 2:
+ sysbus.WriteWord(message.Address, (ushort)message.Payload, context: icpu);
+ break;
+ case 4:
+ sysbus.WriteDoubleWord(message.Address, (uint)message.Payload, context: icpu);
+ break;
+ case 8:
+ sysbus.WriteQuadWord(message.Address, message.Payload, context: icpu);
+ break;
+ default:
+ this.Log(LogLevel.Error, "SystemC integration error - invalid data length {0} sent through backward connection from the SystemC process.", message.DataLength);
+ break;
+ }
+ }
+ else
+ {
+ directAccessPeripherals[message.GetDirectConnectionIndex()].WriteDirect(
+ message.DataLength, (long)message.Address, message.Payload, message.ConnectionIndex);
+ }
+ backwardSocket.Send(message.Serialize(), SocketFlags.None);
+ break;
+ case RenodeAction.Read:
+ if(message.IsSystemBusConnection())
+ {
+ sysbus.TryGetCurrentCPU(out var icpu);
+ switch(message.DataLength)
+ {
+ case 1:
+ payload = (ulong)sysbus.ReadByte(message.Address, context: icpu);
+ break;
+ case 2:
+ payload = (ulong)sysbus.ReadWord(message.Address, context: icpu);
+ break;
+ case 4:
+ payload = (ulong)sysbus.ReadDoubleWord(message.Address, context: icpu);
+ break;
+ case 8:
+ payload = (ulong)sysbus.ReadQuadWord(message.Address, context: icpu);
+ break;
+ default:
+ this.Log(LogLevel.Error, "SystemC integration error - invalid data length {0} sent through backward connection from the SystemC process.", message.DataLength);
+ break;
+ }
+ }
+ else
+ {
+ payload = directAccessPeripherals[message.GetDirectConnectionIndex()].ReadDirect(message.DataLength, (long)message.Address, message.ConnectionIndex);
+ }
+ var responseMessage = new RenodeMessage(message.ActionId, message.DataLength,
+ message.ConnectionIndex, message.Address, payload);
+ backwardSocket.Send(responseMessage.Serialize(), SocketFlags.None);
+ break;
+ default:
+ this.Log(LogLevel.Error, "SystemC integration error - invalid message type {0} sent through backward connection from the SystemC process.", message.ActionId);
+ break;
+ }
+ }
+ }
+
+ private void TryToSkipTransactionTime(ulong timeUS)
+ {
+ if(machine.SystemBus.TryGetCurrentCPU(out var icpu))
+ {
+ var baseCPU = icpu as BaseCPU;
+ if(baseCPU != null)
+ {
+ baseCPU.SkipTime(TimeInterval.FromMicroseconds(timeUS));
+ }
+ else
+ {
+ this.Log(LogLevel.Error, "Failed to get CPU, all SystemC transactions processed as if they have no duration. This can desynchronize Renode and SystemC simulations.");
+ }
+ }
+ }
+
+ private readonly IBusController sysbus;
+ private readonly IMachine machine;
+
+ // NumberOfGPIOPins must be equal to renode_bridge.h:NUM_GPIO
+ private const int NumberOfGPIOPins = 64;
+
+ private readonly string address;
+ private readonly int port;
+ private readonly int timeSyncPeriodUS;
+ private readonly object messageLock;
+
+ private readonly Thread backwardThread;
+
+ private Dictionary<int, IDirectAccessPeripheral> directAccessPeripherals;
+ private string systemcExecutablePath;
+ private Process systemcProcess;
+
+ private ulong outGPIOState;
+
+ private Socket forwardSocket;
+ private Socket backwardSocket;
+ }
+}
diff --git a/src/Plugins/SystemCPlugin/SystemCModule/CMakeLists.txt b/src/Plugins/SystemCPlugin/SystemCModule/CMakeLists.txt
new file mode 100644
index 0000000..cf26517
--- /dev/null
+++ b/src/Plugins/SystemCPlugin/SystemCModule/CMakeLists.txt
@@ -0,0 +1,20 @@
+cmake_minimum_required(VERSION 3.8)
+
+project(
+ RenodeSystemCBridge,
+ VERSION 0.1
+ DESCRIPTION "SystemC module for interfacing with Renode"
+ LANGUAGES CXX)
+
+file(GLOB_RECURSE SOURCES_SOCKET_CPP lib/socket-cpp/*.cpp)
+add_library(socket_cpp ${SOURCES_SOCKET_CPP})
+
+set(SOURCES_RENODE_BRIDGE
+ src/renode_bridge.cpp
+)
+add_library(renode_bridge ${SOURCES_RENODE_BRIDGE})
+target_include_directories(renode_bridge PUBLIC
+ include
+ lib)
+
+target_link_libraries(renode_bridge socket_cpp)
diff --git a/src/Plugins/SystemCPlugin/SystemCModule/include/renode_bridge.h b/src/Plugins/SystemCPlugin/SystemCModule/include/renode_bridge.h
new file mode 100644
index 0000000..1d37462
--- /dev/null
+++ b/src/Plugins/SystemCPlugin/SystemCModule/include/renode_bridge.h
@@ -0,0 +1,144 @@
+//
+// Copyright (c) 2010-2024 Antmicro
+//
+// This file is licensed under the MIT License.
+// Full license text is available in 'licenses/MIT.txt'.
+//
+#pragma once
+
+#include <memory>
+
+#include "tlm.h"
+#include "tlm_utils/simple_initiator_socket.h"
+#include "tlm_utils/simple_target_socket.h"
+
+struct CTCPClient;
+
+#ifndef RENODE_BUSWIDTH
+#define RENODE_BUSWIDTH 32
+#endif
+
+#define NUM_GPIO 64
+#define NUM_DIRECT_CONNECTIONS 4
+
+// ================================================================================
+// renode_bridge
+//
+// SystemC module that serves as an interface with Renode.
+// ================================================================================
+
+class renode_bridge : sc_core::sc_module {
+public:
+ renode_bridge(sc_core::sc_module_name name, const char *address,
+ const char *port);
+ ~renode_bridge();
+
+public:
+ using renode_bus_target_socket =
+ tlm::tlm_target_socket<RENODE_BUSWIDTH, tlm::tlm_base_protocol_types, 1,
+ sc_core::SC_ZERO_OR_MORE_BOUND>;
+ using renode_bus_initiator_socket =
+ tlm::tlm_initiator_socket<RENODE_BUSWIDTH, tlm::tlm_base_protocol_types,
+ 1, sc_core::SC_ZERO_OR_MORE_BOUND>;
+ using gpio_in_port = sc_core::sc_port<sc_core::sc_signal_in_if<bool>, 1,
+ sc_core::SC_ZERO_OR_MORE_BOUND>;
+ using gpio_out_port = sc_core::sc_port<sc_core::sc_signal_out_if<bool>, 1,
+ sc_core::SC_ZERO_OR_MORE_BOUND>;
+ using reset_port = sc_core::sc_port<sc_core::sc_signal_inout_if<bool>, 1,
+ sc_core::SC_ZERO_OR_MORE_BOUND>;
+
+ // Socket forwarding transactions performed in Renode to SystemC.
+ renode_bus_initiator_socket initiator_socket;
+
+ // Socket forwarding transactions performed in SystemC to Renode.
+ renode_bus_target_socket target_socket;
+
+ // Direct connections allow for binding peripherals directly to each other in
+ // Renode (bypassing System Bus).
+ renode_bus_initiator_socket
+ direct_connection_initiators[NUM_DIRECT_CONNECTIONS];
+ renode_bus_target_socket direct_connection_targets[NUM_DIRECT_CONNECTIONS];
+
+ // Input GPIO ports - signal changes are driven by SystemC and propagated to
+ // Renode.
+ gpio_in_port gpio_ports_in[NUM_GPIO];
+
+ // Output GPIO ports - signal changes are driven by Renode and propagated to
+ // SystemC.
+ gpio_out_port gpio_ports_out[NUM_GPIO];
+
+ // Reset signal.
+ // Raised when the peripheral is reset. Expected to be lowered by SystemC
+ // once the reset process is complete.
+ reset_port reset;
+
+private:
+ struct initiator_bw_handler: tlm::tlm_bw_transport_if<> {
+ initiator_bw_handler() = default;
+ void initialize(renode_bridge *);
+
+ virtual tlm::tlm_sync_enum nb_transport_bw(tlm::tlm_generic_payload &trans,
+ tlm::tlm_phase &phase,
+ sc_core::sc_time &t);
+ virtual void invalidate_direct_mem_ptr(sc_dt::uint64 start_range,
+ sc_dt::uint64 end_range);
+
+ private:
+ renode_bridge *bridge;
+ };
+
+ struct target_fw_handler: tlm::tlm_fw_transport_if<> {
+ target_fw_handler() = default;
+
+ renode_bus_target_socket socket;
+
+ void initialize(renode_bridge *, uint8_t connection_idx);
+
+ virtual void invalidate_direct_mem_ptr(sc_dt::uint64 start_range,
+ sc_dt::uint64 end_range);
+ virtual tlm::tlm_sync_enum
+ nb_transport_bw(tlm::tlm_generic_payload &payload, tlm::tlm_phase &phase,
+ sc_core::sc_time &delta);
+ virtual tlm::tlm_sync_enum nb_transport_fw(tlm::tlm_generic_payload &trans,
+ tlm::tlm_phase &phase,
+ sc_core::sc_time &t);
+ virtual void b_transport(tlm::tlm_generic_payload &trans,
+ sc_core::sc_time &delay);
+ virtual bool get_direct_mem_ptr(tlm::tlm_generic_payload &trans,
+ tlm::tlm_dmi &dmi_data);
+ virtual unsigned int transport_dbg(tlm::tlm_generic_payload &trans);
+
+ private:
+ renode_bridge *bridge;
+ uint8_t connection_idx;
+ };
+
+ void forward_loop();
+ void on_port_gpio();
+
+ void update_backward_gpio_state(uint64_t new_gpio_state);
+ void service_backward_request(tlm::tlm_generic_payload &payload,
+ uint8_t connection_idx,
+ sc_core::sc_time &delay);
+ int64_t get_systemc_time_us();
+
+ // Connection from Renode -> SystemC.
+ std::unique_ptr<CTCPClient> forward_connection;
+
+ // Connection from SystemC -> Renode
+ std::unique_ptr<CTCPClient> backward_connection;
+
+ // Construction/destruction of tlm_generic_payload is an expensive operation,
+ // so a single tlm_generic_payload object is reused, as recommended by OSCI
+ // TLM-2.0 Language Reference Manual. It also requires that the object is
+ // allocated on the heap.
+ std::unique_ptr<tlm::tlm_generic_payload> payload;
+
+ initiator_bw_handler bus_initiator_bw_handler;
+ target_fw_handler bus_target_fw_handler;
+
+ initiator_bw_handler dc_initiators[NUM_DIRECT_CONNECTIONS];
+ target_fw_handler dc_targets[NUM_DIRECT_CONNECTIONS];
+};
+
+// ================================================================================
diff --git a/src/Plugins/SystemCPlugin/SystemCModule/lib/socket-cpp b/src/Plugins/SystemCPlugin/SystemCModule/lib/socket-cpp
new file mode 120000
index 0000000..38ff62b
--- /dev/null
+++ b/src/Plugins/SystemCPlugin/SystemCModule/lib/socket-cpp
@@ -0,0 +1 @@
+../../../VerilatorPlugin/VerilatorIntegrationLibrary/libs/socket-cpp
\ No newline at end of file
diff --git a/src/Plugins/SystemCPlugin/SystemCModule/src/renode_bridge.cpp b/src/Plugins/SystemCPlugin/SystemCModule/src/renode_bridge.cpp
new file mode 100644
index 0000000..7c239d3
--- /dev/null
+++ b/src/Plugins/SystemCPlugin/SystemCModule/src/renode_bridge.cpp
@@ -0,0 +1,523 @@
+//
+// Copyright (c) 2010-2024 Antmicro
+//
+// This file is licensed under the MIT License.
+// Full license text is available in 'licenses/MIT.txt'.
+//
+#include "renode_bridge.h"
+
+#include <cstdint>
+#include <cstdlib>
+#include <chrono>
+#include <thread>
+
+#include "socket-cpp/Socket/TCPClient.h"
+
+// ================================================================================
+// > Communication protocol
+// ================================================================================
+
+// Forward socket: Request from Renode, Response from SystemC
+// Backward socket: Request from SystemC, Response From Renode
+
+enum renode_action : uint8_t {
+ INIT = 0,
+ // Socket: forward only
+ // Init message received for the second time signifies Renode terminated and
+ // the process should exit. Request:
+ // data_length: ignored
+ // address: ignored
+ // connection_index: ignored
+ // payload: time synchronization granularity in us
+ // TIMESYNC messages will be sent with this period. This does NOT
+ // guarantee that the processes will never desynchronize by more than
+ // this amount.
+ // Response:
+ // Identical to the request message.
+ READ = 1,
+ // Socket: forward, backward
+ // Request:
+ // data_length: number of bytes to read [1, 8]
+ // address: address to read from, in target's address space payload: value
+ // to write connection_index: 0 for SystemBus, [1, NUM_DIRECT_CONNECTIONS]
+ // for direct connection
+ // Response:
+ // address: duration of transaction in us
+ // payload: read value
+ // Otherwise identical to the request message.
+ WRITE = 2,
+ // Socket: forward, backward
+ // Request:
+ // data_length: number of bytes to write [1, 8].
+ // address: address to write to, in target's address space
+ // connection_index: 0 for SystemBus, [1, NUM_DIRECT_CONNECTIONS] for
+ // direct connection payload: value to write
+ // Response:
+ // address: duration of transaction in us
+ // Otherwise identical to the request message.
+ TIMESYNC = 3,
+ // Socket: forward only
+ // Request:
+ // data_length: ignored
+ // address: ignored
+ // connection_index: ignored
+ // Response:
+ // payload: current target virtual time in microseconds
+ // Otherwise identical to the request message.
+ GPIOWRITE = 4,
+ // Socket: forward, backward
+ // Request:
+ // data_length: ignored
+ // address: ignored
+ // connection_index: ignored
+ // payload: state of GPIO bitfield
+ // Response:
+ // Identical to the request message.
+ RESET = 5,
+ // Socket: forward
+ // Request:
+ // data_length: ignored
+ // address: ignored
+ // connection_index: ignored
+ // payload: ignored
+ // Response:
+ // Identical to the request message.
+};
+
+#pragma pack(push, 1)
+struct renode_message {
+ renode_action action;
+ uint8_t data_length;
+ uint8_t connection_index;
+ uint64_t address;
+ uint64_t payload;
+};
+#pragma pack(pop)
+
+// ================================================================================
+// > Debug printing
+// ================================================================================
+
+static void print_renode_message(renode_message *message) {
+ if (message->action == TIMESYNC)
+ return;
+ uint64_t thread_id = 0;
+ { // Get a cross-platform thread identifier
+ std::hash<std::thread::id> hasher;
+ thread_id = hasher(std::this_thread::get_id());
+ }
+ printf("[0x%08lX][RENODE MESSAGE] Action: ", thread_id);
+ switch (message->action) {
+ case INIT:
+ printf("INIT");
+ break;
+ case READ:
+ printf("READ");
+ break;
+ case WRITE:
+ printf("WRITE");
+ break;
+ case TIMESYNC:
+ printf("TIMESYNC");
+ break;
+ case GPIOWRITE:
+ printf("GPIOWRITE");
+ break;
+ case RESET:
+ printf("RESET");
+ break;
+ default:
+ printf("INVALID");
+ }
+ printf(" | Address: 0x%08lX", message->address);
+ printf(" | Payload: 0x%08lX", message->payload);
+ printf(" | ConnIdx: %u\n", message->connection_index);
+}
+
+static void print_transaction_status(tlm::tlm_generic_payload *payload) {
+ tlm::tlm_response_status status = payload->get_response_status();
+ std::string response_string = payload->get_response_string();
+ printf("Renode transport status: %s\n", response_string.c_str());
+}
+
+// ================================================================================
+// > Renode Bridge SystemC module
+// ================================================================================
+
+static void initialize_payload(tlm::tlm_generic_payload *payload,
+ const renode_message *message, uint8_t *data) {
+ tlm::tlm_command command = tlm::TLM_IGNORE_COMMAND;
+ switch (message->action) {
+ case WRITE:
+ command = tlm::TLM_WRITE_COMMAND;
+ break;
+ case READ:
+ command = tlm::TLM_READ_COMMAND;
+ break;
+ default:
+ assert(!"Only WRITE and READ messages should initialize TLM payload");
+ }
+
+ payload->set_command(command);
+ // Right now the address visible to SystemC is directly the offset
+ // from Renode; i. e. if we write to address 0x9000100 and the peripheral
+ // address is 0x9000000, then address in SystemC will be 0x100.
+ payload->set_address(message->address);
+ payload->set_data_ptr(data);
+ payload->set_data_length(message->data_length);
+ payload->set_byte_enable_ptr(nullptr);
+ payload->set_byte_enable_length(0);
+ payload->set_streaming_width(message->data_length);
+ payload->set_dmi_allowed(false);
+ payload->set_response_status(tlm::TLM_INCOMPLETE_RESPONSE);
+}
+
+static bool initialize_connection(CTCPClient *connection,
+ renode_message *message,
+ int64_t *out_max_desync_us) {
+ // Receive INIT message from Renode and use it to setup connection, e. g.
+ // time synchronization period.
+ // This is done during SystemC elaboration, once per lifetime of the module.
+ int nread = connection->Receive((char *)message, sizeof(renode_message));
+ if (nread <= 0) {
+ return false;
+ }
+
+#ifdef VERBOSE
+ print_renode_message(message);
+#endif
+
+ if (message->action != renode_action::INIT) {
+ fprintf(stderr, "Renode bridge connection error: missing INIT action.\n");
+ return false;
+ }
+ *out_max_desync_us = static_cast<int64_t>(message->payload);
+
+ // Acknowledge initialization is done.
+ connection->Send((char *)message, sizeof(renode_message));
+#ifdef VERBOSE
+ printf("Connection to Renode initialized with timesync period %lu us.\n",
+ *out_max_desync_us);
+#endif
+ return true;
+}
+
+static uint64_t sc_time_to_us(sc_core::sc_time time) {
+ // Converts sc_time to microseconds count.
+ return static_cast<int64_t>(time.to_seconds() * 1000000.0);
+}
+
+static uint64_t
+perform_transaction(renode_bridge::renode_bus_initiator_socket &socket,
+ tlm::tlm_generic_payload *payload) {
+ sc_core::sc_time delay = sc_core::SC_ZERO_TIME;
+ socket->b_transport(*payload, delay);
+#ifdef VERBOSE
+ print_transaction_status(payload);
+#endif
+ return sc_time_to_us(delay);
+}
+
+static void terminate_simulation(int exitstatus) {
+ sc_core::sc_stop();
+ exit(exitstatus);
+}
+
+static void connect_with_retry(CTCPClient* socket, const char* address, const char* port) {
+ constexpr uint32_t max_retry_s = 10;
+ constexpr uint32_t retry_interval_s = 2;
+
+ uint32_t retry_s = 0;
+ while (!socket->Connect(address, port)) {
+ fprintf(stderr, "Failed to connect to Renode, retrying in %us...\n", retry_interval_s);
+ std::this_thread::sleep_for(std::chrono::seconds(retry_interval_s));
+ retry_s += retry_interval_s;
+ if(retry_s >= max_retry_s) {
+ fprintf(stderr, "Maximum timeout reached. Failed to initialize Renode connection. Aborting.\n");
+ terminate_simulation(1);
+ }
+ }
+}
+
+SC_HAS_PROCESS(renode_bridge);
+renode_bridge::renode_bridge(sc_core::sc_module_name name, const char *address,
+ const char *port)
+ : sc_module(name), initiator_socket("initiator_socket") {
+ SC_THREAD(forward_loop);
+ SC_THREAD(on_port_gpio);
+ for (int i = 0; i < NUM_GPIO; ++i) {
+ sensitive << gpio_ports_in[i];
+ }
+
+ bus_target_fw_handler.initialize(this, 0);
+
+ target_socket.bind(bus_target_fw_handler.socket);
+ for (int i = 0; i < NUM_DIRECT_CONNECTIONS; ++i) {
+ dc_initiators[i].initialize(this);
+ dc_targets[i].initialize(this, i + 1);
+ direct_connection_targets[i].bind(dc_targets[i]);
+ direct_connection_initiators[i].bind(dc_initiators[i]);
+ }
+
+ bus_initiator_bw_handler.initialize(this);
+ initiator_socket.bind(bus_initiator_bw_handler);
+
+ payload.reset(new tlm::tlm_generic_payload());
+
+ forward_connection.reset(new CTCPClient(NULL, ASocket::NO_FLAGS));
+ connect_with_retry(forward_connection.get(), address, port);
+
+ backward_connection.reset(new CTCPClient(NULL, ASocket::NO_FLAGS));
+ connect_with_retry(backward_connection.get(), address, port);
+}
+
+renode_bridge::~renode_bridge() {
+ forward_connection->Disconnect();
+ backward_connection->Disconnect();
+}
+
+void renode_bridge::forward_loop() {
+ // Processing of requests initiated by Renode.
+ uint8_t data[8] = {};
+
+ renode_message message;
+
+ int64_t max_desync_us;
+ if (!initialize_connection(forward_connection.get(), &message,
+ &max_desync_us)) {
+ fprintf(stderr, "Failed to initialize Renode connection. Aborting.\n");
+ terminate_simulation(1);
+ return;
+ }
+
+ while (true) {
+ memset(data, 0, sizeof(data));
+
+ int nread =
+ forward_connection->Receive((char *)&message, sizeof(renode_message));
+ if (nread <= 0) {
+#ifdef VERBOSE
+ printf("Connection to Renode closed.\n");
+#endif
+ break;
+ }
+
+#ifdef VERBOSE
+ print_renode_message(&message);
+#endif
+
+ // Choose the appropriate initiator socket to initiate the transaction with.
+ renode_bus_initiator_socket *initiator_socket = nullptr;
+ if (message.connection_index > NUM_DIRECT_CONNECTIONS) {
+ fprintf(stderr,
+ "Invalid connection_index %u, exceeds available number of direct "
+ "connections (%u)\n",
+ message.connection_index, NUM_DIRECT_CONNECTIONS);
+ return;
+ }
+
+ if (message.connection_index == 0) {
+ initiator_socket = &this->initiator_socket;
+ } else {
+ initiator_socket =
+ &this->direct_connection_initiators[message.connection_index - 1];
+ }
+
+ switch (message.action) {
+ case renode_action::WRITE: {
+ initialize_payload(payload.get(), &message, data);
+
+ *((uint64_t *)data) = message.payload;
+
+ uint64_t delay = perform_transaction(*initiator_socket, payload.get());
+
+ // NOTE: address field is re-used here to pass timing information.
+ message.address = delay;
+ forward_connection->Send((char *)&message, sizeof(renode_message));
+
+ wait(sc_core::SC_ZERO_TIME);
+ } break;
+ case renode_action::READ: {
+ initialize_payload(payload.get(), &message, data);
+
+ uint64_t delay = perform_transaction(*initiator_socket, payload.get());
+
+ // NOTE: address field is re-used here to pass timing information.
+ message.address = delay;
+ message.payload = *((uint64_t *)data);
+ forward_connection->Send((char *)&message, sizeof(renode_message));
+ wait(sc_core::SC_ZERO_TIME);
+ } break;
+ case renode_action::TIMESYNC: {
+ // Renode drives the simulation time. This module never leaves the delta
+ // cycle loop until a TIMESYNC with future time is received. It then waits
+ // for the time difference between current virtual time and time from
+ // TIMESYNC, allowing the SystemC simulation to progress in time. This is
+ // effectively a synchronization barrier.
+ int64_t systemc_time_us = sc_time_to_us(sc_core::sc_time_stamp());
+ int64_t renode_time_us = (int64_t)message.payload;
+
+ int64_t dt = renode_time_us - systemc_time_us;
+ message.payload = systemc_time_us;
+ if (dt > max_desync_us) {
+ wait(dt, sc_core::SC_US);
+ }
+ message.payload = sc_time_to_us(sc_core::sc_time_stamp());
+ forward_connection->Send((char *)&message, sizeof(renode_message));
+ } break;
+ case renode_action::GPIOWRITE: {
+ for (int i = 0; i < NUM_GPIO; ++i) {
+ sc_core::sc_interface *interface = gpio_ports_out[i].get_interface();
+ if (interface != nullptr) {
+ gpio_ports_out[i]->write((message.payload & (1 << i)) != 0);
+ }
+ }
+ forward_connection->Send((char *)&message, sizeof(renode_message));
+ } break;
+ case renode_action::INIT: {
+ terminate_simulation(0);
+ } break;
+ case renode_action::RESET: {
+ sc_core::sc_interface *interface = reset.get_interface();
+ if (interface != nullptr) {
+ reset->write(true);
+ }
+ forward_connection->Send((char *)&message, sizeof(renode_message));
+ } break;
+ default:
+ fprintf(stderr, "Malformed message received from Renode - terminating simulation.\n");
+ terminate_simulation(1);
+ }
+ }
+}
+
+void renode_bridge::on_port_gpio() {
+ while (true) {
+ // Wait for a change in any of the GPIO ports.
+ wait();
+
+ uint64_t gpio_state = 0;
+ for (int i = 0; i < NUM_GPIO; ++i) {
+ sc_core::sc_interface *interface = gpio_ports_in[i].get_interface();
+ if (interface != nullptr) {
+ if (gpio_ports_in[i]->read()) {
+ gpio_state |= (1ull << i);
+ } else {
+ gpio_state &= ~(1ull << i);
+ }
+ }
+ }
+
+ renode_message message = {};
+ message.action = renode_action::GPIOWRITE;
+ message.payload = gpio_state;
+
+ backward_connection->Send((char *)&message, sizeof(renode_message));
+ // Response is ignored.
+ backward_connection->Receive((char *)&message, sizeof(renode_message));
+ }
+}
+
+void renode_bridge::service_backward_request(tlm::tlm_generic_payload &payload,
+ uint8_t connection_idx,
+ sc_core::sc_time &delay) {
+ renode_message message = {};
+ message.address = payload.get_address();
+ message.data_length = payload.get_data_length();
+ message.connection_index = connection_idx;
+
+ if (payload.is_read()) {
+ message.action = renode_action::READ;
+ } else if (payload.is_write()) {
+ message.action = renode_action::WRITE;
+ memcpy(&message.payload, payload.get_data_ptr(), message.data_length);
+ }
+
+ backward_connection->Send((char *)&message, sizeof(renode_message));
+ backward_connection->Receive((char *)&message, sizeof(renode_message));
+
+ if (payload.is_read()) {
+ memcpy(payload.get_data_ptr(), &message.payload, message.data_length);
+ }
+
+ payload.set_response_status(tlm::TLM_OK_RESPONSE);
+}
+
+// ================================================================================
+// target_fw_handler
+// ================================================================================
+
+void renode_bridge::target_fw_handler::initialize(
+ renode_bridge *renode_bridge, uint8_t conn_idx) {
+ bridge = renode_bridge;
+ connection_idx = conn_idx;
+ socket.bind(*this);
+}
+
+void renode_bridge::target_fw_handler::b_transport(
+ tlm::tlm_generic_payload &payload, sc_core::sc_time &delay) {
+ bridge->service_backward_request(payload, connection_idx, delay);
+}
+
+tlm::tlm_sync_enum
+renode_bridge::target_fw_handler::nb_transport_fw(
+ tlm::tlm_generic_payload &trans, tlm::tlm_phase &phase,
+ sc_core::sc_time &t) {
+ bridge->service_backward_request(trans, connection_idx, t);
+ return tlm::TLM_COMPLETED;
+}
+
+tlm::tlm_sync_enum
+renode_bridge::target_fw_handler::nb_transport_bw(
+ tlm::tlm_generic_payload &, tlm::tlm_phase &, sc_core::sc_time &) {
+ fprintf(stderr, "[ERROR] nb_transport_bw not implemented for "
+ "target_fw_handler.\n");
+ return tlm::TLM_COMPLETED;
+}
+
+void renode_bridge::target_fw_handler::invalidate_direct_mem_ptr(
+ sc_dt::uint64, sc_dt::uint64) {
+ fprintf(stderr, "[ERROR] invalidate_direct_mem_ptr not implemented for "
+ "target_fw_handler.\n");
+}
+
+bool renode_bridge::target_fw_handler::get_direct_mem_ptr(
+ tlm::tlm_generic_payload &trans, tlm::tlm_dmi &dmi_data) {
+ fprintf(stderr, "[ERROR] get_direct_mem_ptr not implemented for "
+ "target_fw_handler.\n");
+ return false;
+}
+
+unsigned int renode_bridge::target_fw_handler::transport_dbg(
+ tlm::tlm_generic_payload &trans) {
+ fprintf(stderr, "[ERROR] transport_dbg not implemented for "
+ "target_fw_handler.\n");
+ return 0;
+}
+
+// ================================================================================
+// initiator_bw_handler
+// ================================================================================
+
+void renode_bridge::initiator_bw_handler::initialize(
+ renode_bridge *renode_bridge) {
+ bridge = renode_bridge;
+}
+
+tlm::tlm_sync_enum renode_bridge::initiator_bw_handler::nb_transport_bw(
+ tlm::tlm_generic_payload &trans, tlm::tlm_phase &phase,
+ sc_core::sc_time &t) {
+ fprintf(stderr, "[ERROR] nb_transport_bw not implemented for "
+ "initiator_bw_handler- this should never be called, "
+ "as Renode integration only uses b_transfer.\n");
+ return tlm::TLM_COMPLETED;
+}
+
+void renode_bridge::initiator_bw_handler::invalidate_direct_mem_ptr(
+ sc_dt::uint64 start_range, sc_dt::uint64 end_range) {
+ fprintf(stderr, "[ERROR] invalidate_direct_mem_ptr not implemented for "
+ "initiator_bw_handler - this should never be called, "
+ "as Renode integration only uses b_transfer.\n");
+}
+
+// ================================================================================
diff --git a/src/Plugins/SystemCPlugin/SystemCPlugin.csproj b/src/Plugins/SystemCPlugin/SystemCPlugin.csproj
new file mode 100644
index 0000000..9501f41
--- /dev/null
+++ b/src/Plugins/SystemCPlugin/SystemCPlugin.csproj
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{8882BDAF-FE52-4A39-B1F2-84C3F061D5A7}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <RootNamespace>Antmicro.Renode.Plugins.SystemCPlugin</RootNamespace>
+ <AssemblyName>SystemCPlugin</AssemblyName>
+ <TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
+ <PropertiesLocation>..\..\..\output\properties.csproj</PropertiesLocation>
+ <ProductVersion>8.0.30703</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ </PropertyGroup>
+ <Import Project="$(PropertiesLocation)" />
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug</OutputPath>
+ <DefineConstants>DEBUG;$(DefineConstants)</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <ConsolePause>false</ConsolePause>
+ <LangVersion>7.2</LangVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>full</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release</OutputPath>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <ConsolePause>false</ConsolePause>
+ <LangVersion>7.2</LangVersion>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.ServiceModel" />
+ <Reference Include="System.Core" />
+ <Reference Include="Mono.Posix" Condition=" $(CurrentPlatform) != 'Windows'" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Peripheral\SystemCPeripheral.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\Infrastructure\src\Emulator\Main\Emulator.csproj">
+ <Project>{2901AECB-A54F-4FD8-9AC1-033D86DC7257}</Project>
+ <Name>Emulator</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\Infrastructure\src\Emulator\Peripherals\Peripherals.csproj">
+ <Project>{66400796-0C5B-4386-A859-50A2AC3F3DB7}</Project>
+ <Name>Peripherals</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <Folder Include="Peripheral\" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+ <ProjectExtensions>
+ <MonoDevelop>
+ <Properties>
+ <Policies>
+ <DotNetNamingPolicy DirectoryNamespaceAssociation="PrefixedHierarchical" ResourceNamePolicy="FileName" />
+ <TextStylePolicy FileWidth="120" TabWidth="4" IndentWidth="4" RemoveTrailingWhitespace="True" TabsToSpaces="True" NoTabsAfterNonTabs="True" EolMarker="Unix" scope="text/x-csharp" />
+ <CSharpFormattingPolicy IndentBlock="True" IndentBraces="False" IndentSwitchSection="True" IndentSwitchCaseSection="True" LabelPositioning="OneLess" NewLinesForBracesInTypes="True" NewLinesForBracesInMethods="True" NewLinesForBracesInProperties="True" NewLinesForBracesInAccessors="True" NewLinesForBracesInAnonymousMethods="True" NewLinesForBracesInControlBlocks="True" NewLinesForBracesInAnonymousTypes="True" NewLinesForBracesInObjectCollectionArrayInitializers="True" NewLinesForBracesInLambdaExpressionBody="True" NewLineForElse="True" NewLineForCatch="True" NewLineForFinally="True" NewLineForMembersInObjectInit="True" NewLineForMembersInAnonymousTypes="True" NewLineForClausesInQuery="True" SpacingAfterMethodDeclarationName="False" SpaceWithinMethodDeclarationParenthesis="False" SpaceBetweenEmptyMethodDeclarationParentheses="False" SpaceAfterMethodCallName="False" SpaceWithinMethodCallParentheses="False" SpaceBetweenEmptyMethodCallParentheses="False" SpaceWithinExpressionParentheses="False" SpaceWithinCastParentheses="False" SpaceWithinOtherParentheses="False" SpaceAfterCast="False" SpacesIgnoreAroundVariableDeclaration="False" SpaceBeforeOpenSquareBracket="False" SpaceBetweenEmptySquareBrackets="False" SpaceWithinSquareBrackets="False" SpaceAfterColonInBaseTypeDeclaration="True" SpaceAfterComma="True" SpaceAfterDot="False" SpaceAfterSemicolonsInForStatement="True" SpaceBeforeColonInBaseTypeDeclaration="True" SpaceBeforeComma="False" SpaceBeforeDot="False" SpaceBeforeSemicolonsInForStatement="False" SpacingAroundBinaryOperator="Single" WrappingPreserveSingleLine="True" WrappingKeepStatementsOnSingleLine="True" PlaceSystemDirectiveFirst="True" SpaceAfterControlFlowStatementKeyword="False" scope="text/x-csharp" />
+ <TextStylePolicy FileWidth="120" TabWidth="4" IndentWidth="4" RemoveTrailingWhitespace="True" TabsToSpaces="True" NoTabsAfterNonTabs="True" EolMarker="Unix" scope="text/plain" />
+ <StandardHeader IncludeInNewFiles="True" Text="
Copyright (c) 2010-${Year} Antmicro

 This file is licensed under the MIT License.
 Full license text is available in 'licenses/MIT.txt'.
" />
+ </Policies>
+ </Properties>
+ </MonoDevelop>
+ </ProjectExtensions>
+</Project>
diff --git a/src/Plugins/SystemCPlugin/SystemCPlugin_NET.csproj b/src/Plugins/SystemCPlugin/SystemCPlugin_NET.csproj
new file mode 100644
index 0000000..47efd04
--- /dev/null
+++ b/src/Plugins/SystemCPlugin/SystemCPlugin_NET.csproj
@@ -0,0 +1,23 @@
+<Project DefaultTargets="Build" Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <TargetFrameworks Condition="$(OS) != 'Windows_NT'">net6.0</TargetFrameworks>
+ <TargetFrameworks Condition="$(OS) == 'Windows_NT'">net6.0-windows10.0.17763.0</TargetFrameworks>
+ <AssemblyName>SystemCPlugin</AssemblyName>
+ <PropertiesLocation>..\..\..\output\properties.csproj</PropertiesLocation>
+ <LangVersion>7.2</LangVersion>
+ </PropertyGroup>
+ <Import Project="$(PropertiesLocation)" />
+ <ItemGroup>
+ <PackageReference Include="System.ServiceModel.Duplex" Version="4.8.1" />
+ <PackageReference Include="System.ServiceModel.NetTcp" Version="4.8.1" />
+ <PackageReference Include="System.ServiceModel.Federation" Version="4.8.1" />
+ <PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" Condition=" $(OS) != 'Windows_NT'" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Remove="SystemCModule\**\*"/>
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\Infrastructure\src\Emulator\Main\Emulator_NET.csproj"/>
+ <ProjectReference Include="..\..\Infrastructure\src\Emulator\Peripherals\Peripherals_NET.csproj"/>
+ </ItemGroup>
+</Project>
diff --git a/src/Renode/Renode.csproj b/src/Renode/Renode.csproj
index a558c2a..17bcfa7 100644
--- a/src/Renode/Renode.csproj
+++ b/src/Renode/Renode.csproj
@@ -232,6 +232,10 @@
<Project>{887C6088-F483-466A-A671-06EEC42B8DC1}</Project>
<Name>TracePlugin</Name>
</ProjectReference>
+ <ProjectReference Include="..\Plugins\SystemCPlugin\SystemCPlugin.csproj">
+ <Project>{8882BDAF-FE52-4A39-B1F2-84C3F061D5A7}</Project>
+ <Name>SystemCPlugin</Name>
+ </ProjectReference>
<ProjectReference Include="..\Plugins\WiresharkPlugin\WiresharkPlugin.csproj">
<Project>{66A9995A-13AE-4454-88A6-29EB2D6F5988}</Project>
<Name>WiresharkPlugin</Name>
diff --git a/tools/packaging/common_copy_files.sh b/tools/packaging/common_copy_files.sh
index 762fb2a..f9fb0a1 100644
--- a/tools/packaging/common_copy_files.sh
+++ b/tools/packaging/common_copy_files.sh
@@ -18,6 +18,12 @@
cp -r $BASE/tools/sel4_extensions $DIR/tools
cp -r $BASE/tools/csv2resd $DIR/tools
cp -r $BASE/src/Plugins/VerilatorPlugin/VerilatorIntegrationLibrary $DIR/plugins
+cp -r $BASE/src/Plugins/SystemCPlugin/SystemCModule $DIR/plugins
+# For now, SystemCPlugin uses socket-cpp library from VerilatorIntegrationLibrary.
+# ln -f argument is quietly ignored in windows-package environment, so instead of updating remove the link
+# and create it again.
+rm -rf $DIR/plugins/SystemCModule/lib/socket-cpp
+ln -s ../../VerilatorIntegrationLibrary/libs/socket-cpp $DIR/plugins/SystemCModule/lib/socket-cpp
#copy the test instrastructure and update the paths
cp -r $BASE/tests/metrics-analyzer $DIR/tests/metrics-analyzer
diff --git a/tools/packaging/common_make_linux_portable.sh b/tools/packaging/common_make_linux_portable.sh
index 4ced2c2..344448f 100755
--- a/tools/packaging/common_make_linux_portable.sh
+++ b/tools/packaging/common_make_linux_portable.sh
@@ -15,6 +15,9 @@
cp -r $RENODE_ROOT_DIR/tools/sel4_extensions $DESTINATION/tools
cp -r $RENODE_ROOT_DIR/tools/csv2resd $DESTINATION/tools
cp -r $RENODE_ROOT_DIR/src/Plugins/VerilatorPlugin/VerilatorIntegrationLibrary $DESTINATION/plugins
+cp -r $RENODE_ROOT_DIR/src/Plugins/SystemCPlugin/SystemCModule $DESTINATION/plugins
+# For now, SystemCPlugin uses socket-cpp library from VerilatorIntegrationLibrary.
+ln -fs ../../VerilatorIntegrationLibrary/libs/socket-cpp $DESTINATION/plugins/SystemCModule/lib/socket-cpp
sed -i '/nunit/d' $DESTINATION/tests/run_tests.py
sed -i 's#ROOT_PATH/tests/run_tests.py#TEST_PATH/run_tests.py#' $DESTINATION/renode-test