blob: a3d48552bfafaa84ecb9ed37111f4499b138a5e0 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
`ifndef SYNTHESIS
/**
* Tracer module for OTBN. This produces a multi-line string as trace output at most once every
* cycle and provides it to the simulation environment via a DPI call. It uses `otbn_trace_if` to
* get the information it needs. For further information see `hw/ip/otbn/dv/tracer/README.md`.
*/
module otbn_tracer (
input logic clk_i,
input logic rst_ni,
otbn_trace_if otbn_trace
);
import otbn_pkg::*;
// Prefixes used in trace lines. Formats are documented in `hw/ip/otbn/dv/tracer/README.md`
parameter string InsnExecutePrefix = "E";
parameter string InsnStallPrefix = "S";
parameter string WipeInProgressPrefix = "U";
parameter string WipeCompletePrefix = "V";
parameter string RegReadPrefix = "<";
parameter string RegWritePrefix = ">";
parameter string MemWritePrefix = "W";
parameter string MemReadPrefix = "R";
string trace_output_buffer;
logic [31:0] cycle_count;
// Given a WLEN size word output a hex string with the data split into 32-bit chunks separated
// with '_'. WLEN must be a multiple of 32.
function automatic string otbn_wlen_data_str(logic [WLEN-1:0] data);
string data_str;
assert ((WLEN % 32) == 0) else $error("WLEN must be a multiple of 32 in otbn_wlen_data_str");
for (int i = WLEN; i > 0; i-= 32) begin
if (i != WLEN) begin
data_str = $sformatf("%s_", data_str);
end else begin
data_str = "0x";
end
data_str = $sformatf("%s%08x", data_str, data[i-1 -: 32]);
end
return data_str;
endfunction
// Produce trace output string for dmem writes. For a 256-bit write, the address and full data is
// output. For 32-bit writes (determined by looking at the mask) only the relevant 32-bit chunk is
// output along with the address modified so it refers to that chunk.
function automatic string otbn_dmem_write_str(logic [31:0] addr,
logic [WLEN-1:0] data,
logic [WLEN-1:0] wmask);
logic [WLEN-1:0] cur_base_mask;
// For a full WLEN write output all of the data.
if (wmask == '1) begin
return $sformatf("[0x%08x]: %s", addr, otbn_wlen_data_str(data));
end
// Iterate through the possible 32-bit chunks
cur_base_mask = {{(WLEN-32){1'b0}}, 32'hFFFF_FFFF};
for (int i = 0; i < WLEN; i += 32) begin
// If mask matches current chunk alone output trace indicating a single 32-bit write.
if (wmask == cur_base_mask) begin
return $sformatf("[0x%08x]: 0x%08x", addr + (i / 8), data[i*32 +: 32]);
end
cur_base_mask = cur_base_mask << 32;
end
// Fallback where mask isn't as expected, indicate ERR in the trace and provide both full mask
// and data.
return $sformatf("[0x%08x]: Mask ERR Mask: %s Data: %s", addr, otbn_wlen_data_str(wmask),
otbn_wlen_data_str(data));
endfunction
// Determine name for an ISPR
function automatic string otbn_ispr_name_str(ispr_e ispr);
unique case (ispr)
IsprMod: return "MOD";
IsprAcc: return "ACC";
IsprRnd: return "RND";
IsprFlags: return "FLAGS";
IsprUrnd: return "URND";
default: return "UNKNOWN_ISPR";
endcase
endfunction
// Format flag information into a string
function automatic string otbn_flags_str(flags_t f);
return $sformatf("{C: %d, M: %d, L: %d, Z: %d}", f.C, f.M, f.L, f.Z);
endfunction
// Called by other trace functions to append their trace lines to the output buffer
function automatic void output_trace(string prefix, string trace_line);
trace_output_buffer = $sformatf("%s%s %s\n", trace_output_buffer, prefix, trace_line);
endfunction
function automatic void trace_base_rf();
if (otbn_trace.rf_base_rd_en_a) begin
output_trace(RegReadPrefix, $sformatf("x%02d: 0x%08x", otbn_trace.rf_base_rd_addr_a,
otbn_trace.rf_base_rd_data_a));
end
if (otbn_trace.rf_base_rd_en_b) begin
output_trace(RegReadPrefix, $sformatf("x%02d: 0x%08x", otbn_trace.rf_base_rd_addr_b,
otbn_trace.rf_base_rd_data_b));
end
if (|otbn_trace.rf_base_wr_en && otbn_trace.rf_base_wr_commit &&
otbn_trace.rf_base_wr_addr != '0) begin
output_trace(RegWritePrefix, $sformatf("x%02d: 0x%08x", otbn_trace.rf_base_wr_addr,
otbn_trace.rf_base_wr_data));
end
endfunction
function automatic void trace_bignum_rf();
if (otbn_trace.rf_bignum_rd_en_a) begin
output_trace(RegReadPrefix, $sformatf("w%02d: %s", otbn_trace.rf_bignum_rd_addr_a,
otbn_wlen_data_str(otbn_trace.rf_bignum_rd_data_a)));
end
if (otbn_trace.rf_bignum_rd_en_b) begin
output_trace(RegReadPrefix, $sformatf("w%02d: %s", otbn_trace.rf_bignum_rd_addr_b,
otbn_wlen_data_str(otbn_trace.rf_bignum_rd_data_b)));
end
if (|otbn_trace.rf_bignum_wr_en & otbn_trace.rf_bignum_wr_commit) begin
output_trace(RegWritePrefix, $sformatf("w%02d: %s", otbn_trace.rf_bignum_wr_addr,
otbn_wlen_data_str(otbn_trace.rf_bignum_wr_data)));
end
endfunction
function automatic void trace_bignum_mem();
if (otbn_trace.dmem_write) begin
output_trace(MemWritePrefix, otbn_dmem_write_str(otbn_trace.dmem_write_addr,
otbn_trace.dmem_write_data,
otbn_trace.dmem_write_mask));
end
if (otbn_trace.dmem_read) begin
output_trace(MemReadPrefix, $sformatf("[0x%08x]: %s", otbn_trace.dmem_read_addr,
otbn_wlen_data_str(otbn_trace.dmem_read_data)));
end
endfunction
function automatic void trace_ispr_accesses();
// Iterate through all ISPRs outputting reg reads and writes where ISPR accesses have occurred
for (int i_ispr = 0; i_ispr < NIspr; i_ispr++) begin
if (ispr_e'(i_ispr) == IsprFlags) begin
// Special handling for flags ISPR to provide per flag field output
for (int i_fg = 0; i_fg < NFlagGroups; i_fg++) begin
if (otbn_trace.flags_read[i_fg]) begin
output_trace(RegReadPrefix,
$sformatf("%s%1d: %s", otbn_ispr_name_str(ispr_e'(i_ispr)), i_fg,
otbn_flags_str(otbn_trace.flags_read_data[i_fg])));
end
if (otbn_trace.flags_write[i_fg]) begin
output_trace(RegWritePrefix,
$sformatf("%s%1d: %s", otbn_ispr_name_str(ispr_e'(i_ispr)), i_fg,
otbn_flags_str(otbn_trace.flags_write_data[i_fg])));
end
end
end else begin
// For all other ISPRs just dump out the full 256-bits of data being read/written
if (otbn_trace.ispr_read[i_ispr]) begin
output_trace(RegReadPrefix,
$sformatf("%s: %s", otbn_ispr_name_str(ispr_e'(i_ispr)),
otbn_wlen_data_str(otbn_trace.ispr_read_data[i_ispr])));
end
if (otbn_trace.ispr_write[i_ispr]) begin
output_trace(RegWritePrefix,
$sformatf("%s: %s", otbn_ispr_name_str(ispr_e'(i_ispr)),
otbn_wlen_data_str(otbn_trace.ispr_write_data[i_ispr])));
end
end
end
endfunction
function automatic void trace_header();
if (otbn_trace.insn_valid) begin
if (otbn_trace.insn_fetch_err) begin
// This means that we've seen an IMEM integrity error. Squash the reported instruction bits
// and ignore any stall: this will be the last cycle of the instruction either way.
output_trace(InsnExecutePrefix,
$sformatf("PC: 0x%08x, insn: ??", otbn_trace.insn_addr));
end else begin
// We have a valid instruction, either stalled or completing its execution
output_trace(otbn_trace.insn_stall ? InsnStallPrefix : InsnExecutePrefix,
$sformatf("PC: 0x%08x, insn: 0x%08x", otbn_trace.insn_addr,
otbn_trace.insn_data));
end
end
if (otbn_trace.secure_wipe_ack_r) begin
output_trace(WipeCompletePrefix, "");
end else if (otbn_trace.secure_wipe_req) begin
output_trace(WipeInProgressPrefix, "");
end
endfunction
import "DPI-C" function void accept_otbn_trace_string(string trace, int unsigned cycle_count);
function automatic void do_trace();
trace_output_buffer = "";
trace_header();
trace_bignum_rf();
trace_base_rf();
trace_bignum_mem();
trace_ispr_accesses();
if (trace_output_buffer != "") begin
accept_otbn_trace_string(trace_output_buffer, cycle_count);
end
endfunction
always @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
cycle_count <= '0;
end else begin
cycle_count <= cycle_count + 1'b1;
do_trace();
end
end
endmodule
`endif // SYNTHESIS