feat: publish FreeRTOS C FC04 card
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
build-*
|
||||
tb
|
||||
tb-*
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
include ../project_paths.mk
|
||||
|
||||
TOP := tb
|
||||
DOTF := tb.f
|
||||
CONFIG := default
|
||||
TBEXEC := $(patsubst %.f,%,$(DOTF))
|
||||
|
||||
ifneq ($(CONFIG),default)
|
||||
TBEXEC := $(TBEXEC)-$(CONFIG)
|
||||
endif
|
||||
|
||||
FILE_LIST := $(shell HDL=$(HDL) $(SCRIPTS)/listfiles ../tb_common/hdl/$(DOTF))
|
||||
VINCDIR := $(shell HDL=$(HDL) $(SCRIPTS)/listfiles -f flati ../tb_common/hdl/$(DOTF))
|
||||
BUILD_DIR := build-$(patsubst %.f,%,$(DOTF))
|
||||
VOBJ_DIR := $(BUILD_DIR)/obj_dir
|
||||
VLIB := $(VOBJ_DIR)/V$(TOP)__ALL.a
|
||||
VERILATED_OBJS := $(VOBJ_DIR)/verilated.o $(VOBJ_DIR)/verilated_threads.o
|
||||
|
||||
VERILATOR_ROOT := $(shell verilator --getenv VERILATOR_ROOT)
|
||||
CXX := g++
|
||||
VERILATOR := verilator
|
||||
|
||||
TB_CFILES := tb.cpp $(wildcard ../tb_common/*.cpp)
|
||||
CINCLUDE := $(VOBJ_DIR) $(VERILATOR_ROOT)/include ../tb_common/include
|
||||
|
||||
.PHONY: clean all lint vcc vlib
|
||||
|
||||
all: $(TBEXEC)
|
||||
vcc: $(BUILD_DIR)/vcc.touch
|
||||
vlib: $(BUILD_DIR)/vlib.touch
|
||||
|
||||
$(BUILD_DIR)/vcc.touch: $(FILE_LIST) $(wildcard *.vh)
|
||||
mkdir -p $(VOBJ_DIR)
|
||||
$(VERILATOR) --Mdir $(VOBJ_DIR) --cc --top-module $(TOP) $(addprefix -I,$(VINCDIR)) -DCONFIG_HEADER="\"config_$(CONFIG).vh\"" $(FILE_LIST)
|
||||
touch $@
|
||||
|
||||
$(BUILD_DIR)/vlib.touch: $(BUILD_DIR)/vcc.touch
|
||||
# Verilator's archive (Vtb__ALL.a) does not include the run-time objects
|
||||
# (verilated.o/verilated_threads.o). Build them explicitly so the final
|
||||
# tb link step succeeds across Verilator versions/distros.
|
||||
$(MAKE) VERILATOR_ROOT=$(VERILATOR_ROOT) -C $(VOBJ_DIR) -f V$(TOP).mk \
|
||||
V$(TOP)__ALL.a verilated.o verilated_threads.o
|
||||
touch $@
|
||||
|
||||
clean::
|
||||
rm -rf $(BUILD_DIR) $(TBEXEC)
|
||||
|
||||
$(TBEXEC): $(BUILD_DIR)/vlib.touch $(TB_CFILES)
|
||||
$(CXX) -O3 -std=c++14 $(addprefix -D,$(CDEFINES) $(CDEFINES_$(DOTF))) \
|
||||
$(addprefix -I ,$(CINCLUDE)) $(TB_CFILES) $(VLIB) $(VERILATED_OBJS) -o $(TBEXEC) -pthread -latomic
|
||||
|
||||
lint:
|
||||
$(VERILATOR) --lint-only --top-module $(TOP) $(addprefix -I,$(VINCDIR)) -DCONFIG_HEADER="\"config_$(CONFIG).vh\"" $(FILE_LIST)
|
||||
@@ -0,0 +1,13 @@
|
||||
adapter driver jtag_vpi
|
||||
jtag_vpi set_address 127.0.0.1
|
||||
jtag_vpi set_port 5555
|
||||
transport select jtag
|
||||
|
||||
set _CHIPNAME hazard3
|
||||
jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0xdeadbeef
|
||||
set _TARGETNAME $_CHIPNAME.cpu
|
||||
target create $_TARGETNAME riscv -chain-position $_TARGETNAME
|
||||
|
||||
gdb report_data_abort enable
|
||||
init
|
||||
halt
|
||||
+438
@@ -0,0 +1,438 @@
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user