From b119073ab013f387000435ba5a55a3026fc8a8d9 Mon Sep 17 00:00:00 2001 From: Andrew Waterman Date: Wed, 13 Feb 2013 12:59:53 -0800 Subject: [PATCH] add I$/D$/L2$ simulators --- riscv/cachesim.cc | 129 +++++++++++++++++++++++++++++++++++++++++ riscv/cachesim.h | 102 ++++++++++++++++++++++++++++++++ riscv/icsim.cc | 100 -------------------------------- riscv/icsim.h | 55 ------------------ riscv/memtracer.h | 46 +++++++++++++++ riscv/mmu.cc | 27 +++++++-- riscv/mmu.h | 38 +++++++----- riscv/processor.h | 1 + riscv/riscv-isa-run.cc | 66 ++++++++++++--------- riscv/riscv.mk.in | 4 +- riscv/sim.h | 1 + 11 files changed, 363 insertions(+), 206 deletions(-) create mode 100644 riscv/cachesim.cc create mode 100644 riscv/cachesim.h delete mode 100644 riscv/icsim.cc delete mode 100644 riscv/icsim.h create mode 100644 riscv/memtracer.h diff --git a/riscv/cachesim.cc b/riscv/cachesim.cc new file mode 100644 index 0000000..d029c29 --- /dev/null +++ b/riscv/cachesim.cc @@ -0,0 +1,129 @@ +#include "cachesim.h" +#include +#include +#include + +cache_sim_t::cache_sim_t(size_t _sets, size_t _ways, size_t _linesz, const char* _name) + : sets(_sets), ways(_ways), linesz(_linesz), name(_name) +{ + init(); +} + +static void help() +{ + std::cerr << "Cache configurations must be of the form" << std::endl; + std::cerr << " sets:ways:blocksize" << std::endl; + std::cerr << "where sets, ways, and blocksize are positive integers, with" << std::endl; + std::cerr << "sets and blocksize both powers of two and blocksize at least 8." << std::endl; + exit(1); +} + +cache_sim_t::cache_sim_t(const char* config, const char* _name) + : name(_name) +{ + const char* wp = strchr(config, ':'); + if (!wp++) help(); + const char* bp = strchr(wp, ':'); + if (!bp++) help(); + + sets = atoi(std::string(config, wp).c_str()); + ways = atoi(std::string(wp, bp).c_str()); + linesz = atoi(bp); + + init(); +} + +void cache_sim_t::init() +{ + if(sets == 0 || (sets & (sets-1))) + help(); + if(linesz < 8 || (linesz & (linesz-1))) + help(); + + idx_shift = 0; + for (size_t x = linesz; x; x >>= 1) + idx_shift++; + + tags = new uint64_t[sets*ways](); + read_accesses = 0; + read_misses = 0; + bytes_read = 0; + write_accesses = 0; + write_misses = 0; + bytes_written = 0; + writebacks = 0; + + miss_handler = NULL; +} + +cache_sim_t::cache_sim_t(const cache_sim_t& rhs) + : sets(rhs.sets), ways(rhs.ways), linesz(rhs.linesz), + idx_shift(rhs.idx_shift), name(rhs.name) +{ + tags = new uint64_t[sets*ways]; + memcpy(tags, rhs.tags, sets*ways*sizeof(uint64_t)); +} + +cache_sim_t::~cache_sim_t() +{ + print_stats(); + delete [] tags; +} + +void cache_sim_t::print_stats() +{ + if(read_accesses + write_accesses == 0) + return; + + float mr = 100.0f*(read_misses+write_misses)/(read_accesses+write_accesses); + + std::cout << std::setprecision(3) << std::fixed; + std::cout << name << " "; + std::cout << "Bytes Read: " << bytes_read << std::endl; + std::cout << name << " "; + std::cout << "Bytes Written: " << bytes_written << std::endl; + std::cout << name << " "; + std::cout << "Read Accesses: " << read_accesses << std::endl; + std::cout << name << " "; + std::cout << "Write Accesses: " << write_accesses << std::endl; + std::cout << name << " "; + std::cout << "Read Misses: " << read_misses << std::endl; + std::cout << name << " "; + std::cout << "Write Misses: " << write_misses << std::endl; + std::cout << name << " "; + std::cout << "Writebacks: " << writebacks << std::endl; + std::cout << name << " "; + std::cout << "Miss Rate: " << mr << '%' << std::endl; +} + +void cache_sim_t::access(uint64_t addr, size_t bytes, bool store) +{ + store ? write_accesses++ : read_accesses++; + (store ? bytes_written : bytes_read) += bytes; + + size_t idx = (addr >> idx_shift) & (sets-1); + size_t tag = (addr >> idx_shift) | VALID; + size_t* set_tags = &tags[idx*ways]; + + for(size_t i = 0; i < ways; i++) + { + if(tag == (set_tags[i] & ~DIRTY)) // hit + { + if(store) + set_tags[i] |= DIRTY; + return; + } + } + + store ? write_misses++ : read_misses++; + + size_t way = lfsr.next() % ways; + if((set_tags[way] & (VALID | DIRTY)) == (VALID | DIRTY)) + { + uint64_t dirty_addr = (set_tags[way] & ~(VALID | DIRTY)) << idx_shift; + if (miss_handler) miss_handler->access(dirty_addr, linesz, true); + writebacks++; + } + if (miss_handler) miss_handler->access(addr & ~(linesz-1), linesz, false); + set_tags[way] = tag | (store ? DIRTY : 0); +} diff --git a/riscv/cachesim.h b/riscv/cachesim.h new file mode 100644 index 0000000..d4f4fb4 --- /dev/null +++ b/riscv/cachesim.h @@ -0,0 +1,102 @@ +#ifndef _RISCV_CACHE_SIM_H +#define _RISCV_CACHE_SIM_H + +#include "memtracer.h" +#include +#include +#include + +class lfsr_t +{ + public: + lfsr_t() : reg(1) {} + lfsr_t(const lfsr_t& lfsr) : reg(lfsr.reg) {} + uint32_t next() { return reg = (reg>>1)^(-(reg&1) & 0xd0000001); } + private: + uint32_t reg; +}; + +class cache_sim_t +{ + public: + cache_sim_t(const char* config, const char* name); + cache_sim_t(size_t sets, size_t ways, size_t linesz, const char* name); + cache_sim_t(const cache_sim_t& rhs); + ~cache_sim_t(); + + void access(uint64_t addr, size_t bytes, bool store); + void print_stats(); + void set_miss_handler(cache_sim_t* mh) { miss_handler = mh; } + + private: + lfsr_t lfsr; + cache_sim_t* miss_handler; + + size_t sets; + size_t ways; + size_t linesz; + size_t idx_shift; + + uint64_t* tags; + + uint64_t read_accesses; + uint64_t read_misses; + uint64_t bytes_read; + uint64_t write_accesses; + uint64_t write_misses; + uint64_t bytes_written; + uint64_t writebacks; + + std::string name; + + static const uint64_t VALID = 1ULL << 63; + static const uint64_t DIRTY = 1ULL << 62; + + void init(); +}; + +class unified_cache_sim_t : public memtracer_t +{ + public: + unified_cache_sim_t(const char* config) : cache(config, "U$") {} + bool interested_in_range(size_t begin, size_t end, bool store, bool fetch) + { + return true; + } + void trace(uint64_t addr, size_t bytes, bool store, bool fetch) + { + cache.access(addr, bytes, store); + } + private: + cache_sim_t cache; +}; + +class icache_sim_t : public memtracer_t, public cache_sim_t +{ + public: + icache_sim_t(const char* config) : cache_sim_t(config, "I$") {} + bool interested_in_range(size_t begin, size_t end, bool store, bool fetch) + { + return fetch; + } + void trace(uint64_t addr, size_t bytes, bool store, bool fetch) + { + if (fetch) access(addr, bytes, false); + } +}; + +class dcache_sim_t : public memtracer_t, public cache_sim_t +{ + public: + dcache_sim_t(const char* config) : cache_sim_t(config, "D$") {} + bool interested_in_range(size_t begin, size_t end, bool store, bool fetch) + { + return !fetch; + } + void trace(uint64_t addr, size_t bytes, bool store, bool fetch) + { + if (!fetch) access(addr, bytes, store); + } +}; + +#endif diff --git a/riscv/icsim.cc b/riscv/icsim.cc deleted file mode 100644 index 308e7ed..0000000 --- a/riscv/icsim.cc +++ /dev/null @@ -1,100 +0,0 @@ -#include "icsim.h" -#include -#include -#include - -icsim_t::icsim_t(size_t _sets, size_t _ways, size_t _linesz, const char* _name) - : sets(_sets), ways(_ways), linesz(_linesz), idx_mask(_sets-1), name(_name) -{ - if(sets == 0 || (sets & (sets-1))) - throw std::logic_error("sets not a power of 2"); - if(linesz == 0 || (linesz & (linesz-1))) - throw std::logic_error("linesz not a power of 2"); - - idx_shift = 0; - while(_linesz >>= 1) - idx_shift++; - - tags = new uint64_t[sets*ways]; - memset(tags, 0, sets*ways*sizeof(uint64_t)); - - read_accesses = 0; - read_misses = 0; - bytes_read = 0; - write_accesses = 0; - write_misses = 0; - bytes_written = 0; - writebacks = 0; -} - -icsim_t::icsim_t(const icsim_t& rhs) - : sets(rhs.sets), ways(rhs.ways), linesz(rhs.linesz), - idx_shift(rhs.idx_shift), idx_mask(rhs.idx_mask), name(rhs.name) -{ - tags = new uint64_t[sets*ways]; - memcpy(tags, rhs.tags, sets*ways*sizeof(uint64_t)); -} - -icsim_t::~icsim_t() -{ - delete [] tags; -} - -void icsim_t::print_stats() -{ - if(read_accesses + write_accesses == 0) - return; - - float mr = 100.0f*(read_misses+write_misses)/(read_accesses+write_accesses); - - std::cout << std::setprecision(3) << std::fixed; - std::cout << name << " "; - std::cout << "Bytes Read: " << bytes_read << std::endl; - std::cout << name << " "; - std::cout << "Bytes Written: " << bytes_written << std::endl; - std::cout << name << " "; - std::cout << "Read Accesses: " << read_accesses << std::endl; - std::cout << name << " "; - std::cout << "Write Accesses: " << write_accesses << std::endl; - std::cout << name << " "; - std::cout << "Read Misses: " << read_misses << std::endl; - std::cout << name << " "; - std::cout << "Write Misses: " << write_misses << std::endl; - std::cout << name << " "; - std::cout << "Writebacks: " << writebacks << std::endl; - std::cout << name << " "; - std::cout << "Miss Rate: " << mr << '%' << std::endl; - - float cr = read_accesses == 0 ? 0.0f : 100.0f*bytes_read/(4*read_accesses); - if(name == "I$") - { - std::cout << name << " "; - std::cout << "RVC compression ratio: " << cr << '%' << std::endl; - } -} - -void icsim_t::tick(uint64_t pc, int insnlen, bool store) -{ - store ? write_accesses++ : read_accesses++; - (store ? bytes_written : bytes_read) += insnlen; - - size_t idx = (pc >> idx_shift) & idx_mask; - size_t tag = (pc >> idx_shift) | VALID; - - for(size_t i = 0; i < ways; i++) - { - if(tag == (tags[idx + i*sets] & ~DIRTY)) // hit - { - if(store) - tags[idx + i*sets] |= DIRTY; - return; - } - } - - store ? write_misses++ : read_misses++; - - size_t way = lfsr.next() % ways; - if((tags[idx + way*sets] & (VALID | DIRTY)) == (VALID | DIRTY)) - writebacks++; - tags[idx + way*sets] = tag; -} diff --git a/riscv/icsim.h b/riscv/icsim.h deleted file mode 100644 index ec6eae4..0000000 --- a/riscv/icsim.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef _RISCV_ICSIM_H -#define _RISCV_ICSIM_H - -// this file models a simple cache to estimate hit/miss rates. -// it is currently unused. - -#include -#include -#include - -class lfsr_t -{ -public: - lfsr_t() : reg(1) {} - lfsr_t(const lfsr_t& lfsr) : reg(lfsr.reg) {} - uint32_t next() { return reg = (reg>>1)^(-(reg&1) & 0xd0000001); } -private: - uint32_t reg; -}; - -class icsim_t -{ -public: - icsim_t(size_t sets, size_t ways, size_t linesz, const char* name); - icsim_t(const icsim_t& rhs); - ~icsim_t(); - - void tick(uint64_t pc, int insnlen, bool store); - void print_stats(); -private: - lfsr_t lfsr; - - size_t sets; - size_t ways; - size_t linesz; - size_t idx_shift; - size_t idx_mask; - - uint64_t* tags; - - uint64_t read_accesses; - uint64_t read_misses; - uint64_t bytes_read; - uint64_t write_accesses; - uint64_t write_misses; - uint64_t bytes_written; - uint64_t writebacks; - - std::string name; - - static const uint64_t VALID = 1ULL << 63; - static const uint64_t DIRTY = 1ULL << 62; -}; - -#endif diff --git a/riscv/memtracer.h b/riscv/memtracer.h new file mode 100644 index 0000000..ed62be5 --- /dev/null +++ b/riscv/memtracer.h @@ -0,0 +1,46 @@ +#ifndef _MEMTRACER_H +#define _MEMTRACER_H + +#include +#include +#include + +class memtracer_t +{ + public: + memtracer_t() : link(NULL) {} + virtual ~memtracer_t() {} + + virtual bool interested_in_range(uint64_t begin, uint64_t end, bool store, bool fetch) = 0; + virtual void trace(uint64_t addr, size_t bytes, bool store, bool fetch) = 0; + + protected: + + private: + memtracer_t* link; +}; + +class memtracer_list_t : public memtracer_t +{ + public: + bool interested_in_range(uint64_t begin, uint64_t end, bool store, bool fetch) + { + for (std::vector::iterator it = list.begin(); it != list.end(); ++it) + if ((*it)->interested_in_range(begin, end, store, fetch)) + return true; + return false; + } + void trace(uint64_t addr, size_t bytes, bool store, bool fetch) + { + for (std::vector::iterator it = list.begin(); it != list.end(); ++it) + (*it)->trace(addr, bytes, store, fetch); + } + void hook(memtracer_t* h) + { + list.push_back(h); + } + private: + std::vector list; +}; + +#endif diff --git a/riscv/mmu.cc b/riscv/mmu.cc index c8eec16..6782ae2 100644 --- a/riscv/mmu.cc +++ b/riscv/mmu.cc @@ -27,7 +27,7 @@ void mmu_t::flush_icache() memset(icache_tag, -1, sizeof(icache_tag)); } -void* mmu_t::refill(reg_t addr, bool store, bool fetch) +void* mmu_t::refill(reg_t addr, reg_t bytes, bool store, bool fetch) { reg_t idx = (addr >> PGSHIFT) % TLB_ENTRIES; reg_t expected_tag = addr & ~(PGSIZE-1); @@ -49,12 +49,21 @@ void* mmu_t::refill(reg_t addr, bool store, bool fetch) throw store ? trap_store_access_fault : trap_load_access_fault; } - tlb_load_tag[idx] = (pte_perm & PTE_UR) ? expected_tag : -1; - tlb_store_tag[idx] = (pte_perm & PTE_UW) ? expected_tag : -1; - tlb_insn_tag[idx] = (pte_perm & PTE_UX) ? expected_tag : -1; - tlb_data[idx] = (long)(pte >> PTE_PPN_SHIFT << PGSHIFT) + (long)mem; + reg_t pgoff = addr & (PGSIZE-1); + reg_t pgbase = pte >> PTE_PPN_SHIFT << PGSHIFT; + reg_t paddr = pgbase + pgoff; - return (void*)(((long)addr & (PGSIZE-1)) + tlb_data[idx]); + if (unlikely(tracer.interested_in_range(pgbase, pgbase + PGSIZE, store, fetch))) + tracer.trace(paddr, bytes, store, fetch); + else + { + tlb_load_tag[idx] = (pte_perm & PTE_UR) ? expected_tag : -1; + tlb_store_tag[idx] = (pte_perm & PTE_UW) ? expected_tag : -1; + tlb_insn_tag[idx] = (pte_perm & PTE_UX) ? expected_tag : -1; + tlb_data[idx] = (char*)mem + pgbase; + } + + return (char*)mem + paddr; } pte_t mmu_t::walk(reg_t addr) @@ -107,3 +116,9 @@ pte_t mmu_t::walk(reg_t addr) return pte; } + +void mmu_t::register_memtracer(memtracer_t* t) +{ + flush_tlb(); + tracer.hook(t); +} diff --git a/riscv/mmu.h b/riscv/mmu.h index f6ca972..2420878 100644 --- a/riscv/mmu.h +++ b/riscv/mmu.h @@ -6,6 +6,8 @@ #include "common.h" #include "config.h" #include "processor.h" +#include "memtracer.h" +#include class processor_t; @@ -49,7 +51,7 @@ public: badvaddr = addr; \ throw trap_load_address_misaligned; \ } \ - void* paddr = translate(addr, false, false); \ + void* paddr = translate(addr, sizeof(type##_t), false, false); \ return *(type##_t*)paddr; \ } @@ -73,7 +75,7 @@ public: badvaddr = addr; \ throw trap_store_address_misaligned; \ } \ - void* paddr = translate(addr, true, false); \ + void* paddr = translate(addr, sizeof(type##_t), true, false); \ *(type##_t*)paddr = val; \ } @@ -97,7 +99,7 @@ public: #ifdef RISCV_ENABLE_RVC if(addr % 4 == 2 && rvc) // fetch across word boundary { - void* addr_lo = translate(addr, false, true); + void* addr_lo = translate(addr, 2, false, true); insn.bits = *(uint16_t*)addr_lo; *func = processor_t::dispatch_table @@ -105,7 +107,7 @@ public: if(!INSN_IS_RVC(insn.bits)) { - void* addr_hi = translate(addr+2, false, true); + void* addr_hi = translate(addr+2, 2, false, true); insn.bits |= (uint32_t)*(uint16_t*)addr_hi << 16; } } @@ -119,13 +121,17 @@ public: return data; // the processor guarantees alignment based upon rvc mode - void* paddr = translate(addr, false, true); + void* paddr = translate(addr, sizeof(insn_t), false, true); insn = *(insn_t*)paddr; + *func = processor_t::dispatch_table + [insn.bits % processor_t::DISPATCH_TABLE_SIZE]; - icache_tag[idx] = addr; - icache_data[idx] = insn; - icache_func[idx] = *func = processor_t::dispatch_table - [insn.bits % processor_t::DISPATCH_TABLE_SIZE]; + if (!tracer.interested_in_range(addr, addr + sizeof(insn_t), false, true)) + { + icache_tag[idx] = addr; + icache_data[idx] = insn; + icache_func[idx] = *func; + } } return insn; @@ -146,18 +152,20 @@ public: void flush_tlb(); void flush_icache(); + void register_memtracer(memtracer_t*); + private: char* mem; size_t memsz; reg_t badvaddr; - reg_t ptbr; bool supervisor; bool vm_enabled; + memtracer_list_t tracer; // implement a TLB for simulator performance static const reg_t TLB_ENTRIES = 256; - long tlb_data[TLB_ENTRIES]; + char* tlb_data[TLB_ENTRIES]; reg_t tlb_insn_tag[TLB_ENTRIES]; reg_t tlb_load_tag[TLB_ENTRIES]; reg_t tlb_store_tag[TLB_ENTRIES]; @@ -169,22 +177,22 @@ private: reg_t icache_tag[ICACHE_ENTRIES]; // finish translation on a TLB miss and upate the TLB - void* refill(reg_t addr, bool store, bool fetch); + void* refill(reg_t addr, reg_t bytes, bool store, bool fetch); // perform a page table walk for a given virtual address pte_t walk(reg_t addr); // translate a virtual address to a physical address - void* translate(reg_t addr, bool store, bool fetch) + void* translate(reg_t addr, reg_t bytes, bool store, bool fetch) { reg_t idx = (addr >> PGSHIFT) % TLB_ENTRIES; reg_t* tlb_tag = fetch ? tlb_insn_tag : store ? tlb_store_tag :tlb_load_tag; reg_t expected_tag = addr & ~(PGSIZE-1); if(likely(tlb_tag[idx] == expected_tag)) - return (void*)(((long)addr & (PGSIZE-1)) + tlb_data[idx]); + return ((uintptr_t)addr & (PGSIZE-1)) + tlb_data[idx]; - return refill(addr, store, fetch); + return refill(addr, bytes, store, fetch); } friend class processor_t; diff --git a/riscv/processor.h b/riscv/processor.h index 168f95e..d086760 100644 --- a/riscv/processor.h +++ b/riscv/processor.h @@ -26,6 +26,7 @@ public: bool running() { return run; } void set_pcr(int which, reg_t val); reg_t get_pcr(int which); + mmu_t* get_mmu() { return &mmu; } private: sim_t& sim; diff --git a/riscv/riscv-isa-run.cc b/riscv/riscv-isa-run.cc index 156ae82..7b79945 100644 --- a/riscv/riscv-isa-run.cc +++ b/riscv/riscv-isa-run.cc @@ -1,52 +1,60 @@ #include "sim.h" #include "htif.h" +#include "cachesim.h" +#include #include #include +#include #include #include +#include static void help() { fprintf(stderr, "usage: riscv-isa-run [host options] [target options]\n"); fprintf(stderr, "Host Options:\n"); - fprintf(stderr, " -p Simulate processors\n"); - fprintf(stderr, " -m Provide MB of target memory\n"); - fprintf(stderr, " -d Interactive debug mode\n"); - fprintf(stderr, " -h Print this help message\n"); + fprintf(stderr, " -p Simulate processors\n"); + fprintf(stderr, " -m Provide MB of target memory\n"); + fprintf(stderr, " -d Interactive debug mode\n"); + fprintf(stderr, " -h Print this help message\n"); + fprintf(stderr, " -h Print this help message\n"); + fprintf(stderr, " --ic=:: Instantiate a cache model with S sets,\n"); + fprintf(stderr, " --dc=:: W ways, and B-byte blocks (with S and\n"); + fprintf(stderr, " --l2=:: B both powers of 2).\n"); exit(1); } int main(int argc, char** argv) { bool debug = false; - int nprocs = 1; - int mem_mb = 0; + size_t nprocs = 1; + size_t mem_mb = 0; + std::unique_ptr ic; + std::unique_ptr dc; + std::unique_ptr l2; - // parse command-line arguments - for(int c; (c = getopt(argc,argv,"hdp:m:")) != -1; ) - { - switch(c) - { - case 'd': - debug = true; - break; - case 'p': - nprocs = atoi(optarg); - break; - case 'm': - mem_mb = atoi(optarg); - break; - default: - fprintf(stderr, "unknown option: -%c", optopt); - case 'h': - help(); - } - } + option_parser_t parser; + parser.help(&help); + parser.option('d', 0, 0, [&](const char* s){debug = true;}); + parser.option('p', 0, 1, [&](const char* s){nprocs = atoi(s);}); + parser.option('m', 0, 1, [&](const char* s){mem_mb = atoi(s);}); + parser.option(0, "ic", 1, [&](const char* s){ic.reset(new icache_sim_t(s));}); + parser.option(0, "dc", 1, [&](const char* s){dc.reset(new dcache_sim_t(s));}); + parser.option(0, "l2", 1, [&](const char* s){l2.reset(new cache_sim_t(s, "L2$"));}); - if (optind == argc) + auto argv1 = parser.parse(argv); + if (!*argv1) help(); - - std::vector htif_args(argv + optind, argv + argc); + std::vector htif_args(argv1, (const char*const*)argv + argc); sim_t s(nprocs, mem_mb, htif_args); + + if (ic && l2) ic->set_miss_handler(&*l2); + if (dc && l2) dc->set_miss_handler(&*l2); + for (size_t i = 0; i < nprocs; i++) + { + if (ic) s.get_core(i)->get_mmu()->register_memtracer(&*ic); + if (dc) s.get_core(i)->get_mmu()->register_memtracer(&*dc); + } + s.run(debug); } diff --git a/riscv/riscv.mk.in b/riscv/riscv.mk.in index 1abbc46..bf6e07a 100644 --- a/riscv/riscv.mk.in +++ b/riscv/riscv.mk.in @@ -13,6 +13,8 @@ riscv_hdrs = \ opcodes.h \ insn_header.h \ dispatch.h \ + cachesim.h \ + memtracer.h \ NDISPATCH := 10 DISPATCH_SRCS := \ @@ -40,7 +42,7 @@ riscv_srcs = \ sim.cc \ interactive.cc \ trap.cc \ - icsim.cc \ + cachesim.cc \ mmu.cc \ disasm.cc \ $(DISPATCH_SRCS) \ diff --git a/riscv/sim.h b/riscv/sim.h index 085eb68..aed4e87 100644 --- a/riscv/sim.h +++ b/riscv/sim.h @@ -23,6 +23,7 @@ public: // returns the number of processors in this simulator size_t num_cores() { return procs.size(); } + processor_t* get_core(size_t i) { return procs[i]; } // read one of the system control registers reg_t get_scr(int which); -- 2.30.2