Files
lab-rv32i-freertos-c-resour…/vendor/Hazard3/test/sim/tb_verilator/tb.cpp
T

439 lines
12 KiB
C++

#include "Vtb.h"
#include "Vtb___024root.h"
#include "verilated.h"
#include <iostream>
#include <algorithm>
#include <cctype>
#include <cstdint>
#include <string>
#include <stdio.h>
#include <cstring>
#include "tb.h"
#include "tb_cli.h"
#include "tb_gdb.h"
#include "tb_jtag.h"
class tb_verilator_top: public tb_top {
VerilatedContext *contextp;
Vtb *top;
// Loop-carried address-phase requests
bus_request req_i;
bus_request req_d;
bool req_i_vld = false;
bool req_d_vld = false;
public:
tb_verilator_top(const tb_cli_args &parsed_args, int argc, char **argv);
~tb_verilator_top() {delete top; delete contextp;}
void step(const tb_cli_args &args, mem_io_state &memio) override;
void eval() override {top->eval();}
void set_trst_n(bool trst_n) override {top->trst_n = trst_n;}
void set_tck(bool tck) override {top->tck = tck;}
void set_tdi(bool tdi) override {top->tdi = tdi;}
void set_tms(bool tms) override {top->tms = tms;}
bool get_tdo() override {return top->tdo;}
void set_irq(uint32_t mask) override {top->irq = mask;}
void set_soft_irq(uint8_t mask) override {top->soft_irq = mask;}
void set_timer_irq(uint8_t mask) override {top->timer_irq = mask;}
Vtb___024root *rootp() { return top->rootp; }
void reset_dut(const tb_cli_args &args, mem_io_state &memio, uint32_t assert_cycles = 8, uint32_t release_cycles = 0);
};
tb_verilator_top::tb_verilator_top(const tb_cli_args &parsed_args, int argc, char **argv): tb_top {parsed_args} {
contextp = new VerilatedContext;
// Verilated context also gets a chance to parse the arguments. Any
// argument prefixed with "+verilator+" is ignored by our tb arg parsing.
contextp->commandArgs(argc, argv);
top = new Vtb{contextp};
req_i_vld = false;
req_d_vld = false;
req_i.reservation_id = 0;
req_d.reservation_id = 1;
// Set bus interfaces to generate good IDLE responses at first
top->i_hready = true;
top->d_hready = true;
// Reset + initial clock pulse
top->eval();
top->clk = true;
top->tck = true;
top->eval();
top->clk = false;
top->tck = false;
top->trst_n = true;
top->rst_n = true;
top->eval();
}
void tb_verilator_top::reset_dut(const tb_cli_args &args, mem_io_state &memio, uint32_t assert_cycles, uint32_t release_cycles) {
// Clear any loop-carried bus phase state.
req_i_vld = false;
req_d_vld = false;
// Ensure a clean default handshake state at reset release.
top->i_hready = true;
top->d_hready = true;
// Assert global reset (and JTAG TAP reset) for a few core cycles.
top->trst_n = false;
top->rst_n = false;
for (uint32_t i = 0; i < assert_cycles; ++i)
step(args, memio);
// Release reset. By default, we don't advance any cycles after release so
// the CPU is effectively "reset+halted" until the debugger resumes.
top->trst_n = true;
top->rst_n = true;
for (uint32_t i = 0; i < release_cycles; ++i)
step(args, memio);
}
void tb_verilator_top::step(const tb_cli_args &args, mem_io_state &memio) {
top->clk = false;
top->eval();
top->clk = true;
top->eval();
// The two bus ports are handled identically. This enables swapping out of
// various `tb.v` hardware integration files containing:
//
// - A single, dual-ported processor (instruction fetch, load/store ports)
// - A single, single-ported processor (instruction fetch + load/store muxed internally)
// - A pair of single-ported processors, for dual-core debug tests
if (top->d_hready) {
// Clear bus error by default
top->d_hresp = false;
// Handle current data phase
req_d.wdata = top->d_hwdata;
bus_response resp;
if (req_d_vld)
resp = mem_callback_d(*this, memio, req_d);
else
resp.exokay = !memio.monitor_enabled;
if (resp.err) {
// Phase 1 of error response
top->d_hready = false;
top->d_hresp = true;
}
if (req_d_vld && !req_d.write) {
top->d_hrdata = resp.rdata;
} else {
top->d_hrdata = rand();
}
top->d_hexokay = resp.exokay;
} else {
// hready=0. Currently this only happens when we're in the first
// phase of an error response, so go to phase 2.
top->d_hready = true;
}
req_d_vld = false;
if (top->d_hready) {
// Progress current address phase to data phase
req_d_vld = top->d_htrans >> 1;
req_d.write = top->d_hwrite;
req_d.size = (bus_size_t)top->d_hsize;
req_d.addr = top->d_haddr;
req_d.excl = top->d_hexcl;
}
if (top->i_hready) {
top->i_hresp = false;
req_i.wdata = top->i_hwdata;
bus_response resp;
if (req_i_vld)
resp = mem_callback_i(*this, memio, req_i);
else
resp.exokay = !memio.monitor_enabled;
if (resp.err) {
// Phase 1 of error response
top->i_hready = false;
top->i_hresp = true;
}
if (req_i_vld && !req_i.write) {
top->i_hrdata = resp.rdata;
} else {
top->i_hrdata = rand();
}
top->i_hexokay = resp.exokay;
} else {
// hready=0. Currently this only happens when we're in the first
// phase of an error response, so go to phase 2.
top->i_hready = true;
}
req_i_vld = false;
if (top->i_hready) {
// Progress current address phase to data phase
req_i_vld = top->i_htrans >> 1;
req_i.write = top->i_hwrite;
req_i.size = (bus_size_t)top->i_hsize;
req_i.addr = top->i_haddr;
req_i.excl = top->i_hexcl;
}
}
int main(int argc, char **argv) {
tb_cli_args args;
tb_parse_args(argc, argv, args);
VerilatedContext *contextp = new VerilatedContext;
contextp->commandArgs(argc, argv);
tb_jtag_state jtag(args);
mem_io_state memio(args);
tb_verilator_top tb(args, argc, argv);
bool timed_out = false;
int64_t cycle = 0;
if (args.gdb_port != 0) {
struct verilator_gdb_target final : tb_gdb_target {
tb_verilator_top &tb;
mem_io_state &memio;
const tb_cli_args &args;
Vtb___024root *root;
int64_t &cycle;
bool &timed_out;
verilator_gdb_target(tb_verilator_top &tb_, mem_io_state &memio_, const tb_cli_args &args_, int64_t &cycle_, bool &timed_out_)
: tb(tb_), memio(memio_), args(args_), root(tb_.rootp()), cycle(cycle_), timed_out(timed_out_) {}
uint32_t read_reg(uint32_t regno) override {
if (regno == 0)
return 0;
if (regno < 32) {
// GDB single-step stops with decode PC already advanced to the
// next instruction, while the just-computed register result can
// still be sitting in the M stage for one cycle before it is
// committed into the architectural register file. Overlay the
// pending writeback so the debugger sees a coherent post-step
// snapshot.
if (root->tb__DOT__cpu__DOT__core__DOT__m_reg_wen_if_nonzero &&
regno == root->tb__DOT__cpu__DOT__core__DOT__xm_rd) {
return (uint32_t)root->tb__DOT__cpu__DOT__core__DOT__m_result;
}
return (uint32_t)root->tb__DOT__cpu__DOT__core__DOT__regs__DOT__real_dualport_reset__DOT__mem[regno];
}
if (regno == 32) {
return (uint32_t)root->tb__DOT__cpu__DOT__core__DOT__decode_u__DOT__pc;
}
return 0;
}
void write_reg(uint32_t regno, uint32_t value) override {
if (regno == 0)
return;
if (regno < 32) {
root->tb__DOT__cpu__DOT__core__DOT__regs__DOT__real_dualport_reset__DOT__mem[regno] = value;
tb.eval();
return;
}
if (regno == 32) {
root->tb__DOT__cpu__DOT__core__DOT__decode_u__DOT__pc = value;
tb.eval();
return;
}
}
bool read_mem(uint32_t addr, uint8_t *dst, size_t len) override {
if (len == 0)
return true;
if (addr < MEM_BASE || addr + len > MEM_BASE + MEM_SIZE)
return false;
memcpy(dst, memio.mem + (addr - MEM_BASE), len);
return true;
}
bool write_mem(uint32_t addr, const uint8_t *src, size_t len) override {
if (len == 0)
return true;
if (addr < MEM_BASE || addr + len > MEM_BASE + MEM_SIZE)
return false;
memcpy(memio.mem + (addr - MEM_BASE), src, len);
return true;
}
tb_gdb_run_result step_instruction() override {
while (true) {
if (args.max_cycles != 0 && cycle >= args.max_cycles) {
timed_out = true;
return tb_gdb_run_result::timed_out;
}
const bool will_retire = root->tb__DOT__cpu__DOT__core__DOT__x_instr_ret;
memio.step(tb);
tb.step(args, memio);
++cycle;
if (memio.exit_req)
return tb_gdb_run_result::exited;
if (args.max_cycles != 0 && cycle >= args.max_cycles) {
timed_out = true;
return tb_gdb_run_result::timed_out;
}
if (will_retire)
return tb_gdb_run_result::ok;
}
}
uint32_t exit_code() const override { return memio.exit_code; }
bool monitor_cmd(const std::string &cmd, std::string &out_console) override {
auto is_space = [](unsigned char c) { return std::isspace(c) != 0; };
// Normalise whitespace and case to make matching user-friendly.
size_t start = 0;
while (start < cmd.size() && is_space((unsigned char)cmd[start]))
++start;
size_t end = cmd.size();
while (end > start && is_space((unsigned char)cmd[end - 1]))
--end;
std::string norm;
norm.reserve(end - start);
bool in_space = false;
for (size_t i = start; i < end; ++i) {
const unsigned char c = (unsigned char)cmd[i];
if (is_space(c)) {
in_space = true;
continue;
}
if (in_space && !norm.empty())
norm.push_back(' ');
in_space = false;
norm.push_back((char)std::tolower(c));
}
if (norm == "reset halt" || norm == "reset") {
// Clear external testbench state (but keep RAM contents).
memio.mtime = 0;
memio.mtimecmp[0] = 0;
memio.mtimecmp[1] = 0;
memio.exit_req = false;
memio.exit_code = 0;
memio.monitor_enabled = false;
memio.poison_addr = -4u;
memio.soft_irq_state = 0;
memio.irq_state = 0;
for (int i = 0; i < N_RESERVATIONS; ++i) {
memio.reservation_valid[i] = false;
memio.reservation_addr[i] = 0;
}
for (uint32_t i = 0; i < tb_uart_state::N_UARTS; ++i) {
memio.uart.uarts[i].overrun = false;
memio.uart.uarts[i].rx_fifo.clear();
memio.uart.uarts[i].tx_fifo.clear();
}
tb.set_soft_irq(0);
tb.set_timer_irq(0);
tb.set_irq(0);
cycle = 0;
timed_out = false;
tb.reset_dut(args, memio);
out_console = "reset halt\n";
return true;
}
return false;
}
};
verilator_gdb_target target(tb, memio, args, cycle, timed_out);
while (!memio.exit_req && !timed_out) {
tb_gdb_server gdb(args.gdb_port, target);
const tb_gdb_run_result r = gdb.serve();
if (r == tb_gdb_run_result::error)
return 1;
}
} else {
while (args.max_cycles == 0 || cycle < args.max_cycles) {
uint32_t core_cycles_advanced = 0;
bool jtag_exit_cmd = jtag.step(tb, &memio, &cycle, &timed_out, &core_cycles_advanced);
if (memio.exit_req) {
fprintf(tb.logfile, "CPU requested halt. Exit code %d\n", memio.exit_code);
fprintf(tb.logfile, "Ran for " I64_FMT " cycles\n", cycle);
break;
}
if (timed_out) {
fprintf(tb.logfile, "Max cycles reached\n");
break;
}
if (jtag_exit_cmd)
break;
// If the JTAG handler didn't advance the core clock, free-run for one cycle.
if (core_cycles_advanced == 0) {
memio.step(tb);
tb.step(args, memio);
++cycle;
if (memio.exit_req) {
fprintf(tb.logfile, "CPU requested halt. Exit code %d\n", memio.exit_code);
fprintf(tb.logfile, "Ran for " I64_FMT " cycles\n", cycle);
break;
}
if (args.max_cycles != 0 && cycle >= args.max_cycles) {
fprintf(tb.logfile, "Max cycles reached\n");
timed_out = true;
break;
}
}
}
}
jtag.close();
for (auto r : args.dump_ranges) {
fprintf(tb.logfile, "Dumping memory from %08x to %08x:\n", r.first, r.second);
for (int i = 0; i < r.second - r.first; ++i)
fprintf(tb.logfile, "%02x%c", memio.mem[r.first + i - MEM_BASE], i % 16 == 15 ? '\n' : ' ');
fprintf(tb.logfile, "\n");
}
if (args.sig_path != "") {
FILE *sigfile = fopen(args.sig_path.c_str(), "wb");
for (auto r : args.dump_ranges) {
for (uint32_t i = 0; i < r.second - r.first; i += 4) {
fprintf(
sigfile,
"%02x%02x%02x%02x\n",
memio.mem[r.first + i + 3 - MEM_BASE],
memio.mem[r.first + i + 2 - MEM_BASE],
memio.mem[r.first + i + 1 - MEM_BASE],
memio.mem[r.first + i + 0 - MEM_BASE]
);
}
}
fclose(sigfile);
}
if (args.propagate_return_code && timed_out) {
return -1;
} else if (args.propagate_return_code && memio.exit_req) {
return memio.exit_code;
} else {
return 0;
}
}
// Needed on MacOS, haven't looked into why
double sc_time_stamp() {
return 0.0;
}