blob: 89639213e1db4ac1bce2b233d1465d668eff7136 [file]
//
// Copyright (c) 2010-2024 Antmicro
//
// This file is licensed under the MIT License.
// Full license text is available in 'licenses/MIT.txt'.
//
using System;
using System.Threading;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Antmicro.Renode.Core;
using Antmicro.Renode.Exceptions;
using Antmicro.Renode.Logging;
using Antmicro.Renode.Peripherals.Bus;
using Antmicro.Renode.Peripherals.Timers;
using Antmicro.Renode.Plugins.CoSimulationPlugin.Connection;
using Antmicro.Renode.Plugins.CoSimulationPlugin.Connection.Protocols;
using Range = Antmicro.Renode.Core.Range;
namespace Antmicro.Renode.Peripherals.CoSimulated
{
public class CoSimulatedPeripheral : ICoSimulationConnectible, IQuadWordPeripheral, IDoubleWordPeripheral, IWordPeripheral, IBytePeripheral, IBusPeripheral, IDisposable, INumberedGPIOOutput, IGPIOReceiver, IAbsoluteAddressAware
{
public 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)
{
UseAbsoluteAddress = useAbsoluteAddress;
this.maxWidth = maxWidth;
this.renodeToCosimSignalsOffset = renodeToCosimSignalsOffset;
this.cosimToRenodeSignalRange = cosimToRenodeSignalRange;
if(createConnection)
{
connection = new CoSimulationConnection(machine, "cosimulation_connection", frequency,
simulationFilePathLinux, simulationFilePathWindows, simulationFilePathMacOS,
simulationContextLinux, simulationContextWindows, simulationContextMacOS,
limitBuffer, timeout, address);
connection.AttachTo(this);
}
var innerGPIOConnections = new Dictionary<int, IGPIO>();
if(this.cosimToRenodeSignalRange.HasValue)
{
for(int i = 0; i < (int)this.cosimToRenodeSignalRange.Value.Size; i++)
{
innerGPIOConnections[i] = new GPIO();
}
}
Connections = new ReadOnlyDictionary<int, IGPIO>(innerGPIOConnections);
}
public void Reset()
{
connection?.Reset();
}
public void OnGPIO(int number, bool value)
{
// Connection can be null here because OnGPIO is called during initialization
// for each input connection
connection?.SendGPIO((int)renodeToCosimSignalsOffset + number, value);
}
public virtual void OnConnectionAttached(CoSimulationConnection connection)
{
this.connection = connection;
if(cosimToRenodeSignalRange.HasValue)
{
this.connection.RegisterOnGPIOReceive(ReceiveGPIOChange, cosimToRenodeSignalRange.Value);
}
}
public virtual void OnConnectionDetached(CoSimulationConnection connection)
{
if(cosimToRenodeSignalRange.HasValue)
{
this.connection.UnregisterOnGPIOReceive(cosimToRenodeSignalRange.Value);
}
this.connection = null;
}
public byte ReadByte(long offset)
{
offset = UseAbsoluteAddress ? (long)absoluteAddress : offset;
if(!VerifyLength(8, offset))
{
return 0;
}
return (byte)connection.Read(ActionType.ReadFromBusByte, offset);
}
public ushort ReadWord(long offset)
{
offset = UseAbsoluteAddress ? (long)absoluteAddress : offset;
if(!VerifyLength(16, offset))
{
return 0;
}
return (ushort)connection.Read(ActionType.ReadFromBusWord, offset);
}
public uint ReadDoubleWord(long offset)
{
offset = UseAbsoluteAddress ? (long)absoluteAddress : offset;
if(!VerifyLength(32, offset))
{
return 0;
}
return (uint)connection.Read(ActionType.ReadFromBusDoubleWord, offset);
}
public ulong ReadQuadWord(long offset)
{
offset = UseAbsoluteAddress ? (long)absoluteAddress : offset;
if(!VerifyLength(64, offset))
{
return 0;
}
return connection.Read(ActionType.ReadFromBusQuadWord, offset);
}
public void WriteByte(long offset, byte value)
{
offset = UseAbsoluteAddress ? (long)absoluteAddress : offset;
if(VerifyLength(8, offset, value))
{
connection.Write(ActionType.WriteToBusByte, offset, value);
}
}
public void WriteWord(long offset, ushort value)
{
offset = UseAbsoluteAddress ? (long)absoluteAddress : offset;
if(VerifyLength(16, offset, value))
{
connection.Write(ActionType.WriteToBusWord, offset, value);
}
}
public void WriteDoubleWord(long offset, uint value)
{
offset = UseAbsoluteAddress ? (long)absoluteAddress : offset;
if(VerifyLength(32, offset, value))
{
connection.Write(ActionType.WriteToBusDoubleWord, offset, value);
}
}
public void WriteQuadWord(long offset, ulong value)
{
offset = UseAbsoluteAddress ? (long)absoluteAddress : offset;
if(VerifyLength(64, offset, value))
{
connection.Write(ActionType.WriteToBusQuadWord, offset, value);
}
}
public virtual void ReceiveGPIOChange(int coSimNumber, bool value)
{
if(!cosimToRenodeSignalRange.HasValue)
{
this.Log(LogLevel.Warning, $"Received GPIO change from co-simulation, but no cosimToRenodeSignalRange is defined.");
return;
}
var localNumber = coSimNumber - (int)cosimToRenodeSignalRange.Value.StartAddress;
if (!Connections.TryGetValue(localNumber, out var gpioConnection))
{
this.Log(LogLevel.Warning, "Unhandled interrupt: '{0}'", localNumber);
return;
}
gpioConnection.Set(value);
}
public void Dispose()
{
connection?.DetachFrom(this);
}
public IReadOnlyDictionary<int, IGPIO> Connections { get; }
public string ConnectionParameters => connection?.ConnectionParameters ?? "";
public void Connect()
{
AssureIsConnected();
connection.Connect();
}
public void SetAbsoluteAddress(ulong address)
{
absoluteAddress = address;
}
public string SimulationContextLinux
{
get => connection.SimulationContextLinux;
set
{
AssureIsConnected();
connection.SimulationContextLinux = value;
}
}
public string SimulationContextWindows
{
get => connection.SimulationContextWindows;
set
{
AssureIsConnected();
connection.SimulationContextWindows = value;
}
}
public string SimulationContextMacOS
{
get => connection.SimulationContextMacOS;
set
{
AssureIsConnected();
connection.SimulationContextMacOS = value;
}
}
public string SimulationContext
{
get => connection.SimulationContext;
set
{
AssureIsConnected();
connection.SimulationContext = value;
}
}
public string SimulationFilePathLinux
{
get => connection.SimulationFilePathLinux;
set
{
AssureIsConnected();
connection.SimulationFilePathLinux = value;
}
}
public string SimulationFilePathWindows
{
get => connection.SimulationFilePathWindows;
set
{
AssureIsConnected();
connection.SimulationFilePathWindows = value;
}
}
public string SimulationFilePathMacOS
{
get => connection.SimulationFilePathMacOS;
set
{
AssureIsConnected();
connection.SimulationFilePathMacOS = value;
}
}
// The following constant should be in sync with a time unit defined in the `renode` SystemVerilog module.
// It allows using simulation time instead of a number of clock ticks.
public const long VerilogTimeunitFrequency = 1000000000;
public bool UseAbsoluteAddress { get; set; }
private bool VerifyLength(int length, long offset, ulong? value = null)
{
if(length > maxWidth)
{
this.Log(LogLevel.Warning, "Trying to {0} {1} bits at offset 0x{2:X}{3}, but maximum length is {4}",
value.HasValue ? "write" : "read",
length,
offset,
value.HasValue ? $" (value 0x{value})" : String.Empty,
maxWidth
);
return false;
}
return true;
}
private void AssureIsConnected(string message = null)
{
if(connection == null)
{
throw new RecoverableException("CoSimulatedPeripheral is not attached to a CoSimulationConnection.");
}
}
protected CoSimulationConnection connection;
protected const ulong LimitBuffer = 1000000;
protected const int DefaultTimeout = 3000;
readonly protected Range? cosimToRenodeSignalRange;
private int maxWidth;
private ulong renodeToCosimSignalsOffset;
private ulong absoluteAddress = 0;
private const string LimitTimerName = "CoSimulationIntegrationClock";
}
}