// // 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.Linq; using System.Threading; using System.Runtime.InteropServices; using Antmicro.Renode.Core; using Antmicro.Renode.Exceptions; using Antmicro.Renode.Logging; using Antmicro.Renode.Peripherals.CPU; using Antmicro.Renode.Peripherals.CFU; using Antmicro.Renode.Peripherals.Timers; using Antmicro.Renode.Plugins.CoSimulationPlugin.Connection; using Antmicro.Renode.Plugins.CoSimulationPlugin.Connection.Protocols; using Antmicro.Renode.Utilities; using Antmicro.Renode.Utilities.Binding; using Antmicro.Renode.Core.Structure; namespace Antmicro.Renode.Peripherals.CoSimulated { public class CoSimulatedCFU : ICFU, ICoSimulationConnectible, IDisposable { public CoSimulatedCFU(Machine machine, long frequency = 0, ulong limitBuffer = LimitBuffer, int timeout = DefaultTimeout, string simulationFilePathLinux = null, string simulationFilePathWindows = null, string simulationFilePathMacOS = null) { // Multiple CoSimulatedCFUs per CoSimulationConnection are currently not supported. RenodeToCosimIndex = 0; CosimToRenodeIndex = 0; connection = new CoSimulationConnection(machine, "cosimulation_connection", frequency, simulationFilePathLinux, simulationFilePathWindows, simulationFilePathMacOS, null, null, null, limitBuffer, timeout, null); connection.AttachTo(this); errorPointer = IntPtr.Zero; } public virtual void OnConnectionAttached(CoSimulationConnection connection) { } public virtual void OnConnectionDetached(CoSimulationConnection connection) { } public void Reset() { connection?.Reset(); } public void Dispose() { connection.Dispose(); executeBinder?.Dispose(); Marshal.FreeHGlobal(errorPointer); } 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; } } public string SimulationFilePath { get => connection.SimulationFilePath; set { AssureIsConnected(); if(String.IsNullOrWhiteSpace(value)) { this.Log(LogLevel.Warning, "SimulationFilePath not set!"); return; } else if(!String.IsNullOrWhiteSpace(SimulationFilePath)) { LogAndThrowRE("CoSimulatedCFU already connected, cannot change the file name!"); } else if(connectedCpu.Children.Any(child => child.Peripheral.SimulationFilePath == value)) { LogAndThrowRE("Another CFU already connected to provided library!"); } else { try { errorPointer = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(int))); executeBinder = new NativeBinder(this, value); connection.SimulationFilePath = value; } catch(Exception e) { LogAndThrowRE(e.Message); } } } } public ICPU ConnectedCpu { get { return connectedCpu; } set { if(ConnectedCpu != null) { LogAndThrowRE("CFU already connected to CPU, cannot change CPU!"); } else { connectedCpu = value as BaseRiscV; if(connectedCpu == null) { LogAndThrowRE("CFU is supported for RISCV-V CPUs only!"); } RegisterCFU(); } } } // This function is not used here but it is required to properly bind with libVtop.so // so it is left empty on purpose. [Export] private void HandleSenderMessage(IntPtr received) { } private void LogAndThrowRE(string info) { this.Log(LogLevel.Error, info); throw new RecoverableException(info); } private void RegisterCFU() { string opcodePattern = ""; int connectedCfus = connectedCpu.ChildCollection.Count; switch(connectedCfus) { case 1: opcodePattern = "FFFFFFFAAAAABBBBBIIICCCCC0001011"; break; case 2: opcodePattern = "FFFFFFFAAAAABBBBBIIICCCCC0101011"; break; case 3: opcodePattern = "FFFFFFFAAAAABBBBBIIICCCCC1001011"; break; case 4: opcodePattern = "FFFFFFFAAAAABBBBBIIICCCCC1101011"; break; default: this.LogAndThrowRE("Can't handle more than 4 CFUs!"); break; } connectedCpu.InstallCustomInstruction(pattern: opcodePattern, handler: opcode => HandleCustomInstruction(opcode)); this.Log(LogLevel.Noisy, "CFU {0} registered", connectedCfus); } private void HandleCustomInstruction(ulong opcode) { int rD = (int)BitHelper.GetValue(opcode, 7, 5); int rs1 = (int)BitHelper.GetValue(opcode, 15, 5); UInt32 rs1Value = Convert.ToUInt32(connectedCpu.GetRegister(rs1).RawValue); int rs2 = (int)BitHelper.GetValue(opcode, 20, 5); UInt32 rs2Value = Convert.ToUInt32(connectedCpu.GetRegister(rs2).RawValue); UInt32 funct3 = Convert.ToUInt32(BitHelper.GetValue(opcode, 12, 3)); UInt32 funct7 = Convert.ToUInt32(BitHelper.GetValue(opcode, 25, 7)); UInt32 functionID = (funct7 << 3) + funct3; UInt64 result = 0UL; result = execute(functionID, rs1Value, rs2Value, errorPointer); CfuStatus status = (CfuStatus)Marshal.ReadInt32(errorPointer); switch(status) { case CfuStatus.CfuOk: connectedCpu.SetRegister(rD, result); break; case CfuStatus.CfuFail: this.Log(LogLevel.Error, "CFU custom instruction error, opcode: 0x{0:x}, error: {1}", opcode, status); break; case CfuStatus.CfuTimeout: this.Log(LogLevel.Error, "CFU operation timeout, opcode: 0x{0:x}, error: {1}", opcode, status); break; default: this.Log(LogLevel.Error, "CFU unknown error, opcode: 0x{0:x}, error: {1}", opcode, status); break; } } private void AssureIsConnected(string message = null) { if(connection == null) { throw new RecoverableException("CoSimulatedPeripheral is not attached to a CoSimulationConnection."); } } public int RenodeToCosimIndex { get; } public int CosimToRenodeIndex { get; } protected const ulong LimitBuffer = 100000; protected const int DefaultTimeout = 3000; private NativeBinder executeBinder; private BaseRiscV connectedCpu; private IntPtr errorPointer; #pragma warning disable 649 [Import(UseExceptionWrapper = false)] private Func execute; #pragma warning restore 649 private enum CfuStatus { CfuOk = 0, CfuFail = 1, CfuTimeout = 2 } protected CoSimulationConnection connection; } }