diff --git a/generic/CMakeLists.txt b/generic/CMakeLists.txt index a4e3f367..ca2c458f 100644 --- a/generic/CMakeLists.txt +++ b/generic/CMakeLists.txt @@ -25,6 +25,8 @@ set(SOURCES viaduct/fabulous/pack.h viaduct/fabulous/validity_check.cc viaduct/fabulous/validity_check.h + viaduct/fabulous/pcf.cc + viaduct/fabulous/pcf.h ) add_nextpnr_architecture(${family} diff --git a/generic/arch.cc b/generic/arch.cc index f7a3dc03..48021029 100644 --- a/generic/arch.cc +++ b/generic/arch.cc @@ -230,6 +230,16 @@ void Arch::addCellTimingDelay(IdString cell, IdString fromPort, IdString toPort, cellTiming[cell].combDelays[CellDelayKey{fromPort, toPort}] = DelayQuad(delay); } +void Arch::addCellTimingDelayMinMax(IdString cell, IdString fromPort, IdString toPort, delay_t min_delay, + delay_t max_delay) +{ + if (get_or_default(cellTiming[cell].portClasses, fromPort, TMG_IGNORE) == TMG_IGNORE) + cellTiming[cell].portClasses[fromPort] = TMG_COMB_INPUT; + if (get_or_default(cellTiming[cell].portClasses, toPort, TMG_IGNORE) == TMG_IGNORE) + cellTiming[cell].portClasses[toPort] = TMG_COMB_OUTPUT; + cellTiming[cell].combDelays[CellDelayKey{fromPort, toPort}] = DelayQuad(min_delay, max_delay); +} + void Arch::addCellTimingSetupHold(IdString cell, IdString port, IdString clock, delay_t setup, delay_t hold) { TimingClockingInfo ci; diff --git a/generic/arch.h b/generic/arch.h index 1bf1f0b6..533c2d9a 100644 --- a/generic/arch.h +++ b/generic/arch.h @@ -226,6 +226,8 @@ struct Arch : BaseArch void addCellTimingClock(IdString cell, IdString port); void addCellTimingDelay(IdString cell, IdString fromPort, IdString toPort, delay_t delay); + void addCellTimingDelayMinMax(IdString cell, IdString fromPort, IdString toPort, delay_t min_delay, + delay_t max_delay); void addCellTimingSetupHold(IdString cell, IdString port, IdString clock, delay_t setup, delay_t hold); void addCellTimingClockToOut(IdString cell, IdString port, IdString clock, delay_t clktoq); diff --git a/generic/viaduct/fabulous/fabulous.cc b/generic/viaduct/fabulous/fabulous.cc index 07204947..28ca0737 100644 --- a/generic/viaduct/fabulous/fabulous.cc +++ b/generic/viaduct/fabulous/fabulous.cc @@ -34,6 +34,7 @@ #include "fab_defs.h" #include "fasm.h" #include "pack.h" +#include "pcf.h" #include "validity_check.h" #include @@ -50,6 +51,8 @@ struct FabulousImpl : ViaductAPI fasm_file = a.second; else if (a.first == "lut_k") cfg.clb.lut_k = std::stoi(a.second); + else if (a.first == "pcf") + pcf_file = a.second; else log_error("unrecognised fabulous option '%s'\n", a.first.c_str()); } @@ -131,7 +134,14 @@ struct FabulousImpl : ViaductAPI } } - void pack() override { fabulous_pack(ctx, cfg); } + void pack() override + { + if (!pcf_file.empty()) + fabulous_pcf(ctx, pcf_file); + else + log_info("No PCF file specified, skipping constraints application.\n"); + fabulous_pack(ctx, cfg); + } void postRoute() override { @@ -157,6 +167,8 @@ struct FabulousImpl : ViaductAPI std::string fasm_file; + std::string pcf_file; + std::unique_ptr blk_trk; std::string get_env_var(const std::string &name, const std::string &prompt = "") diff --git a/generic/viaduct/fabulous/fasm.cc b/generic/viaduct/fabulous/fasm.cc index 0b8a8283..52f9bfe1 100644 --- a/generic/viaduct/fabulous/fasm.cc +++ b/generic/viaduct/fabulous/fasm.cc @@ -204,6 +204,8 @@ struct FabFasmWriter void write_generic_cell(const CellInfo *ci) { + if (ci->bel == BelId()) + return; prefix = format_name(ctx->getBelName(ci->bel)) + "."; for (auto ¶m : ci->params) { // TODO: better parameter type auto-detection diff --git a/generic/viaduct/fabulous/pack.cc b/generic/viaduct/fabulous/pack.cc index 629b000e..239918b1 100644 --- a/generic/viaduct/fabulous/pack.cc +++ b/generic/viaduct/fabulous/pack.cc @@ -323,12 +323,61 @@ struct FabulousPacker void handle_io() { - // As per the preferred approach for new nextpnr flows, we require IO to be inserted by Yosys - // pre-place-and-route, or just manually instantiated - const pool top_ports{ - CellTypePort(id_IO_1_bidirectional_frame_config_pass, id_PAD), - }; - h.remove_nextpnr_iobs(top_ports); + // Any remaining $nextpnr_*buf cells were not constrained via PCF. + // Verify each connects to an IO cell via PAD, then remove it. + IdString ibuf = ctx->id("$nextpnr_ibuf"); + IdString obuf = ctx->id("$nextpnr_obuf"); + IdString iobuf = ctx->id("$nextpnr_iobuf"); + + std::vector to_remove; + for (auto &cell : ctx->cells) { + auto &ci = *cell.second; + if (!ci.type.in(ibuf, obuf, iobuf)) + continue; + log_info("port is unconstrained: %s This pin will be assigned randomly\n", ctx->nameOf(&ci)); + bool found_pad = false; + for (auto &port : ci.ports) { + NetInfo *net = port.second.net; + if (!net) + continue; + + for (auto &usr : net->users) { + if (usr.cell == &ci) + continue; + if (usr.port != id_PAD) + log_error("Top-level port '%s' connected to illegal port %s.%s (must be PAD)\n", + ctx->nameOf(&ci), ctx->nameOf(usr.cell), ctx->nameOf(usr.port)); + if (found_pad) + log_error("Top-level port '%s' connected to multiple PAD ports (at least %s.%s and %s.%s)\n", + ctx->nameOf(&ci), ctx->nameOf(usr.cell), ctx->nameOf(usr.port), + ctx->nameOf(net->driver.cell), ctx->nameOf(net->driver.port)); + found_pad = true; + } + + if (found_pad) + continue; + + auto &drv = net->driver; + if (drv.cell && drv.cell != &ci) { + if (drv.port != id_PAD) + log_error("Top-level port '%s' connected to illegal port %s.%s (must be PAD)\n", + ctx->nameOf(&ci), ctx->nameOf(drv.cell), ctx->nameOf(drv.port)); + if (found_pad) + log_error("Top-level port '%s' connected to multiple PAD ports (at least %s.%s and %s.%s)\n", + ctx->nameOf(&ci), ctx->nameOf(drv.cell), ctx->nameOf(drv.port), + ctx->nameOf(net->driver.cell), ctx->nameOf(net->driver.port)); + found_pad = true; + } + } + if (!found_pad) + log_error("No IO cell found connected to '%s' via PAD port. Was iopadmap run in Yosys?\n", + ctx->nameOf(&ci)); + ci.disconnectPort(id_I); + ci.disconnectPort(id_O); + to_remove.push_back(ci.name); + } + for (IdString cell_name : to_remove) + ctx->cells.erase(cell_name); } void constrain_carries() diff --git a/generic/viaduct/fabulous/pcf.cc b/generic/viaduct/fabulous/pcf.cc new file mode 100644 index 00000000..84599117 --- /dev/null +++ b/generic/viaduct/fabulous/pcf.cc @@ -0,0 +1,622 @@ +#include +#include +#include +#include + +#include "log.h" +#include "pcf.h" +#include "util.h" + +#define VIADUCT_CONSTIDS "viaduct/fabulous/constids.inc" +#include "viaduct_constids.h" + +NEXTPNR_NAMESPACE_BEGIN + +namespace { +namespace po = boost::program_options; + +// Command parser structure for PCF commands using boost::program_options +struct PCFCommand +{ + std::string name; + po::options_description desc; + po::positional_options_description pos; + std::function handler; + + PCFCommand() : desc("") {} + PCFCommand(const std::string &name, const std::string &description) : name(name), desc(description) {} +}; + +struct FABulousDesignConstraints +{ + Context *ctx; + std::string filename; + int lineno = 0; + std::map commands; + + FABulousDesignConstraints(Context *ctx, const std::string &filename) : ctx(ctx), filename(filename) + { + setup_commands(); + } + + bool parse_loc_from_string(const std::string &s, Loc &loc) const + { + static const std::regex loc_re(R"(X(\d+)Y(\d+)/\w+)"); + std::smatch match; + if (!std::regex_search(s, match, loc_re)) + return false; + loc = Loc(std::stoi(match[1].str()), std::stoi(match[2].str()), 0); + return true; + } + + // Find the iopadmap-created IO cell whose PAD port is connected to `net`. + // Errors if multiple distinct IO cells share the same net. + CellInfo *find_pad_peer(NetInfo *net, int line_number) const + { + if (!net) + return nullptr; + + if (net->driver.cell && net->driver.port == id_PAD) + return net->driver.cell; + + CellInfo *found = nullptr; + for (auto &usr : net->users) { + if (usr.port != id_PAD) + continue; + if (found && usr.cell != found) + log_error("Multiple IO cells connected via PAD on net '%s' (on line %d)\n", net->name.c_str(ctx), + line_number); + found = usr.cell; + } + return found; + } + + void execute_set_io_command(const po::variables_map &vm, int line_number) + { + std::string cell = vm["cell"].as(); + std::string pin = vm["pin"].as(); + + auto buf_it = ctx->cells.find(ctx->id(cell)); + if (buf_it == ctx->cells.end()) { + if (ctx->debug) + log_info("Ignoring constraint for '%s': port does not exist (on line %d)\n", cell.c_str(), line_number); + return; + } + + CellInfo *buf_ci = buf_it->second.get(); + if (!buf_ci->type.in(ctx->id("$nextpnr_ibuf"), ctx->id("$nextpnr_obuf"), ctx->id("$nextpnr_iobuf"))) + log_error("Can only constrain IO cells (on line %d)\n", line_number); + + CellInfo *io_cell = find_pad_peer(buf_ci->getPort(id_O), line_number); + if (!io_cell) + io_cell = find_pad_peer(buf_ci->getPort(id_I), line_number); + if (!io_cell) + log_error("No IO cell found connected to '%s' via PAD port (on line %d). " + "Was iopadmap run in Yosys?\n", + cell.c_str(), line_number); + + BelId pin_bel = ctx->getBelByNameStr(pin); + if (pin_bel == BelId()) + log_error("Cannot find a pin named '%s' (on line %d)\n", pin.c_str(), line_number); + + if (ctx->getBelType(pin_bel) != io_cell->type) + log_error("Pin '%s' bel type '%s' does not match IO cell type '%s' (on line %d)\n", pin.c_str(), + ctx->getBelType(pin_bel).c_str(ctx), io_cell->type.c_str(ctx), line_number); + + if (io_cell->attrs.count(id_BEL)) + log_error("duplicate pin constraint on '%s' (on line %d)\n", cell.c_str(), line_number); + + io_cell->attrs[id_BEL] = ctx->getBelName(pin_bel).str(ctx); + log_info("constrained '%s' to bel '%s'\n", cell.c_str(), io_cell->attrs[id_BEL].as_string().c_str()); + } + + void execute_set_frequency_command(const po::variables_map &vm, int line_number) + { + std::string net = vm["net"].as(); + float frequency = vm["frequency"].as(); + + if (frequency <= 0.0f) { + log_error("frequency must be positive (on line %d)\n", line_number); + } + + ctx->addClock(ctx->id(net), frequency); + log_info("set frequency constraint: %s = %.3f MHz\n", net.c_str(), frequency); + } + + void execute_set_cell_command(const po::variables_map &vm, int line_number) + { + std::string cell = vm["cell"].as(); + std::string bel = vm["bel"].as(); + + auto fnd_cell = ctx->cells.find(ctx->id(cell)); + if (fnd_cell == ctx->cells.end()) { + log_warning("unmatched constraint '%s' (on line %d)\n", cell.c_str(), line_number); + } else { + BelId targetBel = ctx->getBelByNameStr(bel); + if (targetBel == BelId()) + log_error("package does not have a bel named '%s' (on line %d)\n", bel.c_str(), line_number); + if (fnd_cell->second->attrs.count(id_BEL)) + log_error("duplicate bel constraint on '%s' (on line %d)\n", cell.c_str(), line_number); + fnd_cell->second->attrs[id_BEL] = ctx->getBelName(targetBel).str(ctx); + log_info("constrained '%s' to bel '%s'\n", cell.c_str(), + fnd_cell->second->attrs[id_BEL].as_string().c_str()); + } + } + + void execute_pseudo_plug_command(const po::variables_map &vm, int line_number) + { + std::string plug_name = vm["plug-name"].as(); + IdString plug_name_id = ctx->id(plug_name); + size_t port_count = vm.count("port") ? vm["port"].as>().size() : 0; + + // Find the plug cell + CellInfo *plug = nullptr; + if (ctx->cells.count(plug_name_id)) { + plug = ctx->cells.at(plug_name_id).get(); + if (plug->ports.size() != port_count && port_count > 0) { + log_error("Port count on pseudo-plug '%s' (%d) does not match number of --port mappings (%ld) (on " + "line %d). A pseudo-plug need to be fully constrained\n", + plug_name.c_str(), int(plug->ports.size()), port_count, line_number); + } + } else { + log_error("Cannot find cell '%s' (on line %d)\n", plug_name.c_str(), line_number); + } + + if (!plug->isPseudo()) + ctx->createRegionPlug(plug_name_id, plug->type, Loc(0, 0, 0)); + + // Process port mappings + if (vm.count("port")) { + const auto &port_specs = vm["port"].as>(); + for (const auto &mapping : port_specs) { + // Parse port:wire format + size_t colon_pos = mapping.find(':'); + if (colon_pos == std::string::npos || colon_pos == 0 || colon_pos == mapping.length() - 1) { + log_error("Invalid port:wire mapping format '%s' (expected port:wire) (on line %d)\n", + mapping.c_str(), line_number); + } + std::string port_name = mapping.substr(0, colon_pos); + std::string wire_name = mapping.substr(colon_pos + 1); + + Loc wire_loc; + if (!parse_loc_from_string(wire_name, wire_loc)) + log_error("Cannot parse location from '%s' (expected XY/...) (on line %d)\n", + wire_name.c_str(), line_number); + + RegionPlug *rplug = dynamic_cast(plug->pseudo_cell.get()); + rplug->loc = wire_loc; + + IdString port_name_id = ctx->id(port_name); + WireId wire = ctx->getWireByNameStr(wire_name); + + if (wire == WireId()) + log_error("Cannot find wire '%s' (on line %d)\n", wire_name.c_str(), line_number); + + auto bel_pins = ctx->getBelPinsForCellPin(plug, port_name_id); + if (bel_pins.empty()) + log_error("Cannot find port '%s' on cell '%s' (on line %d)\n", port_name.c_str(), plug_name.c_str(), + line_number); + + PortType dir; + if (plug->ports.count(port_name_id)) { + dir = plug->ports[port_name_id].type; + } else { + log_error("port '%s' not found on cell '%s' (on line %d)\n", port_name.c_str(), plug_name.c_str(), + line_number); + } + + if (bel_pins.size() > 1) + log_warning("Port '%s' on cell '%s' has multiple possible pin mappings, using first\n", + port_name.c_str(), plug_name.c_str()); + + ctx->addPlugPin(plug_name_id, bel_pins[0], dir, wire); + log_info("constrained pseudo-plug '%s' port '%s' to wire '%s'\n", plug_name.c_str(), port_name.c_str(), + wire_name.c_str()); + } + } + + // Process timing constraints + if (vm.count("timing")) { + log_warning("Timing constraints on pseudo-plugs are currently NOT respected and will be implemented in the " + "future (on line %d)\n", + line_number); + const auto &timing_specs = vm["timing"].as>(); + for (const auto &spec : timing_specs) { + // Parse port-in:port-out:min-delay:max-delay format + std::vector parts; + std::stringstream spec_ss(spec); + std::string part; + while (std::getline(spec_ss, part, ':')) { + parts.push_back(part); + } + + if (parts.size() != 4) { + log_error("Invalid timing constraint format '%s' (expected " + "port-in:port-out:min-delay:max-delay) (on line %d)\n", + spec.c_str(), line_number); + } + + std::string port_in = parts[0]; + std::string port_out = parts[1]; + float min_delay = 0.0f, max_delay = 0.0f; + try { + min_delay = std::stof(parts[2]); + max_delay = std::stof(parts[3]); + } catch (const std::exception &) { + log_error("Invalid numeric value in timing constraint '%s' (on line %d)\n", spec.c_str(), + line_number); + } + + if (min_delay < 0.0f) { + log_error("min-delay must be non-negative in timing constraint '%s' (on line %d)\n", spec.c_str(), + line_number); + } + if (max_delay < 0.0f) { + log_error("max-delay must be non-negative in timing constraint '%s' (on line %d)\n", spec.c_str(), + line_number); + } + if (min_delay > max_delay) { + log_error("min-delay (%.3f) cannot be greater than max-delay (%.3f) in timing constraint '%s' " + "(on line %d)\n", + min_delay, max_delay, spec.c_str(), line_number); + } + + IdString port_in_id = ctx->id(port_in); + IdString port_out_id = ctx->id(port_out); + + if (!plug->ports.count(port_in_id) || plug->ports.at(port_in_id).type != PORT_IN) + log_error("input port '%s' not found on cell '%s' (on line %d)\n", port_in.c_str(), + plug_name.c_str(), line_number); + if (!plug->ports.count(port_out_id) || plug->ports.at(port_out_id).type != PORT_OUT) + log_error("output port '%s' not found on cell '%s' (on line %d)\n", port_out.c_str(), + plug_name.c_str(), line_number); + + ctx->addCellTimingDelayMinMax(plug_name_id, port_in_id, port_out_id, min_delay, max_delay); + log_info("applied timing constraint %.3f-%.3f ns from port '%s' to port '%s' on pseudo-plug '%s'\n", + min_delay, max_delay, port_in.c_str(), port_out.c_str(), plug_name.c_str()); + } + } + } + + void execute_prohibit_pip_command(const po::variables_map &vm, int line_number) + { + std::string pip_pattern = vm["pip"].as(); + std::string dummy_net_name = "$prohibit_pip"; + NetInfo *dummy_net = nullptr; + if (ctx->nets.count(ctx->id(dummy_net_name))) + dummy_net = ctx->nets.at(ctx->id(dummy_net_name)).get(); + else + dummy_net = ctx->createNet(ctx->id(dummy_net_name)); + + try { + std::regex pip_regex(pip_pattern); + std::vector matching_pips; + + // Find all pips matching the regex pattern + for (auto pip : ctx->getPips()) { + std::string pip_name = ctx->nameOfPip(pip); + if (std::regex_match(pip_name, pip_regex)) { + matching_pips.push_back(pip); + } + } + + if (matching_pips.empty()) { + log_error("No pips found matching pattern '%s' (on line %d)\n", pip_pattern.c_str(), line_number); + } + + // Prohibit all matching pips + for (auto pip : matching_pips) { + ctx->bindPip(pip, dummy_net, STRENGTH_USER); + if (ctx->debug) + log_info("forbade pip '%s' by binding to dummy net '%s'\n", ctx->nameOfPip(pip), + dummy_net_name.c_str()); + } + + log_info("Prohibited %d pips matching pattern '%s'\n", static_cast(matching_pips.size()), + pip_pattern.c_str()); + + } catch (const std::regex_error &e) { + log_error("Invalid regex pattern '%s' in prohibit_pip command (on line %d): %s\n", pip_pattern.c_str(), + line_number, e.what()); + } + } + + void execute_prohibit_wire_command(const po::variables_map &vm, int line_number) + { + std::string wire_pattern = vm["wire"].as(); + std::string dummy_net_name = "$prohibit_wire"; + NetInfo *dummy_net = nullptr; + if (ctx->nets.count(ctx->id(dummy_net_name))) + dummy_net = ctx->nets.at(ctx->id(dummy_net_name)).get(); + else + dummy_net = ctx->createNet(ctx->id(dummy_net_name)); + + try { + std::regex wire_regex(wire_pattern); + std::vector matching_wires; + + // Find all wires matching the regex pattern + for (auto wire : ctx->getWires()) { + std::string wire_name = ctx->nameOfWire(wire); + if (std::regex_match(wire_name, wire_regex)) { + matching_wires.push_back(wire); + } + } + + if (matching_wires.empty()) { + log_error("No wires found matching pattern '%s' (on line %d)\n", wire_pattern.c_str(), line_number); + } + + // Prohibit all matching wires + for (auto wire : matching_wires) { + ctx->bindWire(wire, dummy_net, STRENGTH_USER); + if (ctx->debug) + log_info("forbade wire '%s' by binding to dummy net '%s'\n", ctx->nameOfWire(wire), + dummy_net_name.c_str()); + } + + log_info("Prohibited %d wires matching pattern '%s'\n", static_cast(matching_wires.size()), + wire_pattern.c_str()); + + } catch (const std::regex_error &e) { + log_error("Invalid regex pattern '%s' in prohibit_wire command (on line %d): %s\n", wire_pattern.c_str(), + line_number, e.what()); + } + } + + void execute_prohibit_bel_command(const po::variables_map &vm, int line_number) + { + std::string bel_pattern = vm["bel"].as(); + + try { + std::regex bel_regex(bel_pattern); + std::vector matching_bels; + + // Find all bels matching the regex pattern + for (auto bel : ctx->getBels()) { + std::string bel_name = ctx->nameOfBel(bel); + if (std::regex_match(bel_name, bel_regex)) { + matching_bels.push_back(bel); + } + } + + if (matching_bels.empty()) { + log_error("No bels found matching pattern '%s' (on line %d)\n", bel_pattern.c_str(), line_number); + } + + // Prohibit all matching bels + for (auto bel : matching_bels) { + std::string bel_name = ctx->nameOfBel(bel); + std::string dummy_cell_name = "$prohibit_bel_" + bel_name; + IdString bel_type = ctx->getBelType(bel); + CellInfo *dummy_cell = ctx->createCell(ctx->id(dummy_cell_name), bel_type); + + ctx->bindBel(bel, dummy_cell, STRENGTH_USER); + if (ctx->debug) + log_info("forbade bel '%s' by binding to dummy cell '%s'\n", bel_name.c_str(), + dummy_cell_name.c_str()); + } + + log_info("Prohibited %d bels matching pattern '%s'\n", static_cast(matching_bels.size()), + bel_pattern.c_str()); + + } catch (const std::regex_error &e) { + log_error("Invalid regex pattern '%s' in prohibit_bel command (on line %d): %s\n", bel_pattern.c_str(), + line_number, e.what()); + } + } + + void setup_commands() + { + // Setup set_io command + // Syntax: set_io cell pin + commands.emplace("set_io", PCFCommand("set_io", "Constrain IO cell to pin")); + auto &set_io = commands.at("set_io"); + set_io.desc.add_options()("cell", po::value()->required(), + "Cell name")("pin", po::value()->required(), "Pin name"); + set_io.pos.add("cell", 1); + set_io.pos.add("pin", 1); + set_io.handler = [this](const po::variables_map &vm, int line_number) { + execute_set_io_command(vm, line_number); + }; + + // Setup set_frequency command + // Syntax: set_frequency net frequency + commands.emplace("set_frequency", PCFCommand("set_frequency", "Set clock frequency constraint")); + auto &set_freq = commands.at("set_frequency"); + set_freq.desc.add_options()("net", po::value()->required(), + "Net name")("frequency", po::value()->required(), "Frequency in MHz"); + set_freq.pos.add("net", 1); + set_freq.pos.add("frequency", 1); + set_freq.handler = [this](const po::variables_map &vm, int line_number) { + execute_set_frequency_command(vm, line_number); + }; + + // Setup set_cell command + // Syntax: set_cell cell bel + commands.emplace("set_cell", PCFCommand("set_cell", "Constrain cell to bel")); + auto &set_cell = commands.at("set_cell"); + set_cell.desc.add_options()("cell", po::value()->required(), + "Cell name")("bel", po::value()->required(), "Bel name"); + set_cell.pos.add("cell", 1); + set_cell.pos.add("bel", 1); + set_cell.handler = [this](const po::variables_map &vm, int line_number) { + execute_set_cell_command(vm, line_number); + }; + + // Setup set_pseudo_plug command with flag-based interface and timing constraints support + // Syntax: set_pseudo_plug plug_name --port : --timing ::: + commands.emplace("set_pseudo_plug", + PCFCommand("set_pseudo_plug", + "Configure pseudo plug with flag-based port mappings and timing constraints")); + auto &pseudo_plug = commands.at("set_pseudo_plug"); + pseudo_plug.desc.add_options()("plug-name", po::value()->required(), "Pseudo plug cell name")( + "port", po::value>()->multitoken(), + "Port mapping in format port:wire (repeatable)")( + "timing", po::value>()->multitoken(), + "Timing constraint in format port-in:port-out:min-delay:max-delay (repeatable)"); + pseudo_plug.pos.add("plug-name", 1); + pseudo_plug.handler = [this](const po::variables_map &vm, int line_number) { + execute_pseudo_plug_command(vm, line_number); + }; + + // Setup prohibit_pip command + // Syntax: prohibit_pip pip_pattern + commands.emplace("prohibit_pip", PCFCommand("prohibit_pip", "Prohibit use of pips matching regex pattern")); + auto &prohibit_pip = commands.at("prohibit_pip"); + prohibit_pip.desc.add_options()("pip", po::value()->required(), + "Pip name pattern (regex) to prohibit"); + prohibit_pip.pos.add("pip", 1); + prohibit_pip.handler = [this](const po::variables_map &vm, int line_number) { + execute_prohibit_pip_command(vm, line_number); + }; + + // Setup prohibit_wire command + // Syntax: prohibit_wire wire_pattern + commands.emplace("prohibit_wire", PCFCommand("prohibit_wire", "Prohibit use of wires matching regex pattern")); + auto &prohibit_wire = commands.at("prohibit_wire"); + prohibit_wire.desc.add_options()("wire", po::value()->required(), + "Wire name pattern (regex) to prohibit"); + prohibit_wire.pos.add("wire", 1); + prohibit_wire.handler = [this](const po::variables_map &vm, int line_number) { + execute_prohibit_wire_command(vm, line_number); + }; + + // Setup prohibit_bel command + // Syntax: prohibit_bel bel_pattern + commands.emplace("prohibit_bel", PCFCommand("prohibit_bel", "Prohibit use of bels matching regex pattern")); + auto &prohibit_bel = commands.at("prohibit_bel"); + prohibit_bel.desc.add_options()("bel", po::value()->required(), + "Bel name pattern (regex) to prohibit"); + prohibit_bel.pos.add("bel", 1); + prohibit_bel.handler = [this](const po::variables_map &vm, int line_number) { + execute_prohibit_bel_command(vm, line_number); + }; + } + + bool parse_and_execute_command(const std::vector &words) + { + if (words.empty()) + return false; + + std::string cmd_name = words[0]; + auto it = commands.find(cmd_name); + if (it == commands.end()) { + log_error("unsupported command '%s' (on line %d)\n", cmd_name.c_str(), lineno); + return false; + } + + try { + // Convert words to argc/argv style for program_options + std::vector argv; + for (const auto &word : words) { + argv.push_back(word.c_str()); + } + + po::variables_map vm; + + po::parsed_options parsed = po::command_line_parser(static_cast(argv.size()), argv.data()) + .options(it->second.desc) + .positional(it->second.pos) + .run(); + + po::store(parsed, vm); + po::notify(vm); + + // Execute the command + it->second.handler(vm, lineno); + return true; + + } catch (const po::error &e) { + log_error("Error parsing command '%s' on line %d: %s\n", cmd_name.c_str(), lineno, e.what()); + } catch (const std::exception &e) { + log_error("Error executing command '%s' on line %d: %s\n", cmd_name.c_str(), lineno, e.what()); + } + return false; + } + + void apply_constraints() + { + std::ifstream in(filename); + if (!in) { + log_error("failed to open constraint file\n"); + } + + std::string line; + std::string accumulated_line; + lineno = 0; + int command_start_line = 0; + + while (std::getline(in, line)) { + lineno++; + + // Remove comments before checking for continuation + size_t cstart = line.find('#'); + if (cstart != std::string::npos) + line = line.substr(0, cstart); + + // Trim trailing whitespace + while (!line.empty() && std::isspace(line.back())) + line.pop_back(); + + // Check for line continuation (ends with backslash) + bool has_continuation = false; + if (!line.empty() && line.back() == '\\') { + has_continuation = true; + line.pop_back(); // Remove the backslash + // Trim any whitespace before the backslash + while (!line.empty() && std::isspace(line.back())) + line.pop_back(); + } + + // If this is the start of a new command, record the line number + if (accumulated_line.empty() && !line.empty()) { + command_start_line = lineno; + } + + // Accumulate the line + if (!accumulated_line.empty() && !line.empty()) { + accumulated_line += " "; // Add space between continued lines + } + accumulated_line += line; + + // If no continuation, process the accumulated command + if (!has_continuation) { + // Parse the accumulated command + std::stringstream ss(accumulated_line); + std::vector words; + std::string tmp; + while (ss >> tmp) + words.push_back(tmp); + + if (!words.empty()) { + // Set line number to the start of the command for error reporting + int saved_lineno = lineno; + lineno = command_start_line; + parse_and_execute_command(words); + lineno = saved_lineno; + } + + // Reset for next command + accumulated_line.clear(); + command_start_line = 0; + } + } + + // Handle case where file ends with a continuation (error) + if (!accumulated_line.empty()) { + log_error("File ends with incomplete command starting at line %d (missing continuation or final command)\n", + command_start_line); + } + } +}; +} // namespace + +void fabulous_pcf(Context *ctx, const std::string &filename) +{ + FABulousDesignConstraints PCF(ctx, filename); + PCF.apply_constraints(); + log_info("Finished applying constraints from '%s'\n", filename.c_str()); +} + +NEXTPNR_NAMESPACE_END \ No newline at end of file diff --git a/generic/viaduct/fabulous/pcf.h b/generic/viaduct/fabulous/pcf.h new file mode 100644 index 00000000..cc92db2a --- /dev/null +++ b/generic/viaduct/fabulous/pcf.h @@ -0,0 +1,13 @@ +#ifndef FABULOUS_FDC_H +#define FABULOUS_FDC_H + +#include "fab_cfg.h" +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +void fabulous_pcf(Context *ctx, const std::string &filename); + +NEXTPNR_NAMESPACE_END + +#endif \ No newline at end of file