#include #include #include #include #include #include #include "rv_types.h" #include "rv_csr.h" #include "rv_core.h" #include "rv_mem.h" // Minimal RISC-V interpreter, supporting: // - RV32I // - M // - A // - C (also called Zca) // - Zba // - Zbb // - Zbc // - Zbs // - Zbkb // - Zcmp // - M-mode traps #define RAM_SIZE_DEFAULT (16u * (1u << 20)) #define RAM_BASE 0u #define IO_BASE 0x80000000u #define TBIO_BASE (IO_BASE + 0x0000) const char *help_str = "Usage: tb [--bin x.bin] [--dump start end] [--vcd x.vcd] [--cycles n]\n" " --bin x.bin : Flat binary file loaded to address 0x0 in RAM\n" " --vcd x.vcd : Dummy option for compatibility with CXXRTL tb\n" " --dump start end : Print out memory contents between start and end (exclusive)\n" " after execution finishes. Can be passed multiple times.\n" " --cycles n : Maximum number of cycles to run before exiting.\n" " --cpuret : Testbench's return code is the return code written to\n" " IO_EXIT by the CPU, or -1 if timed out.\n" " --memsize n : Memory size in units of 1024 bytes, default is 16 MiB\n" " --trace : Print out execution tracing info\n" ; void exit_help(std::string errtext = "") { std::cerr << errtext << help_str; exit(-1); } int main(int argc, char **argv) { if (argc < 2) exit_help(); std::vector> dump_ranges; int64_t max_cycles = 100000; uint32_t ram_size = RAM_SIZE_DEFAULT; bool load_bin = false; std::string bin_path; bool trace_execution = false; bool propagate_return_code = false; for (int i = 1; i < argc; ++i) { std::string s(argv[i]); if (s == "--bin") { if (argc - i < 2) exit_help("Option --bin requires an argument\n"); load_bin = true; bin_path = argv[i + 1]; i += 1; } else if (s == "--vcd") { if (argc - i < 2) exit_help("Option --vcd requires an argument\n"); // (We ignore this argument, it's supported for i += 1; } else if (s == "--dump") { if (argc - i < 3) exit_help("Option --dump requires 2 arguments\n"); dump_ranges.push_back(std::make_tuple( std::stoul(argv[i + 1], 0, 0), std::stoul(argv[i + 2], 0, 0) )); i += 2; } else if (s == "--cycles") { if (argc - i < 2) exit_help("Option --cycles requires an argument\n"); max_cycles = std::stol(argv[i + 1], 0, 0); i += 1; } else if (s == "--memsize") { if (argc - i < 2) exit_help("Option --memsize requires an argument\n"); ram_size = 1024 * std::stol(argv[i + 1], 0, 0); i += 1; } else if (s == "--trace") { trace_execution = true; } else if (s == "--cpuret") { propagate_return_code = true; } else { std::cerr << "Unrecognised argument " << s << "\n"; exit_help(""); } } TBMemIO io(trace_execution); MemMap32 mem; mem.add(0x80000000u, 0x1000, &io); RVCore core(mem, RAM_BASE + 0x40, RAM_BASE, ram_size); if (load_bin) { std::ifstream fd(bin_path, std::ios::binary | std::ios::ate); std::streamsize bin_size = fd.tellg(); if (bin_size > ram_size) { std::cerr << "Binary file (" << bin_size << " bytes) is larger than memory (" << ram_size << " bytes)\n"; return -1; } fd.seekg(0, std::ios::beg); fd.read((char*)core.ram, bin_size); } int64_t cyc; int rc = 0; try { for (cyc = 0; cyc < max_cycles; ++cyc) { core.step(trace_execution); io.step(); core.csr.set_irq_t(io.timer_irq_pending()); core.csr.set_irq_s(io.soft_irq_pending()); } if (propagate_return_code) rc = -1; } catch (TBExitException e) { printf("CPU requested halt. Exit code %d\n", e.exitcode); printf("Ran for %ld cycles\n", cyc + 1); if (propagate_return_code) rc = e.exitcode; } for (auto [start, end] : dump_ranges) { printf("Dumping memory from %08x to %08x:\n", start, end); for (uint32_t i = 0; i < end - start; ++i) printf("%02x%c", *core.r8(start + i), i % 16 == 15 ? '\n' : ' '); printf("\n"); } return rc; }