| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| interface sw_logger_if #( |
| // width of the data bus |
| parameter int unsigned AddrDataWidth = 32 |
| ) ( |
| input logic clk_i, |
| input logic rst_ni, |
| input logic wr_valid, // Qualified write access. |
| input logic [AddrDataWidth-1:0] addr, // Incoming addr. |
| input logic [AddrDataWidth-1:0] data // Incoming data. |
| ); |
| |
| `ifdef UVM |
| import uvm_pkg::*; |
| `endif |
| import str_utils_pkg::*; |
| |
| // macro includes |
| `include "dv_macros.svh" |
| |
| // Address to which the SW logs are written to. This is set by the testbench. |
| logic [AddrDataWidth-1:0] sw_log_addr; |
| |
| // Validate the incoming write address. |
| logic data_valid; |
| assign data_valid = wr_valid && (addr == sw_log_addr); |
| |
| // Enable signal to turn the logging on/off at runtime. |
| bit enable = 1'b1; |
| |
| // types |
| // Log & rodata database files for parsing, associated with the sw_name. |
| string sw_log_db_files[string]; |
| string sw_rodata_db_files[string]; |
| |
| // typedef addr / data values |
| typedef bit [AddrDataWidth-1:0] addr_data_t; |
| |
| typedef enum { |
| LogSeverityInfo, |
| LogSeverityWarning, |
| LogSeverityError, |
| LogSeverityFatal |
| } log_severity_e; |
| |
| typedef enum { |
| LogVerbosityNone, |
| LogVerbosityLow, |
| LogVerbosityMedium, |
| LogVerbosityHigh, |
| LogVerbosityFull, |
| LogVerbosityDebug |
| } log_verbosity_e; |
| |
| // Max number of args as format specifiers supported. |
| localparam int unsigned MAX_ARGS = 32; |
| |
| // Use 1024 byte packed array for storing args (integral data or string). |
| typedef bit [1024*8-1:0] arg_t; |
| |
| // Holds the log data structure. |
| typedef struct { |
| string name; // Name of the SW image. |
| log_severity_e severity; // There are 4 - info, warining, error and fatal. |
| log_verbosity_e verbosity; // Verbosity of info logs - currently unsupported. |
| string file; // Name of the C file invoking the log. |
| int line; // Line no in the C file from where the log originated. |
| int nargs; // Number of args provided to the format string. |
| arg_t arg[]; // Actual arg values |
| bit [MAX_ARGS-1:0] str_arg_idx; // Indicates which arg indices are string (%s). |
| string format; // Format string. |
| } sw_log_t; |
| |
| // bit to enable writing the logs to a separate file (disabled by default) |
| bit write_sw_logs_to_file = 1'b0; |
| string sw_logs_output_file; |
| int sw_logs_output_fd = 0; |
| |
| // signal indicating all initializations are done (this is set by calling ready() function) |
| bit _ready; |
| |
| // hash of log with sw_name and addr keys |
| sw_log_t sw_logs[string][addr_data_t]; |
| |
| // hash of rodata with sw_name and addr keys |
| string sw_rodata[string][addr_data_t]; |
| |
| // q of values obtained from the bus |
| addr_data_t addr_data_q[$]; |
| |
| // Indicate when the log was printed and what was the final string. |
| event printed_log_event; |
| arg_t printed_log; |
| arg_t printed_arg []; |
| |
| // Sets the sw_name with the provided path. |
| // |
| // The sw_name is assumed to be the full path to the SW image (example: /path/to/sometest.elf). |
| // The logger expects two files to be available in the same directory as the image - |
| // <sw_name>.logs.txt: contains logs split as fields of `sw_log_t` |
| // <sw_name>.rodata.txt: contains constants from the read-only sections. |
| // These are generated by `util/device_sw_utils/extract_sw_logs.py`. The Bazel build system |
| // already generates it for the 'sim_dv' build device. |
| function automatic void add_sw_log_db(string sw_image); |
| string sw_dir; |
| string sw_basename; |
| if (_ready) `dv_fatal("This function cannot be called after calling ready()") |
| sw_basename = str_utils_pkg::str_path_basename(.filename(sw_image), .drop_extn(1'b1)); |
| sw_log_db_files[sw_basename] = {sw_basename, ".logs.txt"}; |
| sw_rodata_db_files[sw_basename] = {sw_basename, ".rodata.txt"}; |
| endfunction |
| |
| // signal to indicate that this monitor is good to go - all initializations are done |
| function automatic void ready(); |
| _ready = 1'b1; |
| endfunction |
| |
| /********************/ |
| /* Monitoring logic */ |
| /********************/ |
| initial begin |
| wait(_ready); |
| if (enable) begin |
| if (parse_sw_log_file()) begin |
| if (write_sw_logs_to_file) begin |
| sw_logs_output_file = $sformatf("%m.log"); |
| sw_logs_output_fd = $fopen(sw_logs_output_file, "w"); |
| end |
| fork |
| get_addr_data_from_bus(); |
| construct_log_and_print(); |
| join_none |
| end |
| end |
| end |
| |
| final begin |
| if (sw_logs_output_fd) $fclose(sw_logs_output_fd); |
| end |
| |
| /******************/ |
| /* helper methods */ |
| /******************/ |
| // function that parses the log data file |
| // returns 1 if log data is avaiable, else false |
| function automatic bit parse_sw_log_file(); |
| bit result; |
| |
| // Iterate over the available sw names. |
| foreach (sw_log_db_files[sw]) begin |
| int fd; |
| fd = $fopen(sw_log_db_files[sw], "r"); |
| if (!fd) begin |
| `dv_info($sformatf("Failed to open sw log db file %s.", sw_log_db_files[sw])) |
| return 1'b0; |
| end |
| |
| while (!$feof(fd)) begin |
| string field; |
| addr_data_t addr; |
| sw_log_t sw_log; |
| bit status; |
| |
| sw_log.name = sw; |
| status = get_sw_log_field(fd, "addr", field); |
| // We proceed to retrieving other fields only if we get the addr. |
| if (!status) break; |
| addr = field.atohex(); |
| void'(get_sw_log_field(fd, "severity", field)); |
| sw_log.severity = log_severity_e'(field.atoi()); |
| void'(get_sw_log_field(fd, "file", field)); |
| sw_log.file = field; |
| void'(get_sw_log_field(fd, "line", field)); |
| sw_log.line = field.atoi(); |
| void'(get_sw_log_field(fd, "nargs", field)); |
| sw_log.nargs = field.atoi(); |
| `DV_CHECK_LE_FATAL(sw_log.nargs, MAX_ARGS, , $sformatf("%m")) |
| sw_log.arg = new[sw_log.nargs]; |
| void'(get_sw_log_field(fd, "format", field)); |
| sw_log.format = replace_cr_with_nl(field); |
| void'(get_sw_log_field(fd, "str_arg_idx", field)); |
| sw_log.str_arg_idx = get_str_arg_indices(field); |
| |
| if (sw_logs.exists(sw) && sw_logs[sw].exists(addr)) begin |
| `dv_warning($sformatf("Log entry for addr %0x already exists:\nOld: %p\nNew: %p", |
| addr, sw_logs[sw][addr], sw_log)) |
| end |
| sw_logs[sw][addr] = sw_log; |
| end |
| $fclose(fd); |
| |
| if (sw_logs.exists(sw) && sw_logs[sw].size() > 0) begin |
| void'(parse_sw_rodata_file(sw)); |
| result = 1'b1; |
| end |
| end |
| |
| // print parsed logs |
| foreach (sw_logs[sw, addr]) begin |
| `dv_info($sformatf("sw_logs[%0s][%0h] = %p", sw, addr, sw_logs[sw][addr]), UVM_HIGH) |
| end |
| |
| return result; |
| endfunction |
| |
| function automatic bit parse_sw_rodata_file(string sw); |
| int fd; |
| fd = $fopen(sw_rodata_db_files[sw], "r"); |
| if (!fd) return 0; |
| |
| while (!$feof(fd)) begin |
| string field; |
| addr_data_t addr; |
| bit status; |
| |
| status = get_sw_log_field(fd, "addr", field); |
| // We proceed to retrieving other fields only if we get the addr. |
| if (!status) break; |
| addr = field.atohex(); |
| void'(get_sw_log_field(fd, "string", field)); |
| |
| if (sw_rodata.exists(sw) && sw_rodata[sw].exists(addr)) begin |
| `dv_warning($sformatf("Rodata entry for addr %0x already exists:\nOld: %s\nNew: %s", |
| addr, sw_rodata[sw][addr], field)) |
| end |
| // Replace CRs in the middle of the string with NLs. |
| sw_rodata[sw][addr] = replace_cr_with_nl(field); |
| end |
| $fclose(fd); |
| |
| // print parsed rodata |
| foreach (sw_rodata[sw, addr]) begin |
| `dv_info($sformatf("sw_rodata[%0s][%0h] = %p", sw, addr, sw_rodata[sw][addr]), UVM_HIGH) |
| end |
| |
| return (sw_rodata[sw].size() > 0); |
| endfunction |
| |
| // Get the sw log fields by parsing line-by-line. |
| // The ref arg value is used to return the retrived field. |
| // Function returns the successfulness of the operation. |
| function automatic bit get_sw_log_field(int fd, string field, ref string value); |
| byte lf = 8'ha; |
| byte cr = 8'hd; |
| string match = {field, ": *"}; |
| |
| value = ""; |
| while (!$feof(fd)) begin |
| string line; |
| void'($fgets(line, fd)); |
| // Chomp the trailing newlines. |
| while (byte'(line.getc(line.len() - 1)) inside {lf, cr}) begin |
| line = line.substr(0, line.len() - 2); |
| end |
| if (line == "") continue; |
| // Check if field exists |
| if (!uvm_re_match(match, line)) begin |
| value = line.substr(match.len() - 1, line.len() - 1); |
| return 1'b1; |
| end |
| else begin |
| return 1'b0; |
| end |
| end |
| return 1'b0; |
| endfunction |
| |
| // Parse list of indices that have string args (in string format as space separated values. |
| // |
| // Returns a 33-bit vector indicting the bit indices which contain string args. |
| function automatic bit [MAX_ARGS-1:0] get_str_arg_indices(string list); |
| int i; |
| int start = 0; |
| bit [MAX_ARGS-1:0] indices; |
| if (list == "") return indices; |
| for (i = 0; i < list.len(); i++) begin |
| if (list.getc(i) == " " && i == start) begin |
| start++; |
| continue; |
| end |
| else if (list.getc(i) == " ") begin |
| string index = list.substr(start, i - 1); |
| indices[index.atoi()] = 1'b1; |
| start = i + 1; |
| end |
| end |
| if (start < i) begin |
| string index = list.substr(start, i - 1); |
| indices[index.atoi()] = 1'b1; |
| end |
| return indices; |
| endfunction |
| |
| // replace carriage return in the middle of the string with newline. |
| function automatic string replace_cr_with_nl(string text); |
| byte cr = 8'hd; |
| byte lf = 8'ha; |
| if (text.len() >= 2) begin |
| for (int i = 0; i < text.len() - 1; i++) begin |
| if (byte'(text.getc(i)) == cr) begin |
| text.putc(i, lf); |
| end |
| end |
| end |
| return text; |
| endfunction |
| |
| // Retrieve the string at the specified addr. |
| function automatic string get_str_at_addr(string sw, addr_data_t addr); |
| if (sw_rodata[sw].exists(addr)) return sw_rodata[sw][addr]; |
| // The string could start midway from an existing addr entry. |
| foreach (sw_rodata[sw][str_addr]) begin |
| addr_data_t limit = sw_rodata[sw][str_addr].len() - 1; |
| if (addr inside {[str_addr : str_addr + limit]}) begin |
| return sw_rodata[sw][str_addr].substr(addr - str_addr, limit); |
| end |
| end |
| // If no string was found at the provided addr, then return the addr converted to string. |
| begin |
| string result; |
| result.hextoa(addr); |
| return result; |
| end |
| endfunction |
| |
| // retrieve addr or data from the bus |
| task automatic get_addr_data_from_bus(); |
| forever begin |
| @(posedge clk_i or negedge rst_ni); |
| if (!rst_ni) begin |
| addr_data_q.delete(); |
| wait(rst_ni); |
| end else begin |
| if (enable && data_valid) begin |
| addr_data_q.push_back(data); |
| end |
| end |
| end |
| endtask |
| |
| //construct log and print when complete data is available |
| task automatic construct_log_and_print(); |
| forever begin |
| addr_data_t addr; |
| // get addr |
| wait(addr_data_q.size() > 0); |
| addr = addr_data_q.pop_front(); |
| |
| // lookup addr in sw_logs |
| foreach (sw_logs[sw]) begin |
| if (sw_logs[sw].exists(addr)) begin |
| bit rst_occurred; |
| fork |
| begin: isolation_thread |
| fork |
| // get args |
| for (int i = 0; i < sw_logs[sw][addr].nargs; i++) begin |
| addr_data_t addr_data; |
| wait(addr_data_q.size() > 0); |
| addr_data = addr_data_q.pop_front(); |
| if (sw_logs[sw][addr].str_arg_idx[i]) begin |
| // addr_data is the address of the string in rodata. Retrieve it. |
| sw_logs[sw][addr].arg[i] = arg_t'(get_str_at_addr(sw, addr_data)); |
| `dv_info($sformatf("String arg at addr %0h: %0s", addr_data, |
| sw_logs[sw][addr].arg[i]), UVM_LOW) |
| end else begin |
| sw_logs[sw][addr].arg[i] = addr_data; |
| end |
| end |
| begin |
| // check if rst_ni occurred - in that case discard and start over |
| wait(rst_ni === 1'b0); |
| rst_occurred = 1'b1; |
| end |
| join_any |
| disable fork; |
| end: isolation_thread |
| join |
| if (rst_occurred) continue; |
| print_sw_log(sw_logs[sw][addr]); |
| end |
| end |
| end |
| endtask |
| |
| // print the log captured from the SW. |
| function automatic void print_sw_log(sw_log_t sw_log); |
| string log_header = sw_log.name; |
| if (sw_log.file != "") begin |
| // Append the SW file and line to the header. |
| log_header = {log_header, "(", sw_log.file, ":", |
| $sformatf("%0d", sw_log.line), ")"}; |
| end |
| |
| // Shortcuts for use in switching # of args to insert in formatted string. |
| `define _USE_STR_ARG(n) sw_log.arg[``n``] |
| `define _1_ARGS `_USE_STR_ARG(0) |
| `define _2_ARGS `_1_ARGS, `_USE_STR_ARG(1) |
| `define _3_ARGS `_2_ARGS, `_USE_STR_ARG(2) |
| `define _4_ARGS `_3_ARGS, `_USE_STR_ARG(3) |
| `define _5_ARGS `_4_ARGS, `_USE_STR_ARG(4) |
| `define _6_ARGS `_5_ARGS, `_USE_STR_ARG(5) |
| `define _7_ARGS `_6_ARGS, `_USE_STR_ARG(6) |
| `define _8_ARGS `_7_ARGS, `_USE_STR_ARG(7) |
| `define _9_ARGS `_8_ARGS, `_USE_STR_ARG(8) |
| `define _10_ARGS `_9_ARGS, `_USE_STR_ARG(9) |
| `define _11_ARGS `_10_ARGS, `_USE_STR_ARG(10) |
| `define _12_ARGS `_11_ARGS, `_USE_STR_ARG(11) |
| `define _13_ARGS `_12_ARGS, `_USE_STR_ARG(12) |
| `define _14_ARGS `_13_ARGS, `_USE_STR_ARG(13) |
| `define _15_ARGS `_14_ARGS, `_USE_STR_ARG(14) |
| `define _16_ARGS `_15_ARGS, `_USE_STR_ARG(15) |
| `define _17_ARGS `_16_ARGS, `_USE_STR_ARG(16) |
| `define _18_ARGS `_17_ARGS, `_USE_STR_ARG(17) |
| `define _19_ARGS `_18_ARGS, `_USE_STR_ARG(18) |
| `define _20_ARGS `_19_ARGS, `_USE_STR_ARG(19) |
| `define _21_ARGS `_20_ARGS, `_USE_STR_ARG(20) |
| `define _22_ARGS `_21_ARGS, `_USE_STR_ARG(21) |
| `define _23_ARGS `_22_ARGS, `_USE_STR_ARG(22) |
| `define _24_ARGS `_23_ARGS, `_USE_STR_ARG(23) |
| `define _25_ARGS `_24_ARGS, `_USE_STR_ARG(24) |
| `define _26_ARGS `_25_ARGS, `_USE_STR_ARG(25) |
| `define _27_ARGS `_26_ARGS, `_USE_STR_ARG(26) |
| `define _28_ARGS `_27_ARGS, `_USE_STR_ARG(27) |
| `define _29_ARGS `_28_ARGS, `_USE_STR_ARG(28) |
| `define _30_ARGS `_29_ARGS, `_USE_STR_ARG(29) |
| `define _31_ARGS `_30_ARGS, `_USE_STR_ARG(30) |
| `define _32_ARGS `_31_ARGS, `_USE_STR_ARG(31) |
| `define _ADD_ARGS(n) `_``n``_ARGS |
| |
| // construct formatted string based on args |
| case (sw_log.nargs) |
| 0: ; |
| 1: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(1)); |
| 2: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(2)); |
| 3: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(3)); |
| 4: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(4)); |
| 5: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(5)); |
| 6: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(6)); |
| 7: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(7)); |
| 8: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(8)); |
| 9: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(9)); |
| 10: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(10)); |
| 11: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(11)); |
| 12: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(12)); |
| 13: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(13)); |
| 14: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(14)); |
| 15: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(15)); |
| 16: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(16)); |
| 17: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(17)); |
| 18: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(18)); |
| 19: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(19)); |
| 20: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(20)); |
| 21: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(21)); |
| 22: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(22)); |
| 23: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(23)); |
| 24: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(24)); |
| 25: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(25)); |
| 26: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(26)); |
| 27: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(27)); |
| 28: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(28)); |
| 29: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(29)); |
| 30: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(30)); |
| 31: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(31)); |
| 32: sw_log.format = $sformatf(sw_log.format, `_ADD_ARGS(32)); |
| default: `dv_fatal($sformatf("UNSUPPORTED: nargs = %0d (only 0:32 allowed)", sw_log.nargs)) |
| endcase |
| |
| `undef _USE_STR_ARG |
| `undef _1_ARGS |
| `undef _2_ARGS |
| `undef _3_ARGS |
| `undef _4_ARGS |
| `undef _5_ARGS |
| `undef _6_ARGS |
| `undef _7_ARGS |
| `undef _8_ARGS |
| `undef _9_ARGS |
| `undef _10_ARGS |
| `undef _11_ARGS |
| `undef _12_ARGS |
| `undef _13_ARGS |
| `undef _14_ARGS |
| `undef _15_ARGS |
| `undef _16_ARGS |
| `undef _17_ARGS |
| `undef _18_ARGS |
| `undef _19_ARGS |
| `undef _20_ARGS |
| `undef _21_ARGS |
| `undef _22_ARGS |
| `undef _23_ARGS |
| `undef _24_ARGS |
| `undef _25_ARGS |
| `undef _26_ARGS |
| `undef _27_ARGS |
| `undef _28_ARGS |
| `undef _29_ARGS |
| `undef _30_ARGS |
| `undef _31_ARGS |
| `undef _32_ARGS |
| `undef _ADD_ARGS |
| |
| begin |
| `ifdef UVM |
| uvm_verbosity level; |
| case (sw_log.verbosity) |
| LogVerbosityNone: level = UVM_NONE; |
| LogVerbosityLow: level = UVM_LOW; |
| LogVerbosityMedium: level = UVM_MEDIUM; |
| LogVerbosityHigh: level = UVM_HIGH; |
| LogVerbosityFull: level = UVM_FULL; |
| LogVerbosityDebug: level = UVM_DEBUG; |
| default: level = UVM_LOW; |
| endcase |
| `endif |
| case (sw_log.severity) |
| LogSeverityInfo: `dv_info(sw_log.format, level, log_header) |
| LogSeverityWarning: `dv_warning(sw_log.format, log_header) |
| LogSeverityError: `dv_error(sw_log.format, log_header) |
| LogSeverityFatal: `dv_fatal(sw_log.format, log_header) |
| default: `dv_info(sw_log.format, level, log_header) |
| endcase |
| end |
| |
| // write sw log to file if enabled |
| if (sw_logs_output_fd) begin |
| $fwrite(sw_logs_output_fd, "[%15t]: [%0s] %0s\n", $time, log_header, sw_log.format); |
| end |
| |
| printed_log = sw_log.format; |
| printed_arg = sw_log.arg; |
| ->printed_log_event; |
| endfunction |
| |
| endinterface |