From 565927dfcc1be506aec4757c42e7c192a90f9246 Mon Sep 17 00:00:00 2001 From: gatecat Date: Tue, 12 Sep 2023 17:29:26 +0200 Subject: [PATCH] himbaechel: Add discovery of uarch and chipdb Signed-off-by: gatecat --- .github/ci/build_himbaechel.sh | 5 +- common/kernel/command.cc | 186 ++++++++++++++++++++++++++ common/kernel/command.h | 6 + himbaechel/arch.cc | 87 ++++++------ himbaechel/arch.h | 6 +- himbaechel/arch_pybindings.cc | 2 +- himbaechel/himbaechel_api.cc | 12 +- himbaechel/himbaechel_api.h | 11 +- himbaechel/main.cc | 32 +++-- himbaechel/uarch/example/example.cc | 11 +- himbaechel/uarch/gowin/CMakeLists.txt | 8 +- himbaechel/uarch/gowin/gowin.cc | 60 ++++++--- 12 files changed, 332 insertions(+), 94 deletions(-) diff --git a/.github/ci/build_himbaechel.sh b/.github/ci/build_himbaechel.sh index e8adaf8d..73db3b57 100644 --- a/.github/ci/build_himbaechel.sh +++ b/.github/ci/build_himbaechel.sh @@ -12,7 +12,8 @@ function build_nextpnr { # We'd ideally use pypy3 for speed (as works locally), but the version # our CI Ubuntu provides doesn't like some of the typing stuff python3 ../himbaechel/uarch/example/example_arch_gen.py ./example.bba - ./bba/bbasm --l ./example.bba ./example.bin + mkdir -p share/himbaechel/example + ./bba/bbasm --l ./example.bba share/himbaechel/example/example.bin popd } @@ -22,6 +23,6 @@ function run_tests { function run_archcheck { pushd build - ./nextpnr-himbaechel --uarch example --chipdb ./example.bin --test + ./nextpnr-himbaechel --device EXAMPLE --test popd } diff --git a/common/kernel/command.cc b/common/kernel/command.cc index aa070450..ceb058c8 100644 --- a/common/kernel/command.cc +++ b/common/kernel/command.cc @@ -45,8 +45,188 @@ #include "util.h" #include "version.h" +#if defined(_WIN32) +#include +#include +#elif defined(__APPLE__) +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif + +#ifdef __FreeBSD__ +#include +#endif + NEXTPNR_NAMESPACE_BEGIN +static std::string npnr_share_dirname; + +#ifdef _WIN32 +bool check_file_exists(std::string filename, bool) { return _access(filename.c_str(), 0) == 0; } +#else +bool check_file_exists(std::string filename, bool is_exec) +{ + return access(filename.c_str(), is_exec ? X_OK : F_OK) == 0; +} +#endif + +#if defined(__linux__) || defined(__CYGWIN__) +std::string proc_self_dirname() +{ + char path[PATH_MAX]; + ssize_t buflen = readlink("/proc/self/exe", path, sizeof(path)); + if (buflen < 0) { + log_error("readlink(\"/proc/self/exe\") failed: %s\n", strerror(errno)); + } + while (buflen > 0 && path[buflen - 1] != '/') + buflen--; + return std::string(path, buflen); +} +#elif defined(__FreeBSD__) +std::string proc_self_dirname() +{ + int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}; + size_t buflen; + char *buffer; + std::string path; + if (sysctl(mib, 4, NULL, &buflen, NULL, 0) != 0) + log_error("sysctl failed: %s\n", strerror(errno)); + buffer = (char *)malloc(buflen); + if (buffer == NULL) + log_error("malloc failed: %s\n", strerror(errno)); + if (sysctl(mib, 4, buffer, &buflen, NULL, 0) != 0) + log_error("sysctl failed: %s\n", strerror(errno)); + while (buflen > 0 && buffer[buflen - 1] != '/') + buflen--; + path.assign(buffer, buflen); + free(buffer); + return path; +} +#elif defined(__APPLE__) +std::string proc_self_dirname() +{ + char *path = NULL; + uint32_t buflen = 0; + while (_NSGetExecutablePath(path, &buflen) != 0) + path = (char *)realloc((void *)path, buflen); + while (buflen > 0 && path[buflen - 1] != '/') + buflen--; + std::string str(path, buflen); + free(path); + return str; +} +#elif defined(_WIN32) +std::string proc_self_dirname() +{ + int i = 0; +#ifdef __MINGW32__ + char longpath[MAX_PATH + 1]; + char shortpath[MAX_PATH + 1]; +#else + WCHAR longpath[MAX_PATH + 1]; + TCHAR shortpath[MAX_PATH + 1]; +#endif + if (!GetModuleFileName(0, longpath, MAX_PATH + 1)) + log_error("GetModuleFileName() failed.\n"); + if (!GetShortPathName(longpath, shortpath, MAX_PATH + 1)) + log_error("GetShortPathName() failed.\n"); + while (shortpath[i] != 0) + i++; + while (i > 0 && shortpath[i - 1] != '/' && shortpath[i - 1] != '\\') + shortpath[--i] = 0; + std::string path; + for (i = 0; shortpath[i]; i++) + path += char(shortpath[i]); + return path; +} +#elif defined(EMSCRIPTEN) || defined(__wasm) +std::string proc_self_dirname() { return "/"; } +#elif defined(__OpenBSD__) +char npnr_path[PATH_MAX]; +char *npnr_argv0; + +std::string proc_self_dirname(void) +{ + char buf[PATH_MAX + 1] = "", *path, *p; + // if case argv[0] contains a valid path, return it + if (strlen(npnr_path) > 0) { + p = strrchr(npnr_path, '/'); + snprintf(buf, sizeof buf, "%*s/", (int)(npnr_path - p), npnr_path); + return buf; + } + // if argv[0] does not, reconstruct the path out of $PATH + path = strdup(getenv("PATH")); + if (!path) + log_error("getenv(\"PATH\") failed: %s\n", strerror(errno)); + for (p = strtok(path, ":"); p; p = strtok(NULL, ":")) { + snprintf(buf, sizeof buf, "%s/%s", p, npnr_argv0); + if (access(buf, X_OK) == 0) { + *(strrchr(buf, '/') + 1) = '\0'; + free(path); + return buf; + } + } + free(path); + log_error("Can't determine nextpnr executable path\n."); + return NULL; +} +#else +#error "Don't know how to determine process executable base path!" +#endif + +#if defined(EMSCRIPTEN) || defined(__wasm) +void init_share_dirname() { npnr_share_dirname = "/share/"; } +#else +void init_share_dirname() +{ + std::string proc_self_path = proc_self_dirname(); +#if defined(_WIN32) && !defined(nextpnr_WIN32_UNIX_DIR) + std::string proc_share_path = proc_self_path + "share\\"; + if (check_file_exists(proc_share_path, true)) { + npnr_share_dirname = proc_share_path; + return; + } + proc_share_path = proc_self_path + "..\\share\\"; + if (check_file_exists(proc_share_path, true)) { + npnr_share_dirname = proc_share_path; + return; + } +#else + std::string proc_share_path = proc_self_path + "share/"; + if (check_file_exists(proc_share_path, true)) { + npnr_share_dirname = proc_share_path; + return; + } + proc_share_path = proc_self_path + "../share/" + "nextpnr/"; + if (check_file_exists(proc_share_path, true)) { + npnr_share_dirname = proc_share_path; + return; + } +#ifdef nextpnr_DATDIR + proc_share_path = nextpnr_DATDIR "/"; + if (check_file_exists(proc_share_path, true)) { + npnr_share_dirname = proc_share_path; + return; + } +#endif +#endif +} +#endif + +std::string proc_share_dirname() +{ + if (npnr_share_dirname.empty()) + log_error("init_share_dirname: unable to determine share/ directory!\n"); + return npnr_share_dirname; +} + struct no_separator : std::numpunct { protected: @@ -62,6 +242,12 @@ CommandHandler::CommandHandler(int argc, char **argv) : argc(argc), argv(argv) // the locale is broken in this system, so leave it as it is } log_streams.clear(); + +#if defined(__OpenBSD__) + // save the executable origin for proc_self_dirname() + npnr_argv0 = argv[0]; + realpath(npnr_argv0, npnr_path); +#endif } bool CommandHandler::parseOptions() diff --git a/common/kernel/command.h b/common/kernel/command.h index 6cce8c61..8e0e886d 100644 --- a/common/kernel/command.h +++ b/common/kernel/command.h @@ -69,6 +69,12 @@ class CommandHandler std::ofstream logfile; }; +// Relative directory functions from Yosys +bool check_file_exists(std::string filename, bool is_exec); +void init_share_dirname(); +std::string proc_self_dirname(); +std::string proc_share_dirname(); + NEXTPNR_NAMESPACE_END #endif // COMMAND_H diff --git a/himbaechel/arch.cc b/himbaechel/arch.cc index df576333..644dc21e 100644 --- a/himbaechel/arch.cc +++ b/himbaechel/arch.cc @@ -23,6 +23,7 @@ #include "log.h" #include "nextpnr.h" +#include "command.h" #include "placer1.h" #include "placer_heap.h" #include "router1.h" @@ -33,66 +34,72 @@ NEXTPNR_NAMESPACE_BEGIN static const ChipInfoPOD *get_chip_info(const RelPtr *ptr) { return ptr->get(); } -Arch::Arch(ArchArgs args) +Arch::Arch(ArchArgs args) : args(args) { + HimbaechelArch *arch = HimbaechelArch::find_match(args.device); + if (!arch) { + std::string available = HimbaechelArch::list(); + log_error("unable to load uarch for device '%s', included uarches: %s\n", args.device.c_str(), + available.c_str()); + } + log_info("Using uarch '%s' for device '%s'\n", arch->name.c_str(), args.device.c_str()); + this->args.uarch = arch->name; + uarch = arch->create(args.device, args.options); + // Load uarch + uarch->init_database(this); + if (!chip_info) + log_error("uarch didn't load any chipdb, probably a load_chipdb call was missing\n"); + + init_tiles(); +} + +void Arch::load_chipdb(const std::string &path) +{ + std::string db_path; + if (!args.chipdb_override.empty()) { + db_path = args.chipdb_override; + } else { + db_path = proc_share_dirname(); + db_path += "/himbaechel/"; + db_path += path; + } try { - blob_file.open(args.chipdb); - if (args.chipdb.empty() || !blob_file.is_open()) - log_error("Unable to read chipdb %s\n", args.chipdb.c_str()); + blob_file.open(db_path); + if (db_path.empty() || !blob_file.is_open()) + log_error("Unable to read chipdb %s\n", db_path.c_str()); const char *blob = reinterpret_cast(blob_file.data()); chip_info = get_chip_info(reinterpret_cast *>(blob)); } catch (...) { - log_error("Unable to read chipdb %s\n", args.chipdb.c_str()); + log_error("Unable to read chipdb %s\n", db_path.c_str()); } // Check consistency of blob if (chip_info->magic != 0x00ca7ca7) - log_error("chipdb %s does not look like a valid himbächel database!\n", args.chipdb.c_str()); + log_error("chipdb %s does not look like a valid himbächel database!\n", db_path.c_str()); std::string blob_uarch(chip_info->uarch.get()); if (blob_uarch != args.uarch) log_error("database device uarch '%s' does not match selected device uarch '%s'.\n", blob_uarch.c_str(), args.uarch.c_str()); - // Load uarch - uarch = HimbaechelArch::create(args.uarch, args.options); - if (!uarch) { - std::string available = HimbaechelArch::list(); - log_error("unable to load device uarch '%s', available options: %s\n", args.uarch.c_str(), available.c_str()); - } - uarch->init_constids(this); // Setup constids from database for (int i = 0; i < chip_info->extra_constids->bba_ids.ssize(); i++) { IdString::initialize_add(this, chip_info->extra_constids->bba_ids[i].get(), i + chip_info->extra_constids->known_id_count); } +} + +void Arch::set_speed_grade(const std::string &speed) +{ + if (speed.empty()) + return; // Select speed grade - if (args.speed.empty()) { - if (chip_info->speed_grades.ssize() == 0) { - // no timing information and no speed grade specified - speed_grade = nullptr; - } else if (chip_info->speed_grades.ssize() == 1) { - // speed grade not specified but only one available; use it - speed_grade = &(chip_info->speed_grades[0]); - } else { - std::string available_speeds = ""; - for (const auto &speed_data : chip_info->speed_grades) { - if (!available_speeds.empty()) - available_speeds += ", "; - available_speeds += IdString(speed_data.name).c_str(this); - } - log_error("Speed grade must be specified using --speed (available options: %s).\n", - available_speeds.c_str()); - } - } else { - for (const auto &speed_data : chip_info->speed_grades) { - if (IdString(speed_data.name) == id(args.speed)) { - speed_grade = &speed_data; - break; - } - } - if (!speed_grade) { - log_error("Speed grade '%s' not found in database.\n", args.speed.c_str()); + for (const auto &speed_data : chip_info->speed_grades) { + if (IdString(speed_data.name) == id(speed)) { + speed_grade = &speed_data; + break; } } - init_tiles(); + if (!speed_grade) { + log_error("Speed grade '%s' not found in database.\n", speed.c_str()); + } } void Arch::init_tiles() diff --git a/himbaechel/arch.h b/himbaechel/arch.h index d2e2e78d..3507eb1b 100644 --- a/himbaechel/arch.h +++ b/himbaechel/arch.h @@ -386,9 +386,8 @@ struct BelPinRange struct ArchArgs { std::string uarch; - std::string chipdb; + std::string chipdb_override; std::string device; - std::string speed; dict options; }; @@ -421,6 +420,9 @@ struct Arch : BaseArch Arch(ArchArgs args); ~Arch(){}; + void load_chipdb(const std::string &path); + void set_speed_grade(const std::string &speed); + void late_init(); // Database references diff --git a/himbaechel/arch_pybindings.cc b/himbaechel/arch_pybindings.cc index 246adcd9..3825670b 100644 --- a/himbaechel/arch_pybindings.cc +++ b/himbaechel/arch_pybindings.cc @@ -28,7 +28,7 @@ NEXTPNR_NAMESPACE_BEGIN void arch_wrap_python(py::module &m) { using namespace PythonConversion; - py::class_(m, "ArchArgs").def_readwrite("chipdb", &ArchArgs::chipdb); + py::class_(m, "ArchArgs").def_readwrite("device", &ArchArgs::device); py::class_(m, "BelId").def_readwrite("index", &BelId::index); diff --git a/himbaechel/himbaechel_api.cc b/himbaechel/himbaechel_api.cc index e8d403e3..2a04a99a 100644 --- a/himbaechel/himbaechel_api.cc +++ b/himbaechel/himbaechel_api.cc @@ -25,8 +25,6 @@ NEXTPNR_NAMESPACE_BEGIN void HimbaechelAPI::init(Context *ctx) { this->ctx = ctx; } -void HimbaechelAPI::init_constids(Arch *arch) {} - std::vector HimbaechelAPI::getCellTypes() const { std::vector result; @@ -89,17 +87,17 @@ std::string HimbaechelArch::list() } return result; } -std::unique_ptr HimbaechelArch::create(const std::string &name, - const dict &args) + +HimbaechelArch *HimbaechelArch::find_match(const std::string &device) { HimbaechelArch *cursor = HimbaechelArch::list_head; while (cursor) { - if (cursor->name != name) { + if (!cursor->match_device(device)) { cursor = cursor->list_next; continue; } - return cursor->create(args); + return cursor; } - return {}; + return nullptr; } NEXTPNR_NAMESPACE_END diff --git a/himbaechel/himbaechel_api.h b/himbaechel/himbaechel_api.h index be62be2a..eaf1a5ed 100644 --- a/himbaechel/himbaechel_api.h +++ b/himbaechel/himbaechel_api.h @@ -58,8 +58,9 @@ struct PlacerHeapCfg; struct HimbaechelAPI { virtual void init(Context *ctx); - // If constids are being used, this is used to set them up early before loading the db blob - virtual void init_constids(Arch *arch); + // If constids are being used, this is used to set them up early + // then it is responsible for loading the db blob with arch->load_chipdb() + virtual void init_database(Arch *arch) = 0; Context *ctx; bool with_gui = false; @@ -116,10 +117,12 @@ struct HimbaechelArch std::string name; HimbaechelArch(const std::string &name); ~HimbaechelArch(){}; - virtual std::unique_ptr create(const dict &args) = 0; + virtual bool match_device(const std::string &device) = 0; + virtual std::unique_ptr create(const std::string &device, + const dict &args) = 0; static std::string list(); - static std::unique_ptr create(const std::string &name, const dict &args); + static HimbaechelArch *find_match(const std::string &device); }; NEXTPNR_NAMESPACE_END diff --git a/himbaechel/main.cc b/himbaechel/main.cc index d66b3d9c..26686f83 100644 --- a/himbaechel/main.cc +++ b/himbaechel/main.cc @@ -40,16 +40,19 @@ class HimbaechelCommandHandler : public CommandHandler po::options_description getArchOptions() override; }; -HimbaechelCommandHandler::HimbaechelCommandHandler(int argc, char **argv) : CommandHandler(argc, argv) {} +HimbaechelCommandHandler::HimbaechelCommandHandler(int argc, char **argv) : CommandHandler(argc, argv) +{ + init_share_dirname(); +} po::options_description HimbaechelCommandHandler::getArchOptions() { std::string all_uarches = HimbaechelArch::list(); std::string uarch_help = stringf("himbächel micro-arch to use (available: %s)", all_uarches.c_str()); po::options_description specific("Architecture specific options"); - specific.add_options()("uarch", po::value(), uarch_help.c_str()); - specific.add_options()("chipdb", po::value(), "path to chip database file"); - specific.add_options()("speed", po::value(), "device speed grade"); + specific.add_options()("device", po::value(), "name of device to use"); + specific.add_options()("chipdb", po::value(), "override path to chip database file"); + specific.add_options()("list-uarch", "list included uarches"); specific.add_options()("vopt,o", po::value>(), "options to pass to the himbächel uarch"); return specific; @@ -65,14 +68,19 @@ std::unique_ptr HimbaechelCommandHandler::createContext(dict(); - chipArgs.chipdb = vm["chipdb"].as(); - if (vm.count("speed")) - chipArgs.speed = vm["speed"].as(); + if (vm.count("list-uarch")) { + std::string uarches = HimbaechelArch::list(); + log_info("Supported uarches: %s\n", uarches.c_str()); + exit(0); + } + if (!vm.count("device")) + log_error("device must be specified\n"); + chipArgs.device = vm["device"].as(); + + if (vm.count("chipdb")) { + chipArgs.chipdb_override = vm["chipdb"].as(); + } + if (vm.count("vopt")) { std::vector options = vm["vopt"].as>(); for (const auto &opt : options) { diff --git a/himbaechel/uarch/example/example.cc b/himbaechel/uarch/example/example.cc index 5f5d1087..c02280cd 100644 --- a/himbaechel/uarch/example/example.cc +++ b/himbaechel/uarch/example/example.cc @@ -37,7 +37,13 @@ struct ExampleImpl : HimbaechelAPI static constexpr int K = 4; ~ExampleImpl(){}; - void init_constids(Arch *arch) override { init_uarch_constids(arch); } + void init_database(Arch *arch) override + { + init_uarch_constids(arch); + arch->load_chipdb("example/example.bin"); + arch->set_speed_grade("DEFAULT"); + } + void init(Context *ctx) override { h.init(ctx); @@ -134,7 +140,8 @@ struct ExampleImpl : HimbaechelAPI struct ExampleArch : HimbaechelArch { ExampleArch() : HimbaechelArch("example"){}; - std::unique_ptr create(const dict &args) + bool match_device(const std::string &device) override { return device == "EXAMPLE"; } + std::unique_ptr create(const std::string &device, const dict &args) { return std::make_unique(); } diff --git a/himbaechel/uarch/gowin/CMakeLists.txt b/himbaechel/uarch/gowin/CMakeLists.txt index dc1250ce..d2b64e6e 100644 --- a/himbaechel/uarch/gowin/CMakeLists.txt +++ b/himbaechel/uarch/gowin/CMakeLists.txt @@ -25,14 +25,14 @@ else() endif() set(chipdb_binaries) -file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/chipdb) +file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/share/himbaechel/gowin) foreach(device ${HIMBAECHEL_GOWIN_DEVICES}) if(NOT device IN_LIST ALL_HIMBAECHEL_GOWIN_DEVICES) message(FATAL_ERROR "Device ${device} is not a supported Gowin device") endif() - set(device_bba chipdb/chipdb-${device}.bba) - set(device_bin chipdb/chipdb-${device}.bin) + set(device_bba ${CMAKE_BINARY_DIR}/share/himbaechel/gowin/chipdb-${device}.bba) + set(device_bin ${CMAKE_BINARY_DIR}/share/himbaechel/gowin/chipdb-${device}.bin) add_custom_command( OUTPUT ${device_bin} COMMAND ${apycula_Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/gowin_arch_gen.py -d ${device} -o ${device_bba} @@ -48,5 +48,5 @@ foreach(device ${HIMBAECHEL_GOWIN_DEVICES}) endforeach() add_custom_target(chipdb-himbaechel-gowin ALL DEPENDS ${chipdb_binaries}) -install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/chipdb/ DESTINATION share/nextpnr/himbaechel/gowin +install(DIRECTORY ${CMAKE_BINARY_DIR}/share/himbaechel/gowin DESTINATION share/nextpnr/himbaechel/gowin PATTERN "*.bba" EXCLUDE) diff --git a/himbaechel/uarch/gowin/gowin.cc b/himbaechel/uarch/gowin/gowin.cc index bf898564..f67e9b4e 100644 --- a/himbaechel/uarch/gowin/gowin.cc +++ b/himbaechel/uarch/gowin/gowin.cc @@ -22,7 +22,7 @@ struct GowinImpl : HimbaechelAPI { ~GowinImpl(){}; - void init_constids(Arch *arch) override { init_uarch_constids(arch); } + void init_database(Arch *arch) override; void init(Context *ctx) override; void pack() override; @@ -64,12 +64,49 @@ struct GowinImpl : HimbaechelAPI struct GowinArch : HimbaechelArch { GowinArch() : HimbaechelArch("gowin"){}; - std::unique_ptr create(const dict &args) + + bool match_device(const std::string &device) override { return device.size() > 2 && device.substr(0, 2) == "GW"; } + + std::unique_ptr create(const std::string &device, const dict &args) { return std::make_unique(); } } gowinrArch; +void GowinImpl::init_database(Arch *arch) +{ + init_uarch_constids(arch); + const ArchArgs &args = arch->args; + std::string family; + if (args.options.count("family")) { + family = args.options.at("family"); + } else { + bool GW2 = args.device == "GW2A-LV18PG256C8/I7"; + if (GW2) { + family = "GW2A-18"; + } else { + std::regex devicere = std::regex("GW1N([SZ]?)[A-Z]*-(LV|UV|UX)([0-9])(C?).*"); + std::smatch match; + if (!std::regex_match(args.device, match, devicere)) { + log_error("Invalid device %s\n", args.device.c_str()); + } + family = stringf("GW1N%s-%s", match[1].str().c_str(), match[3].str().c_str()); + } + } + + arch->load_chipdb(stringf("gowin/chipdb-%s.bin", family.c_str())); + + // These fields go in the header of the output JSON file and can help + // gowin_pack support different architectures + arch->settings[arch->id("packer.arch")] = std::string("himbaechel/gowin"); + arch->settings[arch->id("packer.chipdb")] = family; + + chip = arch->id(family); + std::string pn = args.device; + partno = arch->id(pn); + arch->settings[arch->id("packer.partno")] = pn; +} + void GowinImpl::init(Context *ctx) { h.init(ctx); @@ -78,24 +115,6 @@ void GowinImpl::init(Context *ctx) gwu.init(ctx); const ArchArgs &args = ctx->getArchArgs(); - // These fields go in the header of the output JSON file and can help - // gowin_pack support different architectures - ctx->settings[ctx->id("packer.arch")] = std::string("himbaechel/gowin"); - ctx->settings[ctx->id("packer.chipdb")] = args.chipdb; - - if (!args.options.count("partno")) { - log_error("Partnumber (like --vopt partno=GW1NR-LV9QN88PC6/I5) must be specified.\n"); - } - // GW1N-9C.xxx -> GW1N-9C - std::string chipdb = args.chipdb; - auto dot_pos = chipdb.find("."); - if (dot_pos != std::string::npos) { - chipdb.resize(dot_pos); - } - chip = ctx->id(chipdb); - std::string pn = args.options.at("partno"); - partno = ctx->id(pn); - ctx->settings[ctx->id("packer.partno")] = pn; // package and speed class std::regex speedre = std::regex("(.*)(C[0-9]/I[0-9])$"); @@ -103,6 +122,7 @@ void GowinImpl::init(Context *ctx) IdString spd; IdString package_idx; + std::string pn = args.device; if (std::regex_match(pn, match, speedre)) { package_idx = ctx->id(match[1]); spd = ctx->id(match[2]);