// // Copyright (c) 2010-2024 Antmicro // // This file is licensed under the MIT License. // Full license text is available in 'licenses/MIT.txt'. // `timescale 1ns / 1ps package renode_pkg; typedef longint address_t; typedef longint data_t; typedef enum int { // The included file contains enumerators of the action type used by the Renode protocol. // The values must be in sync with ActionType from Renode (defined in C#). `include "renode_action_enumerators.svh" } action_e; const int no_peripheral_index = -1; typedef struct { action_e action; address_t address; data_t data; int peripheral_index; } message_t; typedef enum data_t { QuadWord = {64{1'b1}}, DoubleWord = {32'b0, {32{1'b1}}}, Word = {48'b0, {16{1'b1}}}, Byte = {56'b0, {8{1'b1}}} } valid_bits_e; typedef enum int { LogNoisy = -1, LogDebug = 0, LogInfo = 1, LogWarning = 2, LogError = 3 } log_level_e; import "DPI-C" function void renodeDPIConnect( int receiverPort, int senderPort, string address ); import "DPI-C" function void renodeDPIDisconnect(); import "DPI-C" function bit renodeDPIIsConnected(); import "DPI-C" function bit renodeDPILog( int logLevel, string data ); import "DPI-C" function bit renodeDPIReceive( output action_e action, output address_t address, output data_t data, output int peripheralIndex ); import "DPI-C" function bit renodeDPISend( action_e action, address_t address, data_t data, int peripheralIndex ); import "DPI-C" function bit renodeDPISendToAsync( action_e action, address_t address, data_t data, int peripheralIndex ); function static bit is_access_aligned(address_t address, valid_bits_e valid_bits); case(valid_bits) Byte: return 1; Word: return address % 2 == 0; DoubleWord: return address % 4 == 0; QuadWord: return address % 8 == 0; default: return 0; endcase endfunction function static integer valid_bits_to_transaction_width(valid_bits_e valid_bits); case (valid_bits) QuadWord: return 64; DoubleWord: return 32; Word: return 16; Byte: return 8; default: begin $error($sformatf("Cannot determine transaction width for valid_bits %d", valid_bits)); return 0; end endcase endfunction class renode_connection; semaphore exclusive_receive = new(1); function new(); $timeformat(0, 9, "s", 0); endfunction function void connect(int receiver_port, int sender_port, string address); renodeDPIConnect(receiver_port, sender_port, address); if(is_connected()) $display("Renode at %t: Connected using the socket based interface", $realtime); else $error("Renode at %t: Connection error", $realtime); endfunction function bit is_connected(); return renodeDPIIsConnected(); endfunction function void fatal_error(string message); string error_msg; error_msg = $sformatf("Renode at %t: Error! %s", $realtime, message); log(LogError, error_msg); disconnect(); $error(error_msg); $finish; endfunction function void handle_message(message_t message, output bit is_handled); is_handled = 1; case (message.action) renode_pkg::invalidAction: ; // Intentionally left blank renode_pkg::disconnect: handle_disconnect(); default: is_handled = 0; endcase endfunction function void log(log_level_e log_level, string message); `ifdef RENODE_DEBUG $display("Renode at %t logs: %s", $realtime, message); `endif if(!renodeDPILog(log_level, message)) begin $display("Renode at %t: Unable to send the log: %s", $realtime, message); end endfunction function void receive(output message_t message); bit is_received = try_receive(message); if (!is_received) fatal_error("Unable to receive a message."); endfunction function bit try_receive(output message_t message); bit is_received = renodeDPIReceive(message.action, message.address, message.data, message.peripheral_index); if(is_received) begin `ifdef RENODE_DEBUG log(LogDebug, $sformatf("Renode at %t: Received action %0s, address = 'h%h, data = 'h%h, peripheral index: %d", $realtime, message.action.name(), message.address, message.data, message.peripheral_index)); `endif end return is_received; endfunction function void send(message_t message); `ifdef RENODE_DEBUG log(LogDebug, $sformatf("Renode at %t: Sent action %0s, address = 'h%h, data = 'h%h, peripheral index: %d", $realtime, message.action.name(), message.address, message.data, message.peripheral_index)); `endif if (!renodeDPISend(message.action, message.address, message.data, message.peripheral_index)) fatal_error("Unexpected channel disconnection"); endfunction function void send_to_async_receiver(message_t message); `ifdef RENODE_DEBUG log(LogDebug, $sformatf("Renode at %t: Sent async action %0s, address = 'h%h, data = 'h%h, peripheral index: %d", $realtime, message.action.name(), message.address, message.data, message.peripheral_index)); `endif if (!renodeDPISendToAsync(message.action, message.address, message.data, message.peripheral_index)) fatal_error("Unexpected channel disconnection"); endfunction local function void disconnect(); renodeDPIDisconnect(); endfunction local function void handle_disconnect(); send(message_t'{ok, 0, 0, renode_pkg::no_peripheral_index}); disconnect(); `ifdef RENODE_DEBUG $display("Renode at %t: disconnected", $realtime); `endif endfunction endclass class bus_connection; event reset_assert_request; event reset_assert_response; event reset_deassert_request; event reset_deassert_response; event read_transaction_request; event read_transaction_response; address_t read_transaction_address; data_t read_transaction_data; valid_bits_e read_transaction_data_bits; bit read_transaction_is_error; event write_transaction_request; event write_transaction_response; address_t write_transaction_address; data_t write_transaction_data; valid_bits_e write_transaction_data_bits; bit write_transaction_is_error; // Passing a class by a reference isn't supported by Verilator. // Events are indirectly triggered by tasks. task reset_assert(); ->reset_assert_request; @(reset_assert_response); endtask task reset_assert_respond(); ->reset_assert_response; endtask task reset_deassert(); ->reset_deassert_request; @(reset_deassert_response); endtask task reset_deassert_respond(); ->reset_deassert_response; endtask task read(address_t address, valid_bits_e data_bits, output data_t data, output bit is_error); read_transaction_address = address; read_transaction_data_bits = data_bits; ->read_transaction_request; @(read_transaction_response) begin data = read_transaction_data; is_error = read_transaction_is_error; end endtask task read_respond(data_t data, bit is_error); read_transaction_data = data; read_transaction_is_error = is_error; ->read_transaction_response; endtask task write(address_t address, valid_bits_e data_bits, data_t data, output bit is_error); write_transaction_address = address; write_transaction_data_bits = data_bits; write_transaction_data = data; ->write_transaction_request; @(write_transaction_response) begin is_error = write_transaction_is_error; end endtask task write_respond(bit is_error); write_transaction_is_error = is_error; ->write_transaction_response; endtask endclass // It's required to pass the whole instance to modules. // Passing single property triggers a null pointer dereference in Verilator. class renode_runtime; const string ReceiverPortArgName = "RENODE_RECEIVER_PORT"; const string SenderPortArgName = "RENODE_SENDER_PORT"; const string AddressArgName = "RENODE_ADDRESS"; renode_connection connection = new(); // Initialized by the Renode module bus_connection controllers[]; bus_connection peripherals[]; function void connect_plus_args(); int receiver_port, sender_port; string address; if(!$value$plusargs({ReceiverPortArgName, "=%d"}, receiver_port) || !$value$plusargs({SenderPortArgName, "=%d"}, sender_port) || !$value$plusargs({AddressArgName, "=%s"}, address)) begin $error("Please specify the +%s, +%s and +%s arguments in the command that invokes the simulation", ReceiverPortArgName, SenderPortArgName, AddressArgName); end else begin connection.connect(receiver_port, sender_port, address); end endfunction function bit is_connected(); return connection.is_connected(); endfunction endclass endpackage