feat: basic pcf implementation (#1637)

chore: seems like working pcf

feat: add reg support and clean up

chore: add clean up

delay io check and add cell timing min-max delay

fix rebase error

better pcf syntax

add regex support for prohibit command

fix regex and repeat create

fix cell can potentially have no bel

fix IO

chore: clean up

chore: review comment

feat: set pseudo cell loc by wire info

yosys based IO insert

finalise

final finalise
This commit is contained in:
Kelvin Chung 2026-02-17 16:38:01 +00:00 committed by GitHub
parent 49ba0b277f
commit f1fc47e139
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 719 additions and 7 deletions

View File

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

View File

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

View File

@ -226,6 +226,8 @@ struct Arch : BaseArch<ArchRanges>
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);

View File

@ -34,6 +34,7 @@
#include "fab_defs.h"
#include "fasm.h"
#include "pack.h"
#include "pcf.h"
#include "validity_check.h"
#include <filesystem>
@ -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<BlockTracker> blk_trk;
std::string get_env_var(const std::string &name, const std::string &prompt = "")

View File

@ -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 &param : ci->params) {
// TODO: better parameter type auto-detection

View File

@ -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<CellTypePort> 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<IdString> 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()

View File

@ -0,0 +1,622 @@
#include <boost/program_options.hpp>
#include <fstream>
#include <functional>
#include <regex>
#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<void(const po::variables_map &, int)> 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<std::string, PCFCommand> 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>();
std::string pin = vm["pin"].as<std::string>();
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<std::string>();
float frequency = vm["frequency"].as<float>();
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>();
std::string bel = vm["bel"].as<std::string>();
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<std::string>();
IdString plug_name_id = ctx->id(plug_name);
size_t port_count = vm.count("port") ? vm["port"].as<std::vector<std::string>>().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<std::vector<std::string>>();
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 X<num>Y<num>/...) (on line %d)\n",
wire_name.c_str(), line_number);
RegionPlug *rplug = dynamic_cast<RegionPlug *>(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<std::vector<std::string>>();
for (const auto &spec : timing_specs) {
// Parse port-in:port-out:min-delay:max-delay format
std::vector<std::string> 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>();
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<PipId> 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<int>(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>();
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<WireId> 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<int>(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<std::string>();
try {
std::regex bel_regex(bel_pattern);
std::vector<BelId> 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<int>(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<std::string>()->required(),
"Cell name")("pin", po::value<std::string>()->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<std::string>()->required(),
"Net name")("frequency", po::value<float>()->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<std::string>()->required(),
"Cell name")("bel", po::value<std::string>()->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 <port>:<wire> --timing <port-in>:<port-out>:<min>:<max>
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<std::string>()->required(), "Pseudo plug cell name")(
"port", po::value<std::vector<std::string>>()->multitoken(),
"Port mapping in format port:wire (repeatable)")(
"timing", po::value<std::vector<std::string>>()->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<std::string>()->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<std::string>()->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<std::string>()->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<std::string> &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<const char *> 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<int>(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<std::string> 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

View File

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