From 20dfebb09f3d5f73c6a45d6537bc062702fbd8b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Ko=C5=9Bcielnicki?= Date: Tue, 16 Apr 2019 12:59:09 +0200 Subject: [PATCH] wip --- common/nextpnr.h | 1 + common/place_common.cc | 122 +++++++++++-------- common/placer_heap.cc | 6 + ecp5/arch.h | 2 + generic/arch.h | 1 + gui/leuctra/mainwindow.cc | 36 +++++- gui/leuctra/mainwindow.h | 4 + ice40/arch.h | 2 + leuctra/arch.cc | 50 +++++++- leuctra/arch.h | 34 ++++-- leuctra/cells.cc | 244 ++++++++++++++++++++++++++++++++++++++ leuctra/cells.h | 28 +++++ leuctra/main.cc | 44 ++++++- leuctra/pack.cc | 201 ++++++++++++++++++++++++++++++- leuctra/textcfg.cc | 59 +++++++++ leuctra/textcfg.h | 32 +++++ leuctra/ucf.cc | 143 ++++++++++++++++++++++ 17 files changed, 944 insertions(+), 65 deletions(-) create mode 100644 leuctra/cells.cc create mode 100644 leuctra/textcfg.cc create mode 100644 leuctra/textcfg.h create mode 100644 leuctra/ucf.cc diff --git a/common/nextpnr.h b/common/nextpnr.h index 61e04415..71bcef19 100644 --- a/common/nextpnr.h +++ b/common/nextpnr.h @@ -443,6 +443,7 @@ struct CellInfo : ArchCellInfo int constr_y = UNCONSTR; // this.y - parent.y int constr_z = UNCONSTR; // this.z - parent.z bool constr_abs_z = false; // parent.z := 0 + int constr_spec = -1; // parent.[xyz] := 0 when (constr_parent == nullptr) Region *region = nullptr; diff --git a/common/place_common.cc b/common/place_common.cc index cb9799b5..e3b8f50a 100644 --- a/common/place_common.cc +++ b/common/place_common.cc @@ -234,7 +234,7 @@ class ConstraintLegaliseWorker } if (!ctx->checkBelAvail(locBel)) { CellInfo *confCell = ctx->getConflictingBelCell(locBel); - if (confCell->belStrength >= STRENGTH_STRONG) { + if (confCell != cell && confCell->belStrength >= STRENGTH_STRONG) { return false; } } @@ -247,49 +247,59 @@ class ConstraintLegaliseWorker usedLocations.insert(loc); for (auto child : cell->constr_children) { IncreasingDiameterSearch xSearch, ySearch, zSearch; - if (child->constr_x == child->UNCONSTR) { - xSearch = IncreasingDiameterSearch(loc.x, 0, ctx->getGridDimX() - 1); - } else { - xSearch = IncreasingDiameterSearch(loc.x + child->constr_x); - } - if (child->constr_y == child->UNCONSTR) { - ySearch = IncreasingDiameterSearch(loc.y, 0, ctx->getGridDimY() - 1); - } else { - ySearch = IncreasingDiameterSearch(loc.y + child->constr_y); - } - if (child->constr_z == child->UNCONSTR) { - zSearch = IncreasingDiameterSearch(loc.z, 0, ctx->getTileBelDimZ(loc.x, loc.y)); - } else { - if (child->constr_abs_z) { - zSearch = IncreasingDiameterSearch(child->constr_z); - } else { - zSearch = IncreasingDiameterSearch(loc.z + child->constr_z); - } - } bool success = false; - while (!xSearch.done()) { - Loc cloc; - cloc.x = xSearch.get(); - cloc.y = ySearch.get(); - cloc.z = zSearch.get(); - - zSearch.next(); - if (zSearch.done()) { - zSearch.reset(); - ySearch.next(); - if (ySearch.done()) { - ySearch.reset(); - xSearch.next(); + if (child->constr_spec != -1) { + BelId child_bel = ctx->getRelatedBel(locBel, child->constr_spec); + if (child_bel != BelId()) { + Loc cloc = ctx->getBelLocation(child_bel); + if (!usedLocations.count(cloc) && valid_loc_for(child, cloc, solution, usedLocations)) { + success = true; + } + } + } else { + if (child->constr_x == child->UNCONSTR) { + xSearch = IncreasingDiameterSearch(loc.x, 0, ctx->getGridDimX() - 1); + } else { + xSearch = IncreasingDiameterSearch(loc.x + child->constr_x); + } + if (child->constr_y == child->UNCONSTR) { + ySearch = IncreasingDiameterSearch(loc.y, 0, ctx->getGridDimY() - 1); + } else { + ySearch = IncreasingDiameterSearch(loc.y + child->constr_y); + } + if (child->constr_z == child->UNCONSTR) { + zSearch = IncreasingDiameterSearch(loc.z, 0, ctx->getTileBelDimZ(loc.x, loc.y)); + } else { + if (child->constr_abs_z) { + zSearch = IncreasingDiameterSearch(child->constr_z); + } else { + zSearch = IncreasingDiameterSearch(loc.z + child->constr_z); } } - - if (usedLocations.count(cloc)) - continue; - if (valid_loc_for(child, cloc, solution, usedLocations)) { - success = true; - break; + while (!xSearch.done()) { + Loc cloc; + cloc.x = xSearch.get(); + cloc.y = ySearch.get(); + cloc.z = zSearch.get(); + + zSearch.next(); + if (zSearch.done()) { + zSearch.reset(); + ySearch.next(); + if (ySearch.done()) { + ySearch.reset(); + xSearch.next(); + } + } + + if (usedLocations.count(cloc)) + continue; + if (valid_loc_for(child, cloc, solution, usedLocations)) { + success = true; + break; + } } - } + } if (!success) { usedLocations.erase(loc); return false; @@ -316,7 +326,7 @@ class ConstraintLegaliseWorker return true; // Only process chain roots if (constraints_satisfied(cell)) { if (cell->constr_children.size() > 0 || cell->constr_x != cell->UNCONSTR || - cell->constr_y != cell->UNCONSTR || cell->constr_z != cell->UNCONSTR) + cell->constr_y != cell->UNCONSTR || cell->constr_z != cell->UNCONSTR || cell->constr_spec != -1) lockdown_chain(cell); } else { IncreasingDiameterSearch xRootSearch, yRootSearch, zRootSearch; @@ -513,16 +523,26 @@ int get_constraints_distance(const Context *ctx, const CellInfo *cell) if (cell->constr_parent->bel == BelId()) return 100000; Loc parent_loc = ctx->getBelLocation(cell->constr_parent->bel); - if (cell->constr_x != cell->UNCONSTR) - dist += std::abs(cell->constr_x - (loc.x - parent_loc.x)); - if (cell->constr_y != cell->UNCONSTR) - dist += std::abs(cell->constr_y - (loc.y - parent_loc.y)); - if (cell->constr_z != cell->UNCONSTR) { - if (cell->constr_abs_z) - dist += std::abs(cell->constr_z - loc.z); - else - dist += std::abs(cell->constr_z - (loc.z - parent_loc.z)); - } + if (cell->constr_spec != -1) { + BelId child_bel = ctx->getRelatedBel(cell->constr_parent->bel, cell->constr_spec); + if (child_bel == BelId()) + return 100000; + Loc child_loc = ctx->getBelLocation(child_bel); + dist += std::abs(child_loc.x - loc.x); + dist += std::abs(child_loc.y - loc.y); + dist += std::abs(child_loc.z - loc.z); + } else { + if (cell->constr_x != cell->UNCONSTR) + dist += std::abs(cell->constr_x - (loc.x - parent_loc.x)); + if (cell->constr_y != cell->UNCONSTR) + dist += std::abs(cell->constr_y - (loc.y - parent_loc.y)); + if (cell->constr_z != cell->UNCONSTR) { + if (cell->constr_abs_z) + dist += std::abs(cell->constr_z - loc.z); + else + dist += std::abs(cell->constr_z - (loc.z - parent_loc.z)); + } + } } for (auto child : cell->constr_children) dist += get_constraints_distance(ctx, child); diff --git a/common/placer_heap.cc b/common/placer_heap.cc index f336f6e4..8bd88c5f 100644 --- a/common/placer_heap.cc +++ b/common/placer_heap.cc @@ -960,12 +960,18 @@ class HeAPPlacer targets.emplace_back(vc, target); for (auto child : vc->constr_children) { Loc cloc = ploc; + if (child->constr_spec != -1) { + BelId base_bel = ctx->getBelByLocation(ploc); + BelId child_bel = ctx->getRelatedBel(base_bel, child->constr_spec); + cloc = ctx->getBelLocation(child_bel); + } else { if (child->constr_x != child->UNCONSTR) cloc.x += child->constr_x; if (child->constr_y != child->UNCONSTR) cloc.y += child->constr_y; if (child->constr_z != child->UNCONSTR) cloc.z = child->constr_abs_z ? child->constr_z : (ploc.z + child->constr_z); + } visit.emplace(child, cloc); } } diff --git a/ecp5/arch.h b/ecp5/arch.h index b3e36e52..5ee03218 100644 --- a/ecp5/arch.h +++ b/ecp5/arch.h @@ -607,6 +607,8 @@ struct Arch : BaseCtx return id; } + BelId getRelatedBel(BelId bel, int relation) const { return BelId(); } + std::vector> getBelAttrs(BelId) const { std::vector> ret; diff --git a/generic/arch.h b/generic/arch.h index 444d2636..1325070f 100644 --- a/generic/arch.h +++ b/generic/arch.h @@ -203,6 +203,7 @@ struct Arch : BaseCtx CellInfo *getConflictingBelCell(BelId bel) const; const std::vector &getBels() const; IdString getBelType(BelId bel) const; + BelId getRelatedBel(BelId bel, int relation) const { return BelId(); } const std::map &getBelAttrs(BelId bel) const; WireId getBelPinWire(BelId bel, IdString pin) const; PortType getBelPinType(BelId bel, IdString pin) const; diff --git a/gui/leuctra/mainwindow.cc b/gui/leuctra/mainwindow.cc index dbd5cc7c..fdf009a8 100644 --- a/gui/leuctra/mainwindow.cc +++ b/gui/leuctra/mainwindow.cc @@ -18,6 +18,10 @@ */ #include "mainwindow.h" +#include +#include "log.h" + +#include static void initMainResource() { Q_INIT_RESOURCE(nextpnr); } @@ -44,8 +48,38 @@ void MainWindow::newContext(Context *ctx) setWindowTitle(title.c_str()); } -void MainWindow::createMenu() {} +void MainWindow::createMenu() { + // Add arch specific actions + actionLoadUCF = new QAction("Open UCF", this); + actionLoadUCF->setIcon(QIcon(":/icons/resources/open_ucf.png")); + actionLoadUCF->setStatusTip("Open UCF file"); + actionLoadUCF->setEnabled(false); + connect(actionLoadUCF, &QAction::triggered, this, &MainWindow::open_ucf); + + // Add actions in menus + mainActionBar->addSeparator(); + mainActionBar->addAction(actionLoadUCF); + + menuDesign->addSeparator(); + menuDesign->addAction(actionLoadUCF); +} void MainWindow::new_proj() {} +void MainWindow::open_ucf() +{ + QString fileName = QFileDialog::getOpenFileName(this, QString("Open UCF"), QString(), QString("*.ucf")); + if (!fileName.isEmpty()) { + std::ifstream in(fileName.toStdString()); + if (ctx->applyUCF(fileName.toStdString(), in)) { + log("Loading UCF successful.\n"); + actionPack->setEnabled(true); + actionLoadUCF->setEnabled(false); + } else { + actionLoadUCF->setEnabled(true); + log("Loading UCF failed.\n"); + } + } +} + NEXTPNR_NAMESPACE_END diff --git a/gui/leuctra/mainwindow.h b/gui/leuctra/mainwindow.h index bb6a4cf1..b0616b9c 100644 --- a/gui/leuctra/mainwindow.h +++ b/gui/leuctra/mainwindow.h @@ -38,6 +38,10 @@ class MainWindow : public BaseMainWindow protected Q_SLOTS: void new_proj() override; void newContext(Context *ctx); + void open_ucf(); + + private: + QAction *actionLoadUCF; }; NEXTPNR_NAMESPACE_END diff --git a/ice40/arch.h b/ice40/arch.h index ea29f4f1..c156bf38 100644 --- a/ice40/arch.h +++ b/ice40/arch.h @@ -523,6 +523,8 @@ struct Arch : BaseCtx return IdString(chip_info->bel_data[bel.index].type); } + BelId getRelatedBel(BelId bel, int relation) const { return BelId(); } + std::vector> getBelAttrs(BelId bel) const; WireId getBelPinWire(BelId bel, IdString pin) const; diff --git a/leuctra/arch.cc b/leuctra/arch.cc index 84c5e235..f6beb3fa 100644 --- a/leuctra/arch.cc +++ b/leuctra/arch.cc @@ -20,7 +20,9 @@ #include "nextpnr.h" #include "placer1.h" +#include "placer_heap.h" #include "router1.h" +#include "util.h" NEXTPNR_NAMESPACE_BEGIN @@ -451,8 +453,21 @@ BelPin BelPinIterator::operator*() const { } // ----------------------------------------------------------------------- -// -// XXX package pins + +BelId Arch::getPackagePinBel(const std::string &pin) const +{ + IdString pin_id = id(pin); + for (int i = 0; i < package_info->num_pins; i++) { + if (package_info->pin_data[i].name_id == pin_id.index) { + BelId bel; + bel.location.x = package_info->pin_data[i].bel.tile_x; + bel.location.y = package_info->pin_data[i].bel.tile_y; + bel.index = package_info->pin_data[i].bel.bel_idx; + return bel; + } + } + return BelId(); +} std::vector Arch::getBelPins(BelId bel) const { @@ -490,7 +505,24 @@ bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay // ----------------------------------------------------------------------- -bool Arch::place() { return placer1(getCtx(), Placer1Cfg(getCtx())); } +bool Arch::place() +{ + std::string placer = str_or_default(settings, id("placer"), defaultPlacer); + + if (placer == "heap") { + PlacerHeapCfg cfg(getCtx()); + cfg.criticalityExponent = 7; + cfg.ioBufTypes.insert(id("IOB")); + if (!placer_heap(getCtx(), cfg)) + return false; + } else if (placer == "sa") { + if (!placer1(getCtx(), Placer1Cfg(getCtx()))) + return false; + } else { + log_error("Leuctra architecture does not support placer '%s'\n", placer.c_str()); + } + return true; +} bool Arch::route() { return router1(getCtx(), Router1Cfg(getCtx())); } @@ -563,4 +595,16 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port return info; } +#ifdef WITH_HEAP +const std::string Arch::defaultPlacer = "heap"; +#else +const std::string Arch::defaultPlacer = "sa"; +#endif + +const std::vector Arch::availablePlacers = {"sa", +#ifdef WITH_HEAP + "heap" +#endif +}; + NEXTPNR_NAMESPACE_END diff --git a/leuctra/arch.h b/leuctra/arch.h index 44ea64f0..3a2a150f 100644 --- a/leuctra/arch.h +++ b/leuctra/arch.h @@ -789,6 +789,16 @@ struct Arch : BaseCtx std::vector getBelPins(BelId bel) const; + BelId getRelatedBel(BelId bel, int relation) const { + auto &tile = getTile(bel.location); + auto &related = tile.bels[bel.index].related[relation]; + BelId res; + res.location.x = related.tile_x; + res.location.y = related.tile_y; + res.index = related.bel_idx; + return res; + } + // ------------------------------------------------- WireId getWireByName(IdString name) const; @@ -1107,11 +1117,6 @@ struct Arch : BaseCtx } BelId getPackagePinBel(const std::string &pin) const; - std::string getBelPackagePin(BelId bel) const; - int getPioBelBank(BelId bel) const; - // For getting GCLK, PLL, Vref, etc, pins - std::string getPioFunctionName(BelId bel) const; - BelId getPioByFunctionName(const std::string &name) const; PortType getBelPinType(BelId bel, IdString pin) const; @@ -1170,10 +1175,25 @@ struct Arch : BaseCtx // ------------------------------------------------- // Placement validity checks // TODO: validate bel subtype (SLICEM vs SLICEL, IOBM vs IOBS, ...). - bool isValidBelForCell(CellInfo *cell, BelId bel) const { return true; } - bool isBelLocationValid(BelId bel) const { return true; } + bool isValidBelForCell(CellInfo *cell, BelId bel) const { + if (cell->type == id("LEUCTRA_FF") && (0x924924ull & 1ull << bel.index)) + return false; + return true; + } + bool isBelLocationValid(BelId bel) const { + CellInfo *cell = getBoundBelCell(bel); + if (cell == nullptr) + return true; + else + return isValidBelForCell(cell, bel); + } + + // Apply UCF constraints to the context + bool applyUCF(std::string filename, std::istream &in); //void assignArchInfo(); + static const std::string defaultPlacer; + static const std::vector availablePlacers; }; NEXTPNR_NAMESPACE_END diff --git a/leuctra/cells.cc b/leuctra/cells.cc new file mode 100644 index 00000000..d7d5243d --- /dev/null +++ b/leuctra/cells.cc @@ -0,0 +1,244 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 David Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "cells.h" +#include +#include "design_utils.h" +#include "log.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +void add_port(const Context *ctx, CellInfo *cell, std::string name, PortType dir) +{ + IdString id = ctx->id(name); + cell->ports[id] = PortInfo{id, nullptr, dir}; +} + +std::unique_ptr create_leuctra_cell(Context *ctx, IdString type, std::string name) +{ + static int auto_idx = 0; + std::unique_ptr new_cell = std::unique_ptr(new CellInfo()); + if (name.empty()) { + new_cell->name = ctx->id("$nextpnr_" + type.str(ctx) + "_" + std::to_string(auto_idx++)); + } else { + new_cell->name = ctx->id(name); + } + new_cell->type = type; + + auto copy_bel_ports = [&]() { + // First find a Bel of the target type + BelId tgt; + for (auto bel : ctx->getBels()) { + if (ctx->getBelType(bel) == type) { + tgt = bel; + break; + } + } + NPNR_ASSERT(tgt != BelId()); + for (auto port : ctx->getBelPins(tgt)) { + add_port(ctx, new_cell.get(), port.str(ctx), ctx->getBelPinType(tgt, port)); + } + }; + + if (type == ctx->id("LEUCTRA_FF")) { + new_cell->params[ctx->id("MODE")] = "FFSYNC"; + new_cell->params[ctx->id("INIT")] = "0"; + add_port(ctx, new_cell.get(), "D", PORT_IN); + add_port(ctx, new_cell.get(), "CLK", PORT_IN); + add_port(ctx, new_cell.get(), "CE", PORT_IN); + add_port(ctx, new_cell.get(), "SR", PORT_IN); + add_port(ctx, new_cell.get(), "Q", PORT_OUT); + } else if (type == ctx->id("LEUCTRA_LC")) { + new_cell->params[ctx->id("INIT")] = "0000000000000000000000000000000000000000000000000000000000000000"; + add_port(ctx, new_cell.get(), "I1", PORT_IN); + add_port(ctx, new_cell.get(), "I2", PORT_IN); + add_port(ctx, new_cell.get(), "I3", PORT_IN); + add_port(ctx, new_cell.get(), "I4", PORT_IN); + add_port(ctx, new_cell.get(), "I5", PORT_IN); + add_port(ctx, new_cell.get(), "I6", PORT_IN); + add_port(ctx, new_cell.get(), "O6", PORT_OUT); + } else if (type == ctx->id("IOB")) { + new_cell->params[ctx->id("DIR")] = "INPUT"; + new_cell->attrs[ctx->id("IOSTANDARD")] = "LVCMOS33"; + + add_port(ctx, new_cell.get(), "O", PORT_IN); + add_port(ctx, new_cell.get(), "T", PORT_IN); + add_port(ctx, new_cell.get(), "I", PORT_OUT); + } else if (type == ctx->id("ILOGIC2")) { + add_port(ctx, new_cell.get(), "D", PORT_IN); + add_port(ctx, new_cell.get(), "FABRICOUT", PORT_OUT); + } else if (type == ctx->id("OLOGIC2")) { + add_port(ctx, new_cell.get(), "D1", PORT_IN); + add_port(ctx, new_cell.get(), "OQ", PORT_OUT); + add_port(ctx, new_cell.get(), "T1", PORT_IN); + add_port(ctx, new_cell.get(), "TQ", PORT_OUT); + } else if (type == ctx->id("LUT1")) { + new_cell->params[ctx->id("INIT")] = "0"; + + add_port(ctx, new_cell.get(), "I0", PORT_IN); + add_port(ctx, new_cell.get(), "O", PORT_OUT); + } else { + log_error("unable to create Leuctra cell of type %s", type.c_str(ctx)); + } + return new_cell; +} + +void nxio_to_iob(Context *ctx, CellInfo *nxio, CellInfo *iob, std::vector> &created_cells, + std::unordered_set &todelete_cells) +{ + if (nxio->type == ctx->id("$nextpnr_ibuf")) { + iob->params[ctx->id("DIR")] = "INPUT"; + replace_port(nxio, ctx->id("O"), iob, ctx->id("I")); + } else if (nxio->type == ctx->id("$nextpnr_obuf")) { + iob->params[ctx->id("DIR")] = "OUTPUT"; + replace_port(nxio, ctx->id("I"), iob, ctx->id("O")); + } else if (nxio->type == ctx->id("$nextpnr_iobuf")) { + // N.B. tristate will be dealt with below + iob->params[ctx->id("DIR")] = "BIDIR"; + replace_port(nxio, ctx->id("I"), iob, ctx->id("O")); + replace_port(nxio, ctx->id("O"), iob, ctx->id("I")); + } else { + NPNR_ASSERT(false); + } + NetInfo *donet = iob->ports.at(ctx->id("O")).net; + CellInfo *tbuf = net_driven_by( + ctx, donet, [](const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("$_TBUF_"); }, + ctx->id("Y")); + if (tbuf) { + replace_port(tbuf, ctx->id("A"), iob, ctx->id("O")); + // Need to invert E to form T + std::unique_ptr inv_lut = create_leuctra_cell(ctx, ctx->id("LUT1"), iob->name.str(ctx) + "$invert_T"); + replace_port(tbuf, ctx->id("E"), inv_lut.get(), ctx->id("I0")); + inv_lut->params[ctx->id("INIT")] = "1"; + connect_ports(ctx, inv_lut.get(), ctx->id("O"), iob, ctx->id("T")); + created_cells.push_back(std::move(inv_lut)); + + if (donet->users.size() > 1) { + for (auto user : donet->users) + log_info(" remaining tristate user: %s.%s\n", user.cell->name.c_str(ctx), user.port.c_str(ctx)); + log_error("unsupported tristate IO pattern for IO buffer '%s', " + "instantiate IOBUF manually to ensure correct behaviour\n", + nxio->name.c_str(ctx)); + } + ctx->nets.erase(donet->name); + todelete_cells.insert(tbuf->name); + } +} + +void convert_ff(Context *ctx, CellInfo *orig, CellInfo *ff, std::vector> &created_cells, + std::unordered_set &todelete_cells) +{ + if (orig->type == ctx->id("FDRE")) { + ff->params[ctx->id("MODE")] = "FF_SYNC"; + ff->params[ctx->id("INIT")] = "0"; + replace_port(orig, ctx->id("D"), ff, ctx->id("D")); + replace_port(orig, ctx->id("C"), ff, ctx->id("CLK")); + replace_port(orig, ctx->id("CE"), ff, ctx->id("CE")); + replace_port(orig, ctx->id("R"), ff, ctx->id("SR")); + replace_port(orig, ctx->id("Q"), ff, ctx->id("Q")); + } else { + NPNR_ASSERT(false); + } +} + +void convert_lut(Context *ctx, CellInfo *orig, CellInfo *lut, std::vector> &created_cells, + std::unordered_set &todelete_cells) +{ + auto &orig_init = orig->params[ctx->id("INIT")]; + std::string new_init = "0000000000000000000000000000000000000000000000000000000000000000"; + int nbits = 0; + if (orig->type == ctx->id("LUT1")) { + nbits = 2; + replace_port(orig, ctx->id("I0"), lut, ctx->id("I1")); + replace_port(orig, ctx->id("O"), lut, ctx->id("O6")); + } else if (orig->type == ctx->id("LUT2")) { + nbits = 4; + replace_port(orig, ctx->id("I0"), lut, ctx->id("I1")); + replace_port(orig, ctx->id("I1"), lut, ctx->id("I2")); + replace_port(orig, ctx->id("O"), lut, ctx->id("O6")); + } else if (orig->type == ctx->id("LUT3")) { + nbits = 8; + replace_port(orig, ctx->id("I0"), lut, ctx->id("I1")); + replace_port(orig, ctx->id("I1"), lut, ctx->id("I2")); + replace_port(orig, ctx->id("I2"), lut, ctx->id("I3")); + replace_port(orig, ctx->id("O"), lut, ctx->id("O6")); + } else if (orig->type == ctx->id("LUT4")) { + nbits = 16; + replace_port(orig, ctx->id("I0"), lut, ctx->id("I1")); + replace_port(orig, ctx->id("I1"), lut, ctx->id("I2")); + replace_port(orig, ctx->id("I2"), lut, ctx->id("I3")); + replace_port(orig, ctx->id("I3"), lut, ctx->id("I4")); + replace_port(orig, ctx->id("O"), lut, ctx->id("O6")); + } else if (orig->type == ctx->id("LUT5")) { + nbits = 32; + replace_port(orig, ctx->id("I0"), lut, ctx->id("I1")); + replace_port(orig, ctx->id("I1"), lut, ctx->id("I2")); + replace_port(orig, ctx->id("I2"), lut, ctx->id("I3")); + replace_port(orig, ctx->id("I3"), lut, ctx->id("I4")); + replace_port(orig, ctx->id("I4"), lut, ctx->id("I5")); + replace_port(orig, ctx->id("O"), lut, ctx->id("O6")); + } else if (orig->type == ctx->id("LUT6")) { + new_init = orig_init; + replace_port(orig, ctx->id("I0"), lut, ctx->id("I1")); + replace_port(orig, ctx->id("I1"), lut, ctx->id("I2")); + replace_port(orig, ctx->id("I2"), lut, ctx->id("I3")); + replace_port(orig, ctx->id("I3"), lut, ctx->id("I4")); + replace_port(orig, ctx->id("I4"), lut, ctx->id("I5")); + replace_port(orig, ctx->id("I5"), lut, ctx->id("I6")); + replace_port(orig, ctx->id("O"), lut, ctx->id("O6")); + } else { + NPNR_ASSERT(false); + } + if (nbits) { + unsigned long init = std::stoul(orig_init); + for (int i = 0; i < 64; i++) { + int obit = i % nbits; + new_init[63-i] = '0' + (init >> obit & 1); + } + } + lut->params[ctx->id("INIT")] = new_init; +} + +void insert_ilogic_pass(Context *ctx, CellInfo *iob, CellInfo *ilogic) +{ + replace_port(iob, ctx->id("I"), ilogic, ctx->id("FABRICOUT")); + connect_ports(ctx, iob, ctx->id("I"), ilogic, ctx->id("D")); + ilogic->constr_parent = iob; + iob->constr_children.push_back(ilogic); + // XXX enum + ilogic->constr_spec = 1; +} + +void insert_ologic_pass(Context *ctx, CellInfo *iob, CellInfo *ologic) +{ + replace_port(iob, ctx->id("O"), ologic, ctx->id("D1")); + connect_ports(ctx, ologic, ctx->id("OQ"), iob, ctx->id("O")); + NetInfo *net_t = iob->ports.at(ctx->id("T")).net; + if (net_t != nullptr) { + replace_port(iob, ctx->id("T"), ologic, ctx->id("T1")); + connect_ports(ctx, ologic, ctx->id("TQ"), iob, ctx->id("T")); + } + ologic->constr_parent = iob; + iob->constr_children.push_back(ologic); + // XXX enum + ologic->constr_spec = 2; +} + +NEXTPNR_NAMESPACE_END diff --git a/leuctra/cells.h b/leuctra/cells.h index d2e1bc08..b22ee7fc 100644 --- a/leuctra/cells.h +++ b/leuctra/cells.h @@ -24,6 +24,10 @@ NEXTPNR_NAMESPACE_BEGIN +// Create a standard cell and return it +// Name will be automatically assigned if not specified +std::unique_ptr create_leuctra_cell(Context *ctx, IdString type, std::string name = ""); + inline bool is_xilinx_iobuf(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("IBUF") || cell->type == ctx->id("IBUFDS") @@ -36,6 +40,30 @@ inline bool is_xilinx_iobuf(const BaseCtx *ctx, const CellInfo *cell) { || cell->type == ctx->id("IOBUFDS"); } +inline bool is_xilinx_ff(const BaseCtx *ctx, const CellInfo *cell) { + return cell->type == ctx->id("FDRE"); +} + +inline bool is_xilinx_lut(const BaseCtx *ctx, const CellInfo *cell) { + return cell->type == ctx->id("LUT1") + || cell->type == ctx->id("LUT2") + || cell->type == ctx->id("LUT3") + || cell->type == ctx->id("LUT4") + || cell->type == ctx->id("LUT5") + || cell->type == ctx->id("LUT6"); +} + +// Convert a nextpnr IO buffer to an IOB +void nxio_to_iob(Context *ctx, CellInfo *nxio, CellInfo *iob, std::vector> &created_cells, + std::unordered_set &todelete_cells); +void convert_ff(Context *ctx, CellInfo *orig, CellInfo *ff, std::vector> &created_cells, + std::unordered_set &todelete_cells); +void convert_lut(Context *ctx, CellInfo *orig, CellInfo *lc, std::vector> &created_cells, + std::unordered_set &todelete_cells); + +void insert_ilogic_pass(Context *ctx, CellInfo *iob, CellInfo *ilogic); +void insert_ologic_pass(Context *ctx, CellInfo *iob, CellInfo *ologic); + NEXTPNR_NAMESPACE_END #endif diff --git a/leuctra/main.cc b/leuctra/main.cc index 79ebc002..0036d8c1 100644 --- a/leuctra/main.cc +++ b/leuctra/main.cc @@ -24,6 +24,8 @@ #include "design_utils.h" #include "log.h" #include "timing.h" +#include "util.h" +#include "textcfg.h" USING_NEXTPNR_NAMESPACE @@ -34,6 +36,7 @@ class LeuctraCommandHandler : public CommandHandler virtual ~LeuctraCommandHandler(){}; std::unique_ptr createContext() override; void setupArchContext(Context *ctx) override{}; + void customAfterLoad(Context *ctx) override; void customBitstream(Context *ctx) override; protected: @@ -48,10 +51,19 @@ po::options_description LeuctraCommandHandler::getArchOptions() specific.add_options()("device", po::value(), "select device"); specific.add_options()("package", po::value(), "select device package"); specific.add_options()("speed", po::value(), "select device speedgrade"); + specific.add_options()("ucf", po::value>(), "UCF pin constraint file(s)"); + specific.add_options()("ucf-allow-unconstrained", "don't require UCF file(s) to constrain all IO"); + specific.add_options()("textcfg", po::value(), "textual configuration in Leuctra format to write"); return specific; } -void LeuctraCommandHandler::customBitstream(Context *ctx) { log_error("Here is when bitstream gets created"); } +void LeuctraCommandHandler::customBitstream(Context *ctx) { + if (vm.count("textcfg")) { + std::string filename = vm["textcfg"].as(); + std::ofstream f(filename); + write_textcfg(ctx, f); + } +} std::unique_ptr LeuctraCommandHandler::createContext() { @@ -66,6 +78,36 @@ std::unique_ptr LeuctraCommandHandler::createContext() return std::unique_ptr(new Context(chipArgs)); } +void LeuctraCommandHandler::customAfterLoad(Context *ctx) +{ + if (vm.count("ucf")) { + std::vector files = vm["ucf"].as>(); + for (const auto &filename : files) { + std::ifstream in(filename); + if (!in) + log_error("failed to open UCF file '%s'\n", filename.c_str()); + if (!ctx->applyUCF(filename, in)) + log_error("failed to parse UCF file '%s'\n", filename.c_str()); + } + + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_obuf") || + ci->type == ctx->id("$nextpnr_iobuf")) { + if (!ci->attrs.count(ctx->id("LOC"))) { + if (vm.count("ucf-allow-unconstrained")) + log_warning("IO '%s' is unconstrained in UCF and will be automatically placed\n", + cell.first.c_str(ctx)); + else + log_error("IO '%s' is unconstrained in UCF (override this error with " + "--ucf-allow-unconstrained)\n", + cell.first.c_str(ctx)); + } + } + } + } +} + int main(int argc, char *argv[]) { LeuctraCommandHandler handler(argc, argv); diff --git a/leuctra/pack.cc b/leuctra/pack.cc index d3241a4c..f6080248 100644 --- a/leuctra/pack.cc +++ b/leuctra/pack.cc @@ -18,7 +18,9 @@ */ #include "nextpnr.h" +#include "cells.h" #include "log.h" +#include "util.h" NEXTPNR_NAMESPACE_BEGIN @@ -28,6 +30,11 @@ static bool is_nextpnr_iob(Context *ctx, CellInfo *cell) cell->type == ctx->id("$nextpnr_iobuf"); } +static bool is_iob(Context *ctx, CellInfo *cell) +{ + return cell->type == ctx->id("IOB"); +} + class LeuctraPacker { public: @@ -50,17 +57,207 @@ class LeuctraPacker // Remove nextpnr iob cells, insert Xilinx primitives instead. void pack_iob() { - log_info("Packing IOBs..\n"); + log_info("Packing IOBs...\n"); - // XXX + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (is_nextpnr_iob(ctx, ci)) { + CellInfo *iob = nullptr; + std::unique_ptr io_cell = + create_leuctra_cell(ctx, ctx->id("IOB"), ci->name.str(ctx) + "$iob"); + nxio_to_iob(ctx, ci, io_cell.get(), new_cells, packed_cells); + new_cells.push_back(std::move(io_cell)); + iob = new_cells.back().get(); + + packed_cells.insert(ci->name); + if (iob != nullptr) { + for (const auto &attr : ci->attrs) + iob->attrs[attr.first] = attr.second; + + auto loc_attr = iob->attrs.find(ctx->id("LOC")); + if (loc_attr != iob->attrs.end()) { + std::string pin = loc_attr->second; + BelId pinBel = ctx->getPackagePinBel(pin); + if (pinBel == BelId()) { + log_error("IO pin '%s' constrained to pin '%s', which does not exist for package '%s'.\n", + iob->name.c_str(ctx), pin.c_str(), ctx->args.package.c_str()); + } else { + log_info("pin '%s' constrained to Bel '%s'.\n", iob->name.c_str(ctx), + ctx->getBelName(pinBel).c_str(ctx)); + } + iob->attrs[ctx->id("BEL")] = ctx->getBelName(pinBel).str(ctx); + } + } + } + } flush_cells(); } + // Ensure ilogic/ologic cell for every IOB that needs one. + void pack_iologic() + { + log_info("Packing ILOGICs/OLOGICs...\n"); + + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (is_iob(ctx, ci)) { + NetInfo *net_i = ci->ports.at(ctx->id("I")).net; + if (net_i != nullptr) { + // Insert ILOGIC. + std::unique_ptr ilogic = + create_leuctra_cell(ctx, ctx->id("ILOGIC2"), ci->name.str(ctx) + "$ilogic"); + insert_ilogic_pass(ctx, ci, ilogic.get()); + + new_cells.push_back(std::move(ilogic)); + } + NetInfo *net_o = ci->ports.at(ctx->id("O")).net; + if (net_o != nullptr) { + // Insert OLOGIC. + std::unique_ptr ologic = + create_leuctra_cell(ctx, ctx->id("OLOGIC2"), ci->name.str(ctx) + "$ologic"); + insert_ologic_pass(ctx, ci, ologic.get()); + + new_cells.push_back(std::move(ologic)); + } + auto bel_attr = ci->attrs.find(ctx->id("BEL")); + if (bel_attr != ci->attrs.end()) { + BelId bel = ctx->getBelByName(ctx->id(ci->attrs[ctx->id("BEL")])); + for (auto &child : ci->constr_children) { + BelId child_bel = ctx->getRelatedBel(bel, child->constr_spec); + child->attrs[ctx->id("BEL")] = ctx->getBelName(child_bel).str(ctx); + child->constr_parent = nullptr; + child->constr_spec = -1; + + } + ci->constr_children.clear(); + } + } + } + + flush_cells(); + } + + // Convert FFs/latches to LEUCTRA_FFs. + void pack_ff() + { + log_info("Packing FFs...\n"); + + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (is_xilinx_ff(ctx, ci)) { + std::unique_ptr ff_cell = + create_leuctra_cell(ctx, ctx->id("LEUCTRA_FF"), ci->name.str(ctx) + "$ff"); + convert_ff(ctx, ci, ff_cell.get(), new_cells, packed_cells); + new_cells.push_back(std::move(ff_cell)); + + packed_cells.insert(ci->name); + } + } + + flush_cells(); + } + + // Convert FFs/latches to LEUCTRA_FFs. + void pack_lut() + { + log_info("Packing LUTs...\n"); + + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (is_xilinx_lut(ctx, ci)) { + std::unique_ptr lut_cell = + create_leuctra_cell(ctx, ctx->id("LEUCTRA_LC"), ci->name.str(ctx) + "$lc"); + convert_lut(ctx, ci, lut_cell.get(), new_cells, packed_cells); + new_cells.push_back(std::move(lut_cell)); + + packed_cells.insert(ci->name); + } + } + + flush_cells(); + } + + // Merge a net into a constant net + void set_net_constant(const Context *ctx, NetInfo *orig, NetInfo *constnet, bool constval) + { + orig->driver.cell = nullptr; + for (auto user : orig->users) { + if (user.cell != nullptr) { + CellInfo *uc = user.cell; + if (ctx->verbose) + log_info("%s user %s\n", orig->name.c_str(ctx), uc->name.c_str(ctx)); + uc->ports[user.port].net = constnet; + constnet->users.push_back(user); + } + } + orig->users.clear(); + } + + // Pack constants (simple implementation) + void pack_constants() + { + log_info("Packing constants..\n"); + + std::unique_ptr gnd_cell = create_leuctra_cell(ctx, ctx->id("LEUCTRA_LC"), "$PACKER_GND"); + gnd_cell->params[ctx->id("INIT")] = "0000000000000000000000000000000000000000000000000000000000000000"; + std::unique_ptr gnd_net = std::unique_ptr(new NetInfo); + gnd_net->name = ctx->id("$PACKER_GND_NET"); + gnd_net->driver.cell = gnd_cell.get(); + gnd_net->driver.port = ctx->id("O6"); + gnd_cell->ports.at(ctx->id("O6")).net = gnd_net.get(); + + std::unique_ptr vcc_cell = create_leuctra_cell(ctx, ctx->id("LEUCTRA_LC"), "$PACKER_VCC"); + vcc_cell->params[ctx->id("INIT")] = "1111111111111111111111111111111111111111111111111111111111111111"; + std::unique_ptr vcc_net = std::unique_ptr(new NetInfo); + vcc_net->name = ctx->id("$PACKER_VCC_NET"); + vcc_net->driver.cell = vcc_cell.get(); + vcc_net->driver.port = ctx->id("O6"); + vcc_cell->ports.at(ctx->id("O6")).net = vcc_net.get(); + + std::vector dead_nets; + + bool gnd_used = false, vcc_used = false; + + for (auto net : sorted(ctx->nets)) { + NetInfo *ni = net.second; + if (ni->driver.cell != nullptr && ni->driver.cell->type == ctx->id("GND")) { + IdString drv_cell = ni->driver.cell->name; + set_net_constant(ctx, ni, gnd_net.get(), false); + gnd_used = true; + dead_nets.push_back(net.first); + ctx->cells.erase(drv_cell); + } else if (ni->driver.cell != nullptr && ni->driver.cell->type == ctx->id("VCC")) { + IdString drv_cell = ni->driver.cell->name; + set_net_constant(ctx, ni, vcc_net.get(), true); + vcc_used = true; + dead_nets.push_back(net.first); + ctx->cells.erase(drv_cell); + } + } + + if (gnd_used) { + ctx->cells[gnd_cell->name] = std::move(gnd_cell); + ctx->nets[gnd_net->name] = std::move(gnd_net); + } + if (vcc_used) { + ctx->cells[vcc_cell->name] = std::move(vcc_cell); + ctx->nets[vcc_net->name] = std::move(vcc_net); + } + + for (auto dn : dead_nets) { + ctx->nets.erase(dn); + } + } + public: void pack() { pack_iob(); + pack_iologic(); + pack_ff(); + pack_lut(); + pack_constants(); } private: diff --git a/leuctra/textcfg.cc b/leuctra/textcfg.cc new file mode 100644 index 00000000..3503513d --- /dev/null +++ b/leuctra/textcfg.cc @@ -0,0 +1,59 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Clifford Wolf + * Copyright (C) 2018 David Shah + * Copyright (C) 2018 Serge Bazanski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ +#include "nextpnr.h" +#include "textcfg.h" + +NEXTPNR_NAMESPACE_BEGIN + +void write_textcfg(const Context *ctx, std::ostream &out) +{ + out << "DEVICE " << ctx->args.device << " " << ctx->args.package << " " << ctx->args.speed << std::endl; + + for (auto &cell : ctx->cells) { + auto &belid = cell.second->bel; + auto &bel = ctx->getTileTypeBel(belid); + auto name = IdString(bel.name_id); + out << "PRIM " << belid.location.x << " " << belid.location.y << " " << name.str(ctx) << " " << cell.second->name.str(ctx) << std::endl; + for (auto &attr : cell.second->attrs) { + out << "OPT " << attr.first.str(ctx) << " " << attr.second << std::endl; + } + for (auto ¶m : cell.second->params) { + out << "OPT " << param.first.str(ctx) << " " << param.second << std::endl; + } + } + for (auto &net : ctx->nets) { + out << "NET " << net.second->name.str(ctx) << std::endl; + out << "FROM " << net.second->driver.cell->name.str(ctx) << " " << net.second->driver.port.str(ctx) << std::endl; + for (auto &user : net.second->users) { + out << "TO " << user.cell->name.str(ctx) << " " << user.port.str(ctx) << std::endl; + } + for (auto &wire : net.second->wires) { + auto &pip = wire.second.pip; + if (pip != PipId() && pip.kind == PIP_KIND_PIP) { + WireId dst = ctx->getPipDstWire(pip); + WireId src = ctx->getPipSrcWire(pip); + out << "PIP " << pip.location.x << " " << pip.location.y << " " << ctx->getWireBasename(dst).str(ctx) << " " << ctx->getWireBasename(src).str(ctx) << std::endl; + } + } + } +} + +NEXTPNR_NAMESPACE_END diff --git a/leuctra/textcfg.h b/leuctra/textcfg.h new file mode 100644 index 00000000..739ce8a1 --- /dev/null +++ b/leuctra/textcfg.h @@ -0,0 +1,32 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 David Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef TEXTCFG_H +#define TEXTCFG_H + +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +void write_textcfg(const Context *ctx, std::ostream &out); + +NEXTPNR_NAMESPACE_END + +#endif // TEXTCFG_H + diff --git a/leuctra/ucf.cc b/leuctra/ucf.cc new file mode 100644 index 00000000..735b4a0c --- /dev/null +++ b/leuctra/ucf.cc @@ -0,0 +1,143 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 David Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include +#include +#include +#include "log.h" + +NEXTPNR_NAMESPACE_BEGIN + +bool Arch::applyUCF(std::string filename, std::istream &in) +{ + auto isempty = [](const std::string &str) { + return std::all_of(str.begin(), str.end(), [](char c) { return isblank(c) || c == '\r' || c == '\n'; }); + }; + auto strip_quotes = [](const std::string &str) { + if (str.at(0) == '"') { + NPNR_ASSERT(str.back() == '"'); + return str.substr(1, str.size() - 2); + } else { + return str; + } + }; + + try { + if (!in) + log_error("failed to open UCF file\n"); + std::string line; + std::string linebuf; + int lineno = 0; + while (std::getline(in, line)) { + ++lineno; + size_t cstart = line.find('#'); + if (cstart != std::string::npos) + line = line.substr(0, cstart); + if (isempty(line)) + continue; + linebuf += line; + // Look for a command up to a semicolon + size_t scpos = linebuf.find(';'); + while (scpos != std::string::npos) { + std::string command = linebuf.substr(0, scpos); + // Split command into words + std::stringstream ss(command); + std::vector words; + std::string tmp; + while (ss >> tmp) + words.push_back(tmp); + if (words.size() >= 0) { + std::string verb = words.at(0); + if (verb == "CONFIG") { + log_warning(" ignoring unsupported LPF command '%s' (on line %d)\n", command.c_str(), + lineno); + } else if (verb == "NET") { + if (words.size() < 2) + log_error("expected name after NET (on line %d)\n", lineno); + std::string target = strip_quotes(words.at(1)); + int pos = 2; + while (pos < words.size()) { + std::string attr = words.at(pos); + pos++; + if (attr == "LOC" || attr == "IOSTANDARD" || attr == "DRIVE" || attr == "SLEW") { + if (pos + 2 > words.size() || words.at(pos) != "=") + log_error("expected %s = value (on line %d)\n", attr.c_str(), lineno); + std::string value = strip_quotes(words.at(pos + 1)); + pos += 2; + auto fnd_cell = cells.find(id(target)); + if (fnd_cell != cells.end()) { + fnd_cell->second->attrs[id(attr)] = value; + } + } else if (attr == "PULLUP" || attr == "PULLDOWN" || attr == "KEEPER") { + auto fnd_cell = cells.find(id(target)); + if (fnd_cell != cells.end()) { + fnd_cell->second->attrs[id("PULLTYPE")] = attr; + } + } else if (attr == "PERIOD") { + if (pos + 2 > words.size() || words.at(pos) != "=") + log_error("expected PERIOD = value (on line %d)\n", lineno); + std::string value = words.at(pos + 1); + pos += 2; + int upos = 0; + while (upos < value.size() && (std::isdigit(value[upos]) || value[upos] == '.')) + upos++; + float freq = std::stof(value.substr(0, upos)); + std::string unit = value.substr(upos); + boost::algorithm::to_upper(unit); + if (unit == "MHZ") + ; + else if (unit == "KHZ") + freq /= 1.0e3; + else if (unit == "HZ") + freq /= 1.0e6; + else + log_error("unsupported frequency unit '%s' (on line %d)\n", unit.c_str(), lineno); + addClock(id(target), freq); + } else { + log_warning(" ignoring unsupported NET attribute '%s' (on line %d)\n", attr.c_str(), + lineno); + } + if (pos < words.size()) { + std::string cur = words.at(pos); + if (cur != "|") + log_error("expected | before %s (on line %d)\n", cur.c_str(), lineno); + pos++; + } + } + } else { + log_warning(" ignoring unsupported UCF command '%s' (on line %d)\n", verb.c_str(), + lineno); + } + } + + linebuf = linebuf.substr(scpos + 1); + scpos = linebuf.find(';'); + } + } + if (!isempty(linebuf)) + log_error("unexpected end of UCF file\n"); + settings.emplace(id("input/ucf"), filename); + return true; + } catch (log_execution_error_exception) { + return false; + } +} + +NEXTPNR_NAMESPACE_END +