From 6cac19c055600ad4827d12d8fba1e441b06851e8 Mon Sep 17 00:00:00 2001 From: YRabbit Date: Fri, 14 Jul 2023 18:57:20 +1000 Subject: [PATCH] gowin: Himbaechel. Add constraint file processing. - minor fixes for pinout saving; - CST parser taken from generic-based apicula; - $nextpnr IOB detachment is copied here because it is necessary to copy attributes from deleted bels. Signed-off-by: YRabbit --- himbaechel/himbaechel_dbgen/chip.py | 17 +- himbaechel/uarch/gowin/cst.cc | 191 +++++++++++++++++++++++ himbaechel/uarch/gowin/cst.h | 13 ++ himbaechel/uarch/gowin/gowin.cc | 61 +++++++- himbaechel/uarch/gowin/gowin_arch_gen.py | 4 +- himbaechel/uarch/gowin/pack.cc | 32 +++- 6 files changed, 307 insertions(+), 11 deletions(-) create mode 100644 himbaechel/uarch/gowin/cst.cc create mode 100644 himbaechel/uarch/gowin/cst.h diff --git a/himbaechel/himbaechel_dbgen/chip.py b/himbaechel/himbaechel_dbgen/chip.py index 4f16b831..857935df 100644 --- a/himbaechel/himbaechel_dbgen/chip.py +++ b/himbaechel/himbaechel_dbgen/chip.py @@ -388,7 +388,10 @@ class PadInfo(BBAStruct): extra_data: object = None def serialise_lists(self, context: str, bba: BBAWriter): - pass + if self.extra_data is not None: + self.extra_data.serialise_lists(f"{context}_extra_data", bba) + bba.label(f"{context}_extra_data") + self.extra_data.serialise(f"{context}_extra_data", bba) def serialise(self, context: str, bba: BBAWriter): bba.u32(self.package_pin.index) bba.u32(self.tile.index) @@ -414,8 +417,11 @@ class PackageInfo(BBAStruct): return pad def serialise_lists(self, context: str, bba: BBAWriter): - for i, pad in enumerate(self.pad): - bel.serialise_lists(f"{context}_pad{i}", pad) + for i, pad in enumerate(self.pads): + pad.serialise_lists(f"{context}_pad{i}", bba) + bba.label(f"{context}_pads") + for i, pad in enumerate(self.pads): + pad.serialise(f"{context}_pad{i}", bba) def serialise(self, context: str, bba: BBAWriter): bba.u32(self.name.index) @@ -515,6 +521,8 @@ class Chip: shp.serialise_lists(f"nshp{i}", bba) for i, tsh in enumerate(self.tile_shapes): tsh.serialise_lists(f"tshp{i}", bba) + for i, pkg in enumerate(self.packages): + pkg.serialise_lists(f"pkg{i}", bba) for y, row in enumerate(self.tiles): for x, tinst in enumerate(row): tinst.serialise_lists(f"tinst_{x}_{y}", bba) @@ -530,6 +538,9 @@ class Chip: bba.label(f"tile_shapes") for i, tsh in enumerate(self.tile_shapes): tsh.serialise(f"tshp{i}", bba) + bba.label(f"packages") + for i, pkg in enumerate(self.packages): + pkg.serialise(f"pkg{i}", bba) bba.label(f"tile_insts") for y, row in enumerate(self.tiles): for x, tinst in enumerate(row): diff --git a/himbaechel/uarch/gowin/cst.cc b/himbaechel/uarch/gowin/cst.cc new file mode 100644 index 00000000..5b9ddaaa --- /dev/null +++ b/himbaechel/uarch/gowin/cst.cc @@ -0,0 +1,191 @@ +#include +#include +#include + +#include "log.h" +#include "nextpnr.h" +#include "util.h" + +#define HIMBAECHEL_CONSTIDS "uarch/gowin/constids.inc" +#include "himbaechel_constids.h" +#include "himbaechel_helpers.h" + +#include "cst.h" +#include "gowin.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct GowinCstReader +{ + Context *ctx; + std::istream ∈ + + GowinCstReader(Context *ctx, std::istream &in) : ctx(ctx), in(in){}; + + const PadInfoPOD *pinLookup(const PadInfoPOD *list, const size_t len, const IdString idx) + { + for (size_t i = 0; i < len; i++) { + const PadInfoPOD *pin = &list[i]; + if (IdString(pin->package_pin) == idx) { + return pin; + } + } + return nullptr; + } + + Loc getLoc(std::smatch match, int maxX, int maxY) + { + int col = std::stoi(match[2]); + int row = 1; // Top + std::string side = match[1].str(); + if (side == "R") { + row = col; + col = maxX; + } else if (side == "B") { + row = maxY; + } else if (side == "L") { + row = col; + col = 1; + } + int z = match[3].str()[0] - 'A'; + return Loc(col - 1, row - 1, z); + } + + bool run(void) + { + pool> constrained_cells; + auto debug_cell = [this, &constrained_cells](IdString cellId, IdStringList belId) { + if (ctx->debug) { + constrained_cells.insert(std::make_pair(cellId, belId)); + } + }; + + log_info("Reading constraints...\n"); + try { + // If two locations are specified separated by commas (for differential I/O buffers), + // only the first location is actually recognized and used. + // And pin A will be Positive and pin B will be Negative in any case. + std::regex iobre = std::regex("IO_LOC +\"([^\"]+)\" +([^ ,;]+)(, *[^ ;]+)? *;.*[\\s\\S]*"); + std::regex portre = std::regex("IO_PORT +\"([^\"]+)\" +([^;]+;).*[\\s\\S]*"); + std::regex port_attrre = std::regex("([^ =;]+=[^ =;]+) *([^;]*;)"); + std::regex iobelre = std::regex("IO([TRBL])([0-9]+)\\[?([A-Z])\\]?"); + std::regex inslocre = + std::regex("INS_LOC +\"([^\"]+)\" +R([0-9]+)C([0-9]+)\\[([0-9])\\]\\[([AB])\\] *;.*[\\s\\S]*"); + std::regex clockre = std::regex("CLOCK_LOC +\"([^\"]+)\" +BUF([GS])(\\[([0-7])\\])?[^;]*;.*[\\s\\S]*"); + std::smatch match, match_attr, match_pinloc; + std::string line, pinline; + enum + { + ioloc, + ioport, + insloc, + clock + } cst_type; + + while (!in.eof()) { + std::getline(in, line); + cst_type = ioloc; + if (!std::regex_match(line, match, iobre)) { + if (std::regex_match(line, match, portre)) { + cst_type = ioport; + } else { + if (std::regex_match(line, match, clockre)) { + cst_type = clock; + } else { + if (std::regex_match(line, match, inslocre)) { + cst_type = insloc; + } else { + if ((!line.empty()) && (line.rfind("//", 0) == std::string::npos)) { + log_warning("Invalid constraint: %s\n", line.c_str()); + } + continue; + } + } + } + } + + IdString net = ctx->id(match[1]); + auto it = ctx->cells.find(net); + if (cst_type != clock && it == ctx->cells.end()) { + log_info("Cell %s not found\n", net.c_str(ctx)); + continue; + } + switch (cst_type) { + case clock: { // CLOCK name BUFG|S=# + std::string which_clock = match[2]; + std::string lw = match[4]; + int lw_idx = -1; + if (lw.length() > 0) { + lw_idx = atoi(lw.c_str()); + log_info("lw_idx:%d\n", lw_idx); + } + if (which_clock.at(0) == 'S') { + auto ni = ctx->nets.find(net); + if (ni == ctx->nets.end()) { + log_info("Net %s not found\n", net.c_str(ctx)); + continue; + } + // if (!allocate_longwire(ni->second.get(), lw_idx)) { + log_info("Can't use the long wires. The %s network will use normal routing.\n", net.c_str(ctx)); + //} + } else { + log_info("BUFG isn't supported\n"); + continue; + } + } break; + case ioloc: { // IO_LOC name pin + IdString pinname = ctx->id(match[2]); + pinline = match[2]; + + const PadInfoPOD *belname = + pinLookup(ctx->package_info->pads.get(), ctx->package_info->pads.ssize(), pinname); + if (belname != nullptr) { + IdStringList bel = IdStringList::concat(IdString(belname->tile), IdString(belname->bel)); + it->second->setAttr(IdString(ID_BEL), bel.str(ctx)); + debug_cell(it->second->name, bel); + } else { + if (std::regex_match(pinline, match_pinloc, iobelre)) { + // may be it's IOx#[AB] style? + Loc loc = getLoc(match_pinloc, ctx->getGridDimX(), ctx->getGridDimY()); + BelId bel = ctx->getBelByLocation(loc); + if (bel == BelId()) { + log_error("Pin %s not found (TRBL style). \n", pinline.c_str()); + } + it->second->setAttr(IdString(ID_BEL), std::string(ctx->nameOfBel(bel))); + debug_cell(it->second->name, ctx->getBelName(bel)); + } else { + log_error("Pin %s not found (pin# style)\n", pinname.c_str(ctx)); + } + } + } break; + default: { // IO_PORT attr=value + std::string attr_val = match[2]; + while (std::regex_match(attr_val, match_attr, port_attrre)) { + std::string attr = "&"; + attr += match_attr[1]; + boost::algorithm::to_upper(attr); + it->second->setAttr(ctx->id(attr), 1); + attr_val = match_attr[2]; + } + } + } + } + if (ctx->debug) { + for (auto &cell : constrained_cells) { + log_info("Cell %s is constrained to %s\n", cell.first.c_str(ctx), cell.second.str(ctx).c_str()); + } + } + return true; + } catch (log_execution_error_exception) { + return false; + } + } +}; + +bool gowin_apply_constraints(Context *ctx, std::istream &in) +{ + GowinCstReader reader(ctx, in); + return reader.run(); +} + +NEXTPNR_NAMESPACE_END diff --git a/himbaechel/uarch/gowin/cst.h b/himbaechel/uarch/gowin/cst.h new file mode 100644 index 00000000..255e57b0 --- /dev/null +++ b/himbaechel/uarch/gowin/cst.h @@ -0,0 +1,13 @@ +#ifndef GOWIN_CST_H +#define GOWIN_CST_H + +#include +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +bool gowin_apply_constraints(Context *ctx, std::istream &in); + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/himbaechel/uarch/gowin/gowin.cc b/himbaechel/uarch/gowin/gowin.cc index 082c0980..416ccae2 100644 --- a/himbaechel/uarch/gowin/gowin.cc +++ b/himbaechel/uarch/gowin/gowin.cc @@ -1,3 +1,5 @@ +#include + #include "himbaechel_api.h" #include "himbaechel_helpers.h" #include "log.h" @@ -7,6 +9,7 @@ #define HIMBAECHEL_CONSTIDS "uarch/gowin/constids.inc" #include "himbaechel_constids.h" +#include "cst.h" #include "globals.h" #include "gowin.h" #include "pack.h" @@ -76,8 +79,6 @@ void GowinImpl::init(Context *ctx) if (!args.options.count("partno")) { log_error("Partnumber (like --vopt partno=GW1NR-LV9QN88PC6/I5) must be specified.\n"); } - ctx->settings[ctx->id("packer.partno")] = args.options.at("partno"); - // GW1N-9C.xxx -> GW1N-9C std::string chipdb = args.chipdb; auto dot_pos = chipdb.find("."); @@ -85,10 +86,60 @@ void GowinImpl::init(Context *ctx) chipdb.resize(dot_pos); } chip = ctx->id(chipdb); - partno = ctx->id(args.options.at("partno")); + std::string pn = args.options.at("partno"); + partno = ctx->id(pn); + ctx->settings[ctx->id("packer.partno")] = pn; + + std::regex speedre = std::regex("(.*)(C[0-9]/I[0-9])$"); + std::smatch match; + + IdString spd; + IdString package_idx; + if (std::regex_match(pn, match, speedre)) { + package_idx = ctx->id(match[1]); + spd = ctx->id(match[2]); + } else { + if (pn.length() > 2 && pn.compare(pn.length() - 2, 2, "ES")) { + package_idx = ctx->id(pn.substr(pn.length() - 2)); + spd = ctx->id("ES"); + } + } + + if (ctx->debug) { + log_info("packages:%d\n", ctx->chip_info->packages.ssize()); + } + for (int i = 0; i < ctx->chip_info->packages.ssize(); ++i) { + if (IdString(ctx->chip_info->packages[i].name) == package_idx) { + if (ctx->debug) { + log_info("i:%d %s\n", i, package_idx.c_str(ctx)); + } + ctx->package_info = &ctx->chip_info->packages[i]; + break; + } + } + if (ctx->package_info == nullptr) { + log_error("No package for partnumber %s\n", partno.c_str(ctx)); + } + + if (args.options.count("cst")) { + ctx->settings[ctx->id("cst.filename")] = args.options.at("cst"); + } } -void GowinImpl::pack() { gowin_pack(ctx); } +void GowinImpl::pack() +{ + if (ctx->settings.count(ctx->id("cst.filename"))) { + std::string filename = ctx->settings[ctx->id("cst.filename")].as_string(); + std::ifstream in(filename); + if (!in) { + log_error("failed to open CST file '%s'\n", filename.c_str()); + } + if (!gowin_apply_constraints(ctx, in)) { + log_error("failed to parse CST file '%s'\n", filename.c_str()); + } + } + gowin_pack(ctx); +} void GowinImpl::prePlace() { assign_cell_info(); } void GowinImpl::postPlace() { @@ -99,7 +150,7 @@ void GowinImpl::postPlace() IdStringList bel = ctx->getBelName(ci->bel); log_info("%s -> %s\n", ctx->nameOf(ci), bel.str(ctx).c_str()); } - log_info("======================================================\n"); + log_break(); } } diff --git a/himbaechel/uarch/gowin/gowin_arch_gen.py b/himbaechel/uarch/gowin/gowin_arch_gen.py index 191268ce..3fe8137c 100644 --- a/himbaechel/uarch/gowin/gowin_arch_gen.py +++ b/himbaechel/uarch/gowin/gowin_arch_gen.py @@ -353,8 +353,8 @@ def create_packages(chip: Chip, db: chipdb): partno = partno_spd.removesuffix(spd) # drop SPEED like 'C7/I6' if partno in created_pkgs: continue + created_pkgs.add(partno) pkg = chip.create_package(partno) - print(partno) for pinno, pininfo in db.pinout[variant][pkgname].items(): io_loc, cfgs = pininfo tile, bel = ioloc_to_tile_bel(io_loc) @@ -391,7 +391,7 @@ def main(): # these differences (in case it turns out later that there is a slightly # different routing or something like that). logic_tiletypes = {12, 13, 14, 15, 16} - io_tiletypes = {53, 55, 58, 59, 64, 65, 66} + io_tiletypes = {52, 53, 55, 58, 59, 64, 65, 66} ssram_tiletypes = {17, 18, 19} # Setup tile grid for x in range(X): diff --git a/himbaechel/uarch/gowin/pack.cc b/himbaechel/uarch/gowin/pack.cc index 7be4966d..06bc4290 100644 --- a/himbaechel/uarch/gowin/pack.cc +++ b/himbaechel/uarch/gowin/pack.cc @@ -29,7 +29,37 @@ struct GowinPacker CellTypePort(id_IBUF, id_I), CellTypePort(id_OBUF, id_O), }; - h.remove_nextpnr_iobs(top_ports); + std::vector to_remove; + for (auto &cell : ctx->cells) { + auto &ci = *cell.second; + if (!ci.type.in(ctx->id("$nextpnr_ibuf"), ctx->id("$nextpnr_obuf"), ctx->id("$nextpnr_iobuf"))) + continue; + NetInfo *i = ci.getPort(ctx->id("I")); + if (i && i->driver.cell) { + if (!top_ports.count(CellTypePort(i->driver))) + log_error("Top-level port '%s' driven by illegal port %s.%s\n", ctx->nameOf(&ci), + ctx->nameOf(i->driver.cell), ctx->nameOf(i->driver.port)); + for (const auto &attr : ci.attrs) { + i->driver.cell->attrs[attr.first] = attr.second; + } + } + NetInfo *o = ci.getPort(ctx->id("O")); + if (o) { + for (auto &usr : o->users) { + if (!top_ports.count(CellTypePort(usr))) + log_error("Top-level port '%s' driving illegal port %s.%s\n", ctx->nameOf(&ci), + ctx->nameOf(usr.cell), ctx->nameOf(usr.port)); + for (const auto &attr : ci.attrs) { + usr.cell->attrs[attr.first] = attr.second; + } + } + } + ci.disconnectPort(ctx->id("I")); + ci.disconnectPort(ctx->id("O")); + to_remove.push_back(ci.name); + } + for (IdString cell_name : to_remove) + ctx->cells.erase(cell_name); } // ===================================