diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index bc7fddb5..d12aa602 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -28,7 +28,7 @@ jobs: ${{ runner.os }}-${{ env.cache-name }}- - name: Install Dependencies - run: brew tap Homebrew/bundle && brew bundle + run: brew bundle --file=./Brewfile - name: Export the brew path run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 83095eb0..8dd9c50a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,11 @@ cmake_minimum_required(VERSION 3.19) +# CMake 4.0+ rejects subprojects that still declare cmake_minimum_required(VERSION 3.0) +# (e.g. FetchContent glucose). Raise the policy floor so dependency trees configure. +if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0") + set(CMAKE_POLICY_VERSION_MINIMUM "3.5" CACHE STRING "" FORCE) +endif() + # * Macro to exclude the directory from source function(EXCLUDE_FROM_LIST REMOVE_LIST TARGET_LIST) message(STATUS "Exclude the directory - ${${REMOVE_LIST}}") @@ -44,9 +50,30 @@ project( LANGUAGES CXX VERSION 0.3.0) +# This project is configured as CXX-only, so CMAKE_C_COMPILER can be empty. +# External C projects (e.g. abc, vcd-parser) still require a valid C compiler. +if(CMAKE_C_COMPILER) + set(GV_C_COMPILER "${CMAKE_C_COMPILER}") +else() + set(GV_C_COMPILER "cc") +endif() + +if(CMAKE_CXX_COMPILER) + set(GV_CXX_COMPILER "${CMAKE_CXX_COMPILER}") +else() + set(GV_CXX_COMPILER "c++") +endif() + # * Git clokne the ABC repo at build time # * Run the BUILD_COMMAND "make libabc.a" for creating the ABC static library set(LIBABC_NAME libabc.a) +if(APPLE AND CMAKE_OSX_SYSROOT) + set(ABC_CC "${GV_C_COMPILER} --sysroot=${CMAKE_OSX_SYSROOT}") + set(ABC_CXX "${GV_CXX_COMPILER} --sysroot=${CMAKE_OSX_SYSROOT}") +else() + set(ABC_CC "${GV_C_COMPILER}") + set(ABC_CXX "${GV_CXX_COMPILER}") +endif() ExternalProject_Add( engine-abc GIT_REPOSITORY https://github.com/berkeley-abc/abc.git @@ -58,7 +85,7 @@ ExternalProject_Add( INSTALL_COMMAND "" UPDATE_COMMAND "" UPDATE_DISCONNECTED TRUE - BUILD_COMMAND $(MAKE) "CC=${CMAKE_C_COMPILER} --sysroot=${CMAKE_OSX_SYSROOT}" "CXX=${CMAKE_CXX_COMPILER} --sysroot=${CMAKE_OSX_SYSROOT}" ABC_USE_STDINT_H=1 ${LIBABC_NAME} + BUILD_COMMAND $(MAKE) "CC=${ABC_CC}" "CXX=${ABC_CXX}" ABC_USE_STDINT_H=1 ${LIBABC_NAME} LOG_CONFIGURE ON LOG_INSTALL ON # LOG_BUILD ON @@ -118,8 +145,8 @@ ExternalProject_Add( BUILD_COMMAND ${CMAKE_COMMAND} -E env SDKROOT=${CMAKE_OSX_SYSROOT} - CC=${CMAKE_C_COMPILER} - CXX=${CMAKE_CXX_COMPILER} + CC=${GV_C_COMPILER} + CXX=${GV_CXX_COMPILER} $(MAKE) all LOG_CONFIGURE ON LOG_INSTALL ON diff --git a/src/cmd/gvCmdMgr.cpp b/src/cmd/gvCmdMgr.cpp index 3101d8ba..e31d3f33 100644 --- a/src/cmd/gvCmdMgr.cpp +++ b/src/cmd/gvCmdMgr.cpp @@ -4,6 +4,7 @@ #include "gvCmdMgr.h" #include +#include #include #include #include @@ -63,6 +64,21 @@ bool GVCmdExec::lexSingleOption(const string& option, string& token, bool option return true; } +bool GVCmdExec::checkOptionToken(const string& token, const string& optionPattern, size_t mandatoryLen) const { + // Mandatory prefix must exist; optional suffix may be omitted, but any provided + // characters must match the pattern exactly (case-insensitive). + if (mandatoryLen == 0) return false; + if (optionPattern.size() < mandatoryLen) return false; + if (token.size() < mandatoryLen || token.size() > optionPattern.size()) return false; + + for (size_t i = 0; i < token.size(); ++i) { + unsigned char lhs = static_cast(token[i]); + unsigned char rhs = static_cast(optionPattern[i]); + if (tolower(lhs) != tolower(rhs)) return false; + } + return true; +} + GVCmdExecStatus GVCmdExec::errorOption(GVCmdOptionError err, const string& opt) const { switch (err) { diff --git a/src/cmd/gvCmdMgr.h b/src/cmd/gvCmdMgr.h index 74e06a5c..d6ecc777 100644 --- a/src/cmd/gvCmdMgr.h +++ b/src/cmd/gvCmdMgr.h @@ -110,6 +110,7 @@ class GVCmdExec { protected: void lexOptions(const string&, vector&) const; bool lexSingleOption(const string&, string&, bool optional = true) const; + bool checkOptionToken(const string& token, const string& optionPattern, size_t mandatoryLen) const; GVCmdExecStatus errorOption(GVCmdOptionError err, const string& opt) const; private: diff --git a/src/ext/CMakeLists.txt b/src/ext/CMakeLists.txt index da4f775b..1c351cab 100644 --- a/src/ext/CMakeLists.txt +++ b/src/ext/CMakeLists.txt @@ -6,6 +6,12 @@ file(GLOB MY_SOURCES "./*.cc") # Make the plugin library with the .so suffix set(CMAKE_SHARED_LIBRARY_SUFFIX ".so") +# random_sim shells out to compile .sim_main.cpp; use the same CXX as this build (not bare "g++"). +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/sim_random_compile_config.h.in + ${CMAKE_CURRENT_BINARY_DIR}/sim_random_compile_config.h + @ONLY) + # Compile each .cc file into a .so file foreach(source_file ${MY_SOURCES}) # Extract the file name (without path and extension) @@ -13,7 +19,7 @@ foreach(source_file ${MY_SOURCES}) # Compile each .cc file into a .so file add_library(${name} SHARED ${source_file}) set_property(TARGET ${name} PROPERTY POSITION_INDEPENDENT_CODE ON) - target_include_directories(${name} PRIVATE ${YOSYS_DIR}) + target_include_directories(${name} PRIVATE ${YOSYS_DIR} ${CMAKE_CURRENT_BINARY_DIR}) if(APPLE) target_link_options(${name} PRIVATE "-dynamic" "-undefined" "dynamic_lookup") else() diff --git a/src/ext/sim.cc b/src/ext/sim.cc index 86483dc9..c29fbd93 100644 --- a/src/ext/sim.cc +++ b/src/ext/sim.cc @@ -7,6 +7,9 @@ #include #include #include + +#include "sim_random_compile_config.h" + USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN @@ -35,17 +38,20 @@ struct randomSim : public Pass { log(" The reset_n name of the verilog file you want to simulate\n"); log(" Default to be \"reset_n\"\n"); log(" reset when reset_n = 0\n"); - log(" Only one of reset and reset_n should be set\n"); + log(" [-continue]\n"); + log(" Continue from previous cycle and state\n"); + log(" Only one of reset, reset_n and continue should be set\n"); log(" [-output]\n"); log(" The name of output file which contains the simulation result\n"); - log(" [-v]\n"); - log(" verbose print the result of simulation on the command line\n"); + log(" [-Verbose_printing_results]\n"); + log(" verbose print the result of simulation on the command line (also supports -V)\n"); } void execute(std::vector args, Design* design) override { int sim_cycle = 20, property = -1; size_t argidx, num_inputs = 0; bool reset_set = false, property_set = false; bool reset_n_set = false; + bool continue_set = false; bool clk_set = false; bool verbose = false, verilog_file_name_set = false; bool output_file_set = false, top_module_name_set = false, stimulus = false; @@ -67,11 +73,21 @@ struct randomSim : public Pass { if (args[argidx] == "-reset" && argidx + 1 < args.size()) { reset_name = args[++argidx]; reset_set = true; + if ((reset_set ? 1 : 0) + (reset_n_set ? 1 : 0) + (continue_set ? 1 : 0) > 1) + log_error("Only one of -reset, -reset_n and -continue should be set\n"); continue; } if (args[argidx] == "-reset_n" && argidx + 1 < args.size() && reset_set == false) { reset_n_name = args[++argidx]; reset_n_set = true; + if ((reset_set ? 1 : 0) + (reset_n_set ? 1 : 0) + (continue_set ? 1 : 0) > 1) + log_error("Only one of -reset, -reset_n and -continue should be set\n"); + continue; + } + if (args[argidx] == "-continue" || args[argidx] == "-Continue") { + continue_set = true; + if ((reset_set ? 1 : 0) + (reset_n_set ? 1 : 0) + (continue_set ? 1 : 0) > 1) + log_error("Only one of -reset, -reset_n and -continue should be set\n"); continue; } if (args[argidx] == "-clk" && argidx + 1 < args.size()) { @@ -79,8 +95,12 @@ struct randomSim : public Pass { clk_set = true; continue; } - if (args[argidx] == "-v") { - verbose = true; + if (args[argidx].rfind("-V", 0) == 0) { + // "-V" is mandatory substring, and the suffix "erbose_printing_results" is optional. + // Supported examples: "-V", "-Verbose_printing_results". + std::string suffix = args[argidx].substr(2); // after "-V" + if (suffix.empty() || suffix == "erbose_printing_results") + verbose = true; continue; } if (args[argidx] == "-file" && argidx + 1 < args.size()) { @@ -134,6 +154,8 @@ struct randomSim : public Pass { ofs << "#include \n"; ofs << "#include \n"; ofs << "#include \n"; + ofs << "#include \n"; + ofs << "#include \n"; ofs << "#include \n"; ofs << "#include \n"; module_name = log_id(design->top_module()->name); @@ -182,6 +204,49 @@ struct randomSim : public Pass { ofs << "ofs.open(\"" << output_file_name << "\");\n"; } ofs << " cxxrtl_design::p_" + module_name + " top;\n"; + ofs << "long long base_cycle = 0;\n"; + ofs << "const char* state_file = \".sim_state.txt\";\n"; + ofs << "cxxrtl::debug_items state_items;\n"; + ofs << "top.debug_info(state_items);\n"; + if (continue_set) { + ofs << "std::unordered_map state_map;\n"; + ofs << "for (auto &entry : state_items.table) {\n"; + ofs << " for (size_t part = 0; part < entry.second.size(); ++part) {\n"; + ofs << " auto *dbg = &entry.second[part];\n"; + ofs << " if (dbg->next == nullptr) continue;\n"; + ofs << " state_map[entry.first + \"#\" + std::to_string(part)] = dbg;\n"; + ofs << " }\n"; + ofs << "}\n"; + ofs << "std::ifstream state_ifs(state_file);\n"; + ofs << "if (state_ifs.good()) {\n"; + ofs << " std::string line;\n"; + ofs << " while (std::getline(state_ifs, line)) {\n"; + ofs << " if (line.empty()) continue;\n"; + ofs << " std::istringstream iss(line);\n"; + ofs << " std::string key;\n"; + ofs << " iss >> key;\n"; + ofs << " if (key == \"__cycle__\") {\n"; + ofs << " iss >> base_cycle;\n"; + ofs << " continue;\n"; + ofs << " }\n"; + ofs << " auto it = state_map.find(key);\n"; + ofs << " if (it == state_map.end()) continue;\n"; + ofs << " cxxrtl::debug_item* item = it->second;\n"; + ofs << " if (item->next == nullptr) continue;\n"; + ofs << " size_t width = 0, chunks = 0;\n"; + ofs << " iss >> width >> chunks;\n"; + ofs << " if (width != item->width) continue;\n"; + ofs << " for (size_t c = 0; c < chunks; ++c) {\n"; + ofs << " std::string tok;\n"; + ofs << " if (!(iss >> tok)) break;\n"; + ofs << " uint32_t value = static_cast(std::stoul(tok, nullptr, 16));\n"; + ofs << " item->curr[c] = value;\n"; + ofs << " item->next[c] = value;\n"; + ofs << " }\n"; + ofs << " }\n"; + ofs << "}\n"; + ofs << "state_ifs.close();\n"; + } // For VCD file. if (vcd_file_set) { ofs << "cxxrtl::debug_items all_debug_items;\n"; @@ -193,15 +258,17 @@ struct randomSim : public Pass { ofs << "vcd.sample(0);\n"; } - ofs << "top.step();\n"; + if (!continue_set) + ofs << "top.step();\n"; ofs << "for(int cycle=0;cycle<" << sim_cycle << ";++cycle){\n"; + ofs << "long long display_cycle = base_cycle + cycle + 1;\n"; ofs << "top.p_" << clk_name << ".set(false);\n"; ofs << "top.step();\n"; // For VCD file. if (vcd_file_set) ofs << "vcd.sample(cycle*2 + 0);\n"; - if (reset_set || reset_n_set) { + if (!continue_set && (reset_set || reset_n_set)) { ofs << "if(cycle == 0)\n"; if (reset_set) { ofs << " top.p_" << reset_name << ".set(true);\n"; @@ -215,7 +282,10 @@ struct randomSim : public Pass { } } - ofs << "if(cycle > 0)\n"; + if (continue_set) + ofs << "if(cycle >= 0)\n"; + else + ofs << "if(cycle > 0)\n"; ofs << "{\n"; ofs << "size_t idx = 0;\n"; for (auto wire : design->top_module()->wires()) { @@ -300,7 +370,7 @@ struct randomSim : public Pass { if (verbose) { ofs << "cout << \"==========================================\\n\";\n"; ofs << "cout << \"= cycle \"" - << " << cycle + 1 " + << " << display_cycle " << "<< \"\\n\";\n"; ofs << "cout << \"==========================================\\n\";\n"; for (auto wire : design->top_module()->wires()) { @@ -335,7 +405,7 @@ struct randomSim : public Pass { if (output_file_set) { ofs << "ofs << \"==========================================\\n\";\n"; ofs << "ofs << \"= cycle \"" - << " << cycle + 1 " + << " << display_cycle " << "<< \"\\n\";\n"; ofs << "ofs << \"==========================================\\n\";\n"; for (auto wire : design->top_module()->wires()) { @@ -350,6 +420,20 @@ struct randomSim : public Pass { } ofs << "}\n"; + ofs << "std::ofstream state_ofs(state_file);\n"; + ofs << "state_ofs << \"__cycle__ \" << (base_cycle + " << sim_cycle << ") << \"\\n\";\n"; + ofs << "for (auto &entry : state_items.table) {\n"; + ofs << " for (size_t part = 0; part < entry.second.size(); ++part) {\n"; + ofs << " auto &item = entry.second[part];\n"; + ofs << " if (item.depth != 1 || item.curr == nullptr || item.next == nullptr) continue;\n"; + ofs << " size_t chunks = (item.width + 31) / 32;\n"; + ofs << " state_ofs << entry.first << \"#\" << part << \" \" << item.width << \" \" << chunks;\n"; + ofs << " for (size_t c = 0; c < chunks; ++c)\n"; + ofs << " state_ofs << \" \" << std::hex << item.curr[c] << std::dec;\n"; + ofs << " state_ofs << \"\\n\";\n"; + ofs << " }\n"; + ofs << "}\n"; + ofs << "state_ofs.close();\n"; if (output_file_set) ofs << "ofs.close();\n"; ofs << "}\n"; ofs << "\n"; @@ -362,18 +446,24 @@ struct randomSim : public Pass { if (slashPos != std::string::npos) yosysConfig = yosysBin.substr(0, slashPos) + "/yosys-config"; - std::string compileCmd = - " g++ -g -O3 -std=c++14 " - //"-isysroot `xcrun --sdk macosx --show-sdk-path` " - "-I `" + - yosysConfig + - " --datdir`/include " - //"-Wno-vla-cxx-extension -w " - "-w " - ".sim_main.cpp -o .tb "; + // Must use the same toolchain as the GV build: bare "g++" may be Homebrew GCC without + // libc++ paths, while the project is often built with Apple or Homebrew Clang. + std::string compileCmd = "\""; + compileCmd += GV_RANDOM_SIM_CXX; + compileCmd += "\" -g -O3 -std=c++14 "; +#ifdef __APPLE__ + compileCmd += "-stdlib=libc++ "; +#endif + compileCmd += "-I `" + yosysConfig + " --datdir`/include " + "-w " + ".sim_main.cpp -o .tb "; - run_command(compileCmd); - run_command(" ./.tb "); + int compileRet = run_command(compileCmd); + if (compileRet != 0) + log_error("random_sim failed: could not compile generated simulator (.sim_main.cpp).\n"); + int simRet = run_command(" ./.tb "); + if (simRet != 0) + log_error("random_sim failed: simulator process exited abnormally (./.tb).\n"); } } randomSim; diff --git a/src/ext/sim_random_compile_config.h.in b/src/ext/sim_random_compile_config.h.in new file mode 100644 index 00000000..b1167a69 --- /dev/null +++ b/src/ext/sim_random_compile_config.h.in @@ -0,0 +1,3 @@ +#pragma once +/* Generated by CMake: C++ driver used to build GV; random_sim uses it to compile .sim_main.cpp. */ +#define GV_RANDOM_SIM_CXX "@CMAKE_CXX_COMPILER@" diff --git a/src/sim/gvSimCmd.cpp b/src/sim/gvSimCmd.cpp index 2e7615a2..605cc816 100644 --- a/src/sim/gvSimCmd.cpp +++ b/src/sim/gvSimCmd.cpp @@ -30,7 +30,7 @@ bool initSimCmd() { // RAndom Sim //---------------------------------------------------------------------------- GVCmdExecStatus GVRandomSimCmd::exec(const string& option) { - gvMsg(GV_MSG_IFO) << "I am GVRandomSimCmd " << endl; + // gvMsg(GV_MSG_IFO) << "I am GVRandomSimCmd " << endl; if (!cirMgr) { cout << "[ERROR]: Please use command \"CIRRead\" to read the input file first !!\n"; @@ -49,6 +49,7 @@ GVCmdExecStatus GVRandomSimCmd::exec(const string& option) { bool rstSet = false; bool rstNSet = false; bool clkSet = false; + bool continueSet = false; bool outFileSet = false; bool inFileSet = false; bool stimulusSet = false; @@ -69,28 +70,40 @@ GVCmdExecStatus GVRandomSimCmd::exec(const string& option) { for (size_t i = 0; i < n; ++i) { const string& token = options[i]; - if (myStrNCmp("-v", token, 1) == 0) { + if (checkOptionToken(token, "-Verbose_printing_results", 2)) { verbose = true; - command += " -v"; + // Pass canonical long option to random_sim plugin. + command += " -Verbose_printing_results"; continue; } - if (myStrNCmp("-rst_n", token, 4) == 0) { + if (checkOptionToken(token, "-RST_N", 4)) { if (++i >= n) return GVCmdExec::errorOption(GV_CMD_OPT_MISSING, token); rstNSet = true; rst_n = options[i]; command += " -reset_n " + rst_n; + if ((rstSet ? 1 : 0) + (rstNSet ? 1 : 0) + (continueSet ? 1 : 0) > 1) + return GVCmdExec::errorOption(GV_CMD_OPT_ILLEGAL, token); continue; } - if (myStrNCmp("-rst", token, 3) == 0) { + if (checkOptionToken(token, "-RST", 4)) { if (++i >= n) return GVCmdExec::errorOption(GV_CMD_OPT_MISSING, token); rstSet = true; rst = options[i]; command += " -reset " + rst; + if ((rstSet ? 1 : 0) + (rstNSet ? 1 : 0) + (continueSet ? 1 : 0) > 1) + return GVCmdExec::errorOption(GV_CMD_OPT_ILLEGAL, token); continue; } - if (myStrNCmp("-clk", token, 1) == 0) { + if (checkOptionToken(token, "-Continue", 2)) { + continueSet = true; + command += " -continue"; + if ((rstSet ? 1 : 0) + (rstNSet ? 1 : 0) + (continueSet ? 1 : 0) > 1) + return GVCmdExec::errorOption(GV_CMD_OPT_ILLEGAL, token); + continue; + } + if (checkOptionToken(token, "-CLK", 4)) { if (++i >= n) return GVCmdExec::errorOption(GV_CMD_OPT_MISSING, token); clkSet = true; @@ -98,7 +111,7 @@ GVCmdExecStatus GVRandomSimCmd::exec(const string& option) { command += " -clk " + clk; continue; } - if (myStrNCmp("-sim_cycle", token, 1) == 0) { + if (checkOptionToken(token, "-SIM_cycle", 4)) { if (++i >= n) return GVCmdExec::errorOption(GV_CMD_OPT_MISSING, token); simCycleSet = true; @@ -106,7 +119,7 @@ GVCmdExecStatus GVRandomSimCmd::exec(const string& option) { command += " -sim_cycle " + cycles; continue; } - if (myStrNCmp("-input", token, 1) == 0) { + if (checkOptionToken(token, "-INput", 3)) { if (++i >= n) return GVCmdExec::errorOption(GV_CMD_OPT_MISSING, token); inFileName = options[i]; @@ -114,7 +127,7 @@ GVCmdExecStatus GVRandomSimCmd::exec(const string& option) { command += " -input " + inFileName; continue; } - if (myStrNCmp("-output", token, 1) == 0) { + if (checkOptionToken(token, "-OUTput", 4)) { if (++i >= n) return GVCmdExec::errorOption(GV_CMD_OPT_MISSING, token); outFileName = options[i]; @@ -128,7 +141,7 @@ GVCmdExecStatus GVRandomSimCmd::exec(const string& option) { outfile.close(); continue; } - if (myStrNCmp("-file", token, 1) == 0) { + if (checkOptionToken(token, "-File", 2)) { if (++i >= n) return GVCmdExec::errorOption(GV_CMD_OPT_MISSING, token); stimulusFileName = options[i]; @@ -141,7 +154,7 @@ GVCmdExecStatus GVRandomSimCmd::exec(const string& option) { infile.close(); continue; } - if (myStrNCmp("-vcd", token, 4) == 0) { + if (checkOptionToken(token, "-VCD", 4)) { if (++i >= n) return GVCmdExec::errorOption(GV_CMD_OPT_MISSING, token); vcdFileName = options[i]; @@ -176,9 +189,9 @@ GVCmdExecStatus GVRandomSimCmd::exec(const string& option) { void GVRandomSimCmd::usage(const bool& verbose) const { gvMsg(GV_MSG_IFO) - << "Usage: RAndom Sim <-input file_name.v> [-sim_cycle num_cycle_sim] " - "[-rst rst_name] [-rst_n rst_n_name] [-clk clk_name] " - "[-output out_file_name] [-v verbose print result] [-file stimulus]" + << "Usage: RAndom Sim <-INput file_name.v> [-SIM_cycle num_cycle_sim] " + "[-RST rst_name | -RST_N rst_n_name | -Continue] [-CLK clk_name] " + "[-OUTput out_file_name] [-Verbose_printing_results] [-File stimulus]" << endl; }