const int NXPR = 32;
const int NFPR = 32;
+const int NCSR = 4096;
#define X_RA 1
#define X_SP 2
--- /dev/null
+#include "devices.h"
+
+void bus_t::add_device(reg_t addr, abstract_device_t* dev)
+{
+ devices[-addr] = dev;
+}
+
+bool bus_t::load(reg_t addr, size_t len, uint8_t* bytes)
+{
+ auto it = devices.lower_bound(-addr);
+ if (it == devices.end())
+ return false;
+ return it->second->load(addr - -it->first, len, bytes);
+}
+
+bool bus_t::store(reg_t addr, size_t len, const uint8_t* bytes)
+{
+ auto it = devices.lower_bound(-addr);
+ if (it == devices.end())
+ return false;
+ return it->second->store(addr - -it->first, len, bytes);
+}
+
+rom_device_t::rom_device_t(std::vector<char> data)
+ : data(data)
+{
+}
+
+bool rom_device_t::load(reg_t addr, size_t len, uint8_t* bytes)
+{
+ if (addr + len > data.size())
+ return false;
+ memcpy(bytes, &data[addr], len);
+ return true;
+}
+
+bool rom_device_t::store(reg_t addr, size_t len, const uint8_t* bytes)
+{
+ return false;
+}
--- /dev/null
+#ifndef _RISCV_DEVICES_H
+#define _RISCV_DEVICES_H
+
+#include "decode.h"
+#include <map>
+#include <vector>
+
+class abstract_device_t {
+ public:
+ virtual bool load(reg_t addr, size_t len, uint8_t* bytes) = 0;
+ virtual bool store(reg_t addr, size_t len, const uint8_t* bytes) = 0;
+ virtual ~abstract_device_t() {}
+};
+
+class bus_t : public abstract_device_t {
+ public:
+ bool load(reg_t addr, size_t len, uint8_t* bytes);
+ bool store(reg_t addr, size_t len, const uint8_t* bytes);
+ void add_device(reg_t addr, abstract_device_t* dev);
+
+ private:
+ std::map<reg_t, abstract_device_t*> devices;
+};
+
+class rom_device_t : public abstract_device_t {
+ public:
+ rom_device_t(std::vector<char> data);
+ bool load(reg_t addr, size_t len, uint8_t* bytes);
+ bool store(reg_t addr, size_t len, const uint8_t* bytes);
+ private:
+ std::vector<char> data;
+};
+
+#endif
--- /dev/null
+// See LICENSE for license details.
+
+#ifndef _RISCV_DEVICETREE_H
+#define _RISCV_DEVICETREE_H
+
+#include <stdint.h>
+#include <string.h>
+#include <string>
+#include <map>
+#include <vector>
+#include <arpa/inet.h>
+
+#define FDT_MAGIC 0xd00dfeedU
+#define FDT_VERSION 17
+#define FDT_COMP_VERSION 16
+#define FDT_BEGIN_NODE 1
+#define FDT_END_NODE 2
+#define FDT_PROP 3
+#define FDT_END 9
+
+struct fdt_header {
+ uint32_t magic;
+ uint32_t totalsize;
+ uint32_t off_dt_struct;
+ uint32_t off_dt_strings;
+ uint32_t off_rsvmap;
+ uint32_t version;
+ uint32_t last_comp_version;
+ uint32_t boot_cpuid_phys;
+ uint32_t size_dt_strings;
+ uint32_t size_dt_struct;
+};
+
+struct fdt_reserve_entry {
+ uint64_t address;
+ uint64_t size;
+};
+
+struct string_table {
+ std::map<std::string, size_t> strings;
+ std::vector<char> data;
+
+ size_t add(std::string s) {
+ if (!strings.count(s)) {
+ strings[s] = data.size();
+ data.insert(data.end(), s.begin(), s.end());
+ data.push_back(0);
+ }
+ return strings[s];
+ }
+};
+
+struct device_tree {
+ device_tree() {
+ memset(rsvmap, 0, sizeof(rsvmap));
+ }
+
+ void begin_node(std::string s) {
+ std::vector<uint32_t> name = s2v(s);
+ sblock.push_back(FDT_BEGIN_NODE);
+ sblock.insert(sblock.end(), name.begin(), name.end());
+ }
+
+ void end_node() {
+ sblock.push_back(FDT_END_NODE);
+ }
+
+ std::vector<char> finalize() {
+ sblock.push_back(FDT_END);
+
+ struct fdt_header h;
+ h.size_dt_struct = sblock.size() * sizeof(sblock[0]);
+ h.size_dt_strings = strings.data.size();
+ h.magic = FDT_MAGIC;
+ h.off_rsvmap = sizeof(h);
+ h.off_dt_struct = h.off_rsvmap + sizeof(rsvmap);
+ h.off_dt_strings = h.off_dt_struct + h.size_dt_struct;
+ h.totalsize = h.off_dt_strings + h.size_dt_strings;
+ h.version = FDT_VERSION;
+ h.last_comp_version = FDT_COMP_VERSION;
+ h.boot_cpuid_phys = 0;
+
+ for (uint32_t* p = &h.magic; p < &h.magic + sizeof(h)/sizeof(uint32_t); p++)
+ *p = htonl(*p);
+ for (uint32_t& p : sblock)
+ p = htonl(p);
+
+ std::vector<char> res;
+ res.insert(res.end(), (char*)&h, (char*)&h + sizeof(h));
+ res.insert(res.end(), (char*)&rsvmap, (char*)&rsvmap + sizeof(rsvmap));
+ res.insert(res.end(), (char*)&sblock[0],
+ (char*)&sblock[0] + sblock.size() * sizeof(sblock[0]));
+ res.insert(res.end(), strings.data.begin(), strings.data.end());
+ return res;
+ }
+
+ void add_prop(std::string name, uint32_t data)
+ {
+ add_prop(name, std::vector<uint32_t>(1, data), sizeof(data));
+ }
+
+ void add_reg(std::vector<uint64_t> values)
+ {
+ std::vector<uint32_t> v;
+ for (auto x : values) {
+ v.push_back(x >> 32);
+ v.push_back(x);
+ }
+ add_prop("reg", v, v.size() * sizeof(v[0]));
+ }
+
+ void add_prop(std::string name, std::string data)
+ {
+ add_prop(name, s2v(data), data.size()+1);
+ }
+
+ private:
+ struct string_table strings;
+ std::vector<uint32_t> sblock;
+ struct fdt_reserve_entry rsvmap[1];
+
+ std::vector<uint32_t> s2v(std::string data) {
+ std::vector<char> v(data.begin(), data.end());
+ do {
+ v.push_back(0);
+ } while (v.size() % 4);
+
+ std::vector<uint32_t> words;
+ for (size_t i = 0; i < v.size(); i += 4)
+ words.push_back((v[i] << 24) | (v[i+1] << 16) | (v[i+2] << 8) | v[i+3]);
+ return words;
+ }
+
+ void add_prop(std::string name, std::vector<uint32_t> data, size_t len) {
+ sblock.push_back(FDT_PROP);
+ sblock.push_back(len);
+ sblock.push_back(strings.add(name));
+ sblock.insert(sblock.end(), data.begin(), data.end());
+ }
+};
+
+#endif
#define MASK_C_SWSP 0xe003
#define MATCH_C_FSWSP 0xe002
#define MASK_C_FSWSP 0xe003
+#define MATCH_CUSTOM0 0xb
+#define MASK_CUSTOM0 0x707f
+#define MATCH_CUSTOM0_RS1 0x200b
+#define MASK_CUSTOM0_RS1 0x707f
+#define MATCH_CUSTOM0_RS1_RS2 0x300b
+#define MASK_CUSTOM0_RS1_RS2 0x707f
+#define MATCH_CUSTOM0_RD 0x400b
+#define MASK_CUSTOM0_RD 0x707f
+#define MATCH_CUSTOM0_RD_RS1 0x600b
+#define MASK_CUSTOM0_RD_RS1 0x707f
+#define MATCH_CUSTOM0_RD_RS1_RS2 0x700b
+#define MASK_CUSTOM0_RD_RS1_RS2 0x707f
+#define MATCH_CUSTOM1 0x2b
+#define MASK_CUSTOM1 0x707f
+#define MATCH_CUSTOM1_RS1 0x202b
+#define MASK_CUSTOM1_RS1 0x707f
+#define MATCH_CUSTOM1_RS1_RS2 0x302b
+#define MASK_CUSTOM1_RS1_RS2 0x707f
+#define MATCH_CUSTOM1_RD 0x402b
+#define MASK_CUSTOM1_RD 0x707f
+#define MATCH_CUSTOM1_RD_RS1 0x602b
+#define MASK_CUSTOM1_RD_RS1 0x707f
+#define MATCH_CUSTOM1_RD_RS1_RS2 0x702b
+#define MASK_CUSTOM1_RD_RS1_RS2 0x707f
+#define MATCH_CUSTOM2 0x5b
+#define MASK_CUSTOM2 0x707f
+#define MATCH_CUSTOM2_RS1 0x205b
+#define MASK_CUSTOM2_RS1 0x707f
+#define MATCH_CUSTOM2_RS1_RS2 0x305b
+#define MASK_CUSTOM2_RS1_RS2 0x707f
+#define MATCH_CUSTOM2_RD 0x405b
+#define MASK_CUSTOM2_RD 0x707f
+#define MATCH_CUSTOM2_RD_RS1 0x605b
+#define MASK_CUSTOM2_RD_RS1 0x707f
+#define MATCH_CUSTOM2_RD_RS1_RS2 0x705b
+#define MASK_CUSTOM2_RD_RS1_RS2 0x707f
+#define MATCH_CUSTOM3 0x7b
+#define MASK_CUSTOM3 0x707f
+#define MATCH_CUSTOM3_RS1 0x207b
+#define MASK_CUSTOM3_RS1 0x707f
+#define MATCH_CUSTOM3_RS1_RS2 0x307b
+#define MASK_CUSTOM3_RS1_RS2 0x707f
+#define MATCH_CUSTOM3_RD 0x407b
+#define MASK_CUSTOM3_RD 0x707f
+#define MATCH_CUSTOM3_RD_RS1 0x607b
+#define MASK_CUSTOM3_RD_RS1 0x707f
+#define MATCH_CUSTOM3_RD_RS1_RS2 0x707b
+#define MASK_CUSTOM3_RD_RS1_RS2 0x707f
#define CSR_FFLAGS 0x1
#define CSR_FRM 0x2
#define CSR_FCSR 0x3
#define CSR_MTOHOST 0x780
#define CSR_MFROMHOST 0x781
#define CSR_MRESET 0x782
-#define CSR_SEND_IPI 0x783
+#define CSR_MIPI 0x783
+#define CSR_MIOBASE 0x784
#define CSR_CYCLEH 0xc80
#define CSR_TIMEH 0xc81
#define CSR_INSTRETH 0xc82
DECLARE_INSN(c_fsdsp, MATCH_C_FSDSP, MASK_C_FSDSP)
DECLARE_INSN(c_swsp, MATCH_C_SWSP, MASK_C_SWSP)
DECLARE_INSN(c_fswsp, MATCH_C_FSWSP, MASK_C_FSWSP)
+DECLARE_INSN(custom0, MATCH_CUSTOM0, MASK_CUSTOM0)
+DECLARE_INSN(custom0_rs1, MATCH_CUSTOM0_RS1, MASK_CUSTOM0_RS1)
+DECLARE_INSN(custom0_rs1_rs2, MATCH_CUSTOM0_RS1_RS2, MASK_CUSTOM0_RS1_RS2)
+DECLARE_INSN(custom0_rd, MATCH_CUSTOM0_RD, MASK_CUSTOM0_RD)
+DECLARE_INSN(custom0_rd_rs1, MATCH_CUSTOM0_RD_RS1, MASK_CUSTOM0_RD_RS1)
+DECLARE_INSN(custom0_rd_rs1_rs2, MATCH_CUSTOM0_RD_RS1_RS2, MASK_CUSTOM0_RD_RS1_RS2)
+DECLARE_INSN(custom1, MATCH_CUSTOM1, MASK_CUSTOM1)
+DECLARE_INSN(custom1_rs1, MATCH_CUSTOM1_RS1, MASK_CUSTOM1_RS1)
+DECLARE_INSN(custom1_rs1_rs2, MATCH_CUSTOM1_RS1_RS2, MASK_CUSTOM1_RS1_RS2)
+DECLARE_INSN(custom1_rd, MATCH_CUSTOM1_RD, MASK_CUSTOM1_RD)
+DECLARE_INSN(custom1_rd_rs1, MATCH_CUSTOM1_RD_RS1, MASK_CUSTOM1_RD_RS1)
+DECLARE_INSN(custom1_rd_rs1_rs2, MATCH_CUSTOM1_RD_RS1_RS2, MASK_CUSTOM1_RD_RS1_RS2)
+DECLARE_INSN(custom2, MATCH_CUSTOM2, MASK_CUSTOM2)
+DECLARE_INSN(custom2_rs1, MATCH_CUSTOM2_RS1, MASK_CUSTOM2_RS1)
+DECLARE_INSN(custom2_rs1_rs2, MATCH_CUSTOM2_RS1_RS2, MASK_CUSTOM2_RS1_RS2)
+DECLARE_INSN(custom2_rd, MATCH_CUSTOM2_RD, MASK_CUSTOM2_RD)
+DECLARE_INSN(custom2_rd_rs1, MATCH_CUSTOM2_RD_RS1, MASK_CUSTOM2_RD_RS1)
+DECLARE_INSN(custom2_rd_rs1_rs2, MATCH_CUSTOM2_RD_RS1_RS2, MASK_CUSTOM2_RD_RS1_RS2)
+DECLARE_INSN(custom3, MATCH_CUSTOM3, MASK_CUSTOM3)
+DECLARE_INSN(custom3_rs1, MATCH_CUSTOM3_RS1, MASK_CUSTOM3_RS1)
+DECLARE_INSN(custom3_rs1_rs2, MATCH_CUSTOM3_RS1_RS2, MASK_CUSTOM3_RS1_RS2)
+DECLARE_INSN(custom3_rd, MATCH_CUSTOM3_RD, MASK_CUSTOM3_RD)
+DECLARE_INSN(custom3_rd_rs1, MATCH_CUSTOM3_RD_RS1, MASK_CUSTOM3_RD_RS1)
+DECLARE_INSN(custom3_rd_rs1_rs2, MATCH_CUSTOM3_RD_RS1_RS2, MASK_CUSTOM3_RD_RS1_RS2)
#endif
#ifdef DECLARE_CSR
DECLARE_CSR(fflags, CSR_FFLAGS)
DECLARE_CSR(mtohost, CSR_MTOHOST)
DECLARE_CSR(mfromhost, CSR_MFROMHOST)
DECLARE_CSR(mreset, CSR_MRESET)
-DECLARE_CSR(send_ipi, CSR_SEND_IPI)
+DECLARE_CSR(mipi, CSR_MIPI)
+DECLARE_CSR(miobase, CSR_MIOBASE)
DECLARE_CSR(cycleh, CSR_CYCLEH)
DECLARE_CSR(timeh, CSR_TIMEH)
DECLARE_CSR(instreth, CSR_INSTRETH)
tracer.trace(paddr, len, LOAD);
else
refill_tlb(addr, paddr, LOAD);
- } else if (!proc || !proc->sim->mmio_load(addr, len, bytes)) {
+ } else if (!proc || !proc->sim->mmio_load(paddr, len, bytes)) {
throw trap_load_access_fault(addr);
}
}
tracer.trace(paddr, len, STORE);
else
refill_tlb(addr, paddr, STORE);
- } else if (!proc || !proc->sim->mmio_store(addr, len, bytes)) {
+ } else if (!proc || !proc->sim->mmio_store(paddr, len, bytes)) {
throw trap_store_access_fault(addr);
}
}
abort();
}
-void processor_t::parse_isa_string(const char* isa)
+void processor_t::parse_isa_string(const char* str)
{
- const char* p = isa;
- const char* all_subsets = "IMAFDC";
- std::string tmp;
+ std::string lowercase, tmp;
+ for (const char *r = str; *r; r++)
+ lowercase += std::tolower(*r);
+
+ const char* p = lowercase.c_str();
+ const char* all_subsets = "imafdc";
max_xlen = 64;
cpuid = reg_t(2) << 62;
- if (strncmp(p, "RV32", 4) == 0)
+ if (strncmp(p, "rv32", 4) == 0)
max_xlen = 32, cpuid = 0, p += 4;
- else if (strncmp(p, "RV64", 4) == 0)
+ else if (strncmp(p, "rv64", 4) == 0)
p += 4;
- else if (strncmp(p, "RV", 2) == 0)
+ else if (strncmp(p, "rv", 2) == 0)
p += 2;
if (!*p) {
p = all_subsets;
- } else if (*p == 'G') { // treat "G" as "IMAFD"
- tmp = std::string("IMAFD") + (p+1);
+ } else if (*p == 'g') { // treat "G" as "IMAFD"
+ tmp = std::string("imafd") + (p+1);
p = &tmp[0];
- } else if (*p != 'I') {
- bad_isa_string(isa);
+ } else if (*p != 'i') {
+ bad_isa_string(str);
}
- cpuid |= 1L << ('S' - 'A'); // advertise support for supervisor mode
+ isa = "rv" + std::to_string(max_xlen) + p;
+ cpuid |= 1L << ('s' - 'a'); // advertise support for supervisor mode
while (*p) {
- cpuid |= 1L << (*p - 'A');
+ cpuid |= 1L << (*p - 'a');
if (auto next = strchr(all_subsets, *p)) {
all_subsets = next + 1;
p++;
- } else if (*p == 'X') {
+ } else if (*p == 'x') {
const char* ext = p+1, *end = ext;
while (islower(*end))
end++;
register_extension(find_extension(std::string(ext, end - ext).c_str())());
p = end;
} else {
- bad_isa_string(isa);
+ bad_isa_string(str);
}
}
if (supports_extension('D') && !supports_extension('F'))
- bad_isa_string(isa);
+ bad_isa_string(str);
}
void state_t::reset()
t.side_effects(&state); // might set badvaddr etc.
}
-void processor_t::deliver_ipi()
-{
- state.mip |= MIP_MSIP;
-}
-
void processor_t::disasm(insn_t insn)
{
uint64_t bits = insn.bits() & ((1ULL << (8 * insn_length(insn.bits()))) - 1);
state.mip = (state.mip & ~mask) | (val & mask);
break;
}
+ case CSR_MIPI: {
+ state.mip |= MIP_MSIP;
+ break;
+ }
case CSR_MIE: {
reg_t mask = MIP_SSIP | MIP_MSIP | MIP_STIP | MIP_MTIP;
state.mie = (state.mie & ~mask) | (val & mask);
state.mip &= ~MIP_MTIP;
state.mtimecmp = val;
break;
- case CSR_SEND_IPI: sim->send_ipi(val); break;
case CSR_MTOHOST:
if (state.tohost == 0)
state.tohost = val;
case CSR_SSCRATCH: return state.sscratch;
case CSR_MSTATUS: return state.mstatus;
case CSR_MIP: return state.mip;
+ case CSR_MIPI: return 0;
case CSR_MIE: return state.mie;
case CSR_MEPC: return state.mepc;
case CSR_MSCRATCH: return state.mscratch;
case CSR_MFROMHOST:
sim->get_htif()->tick(); // not necessary, but faster
return state.fromhost;
- case CSR_SEND_IPI: return 0;
+ case CSR_MIOBASE: return sim->memsz;
case CSR_UARCH0:
case CSR_UARCH1:
case CSR_UARCH2:
register_insn({0, 0, &illegal_instruction, &illegal_instruction});
build_opcode_map();
}
+
+bool processor_t::load(reg_t addr, size_t len, uint8_t* bytes)
+{
+ try {
+ auto res = get_csr(addr / (max_xlen / 8));
+ memcpy(bytes, &res, len);
+ return true;
+ } catch (trap_illegal_instruction& t) {
+ return false;
+ }
+}
+
+bool processor_t::store(reg_t addr, size_t len, const uint8_t* bytes)
+{
+ try {
+ reg_t value = 0;
+ memcpy(&value, bytes, len);
+ set_csr(addr / (max_xlen / 8), value);
+ return true;
+ } catch (trap_illegal_instruction& t) {
+ return false;
+ }
+}
#include "decode.h"
#include "config.h"
+#include "devices.h"
#include <cstring>
#include <vector>
#include <map>
};
// this class represents one processor in a RISC-V machine.
-class processor_t
+class processor_t : public abstract_device_t
{
public:
processor_t(const char* isa, sim_t* sim, uint32_t id);
void set_histogram(bool value);
void reset(bool value);
void step(size_t n); // run for n cycles
- void deliver_ipi(); // register an interprocessor interrupt
bool running() { return run; }
void set_csr(int which, reg_t val);
void raise_interrupt(reg_t which);
state_t* get_state() { return &state; }
extension_t* get_extension() { return ext; }
bool supports_extension(unsigned char ext) {
+ if (ext >= 'a' && ext <= 'z') ext += 'A' - 'a';
return ext >= 'A' && ext <= 'Z' && ((cpuid >> (ext - 'A')) & 1);
}
void push_privilege_stack();
void register_insn(insn_desc_t);
void register_extension(extension_t*);
+ // MMIO slave interface
+ bool load(reg_t addr, size_t len, uint8_t* bytes);
+ bool store(reg_t addr, size_t len, const uint8_t* bytes);
+
private:
sim_t* sim;
mmu_t* mmu; // main memory is always accessed via the mmu
uint32_t id;
int max_xlen;
int xlen;
+ std::string isa;
bool run; // !reset
bool debug;
bool histogram_enabled;
extensions.cc \
rocc.cc \
regnames.cc \
+ devices.cc \
$(riscv_gen_srcs) \
riscv_test_srcs =
#include "sim.h"
#include "htif.h"
+#include "devicetree.h"
#include <map>
#include <iostream>
#include <climits>
for (size_t i = 0; i < procs.size(); i++)
procs[i] = new processor_t(isa, this, i);
+
+ make_device_tree();
}
sim_t::~sim_t()
free(mem);
}
-void sim_t::send_ipi(reg_t who)
-{
- if (who < procs.size())
- procs[who]->deliver_ipi();
-}
-
reg_t sim_t::get_scr(int which)
{
switch (which)
bool sim_t::mmio_load(reg_t addr, size_t len, uint8_t* bytes)
{
- return false;
+ if (addr + len < addr)
+ return false;
+ return bus.load(addr, len, bytes);
}
bool sim_t::mmio_store(reg_t addr, size_t len, const uint8_t* bytes)
{
- return false;
+ if (addr + len < addr)
+ return false;
+ return bus.store(addr, len, bytes);
+}
+
+void sim_t::make_device_tree()
+{
+ char buf[32];
+ size_t max_devtree_size = procs.size() * 4096; // sloppy upper bound
+ size_t cpu_size = NCSR * procs[0]->max_xlen / 8;
+ reg_t cpu_addr = memsz + max_devtree_size;
+
+ device_tree dt;
+ dt.begin_node("");
+ dt.add_prop("#address-cells", 2);
+ dt.add_prop("#size-cells", 2);
+ dt.add_prop("model", "Spike");
+ dt.begin_node("memory@0");
+ dt.add_prop("device_type", "memory");
+ dt.add_reg({0, memsz});
+ dt.end_node();
+ dt.begin_node("cpus");
+ dt.add_prop("#address-cells", 2);
+ dt.add_prop("#size-cells", 2);
+ for (size_t i = 0; i < procs.size(); i++) {
+ sprintf(buf, "cpu@%" PRIx64, cpu_addr);
+ dt.begin_node(buf);
+ dt.add_prop("device_type", "cpu");
+ dt.add_prop("compatible", "riscv");
+ dt.add_prop("isa", procs[i]->isa);
+ dt.add_reg({cpu_addr});
+ dt.end_node();
+
+ bus.add_device(cpu_addr, procs[i]);
+ cpu_addr += cpu_size;
+ }
+ dt.end_node();
+ dt.end_node();
+
+ devicetree.reset(new rom_device_t(dt.finalize()));
+ bus.add_device(memsz, devicetree.get());
}
#include <memory>
#include "processor.h"
#include "mmu.h"
+#include "devices.h"
class htif_isasim_t;
void set_procs_debug(bool value);
htif_isasim_t* get_htif() { return htif.get(); }
- // deliver an IPI to a specific processor
- void send_ipi(reg_t who);
-
// returns the number of processors in this simulator
size_t num_cores() { return procs.size(); }
processor_t* get_core(size_t i) { return procs.at(i); }
size_t memsz; // memory size in bytes
mmu_t* debug_mmu; // debug port into main memory
std::vector<processor_t*> procs;
+ std::unique_ptr<rom_device_t> devicetree;
+ bus_t bus;
processor_t* get_core(const std::string& i);
void step(size_t n); // step through simulation
// memory-mapped I/O routines
bool mmio_load(reg_t addr, size_t len, uint8_t* bytes);
bool mmio_store(reg_t addr, size_t len, const uint8_t* bytes);
+ void make_device_tree();
// presents a prompt for introspection into the simulation
void interactive();