himbaechel: Add discovery of uarch and chipdb

Signed-off-by: gatecat <gatecat@ds0.me>
This commit is contained in:
gatecat 2023-09-12 17:29:26 +02:00 committed by myrtle
parent 3cac90a30a
commit 565927dfcc
12 changed files with 332 additions and 94 deletions

View File

@ -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
}

View File

@ -45,8 +45,188 @@
#include "util.h"
#include "version.h"
#if defined(_WIN32)
#include <io.h>
#include <windows.h>
#elif defined(__APPLE__)
#include <dirent.h>
#include <mach-o/dyld.h>
#include <sys/stat.h>
#include <unistd.h>
#else
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#ifdef __FreeBSD__
#include <sys/sysctl.h>
#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<char>
{
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()

View File

@ -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

View File

@ -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<ChipInfoPOD> *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<const char *>(blob_file.data());
chip_info = get_chip_info(reinterpret_cast<const RelPtr<ChipInfoPOD> *>(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()

View File

@ -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<std::string, std::string> options;
};
@ -421,6 +420,9 @@ struct Arch : BaseArch<ArchRanges>
Arch(ArchArgs args);
~Arch(){};
void load_chipdb(const std::string &path);
void set_speed_grade(const std::string &speed);
void late_init();
// Database references

View File

@ -28,7 +28,7 @@ NEXTPNR_NAMESPACE_BEGIN
void arch_wrap_python(py::module &m)
{
using namespace PythonConversion;
py::class_<ArchArgs>(m, "ArchArgs").def_readwrite("chipdb", &ArchArgs::chipdb);
py::class_<ArchArgs>(m, "ArchArgs").def_readwrite("device", &ArchArgs::device);
py::class_<BelId>(m, "BelId").def_readwrite("index", &BelId::index);

View File

@ -25,8 +25,6 @@ NEXTPNR_NAMESPACE_BEGIN
void HimbaechelAPI::init(Context *ctx) { this->ctx = ctx; }
void HimbaechelAPI::init_constids(Arch *arch) {}
std::vector<IdString> HimbaechelAPI::getCellTypes() const
{
std::vector<IdString> result;
@ -89,17 +87,17 @@ std::string HimbaechelArch::list()
}
return result;
}
std::unique_ptr<HimbaechelAPI> HimbaechelArch::create(const std::string &name,
const dict<std::string, std::string> &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

View File

@ -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<HimbaechelAPI> create(const dict<std::string, std::string> &args) = 0;
virtual bool match_device(const std::string &device) = 0;
virtual std::unique_ptr<HimbaechelAPI> create(const std::string &device,
const dict<std::string, std::string> &args) = 0;
static std::string list();
static std::unique_ptr<HimbaechelAPI> create(const std::string &name, const dict<std::string, std::string> &args);
static HimbaechelArch *find_match(const std::string &device);
};
NEXTPNR_NAMESPACE_END

View File

@ -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<std::string>(), uarch_help.c_str());
specific.add_options()("chipdb", po::value<std::string>(), "path to chip database file");
specific.add_options()("speed", po::value<std::string>(), "device speed grade");
specific.add_options()("device", po::value<std::string>(), "name of device to use");
specific.add_options()("chipdb", po::value<std::string>(), "override path to chip database file");
specific.add_options()("list-uarch", "list included uarches");
specific.add_options()("vopt,o", po::value<std::vector<std::string>>(), "options to pass to the himbächel uarch");
return specific;
@ -65,14 +68,19 @@ std::unique_ptr<Context> HimbaechelCommandHandler::createContext(dict<std::strin
if (arch_name != "himbaechel")
log_error("Unsupported architecture '%s'.\n", arch_name.c_str());
}
if (!vm.count("uarch"))
log_error("uarch must be specified\n");
if (!vm.count("chipdb"))
log_error("chip database path must be specified.\n");
chipArgs.uarch = vm["uarch"].as<std::string>();
chipArgs.chipdb = vm["chipdb"].as<std::string>();
if (vm.count("speed"))
chipArgs.speed = vm["speed"].as<std::string>();
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<std::string>();
if (vm.count("chipdb")) {
chipArgs.chipdb_override = vm["chipdb"].as<std::string>();
}
if (vm.count("vopt")) {
std::vector<std::string> options = vm["vopt"].as<std::vector<std::string>>();
for (const auto &opt : options) {

View File

@ -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<HimbaechelAPI> create(const dict<std::string, std::string> &args)
bool match_device(const std::string &device) override { return device == "EXAMPLE"; }
std::unique_ptr<HimbaechelAPI> create(const std::string &device, const dict<std::string, std::string> &args)
{
return std::make_unique<ExampleImpl>();
}

View File

@ -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)

View File

@ -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<HimbaechelAPI> create(const dict<std::string, std::string> &args)
bool match_device(const std::string &device) override { return device.size() > 2 && device.substr(0, 2) == "GW"; }
std::unique_ptr<HimbaechelAPI> create(const std::string &device, const dict<std::string, std::string> &args)
{
return std::make_unique<GowinImpl>();
}
} 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]);