blob: af1944cc184897b51c6eade2ed1e363fc75af811 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "otbn_trace_entry.h"
#include <cassert>
#include <iostream>
#include <regex>
#include <sstream>
bool OtbnTraceBodyLine::fill_from_string(const std::string &src,
const std::string &line) {
// A valid line matches the following regex
std::regex re("(.) ([^:]+): (.+)");
std::smatch match;
if (!std::regex_match(line, match, re)) {
std::cerr << "OTBN trace body line from " << src
<< " does not have expected format. Saw: `" << line << "'.\n";
return false;
}
assert(match.size() == 4);
raw_ = line;
type_ = match[1].str()[0];
loc_ = match[2].str();
value_ = match[3].str();
return true;
}
bool OtbnTraceBodyLine::operator==(const OtbnTraceBodyLine &other) const {
// If the raw lines are identical, the two objects are identical and no
// further checks are required.
if (raw_ == other.raw_) {
return true;
}
// If the raw lines are not identical, the two objects can be identical if one
// of them contains unknown values.
// Type and location have to be identical, though.
if (type_ != other.type_ || loc_ != other.loc_) {
return false;
}
// The values have to be of identical length.
if (value_.size() != other.value_.size()) {
return false;
}
// Compare values digit by digit and treat `x` as unknown value, which is
// identical to any other value.
std::string::const_iterator other_it = other.value_.begin();
for (std::string::const_iterator it = value_.begin(); it != value_.end();
it++) {
if (*it != *other_it && !(*it == 'x' || *other_it == 'x')) {
return false;
}
other_it++;
}
return true;
}
bool OtbnTraceEntry::from_rtl_trace(const std::string &trace) {
size_t eol = trace.find('\n');
hdr_ = trace.substr(0, eol);
trace_type_ = hdr_to_trace_type(hdr_);
while (eol != std::string::npos) {
size_t bol = eol + 1;
eol = trace.find('\n', bol);
size_t line_len =
(eol == std::string::npos) ? std::string::npos : eol - bol;
std::string line = trace.substr(bol, line_len);
// We're only interested in register writes
if (!(line.size() > 0 && line[0] == '>'))
continue;
OtbnTraceBodyLine parsed_line;
if (!parsed_line.fill_from_string("RTL", line)) {
return false;
}
writes_[parsed_line.get_loc()].push_back(parsed_line);
}
return true;
}
bool OtbnTraceEntry::compare_rtl_iss_entries(const OtbnTraceEntry &other,
bool no_sec_wipe_data_chk,
std::string *err_desc) const {
assert(err_desc);
if (hdr_ != other.hdr_) {
*err_desc = "Headers don't match.";
return false;
}
for (const auto &rtlptr : writes_) {
auto isskey = other.writes_.find(rtlptr.first);
if (isskey == other.writes_.end()) {
std::ostringstream oss;
oss << "RTL had a write to `" << rtlptr.first
<< "', but the ISS doesn't have a write to that location.";
*err_desc = oss.str();
return false;
}
// compare rtlptr.second and isskey.second
if (!check_entries_compatible(trace_type_, rtlptr.first, rtlptr.second,
isskey->second, no_sec_wipe_data_chk,
err_desc))
return false;
}
if (writes_.size() != other.writes_.size()) {
std::ostringstream oss;
oss << "RTL wrote to " << writes_.size() << " locations; the ISS wrote to "
<< other.writes_.size() << ".";
*err_desc = oss.str();
return false;
}
return true;
}
void OtbnTraceEntry::print(const std::string &indent, std::ostream &os) const {
os << indent << hdr_ << "\n";
for (const auto &pr : writes_) {
for (const auto &line : pr.second) {
os << indent << line.get_string() << "\n";
}
}
}
void OtbnTraceEntry::take_writes(const OtbnTraceEntry &other,
bool other_first) {
for (const auto &pr : other.writes_) {
std::vector<OtbnTraceBodyLine> &so_far = writes_[pr.first];
if (other_first) {
// If other_first is true, we should prepend the writes from other. We do
// so by creating a temporary vector (with a copy of the writes from
// other) and then appending any writes we had before.
std::vector<OtbnTraceBodyLine> tmp(pr.second);
tmp.insert(tmp.end(), so_far.begin(), so_far.end());
writes_[pr.first] = tmp;
} else {
// If other_first is false, we should append the writes from other. We
// can do that with just a call to insert.
so_far.insert(so_far.end(), pr.second.begin(), pr.second.end());
}
}
}
bool OtbnTraceEntry::is_compatible(const OtbnTraceEntry &prev) const {
// Two entries are compatible if they might both come from the multi-cycle
// execution of one instruction. For example, you might expect to see these
// two lines:
//
// S PC: 0x00000010, insn: 0x00107db8
// E PC: 0x00000010, insn: 0x00107db8
//
// which show an instruction at 0x10 stalling for a cycle and then managing
// to execute.
//
// Similarly, you might expect to see U followed by U or V.
//
// As an added complication, if we see an IMEM fetch error, the entry will
// become
//
// E PC: 0x00000010, insn: ??
//
// and that's fine. So the rule is:
//
// - Check the types are compatible (S then S or E; U then U or V)
// - Compare the two lines from character 1 onwards.
// - If they match: accept.
// - Otherwise, if the second line has no '?' then reject.
// - If there is a '?' and they match up to that point, accept.
// - Otherwise: reject
//
// (This wrongly accepts some malformed examples, but that's fine: it's just
// meant as a quick check to make sure our trace machinery isn't dropping
// entries)
bool matching_types;
switch (prev.trace_type()) {
case Stall:
matching_types = (trace_type_ == Stall || trace_type_ == Exec);
break;
case WipeInProgress:
matching_types =
(trace_type_ == WipeInProgress || trace_type_ == WipeComplete);
break;
default:
matching_types = false;
}
if (!matching_types)
return false;
bool exact_match =
0 == hdr_.compare(1, std::string::npos, prev.hdr_, 1, std::string::npos);
if (exact_match)
return true;
size_t first_qm = hdr_.find('?', 1);
if (first_qm == std::string::npos)
return false;
return 0 == hdr_.compare(1, first_qm - 1, prev.hdr_, 1, first_qm - 1);
}
bool OtbnTraceEntry::is_partial() const {
return ((trace_type_ == OtbnTraceEntry::Stall) ||
(trace_type_ == OtbnTraceEntry::WipeInProgress));
}
bool OtbnTraceEntry::is_final() const {
return ((trace_type_ == OtbnTraceEntry::Exec) ||
(trace_type_ == OtbnTraceEntry::WipeComplete));
}
bool OtbnTraceEntry::check_entries_compatible(
trace_type_t type, const std::string &key,
const std::vector<OtbnTraceBodyLine> &rtl_lines,
const std::vector<OtbnTraceBodyLine> &iss_lines, bool no_sec_wipe_data_chk,
std::string *err_desc) {
assert(rtl_lines.size() && iss_lines.size());
assert(type == WipeComplete || type == Exec);
assert(err_desc);
if (type == WipeComplete && key != "FLAGS0" && key != "FLAGS1") {
if (rtl_lines.size() != 2) {
std::ostringstream oss;
oss << "There are " << rtl_lines.size() << "RTL lines for key `" << key
<< "'; we expected 2.";
*err_desc = oss.str();
return false;
}
if (!no_sec_wipe_data_chk && rtl_lines.front() == rtl_lines.back()) {
std::ostringstream oss;
oss << "Repeated identical RTL lines for key `" << key << "'.";
*err_desc = oss.str();
return false;
}
}
if (!(rtl_lines.back() == iss_lines.back())) {
std::ostringstream oss;
oss << "Final values of ISS and RTL don't match for key `" << key << "'.";
*err_desc = oss.str();
return false;
}
return true;
}
OtbnTraceEntry::trace_type_t OtbnTraceEntry::hdr_to_trace_type(
const std::string &hdr) {
if (hdr.empty()) {
return Invalid;
}
switch (hdr[0]) {
case 'S':
return Stall;
case 'E':
return Exec;
case 'U':
return WipeInProgress;
case 'V':
return WipeComplete;
default:
return Invalid;
}
}
bool OtbnIssTraceEntry::from_iss_trace(const std::vector<std::string> &lines) {
// Read FSM. state 0 = read header; state 1 = read mnemonic (for E
// lines); state 2 = read writes
int state = 0;
std::regex re("# @0x([0-9a-f]{8}): (.*)");
std::smatch match;
for (const std::string &line : lines) {
switch (state) {
case 0:
hdr_ = line;
trace_type_ = hdr_to_trace_type(hdr_);
state = (!line.empty() && line[0] == 'E') ? 1 : 2;
break;
case 1:
// This some "special" extra data from the ISS that we use for
// functional coverage calculations. The line should be of the form
//
// # @ADDR: MNEMONIC
//
// where ADDR is an 8-digit instruction address (in hex) and mnemonic
// is the string mnemonic.
if (!std::regex_match(line, match, re)) {
std::cerr << "Bad 'special' line for ISS trace with header `" << hdr_
<< "': `" << line << "'.\n";
return false;
}
assert(match.size() == 3);
data_.insn_addr =
(uint32_t)strtoul(match[1].str().c_str(), nullptr, 16);
data_.mnemonic = match[2].str();
state = 2;
break;
default: {
assert(state == 2);
// Ignore '!' lines (which are used to tell the simulation about
// external register changes, not tracked by the RTL core simulation)
bool is_bang = (line.size() > 0 && line[0] == '!');
if (!is_bang) {
OtbnTraceBodyLine parsed_line;
if (!parsed_line.fill_from_string("ISS", line)) {
return false;
}
writes_[parsed_line.get_loc()].push_back(parsed_line);
}
break;
}
}
}
// We shouldn't be in state 1 here: that would mean an E line with no
// follow-up '#' line.
if (state == 1) {
std::cerr << "No 'special' line for ISS trace with header `" << hdr_
<< "'.\n";
return false;
}
return true;
}