1
0
mirror of https://github.com/YosysHQ/nextpnr.git synced 2026-01-11 23:53:21 +00:00
Miodrag Milanović d6483adb4d
Gatemate FPGA initial support (#1473)
* Initial code for GateMate

* Initial work on forming bitstream

* Add CCF parsing

* Use CCF to set IO location

* Propagate errors

* Restructure code

* Add support for reading from config

* Start adding infrastructure for reading bitstream

* Fix script

* GPIO initial work

* Add IN1->RAM_O2 propagation

* Fixed typo

* Cleanup

* More parameter checks

* Add LVDS support

* Cleanup

* Keep just used connections for now

* Naive lut tree CPE pack

* Naive pack CC_DFF

* pack DFF fixes

* Handle MUX flags

* Fix DFF pack

* Prevent pass trough issues

* Cleanup

* Use device wrapper class

* Update due to API changes

* Use pin  connection aliases

* Start work on BUFG support

* Fix CC_L2T5 pack

* Add CPE input inverters

* Constrain routes to have correct inversion state

* Add clock inversion pip

* Added MX2 and MX4 support

* Fix script

* BUFG support

* debug print if route found with wrong polarity

* Some CC_DFF improvements

* Create reproducible chip database

* Simplify inversion of special signals

* Few more DFF features

* Add forgotten virtual port renames

* Handle muxes with constant inputs

* Allow inversion for muxes

* cleanup

* DFF input can be constant

* init DFF only when needed

* cleanup

* Add basic PLL support

* Add some timings

* Add USR_RSTN support

* Display few more primitives

* Use pass trough signals to validate architecture data

* Use extra tile information from chip database

* Updates needed for a build system changes

* Implement SB_DRIVE support

* Properly named configuration bits

* autogenerated constids.inc

* small fix

* Initial code for CPE halfs

* Some cleanup

* make sure FFs are compatible

* reverted due to db change

* Merge DFF where applicable

* memory allocation issue

* fix

* better MX2

* ram_i handling

* Cleanup MX4

* Support latches

* compare L_D flag as well

* Move virtual pips

* Naive addf pack

* carry chains grouping

* Keep chip database reproducible

* split addf vectors

* Block CPEs when GPIO is used

* Prepare placement code

* RAM_I/RAM_O rewrite

* fix ram_i/o index

* Display RAM and add new primitives

* PLL wip code

* CC_PLL_ADV packing

* PLL handling cleanup

* Add PLL comments

* Keep only high fan-out BUFG

* Add skeleton for tests

* Utilize move_ram_o

* GPIO wip

* GPIO wip

* PLL fixes

* cleanup

* FF_OBF support

* Handle FF_IBF

* Make SLEW FAST if not defined as in latest p_r

* Make sure FF_OBF only driving GPIO

* Moved pll calc into separate file

* IDDR handling and started ODDR

* Route DDR input for CC_ODDR

* Notify error in case ODDR or IDDR are used but not with I/O pin

* cleanup for CC_USR_RSTN

* Extract proper RAM location  for bitstream

* Code cleanup

* Allow auto place of pads

* Use clock source flag

* Configure GPIO clock signals

* Handle conflicting clk

* Use BUGF in proper order

* Connected CLK, works without but good for debugging

* CC_CFG_CTRL placement

* Group RAM data 40 bytes per row

* Write BRAM content

* RAM wip

* Use relative constraints from chipdb

* fix broken build

* Memory wip

* Handle custom clock for memories

* Support FIFO

* optimize move_ram_io

* Fix SR signal handling acorrding to findings

* set placer beta

* Pre place what we can

* Revert "debug print if route found with wrong polarity"

This reverts commit cf9ded2f183db0f4e85da454e5d9f2a4da3f5cfe.

* Revert "Constrain routes to have correct inversion state"

This reverts commit 795c284d4856ff17e37d64f013775e86f1bed803.

* Remove virtual pips

* Implement post processing inversion

* ADDF add ability to route additional CO

* Merge two ADDFs in one CPE

* Added TODO

* clangformat

* Cleanup

* Add serdes handling in config file

* Cleanup

* Cleanup

* Cleanup

* Fix in PLL handling

* Fixed ADDF edge case

* No need for this

* Fix latch

* Sanity checks

* Support CC_BRAM_20K merge

* Start creating testing environment

* LVDS fixes

* Add connection helper

* Cleanup

* Fix tabs

* Formatting fix

* Remove optimization tests for now

* remove read_bitstream

* removed .c_str()

* Removed config parsing

* using snake_case

* Use bool_or_default where applicable

* refactored bitstream write code

* Add allow-unconstrained option

* Update DFF related messages

* Add clock constraint propagation

---------

Co-authored-by: Lofty <dan.ravensloft@gmail.com>
2025-04-22 16:41:01 +02:00

2189 lines
91 KiB
C++

/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* 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 <boost/algorithm/string.hpp>
#include "design_utils.h"
#include "gatemate_util.h"
#include "pack.h"
#define HIMBAECHEL_CONSTIDS "uarch/gatemate/constids.inc"
#include "himbaechel_constids.h"
NEXTPNR_NAMESPACE_BEGIN
// Return true if a cell is a flipflop
inline bool is_dff(const BaseCtx *ctx, const CellInfo *cell) { return cell->type.in(id_CC_DFF, id_CC_DLT); }
void GateMatePacker::flush_cells()
{
for (auto pcell : packed_cells) {
for (auto &port : ctx->cells[pcell]->ports) {
ctx->cells[pcell]->disconnectPort(port.first);
}
ctx->cells.erase(pcell);
}
packed_cells.clear();
}
void GateMatePacker::disconnect_if_gnd(CellInfo *cell, IdString input)
{
NetInfo *net = cell->getPort(input);
if (!net)
return;
if (net->name.in(ctx->id("$PACKER_GND"))) {
cell->disconnectPort(input);
}
}
BelId GateMatePacker::get_bank_cpe(int bank)
{
switch (bank) {
case 0:
return ctx->getBelByLocation(Loc(97 + 2, 128 + 2, 1)); // N1, RAM_O1
case 1:
return ctx->getBelByLocation(Loc(97 + 2, 128 + 2, 0)); // N2, RAM_O2
case 2:
return ctx->getBelByLocation(Loc(160 + 2, 65 + 2, 1)); // E1, RAM_O1
case 3:
return ctx->getBelByLocation(Loc(160 + 2, 65 + 2, 0)); // E2, RAM_O2
case 4:
return ctx->getBelByLocation(Loc(1 + 2, 65 + 2, 1)); // W1, RAM_O1
case 5:
return ctx->getBelByLocation(Loc(1 + 2, 65 + 2, 0)); // W2, RAM_O2
case 6:
return ctx->getBelByLocation(Loc(97 + 2, 1 + 2, 1)); // S1, RAM_O1
case 7:
return ctx->getBelByLocation(Loc(97 + 2, 1 + 2, 0)); // S2, RAM_O2
case 8:
return ctx->getBelByLocation(Loc(49 + 2, 1 + 2, 1)); // S3, RAM_O1
default:
log_error("Unkown bank\n");
}
}
void GateMatePacker::pack_io()
{
// Trim nextpnr IOBs - assume IO buffer insertion has been done in synthesis
for (auto &port : ctx->ports) {
if (!ctx->cells.count(port.first))
log_error("Port '%s' doesn't seem to have a corresponding top level IO\n", ctx->nameOf(port.first));
CellInfo *ci = ctx->cells.at(port.first).get();
PortRef top_port;
top_port.cell = nullptr;
bool is_npnr_iob = false;
if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) {
// Might have an input buffer connected to it
is_npnr_iob = true;
NetInfo *o = ci->getPort(id_O);
if (o == nullptr)
;
else if (o->users.entries() > 1)
log_error("Top level pin '%s' has multiple input buffers\n", ctx->nameOf(port.first));
else if (o->users.entries() == 1)
top_port = *o->users.begin();
}
if (ci->type == ctx->id("$nextpnr_obuf") || ci->type == ctx->id("$nextpnr_iobuf")) {
// Might have an output buffer connected to it
is_npnr_iob = true;
NetInfo *i = ci->getPort(id_I);
if (i != nullptr && i->driver.cell != nullptr) {
if (top_port.cell != nullptr)
log_error("Top level pin '%s' has multiple input/output buffers\n", ctx->nameOf(port.first));
top_port = i->driver;
}
// Edge case of a bidirectional buffer driving an output pin
if (i->users.entries() > 2) {
log_error("Top level pin '%s' has illegal buffer configuration\n", ctx->nameOf(port.first));
} else if (i->users.entries() == 2) {
if (top_port.cell != nullptr)
log_error("Top level pin '%s' has illegal buffer configuration\n", ctx->nameOf(port.first));
for (auto &usr : i->users) {
if (usr.cell->type == ctx->id("$nextpnr_obuf") || usr.cell->type == ctx->id("$nextpnr_iobuf"))
continue;
top_port = usr;
break;
}
}
}
if (!is_npnr_iob)
log_error("Port '%s' doesn't seem to have a corresponding top level IO (internal cell type mismatch)\n",
ctx->nameOf(port.first));
if (top_port.cell == nullptr) {
log_info("Trimming port '%s' as it is unused.\n", ctx->nameOf(port.first));
} else {
// Copy attributes to real IO buffer
for (auto &attrs : ci->attrs)
top_port.cell->attrs[attrs.first] = attrs.second;
for (auto &params : ci->params) {
IdString key = params.first;
if (key == id_LOC &&
top_port.cell->type.in(id_CC_LVDS_IBUF, id_CC_LVDS_OBUF, id_CC_LVDS_TOBUF, id_CC_LVDS_IOBUF)) {
if (top_port.port.in(id_I_P, id_O_P, id_IO_P))
key = id_PIN_NAME_P;
if (top_port.port.in(id_I_N, id_O_N, id_IO_N))
key = id_PIN_NAME_N;
}
if (top_port.cell->params.count(key)) {
if (top_port.cell->params[key] != params.second) {
std::string val = params.second.is_string ? params.second.as_string()
: std::to_string(params.second.as_int64());
log_warning("Overriding parameter '%s' with value '%s' for cell '%s'.\n", key.c_str(ctx),
val.c_str(), ctx->nameOf(top_port.cell));
}
}
top_port.cell->params[key] = params.second;
}
// Make sure that top level net is set correctly
port.second.net = top_port.cell->ports.at(top_port.port).net;
}
// Now remove the nextpnr-inserted buffer
ci->disconnectPort(id_I);
ci->disconnectPort(id_O);
ctx->cells.erase(port.first);
}
for (auto &cell : ctx->cells) {
CellInfo &ci = *cell.second;
if (!ci.type.in(id_CC_IBUF, id_CC_OBUF, id_CC_TOBUF, id_CC_IOBUF, id_CC_LVDS_IBUF, id_CC_LVDS_OBUF,
id_CC_LVDS_TOBUF, id_CC_LVDS_IOBUF))
continue;
bool is_lvds = ci.type.in(id_CC_LVDS_IBUF, id_CC_LVDS_OBUF, id_CC_LVDS_TOBUF, id_CC_LVDS_IOBUF);
std::string loc = str_or_default(ci.params, is_lvds ? id_PIN_NAME_P : id_PIN_NAME, "UNPLACED");
if (ci.params.count(id_LOC)) {
std::string new_loc = str_or_default(ci.params, id_LOC, "UNPLACED");
if (loc != "UNPLACED" && loc != new_loc)
log_warning("Overriding location of cell '%s' from '%s' with '%s'\n", ctx->nameOf(&ci), loc.c_str(),
new_loc.c_str());
loc = new_loc;
}
if (loc == "UNPLACED") {
const ArchArgs &args = ctx->args;
if (args.options.count("allow-unconstrained"))
log_warning("IO '%s' is unconstrained in CCF and will be automatically placed.\n", ctx->nameOf(&ci));
else
log_error("IO '%s' is unconstrained in CCF (override this error with "
"--vopt allow-unconstrained).\n",
ctx->nameOf(&ci));
}
disconnect_if_gnd(&ci, id_T);
if (ci.type == id_CC_TOBUF && !ci.getPort(id_T))
ci.type = id_CC_OBUF;
if (ci.type == id_CC_LVDS_TOBUF && !ci.getPort(id_T))
ci.type = id_CC_LVDS_OBUF;
std::vector<IdString> keys;
for (auto &p : ci.params) {
if (p.first.in(id_PIN_NAME, id_PIN_NAME_P, id_PIN_NAME_N)) {
if (ctx->get_package_pin_bel(ctx->id(p.second.as_string())) == BelId())
log_error("Unknown %s '%s' for cell '%s'.\n", p.first.c_str(ctx), p.second.as_string().c_str(),
ci.name.c_str(ctx));
keys.push_back(p.first);
continue;
}
if (p.first.in(id_V_IO, id_LOC)) {
keys.push_back(p.first);
continue;
}
if (ci.type.in(id_CC_IBUF, id_CC_IOBUF) &&
p.first.in(id_PULLUP, id_PULLDOWN, id_KEEPER, id_SCHMITT_TRIGGER, id_DELAY_IBF, id_FF_IBF))
continue;
if (ci.type.in(id_CC_TOBUF) && p.first.in(id_PULLUP, id_PULLDOWN, id_KEEPER))
continue;
if (ci.type.in(id_CC_OBUF, id_CC_TOBUF, id_CC_IOBUF) &&
p.first.in(id_DRIVE, id_SLEW, id_DELAY_OBF, id_FF_OBF))
continue;
if (ci.type.in(id_CC_LVDS_IBUF, id_CC_LVDS_IOBUF) && p.first.in(id_LVDS_RTERM, id_DELAY_IBF, id_FF_IBF))
continue;
if (ci.type.in(id_CC_LVDS_OBUF, id_CC_LVDS_TOBUF, id_CC_LVDS_IOBUF) &&
p.first.in(id_LVDS_BOOST, id_DELAY_OBF, id_FF_OBF))
continue;
log_warning("Removing unsupported parameter '%s' for type '%s'.\n", p.first.c_str(ctx), ci.type.c_str(ctx));
keys.push_back(p.first);
}
if (ci.params.count(id_SLEW)) {
std::string val = str_or_default(ci.params, id_SLEW, "UNDEFINED");
if (val == "UNDEFINED")
keys.push_back(id_SLEW);
else if (val == "FAST")
ci.params[id_SLEW] = Property(Property::State::S1);
else if (val == "SLOW")
ci.params[id_SLEW] = Property(Property::State::S0);
else
log_error("Unknown value '%s' for SLEW parameter of '%s' cell.\n", val.c_str(), ci.name.c_str(ctx));
}
if (is_lvds) {
std::string p_pin = str_or_default(ci.params, id_PIN_NAME_P, "UNPLACED");
std::string n_pin = str_or_default(ci.params, id_PIN_NAME_N, "UNPLACED");
if (p_pin == "UNPLACED" || n_pin == "UNPLACED")
log_error("Both LVDS pins must be set to a valid locations.\n");
if (p_pin.substr(0, 6) != n_pin.substr(0, 6) || p_pin[7] != n_pin[7])
log_error("LVDS pads '%s' and '%s' do not match.\n", p_pin.c_str(), n_pin.c_str());
if (p_pin[6] != 'A')
log_error("LVDS positive pad must be from type A.\n");
if (n_pin[6] != 'B')
log_error("LVDS negative pad must be from type B.\n");
}
for (auto key : keys)
ci.params.erase(key);
// For output pins set SLEW to FAST if not defined
if (!ci.params.count(id_SLEW) && ci.type.in(id_CC_OBUF, id_CC_TOBUF, id_CC_IOBUF))
ci.params[id_SLEW] = Property(Property::State::S1);
if ((ci.params.count(id_KEEPER) + ci.params.count(id_PULLUP) + ci.params.count(id_PULLDOWN)) > 1)
log_error("PULLUP, PULLDOWN and KEEPER are mutually exclusive parameters.\n");
if (is_lvds)
ci.params[id_LVDS_EN] = Property(Property::State::S1);
// DELAY_IBF and DELAY_OBF must be set depending of type
// Also we need to enable input/output
if (ci.type.in(id_CC_IBUF, id_CC_IOBUF, id_CC_LVDS_IBUF, id_CC_LVDS_IOBUF)) {
ci.params[id_DELAY_IBF] = Property(1 << int_or_default(ci.params, id_DELAY_IBF, 0), 16);
if (is_lvds)
ci.params[id_LVDS_IE] = Property(Property::State::S1);
else
ci.params[id_INPUT_ENABLE] = Property(Property::State::S1);
}
if (ci.type.in(id_CC_OBUF, id_CC_TOBUF, id_CC_IOBUF, id_CC_LVDS_OBUF, id_CC_LVDS_TOBUF, id_CC_LVDS_IOBUF)) {
ci.params[id_DELAY_OBF] = Property(1 << int_or_default(ci.params, id_DELAY_OBF, 0), 16);
ci.params[id_OE_ENABLE] = Property(Property::State::S1);
}
if (ci.params.count(id_DRIVE)) {
int val = int_or_default(ci.params, id_DRIVE, 0);
if (val != 3 && val != 6 && val != 9 && val != 12)
log_error("Unsupported value '%d' for DRIVE parameter of '%s' cell.\n", val, ci.name.c_str(ctx));
ci.params[id_DRIVE] = Property((val - 3) / 3, 2);
}
for (auto &p : ci.params) {
if (p.first.in(id_PULLUP, id_PULLDOWN, id_KEEPER, id_SCHMITT_TRIGGER, id_FF_OBF, id_FF_IBF, id_LVDS_RTERM,
id_LVDS_BOOST)) {
int val = int_or_default(ci.params, p.first, 0);
if (val != 0 && val != 1)
log_error("Unsupported value '%d' for %s parameter of '%s' cell.\n", val, p.first.c_str(ctx),
ci.name.c_str(ctx));
ci.params[p.first] = Property(val, 1);
}
}
// Disconnect PADs
ci.disconnectPort(id_IO);
ci.disconnectPort(id_I);
ci.disconnectPort(id_O);
ci.disconnectPort(id_IO_P);
ci.disconnectPort(id_IO_N);
ci.disconnectPort(id_I_P);
ci.disconnectPort(id_I_N);
ci.disconnectPort(id_O_P);
ci.disconnectPort(id_O_N);
if (loc.empty() || loc == "UNPLACED") {
if (uarch->available_pads.empty())
log_error("No more pads available.\n");
IdString id = *(uarch->available_pads.begin());
uarch->available_pads.erase(id);
loc = id.c_str(ctx);
}
ci.params[id_LOC] = Property(loc);
BelId bel = ctx->get_package_pin_bel(ctx->id(loc));
if (bel == BelId())
log_error("Unable to constrain IO '%s', device does not have a pin named '%s'\n", ci.name.c_str(ctx),
loc.c_str());
log_info(" Constraining '%s' to pad '%s'\n", ci.name.c_str(ctx), loc.c_str());
if (!ctx->checkBelAvail(bel)) {
log_error("Can't place %s at %s because it's already taken by %s\n", ctx->nameOf(&ci), ctx->nameOfBel(bel),
ctx->nameOf(ctx->getBoundBelCell(bel)));
}
ctx->bindBel(bel, &ci, PlaceStrength::STRENGTH_FIXED);
}
}
void GateMatePacker::pack_io_sel()
{
std::vector<CellInfo *> cells;
for (auto &cell : ctx->cells) {
CellInfo &ci = *cell.second;
if (!ci.type.in(id_CC_IBUF, id_CC_OBUF, id_CC_TOBUF, id_CC_IOBUF, id_CC_LVDS_IBUF, id_CC_LVDS_OBUF,
id_CC_LVDS_TOBUF, id_CC_LVDS_IOBUF))
continue;
cells.push_back(&ci);
}
CellInfo *ddr[9] = {nullptr}; // for each bank
auto set_out_clk = [&](CellInfo *cell, CellInfo *target) -> bool {
NetInfo *clk_net = cell->getPort(id_CLK);
if (clk_net) {
if (clk_net->name == ctx->id("$PACKER_GND")) {
cell->disconnectPort(id_CLK);
} else if (clk_net->name == ctx->id("$PACKER_VCC")) {
cell->disconnectPort(id_CLK);
} else {
if (!global_signals.count(clk_net)) {
cell->movePortTo(id_CLK, target, id_OUT4);
target->params[id_SEL_OUT_CLOCK] = Property(Property::State::S1);
return true;
} else {
int index = global_signals[clk_net];
cell->movePortTo(id_CLK, target, ctx->idf("CLOCK%d", index + 1));
target->params[id_OUT_CLOCK] = Property(index, 2);
}
}
}
return false;
};
auto set_in_clk = [&](CellInfo *cell, CellInfo *target) {
NetInfo *clk_net = cell->getPort(id_CLK);
if (clk_net) {
if (clk_net->name == ctx->id("$PACKER_GND")) {
cell->disconnectPort(id_CLK);
} else if (clk_net->name == ctx->id("$PACKER_VCC")) {
cell->disconnectPort(id_CLK);
} else {
if (!global_signals.count(clk_net)) {
cell->movePortTo(id_CLK, target, id_OUT4);
target->params[id_SEL_IN_CLOCK] = Property(Property::State::S1);
} else {
int index = global_signals[clk_net];
cell->movePortTo(id_CLK, target, ctx->idf("CLOCK%d", index + 1));
target->params[id_IN_CLOCK] = Property(index, 2);
}
}
}
};
auto merge_ibf = [&](NetInfo *di_net, CellInfo &ci, bool use_custom_clock) -> bool {
CellInfo *dff = (*di_net->users.begin()).cell;
if (is_gpio_valid_dff(dff)) {
if (!global_signals.count(ci.getPort(id_CLK)) && use_custom_clock) {
log_warning("Found DFF %s cell, but not enough CLK signals.\n", dff->name.c_str(ctx));
return false;
}
// We configure both GPIO IN and let router decide
ci.params[id_IN1_FF] = Property(Property::State::S1);
ci.params[id_IN2_FF] = Property(Property::State::S1);
packed_cells.emplace(dff->name);
ci.disconnectPort(id_Y);
dff->movePortTo(id_Q, &ci, id_DI);
set_in_clk(dff, &ci);
bool invert = bool_or_default(dff->params, id_CLK_INV, 0);
if (invert) {
ci.params[id_INV_IN1_CLOCK] = Property(Property::State::S1);
ci.params[id_INV_IN2_CLOCK] = Property(Property::State::S1);
}
return true;
} else {
log_warning("DFF '%s' cell for IO '%s', but unable to merge.\n", dff->name.c_str(ctx), ci.name.c_str(ctx));
}
return false;
};
auto merge_iddr = [&](NetInfo *di_net, CellInfo &ci, bool use_custom_clock) -> bool {
CellInfo *iddr = (*di_net->users.begin()).cell;
if (!global_signals.count(ci.getPort(id_CLK)) && use_custom_clock) {
log_warning("Found IDDR %s cell, but not enough CLK signals.\n", iddr->name.c_str(ctx));
return false;
}
ci.params[id_IN1_FF] = Property(Property::State::S1);
ci.params[id_IN2_FF] = Property(Property::State::S1);
packed_cells.emplace(iddr->name);
ci.disconnectPort(id_Y);
iddr->movePortTo(id_Q0, &ci, id_IN1);
iddr->movePortTo(id_Q1, &ci, id_IN2);
set_in_clk(iddr, &ci);
bool invert = bool_or_default(iddr->params, id_CLK_INV, 0);
if (invert) {
ci.params[id_INV_IN1_CLOCK] = Property(Property::State::S1);
} else {
ci.params[id_INV_IN2_CLOCK] = Property(Property::State::S1);
}
return false;
};
for (auto &cell : cells) {
CellInfo &ci = *cell;
bool ff_obf = bool_or_default(ci.params, id_FF_OBF, 0);
bool ff_ibf = bool_or_default(ci.params, id_FF_IBF, 0);
ci.unsetParam(id_FF_OBF);
ci.unsetParam(id_FF_IBF);
if (ci.getPort(id_T)) {
ci.params[id_OE_SIGNAL] = Property(0b10, 2);
ci.renamePort(id_T, id_OUT3);
}
ci.cluster = ci.name;
std::string loc = str_or_default(ci.params, id_LOC, "UNPLACED");
ci.unsetParam(id_LOC);
NetInfo *do_net = ci.getPort(id_A);
bool use_custom_clock = false;
if (do_net) {
if (do_net->name.in(ctx->id("$PACKER_GND"), ctx->id("$PACKER_VCC"))) {
ci.params[id_OUT23_14_SEL] =
Property(do_net->name == ctx->id("$PACKER_VCC") ? Property::State::S1 : Property::State::S0);
ci.disconnectPort(id_A);
} else {
ci.params[id_OUT_SIGNAL] = Property(Property::State::S1);
bool ff_obf_merged = false;
if (ff_obf && do_net->driver.cell->type == id_CC_DFF && do_net->users.entries() == 1) {
CellInfo *dff = do_net->driver.cell;
if (is_gpio_valid_dff(dff)) {
ci.params[id_OUT1_FF] = Property(Property::State::S1);
packed_cells.emplace(dff->name);
ci.disconnectPort(id_A);
dff->movePortTo(id_D, &ci, id_OUT1);
use_custom_clock = set_out_clk(dff, &ci);
bool invert = bool_or_default(dff->params, id_CLK_INV, 0);
if (invert) {
ci.params[id_INV_OUT1_CLOCK] = Property(Property::State::S1);
ci.params[id_INV_OUT2_CLOCK] = Property(Property::State::S1);
}
ff_obf_merged = true;
} else {
log_warning("DFF '%s' cell for IO '%s', but unable to merge.\n", dff->name.c_str(ctx),
ci.name.c_str(ctx));
}
}
bool oddr_merged = false;
if (do_net->driver.cell->type == id_CC_ODDR && do_net->users.entries() == 1) {
CellInfo *oddr = do_net->driver.cell;
ci.params[id_OUT1_FF] = Property(Property::State::S1);
ci.params[id_OUT2_FF] = Property(Property::State::S1);
ci.params[id_USE_DDR] = Property(Property::State::S1);
packed_cells.emplace(oddr->name);
ci.disconnectPort(id_A);
// TODO: check mapping
oddr->movePortTo(id_D0, &ci, id_OUT2);
oddr->movePortTo(id_D1, &ci, id_OUT1);
const auto &pad = ctx->get_package_pin(ctx->id(loc));
CellInfo *cpe_half = ddr[pad->pad_bank];
if (cpe_half) {
if (cpe_half->getPort(id_IN1) != oddr->getPort(id_DDR))
log_error("DDR port use signal different than already occupied DDR source.\n");
ci.ports[id_DDR].name = id_DDR;
ci.ports[id_DDR].type = PORT_IN;
ci.connectPort(id_DDR, cpe_half->getPort(id_RAM_O));
} else {
oddr->movePortTo(id_DDR, &ci, id_DDR);
cpe_half = move_ram_o(&ci, id_DDR, false);
ctx->bindBel(get_bank_cpe(pad->pad_bank), cpe_half, PlaceStrength::STRENGTH_FIXED);
ddr[pad->pad_bank] = cpe_half;
}
use_custom_clock = set_out_clk(oddr, &ci);
bool invert = bool_or_default(oddr->params, id_CLK_INV, 0);
if (invert) {
ci.params[id_INV_OUT1_CLOCK] = Property(Property::State::S1);
} else {
ci.params[id_INV_OUT2_CLOCK] = Property(Property::State::S1);
}
oddr_merged = true;
}
if (!ff_obf_merged && !oddr_merged)
ci.renamePort(id_A, id_OUT1);
}
}
NetInfo *di_net = ci.getPort(id_Y);
if (di_net) {
bool ff_ibf_merged = false;
if (ff_ibf && di_net->users.entries() == 1 && (*di_net->users.begin()).cell->type == id_CC_DFF) {
ff_ibf_merged = merge_ibf(di_net, ci, use_custom_clock);
}
bool iddr_merged = false;
if (di_net->users.entries() == 1 && (*di_net->users.begin()).cell->type == id_CC_IDDR) {
iddr_merged = merge_iddr(di_net, ci, use_custom_clock);
}
if (!ff_ibf_merged && !iddr_merged)
ci.renamePort(id_Y, id_DI);
}
Loc root_loc = ctx->getBelLocation(ci.bel);
for (int i = 0; i < 4; i++) {
move_ram_o_fixed(&ci, ctx->idf("OUT%d", i + 1), root_loc);
}
}
flush_cells();
}
bool GateMatePacker::is_gpio_valid_dff(CellInfo *dff)
{
NetInfo *en_net = dff->getPort(id_EN);
bool invert = bool_or_default(dff->params, id_EN_INV, 0);
if (en_net) {
if (en_net->name == ctx->id("$PACKER_GND")) {
if (!invert)
return false;
dff->disconnectPort(id_EN);
} else if (en_net->name == ctx->id("$PACKER_VCC")) {
if (invert)
return false;
dff->disconnectPort(id_EN);
} else {
return false;
}
}
dff->unsetParam(id_EN_INV);
NetInfo *sr_net = dff->getPort(id_SR);
invert = bool_or_default(dff->params, id_SR_INV, 0);
if (sr_net) {
if (sr_net->name.in(ctx->id("$PACKER_GND"), ctx->id("$PACKER_VCC"))) {
bool sr_signal = sr_net->name == ctx->id("$PACKER_VCC");
if (sr_signal ^ invert)
log_error("Currently unsupported DFF configuration for '%s'\n.", dff->name.c_str(ctx));
dff->disconnectPort(id_SR);
} else {
return false;
}
}
dff->unsetParam(id_SR_VAL);
dff->unsetParam(id_SR_INV);
// Sanity check for CLK signal, that it must exist
NetInfo *clk_net = dff->getPort(id_CLK);
if (clk_net) {
if (clk_net->name == ctx->id("$PACKER_GND")) {
return false;
} else if (clk_net->name == ctx->id("$PACKER_VCC")) {
return false;
}
} else {
return false;
}
return true;
}
void GateMatePacker::dff_to_cpe(CellInfo *dff, CellInfo *cpe)
{
bool invert;
bool is_latch = dff->type == id_CC_DLT;
if (is_latch) {
NetInfo *g_net = cpe->getPort(id_G);
invert = bool_or_default(dff->params, id_G_INV, 0);
if (g_net) {
if (g_net->name == ctx->id("$PACKER_GND")) {
cpe->params[id_C_CPE_CLK] = Property(invert ? 0b11 : 0b00, 2);
cpe->disconnectPort(id_G);
} else if (g_net->name == ctx->id("$PACKER_VCC")) {
cpe->params[id_C_CPE_CLK] = Property(invert ? 0b00 : 0b11, 2);
cpe->disconnectPort(id_G);
} else {
cpe->params[id_C_CPE_CLK] = Property(invert ? 0b01 : 0b10, 2);
}
} else {
cpe->params[id_C_CPE_CLK] = Property(invert ? 0b11 : 0b00, 2);
}
dff->unsetParam(id_G_INV);
cpe->renamePort(id_G, id_CLK);
cpe->params[id_C_CPE_EN] = Property(0b11, 2);
cpe->params[id_C_L_D] = Property(0b1, 1);
} else {
NetInfo *en_net = cpe->getPort(id_EN);
bool invert = bool_or_default(dff->params, id_EN_INV, 0);
if (en_net) {
if (en_net->name == ctx->id("$PACKER_GND")) {
cpe->params[id_C_CPE_EN] = Property(invert ? 0b11 : 0b00, 2);
cpe->disconnectPort(id_EN);
} else if (en_net->name == ctx->id("$PACKER_VCC")) {
cpe->params[id_C_CPE_EN] = Property(invert ? 0b00 : 0b11, 2);
cpe->disconnectPort(id_EN);
} else {
cpe->params[id_C_CPE_EN] = Property(invert ? 0b01 : 0b10, 2);
}
} else {
cpe->params[id_C_CPE_EN] = Property(invert ? 0b11 : 0b00, 2);
}
dff->unsetParam(id_EN_INV);
NetInfo *clk_net = cpe->getPort(id_CLK);
invert = bool_or_default(dff->params, id_CLK_INV, 0);
if (clk_net) {
if (clk_net->name == ctx->id("$PACKER_GND")) {
cpe->params[id_C_CPE_CLK] = Property(invert ? 0b11 : 0b00, 2);
cpe->disconnectPort(id_CLK);
} else if (clk_net->name == ctx->id("$PACKER_VCC")) {
cpe->params[id_C_CPE_CLK] = Property(invert ? 0b00 : 0b11, 2);
cpe->disconnectPort(id_CLK);
} else {
cpe->params[id_C_CPE_CLK] = Property(invert ? 0b01 : 0b10, 2);
}
} else {
cpe->params[id_C_CPE_CLK] = Property(invert ? 0b11 : 0b00, 2);
}
dff->unsetParam(id_CLK_INV);
}
NetInfo *sr_net = cpe->getPort(id_SR);
invert = bool_or_default(dff->params, id_SR_INV, 0);
bool sr_val = bool_or_default(dff->params, id_SR_VAL, 0);
if (sr_net) {
if (sr_net->name.in(ctx->id("$PACKER_GND"), ctx->id("$PACKER_VCC"))) {
bool sr_signal = sr_net->name == ctx->id("$PACKER_VCC");
if (sr_signal ^ invert)
log_error("Currently unsupported DFF configuration for '%s'\n.", dff->name.c_str(ctx));
cpe->params[id_C_CPE_RES] = Property(0b11, 2);
cpe->params[id_C_CPE_SET] = Property(0b11, 2);
cpe->disconnectPort(id_SR);
} else {
if (sr_val) {
cpe->params[id_C_CPE_RES] = Property(0b11, 2);
cpe->params[id_C_CPE_SET] = Property(invert ? 0b10 : 0b01, 2);
if (is_latch)
cpe->renamePort(id_SR, id_EN);
else
cpe->params[id_C_EN_SR] = Property(0b1, 1);
} else {
cpe->params[id_C_CPE_RES] = Property(invert ? 0b10 : 0b01, 2);
cpe->params[id_C_CPE_SET] = Property(0b11, 2);
}
}
} else {
cpe->params[id_C_CPE_RES] = Property(0b11, 2);
cpe->params[id_C_CPE_SET] = Property(0b11, 2);
}
dff->unsetParam(id_SR_VAL);
dff->unsetParam(id_SR_INV);
if (dff->params.count(id_INIT) && dff->params[id_INIT].is_fully_def()) {
bool init = bool_or_default(dff->params, id_INIT, 0);
if (init)
cpe->params[id_FF_INIT] = Property(0b11, 2);
else
cpe->params[id_FF_INIT] = Property(0b10, 2);
dff->unsetParam(id_INIT);
} else {
dff->unsetParam(id_INIT);
}
cpe->timing_index = ctx->get_cell_timing_idx(id_CPE_DFF);
cpe->params[id_C_O] = Property(0b00, 2);
}
void GateMatePacker::pack_cpe()
{
log_info("Packing CPEs..\n");
std::vector<CellInfo *> l2t5_list;
for (auto &cell : ctx->cells) {
CellInfo &ci = *cell.second;
if (!ci.type.in(id_CC_L2T4, id_CC_L2T5, id_CC_LUT2, id_CC_LUT1, id_CC_MX2))
continue;
if (ci.type == id_CC_L2T5) {
l2t5_list.push_back(&ci);
ci.renamePort(id_I0, id_IN1);
ci.renamePort(id_I1, id_IN2);
ci.renamePort(id_I2, id_IN3);
ci.renamePort(id_I3, id_IN4);
ci.renamePort(id_O, id_OUT);
ci.params[id_C_O] = Property(0b11, 2);
ci.type = id_CPE_HALF_L;
} else if (ci.type == id_CC_MX2) {
ci.params[id_C_O] = Property(0b11, 2);
ci.renamePort(id_D1, id_IN1);
NetInfo *sel = ci.getPort(id_S0);
ci.renamePort(id_S0, id_IN2);
ci.ports[id_IN3].name = id_IN3;
ci.ports[id_IN3].type = PORT_IN;
ci.connectPort(id_IN3, sel);
ci.renamePort(id_D0, id_IN4);
ci.disconnectPort(id_D1);
ci.params[id_INIT_L00] = Property(0b1000, 4); // AND
ci.params[id_INIT_L01] = Property(0b0100, 4); // AND inv D0
ci.params[id_INIT_L10] = Property(0b1110, 4); // OR
ci.renamePort(id_Y, id_OUT);
ci.type = id_CPE_HALF;
} else {
ci.renamePort(id_I0, id_IN1);
ci.renamePort(id_I1, id_IN2);
ci.renamePort(id_I2, id_IN3);
ci.renamePort(id_I3, id_IN4);
ci.renamePort(id_O, id_OUT);
ci.params[id_C_O] = Property(0b11, 2);
if (ci.type.in(id_CC_LUT1, id_CC_LUT2)) {
uint8_t val = int_or_default(ci.params, id_INIT, 0);
if (ci.type == id_CC_LUT1)
val = val << 2 | val;
ci.params[id_INIT_L00] = Property(val, 4);
ci.unsetParam(id_INIT);
ci.params[id_INIT_L10] = Property(0b1010, 4);
}
ci.type = id_CPE_HALF;
}
NetInfo *o = ci.getPort(id_OUT);
if (o) {
CellInfo *dff = net_only_drives(ctx, o, is_dff, id_D, true);
if (dff) {
if (dff->type == id_CC_DLT) {
dff->movePortTo(id_G, &ci, id_G);
} else {
dff->movePortTo(id_EN, &ci, id_EN);
dff->movePortTo(id_CLK, &ci, id_CLK);
}
dff->movePortTo(id_SR, &ci, id_SR);
dff->disconnectPort(id_D);
ci.disconnectPort(id_OUT);
dff->movePortTo(id_Q, &ci, id_OUT);
dff_to_cpe(dff, &ci);
packed_cells.insert(dff->name);
}
}
}
for (auto ci : l2t5_list) {
CellInfo *upper = create_cell_ptr(id_CPE_HALF_U, ctx->idf("%s$upper", ci->name.c_str(ctx)));
upper->cluster = ci->name;
upper->constr_abs_z = false;
upper->constr_z = -1;
ci->cluster = ci->name;
ci->movePortTo(id_I4, upper, id_IN1);
upper->params[id_INIT_L00] = Property(0b1010, 4);
upper->params[id_INIT_L10] = Property(0b1010, 4);
ci->constr_children.push_back(upper);
}
l2t5_list.clear();
flush_cells();
std::vector<CellInfo *> mux_list;
for (auto &cell : ctx->cells) {
CellInfo &ci = *cell.second;
if (!ci.type.in(id_CC_MX4))
continue;
mux_list.push_back(&ci);
}
for (auto &cell : mux_list) {
CellInfo &ci = *cell;
ci.cluster = ci.name;
ci.renamePort(id_Y, id_OUT);
ci.renamePort(id_S0, id_IN2); // IN6
ci.renamePort(id_S1, id_IN4); // IN8
uint8_t select = 0;
uint8_t invert = 0;
for (int i = 0; i < 4; i++) {
NetInfo *net = ci.getPort(ctx->idf("D%d", i));
if (net) {
if (net->name.in(ctx->id("$PACKER_GND"), ctx->id("$PACKER_VCC"))) {
if (net->name == ctx->id("$PACKER_VCC"))
invert |= 1 << i;
ci.disconnectPort(ctx->idf("D%d", i));
} else {
select |= 1 << i;
}
}
}
ci.params[id_C_FUNCTION] = Property(C_MX4, 3);
ci.params[id_INIT_L02] = Property(0b1100, 4); // IN6
ci.params[id_INIT_L03] = Property(0b1100, 4); // IN8
ci.params[id_INIT_L11] = Property(invert, 4); // Inversion bits
// ci.params[id_INIT_L20] = Property(0b1100, 4); // Always D1
ci.params[id_C_O] = Property(0b11, 2);
ci.type = id_CPE_HALF_L;
CellInfo *upper = create_cell_ptr(id_CPE_HALF_U, ctx->idf("%s$upper", ci.name.c_str(ctx)));
upper->cluster = ci.name;
upper->constr_abs_z = false;
upper->constr_z = -1;
upper->params[id_INIT_L10] = Property(select, 4); // Selection bits
upper->params[id_C_FUNCTION] = Property(C_MX4, 3);
ci.movePortTo(id_D0, upper, id_IN1);
ci.movePortTo(id_D1, upper, id_IN2);
ci.movePortTo(id_D2, upper, id_IN3);
ci.movePortTo(id_D3, upper, id_IN4);
ci.constr_children.push_back(upper);
}
mux_list.clear();
for (auto &cell : ctx->cells) {
CellInfo &ci = *cell.second;
if (!ci.type.in(id_CC_DFF, id_CC_DLT))
continue;
ci.renamePort(id_Q, id_OUT);
NetInfo *d_net = ci.getPort(id_D);
if (d_net->name == ctx->id("$PACKER_GND")) {
ci.params[id_INIT_L00] = Property(0b0000, 4);
ci.disconnectPort(id_D);
} else if (d_net->name == ctx->id("$PACKER_VCC")) {
ci.params[id_INIT_L00] = Property(0b1111, 4);
ci.disconnectPort(id_D);
} else {
ci.params[id_INIT_L00] = Property(0b1010, 4);
}
ci.params[id_INIT_L10] = Property(0b1010, 4);
ci.renamePort(id_D, id_IN1);
dff_to_cpe(&ci, &ci);
ci.type = id_CPE_HALF;
}
}
static bool is_addf_ci(NetInfo *net)
{
return net && net->users.entries() == 1 && (*net->users.begin()).cell->type == id_CC_ADDF &&
(*net->users.begin()).port == id_CI;
}
void GateMatePacker::pack_addf()
{
log_info("Packing ADDFs..\n");
std::vector<CellInfo *> root_cys;
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (ci->type != id_CC_ADDF)
continue;
NetInfo *ci_net = ci->getPort(id_CI);
if (!ci_net || !ci_net->driver.cell ||
!(ci_net->driver.cell->type == id_CC_ADDF && ci_net->driver.port == id_CO)) {
root_cys.push_back(ci);
}
}
std::vector<std::vector<CellInfo *>> groups;
for (auto root : root_cys) {
std::vector<CellInfo *> group;
CellInfo *cy = root;
group.push_back(cy);
while (true) {
NetInfo *co_net = cy->getPort(id_CO);
if (co_net) {
bool found = false;
for (auto &usr : co_net->users) {
if (usr.cell->type == id_CC_ADDF && usr.port == id_CI) {
if (found)
log_error("Only one other ADDF can be connected.\n");
cy = usr.cell;
group.push_back(cy);
found = true;
}
}
if (!found)
break;
} else
break;
}
groups.push_back(group);
}
// Merge two ADDF cells to one CPE when possible
// use artificial CC_ADDF2 cell for that
for (size_t i = 0; i < groups.size(); i++) {
std::vector<CellInfo *> regrouped;
size_t pos = 0;
auto &grp = groups.at(i);
while (pos < grp.size()) {
bool merged = false;
CellInfo *cy = grp.at(pos);
NetInfo *co_net = cy->getPort(id_CO);
bool last = pos + 1 == grp.size();
if (!last && is_addf_ci(co_net)) {
CellInfo *cy2 = grp.at(pos + 1);
co_net = cy2->getPort(id_CO);
last = pos + 2 == grp.size();
if (!co_net || last || is_addf_ci(co_net)) {
cy2->type = id_CC_ADDF2;
cy2->disconnectPort(id_CI);
// Do actual merge of cells
cy->movePortTo(id_A, cy2, id_A2);
cy->movePortTo(id_B, cy2, id_B2);
cy->movePortTo(id_S, cy2, id_S2);
cy->disconnectPort(id_CO);
cy->movePortTo(id_CI, cy2, id_CI);
packed_cells.insert(cy->name);
regrouped.push_back(cy2);
merged = true;
pos++;
}
}
if (!merged)
regrouped.push_back(cy);
pos++;
}
grp = regrouped;
}
flush_cells();
for (auto &grp : splitNestedVector(groups)) {
CellInfo *root = grp.front();
root->cluster = root->name;
CellInfo *ci_upper = create_cell_ptr(id_CPE_HALF_U, ctx->idf("%s$ci_upper", root->name.c_str(ctx)));
root->constr_children.push_back(ci_upper);
ci_upper->cluster = root->name;
ci_upper->constr_abs_z = false;
ci_upper->constr_z = -1;
ci_upper->constr_y = -1;
CellInfo *ci_lower = create_cell_ptr(id_CPE_HALF_L, ctx->idf("%s$ci_lower", root->name.c_str(ctx)));
root->constr_children.push_back(ci_lower);
ci_lower->cluster = root->name;
ci_lower->constr_abs_z = false;
ci_lower->constr_y = -1;
ci_lower->params[id_C_O] = Property(0b11, 2);
ci_lower->params[id_C_SELY1] = Property(1, 1);
ci_lower->params[id_C_CY1_I] = Property(1, 1);
ci_lower->params[id_INIT_L10] = Property(0b1010, 4); // D0
NetInfo *ci_net = root->getPort(id_CI);
if (ci_net->name == ctx->id("$PACKER_GND")) {
ci_lower->params[id_INIT_L00] = Property(0b0000, 4);
root->disconnectPort(id_CI);
} else if (ci_net->name == ctx->id("$PACKER_VCC")) {
ci_lower->params[id_INIT_L00] = Property(0b1111, 4);
root->disconnectPort(id_CI);
} else {
root->movePortTo(id_CI, ci_lower, id_IN1);
ci_lower->params[id_INIT_L00] = Property(0b1010, 4); // IN5
}
NetInfo *ci_conn = ctx->createNet(ctx->idf("%s$ci", root->name.c_str(ctx)));
ci_lower->connectPort(id_COUTY1, ci_conn);
root->ports[id_CINY1].name = id_CINY1;
root->ports[id_CINY1].type = PORT_IN;
root->connectPort(id_CINY1, ci_conn);
for (size_t i = 0; i < grp.size(); i++) {
CellInfo *cy = grp.at(i);
if (i != 0) {
cy->cluster = root->name;
root->constr_children.push_back(cy);
cy->constr_abs_z = false;
cy->constr_y = +i;
cy->renamePort(id_CI, id_CINY1);
}
bool merged = cy->type != id_CC_ADDF;
if (merged) {
NetInfo *a_net = cy->getPort(id_A2);
if (a_net->name == ctx->id("$PACKER_GND")) {
cy->params[id_INIT_L02] = Property(0b0000, 4);
cy->disconnectPort(id_A2);
} else if (a_net->name == ctx->id("$PACKER_VCC")) {
cy->params[id_INIT_L02] = Property(0b1111, 4);
cy->disconnectPort(id_A2);
} else {
cy->renamePort(id_A2, id_IN1);
cy->params[id_INIT_L02] = Property(0b1010, 4); // IN1
}
NetInfo *b_net = cy->getPort(id_B2);
if (b_net->name == ctx->id("$PACKER_GND")) {
cy->params[id_INIT_L03] = Property(0b0000, 4);
cy->disconnectPort(id_B2);
} else if (b_net->name == ctx->id("$PACKER_VCC")) {
cy->params[id_INIT_L03] = Property(0b1111, 4);
cy->disconnectPort(id_B2);
} else {
cy->renamePort(id_B2, id_IN3);
cy->params[id_INIT_L03] = Property(0b1010, 4); // IN3
}
cy->params[id_INIT_L11] = Property(0b0110, 4); // XOR
cy->renamePort(id_S2, id_OUT);
} else {
cy->params[id_INIT_L02] = Property(0b0000, 4); // 0
cy->params[id_INIT_L03] = Property(0b0000, 4); // 0
cy->params[id_INIT_L11] = Property(0b0110, 4); // XOR
cy->params[id_INIT_L20] = Property(0b0110, 4); // XOR
}
cy->params[id_C_FUNCTION] = Property(merged ? C_ADDF2 : C_ADDF, 3);
cy->params[id_C_O] = Property(0b11, 2);
cy->type = id_CPE_HALF_L;
CellInfo *upper = create_cell_ptr(id_CPE_HALF_U, ctx->idf("%s$upper", cy->name.c_str(ctx)));
upper->cluster = root->name;
root->constr_children.push_back(upper);
upper->constr_abs_z = false;
upper->constr_y = +i;
upper->constr_z = -1;
if (merged) {
cy->movePortTo(id_S, upper, id_OUT);
upper->params[id_C_O] = Property(0b11, 2);
} else {
cy->renamePort(id_S, id_OUT);
}
NetInfo *a_net = cy->getPort(id_A);
if (a_net->name == ctx->id("$PACKER_GND")) {
upper->params[id_INIT_L00] = Property(0b0000, 4);
cy->disconnectPort(id_A);
} else if (a_net->name == ctx->id("$PACKER_VCC")) {
upper->params[id_INIT_L00] = Property(0b1111, 4);
cy->disconnectPort(id_A);
} else {
cy->movePortTo(id_A, upper, id_IN1);
upper->params[id_INIT_L00] = Property(0b1010, 4); // IN1
}
NetInfo *b_net = cy->getPort(id_B);
if (b_net->name == ctx->id("$PACKER_GND")) {
upper->params[id_INIT_L01] = Property(0b0000, 4);
cy->disconnectPort(id_B);
} else if (b_net->name == ctx->id("$PACKER_VCC")) {
upper->params[id_INIT_L01] = Property(0b1111, 4);
cy->disconnectPort(id_B);
} else {
cy->movePortTo(id_B, upper, id_IN3);
upper->params[id_INIT_L01] = Property(0b1010, 4); // IN3
}
upper->params[id_INIT_L10] = Property(0b0110, 4); // XOR
upper->params[id_C_FUNCTION] = Property(merged ? C_ADDF2 : C_ADDF, 3);
if (i == grp.size() - 1) {
if (!cy->getPort(id_CO))
break;
CellInfo *co_upper = create_cell_ptr(id_CPE_HALF_U, ctx->idf("%s$co_upper", cy->name.c_str(ctx)));
co_upper->cluster = root->name;
root->constr_children.push_back(co_upper);
co_upper->constr_abs_z = false;
co_upper->constr_z = -1;
co_upper->constr_y = +i + 1;
CellInfo *co_lower = create_cell_ptr(id_CPE_HALF_L, ctx->idf("%s$co_lower", cy->name.c_str(ctx)));
co_lower->cluster = root->name;
root->constr_children.push_back(co_lower);
co_lower->constr_abs_z = false;
co_lower->constr_y = +i + 1;
co_lower->params[id_C_O] = Property(0b11, 2);
co_lower->params[id_C_FUNCTION] = Property(C_EN_CIN, 3);
co_lower->params[id_INIT_L11] = Property(0b1100, 4);
co_lower->params[id_INIT_L20] = Property(0b1100, 4);
NetInfo *co_conn = ctx->createNet(ctx->idf("%s$co", cy->name.c_str(ctx)));
co_lower->ports[id_CINY1].name = id_CINY1;
co_lower->ports[id_CINY1].type = PORT_IN;
co_lower->connectPort(id_CINY1, co_conn);
cy->ports[id_COUTY1].name = id_COUTY1;
cy->ports[id_COUTY1].type = PORT_OUT;
cy->connectPort(id_COUTY1, co_conn);
cy->movePortTo(id_CO, co_lower, id_OUT);
} else {
NetInfo *co_net = cy->getPort(id_CO);
if (!co_net || co_net->users.entries() == 1) {
cy->renamePort(id_CO, id_COUTY1);
} else {
for (auto &usr : co_net->users) {
if (usr.cell->type == id_CC_ADDF || usr.port == id_CI) {
usr.cell->disconnectPort(id_CI);
NetInfo *co_conn = ctx->createNet(ctx->idf("%s$co", cy->name.c_str(ctx)));
cy->ports[id_COUTY1].name = id_COUTY1;
cy->ports[id_COUTY1].type = PORT_OUT;
cy->connectPort(id_COUTY1, co_conn);
usr.cell->connectPort(id_CI, co_conn);
break;
}
}
upper->params[id_C_O] = Property(0b10, 2);
cy->movePortTo(id_CO, upper, id_OUT);
}
}
}
}
}
void GateMatePacker::sort_bufg()
{
struct ItemBufG
{
CellInfo *cell;
int32_t fan_out;
ItemBufG(CellInfo *cell, int32_t fan_out) : cell(cell), fan_out(fan_out) {}
};
std::vector<ItemBufG> bufg;
for (auto &cell : ctx->cells) {
CellInfo &ci = *cell.second;
if (!ci.type.in(id_CC_BUFG))
continue;
NetInfo *i_net = ci.getPort(id_I);
if (!i_net) {
log_warning("Removing BUFG cell %s since there is no input used.\n", ci.name.c_str(ctx));
packed_cells.emplace(ci.name); // Remove if no input
continue;
}
NetInfo *o_net = ci.getPort(id_O);
if (!o_net) {
log_warning("Removing BUFG cell %s since there is no output used.\n", ci.name.c_str(ctx));
packed_cells.emplace(ci.name); // Remove if no output
continue;
}
bufg.push_back(ItemBufG(&ci, o_net->users.entries()));
}
if (bufg.size() > 4) {
log_warning("More than 4 BUFG used. Those with highest fan-out will be used.\n");
std::sort(bufg.begin(), bufg.end(), [](const ItemBufG &a, const ItemBufG &b) { return a.fan_out > b.fan_out; });
for (size_t i = 4; i < bufg.size(); i++) {
log_warning("Removing BUFG cell %s.\n", bufg.at(i).cell->name.c_str(ctx));
CellInfo *cell = bufg.at(i).cell;
NetInfo *i_net = cell->getPort(id_I);
NetInfo *o_net = cell->getPort(id_O);
for (auto s : o_net->users) {
s.cell->disconnectPort(s.port);
s.cell->connectPort(s.port, i_net);
}
packed_cells.emplace(bufg.at(i).cell->name);
}
}
flush_cells();
}
void GateMatePacker::pack_bufg()
{
log_info("Packing BUFGs..\n");
CellInfo *bufg[4] = {nullptr};
for (auto &cell : ctx->cells) {
CellInfo &ci = *cell.second;
if (!ci.type.in(id_CC_BUFG))
continue;
NetInfo *in_net = ci.getPort(id_I);
if (in_net) {
bool is_cpe_source = true;
if (ctx->getBelBucketForCellType(in_net->driver.cell->type) == id_GPIO) {
auto pad_info = uarch->bel_to_pad[in_net->driver.cell->bel];
if (pad_info->flags)
is_cpe_source = false;
}
if (ctx->getBelBucketForCellType(in_net->driver.cell->type) == id_PLL) {
is_cpe_source = false;
int pll_index = in_net->driver.cell->constr_z - 4;
if (bufg[pll_index] == nullptr) {
bufg[pll_index] = &ci;
} else {
IdString origPort = in_net->driver.port;
int index = 0;
if (origPort == id_CLK90)
index = 1;
else if (origPort == id_CLK180)
index = 2;
else if (origPort == id_CLK270)
index = 3;
if (bufg[index] == nullptr) {
bufg[pll_index] = &ci;
} else {
log_error("Unable to place BUFG for PLL.\n");
}
}
}
if (is_cpe_source) {
ci.cluster = ci.name;
move_ram_o(&ci, id_I);
}
if (in_net->clkconstr) {
NetInfo *o_net = ci.getPort(id_O);
o_net->clkconstr = std::unique_ptr<ClockConstraint>(new ClockConstraint());
o_net->clkconstr->low = in_net->clkconstr->low;
o_net->clkconstr->high = in_net->clkconstr->high;
o_net->clkconstr->period = in_net->clkconstr->period;
}
}
ci.type = id_BUFG;
}
for (auto &cell : ctx->cells) {
CellInfo &ci = *cell.second;
if (!ci.type.in(id_BUFG))
continue;
NetInfo *in_net = ci.getPort(id_I);
if (in_net && ctx->getBelBucketForCellType(in_net->driver.cell->type) != id_PLL) {
for (int i = 0; i < 4; i++) {
if (bufg[i] == nullptr) {
bufg[i] = &ci;
break;
}
}
}
}
for (int i = 0; i < 4; i++) {
if (bufg[i]) {
CellInfo &ci = *bufg[i];
global_signals.emplace(ci.getPort(id_O), i);
ci.cluster = ci.name;
ci.constr_abs_z = true;
ci.constr_z = i;
}
}
}
CellInfo *GateMatePacker::move_ram_i(CellInfo *cell, IdString origPort, bool place)
{
CellInfo *cpe_half = nullptr;
NetInfo *net = cell->getPort(origPort);
if (net) {
cpe_half = create_cell_ptr(id_CPE_HALF, ctx->idf("%s$%s_cpe_half", cell->name.c_str(ctx), origPort.c_str(ctx)));
if (place) {
cell->constr_children.push_back(cpe_half);
cpe_half->cluster = cell->name;
cpe_half->constr_abs_z = false;
cpe_half->constr_z = PLACE_DB_CONSTR + origPort.index;
}
cpe_half->params[id_C_RAM_I] = Property(1, 1);
NetInfo *ram_i = ctx->createNet(ctx->idf("%s$ram_i", cpe_half->name.c_str(ctx)));
cell->movePortTo(origPort, cpe_half, id_OUT);
cell->connectPort(origPort, ram_i);
cpe_half->connectPort(id_RAM_I, ram_i);
}
return cpe_half;
}
CellInfo *GateMatePacker::move_ram_o(CellInfo *cell, IdString origPort, bool place)
{
CellInfo *cpe_half = nullptr;
NetInfo *net = cell->getPort(origPort);
if (net) {
cpe_half = create_cell_ptr(id_CPE_HALF, ctx->idf("%s$%s_cpe_half", cell->name.c_str(ctx), origPort.c_str(ctx)));
if (place) {
cell->constr_children.push_back(cpe_half);
cpe_half->cluster = cell->name;
cpe_half->constr_abs_z = false;
cpe_half->constr_z = PLACE_DB_CONSTR + origPort.index;
}
if (net->name == ctx->id("$PACKER_GND")) {
cpe_half->params[id_INIT_L00] = Property(0b0000, 4);
cell->disconnectPort(origPort);
} else if (net->name == ctx->id("$PACKER_VCC")) {
cpe_half->params[id_INIT_L00] = Property(0b1111, 4);
cell->disconnectPort(origPort);
} else {
cpe_half->params[id_INIT_L00] = Property(0b1010, 4);
cell->movePortTo(origPort, cpe_half, id_IN1);
}
cpe_half->params[id_INIT_L10] = Property(0b1010, 4);
cpe_half->params[id_C_O] = Property(0b11, 2);
cpe_half->params[id_C_RAM_O] = Property(1, 1);
NetInfo *ram_o = ctx->createNet(ctx->idf("%s$ram_o", cpe_half->name.c_str(ctx)));
cell->connectPort(origPort, ram_o);
cpe_half->connectPort(id_RAM_O, ram_o);
}
return cpe_half;
}
CellInfo *GateMatePacker::move_ram_i_fixed(CellInfo *cell, IdString origPort, Loc fixed)
{
CellInfo *cpe = move_ram_i(cell, origPort, false);
if (cpe) {
BelId b = ctx->getBelByLocation(uarch->getRelativeConstraint(fixed, origPort));
ctx->bindBel(b, cpe, PlaceStrength::STRENGTH_FIXED);
}
return cpe;
}
CellInfo *GateMatePacker::move_ram_o_fixed(CellInfo *cell, IdString origPort, Loc fixed)
{
CellInfo *cpe = move_ram_o(cell, origPort, false);
if (cpe) {
BelId b = ctx->getBelByLocation(uarch->getRelativeConstraint(fixed, origPort));
ctx->bindBel(b, cpe, PlaceStrength::STRENGTH_FIXED);
}
return cpe;
}
CellInfo *GateMatePacker::move_ram_io(CellInfo *cell, IdString iPort, IdString oPort, bool place)
{
CellInfo *cpe_half = nullptr;
NetInfo *i_net = cell->getPort(iPort);
NetInfo *o_net = cell->getPort(oPort);
if (!i_net && !o_net)
return cpe_half;
cpe_half = create_cell_ptr(id_CPE_HALF, ctx->idf("%s$%s_cpe_half", cell->name.c_str(ctx), oPort.c_str(ctx)));
if (place) {
cell->constr_children.push_back(cpe_half);
cpe_half->cluster = cell->name;
cpe_half->constr_abs_z = false;
cpe_half->constr_z = PLACE_DB_CONSTR + oPort.index;
}
if (o_net) {
if (o_net->name == ctx->id("$PACKER_GND")) {
cpe_half->params[id_INIT_L00] = Property(0b0000, 4);
cell->disconnectPort(oPort);
} else if (o_net->name == ctx->id("$PACKER_VCC")) {
cpe_half->params[id_INIT_L00] = Property(0b1111, 4);
cell->disconnectPort(oPort);
} else {
cpe_half->params[id_INIT_L00] = Property(0b1010, 4);
cell->movePortTo(oPort, cpe_half, id_IN1);
}
cpe_half->params[id_INIT_L10] = Property(0b1010, 4);
cpe_half->params[id_C_O] = Property(0b11, 2);
cpe_half->params[id_C_RAM_O] = Property(1, 1);
NetInfo *ram_o = ctx->createNet(ctx->idf("%s$ram_o", cpe_half->name.c_str(ctx)));
cell->connectPort(oPort, ram_o);
cpe_half->connectPort(id_RAM_O, ram_o);
}
if (i_net) {
cpe_half->params[id_C_RAM_I] = Property(1, 1);
NetInfo *ram_i = ctx->createNet(ctx->idf("%s$ram_i", cpe_half->name.c_str(ctx)));
cell->movePortTo(iPort, cpe_half, id_OUT);
cell->connectPort(iPort, ram_i);
cpe_half->connectPort(id_RAM_I, ram_i);
}
// TODO: set proper timing model, without this it detects combinational loops
cpe_half->timing_index = ctx->get_cell_timing_idx(id_CPE_DFF);
return cpe_half;
}
void GateMatePacker::pll_out(CellInfo *cell, IdString origPort, Loc fixed)
{
NetInfo *net = cell->getPort(origPort);
if (!net)
return;
CellInfo *bufg = nullptr;
for (auto &usr : net->users) {
if (usr.cell->type == id_CC_BUFG)
bufg = usr.cell;
}
if (bufg) {
if (net->users.entries() != 1) {
log_error("not handled BUFG\n");
}
} else {
move_ram_i_fixed(cell, origPort, fixed);
}
}
void GateMatePacker::insert_bufg(CellInfo *cell, IdString port)
{
NetInfo *clk = cell->getPort(port);
if (clk) {
if (!(clk->users.entries() == 1 && (*clk->users.begin()).cell->type == id_CC_BUFG)) {
CellInfo *bufg =
create_cell_ptr(id_CC_BUFG, ctx->idf("%s$BUFG_%s", cell->name.c_str(ctx), port.c_str(ctx)));
cell->movePortTo(port, bufg, id_O);
cell->ports[port].name = port;
cell->ports[port].type = PORT_OUT;
NetInfo *net = ctx->createNet(ctx->idf("%s", bufg->name.c_str(ctx)));
cell->connectPort(port, net);
bufg->connectPort(id_I, net);
log_info("Added BUFG for cell '%s' signal %s\n", cell->name.c_str(ctx), port.c_str(ctx));
}
}
}
void GateMatePacker::insert_pll_bufg()
{
std::vector<CellInfo *> cells;
for (auto &cell : ctx->cells) {
CellInfo &ci = *cell.second;
if (!ci.type.in(id_CC_PLL, id_CC_PLL_ADV))
continue;
cells.push_back(&ci);
}
for (auto &cell : cells) {
insert_bufg(cell, id_CLK0);
insert_bufg(cell, id_CLK90);
insert_bufg(cell, id_CLK180);
insert_bufg(cell, id_CLK270);
}
}
void GateMatePacker::pack_pll()
{
int pll_index = 0;
log_info("Packing PLLss..\n");
for (auto &cell : ctx->cells) {
CellInfo &ci = *cell.second;
if (!ci.type.in(id_CC_PLL, id_CC_PLL_ADV))
continue;
disconnect_if_gnd(&ci, id_CLK_REF);
disconnect_if_gnd(&ci, id_USR_CLK_REF);
disconnect_if_gnd(&ci, id_CLK_FEEDBACK);
disconnect_if_gnd(&ci, id_USR_LOCKED_STDY_RST);
ci.cluster = ci.name;
ci.constr_abs_z = true;
ci.constr_z = 4 + pll_index; // Position to a proper Z location
Loc fixed_loc(33 + 2, 131 + 2, 4 + pll_index); // PLL
BelId pll_bel = ctx->getBelByLocation(fixed_loc);
ctx->bindBel(pll_bel, &ci, PlaceStrength::STRENGTH_FIXED);
if (pll_index > 4)
log_error("Used more than available PLLs.\n");
if (ci.getPort(id_CLK_REF) == nullptr && ci.getPort(id_USR_CLK_REF) == nullptr)
log_error("At least one reference clock (CLK_REF or USR_CLK_REF) must be set.\n");
if (ci.getPort(id_CLK_REF) != nullptr && ci.getPort(id_USR_CLK_REF) != nullptr)
log_error("CLK_REF and USR_CLK_REF are not allowed to be set in same time.\n");
NetInfo *clk = ci.getPort(id_CLK_REF);
delay_t period = ctx->getDelayFromNS(1.0e9 / ctx->setting<float>("target_freq"));
if (clk) {
if (ctx->getBelBucketForCellType(clk->driver.cell->type) != id_GPIO)
log_error("CLK_REF must be driven with GPIO pin.\n");
auto pad_info = uarch->bel_to_pad[clk->driver.cell->bel];
if (!(pad_info->flags & 1))
log_error("CLK_REF must be driven with CLK dedicated pin.\n");
if (clk->clkconstr)
period = clk->clkconstr->period.minDelay();
}
clk = ci.getPort(id_USR_CLK_REF);
if (clk) {
move_ram_o_fixed(&ci, id_USR_CLK_REF, fixed_loc);
if (clk->clkconstr)
period = clk->clkconstr->period.minDelay();
}
// TODO: handle CLK_FEEDBACK
// TODO: handle CLK_REF_OUT
pll_out(&ci, id_CLK0, fixed_loc);
pll_out(&ci, id_CLK90, fixed_loc);
pll_out(&ci, id_CLK180, fixed_loc);
pll_out(&ci, id_CLK270, fixed_loc);
move_ram_i_fixed(&ci, id_USR_PLL_LOCKED, fixed_loc);
move_ram_i_fixed(&ci, id_USR_PLL_LOCKED_STDY, fixed_loc);
double out_clk_max = 0;
int clk270_doub = 0;
int clk180_doub = 0;
if (ci.type == id_CC_PLL) {
int low_jitter = int_or_default(ci.params, id_LOW_JITTER, 0);
int ci_const = int_or_default(ci.params, id_CI_FILTER_CONST, 0);
int cp_const = int_or_default(ci.params, id_CP_FILTER_CONST, 0);
clk270_doub = int_or_default(ci.params, id_CLK270_DOUB, 0);
clk180_doub = int_or_default(ci.params, id_CLK180_DOUB, 0);
int lock_req = int_or_default(ci.params, id_LOCK_REQ, 0);
if (!ci.getPort(id_CLK_FEEDBACK))
ci.params[id_LOCK_REQ] = Property(lock_req, 1);
ci.params[id_CLK180_DOUB] = Property(clk180_doub, 1);
ci.params[id_CLK270_DOUB] = Property(clk270_doub, 1);
std::string mode = str_or_default(ci.params, id_PERF_MD, "SPEED");
boost::algorithm::to_upper(mode);
int perf_md;
double max_freq = 0.0;
if (mode == "LOWPOWER") {
perf_md = 1;
max_freq = 250.00;
} else if (mode == "ECONOMY") {
perf_md = 2;
max_freq = 312.50;
} else if (mode == "SPEED") {
perf_md = 3;
max_freq = 416.75;
} else {
log_error("Unknown PERF_MD parameter value '%s' for cell %s.\n", mode.c_str(), ci.name.c_str(ctx));
}
double ref_clk = double_or_default(ci.params, id_REF_CLK, 0.0);
if (ref_clk <= 0 || ref_clk > 125)
log_error("REF_CLK parameter is out of range (0,125.00].\n");
double out_clk = double_or_default(ci.params, id_OUT_CLK, 0.0);
if (out_clk <= 0 || out_clk > max_freq)
log_error("OUT_CLK parameter is out of range (0,%.2lf].\n", max_freq);
if ((ci_const < 1) || (ci_const > 31)) {
log_warning("CI const out of range. Set to default CI = 2\n");
ci_const = 2;
}
if ((cp_const < 1) || (cp_const > 31)) {
log_warning("CP const out of range. Set to default CP = 4\n");
cp_const = 4;
}
// PLL_cfg_val_800_1400 PLL values from 11.08.2021
bool feedback = false;
if (ci.getPort(id_CLK_FEEDBACK)) {
ci.params[ctx->id("CFG_A.FB_PATH")] = Property(0b1, 1);
feedback = true;
}
ci.params[ctx->id("CFG_A.FINE_TUNE")] = Property(0b00011001000, 11);
ci.params[ctx->id("CFG_A.COARSE_TUNE")] = Property(0b100, 3);
ci.params[ctx->id("CFG_A.AO_SW")] = Property(0b01000, 5);
ci.params[ctx->id("CFG_A.OPEN_LOOP")] = Property(0b0, 1);
ci.params[ctx->id("CFG_A.ENFORCE_LOCK")] = Property(0b0, 1);
ci.params[ctx->id("CFG_A.PFD_SEL")] = Property(0b0, 1);
ci.params[ctx->id("CFG_A.LOCK_DETECT_WIN")] = Property(0b0, 1);
ci.params[ctx->id("CFG_A.SYNC_BYPASS")] = Property(0b0, 1);
ci.params[ctx->id("CFG_A.FILTER_SHIFT")] = Property(0b10, 2);
ci.params[ctx->id("CFG_A.FAST_LOCK")] = Property(0b1, 1);
ci.params[ctx->id("CFG_A.SAR_LIMIT")] = Property(0b010, 3);
ci.params[ctx->id("CFG_A.OP_LOCK")] = Property(0b0, 1);
ci.params[ctx->id("CFG_A.PDIV0_MUX")] = Property(0b1, 1);
ci.params[ctx->id("CFG_A.EN_COARSE_TUNE")] = Property(0b1, 1);
ci.params[ctx->id("CFG_A.EN_USR_CFG")] = Property(0b0, 1);
ci.params[ctx->id("CFG_A.PLL_EN_SEL")] = Property(0b0, 1);
ci.params[ctx->id("CFG_A.CI_FILTER_CONST")] = Property(ci_const, 5);
ci.params[ctx->id("CFG_A.CP_FILTER_CONST")] = Property(cp_const, 5);
/*
clock path selection
0-0 PDIV0_MUX = 0, FB_PATH = 0 // DCO clock with intern feedback
1-0 PDIV0_MUX = 1, FB_PATH = 0 // divided clock: PDIV1->M1->M2 with intern feedback DEFAULT
0-1 not possible f_core = f_ref will set PDIV0_MUX = 1
1-1 PDIV0_MUX = 1, FB_PATH = 1 // divided clock: PDIV1->M1->M2 with extern feedback
PDIV1->M1->M2->PDIV0->N1->N2 }
*/
bool pdiv0_mux = true;
PllCfgRecord val = get_pll_settings(ref_clk, out_clk, perf_md, low_jitter, pdiv0_mux, feedback);
if (val.f_core > 0) { // cfg exists
ci.params[ctx->id("CFG_A.K")] = Property(val.K, 12);
ci.params[ctx->id("CFG_A.N1")] = Property(val.N1, 6);
ci.params[ctx->id("CFG_A.N2")] = Property(val.N2, 10);
ci.params[ctx->id("CFG_A.M1")] = Property(val.M1, 6);
ci.params[ctx->id("CFG_A.M2")] = Property(val.M2, 10);
ci.params[ctx->id("CFG_A.PDIV1_SEL")] = Property(val.PDIV1 == 2 ? 1 : 0, 1);
} else {
log_error("Unable to configure PLL %s\n", ci.name.c_str(ctx));
}
// Remove all not propagated parameters
ci.unsetParam(id_PERF_MD);
ci.unsetParam(id_REF_CLK);
ci.unsetParam(id_OUT_CLK);
ci.unsetParam(id_LOW_JITTER);
ci.unsetParam(id_CI_FILTER_CONST);
ci.unsetParam(id_CP_FILTER_CONST);
out_clk_max = out_clk;
} else {
// Handling CC_PLL_ADV
for (int i = 0; i < 2; i++) {
char cfg = 'A' + i;
IdString id = i == 0 ? id_PLL_CFG_A : id_PLL_CFG_B;
ci.params[ctx->idf("CFG_%c.CI_FILTER_CONST", cfg)] = Property(extract_bits(ci.params, id, 0, 5), 5);
ci.params[ctx->idf("CFG_%c.CP_FILTER_CONST", cfg)] = Property(extract_bits(ci.params, id, 5, 5), 5);
ci.params[ctx->idf("CFG_%c.N1", cfg)] = Property(extract_bits(ci.params, id, 10, 6), 6);
ci.params[ctx->idf("CFG_%c.N2", cfg)] = Property(extract_bits(ci.params, id, 16, 10), 10);
ci.params[ctx->idf("CFG_%c.M1", cfg)] = Property(extract_bits(ci.params, id, 26, 6), 6);
ci.params[ctx->idf("CFG_%c.M2", cfg)] = Property(extract_bits(ci.params, id, 32, 10), 10);
ci.params[ctx->idf("CFG_%c.K", cfg)] = Property(extract_bits(ci.params, id, 42, 12), 12);
ci.params[ctx->idf("CFG_%c.FB_PATH", cfg)] = Property(extract_bits(ci.params, id, 54, 1), 1);
ci.params[ctx->idf("CFG_%c.FINE_TUNE", cfg)] = Property(extract_bits(ci.params, id, 55, 11), 11);
ci.params[ctx->idf("CFG_%c.COARSE_TUNE", cfg)] = Property(extract_bits(ci.params, id, 66, 3), 3);
ci.params[ctx->idf("CFG_%c.AO_SW", cfg)] = Property(extract_bits(ci.params, id, 69, 5), 5);
ci.params[ctx->idf("CFG_%c.OPEN_LOOP", cfg)] = Property(extract_bits(ci.params, id, 74, 1), 1);
ci.params[ctx->idf("CFG_%c.ENFORCE_LOCK", cfg)] = Property(extract_bits(ci.params, id, 75, 1), 1);
ci.params[ctx->idf("CFG_%c.PFD_SEL", cfg)] = Property(extract_bits(ci.params, id, 76, 1), 1);
ci.params[ctx->idf("CFG_%c.LOCK_DETECT_WIN", cfg)] = Property(extract_bits(ci.params, id, 77, 1), 1);
ci.params[ctx->idf("CFG_%c.SYNC_BYPASS", cfg)] = Property(extract_bits(ci.params, id, 78, 1), 1);
ci.params[ctx->idf("CFG_%c.FILTER_SHIFT", cfg)] = Property(extract_bits(ci.params, id, 79, 2), 2);
ci.params[ctx->idf("CFG_%c.FAST_LOCK", cfg)] = Property(extract_bits(ci.params, id, 81, 1), 1);
ci.params[ctx->idf("CFG_%c.SAR_LIMIT", cfg)] = Property(extract_bits(ci.params, id, 82, 3), 3);
ci.params[ctx->idf("CFG_%c.OP_LOCK", cfg)] = Property(extract_bits(ci.params, id, 85, 1), 1);
ci.params[ctx->idf("CFG_%c.PDIV1_SEL", cfg)] = Property(extract_bits(ci.params, id, 86, 1), 1);
ci.params[ctx->idf("CFG_%c.PDIV0_MUX", cfg)] = Property(extract_bits(ci.params, id, 87, 1), 1);
ci.params[ctx->idf("CFG_%c.EN_COARSE_TUNE", cfg)] = Property(extract_bits(ci.params, id, 88, 1), 1);
ci.params[ctx->idf("CFG_%c.EN_USR_CFG", cfg)] = Property(extract_bits(ci.params, id, 89, 1), 1);
ci.params[ctx->idf("CFG_%c.PLL_EN_SEL", cfg)] = Property(extract_bits(ci.params, id, 90, 1), 1);
int N1 = int_or_default(ci.params, ctx->idf("CFG_%c.N1", cfg));
int N2 = int_or_default(ci.params, ctx->idf("CFG_%c.N2", cfg));
int M1 = int_or_default(ci.params, ctx->idf("CFG_%c.M1", cfg));
int M2 = int_or_default(ci.params, ctx->idf("CFG_%c.M2", cfg));
int K = int_or_default(ci.params, ctx->idf("CFG_%c.K", cfg));
int PDIV1 = bool_or_default(ci.params, ctx->idf("CFG_%c.PDIV1_SEL", cfg)) ? 2 : 0;
double out_clk;
double ref_clk = 1000.0f / ctx->getDelayNS(period);
if (!bool_or_default(ci.params, ctx->idf("CFG_%c.FB_PATH", cfg))) {
if (bool_or_default(ci.params, ctx->idf("CFG_%c.PDIV0_MUX", cfg))) {
out_clk = (ref_clk * N1 * N2) / (K * 2 * M1 * M2);
} else {
out_clk = (ref_clk / K) * N1 * N2 * PDIV1;
}
} else {
out_clk = (ref_clk / K) * N1 * N2;
}
if (out_clk > out_clk_max)
out_clk_max = out_clk;
}
NetInfo *select_net = ci.getPort(id_USR_SEL_A_B);
if (select_net == nullptr || select_net->name == ctx->id("$PACKER_GND")) {
ci.params[ctx->id("SET_SEL")] = Property(0b0, 1);
ci.params[ctx->id("USR_SET")] = Property(0b0, 1);
ci.disconnectPort(id_USR_SEL_A_B);
} else if (select_net->name == ctx->id("$PACKER_VCC")) {
ci.params[ctx->id("SET_SEL")] = Property(0b1, 1);
ci.params[ctx->id("USR_SET")] = Property(0b0, 1);
ci.disconnectPort(id_USR_SEL_A_B);
} else {
ci.params[ctx->id("USR_SET")] = Property(0b1, 1);
move_ram_o_fixed(&ci, id_USR_SEL_A_B, fixed_loc);
}
ci.params[ctx->id("LOCK_REQ")] = Property(0b1, 1);
ci.unsetParam(id_PLL_CFG_A);
ci.unsetParam(id_PLL_CFG_B);
if (!ci.getPort(id_CLK_FEEDBACK))
ci.params[ctx->id("LOCK_REQ")] = Property(0b1, 1);
}
// PLL control register A
ci.params[ctx->id("PLL_RST")] = Property(0b1, 1);
ci.params[ctx->id("PLL_EN")] = Property(0b1, 1);
// PLL_AUTN - for Autonomous Mode - not set
// SET_SEL - handled in CC_PLL_ADV
// USR_SET - handled in CC_PLL_ADV
// TODO: USR_CLK_REF - based on signals used
ci.params[ctx->id("CLK_OUT_EN")] = Property(0b1, 1);
// LOCK_REQ - set by CC_PLL parameter
// PLL control register B
// AUTN_CT_I - for Autonomous Mode - not set
// CLK180_DOUB - set by CC_PLL parameter
// CLK270_DOUB - set by CC_PLL parameter
// bits 6 and 7 are unused
// TODO: USR_CLK_OUT - part of routing, mux from chipdb
if (ci.getPort(id_CLK0))
ctx->addClock(ci.getPort(id_CLK0)->name, out_clk_max);
if (ci.getPort(id_CLK90))
ctx->addClock(ci.getPort(id_CLK90)->name, out_clk_max);
if (ci.getPort(id_CLK180))
ctx->addClock(ci.getPort(id_CLK180)->name, clk180_doub ? out_clk_max * 2 : out_clk_max);
if (ci.getPort(id_CLK270))
ctx->addClock(ci.getPort(id_CLK270)->name, clk270_doub ? out_clk_max * 2 : out_clk_max);
ci.type = id_PLL;
pll_index++;
}
}
void GateMatePacker::pack_misc()
{
for (auto &cell : ctx->cells) {
CellInfo &ci = *cell.second;
if (!ci.type.in(id_CC_USR_RSTN))
continue;
ci.type = id_USR_RSTN;
ci.cluster = ci.name;
Loc fixed_loc(0, 0, 3); // USR_RSTN
ctx->bindBel(ctx->getBelByLocation(fixed_loc), &ci, PlaceStrength::STRENGTH_FIXED);
move_ram_i_fixed(&ci, id_USR_RSTN, fixed_loc);
}
for (auto &cell : ctx->cells) {
CellInfo &ci = *cell.second;
if (!ci.type.in(id_CC_CFG_CTRL))
continue;
ci.type = id_CFG_CTRL;
ci.cluster = ci.name;
Loc fixed_loc(0, 0, 2); // CFG_CTRL
ctx->bindBel(ctx->getBelByLocation(fixed_loc), &ci, PlaceStrength::STRENGTH_FIXED);
move_ram_o_fixed(&ci, id_CLK, fixed_loc);
move_ram_o_fixed(&ci, id_EN, fixed_loc);
move_ram_o_fixed(&ci, id_VALID, fixed_loc);
move_ram_o_fixed(&ci, id_RECFG, fixed_loc);
for (int i = 0; i < 8; i++)
move_ram_o_fixed(&ci, ctx->idf("DATA[%d]", i), fixed_loc);
}
for (auto &cell : ctx->cells) {
CellInfo &ci = *cell.second;
if (!ci.type.in(id_CC_ODDR, id_CC_IDDR))
continue;
log_error("Cell '%s' of type %s is not connected to GPIO pin.\n", ci.name.c_str(ctx), ci.type.c_str(ctx));
}
}
void GateMatePacker::pack_constants()
{
log_info("Packing constants..\n");
// Replace constants with LUTs
const dict<IdString, Property> vcc_params = {{id_INIT_L10, Property(0b1111, 4)}, {id_C_O, Property(0b11, 2)}};
const dict<IdString, Property> gnd_params = {{id_INIT_L10, Property(0b0000, 4)}, {id_C_O, Property(0b11, 2)}};
h.replace_constants(CellTypePort(id_CPE_HALF, id_OUT), CellTypePort(id_CPE_HALF, id_OUT), vcc_params, gnd_params);
}
void GateMatePacker::remove_constants()
{
log_info("Removing constants..\n");
auto fnd_cell = ctx->cells.find(ctx->id("$PACKER_VCC_DRV"));
if (fnd_cell != ctx->cells.end()) {
auto fnd_net = ctx->nets.find(ctx->id("$PACKER_VCC"));
if (fnd_net != ctx->nets.end() && fnd_net->second->users.entries() == 0) {
BelId bel = (*fnd_cell).second.get()->bel;
if (bel != BelId())
ctx->unbindBel(bel);
ctx->cells.erase(fnd_cell);
ctx->nets.erase(fnd_net);
log_info(" Removed unused VCC cell\n");
}
}
fnd_cell = ctx->cells.find(ctx->id("$PACKER_GND_DRV"));
if (fnd_cell != ctx->cells.end()) {
auto fnd_net = ctx->nets.find(ctx->id("$PACKER_GND"));
if (fnd_net != ctx->nets.end() && fnd_net->second->users.entries() == 0) {
BelId bel = (*fnd_cell).second.get()->bel;
if (bel != BelId())
ctx->unbindBel(bel);
ctx->cells.erase(fnd_cell);
ctx->nets.erase(fnd_net);
log_info(" Removed unused GND cell\n");
}
}
}
void GateMatePacker::remove_not_used()
{
for (auto &cell : ctx->cells) {
CellInfo &ci = *cell.second;
for (auto &p : ci.ports) {
if (p.second.type == PortType::PORT_OUT) {
NetInfo *net = ci.getPort(p.first);
if (net && net->users.entries() == 0) {
ci.disconnectPort(p.first);
}
}
}
}
}
uint8_t GateMatePacker::ram_ctrl_signal(CellInfo *cell, IdString port, bool alt)
{
NetInfo *net = cell->getPort(port);
if (net) {
if (net->name == ctx->id("$PACKER_GND")) {
cell->disconnectPort(port);
return 0b00000011;
} else if (net->name == ctx->id("$PACKER_VCC")) {
cell->disconnectPort(port);
return 0b00010011;
} else {
return alt ? 0b00000100 : 0b00000000;
}
}
return 0b00000011;
}
uint8_t GateMatePacker::ram_clk_signal(CellInfo *cell, IdString port)
{
NetInfo *clk_net = cell->getPort(port);
if (!global_signals.count(clk_net)) {
return 0b00000000;
} else {
int index = global_signals[clk_net];
uint8_t val = 0;
switch (index) {
case 0:
val = 0b00100011;
break;
case 1:
val = 0b00110011;
break;
case 2:
val = 0b00000011;
break;
case 3:
val = 0b00010011;
break;
}
cell->disconnectPort(port);
return val;
}
}
int width_to_config(int width)
{
switch (width) {
case 0:
return 0;
case 1:
return 1;
case 2:
return 2;
case 3 ... 5:
return 3;
case 6 ... 10:
return 4;
case 11 ... 20:
return 5;
case 21 ... 40:
return 6;
case 41 ... 80:
return 7;
default:
log_error("Unsupported width '%d'.\n", width);
}
}
static void rename_or_move(CellInfo *main, CellInfo *other, IdString port, IdString other_port)
{
if (main == other)
main->renamePort(port, other_port);
else
main->movePortTo(port, other, other_port);
}
void GateMatePacker::pack_ram_cell(CellInfo &ci, CellInfo *cell, int num, bool is_split)
{
// Port Widths
int a_rd_width = int_or_default(cell->params, id_A_RD_WIDTH, 0);
int b_rd_width = int_or_default(cell->params, id_B_RD_WIDTH, 0);
int a_wr_width = int_or_default(cell->params, id_A_WR_WIDTH, 0);
int b_wr_width = int_or_default(cell->params, id_B_WR_WIDTH, 0);
std::string a_wr_mode_str = str_or_default(cell->params, id_A_WR_MODE, "NO_CHANGE");
if (a_wr_mode_str != "NO_CHANGE" && a_wr_mode_str != "WRITE_THROUGH")
log_error("Unknown A_WR_MODE parameter value '%s' for cell %s.\n", a_wr_mode_str.c_str(),
cell->name.c_str(ctx));
int a_wr_mode = a_wr_mode_str == "NO_CHANGE" ? 0 : 1;
std::string b_wr_mode_str = str_or_default(cell->params, id_B_WR_MODE, "NO_CHANGE");
if (b_wr_mode_str != "NO_CHANGE" && b_wr_mode_str != "WRITE_THROUGH")
log_error("Unknown B_WR_MODE parameter value '%s' for cell %s.\n", b_wr_mode_str.c_str(),
cell->name.c_str(ctx));
int b_wr_mode = b_wr_mode_str == "NO_CHANGE" ? 0 : 1;
// Inverting Control Pins
int a_clk_inv = int_or_default(cell->params, id_A_CLK_INV, 0);
int b_clk_inv = int_or_default(cell->params, id_B_CLK_INV, 0);
int a_en_inv = int_or_default(cell->params, id_A_EN_INV, 0);
int b_en_inv = int_or_default(cell->params, id_B_EN_INV, 0);
int a_we_inv = int_or_default(cell->params, id_A_WE_INV, 0);
int b_we_inv = int_or_default(cell->params, id_B_WE_INV, 0);
// Output Register
int a_do_reg = int_or_default(cell->params, id_A_DO_REG, 0);
int b_do_reg = int_or_default(cell->params, id_B_DO_REG, 0);
uint8_t cfg_a = ram_clk_signal(cell, id_A_CLK);
uint8_t cfg_b = ram_clk_signal(cell, id_B_CLK);
uint8_t a_inv = a_clk_inv << 2 | a_we_inv << 1 | a_en_inv;
uint8_t b_inv = b_clk_inv << 2 | b_we_inv << 1 | b_en_inv;
uint8_t a_en = ram_ctrl_signal(cell, id_A_EN, false);
uint8_t b_en = ram_ctrl_signal(cell, id_B_EN, false);
uint8_t a_we = ram_ctrl_signal(cell, id_A_WE, false);
uint8_t b_we = ram_ctrl_signal(cell, id_B_WE, false);
if (num) {
ci.params[id_RAM_cfg_forward_a1_clk] = Property(cfg_a, 8);
ci.params[id_RAM_cfg_forward_b1_clk] = Property(cfg_b, 8);
ci.params[id_RAM_cfg_forward_a1_en] = Property(a_en, 8);
ci.params[id_RAM_cfg_forward_b1_en] = Property(b_en, 8);
ci.params[id_RAM_cfg_forward_a1_we] = Property(a_we, 8);
ci.params[id_RAM_cfg_forward_b1_we] = Property(b_we, 8);
ci.params[id_RAM_cfg_input_config_a1] = Property(width_to_config(a_wr_width), 3);
ci.params[id_RAM_cfg_input_config_b1] = Property(width_to_config(b_wr_width), 3);
ci.params[id_RAM_cfg_output_config_a1] = Property(width_to_config(a_rd_width), 3);
ci.params[id_RAM_cfg_output_config_b1] = Property(width_to_config(b_rd_width), 3);
ci.params[id_RAM_cfg_a1_writemode] = Property(a_wr_mode, 1);
ci.params[id_RAM_cfg_b1_writemode] = Property(b_wr_mode, 1);
ci.params[id_RAM_cfg_a1_set_outputreg] = Property(a_do_reg, 1);
ci.params[id_RAM_cfg_b1_set_outputreg] = Property(b_do_reg, 1);
ci.params[id_RAM_cfg_inversion_a1] = Property(a_inv, 3);
ci.params[id_RAM_cfg_inversion_b1] = Property(b_inv, 3);
} else {
ci.params[id_RAM_cfg_forward_a0_clk] = Property(cfg_a, 8);
if (!is_split)
ci.params[id_RAM_cfg_forward_a1_clk] = Property(cfg_a, 8);
ci.params[id_RAM_cfg_forward_b0_clk] = Property(cfg_b, 8);
if (!is_split)
ci.params[id_RAM_cfg_forward_b1_clk] = Property(cfg_b, 8);
ci.params[id_RAM_cfg_forward_a0_en] = Property(a_en, 8);
ci.params[id_RAM_cfg_forward_b0_en] = Property(b_en, 8);
ci.params[id_RAM_cfg_forward_a0_we] = Property(a_we, 8);
ci.params[id_RAM_cfg_forward_b0_we] = Property(b_we, 8);
ci.params[id_RAM_cfg_input_config_a0] = Property(width_to_config(a_wr_width), 3);
ci.params[id_RAM_cfg_input_config_b0] = Property(width_to_config(b_wr_width), 3);
ci.params[id_RAM_cfg_output_config_a0] = Property(width_to_config(a_rd_width), 3);
ci.params[id_RAM_cfg_output_config_b0] = Property(width_to_config(b_rd_width), 3);
ci.params[id_RAM_cfg_a0_writemode] = Property(a_wr_mode, 1);
ci.params[id_RAM_cfg_b0_writemode] = Property(b_wr_mode, 1);
ci.params[id_RAM_cfg_a0_set_outputreg] = Property(a_do_reg, 1);
ci.params[id_RAM_cfg_b0_set_outputreg] = Property(b_do_reg, 1);
ci.params[id_RAM_cfg_inversion_a0] = Property(a_inv, 3);
ci.params[id_RAM_cfg_inversion_b0] = Property(b_inv, 3);
}
int index = (num == 0) ? 0 : 2;
rename_or_move(cell, &ci, id_A_CLK, ctx->idf("CLKA[%d]", index));
rename_or_move(cell, &ci, id_B_CLK, ctx->idf("CLKB[%d]", index));
rename_or_move(cell, &ci, id_A_EN, ctx->idf("ENA[%d]", index));
rename_or_move(cell, &ci, id_B_EN, ctx->idf("ENB[%d]", index));
rename_or_move(cell, &ci, id_A_WE, ctx->idf("GLWEA[%d]", index));
rename_or_move(cell, &ci, id_B_WE, ctx->idf("GLWEB[%d]", index));
int items = is_split ? 20 : 40;
for (int i = 0; i < items; i++) {
rename_or_move(cell, &ci, ctx->idf("A_BM[%d]", i), ctx->idf("WEA[%d]", i + num * 20));
rename_or_move(cell, &ci, ctx->idf("B_BM[%d]", i), ctx->idf("WEB[%d]", i + num * 20));
}
for (int i = 0; i < 16; i++) {
rename_or_move(cell, &ci, ctx->idf("A_ADDR[%d]", i), ctx->idf("ADDRA%d[%d]", num, i));
rename_or_move(cell, &ci, ctx->idf("B_ADDR[%d]", i), ctx->idf("ADDRB%d[%d]", num, i));
}
for (int i = 0; i < items; i++) {
rename_or_move(cell, &ci, ctx->idf("A_DI[%d]", i), ctx->idf("DIA[%d]", i + num * 20));
rename_or_move(cell, &ci, ctx->idf("A_DO[%d]", i), ctx->idf("DOA[%d]", i + num * 20));
rename_or_move(cell, &ci, ctx->idf("B_DI[%d]", i), ctx->idf("DIB[%d]", i + num * 20));
rename_or_move(cell, &ci, ctx->idf("B_DO[%d]", i), ctx->idf("DOB[%d]", i + num * 20));
}
}
void GateMatePacker::pack_ram()
{
std::vector<std::pair<CellInfo *, CellInfo *>> rams;
std::vector<std::pair<CellInfo *, CellInfo *>> rams_merged[2];
for (auto &cell : ctx->cells) {
CellInfo &ci = *cell.second;
if (!ci.type.in(id_CC_BRAM_20K, id_CC_BRAM_40K, id_CC_FIFO_40K))
continue;
int split = ci.type.in(id_CC_BRAM_20K) ? 1 : 0;
std::string ram_mode_str = str_or_default(ci.params, id_RAM_MODE, "SDP");
if (ram_mode_str != "SDP" && ram_mode_str != "TDP")
log_error("Unknown RAM_MODE parameter value '%s' for cell %s.\n", ram_mode_str.c_str(), ci.name.c_str(ctx));
int ram_mode = ram_mode_str == "SDP" ? 1 : 0;
if (split) {
bool added = false;
if (!rams_merged[ram_mode].empty()) {
auto &last = rams_merged[ram_mode].back();
if (last.second == nullptr) {
last.second = &ci;
packed_cells.insert(ci.name);
added = true;
}
}
if (!added)
rams_merged[ram_mode].push_back(std::make_pair(&ci, nullptr));
} else {
rams.push_back(std::make_pair(&ci, nullptr));
}
}
rams.insert(rams.end(), rams_merged[0].begin(), rams_merged[0].end());
rams.insert(rams.end(), rams_merged[1].begin(), rams_merged[1].end());
for (auto item : rams) {
CellInfo &ci = *item.first;
int split = ci.type.in(id_CC_BRAM_20K) ? 1 : 0;
bool is_fifo = ci.type.in(id_CC_FIFO_40K);
ci.type = id_RAM;
ci.cluster = ci.name;
// Location format: D(0..N-1)X(0..3)Y(0..7) or UNPLACED
std::string loc = str_or_default(ci.params, id_LOC, "UNPLACED");
std::string cas = str_or_default(ci.params, id_CAS, "NONE");
int cascade = 0;
if (cas == "NONE") {
cascade = 0;
} else if (cas == "UPPER") {
cascade = 1;
} else if (cas == "LOWER") {
cascade = 2;
} else {
log_error("Unknown CAS parameter value '%s' for cell %s.\n", cas.c_str(), ci.name.c_str(ctx));
}
// RAM and Write Modes
std::string ram_mode_str = str_or_default(ci.params, id_RAM_MODE, "SDP");
if (ram_mode_str != "SDP" && ram_mode_str != "TDP")
log_error("Unknown RAM_MODE parameter value '%s' for cell %s.\n", ram_mode_str.c_str(), ci.name.c_str(ctx));
int ram_mode = ram_mode_str == "SDP" ? 1 : 0;
// Error Checking and Correction
int a_ecc_en = int_or_default(ci.params, id_A_ECC_EN, 0);
int b_ecc_en = int_or_default(ci.params, id_B_ECC_EN, 0);
ci.params[id_RAM_cfg_forward_a_addr] = Property(0b00000000, 8);
ci.params[id_RAM_cfg_forward_b_addr] = Property(0b00000000, 8);
ci.params[id_RAM_cfg_sram_mode] = Property(ram_mode << 1 | split, 2);
pack_ram_cell(ci, item.first, 0, split);
if (item.second) {
pack_ram_cell(ci, item.second, 1, split);
}
if (split) {
for (int i = 63; i >= 0; i--) {
std::vector<bool> orig_first =
item.first->params.at(ctx->idf("INIT_%02X", i)).extract(0, 320).as_bits();
std::vector<bool> orig_second;
if (item.second)
orig_second = item.second->params.at(ctx->idf("INIT_%02X", i)).extract(0, 320).as_bits();
std::string init[2];
for (int j = 0; j < 2; j++) {
for (int k = 0; k < 4; k++) {
for (int l = 0; l < 40; l++) {
if (item.second)
init[j].push_back(orig_second.at(319 - (l + k * 40 + j * 160)) ? '1' : '0');
else
init[j].push_back('0');
}
for (int l = 0; l < 40; l++) {
init[j].push_back(orig_first.at(319 - (l + k * 40 + j * 160)) ? '1' : '0');
}
}
}
ci.params[ctx->idf("INIT_%02X", i * 2 + 1)] = Property::from_string(init[0]);
ci.params[ctx->idf("INIT_%02X", i * 2 + 0)] = Property::from_string(init[1]);
}
}
if (is_fifo) {
int a_rd_width = int_or_default(ci.params, id_A_WIDTH, 0);
int b_wr_width = int_or_default(ci.params, id_B_WIDTH, 0);
if (a_rd_width != b_wr_width)
log_error("The FIFO configuration of A_WIDTH and B_WIDTH must be equal.\n");
if (a_rd_width != 80 && ram_mode == 1)
log_error("FIFO SDP is ony supported in 80 bit mode.\n");
ci.params[id_RAM_cfg_input_config_b0] = Property(width_to_config(b_wr_width), 3);
ci.params[id_RAM_cfg_output_config_a0] = Property(width_to_config(a_rd_width), 3);
std::string fifo_mode_str = str_or_default(ci.params, id_FIFO_MODE, "SYNC");
if (fifo_mode_str != "SYNC" && fifo_mode_str != "ASYNC")
log_error("Unknown FIFO_MODE parameter value '%s' for cell %s.\n", fifo_mode_str.c_str(),
ci.name.c_str(ctx));
int fifo_mode = fifo_mode_str == "SYNC" ? 1 : 0;
if (fifo_mode)
ci.params[id_RAM_cfg_fifo_sync_enable] = Property(0b1, 1);
else
ci.params[id_RAM_cfg_fifo_async_enable] = Property(0b1, 1);
// TODO: Handle dynamic almost empty/full
int dyn_stat_select = int_or_default(ci.params, id_DYN_STAT_SELECT, 0);
if (dyn_stat_select != 0 && dyn_stat_select != 1)
log_error("DYN_STAT_SELECT must be 0 or 1.\n");
if (dyn_stat_select != 0 && ram_mode == 1)
log_error("Dynamic FIFO offset configuration is not supported in SDP mode.\n");
ci.params[id_RAM_cfg_dyn_stat_select] = Property(dyn_stat_select << 1, 2);
ci.params[id_RAM_cfg_almost_empty_offset] =
Property(int_or_default(ci.params, id_F_ALMOST_EMPTY_OFFSET, 0), 15);
ci.params[id_RAM_cfg_almost_full_offset] =
Property(int_or_default(ci.params, id_F_ALMOST_FULL_OFFSET, 0), 15);
}
ci.params[id_RAM_cfg_ecc_enable] = Property(b_ecc_en << 1 | a_ecc_en, 2);
ci.params[id_RAM_cfg_sram_delay] = Property(0b000101, 6); // Always set to default
// id_RAM_cfg_datbm_sel
ci.params[id_RAM_cfg_cascade_enable] = Property(cascade, 2);
for (int i = 0; i < 40; i++) {
move_ram_o(&ci, ctx->idf("WEA[%d]", i));
move_ram_o(&ci, ctx->idf("WEB[%d]", i));
}
for (int i = 0; i < 16; i++) {
move_ram_o(&ci, ctx->idf("ADDRA0[%d]", i));
move_ram_o(&ci, ctx->idf("ADDRB0[%d]", i));
move_ram_o(&ci, ctx->idf("ADDRA1[%d]", i));
move_ram_o(&ci, ctx->idf("ADDRB1[%d]", i));
}
for (int i = 0; i < 40; i++) {
move_ram_io(&ci, ctx->idf("DOA[%d]", i), ctx->idf("DIA[%d]", i));
move_ram_io(&ci, ctx->idf("DOB[%d]", i), ctx->idf("DIB[%d]", i));
}
for (int i = 0; i < 4; i++) {
move_ram_o(&ci, ctx->idf("CLKA[%d]", i));
move_ram_o(&ci, ctx->idf("CLKB[%d]", i));
move_ram_o(&ci, ctx->idf("ENA[%d]", i));
move_ram_o(&ci, ctx->idf("ENB[%d]", i));
move_ram_o(&ci, ctx->idf("GLWEA[%d]", i));
move_ram_o(&ci, ctx->idf("GLWEB[%d]", i));
}
if (is_fifo) {
for (int i = 0; i < 15; i++) {
ci.disconnectPort(ctx->idf("F_ALMOST_EMPTY_OFFSET[%d]", i));
ci.disconnectPort(ctx->idf("F_ALMOST_FULL_OFFSET[%d]", i));
}
ci.renamePort(id_F_EMPTY, ctx->id("F_EMPTY[0]"));
move_ram_i(&ci, ctx->id("F_EMPTY[0]"));
ci.renamePort(id_F_FULL, ctx->id("F_FULL[0]"));
move_ram_i(&ci, ctx->id("F_FULL[0]"));
ci.renamePort(id_F_ALMOST_FULL, ctx->id("F_AL_FULL[0]"));
move_ram_i(&ci, ctx->id("F_AL_FULL[0]"));
ci.renamePort(id_F_ALMOST_EMPTY, ctx->id("F_AL_EMPTY[0]"));
move_ram_i(&ci, ctx->id("F_AL_EMPTY[0]"));
ci.renamePort(id_F_WR_ERROR, ctx->id("FWR_ERR[0]"));
move_ram_i(&ci, ctx->id("FWR_ERR[0]"));
ci.renamePort(id_F_RD_ERROR, ctx->id("FRD_ERR[0]"));
move_ram_i(&ci, ctx->id("FRD_ERR[0]"));
ci.renamePort(id_F_RST_N, ctx->id("F_RSTN"));
move_ram_o(&ci, ctx->id("F_RSTN"));
}
}
flush_cells();
}
void GateMateImpl::pack()
{
const ArchArgs &args = ctx->args;
if (args.options.count("ccf")) {
parse_ccf(args.options.at("ccf"));
}
GateMatePacker packer(ctx, this);
packer.pack_constants();
packer.remove_not_used();
packer.pack_io();
packer.insert_pll_bufg();
packer.sort_bufg();
packer.pack_pll();
packer.pack_bufg();
packer.pack_io_sel(); // merge in FF and DDR
packer.pack_misc();
packer.pack_ram();
packer.pack_addf();
packer.pack_cpe();
packer.remove_constants();
}
NEXTPNR_NAMESPACE_END