diff --git a/.github/scripts/install_dependencies.sh b/.github/scripts/install_dependencies.sh index 351ec7d56a7..7ef9fd31590 100755 --- a/.github/scripts/install_dependencies.sh +++ b/.github/scripts/install_dependencies.sh @@ -32,6 +32,7 @@ sudo apt install -y \ libxml++2.6-dev \ libreadline-dev \ tcl-dev \ + tk-dev \ libffi-dev \ perl \ texinfo \ diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 28de7e0de7c..4d73f1da898 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,26 +16,26 @@ jobs: Run-tests: # Prevents from running on forks where no custom runners are available - if: ${{ github.repository_owner == 'verilog-to-routing' }} + #if: ${{ github.repository_owner == 'verilog-to-routing' }} container: ubuntu:bionic - runs-on: [self-hosted, Linux, X64] + runs-on: ubuntu-18.04 strategy: fail-fast: false matrix: include: - - {test: "vtr_reg_nightly_test1", cores: "8", options: "", cmake: "" } - - {test: "vtr_reg_nightly_test2", cores: "16", options: "", cmake: "" } - - {test: "vtr_reg_nightly_test3", cores: "16", options: "", cmake: "" } - - {test: "vtr_reg_nightly_test4", cores: "16", options: "", cmake: "" } + #- {test: "vtr_reg_nightly_test1", cores: "8", options: "", cmake: "" } + #- {test: "vtr_reg_nightly_test2", cores: "16", options: "", cmake: "" } + #- {test: "vtr_reg_nightly_test3", cores: "16", options: "", cmake: "" } + #- {test: "vtr_reg_nightly_test4", cores: "16", options: "", cmake: "" } - {test: "vtr_reg_strong", cores: "16", options: "", cmake: "-DVTR_ASSERT_LEVEL=3" } - - {test: "vtr_reg_strong", cores: "16", options: "-skip_qor", cmake: "-DVTR_ASSERT_LEVEL=3 -DVTR_ENABLE_SANITIZE=ON"} - - {test: "vtr_reg_yosys", cores: "16", options: "", cmake: "-DWITH_YOSYS=ON" } - - {test: "vtr_reg_yosys_odin", cores: "16", options: "", cmake: "-DODIN_USE_YOSYS=ON" } - - {test: "odin_tech_strong", cores: "16", options: "", cmake: "-DODIN_USE_YOSYS=ON" } - - {test: "odin_reg_strong", cores: "16", options: "", cmake: "" } + #- {test: "vtr_reg_strong", cores: "16", options: "-skip_qor", cmake: "-DVTR_ASSERT_LEVEL=3 -DVTR_ENABLE_SANITIZE=ON"} + #- {test: "vtr_reg_yosys", cores: "16", options: "", cmake: "-DWITH_YOSYS=ON" } + #- {test: "vtr_reg_yosys_odin", cores: "16", options: "", cmake: "-DODIN_USE_YOSYS=ON" } + #- {test: "odin_tech_strong", cores: "16", options: "", cmake: "-DODIN_USE_YOSYS=ON" } + #- {test: "odin_reg_strong", cores: "16", options: "", cmake: "" } env: DEBIAN_FRONTEND: "noninteractive" diff --git a/CMakeLists.txt b/CMakeLists.txt index a69ecf55017..0a104810b2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -439,6 +439,7 @@ list(APPEND DIRS_TO_FORMAT_CPP "${CMAKE_CURRENT_SOURCE_DIR}/libs/libvtrutil") list(APPEND DIRS_TO_FORMAT_CPP "${CMAKE_CURRENT_SOURCE_DIR}/libs/libpugiutil") list(APPEND DIRS_TO_FORMAT_CPP "${CMAKE_CURRENT_SOURCE_DIR}/libs/liblog") list(APPEND DIRS_TO_FORMAT_CPP "${CMAKE_CURRENT_SOURCE_DIR}/libs/librtlnumber") +list(APPEND DIRS_TO_FORMAT_CPP "${CMAKE_CURRENT_SOURCE_DIR}/libs/libtclcpp") list(APPEND DIRS_TO_FORMAT_CPP "${CMAKE_CURRENT_SOURCE_DIR}/ODIN_II") include(AutoClangFormat) diff --git a/install_apt_packages.sh b/install_apt_packages.sh index ac225737a46..b52073fe6ad 100755 --- a/install_apt_packages.sh +++ b/install_apt_packages.sh @@ -20,6 +20,7 @@ sudo apt-get install -y \ sudo apt-get install -y \ clang \ tcl-dev \ + tk-dev \ libreadline-dev # Required to build the documentation diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index 152eafec323..c2dc51a973f 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -18,3 +18,4 @@ if(${VTR_ENABLE_CAPNPROTO}) add_subdirectory(libvtrcapnproto) endif() add_subdirectory(librrgraph) +add_subdirectory(libtclcpp) \ No newline at end of file diff --git a/libs/libarchfpga/src/physical_types.h b/libs/libarchfpga/src/physical_types.h index 8eeb821a5de..1581d99e67c 100644 --- a/libs/libarchfpga/src/physical_types.h +++ b/libs/libarchfpga/src/physical_types.h @@ -976,7 +976,7 @@ struct t_mode { * input_string: input string verbatim to parse later * output_string: input string output to parse later * annotations: Annotations for delay, power, etc - * num_annotations: Total number of annotations + * num_annotations: Total number opb_typef annotations * infer_annotations: This interconnect is autogenerated, if true, infer pack_patterns * such as carry-chains and forced packs based on interconnect linked to it * parent_mode_index: Mode of parent as int @@ -1773,6 +1773,14 @@ struct t_lut_element { } }; +struct t_phys_map_region { + int x, y, w, h; + int subtile; +}; + +/* Maps Physical PIN name to a name of PAD BEL */ +typedef std::unordered_map t_phys_grid_mapping; + /* Detailed routing architecture */ struct t_arch { mutable vtr::string_internment strings; @@ -1831,6 +1839,7 @@ struct t_arch { std::string ipin_cblock_switch_name; std::vector grid_layouts; //Set of potential device layouts + t_phys_grid_mapping phys_grid_mapping; //Mapping from physical pins to grid locations t_clock_arch_spec clock_arch; // Clock related data types }; diff --git a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp index 6ac7de1a0cc..f919ec3200e 100644 --- a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp +++ b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp @@ -806,10 +806,13 @@ struct ArchReader { } void process_package_pins() { + std::unordered_map site_to_pin_map; + for (auto package : ar_.getPackages()) { for (auto pin : package.getPackagePins()) { t_package_pin pckg_pin; pckg_pin.name = str(pin.getPackagePin()); + if (pin.getBel().isBel()) { pckg_pin.bel_name = str(pin.getBel().getBel()); @@ -820,6 +823,35 @@ struct ArchReader { pckg_pin.site_name = str(pin.getSite().getSite()); package_pins_.push_back(pckg_pin); + + site_to_pin_map[pckg_pin.site_name] = pckg_pin.name; + } + } + + /* Populate phys_grid_mapping - associate pin names with grid locations */ + for (const auto& tile : ar_.getTileList()) { + int site_idx = 0; + for (const auto& site : tile.getSites()) { + int subtile = site_idx++; + + std::string site_name = str(site.getName()); + auto it = site_to_pin_map.find(site_name); + if (it == site_to_pin_map.end()) + continue; + std::string& pin_name = it->second; + + auto tile_type = ar_.getTileTypeList()[tile.getType()]; + + /* TODO: All tiles are currently set to 1x1 size, but this is done in process_tiles method which gets + * called later. We should split this logic in a way that's more suitable for constraining pins, + * because currently the line below makes an assumption about tile size which happens to be true only + * due to process_tiles implementation. + * + * A similar thing could be said about subtile indexing. + */ + t_phys_map_region pin_region { tile.getCol() + 1, tile.getRow() + 1, 1, 1, subtile }; + + arch_->phys_grid_mapping[pin_name] = std::move(pin_region); } } } diff --git a/libs/libtclcpp/CMakeLists.txt b/libs/libtclcpp/CMakeLists.txt new file mode 100644 index 00000000000..7694b477c17 --- /dev/null +++ b/libs/libtclcpp/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.9) + +project("libtclcpp") + +file(GLOB_RECURSE LIB_SOURCES src/*.cpp) +file(GLOB_RECURSE LIB_HEADERS src/*.h) +files_to_dirs(LIB_HEADERS LIB_INCLUDE_DIRS) + +add_library(libtclcpp STATIC + ${LIB_HEADERS} + ${LIB_SOURCES} +) + +target_include_directories(libtclcpp PUBLIC ${LIB_INCLUDE_DIRS}) + +set_target_properties(libtclcpp PROPERTIES PREFIX "") + +find_package(TCL REQUIRED) +target_link_libraries(libtclcpp tcl) + +install(TARGETS libtclcpp DESTINATION bin) diff --git a/libs/libtclcpp/src/tclcpp.cpp b/libs/libtclcpp/src/tclcpp.cpp new file mode 100644 index 00000000000..b6a669a2ea3 --- /dev/null +++ b/libs/libtclcpp/src/tclcpp.cpp @@ -0,0 +1,157 @@ +#include "tclcpp.h" + +#include +#include +#include +#include +#include +#include + +/* TCL ERRORS */ +#define OV_TOSTRING(c, e) \ + c::operator std::string() const { return (e); } + +OV_TOSTRING(TCL_eException, "Unknown error") + +TCL_eCustomException::TCL_eCustomException(std::string&& message_) + : message(std::move(message_)) {} +TCL_eCustomException::operator std::string() const { return (this->message); } + +TCL_eFailedToInitTcl::TCL_eFailedToInitTcl(int error_code_) + : error_code(error_code_) {} +OV_TOSTRING(TCL_eFailedToInitTcl, "Can't initialize TCL (code " + std::to_string(error_code) + ")") + +TCL_eErroneousTCL::TCL_eErroneousTCL( + std::string&& filename_, + int line_, + int column_, + std::string&& message_) + : filename(std::move(filename_)) + , line(line_) + , column(column_) + , message(message_) {} +OV_TOSTRING(TCL_eErroneousTCL, this->filename + ":" + std::to_string(this->line) + "," + std::to_string(this->column) + ": " + this->message) + +void Tcl_SetStringResult(Tcl_Interp* interp, const std::string& s) { + char* copy = Tcl_Alloc(s.length() + 1); + std::strcpy(copy, s.c_str()); + Tcl_SetResult(interp, copy, TCL_DYNAMIC); +} + +TclClient::TclClient() + : cmd_status(e_TclCommandStatus::TCL_CMD_SUCCESS) + , string("No errors") {} + +void tcl_obj_dup(Tcl_Obj* src, Tcl_Obj* dst) { + dst->internalRep.twoPtrValue = src->internalRep.twoPtrValue; + dst->typePtr = src->typePtr; + dst->bytes = nullptr; + dst->length = 0; +} + +void tcl_set_obj_string(Tcl_Obj* obj, const std::string& str) { + if (obj->bytes != nullptr) + Tcl_Free(obj->bytes); + obj->bytes = Tcl_Alloc(str.length() + 1); + obj->length = str.length(); + std::strcpy(obj->bytes, str.c_str()); +} + +int tcl_set_from_none(Tcl_Interp* tcl_interp, Tcl_Obj* obj) { + (void)(obj); /* Surpress "unused parameter" macro */ + /* TODO: Better error message */ + Tcl_SetStringResult(tcl_interp, "Attempted an invalid conversion."); + return TCL_ERROR; +} + +TclCtx::TclCtx() { + this->_tcl_interp = Tcl_CreateInterp(); +#ifdef DEBUG + this->_init = false; +#endif +} + +TclCtx::~TclCtx() { + Tcl_DeleteInterp(this->_tcl_interp); +} + +void TclCtx::_init() { + int error; + + if ((error = Tcl_Init(this->_tcl_interp)) != TCL_OK) + throw TCL_eFailedToInitTcl(error); + +#ifdef DEBUG + this->_init = true; +#endif +} + +void TclCtx::read_tcl(std::istream& tcl_stream) { + int error; + + this->_debug_init(); + + std::ostringstream os; + tcl_stream >> os.rdbuf(); + std::string tcl = os.str(); + + error = Tcl_Eval(this->_tcl_interp, tcl.c_str()); + /* TODO: More precise error */ + if (error != TCL_OK) { + int error_line = Tcl_GetErrorLine(this->_tcl_interp); + const char* msg = Tcl_GetStringResult(this->_tcl_interp); + throw TCL_eErroneousTCL("", error_line, 0, std::string(msg)); + } +} + +int TclCtx::_tcl_do_method( + ClientData cd, + Tcl_Interp* tcl_interp, + int objc, + Tcl_Obj* const objvp[]) { + TclMethodDispatchBase* d = static_cast(cd); + d->do_method(objc, objvp); + + switch (d->client.cmd_status) { + case e_TclCommandStatus::TCL_CMD_FAIL: + Tcl_SetStringResult(tcl_interp, d->client.string); + return TCL_ERROR; + case e_TclCommandStatus::TCL_CMD_SUCCESS_STRING: + Tcl_SetStringResult(tcl_interp, d->client.string); + return TCL_OK; + case e_TclCommandStatus::TCL_CMD_SUCCESS_OBJECT: + case e_TclCommandStatus::TCL_CMD_SUCCESS_LIST: + Tcl_SetObjResult(tcl_interp, d->client.object); + default: + break; + } + return TCL_OK; +} + +TclDynList::TclDynList(Tcl_Interp* interp, Tcl_Obj* obj) + : _interp(interp) { + const Tcl_ObjType* obj_type = obj->typePtr; + if (obj_type == nullptr) + return; + + if (obj_type == nullptr || std::strcmp(obj_type->name, "list")) + this->_obj = Tcl_NewListObj(1, &obj); + else + this->_obj = obj; +} + +TclDynList tcl_obj_getdynlist (TclClient* client, Tcl_Obj* obj) { + return TclDynList(client->_interp, obj); +} + +Tcl_Obj* TclDynList::operator[](size_t idx) { + int count; + Tcl_Obj* objp; + + Tcl_ListObjLength(this->_interp, this->_obj, &count); + if (idx >= size_t(count)) + return nullptr; + Tcl_ListObjIndex(this->_interp, this->_obj, idx, &objp); + + return objp; +} diff --git a/libs/libtclcpp/src/tclcpp.h b/libs/libtclcpp/src/tclcpp.h new file mode 100644 index 00000000000..1cba5fb2c4b --- /dev/null +++ b/libs/libtclcpp/src/tclcpp.h @@ -0,0 +1,697 @@ +#ifndef TCLCPP_H +#define TCLCPP_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +/** + * @file + * @brief Object-oriented wrappers for TCL interpreter. + * Overview + * ======== + * This file contains function declarations and definitionsas well as classes that allow embedding TCL interpreter in C++ + * context in a way that allows it to be extended with multiple "clients" that provide methods for managing their content. + * + * A standard scenario is: + * - // Create an instance of TclClient-derived class + * MyTclClient client; + * - // Create a TclCtx context and run code within its lifetime. + * TclCtx::with_ctx([&](TclCtx& ctx) {; + * - // Call this before doing anything else with `ctx`. + * ctx.init() + * - // This will register all methods provided by `client` + * ctx.add_client(client) + * - // Read and interpret TCL code + * ctx.read_tcl(stream) + * - // TclCtx gets destroyd at the end of this call + * }); + * - // Process results stored by a client + * do_something(client.data) + */ + +/** + * @brief Base class for any exception thrown within TCLCtx. + */ +class TCL_eException { + public: + virtual ~TCL_eException() = default; + virtual operator std::string() const; +}; + +/** + * @brief Generic exception thrown within TCLCtx. Features a message string. + */ +class TCL_eCustomException : TCL_eException { + public: + TCL_eCustomException(std::string&& message_); + ~TCL_eCustomException() override = default; + operator std::string() const override; + + std::string message; +}; + +/** + * @brief Exception thrown within TCLCtx if it fails during TCLCtx::init() + */ +class TCL_eFailedToInitTcl : TCL_eException { + public: + TCL_eFailedToInitTcl(int error_code_); + ~TCL_eFailedToInitTcl() override = default; + operator std::string() const override; + + int error_code; +}; + +/** + * @brief Exception thrown within TCLCtx when an error occurs while interpreting TCL. + */ +class TCL_eErroneousTCL : TCL_eException { + public: + TCL_eErroneousTCL(std::string&& filename_, int line_, int column_, std::string&& message_); + ~TCL_eErroneousTCL() override = default; + operator std::string() const override; + + std::string filename; + int line, column; + std::string message; +}; + +void Tcl_SetStringResult(Tcl_Interp* interp, const std::string& s); + +template +constexpr const Tcl_ObjType* tcl_type_of(); + +/** + * @brief Get pointer to a C++ object managed within TCL-context + * @return Pointer to a C++ object if `T` matches the type of an object. Otherwise nullptr. + */ +template +static T* tcl_obj_getptr(Tcl_Obj* obj) { + if (obj->typePtr != tcl_type_of()) + return nullptr; + return static_cast(obj->internalRep.twoPtrValue.ptr2); +} + +/** + * @brief Get pointer to an extra referenced C++ object. + * @return Pointer to a C++ object. + * + * This can be used to access context associated with a client, but when doing so + * the user must ensure that whatever client is bound there, gets handled correctly. + * Ie. when passing an object between two different clients, this should point to a + * common interface. + */ +template +static T* tcl_obj_get_ctx_ptr(Tcl_Obj* obj) { + return static_cast(obj->internalRep.twoPtrValue.ptr1); +} + +using TclCommandError = std::string; + +/** + * @brief Descibes a state after exiting a custom TCL command implementation. + */ +enum class e_TclCommandStatus : int { + TCL_CMD_SUCCESS, /* Command exited with no return value. */ + TCL_CMD_SUCCESS_STRING, /* Command exited returning a string. */ + TCL_CMD_SUCCESS_OBJECT, /* Command exited returning a custom object. */ + TCL_CMD_SUCCESS_LIST, /* Command exited returning a list. */ + TCL_CMD_FAIL /* Command exited with a failure. Will cause a TCL_eErroneousTCL to be thrown. */ +}; + +template::value>::type> +Tcl_Obj* Tcl_MoveCppObject(void* ctx, T& object_data) { + const Tcl_ObjType* type = tcl_type_of(); + Tcl_Obj* obj = Tcl_NewObj(); + obj->bytes = nullptr; + obj->typePtr = type; + obj->internalRep.twoPtrValue.ptr1 = ctx; + obj->internalRep.twoPtrValue.ptr2 = Tcl_Alloc(sizeof(T)); + new (obj->internalRep.twoPtrValue.ptr2) T(std::move(object_data)); + if (type->updateStringProc != nullptr) + type->updateStringProc(obj); + + return obj; +} + +/** + * @brief C++ wrapper for TCL lists of TCL-managed C++ objects. Allows easy iteration and construction from C++ containers. + */ +template +class TclList { + public: + /** + * @brief Create empty TCL list + */ + TclList(Tcl_Interp* interp) + : _interp(interp) + , _obj(Tcl_NewListObj(0, nullptr)) {} + + /** + * @brief Create a TCL list from a C++ container + * Client must point to the concrete TclClient instance. + */ + template + TclList(Tcl_Interp* interp, void* client, Container&& elements) + : _interp(interp) { + std::vector tcl_objs; + for (auto&& elem : elements) { + Tcl_Obj* obj = Tcl_MoveCppObject(client, elem); + tcl_objs.push_back(obj); + } + + this->_obj = Tcl_NewListObj(tcl_objs.size(), &tcl_objs[0]); + } + + /** + * @brief Import a TCL list from Tcl_Obj of list type, or create one-element list from + * object of any other type. + */ + TclList(Tcl_Interp* interp, Tcl_Obj* obj) + : _interp(interp) { + const Tcl_ObjType* obj_type = obj->typePtr; + if (obj_type == nullptr) + return; + + if (obj_type == nullptr || std::strcmp(obj_type->name, "list")) + this->_obj = Tcl_NewListObj(1, &obj); + else + this->_obj = obj; + } + + /** + * @brief Get the raw TCL_Obj representing the list. + */ + Tcl_Obj* tcl_obj() { + return this->_obj; + } + + Tcl_Obj* operator*(size_t idx) { + int count; + Tcl_Obj* objp; + + Tcl_ListObjLength(this->_interp, this->_obj, &count); + if (idx >= count) + return nullptr; + Tcl_ListObjIndex(this->_interp, this->_obj, idx, &objp); + + return tcl_obj_getptr(objp); + } + + /** + * @brief Iterator + * Dereferencing it yields a pointer to a C++ object + * + * TODO: If we ever need to handle a TCL list of TCL lists this won't work. It might be possible to use + * template spcializations to derive a class with alternative implementation of `operator*()` that would + * return `TclList` for `TclList>::Iterator::operaotr*()`. + * Primitive TCL types might also need a specialized handling. + */ + class Iterator { + public: + Iterator& operator=(const Iterator& other) { + this->_idx = other._idx; + return *this; + } + + Iterator& operator++() { + this->_idx++; + return *this; + } + + bool operator!=(const Iterator& other) { + return (this->_interp != other._interp) || (this->_obj != other._obj) || (this->_idx != other._idx); + } + + T* operator*() const { + Tcl_Obj* objp; + Tcl_ListObjIndex(this->_interp, this->_obj, this->_idx, &objp); + return tcl_obj_getptr(objp); + } + + Iterator(Tcl_Interp* interp, Tcl_Obj* obj, int idx) + : _interp(interp) + , _idx(idx) + , _obj(obj) {} + + protected: + Tcl_Interp* _interp; + int _idx; /* Currently visited index on the list */ + Tcl_Obj* _obj; /* Underlying Tcl_Obj representing the list */ + }; + + Iterator begin() { + return Iterator(this->_interp, this->_obj, 0); + } + + Iterator end() { + int count; + Tcl_ListObjLength(this->_interp, this->_obj, &count); + return Iterator(this->_interp, this->_obj, count); + } + + /** + * @brief Get number of elements on the list. + */ + size_t size() const { + int len; + Tcl_ListObjLength(this->_interp, this->_obj, &len); + return size_t(len); + } + + protected: + Tcl_Interp* _interp; + Tcl_Obj* _obj; /* Underlying Tcl_Obj representing the list */ +}; + +class TclDynList { + public: + /** + * @brief Create empty TCL list + */ + inline TclDynList(Tcl_Interp* interp) + : _interp(interp) + , _obj(Tcl_NewListObj(0, nullptr)) {} + + /** + * @brief Import a TCL list from Tcl_Obj of list type, or create one-element list from + * object of any other type. + */ + TclDynList(Tcl_Interp* interp, Tcl_Obj* obj); + + /** + * @brief Get the raw TCL_Obj representing the list. + */ + inline Tcl_Obj* tcl_obj() { + return this->_obj; + } + + Tcl_Obj* operator[](size_t idx); + + template + Tcl_Obj* at(size_t idx) { + Tcl_Obj* obj = (*this)[idx]; + if (obj == nullptr) + return nullptr; + return tcl_obj_getptr(obj); + } + + /** + * @brief Iterator + * Dereferencing it yields a pointer to a C++ object + * + * TODO: If we ever need to handle a TCL list of TCL lists this won't work. It might be possible to use + * template spcializations to derive a class with alternative implementation of `operator*()` that would + * return `TclList` for `TclList>::Iterator::operaotr*()`. + * Primitive TCL types might also need a specialized handling. + */ + class Iterator { + public: + inline Iterator& operator=(const Iterator& other) { + this->_idx = other._idx; + return *this; + } + + inline Iterator& operator++() { + this->_idx++; + return *this; + } + + inline bool operator!=(const Iterator& other) { + return (this->_interp != other._interp) || (this->_obj != other._obj) || (this->_idx != other._idx); + } + + inline Tcl_Obj* operator*() const { + Tcl_Obj* objp; + Tcl_ListObjIndex(this->_interp, this->_obj, this->_idx, &objp); + return objp; + } + + template + inline T* at() const { + return tcl_obj_getptr(**this); + } + + inline Iterator(Tcl_Interp* interp, Tcl_Obj* obj, int idx) + : _interp(interp) + , _idx(idx) + , _obj(obj) {} + + protected: + Tcl_Interp* _interp; + int _idx; /* Currently visited index on the list */ + Tcl_Obj* _obj; /* Underlying Tcl_Obj representing the list */ + }; + + inline Iterator begin() { + return Iterator(this->_interp, this->_obj, 0); + } + + inline Iterator end() { + int count; + Tcl_ListObjLength(this->_interp, this->_obj, &count); + return Iterator(this->_interp, this->_obj, count); + } + + /** + * @brief Get number of elements on the list. + */ + inline size_t size() const { + int len; + Tcl_ListObjLength(this->_interp, this->_obj, &len); + return size_t(len); + } + + protected: + Tcl_Interp* _interp; + Tcl_Obj* _obj; /* Underlying Tcl_Obj representing the list */ +}; + +class TclCtx; +class TclClient; + +TclDynList tcl_obj_getdynlist(TclClient* client, Tcl_Obj* obj); + +/** + * @brief Provide a C++ state with TCL interface. + * + * This class should be a base for a Client class to implement a TCL interface. + * + * Commands need to be registered within + * template + * void register_methods(F register_method) + * method template. + * + * Commands are implemented as methods of TclClient-derived class: + * int(DerivedClient::*)(int objc, Tcl_Obj* const objvp[])) + * + * Use TclClient::_ret_* methods to return from a command implementation. + */ +class TclClient { + public: + e_TclCommandStatus cmd_status; + std::string string; + Tcl_Obj* object; + + /** + * Define this method template in a derived TclClient: + * + * template + * void register_methods(F register_method) + * + * F should be a functor of acting as the following type: + * void(*)(const char* name, int(DerivedClient::*)(int objc, Tcl_Obj* const objvp[])) + * + * (alternatively you can define a method taking std::function) + * + * Eg.: + * template + * void DerivedClient::register_methods(F register_method) { + * register_method("my_custom_command", DerivedClient::my_custom_command); + * register_method("my_other_custom_command", DerivedClient::my_other_custom_command); + * } + * + * In the future if C++20 is going to be adopted as a standard for VPR, this should be + * handled using a concept. + */ + + protected: + /* TclClient must be derived */ + TclClient(); + + /* TclClient must be immovable to prevent invalidating any references to it stored within TCL objects */ + TclClient(TclClient&& other) = delete; + /* TclClient shouldn't be copied, because that would require duplicating this->object and binding it to + * the new copy of a client. This might be an unwanted behaviour. */ + TclClient(const TclClient& other) = delete; + TclClient& operator=(const TclClient& other) = delete; + + /* Return methods + * Use these when returning from a TCL command implementation. + */ + + /** + * @brief Throw an error from a TCL command. + */ + inline int _ret_error(TclCommandError&& error) { + this->cmd_status = e_TclCommandStatus::TCL_CMD_FAIL; + this->string = std::move(error); + return TCL_ERROR; + } + + /** + * @brief Return from a TCL command with no errors. + */ + inline int _ret_ok() { + this->cmd_status = e_TclCommandStatus::TCL_CMD_SUCCESS; + return TCL_OK; + } + + /** + * @brief Return a string from a TCL command with no errors. + */ + inline int _ret_string(std::string&& string_) { + this->cmd_status = e_TclCommandStatus::TCL_CMD_SUCCESS_STRING; + this->string = std::move(string_); + return TCL_OK; + } + + /** + * @brief Move an object to TCL-managed context and return it in TCL code. + * @param T registered custom TCL type + * @param this_ pointer to the concrete client. Note that it might be different than `this` within context of base class. + * @param object_data object to be moved into TCL-managed context + */ + template::value>::type> + int _ret_obj(void* this_, const T&& object_data) { + Tcl_Obj* obj = Tcl_MoveCppObject(this_, object_data); + + this->object = obj; + this->cmd_status = e_TclCommandStatus::TCL_CMD_SUCCESS_OBJECT; + + return TCL_OK; + } + + template + TclList _list(void* this_, Container&& elements) { + return TclList(this->_interp, reinterpret_cast(this_), std::move(elements)); + } + + template + int _ret_list(TclList& list) { + this->object = list.tcl_obj(); + this->cmd_status = e_TclCommandStatus::TCL_CMD_SUCCESS_LIST; + + return TCL_OK; + } + + private: + friend class TclCtx; + template + friend TclList tcl_obj_getlist(TclClient* client, Tcl_Obj* obj); + friend TclDynList tcl_obj_getdynlist(TclClient* client, Tcl_Obj* obj); + + Tcl_Interp* _interp; +}; + +template +static TclList tcl_obj_getlist(TclClient* client, Tcl_Obj* obj) { + return TclList(client->_interp, obj); +} + +/* Default implementations for handling TCL-managed C++ objects */ +template::value>::type> +static void tcl_obj_free(Tcl_Obj* obj) { + T* obj_data = tcl_obj_getptr(obj); + obj_data->~T(); + Tcl_Free(reinterpret_cast(obj_data)); +} +void tcl_obj_dup(Tcl_Obj* src, Tcl_Obj* dst); +int tcl_set_from_none(Tcl_Interp* tcl_interp, Tcl_Obj* obj); + +/** + * @brief set TCL object's string representation + */ +void tcl_set_obj_string(Tcl_Obj* obj, const std::string& str); + +/** + * @brief Provide a TCL runtime. + * Allows extending TCL language with new types which are associated with C++ types and new methods provided by objects of TclClient-derived classes. + * + * `TclCtx` binds a TCL context to the lifetime of a C++ object, so once TclCtx::~TclCtx gets called, TCL context will be cleaned-up. + * It is illegal to call any method of `TclCtx` othar than its destructor after destroying a bound client. + */ +class TclCtx { + public: + /** + * @brief Create and initialize context, then run a closure with it. + * This is done to limit TclCtx lifetime. + */ + template + static T with_ctx(std::function fun) { + TclCtx ctx; + ctx._init(); + fun(ctx); + } + + /** + * @brief Read and interpret a TCL script within this context. + */ + void read_tcl(std::istream& tcl_stream); + + /** + * @brief Add a client class that provides methods to interface with a context managed by it + */ + template + void add_tcl_client(C& client) { + auto* client_container = new TclClientContainer(client); + auto register_command = [&](const char* name, int (C::*method)(int objc, Tcl_Obj* const objvp[])) { + client_container->methods.push_front(TclMethodDispatch(method, client)); + auto& md = client_container->methods.front(); + Tcl_CreateObjCommand(this->_tcl_interp, name, TclCtx::_tcl_do_method, static_cast(&md), nullptr); + }; + + client._interp = this->_tcl_interp; + + client.register_methods(register_command); + + this->_tcl_clients.push_front(std::unique_ptr(static_cast(client_container))); + } + + /** + * @brief Add a custom type to TCL. + * + * The type has to be moveable in order to allow passing it between Tcl-managed cotext and C++ code. + * Use REGISTER_TCL_TYPE macro to associate type T with a static struct instance describing the Tcl type. + */ + template::value && std::is_move_constructible::value>::type> + inline void add_tcl_type() { + Tcl_RegisterObjType(tcl_type_of()); + } + + protected: + TclCtx(); + + ~TclCtx(); + + /** + * @brief Initialize a newly created instance of TclCtx. It's illegal to call any other method before calling this one. + */ + void _init(); + + /** + * @brief Tcl by default will interpret bus indices as calls to commands + * (eg. in[0] gets interpreted as call to command `i` with argument being a + * result of calling `0`). This overrides this behaviour. + * + * TODO: This is SDC/XDC-specific and should be handled somewhere else. + */ + /* int _fix_port_indexing(); */ + + /** + * @brief Provide an interface to call a client method. + */ + struct TclMethodDispatchBase { + inline TclMethodDispatchBase(TclClient& client_) + : client(client_) {} + virtual ~TclMethodDispatchBase() {} + virtual int do_method(int objc, Tcl_Obj* const objvp[]) = 0; + TclClient& client; + }; + + /** + * @brief Wrap a call-by-PTM (pointer-to-method) of a concrete TclClient-derived class as a virtual function of TclMethodDispatchBase. + * This allows us to avoid making methods defined within TclClient-derived virtual methods of TclClient. + */ + template + struct TclMethodDispatch : TclMethodDispatchBase { + typedef int (C::*TclMethod)(int objc, Tcl_Obj* const objvp[]); + inline TclMethodDispatch(TclMethod method_, TclClient& client_) + : TclMethodDispatchBase(client_) + , method(method_) {} + virtual ~TclMethodDispatch() = default; + + virtual int do_method(int objc, Tcl_Obj* const objvp[]) { + auto& casted_client = static_cast(this->client); + return (casted_client.*(this->method))(objc, objvp); + } + + TclMethod method; + }; + + struct TclClientContainerBase {}; + + /** + * @brief Store a reference to a client along with method dispatch list associated with that client. + */ + template + struct TclClientContainer : TclClientContainerBase { + inline TclClientContainer(C& client_) + : client(client_) {} + C& client; + /* Use list to guarantee the validity of references */ + std::list> methods; + }; + + static int _tcl_do_method( + ClientData cd, + Tcl_Interp* tcl_interp, + int objc, + Tcl_Obj* const objvp[]); + + Tcl_Interp* _tcl_interp; + /* Use list to guarantee the validity of references */ + std::list> _tcl_clients; + + private: +#ifdef DEBUG + inline void _debug_init() const { + VTR_ASSERT(this->_init); + } + + bool _init; +#else + inline void _debug_init() const {} +#endif +}; + +#define TCL_TYPE_OF(cxx_type) cxx_type##_tcl_t + +/** + * @brief Declare Tcl type associated with a C++ type + * @param cxx_type C++ type name. + */ +#define DECLARE_TCL_TYPE(cxx_type) \ + extern const Tcl_ObjType TCL_TYPE_OF(cxx_type); \ + template<> \ + constexpr const Tcl_ObjType* tcl_type_of() { \ + return &TCL_TYPE_OF(cxx_type); \ + } + +/** + * @brief Associate a C++ struct/class with a `Tcl_ObjType`. + * @param cxx_type C++ type name. + * + * Foollow it with a body of updateStringProc and close it with END_REGISTER_TCL_TYPE: + * REGISTER_TCL_TYPE_W_STR_UPDATE(CppType)(Tcl_Obj* obj) { ...body... } END_REGISTER_TCL_TYPE; + */ +#define REGISTER_TCL_TYPE_W_STR_UPDATE(cxx_type) \ + const Tcl_ObjType TCL_TYPE_OF(cxx_type) { \ + /* .name = */ #cxx_type, \ + /* .freeIntRepProc = */ tcl_obj_free , \ + /* .dupIntRepProc = */ tcl_obj_dup, \ + /* .updateStringProc = */[] + +#define END_REGISTER_TCL_TYPE \ + , \ + /* .setFromAnyProc = */ tcl_set_from_none \ + } + +#endif \ No newline at end of file diff --git a/libs/libvtrutil/src/vtr_range.h b/libs/libvtrutil/src/vtr_range.h index 493a379fbd9..28aa5296eca 100644 --- a/libs/libvtrutil/src/vtr_range.h +++ b/libs/libvtrutil/src/vtr_range.h @@ -53,9 +53,9 @@ class Range { ///@brief Return an iterator to the end of the range (immutable) const T end() const { return end_; } ///@brief Return true if empty - bool empty() { return begin_ == end_; } + bool empty() const { return begin_ == end_; } ///@brief Return the range size - size_t size() { return std::distance(begin_, end_); } + size_t size() const { return std::distance(begin_, end_); } private: T begin_; diff --git a/vpr/CMakeLists.txt b/vpr/CMakeLists.txt index f43da412c74..fef8150246d 100644 --- a/vpr/CMakeLists.txt +++ b/vpr/CMakeLists.txt @@ -53,6 +53,9 @@ add_library(libvpr STATIC ${LIB_SOURCES} ) +#Find TCL +find_package(TCL REQUIRED) +target_link_libraries(libvpr tcl) target_include_directories(libvpr PUBLIC ${LIB_INCLUDE_DIRS}) @@ -84,6 +87,7 @@ target_link_libraries(libvpr libargparse libpugixml librrgraph + libtclcpp ) #link graphics library only when graphics set to on diff --git a/vpr/src/base/SetupVPR.cpp b/vpr/src/base/SetupVPR.cpp index c03fef2e119..99388d92bda 100644 --- a/vpr/src/base/SetupVPR.cpp +++ b/vpr/src/base/SetupVPR.cpp @@ -97,6 +97,7 @@ void SetupVPR(const t_options* Options, FileNameOpts->CmosTechFile = Options->CmosTechFile; FileNameOpts->out_file_prefix = Options->out_file_prefix; FileNameOpts->read_vpr_constraints_file = Options->read_vpr_constraints_file; + FileNameOpts->read_xdc_constraints_files = Options->XDCFiles; FileNameOpts->write_vpr_constraints_file = Options->write_vpr_constraints_file; FileNameOpts->write_block_usage = Options->write_block_usage; @@ -118,6 +119,7 @@ void SetupVPR(const t_options* Options, Arch, device_ctx.physical_tile_types, device_ctx.logical_block_types); + VTR_ASSERT_MSG(Options->XDCFiles.value().size() != 0, "XDC constraints are not supported for VTR format"); break; case e_arch_format::FPGAInterchange: VTR_LOG("Use FPGA Interchange device\n"); diff --git a/vpr/src/base/read_options.cpp b/vpr/src/base/read_options.cpp index 074e84a2e8c..7d1f67bf78d 100644 --- a/vpr/src/base/read_options.cpp +++ b/vpr/src/base/read_options.cpp @@ -1518,6 +1518,12 @@ argparse::ArgumentParser create_arg_parser(std::string prog_name, t_options& arg .help("Path to timing constraints file in SDC format") .show_in(argparse::ShowIn::HELP_ONLY); + file_grp.add_argument(args.XDCFiles, "--xdc_files") + .nargs('+') + .default_value({}) + .help("Path to Xilinx's Design Constraints file") + .show_in(argparse::ShowIn::HELP_ONLY); + file_grp.add_argument(args.read_rr_graph_file, "--read_rr_graph") .help( "The routing resource graph file to load." diff --git a/vpr/src/base/read_options.h b/vpr/src/base/read_options.h index d2edecc27ac..16cf3287f89 100644 --- a/vpr/src/base/read_options.h +++ b/vpr/src/base/read_options.h @@ -19,6 +19,7 @@ struct t_options { argparse::ArgValue PowerFile; argparse::ArgValue CmosTechFile; argparse::ArgValue SDCFile; + argparse::ArgValue> XDCFiles; argparse::ArgValue arch_format; argparse::ArgValue circuit_format; diff --git a/vpr/src/base/vpr_api.cpp b/vpr/src/base/vpr_api.cpp index f4d759a862e..90a5fb3c555 100644 --- a/vpr/src/base/vpr_api.cpp +++ b/vpr/src/base/vpr_api.cpp @@ -69,6 +69,8 @@ #include "cluster.h" #include "output_clustering.h" #include "vpr_constraints_reader.h" +#include "tclcpp.h" +#include "xdc_constraints.h" #include "place_constraints.h" #include "place_util.h" @@ -343,8 +345,16 @@ void vpr_init_with_options(const t_options* options, t_vpr_setup* vpr_setup, t_a //Initialize vpr floorplanning constraints auto& filename_opts = vpr_setup->FileNameOpts; - if (!filename_opts.read_vpr_constraints_file.empty()) { + if (!filename_opts.read_vpr_constraints_file.empty()) load_vpr_constraints_file(filename_opts.read_vpr_constraints_file.c_str()); + if (filename_opts.read_xdc_constraints_files.size()) { + try { + load_xdc_constraints_files(filename_opts.read_xdc_constraints_files, *arch, atom_ctx.nlist); + } catch (const TCL_eErroneousTCL& e) { + vpr_throw(VPR_ERROR_XDC, e.filename.c_str(), e.line, e.message.c_str()); + } catch (const TCL_eException& e) { + VPR_THROW(VPR_ERROR_OTHER, "Error loading XDC: %s\n", std::string(e).c_str()); + } } fflush(stdout); @@ -1392,6 +1402,9 @@ void vpr_print_error(const VprError& vpr_error) { case VPR_ERROR_SDC: error_type = "SDC file"; break; + case VPR_ERROR_XDC: + error_type = "XDC file"; + break; case VPR_ERROR_NET_F: error_type = "Netlist file"; break; diff --git a/vpr/src/base/vpr_constraints.cpp b/vpr/src/base/vpr_constraints.cpp index 95c7e7b7358..927df3e3d4f 100644 --- a/vpr/src/base/vpr_constraints.cpp +++ b/vpr/src/base/vpr_constraints.cpp @@ -28,8 +28,9 @@ PartitionId VprConstraints::get_atom_partition(AtomBlockId blk_id) { } } -void VprConstraints::add_partition(Partition part) { +PartitionId VprConstraints::add_partition(Partition part) { partitions.push_back(part); + return PartitionId(partitions.size() - 1); } Partition VprConstraints::get_partition(PartitionId part_id) { diff --git a/vpr/src/base/vpr_constraints.h b/vpr/src/base/vpr_constraints.h index fd3f64842a4..524c9ae5a54 100644 --- a/vpr/src/base/vpr_constraints.h +++ b/vpr/src/base/vpr_constraints.h @@ -55,11 +55,11 @@ class VprConstraints { PartitionId get_atom_partition(AtomBlockId blk_id); /** - * @brief Store a partition + * @brief Store a partition, return its id * * @param part The partition being stored */ - void add_partition(Partition part); + PartitionId add_partition(Partition part); /** * @brief Return a partition diff --git a/vpr/src/base/vpr_types.h b/vpr/src/base/vpr_types.h index f4c72fdcadd..106f15425a2 100644 --- a/vpr/src/base/vpr_types.h +++ b/vpr/src/base/vpr_types.h @@ -721,6 +721,7 @@ struct t_file_name_opts { std::string CmosTechFile; std::string out_file_prefix; std::string read_vpr_constraints_file; + std::vector read_xdc_constraints_files; std::string write_vpr_constraints_file; std::string write_block_usage; bool verify_file_digests; diff --git a/vpr/src/base/xdc_constraints.cpp b/vpr/src/base/xdc_constraints.cpp new file mode 100644 index 00000000000..7e0a50a2b4d --- /dev/null +++ b/vpr/src/base/xdc_constraints.cpp @@ -0,0 +1,299 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "globals.h" +#include "vpr_constraints.h" +#include "atom_netlist.h" + +#include "tclcpp.h" +#include "xdc_constraints.h" + +enum class e_XDCProperty { + XDC_PROP_PACKAGE_PIN, + XDC_PROP_IOSTANDARD, + XDC_PROP_UNKNOWN +}; + +static e_XDCProperty xdc_prop_from_str(const char* str) { + if (!std::strcmp(str, "PACKAGE_PIN")) + return e_XDCProperty::XDC_PROP_PACKAGE_PIN; + if (!std::strcmp(str, "IOSTANDARD")) + return e_XDCProperty::XDC_PROP_IOSTANDARD; + + return e_XDCProperty::XDC_PROP_UNKNOWN; +} + +DECLARE_TCL_TYPE(AtomPortId) + +class TclPhysicalConstraintsClient : public TclClient { + public: + VprConstraints& constraints; + const t_arch& arch; + AtomNetlist& netlist; + + TclPhysicalConstraintsClient(VprConstraints& constraints_, const t_arch& arch_, AtomNetlist& netlist_) + : constraints(constraints_) + , arch(arch_) + , netlist(netlist_) {} + + template + void register_methods(F register_method) { + register_method("get_ports", &TclPhysicalConstraintsClient::get_ports); + register_method("set_property", &TclPhysicalConstraintsClient::set_property); + register_method("get_cells", &TclPhysicalConstraintsClient::get_cells); + } + + /* TCL functions */ + + int set_property(int objc, Tcl_Obj* const objvp[]) { + if (objc < 3) + return this->_ret_error("set_property: Expected at least 2 arguments, got " + std::to_string(objc) + "."); + + const char* property_name = Tcl_GetString(objvp[1]); + if (property_name == nullptr) + return this->_ret_error("set_property: First argument should be a string."); + + e_XDCProperty property = xdc_prop_from_str(property_name); + + switch (property) { + case e_XDCProperty::XDC_PROP_PACKAGE_PIN: + if (objc != 4) + return this->_ret_error( + "set_property: Property `PACKAGE_PIN` " + "requires one target and one value."); + return this->_set_property_package_pin(objvp[2], objvp[3]); + case e_XDCProperty::XDC_PROP_IOSTANDARD: + if (objc != 4) + return this->_ret_error( + "set_property: Property `IOSTANDARD` " + "requires one value and one target."); + return this->_set_property_iostandard(objvp[2], objvp[3]); + case e_XDCProperty::XDC_PROP_UNKNOWN: + return this->_ret_error("set_property: Property `" + std::string(property_name) + "` is not recognized."); + default: + break; + } + return this->_ret_error("set_property: Property `" + std::string(property_name) + "` is not supported."); + } + + int get_ports(int objc, Tcl_Obj* const objvp[]) { + if (objc < 2) + return this->_ret_error("get_ports: Expected one or more arguments."); + + std::vector port_names; + + for (int i = 1; i < objc; i++) { + const char* pin_name = Tcl_GetString(objvp[i]); + if (pin_name == nullptr) + return this->_ret_error("get_ports: pin_name should be a string."); + port_names.push_back(pin_name); + } + + return this->_do_get_ports(std::move(port_names)); + } + + int get_cells(int objc, Tcl_Obj* const objvp[]) { + (void)objc; + (void)objvp; + return this->_ret_error("get_cells: unimplemented"); + } + + protected: + int _set_property_package_pin(Tcl_Obj* tcl_pin_name, Tcl_Obj* tcl_ports) { + const char* pin_name = Tcl_GetString(tcl_pin_name); + if (pin_name == nullptr) + return this->_ret_error("set_property: pin_name of PACKAGE_PIN should be a string."); + + auto port_list = tcl_obj_getlist(static_cast(this), tcl_ports); + size_t port_count = port_list.size(); + if (port_count != 1) + return this->_ret_error("set_property PACKAGE_PIN: Expected 1 port, got " + std::to_string(port_count) + "."); + auto* port = *port_list.begin(); + if (port == nullptr) + return this->_ret_error("set_property: port_name of PACKAGE_PIN should have a `AtomPortId` type."); + + return this->_do_set_property_package_pin(pin_name, *port); + } + + int _do_set_property_package_pin(const char* pin_name, AtomPortId port) { + /* Find associated AtomBlock */ + + const auto& phys_grid_mapping = this->arch.phys_grid_mapping; + + std::string pin_str(pin_name); + auto it = phys_grid_mapping.find(pin_str); + if (it == phys_grid_mapping.end()) + return this->_ret_error("Can't find physical PIN named `" + pin_str + "`"); + const t_phys_map_region& package_pin_region = it->second; + + AtomBlockId port_block = this->netlist.port_block(port); + + /* Create a 1x1 VprConstraints Partition to contrain the block */ + + Partition part; + part.set_name(pin_str + ".PART"); + Region r; + r.set_region_rect( + package_pin_region.x, + package_pin_region.y, + package_pin_region.x + package_pin_region.w - 1, + package_pin_region.y + package_pin_region.h - 1); + r.set_sub_tile(package_pin_region.subtile); + PartitionRegion pr; + pr.add_to_part_region(r); + part.set_part_region(pr); + PartitionId part_id = this->constraints.add_partition(part); + + /* Constrain the AtomBlock */ + + this->constraints.add_constrained_atom(port_block, part_id); + + VTR_LOG("XDC: Assigned PACKAGE_PIN %s = [%d, %d, %d, %d].%d\n", + pin_name, package_pin_region.x, package_pin_region.y, + package_pin_region.w, package_pin_region.h, package_pin_region.subtile); + + return this->_ret_ok(); + } + + int _set_property_iostandard(Tcl_Obj* tcl_io_standard, Tcl_Obj* tcl_ports) { + const char* io_standard = Tcl_GetString(tcl_io_standard); + + auto ports = tcl_obj_getlist(static_cast(this), tcl_ports); + + return this->_do_set_property_iostandard(io_standard, ports); + } + + int _do_set_property_iostandard(const char* io_standard, TclList& ports) { + for (auto port : ports) { + if (port == nullptr) + return this->_ret_error("set_property: port_name of IOSTANDARD should have a `AtomPortId` type."); + AtomBlockId block = this->netlist.port_block(*port); + this->netlist.set_block_param(block, "IOSTANDARD", io_standard); + VTR_LOG("XDC: Assigned IOSTANDARD %s = %s\n", this->netlist.block_name(block).c_str(), io_standard); + } + + return this->_ret_ok(); + } + + int _do_get_ports(std::vector pin_names) { + std::vector port_ids; + for (auto pin_name : pin_names) { + AtomPortId port; + int result = this->_do_get_port(pin_name, &port); + if (result != TCL_OK) + return result; + port_ids.push_back(port); + } + + auto port_list = this->_list(this, std::move(port_ids)); + return this->_ret_list(port_list); + } + + int _do_get_port(const char* pin_name, AtomPortId* port_) { + AtomBlockId pin_block = this->netlist.find_block(pin_name); + + if (pin_block == AtomBlockId::INVALID()) { + std::string available_ports; + const auto& pins = this->netlist.blocks(); + int pins_left = pins.size(); + for (auto& pin_block_ : pins) { + auto ports = this->netlist.block_ports(pin_block_); + auto port_it = this->netlist.block_ports(pin_block_).begin(); + if (port_it != ports.end()) { + available_ports += this->netlist.block_name(pin_block_); + if (pins_left != 1) + available_ports += ", "; + } + pins_left--; + } + + return this->_ret_error("get_ports: Can't find ports for `" + std::string(pin_name) + "`. Available blocks: " + available_ports); + } + + auto ports = this->netlist.block_ports(pin_block); + + auto port_it = this->netlist.block_ports(pin_block).begin(); + if (port_it == ports.end()) + return this->_ret_error("get_ports: '" + std::string(pin_name) + "' - no ports found."); + + /* We assume that there's only one port, which just happens to be true in case of fpga interchange. + * If we ever want to handle it better, we should return a list of port ids instead. + */ + VTR_ASSERT(ports.size() == 1); + AtomPortId port = *ports.begin(); + std::string port_name = this->netlist.block_name(pin_block); + + VTR_LOG("get_ports: Got port `%s` (%llu) for pin `%s`\n", port_name.c_str(), (size_t)port, pin_name); + + *port_ = port; + return this->_ret_ok(); + } +}; + +REGISTER_TCL_TYPE_W_STR_UPDATE(AtomPortId) +(Tcl_Obj* obj) { + const auto* port = tcl_obj_getptr(obj); + const auto* client = tcl_obj_get_ctx_ptr(obj); + std::string port_name = client->netlist.port_name(*port); + tcl_set_obj_string(obj, port_name); +} +END_REGISTER_TCL_TYPE; + +VprConstraints read_xdc_constraints_to_vpr(std::vector&& xdc_streams, const t_arch& arch, AtomNetlist& netlist) { + VprConstraints constraints; + TclPhysicalConstraintsClient pc_client(constraints, arch, netlist); + + TclCtx::with_ctx([&](TclCtx& ctx) { + /* + * Tcl by default will interpret bus indices as calls to commands + * (eg. in[0] gets interpreted as call to command `i` with argument being a + * result of calling `0`). This overrides this behaviour. + */ + std::istringstream fix_port_indexing( + "rename unknown _original_unknown\n" + "proc unknown args {\n" + " set result [scan [lindex $args 0] \"%d\" value]\n" + " if { $result == 1 && [llength $args] == 1 } {\n" + " return \\[$value\\]\n" + " } else {\n" + " uplevel 1 [list _original_unknown {*}$args]\n" + " }\n" + "}"); + ctx.read_tcl(fix_port_indexing); + + /* Add types and commands to handle XDC files */ + ctx.add_tcl_type(); + ctx.add_tcl_client(pc_client); + + /* Read and interpret XDCs */ + for (auto& stream : xdc_streams) { + try { + ctx.read_tcl(*stream.stream); + } catch (TCL_eErroneousTCL& e) { + e.filename = stream.name; + throw e; + } + } + }); + + /* At this point `pc_client` has written the contraints */ + return constraints; +} + +void load_xdc_constraints_files(const std::vector xdc_paths, const t_arch& arch, AtomNetlist& netlist) { + std::vector xdc_streams; + for (auto&& path : xdc_paths) { + VTR_ASSERT(vtr::check_file_name_extension(path.c_str(), ".xdc")); + VTR_LOG("Reading XDC %s...\n", path.c_str()); + auto file = std::unique_ptr(new std::ifstream(path)); + xdc_streams.push_back(XDCStream(std::move(path), std::move(file))); + } + + FloorplanningContext& floorplanning_ctx = g_vpr_ctx.mutable_floorplanning(); + floorplanning_ctx.constraints = read_xdc_constraints_to_vpr(std::move(xdc_streams), arch, netlist); +} diff --git a/vpr/src/base/xdc_constraints.h b/vpr/src/base/xdc_constraints.h new file mode 100644 index 00000000000..5419c40942d --- /dev/null +++ b/vpr/src/base/xdc_constraints.h @@ -0,0 +1,53 @@ +#ifndef XDC_CONSTRAINTS_H +#define XDC_CONSTRAINTS_H + +#include +#include +#include +#include + +#include "atom_netlist.h" +#include "physical_types.h" +#include "vpr_constraints.h" + +class XDCStream { + public: + inline XDCStream(std::string name_, std::unique_ptr&& stream_) + : name(name_) + , stream(std::move(stream_)) {} + + std::string name; + std::unique_ptr stream; +}; + +/** + * @brief Creates VprConstraints from input stream in XDC format. + * @param xdc_stream XDC script + * @param arch reference to loaded device architecture + * @param netlist loaded AtomNetlist + * @throws TCL_eException: base class for all exceptions + * TCL_eFailedToInitTcl: Failure during initializations of TCL subsystem + * TCL_eErroneousTCL: Failure when parsing XDC + * TCL_eCustomException: Anything else that doesn't fit he above categories. + * @return VprConstraints created from an XDC script. + * + * This function may modify the netlist by changing its blocks' properties. + */ +VprConstraints read_xdc_constraints_to_vpr(std::vector&& xdc_streams, const t_arch& arch, AtomNetlist& netlist); + +/** + * @brief Parse a file in XDC format and apply it to global FloorplanningContext. + * @param xdc_paths paths to XDC files with constraints + * @param arch reference to loaded device architecture + * @param netlist loaded AtomNetlist + * @throws TCL_eException: base class for all exceptions + * TCL_eFailedToInitTcl: Failure during initializations of TCL subsystem + * TCL_eErroneousTCL: Failure when parsing XDC + * TCL_eCustomException: Anything else that doesn't fit he above categories. + * + * This function overwrites global floorplanning constraints (g_vpr.constraints_.constraints)\ + * This function may modify the netlist by changing its blocks' properties. + */ +void load_xdc_constraints_files(const std::vector xdc_paths, const t_arch& arch, AtomNetlist& netlist); + +#endif diff --git a/vpr/src/util/vpr_error.h b/vpr/src/util/vpr_error.h index 0c3d5a1cbf8..0c1076100e9 100644 --- a/vpr/src/util/vpr_error.h +++ b/vpr/src/util/vpr_error.h @@ -18,6 +18,7 @@ enum e_vpr_error { VPR_ERROR_TIMING, VPR_ERROR_POWER, VPR_ERROR_SDC, + VPR_ERROR_XDC, // File parsing errors VPR_ERROR_NET_F, // Error while parsing the packed netlist file