| // 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 { |
| return raw_ == other.raw_; |
| } |
| |
| 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()] = parsed_line; |
| } |
| return true; |
| } |
| |
| bool OtbnTraceEntry::operator==(const OtbnTraceEntry &other) const { |
| return hdr_ == other.hdr_ && writes_ == other.writes_; |
| } |
| |
| void OtbnTraceEntry::print(const std::string &indent, std::ostream &os) const { |
| os << indent << hdr_ << "\n"; |
| for (const auto &pr : writes_) { |
| os << indent << pr.second.get_string() << "\n"; |
| } |
| } |
| |
| void OtbnTraceEntry::take_writes(const OtbnTraceEntry &other) { |
| if (!other.writes_.empty()) { |
| for (const auto &pr : other.writes_) { |
| writes_[pr.first] = pr.second; |
| } |
| } |
| } |
| |
| 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)); |
| } |
| |
| 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()] = 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; |
| } |