From 5906a9e93ab4deeee8b56aebd24e25d59d7c7411 Mon Sep 17 00:00:00 2001 From: Yinan Xu Date: Sun, 10 Sep 2023 15:29:00 +0800 Subject: [PATCH] Add support for footprint and in-memory inputs (#145) Since Verilator v4.218 by default uses c++14, the code is compiled with warnings. However, with newer versions of Verilator, they compile without warnings. --- .github/workflows/main.yml | 11 +++ src/test/csrc/common/ram.cpp | 134 ++++++++++++++++++++++++++++++++ src/test/csrc/common/ram.h | 71 +++++++++++++++++ src/test/csrc/verilator/emu.cpp | 28 ++++++- src/test/csrc/verilator/emu.h | 3 + 5 files changed, 246 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index edeca9e43..e80aaf60a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -119,3 +119,14 @@ jobs: make emu ./build/emu -e 0 -i ./ready-to-run/microbench.bin --diff ./ready-to-run/riscv64-nemu-interpreter-so --dump-difftrace microbench ./build/emu -e 0 -i ./ready-to-run/microbench.bin --diff ./ready-to-run/riscv64-nemu-interpreter-so --load-difftrace microbench + + - name: Difftest with Footprints + run: | + cd $GITHUB_WORKSPACE/../xs-env + source ./env.sh + cd $GITHUB_WORKSPACE/../xs-env/NutShell + source ./env.sh + make clean + make emu + ./build/emu -e 0 -i ./ready-to-run/microbench.bin --diff ./ready-to-run/riscv64-nemu-interpreter-so --dump-footprints microbench.bin + ./build/emu -e 0 -i microbench.bin --diff ./ready-to-run/riscv64-nemu-interpreter-so --as-footprints diff --git a/src/test/csrc/common/ram.cpp b/src/test/csrc/common/ram.cpp index d2054392f..0af3e8981 100644 --- a/src/test/csrc/common/ram.cpp +++ b/src/test/csrc/common/ram.cpp @@ -146,6 +146,45 @@ uint64_t StdinReader::read_all(void *dest, uint64_t max_bytes) { return n_read; } +// wim@[base_addr],[wim_size] +uint64_t *SimMemory::is_wim(const char *image, uint64_t &wim_size) { + const char wim_prefix[] = "wim"; + const char *wim_info = strchr(image, '@'); + if (!wim_info || strncmp(wim_prefix, image, sizeof(wim_prefix) - 1)) { + return nullptr; + } + wim_info++; + uint64_t base_addr = strtoul(wim_info, (char **)&wim_info, 16); + if (base_addr % sizeof(uint64_t) || *wim_info != '+') { + return nullptr; + } + wim_info++; + wim_size = strtoul(wim_info, (char **)&wim_info, 16); + if (*wim_info) { + return nullptr; + } + return (uint64_t *)base_addr; +} + +uint64_t WimReader::next() { + if (index + sizeof(uint64_t) > size) { + return 0; + } + uint64_t value = base_addr[index / sizeof(uint64_t)]; + index += sizeof(uint64_t); + return value; +} + +uint64_t WimReader::read_all(void *dest, uint64_t max_bytes) { + uint64_t n_read = size - index; + if (n_read >= max_bytes) { + n_read = max_bytes; + } + memcpy(dest, base_addr, n_read); + index += n_read; + return n_read; +} + FileReader::FileReader(const char *filename) : file(filename, std::ios::binary) { if (!file.is_open()) { std::cerr << "Cannot open '" << filename << "'\n"; @@ -179,6 +218,10 @@ InputReader *SimMemory::createInputReader(const char *image) { if (is_stdin(image)) { return new StdinReader(); } + uint64_t n_bytes; + if (uint64_t *ptr = is_wim(image, n_bytes)) { + return new WimReader(ptr, n_bytes); + } return new FileReader(image); } @@ -277,6 +320,97 @@ void pmem_write(uint64_t waddr, uint64_t wdata) { return ram_write_helper(waddr / sizeof(uint64_t), wdata, -1UL, 1); } +MmapMemoryWithFootprints::MmapMemoryWithFootprints(const char *image, uint64_t n_bytes, const char *footprints_name) + : MmapMemory(image, n_bytes) { + uint64_t n_touched = memory_size / sizeof(uint64_t); + touched = (uint8_t *)mmap(NULL, n_touched, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); + footprints_file.open(footprints_name, std::ios::binary); + if (!footprints_file.is_open()) { + printf("Cannot open %s as the footprints file\n", footprints_name); + assert(0); + } +} + +MmapMemoryWithFootprints::~MmapMemoryWithFootprints() { + munmap(touched, memory_size / sizeof(uint64_t)); + footprints_file.close(); +} + +uint64_t& MmapMemoryWithFootprints::at(uint64_t index) { + uint64_t &data = MmapMemory::at(index); + if (!touched[index]) { + footprints_file.write(reinterpret_cast(&data), sizeof(data)); + touched[index] = 1; + } + return data; +} + +FootprintsMemory::FootprintsMemory(const char *footprints_name, uint64_t n_bytes) + : SimMemory(n_bytes), reader(createInputReader(footprints_name)), n_accessed(0) { + printf("The image is %s\n", footprints_name); + add_callback([this](uint64_t, uint64_t) { this->on_access(this->n_accessed / sizeof(uint64_t)); }); +} + +FootprintsMemory::~FootprintsMemory() { + delete reader; +} + +uint64_t& FootprintsMemory::at(uint64_t index) { + if (ram.find(index) == ram.end()) { + uint64_t value = reader->next(); + ram[index] = value; + for (auto& cb : callbacks) { + cb(index, value); + } + n_accessed += sizeof(uint64_t); + } + return ram[index]; +} + +LinearizedFootprintsMemory::LinearizedFootprintsMemory( + const char *footprints_name, + uint64_t n_bytes, + const char *linear_name) + : FootprintsMemory(footprints_name, n_bytes), linear_name(linear_name), n_touched(0) { + linear_memory = (uint64_t*)mmap(nullptr, n_bytes, + PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if (linear_memory == MAP_FAILED) { + perror("mmap"); + exit(EXIT_FAILURE); + } + add_callback([this](uint64_t index, uint64_t value) { + if (value) { + linear_memory[index] = value; + n_touched++; + } + }); +} + +LinearizedFootprintsMemory::~LinearizedFootprintsMemory() { + save_linear_memory(linear_name); + munmap(linear_memory, get_size()); +} + +void LinearizedFootprintsMemory::save_linear_memory(const char* filename) { + std::ofstream out_file(filename, std::ios::out | std::ios::binary); + if (!out_file) { + std::cerr << "Cannot open output file: " << filename << std::endl; + return; + } + // Find the position of the last non-zero element + uint64_t last_nonzero_index = 0, nonzero_count = 0; + for (uint64_t i = 0; i < get_size() / sizeof(uint64_t) && nonzero_count < n_touched; ++i) { + if (linear_memory[i] != 0) { + last_nonzero_index = i; + nonzero_count++; + } + } + // Even if all all zeros, we still write one uint64_t. + size_t n_bytes = (last_nonzero_index + 1) * sizeof(uint64_t); + out_file.write(reinterpret_cast(linear_memory), n_bytes); + out_file.close(); +} + #ifdef WITH_DRAMSIM3 void dramsim3_init() { #if !defined(DRAMSIM3_CONFIG) || !defined(DRAMSIM3_OUTDIR) diff --git a/src/test/csrc/common/ram.h b/src/test/csrc/common/ram.h index 0405372af..ed2e33ca1 100644 --- a/src/test/csrc/common/ram.h +++ b/src/test/csrc/common/ram.h @@ -52,6 +52,19 @@ class StdinReader : public InputReader { uint64_t n_bytes; }; +class WimReader : public InputReader { +public: + WimReader(uint64_t *addr, uint64_t size) : base_addr(addr), size(size), index(0) {} + ~WimReader() {} + uint64_t len() { return size; }; + uint64_t next(); + uint64_t read_all(void *dest, uint64_t max_bytes = -1ULL); + +private: + uint64_t *base_addr; + uint64_t size, index; +}; + class FileReader : public InputReader { public: FileReader(const char *filename); @@ -126,6 +139,64 @@ class MmapMemory : public SimMemory { uint64_t *as_ptr() { return ram; } }; +class MmapMemoryWithFootprints : public MmapMemory { +private: + uint8_t *touched; + std::ofstream footprints_file; + +public: + MmapMemoryWithFootprints(const char *image, uint64_t n_bytes, const char *footprints_name); + ~MmapMemoryWithFootprints(); + uint64_t& at(uint64_t index); +}; + +class FootprintsMemory : public SimMemory { +private: + std::unordered_map ram; + std::ifstream footprints_file; + std::vector> callbacks; + InputReader *reader; + uint64_t n_accessed; + +protected: + virtual inline uint64_t get_img_size() { + return reader->len(); + } + void add_callback(std::function func) { + callbacks.push_back(func); + } + +public: + FootprintsMemory(const char *footprints_name, uint64_t n_bytes); + ~FootprintsMemory(); + uint64_t& at(uint64_t index); + void clone(std::function func, bool skip_zero = false) { + printf("clone_instant not support by FootprintsMemory\n"); + assert(0); + } + void clone_on_demand(std::function func, bool skip_zero = false) { + auto cb = [func](uint64_t index, uint64_t value) { + func(index * sizeof(uint64_t), &value, sizeof(uint64_t)); + }; + for (const auto& [index, value] : ram) { + cb(index, value); + } + add_callback(cb); + } +}; + +class LinearizedFootprintsMemory : public FootprintsMemory { +private: + const char *linear_name; + uint64_t* linear_memory; + uint64_t n_touched; // for performance opt + +public: + LinearizedFootprintsMemory(const char *footprints_name, uint64_t n_bytes, const char *linear_name); + ~LinearizedFootprintsMemory(); + void save_linear_memory(const char* filename); +}; + extern SimMemory *simMemory; #ifdef WITH_DRAMSIM3 diff --git a/src/test/csrc/verilator/emu.cpp b/src/test/csrc/verilator/emu.cpp index 1a40ac3ad..69683b552 100644 --- a/src/test/csrc/verilator/emu.cpp +++ b/src/test/csrc/verilator/emu.cpp @@ -107,6 +107,9 @@ static inline void print_help(const char *file) { #endif // VM_COVERAGE printf(" --load-difftrace=NAME load from trace NAME\n"); printf(" --dump-difftrace=NAME dump to trace NAME\n"); + printf(" --dump-footprints=NAME dump memory access footprints to NAME\n"); + printf(" --as-footprints load the image as memory access footprints\n"); + printf(" --dump-linearized=NAME dump the linearized footprints to NAME\n"); printf(" -h, --help print program help info\n"); printf("\n"); } @@ -137,6 +140,9 @@ inline EmuArgs parse_args(int argc, const char *argv[]) { { "dump-commit-trace", 0, NULL, 0 }, { "load-difftrace", 1, NULL, 0 }, { "dump-difftrace", 1, NULL, 0 }, + { "dump-footprints", 1, NULL, 0 }, + { "as-footprints", 0, NULL, 0 }, + { "dump-linearized", 1, NULL, 0 }, { "seed", 1, NULL, 's' }, { "max-cycles", 1, NULL, 'C' }, { "fork-interval", 1, NULL, 'X' }, @@ -204,6 +210,9 @@ inline EmuArgs parse_args(int argc, const char *argv[]) { case 16: args.enable_commit_trace = true; continue; case 17: args.trace_name = optarg; args.trace_is_read = true; continue; case 18: args.trace_name = optarg; args.trace_is_read = false; continue; + case 19: args.footprints_name = optarg; continue; + case 20: args.image_as_footprints = true; continue; + case 21: args.linearized_name = optarg; continue; } // fall through default: @@ -316,7 +325,24 @@ Emulator::Emulator(int argc, const char *argv[]): if (args.ram_size) { ram_size = parse_and_update_ramsize(args.ram_size); } - simMemory = new MmapMemory(args.image, ram_size); + // footprints + if (args.image_as_footprints) { + if (args.linearized_name) { + simMemory = new LinearizedFootprintsMemory(args.image, ram_size, args.linearized_name); + } + else { + simMemory = new FootprintsMemory(args.image, ram_size); + } + } + // normal linear memory + else { + if (args.footprints_name) { + simMemory = new MmapMemoryWithFootprints(args.image, ram_size, args.footprints_name); + } + else { + simMemory = new MmapMemory(args.image, ram_size); + } + } #ifdef ENABLE_CHISEL_DB init_db(args.dump_db, (args.select_db != NULL), args.select_db); diff --git a/src/test/csrc/verilator/emu.h b/src/test/csrc/verilator/emu.h index 9f08d1ddd..696304fae 100644 --- a/src/test/csrc/verilator/emu.h +++ b/src/test/csrc/verilator/emu.h @@ -61,6 +61,8 @@ struct EmuArgs { const char *flash_bin = nullptr; const char *select_db = nullptr; const char *trace_name = nullptr; + const char *footprints_name = nullptr; + const char *linearized_name = nullptr; bool enable_waveform = false; bool enable_ref_trace = false; bool enable_commit_trace = false; @@ -73,6 +75,7 @@ struct EmuArgs { bool dump_db = false; bool trace_is_read = true; bool dump_coverage = false; + bool image_as_footprints = false; }; enum {