commit
b53a4862db
@ -237,6 +237,12 @@ class ConstraintLegaliseWorker
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Don't place at tiles where any strongly bound Bels exist, as we might need to rip them up later
|
||||
for (auto tilebel : ctx->getBelsByTile(loc.x, loc.y)) {
|
||||
CellInfo *tcell = ctx->getBoundBelCell(tilebel);
|
||||
if (tcell && tcell->belStrength >= STRENGTH_STRONG)
|
||||
return false;
|
||||
}
|
||||
usedLocations.insert(loc);
|
||||
for (auto child : cell->constr_children) {
|
||||
IncreasingDiameterSearch xSearch, ySearch, zSearch;
|
||||
|
62
ecp5/arch.cc
62
ecp5/arch.cc
@ -19,6 +19,7 @@
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include "gfx.h"
|
||||
@ -76,11 +77,13 @@ Arch::Arch(ArchArgs args) : args(args)
|
||||
log_error("Unsupported ECP5 chip type.\n");
|
||||
}
|
||||
#else
|
||||
if (args.type == ArchArgs::LFE5U_25F) {
|
||||
if (args.type == ArchArgs::LFE5U_25F || args.type == ArchArgs::LFE5UM_25F || args.type == ArchArgs::LFE5UM5G_25F) {
|
||||
chip_info = get_chip_info(reinterpret_cast<const RelPtr<ChipInfoPOD> *>(chipdb_blob_25k));
|
||||
} else if (args.type == ArchArgs::LFE5U_45F) {
|
||||
} else if (args.type == ArchArgs::LFE5U_45F || args.type == ArchArgs::LFE5UM_45F ||
|
||||
args.type == ArchArgs::LFE5UM5G_45F) {
|
||||
chip_info = get_chip_info(reinterpret_cast<const RelPtr<ChipInfoPOD> *>(chipdb_blob_45k));
|
||||
} else if (args.type == ArchArgs::LFE5U_85F) {
|
||||
} else if (args.type == ArchArgs::LFE5U_85F || args.type == ArchArgs::LFE5UM_85F ||
|
||||
args.type == ArchArgs::LFE5UM5G_85F) {
|
||||
chip_info = get_chip_info(reinterpret_cast<const RelPtr<ChipInfoPOD> *>(chipdb_blob_85k));
|
||||
} else {
|
||||
log_error("Unsupported ECP5 chip type.\n");
|
||||
@ -110,6 +113,18 @@ std::string Arch::getChipName() const
|
||||
return "LFE5U-45F";
|
||||
} else if (args.type == ArchArgs::LFE5U_85F) {
|
||||
return "LFE5U-85F";
|
||||
} else if (args.type == ArchArgs::LFE5UM_25F) {
|
||||
return "LFE5UM-25F";
|
||||
} else if (args.type == ArchArgs::LFE5UM_45F) {
|
||||
return "LFE5UM-45F";
|
||||
} else if (args.type == ArchArgs::LFE5UM_85F) {
|
||||
return "LFE5UM-85F";
|
||||
} else if (args.type == ArchArgs::LFE5UM5G_25F) {
|
||||
return "LFE5UM5G-25F";
|
||||
} else if (args.type == ArchArgs::LFE5UM5G_45F) {
|
||||
return "LFE5UM5G-45F";
|
||||
} else if (args.type == ArchArgs::LFE5UM5G_85F) {
|
||||
return "LFE5UM5G-85F";
|
||||
} else {
|
||||
log_error("Unknown chip\n");
|
||||
}
|
||||
@ -125,6 +140,18 @@ IdString Arch::archArgsToId(ArchArgs args) const
|
||||
return id("lfe5u_45f");
|
||||
if (args.type == ArchArgs::LFE5U_85F)
|
||||
return id("lfe5u_85f");
|
||||
if (args.type == ArchArgs::LFE5UM_25F)
|
||||
return id("lfe5um_25f");
|
||||
if (args.type == ArchArgs::LFE5UM_45F)
|
||||
return id("lfe5um_45f");
|
||||
if (args.type == ArchArgs::LFE5UM_85F)
|
||||
return id("lfe5um_85f");
|
||||
if (args.type == ArchArgs::LFE5UM5G_25F)
|
||||
return id("lfe5um5g_25f");
|
||||
if (args.type == ArchArgs::LFE5UM5G_45F)
|
||||
return id("lfe5um5g_45f");
|
||||
if (args.type == ArchArgs::LFE5UM5G_85F)
|
||||
return id("lfe5um5g_85f");
|
||||
return IdString();
|
||||
}
|
||||
|
||||
@ -531,6 +558,19 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else if (cell->type == id_DP16KD) {
|
||||
if (fromPort == id_CLKA) {
|
||||
if (toPort.str(this).substr(0, 3) == "DOA") {
|
||||
delay.delay = 4260;
|
||||
return true;
|
||||
}
|
||||
} else if (fromPort == id_CLKB) {
|
||||
if (toPort.str(this).substr(0, 3) == "DOB") {
|
||||
delay.delay = 4280;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@ -582,6 +622,22 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
|
||||
if (port == id_CLKO)
|
||||
return TMG_COMB_OUTPUT;
|
||||
return TMG_IGNORE;
|
||||
} else if (cell->type == id_DP16KD) {
|
||||
if (port == id_CLKA || port == id_CLKB)
|
||||
return TMG_CLOCK_INPUT;
|
||||
std::string port_name = port.str(this);
|
||||
for (auto c : boost::adaptors::reverse(port_name)) {
|
||||
if (std::isdigit(c))
|
||||
continue;
|
||||
if (c == 'A')
|
||||
clockPort = id_CLKA;
|
||||
else if (c == 'B')
|
||||
clockPort = id_CLKB;
|
||||
else
|
||||
NPNR_ASSERT_FALSE_STR("bad ram port");
|
||||
return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT;
|
||||
}
|
||||
NPNR_ASSERT_FALSE_STR("no timing type for RAM port '" + port.str(this) + "'");
|
||||
} else {
|
||||
NPNR_ASSERT_FALSE_STR("no timing data for cell type '" + cell->type.str(this) + "'");
|
||||
}
|
||||
|
@ -392,6 +392,12 @@ struct ArchArgs
|
||||
LFE5U_25F,
|
||||
LFE5U_45F,
|
||||
LFE5U_85F,
|
||||
LFE5UM_25F,
|
||||
LFE5UM_45F,
|
||||
LFE5UM_85F,
|
||||
LFE5UM5G_25F,
|
||||
LFE5UM5G_45F,
|
||||
LFE5UM5G_85F,
|
||||
} type = NONE;
|
||||
std::string package;
|
||||
int speed = 6;
|
||||
|
@ -20,6 +20,8 @@
|
||||
#include "bitstream.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <regex>
|
||||
#include <streambuf>
|
||||
|
||||
#include "config.h"
|
||||
@ -61,6 +63,101 @@ static std::vector<bool> int_to_bitvector(int val, int size)
|
||||
return bv;
|
||||
}
|
||||
|
||||
static std::vector<bool> str_to_bitvector(std::string str, int size)
|
||||
{
|
||||
std::vector<bool> bv;
|
||||
bv.resize(size, 0);
|
||||
if (str.substr(0, 2) != "0b")
|
||||
log_error("error parsing value '%s', expected 0b prefix\n", str.c_str());
|
||||
for (int i = 0; i < int(str.size()) - 2; i++) {
|
||||
char c = str.at((str.size() - i) - 1);
|
||||
NPNR_ASSERT(c == '0' || c == '1');
|
||||
bv.at(i) = (c == '1');
|
||||
}
|
||||
return bv;
|
||||
}
|
||||
|
||||
// Tie a wire using the CIB ties
|
||||
static void tie_cib_signal(Context *ctx, ChipConfig &cc, WireId wire, bool value)
|
||||
{
|
||||
static const std::regex cib_re("J([A-D]|CE|LSR|CLK)[0-7]");
|
||||
WireId cibsig = wire;
|
||||
std::string basename = ctx->getWireBasename(wire).str(ctx);
|
||||
|
||||
while (!std::regex_match(basename, cib_re)) {
|
||||
auto uphill = ctx->getPipsUphill(cibsig);
|
||||
NPNR_ASSERT(uphill.begin() != uphill.end()); // At least one uphill pip
|
||||
auto iter = uphill.begin();
|
||||
cibsig = ctx->getPipSrcWire(*iter);
|
||||
basename = ctx->getWireBasename(cibsig).str(ctx);
|
||||
++iter;
|
||||
NPNR_ASSERT(!(iter != uphill.end())); // Exactly one uphill pip
|
||||
}
|
||||
|
||||
bool out_value = value;
|
||||
if (basename.substr(0, 3) == "JCE")
|
||||
NPNR_ASSERT(value);
|
||||
if (basename.substr(0, 4) == "JCLK" || basename.substr(0, 4) == "JLSR") {
|
||||
NPNR_ASSERT(value);
|
||||
out_value = 0;
|
||||
}
|
||||
|
||||
for (const auto &tile : ctx->getTilesAtLocation(cibsig.location.y, cibsig.location.x)) {
|
||||
if (tile.second.substr(0, 3) == "CIB" || tile.second.substr(0, 4) == "VCIB") {
|
||||
|
||||
cc.tiles[tile.first].add_enum("CIB." + basename + "MUX", out_value ? "1" : "0");
|
||||
return;
|
||||
}
|
||||
}
|
||||
NPNR_ASSERT_FALSE("CIB tile not found at location");
|
||||
}
|
||||
|
||||
inline int chtohex(char c)
|
||||
{
|
||||
static const std::string hex = "0123456789ABCDEF";
|
||||
return hex.find(c);
|
||||
}
|
||||
|
||||
std::vector<bool> parse_init_str(const std::string &str, int length)
|
||||
{
|
||||
// Parse a string that may be binary or hex
|
||||
std::vector<bool> result;
|
||||
result.resize(length, false);
|
||||
if (str.substr(0, 2) == "0x") {
|
||||
// Lattice style hex string
|
||||
if (int(str.length()) > (2 + ((length + 3) / 4)))
|
||||
log_error("hex string value too long, expected up to %d chars and found %d.\n", (2 + ((length + 3) / 4)),
|
||||
int(str.length()));
|
||||
for (int i = 0; i < int(str.length()) - 2; i++) {
|
||||
char c = str.at((str.size() - i) - 1);
|
||||
int nibble = chtohex(c);
|
||||
result.at(i * 4) = nibble & 0x1;
|
||||
result.at(i * 4 + 1) = nibble & 0x2;
|
||||
result.at(i * 4 + 2) = nibble & 0x4;
|
||||
result.at(i * 4 + 3) = nibble & 0x8;
|
||||
}
|
||||
} else {
|
||||
// Yosys style binary string
|
||||
if (int(str.length()) > length)
|
||||
log_error("hex string value too long, expected up to %d bits and found %d.\n", length, int(str.length()));
|
||||
for (int i = 0; i < int(str.length()); i++) {
|
||||
char c = str.at((str.size() - i) - 1);
|
||||
NPNR_ASSERT(c == '0' || c == '1' || c == 'X' || c == 'x');
|
||||
result.at(i) = (c == '1');
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline uint16_t bit_reverse(uint16_t x, int size)
|
||||
{
|
||||
uint16_t y = 0;
|
||||
for (int i = 0; i < size; i++)
|
||||
if (x & (1 << i))
|
||||
y |= (1 << ((size - 1) - i));
|
||||
return y;
|
||||
}
|
||||
|
||||
// Get the PIO tile corresponding to a PIO bel
|
||||
static std::string get_pio_tile(Context *ctx, BelId bel)
|
||||
{
|
||||
@ -144,6 +241,77 @@ static std::string get_pic_tile(Context *ctx, BelId bel)
|
||||
}
|
||||
}
|
||||
|
||||
// Get the list of tiles corresponding to a blockram
|
||||
std::vector<std::string> get_bram_tiles(Context *ctx, BelId bel)
|
||||
{
|
||||
std::vector<std::string> tiles;
|
||||
Loc loc = ctx->getBelLocation(bel);
|
||||
|
||||
static const std::set<std::string> ebr0 = {"MIB_EBR0", "EBR_CMUX_UR", "EBR_CMUX_LR", "EBR_CMUX_LR_25K"};
|
||||
static const std::set<std::string> ebr8 = {"MIB_EBR8", "EBR_SPINE_UL1", "EBR_SPINE_UR1", "EBR_SPINE_LL1",
|
||||
"EBR_CMUX_UL", "EBR_SPINE_LL0", "EBR_CMUX_LL", "EBR_SPINE_LR0",
|
||||
"EBR_SPINE_LR1", "EBR_CMUX_LL_25K", "EBR_SPINE_UL2", "EBR_SPINE_UL0",
|
||||
"EBR_SPINE_UR2", "EBR_SPINE_LL2", "EBR_SPINE_LR2", "EBR_SPINE_UR0"};
|
||||
|
||||
switch (loc.z) {
|
||||
case 0:
|
||||
tiles.push_back(ctx->getTileByTypeAndLocation(loc.y, loc.x, ebr0));
|
||||
tiles.push_back(ctx->getTileByTypeAndLocation(loc.y, loc.x + 1, "MIB_EBR1"));
|
||||
break;
|
||||
case 1:
|
||||
tiles.push_back(ctx->getTileByTypeAndLocation(loc.y, loc.x, "MIB_EBR2"));
|
||||
tiles.push_back(ctx->getTileByTypeAndLocation(loc.y, loc.x + 1, "MIB_EBR3"));
|
||||
tiles.push_back(ctx->getTileByTypeAndLocation(loc.y, loc.x + 2, "MIB_EBR4"));
|
||||
break;
|
||||
case 2:
|
||||
tiles.push_back(ctx->getTileByTypeAndLocation(loc.y, loc.x, "MIB_EBR4"));
|
||||
tiles.push_back(ctx->getTileByTypeAndLocation(loc.y, loc.x + 1, "MIB_EBR5"));
|
||||
tiles.push_back(ctx->getTileByTypeAndLocation(loc.y, loc.x + 2, "MIB_EBR6"));
|
||||
break;
|
||||
case 3:
|
||||
tiles.push_back(ctx->getTileByTypeAndLocation(loc.y, loc.x, "MIB_EBR6"));
|
||||
tiles.push_back(ctx->getTileByTypeAndLocation(loc.y, loc.x + 1, "MIB_EBR7"));
|
||||
tiles.push_back(ctx->getTileByTypeAndLocation(loc.y, loc.x + 2, ebr8));
|
||||
break;
|
||||
default:
|
||||
NPNR_ASSERT_FALSE("bad EBR z loc");
|
||||
}
|
||||
return tiles;
|
||||
}
|
||||
|
||||
void fix_tile_names(Context *ctx, ChipConfig &cc)
|
||||
{
|
||||
// Remove the V prefix/suffix on certain tiles if device is a SERDES variant
|
||||
if (ctx->args.type == ArchArgs::LFE5UM_25F || ctx->args.type == ArchArgs::LFE5UM_45F ||
|
||||
ctx->args.type == ArchArgs::LFE5UM_85F || ctx->args.type == ArchArgs::LFE5UM5G_25F ||
|
||||
ctx->args.type == ArchArgs::LFE5UM5G_45F || ctx->args.type == ArchArgs::LFE5UM5G_85F) {
|
||||
std::map<std::string, std::string> tiletype_xform;
|
||||
for (const auto &tile : cc.tiles) {
|
||||
std::string newname = tile.first;
|
||||
auto vcib = tile.first.find("VCIB");
|
||||
if (vcib != std::string::npos) {
|
||||
// Remove the V
|
||||
newname.erase(vcib);
|
||||
tiletype_xform[tile.first] = newname;
|
||||
} else if (tile.first.back() == 'V') {
|
||||
// BMID_0V or BMID_2V
|
||||
if (tile.first.at(tile.first.size() - 2) == '0') {
|
||||
newname.at(tile.first.size() - 1) = 'H';
|
||||
tiletype_xform[tile.first] = newname;
|
||||
} else if (tile.first.at(tile.first.size() - 2) == '2') {
|
||||
newname.pop_back();
|
||||
tiletype_xform[tile.first] = newname;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Apply the name changes
|
||||
for (auto xform : tiletype_xform) {
|
||||
cc.tiles[xform.second] = cc.tiles.at(xform.first);
|
||||
cc.tiles.erase(xform.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void write_bitstream(Context *ctx, std::string base_config_file, std::string text_config_file)
|
||||
{
|
||||
ChipConfig cc;
|
||||
@ -289,11 +457,8 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
|
||||
if (str_or_default(ci->params, ctx->id("MODE"), "LOGIC") == "DPRAM" && slice == "SLICEA") {
|
||||
cc.tiles[tname].add_enum(slice + ".WREMUX", str_or_default(ci->params, ctx->id("WREMUX"), "WRE"));
|
||||
|
||||
NetInfo *wcknet = nullptr;
|
||||
std::string wckmux = str_or_default(ci->params, ctx->id("WCKMUX"), "WCK");
|
||||
wckmux = (wckmux == "WCK") ? "CLK" : wckmux;
|
||||
if (ci->ports.find(ctx->id("WCK")) != ci->ports.end() && ci->ports.at(ctx->id("WCK")).net != nullptr)
|
||||
wcknet = ci->ports.at(ctx->id("WCK")).net;
|
||||
cc.tiles[tname].add_enum("CLK1.CLKMUX", wckmux);
|
||||
}
|
||||
|
||||
@ -344,11 +509,120 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
|
||||
}
|
||||
} else if (ci->type == ctx->id("DCCA")) {
|
||||
// Nothing to do
|
||||
} else if (ci->type == ctx->id("DP16KD")) {
|
||||
TileGroup tg;
|
||||
Loc loc = ctx->getBelLocation(ci->bel);
|
||||
tg.tiles = get_bram_tiles(ctx, ci->bel);
|
||||
std::string ebr = "EBR" + std::to_string(loc.z);
|
||||
|
||||
tg.config.add_enum(ebr + ".MODE", "DP16KD");
|
||||
|
||||
auto csd_a = str_to_bitvector(str_or_default(ci->params, ctx->id("CSDECODE_A"), "0b000"), 3),
|
||||
csd_b = str_to_bitvector(str_or_default(ci->params, ctx->id("CSDECODE_B"), "0b000"), 3);
|
||||
|
||||
tg.config.add_enum(ebr + ".DP16KD.DATA_WIDTH_A", str_or_default(ci->params, ctx->id("DATA_WIDTH_A"), "18"));
|
||||
tg.config.add_enum(ebr + ".DP16KD.DATA_WIDTH_B", str_or_default(ci->params, ctx->id("DATA_WIDTH_B"), "18"));
|
||||
|
||||
tg.config.add_enum(ebr + ".DP16KD.WRITEMODE_A",
|
||||
str_or_default(ci->params, ctx->id("WRITEMODE_A"), "NORMAL"));
|
||||
tg.config.add_enum(ebr + ".DP16KD.WRITEMODE_B",
|
||||
str_or_default(ci->params, ctx->id("WRITEMODE_B"), "NORMAL"));
|
||||
|
||||
tg.config.add_enum(ebr + ".REGMODE_A", str_or_default(ci->params, ctx->id("REGMODE_A"), "NOREG"));
|
||||
tg.config.add_enum(ebr + ".REGMODE_B", str_or_default(ci->params, ctx->id("REGMODE_B"), "NOREG"));
|
||||
|
||||
tg.config.add_enum(ebr + ".RESETMODE", str_or_default(ci->params, ctx->id("RESETMODE"), "SYNC"));
|
||||
tg.config.add_enum(ebr + ".ASYNC_RESET_RELEASE",
|
||||
str_or_default(ci->params, ctx->id("ASYNC_RESET_RELEASE"), "SYNC"));
|
||||
tg.config.add_enum(ebr + ".GSR", str_or_default(ci->params, ctx->id("GSR"), "DISABLED"));
|
||||
|
||||
tg.config.add_word(ebr + ".WID",
|
||||
int_to_bitvector(bit_reverse(int_or_default(ci->attrs, ctx->id("WID"), 0), 9), 9));
|
||||
|
||||
// Tie signals as appropriate
|
||||
for (auto port : ci->ports) {
|
||||
if (port.second.net == nullptr && port.second.type == PORT_IN) {
|
||||
if (port.first == id_CLKA || port.first == id_CLKB || port.first == id_WEA ||
|
||||
port.first == id_WEB || port.first == id_RSTA || port.first == id_RSTB) {
|
||||
// CIB clock or LSR. Tie to "1" (also 0 in prjtrellis db?) in CIB
|
||||
// If MUX doesn't exist, set to INV to emulate default 0
|
||||
tie_cib_signal(ctx, cc, ctx->getBelPinWire(ci->bel, port.first), true);
|
||||
if (!ci->params.count(ctx->id(port.first.str(ctx) + "MUX")))
|
||||
ci->params[ctx->id(port.first.str(ctx) + "MUX")] = "INV";
|
||||
} else if (port.first == id_CEA || port.first == id_CEB || port.first == id_OCEA ||
|
||||
port.first == id_OCEB) {
|
||||
// CIB CE. Tie to "1" in CIB
|
||||
// If MUX doesn't exist, set to passthru to emulate default 1
|
||||
tie_cib_signal(ctx, cc, ctx->getBelPinWire(ci->bel, port.first), true);
|
||||
if (!ci->params.count(ctx->id(port.first.str(ctx) + "MUX")))
|
||||
ci->params[ctx->id(port.first.str(ctx) + "MUX")] = port.first.str(ctx);
|
||||
} else if (port.first == id_CSA0 || port.first == id_CSA1 || port.first == id_CSA2 ||
|
||||
port.first == id_CSB0 || port.first == id_CSB1 || port.first == id_CSB2) {
|
||||
// CIB CE. Tie to "1" in CIB.
|
||||
// If MUX doesn't exist, set to INV to emulate default 0
|
||||
tie_cib_signal(ctx, cc, ctx->getBelPinWire(ci->bel, port.first), true);
|
||||
if (!ci->params.count(ctx->id(port.first.str(ctx) + "MUX")))
|
||||
ci->params[ctx->id(port.first.str(ctx) + "MUX")] = "INV";
|
||||
} else {
|
||||
// CIB ABCD signal
|
||||
// Tie signals low unless explicit MUX param specified
|
||||
bool value = bool_or_default(ci->params, ctx->id(port.first.str(ctx) + "MUX"), false);
|
||||
tie_cib_signal(ctx, cc, ctx->getBelPinWire(ci->bel, port.first), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Invert CSDECODE bits to emulate inversion muxes on CSA/CSB signals
|
||||
for (auto port : {std::make_pair("CSA", std::ref(csd_a)), std::make_pair("CSB", std::ref(csd_b))}) {
|
||||
for (int bit = 0; bit < 3; bit++) {
|
||||
std::string sig = port.first + std::to_string(bit);
|
||||
if (str_or_default(ci->params, ctx->id(sig + "MUX"), sig) == "INV")
|
||||
port.second.at(bit) = !port.second.at(bit);
|
||||
}
|
||||
}
|
||||
|
||||
tg.config.add_enum(ebr + ".CLKAMUX", str_or_default(ci->params, ctx->id("CLKAMUX"), "CLKA"));
|
||||
tg.config.add_enum(ebr + ".CLKBMUX", str_or_default(ci->params, ctx->id("CLKBMUX"), "CLKB"));
|
||||
|
||||
tg.config.add_enum(ebr + ".RSTAMUX", str_or_default(ci->params, ctx->id("RSTAMUX"), "RSTA"));
|
||||
tg.config.add_enum(ebr + ".RSTBMUX", str_or_default(ci->params, ctx->id("RSTBMUX"), "RSTB"));
|
||||
tg.config.add_enum(ebr + ".WEAMUX", str_or_default(ci->params, ctx->id("WEAMUX"), "WEA"));
|
||||
tg.config.add_enum(ebr + ".WEBMUX", str_or_default(ci->params, ctx->id("WEBMUX"), "WEB"));
|
||||
|
||||
tg.config.add_enum(ebr + ".CEAMUX", str_or_default(ci->params, ctx->id("CEAMUX"), "CEA"));
|
||||
tg.config.add_enum(ebr + ".CEBMUX", str_or_default(ci->params, ctx->id("CEBMUX"), "CEB"));
|
||||
tg.config.add_enum(ebr + ".OCEAMUX", str_or_default(ci->params, ctx->id("OCEAMUX"), "OCEA"));
|
||||
tg.config.add_enum(ebr + ".OCEBMUX", str_or_default(ci->params, ctx->id("OCEBMUX"), "OCEB"));
|
||||
|
||||
tg.config.add_word(ebr + ".CSDECODE_A", csd_a);
|
||||
tg.config.add_word(ebr + ".CSDECODE_B", csd_b);
|
||||
|
||||
std::vector<uint16_t> init_data;
|
||||
init_data.resize(2048, 0x0);
|
||||
// INIT_00 .. INIT_3F
|
||||
for (int i = 0; i <= 0x3F; i++) {
|
||||
IdString param = ctx->id("INITVAL_" +
|
||||
fmt_str(std::hex << std::uppercase << std::setw(2) << std::setfill('0') << i));
|
||||
auto value = parse_init_str(str_or_default(ci->params, param, "0"), 320);
|
||||
for (int j = 0; j < 16; j++) {
|
||||
// INIT parameter consists of 16 18-bit words with 2-bit padding
|
||||
int ofs = 20 * j;
|
||||
for (int k = 0; k < 18; k++) {
|
||||
if (value.at(ofs + k))
|
||||
init_data.at(i * 32 + j * 2 + (k / 9)) |= (1 << (k % 9));
|
||||
}
|
||||
}
|
||||
}
|
||||
int wid = int_or_default(ci->attrs, ctx->id("WID"), 0);
|
||||
NPNR_ASSERT(!cc.bram_data.count(wid));
|
||||
cc.bram_data[wid] = init_data;
|
||||
cc.tilegroups.push_back(tg);
|
||||
} else {
|
||||
NPNR_ASSERT_FALSE("unsupported cell type");
|
||||
}
|
||||
}
|
||||
|
||||
// Fixup tile names
|
||||
fix_tile_names(ctx, cc);
|
||||
// Configure chip
|
||||
if (!text_config_file.empty()) {
|
||||
std::ofstream out_config(text_config_file);
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
#include <iomanip>
|
||||
#include "log.h"
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
@ -274,6 +275,28 @@ std::ostream &operator<<(std::ostream &out, const ChipConfig &cc)
|
||||
out << std::endl;
|
||||
}
|
||||
}
|
||||
for (const auto &bram : cc.bram_data) {
|
||||
out << ".bram_init " << bram.first << std::endl;
|
||||
std::ios_base::fmtflags f(out.flags());
|
||||
for (size_t i = 0; i < bram.second.size(); i++) {
|
||||
out << std::setw(3) << std::setfill('0') << std::hex << bram.second.at(i);
|
||||
if (i % 8 == 7)
|
||||
out << std::endl;
|
||||
else
|
||||
out << " ";
|
||||
}
|
||||
out.flags(f);
|
||||
out << std::endl;
|
||||
}
|
||||
for (const auto &tg : cc.tilegroups) {
|
||||
out << ".tile_group";
|
||||
for (const auto &tile : tg.tiles) {
|
||||
out << " " << tile;
|
||||
}
|
||||
out << std::endl;
|
||||
out << tg.config;
|
||||
out << std::endl;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@ -294,6 +317,29 @@ std::istream &operator>>(std::istream &in, ChipConfig &cc)
|
||||
TileConfig tc;
|
||||
in >> tc;
|
||||
cc.tiles[tilename] = tc;
|
||||
} else if (verb == ".tile_group") {
|
||||
TileGroup tg;
|
||||
std::string line;
|
||||
getline(in, line);
|
||||
std::stringstream ss2(line);
|
||||
|
||||
std::string tile;
|
||||
while (ss2) {
|
||||
ss2 >> tile;
|
||||
tg.tiles.push_back(tile);
|
||||
}
|
||||
in >> tg.config;
|
||||
cc.tilegroups.push_back(tg);
|
||||
} else if (verb == ".bram_init") {
|
||||
uint16_t bram;
|
||||
in >> bram;
|
||||
std::ios_base::fmtflags f(in.flags());
|
||||
while (!skip_check_eor(in)) {
|
||||
uint16_t value;
|
||||
in >> std::hex >> value;
|
||||
cc.bram_data[bram].push_back(value);
|
||||
}
|
||||
in.flags(f);
|
||||
} else {
|
||||
log_error("unrecognised config entry %s\n", verb.c_str());
|
||||
}
|
||||
|
@ -98,6 +98,14 @@ std::ostream &operator<<(std::ostream &out, const TileConfig &tc);
|
||||
|
||||
std::istream &operator>>(std::istream &in, TileConfig &ce);
|
||||
|
||||
// A group of tiles to configure at once for a particular feature that is split across tiles
|
||||
// TileGroups are currently for non-routing configuration only
|
||||
struct TileGroup
|
||||
{
|
||||
std::vector<std::string> tiles;
|
||||
TileConfig config;
|
||||
};
|
||||
|
||||
// This represents the configuration of a chip at a high level
|
||||
class ChipConfig
|
||||
{
|
||||
@ -105,6 +113,8 @@ class ChipConfig
|
||||
std::string chip_name;
|
||||
std::vector<std::string> metadata;
|
||||
std::map<std::string, TileConfig> tiles;
|
||||
std::vector<TileGroup> tilegroups;
|
||||
std::map<uint16_t, std::vector<uint16_t>> bram_data;
|
||||
};
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const ChipConfig &cc);
|
||||
|
@ -54,3 +54,121 @@ X(SRMODE)
|
||||
|
||||
X(CLKI)
|
||||
X(CLKO)
|
||||
|
||||
X(DP16KD)
|
||||
X(DIA0)
|
||||
X(DIA1)
|
||||
X(DIA2)
|
||||
X(DIA3)
|
||||
X(DIA4)
|
||||
X(DIA5)
|
||||
X(DIA6)
|
||||
X(DIA7)
|
||||
X(DIA8)
|
||||
X(DIA9)
|
||||
X(DIA10)
|
||||
X(DIA11)
|
||||
X(DIA12)
|
||||
X(DIA13)
|
||||
X(DIA14)
|
||||
X(DIA15)
|
||||
X(DIA16)
|
||||
X(DIA17)
|
||||
X(ADA0)
|
||||
X(ADA1)
|
||||
X(ADA2)
|
||||
X(ADA3)
|
||||
X(ADA4)
|
||||
X(ADA5)
|
||||
X(ADA6)
|
||||
X(ADA7)
|
||||
X(ADA8)
|
||||
X(ADA9)
|
||||
X(ADA10)
|
||||
X(ADA11)
|
||||
X(ADA12)
|
||||
X(ADA13)
|
||||
X(CEA)
|
||||
X(OCEA)
|
||||
X(CLKA)
|
||||
X(WEA)
|
||||
X(CSA2)
|
||||
X(CSA1)
|
||||
X(CSA0)
|
||||
X(RSTA)
|
||||
X(DIB0)
|
||||
X(DIB1)
|
||||
X(DIB2)
|
||||
X(DIB3)
|
||||
X(DIB4)
|
||||
X(DIB5)
|
||||
X(DIB6)
|
||||
X(DIB7)
|
||||
X(DIB8)
|
||||
X(DIB9)
|
||||
X(DIB10)
|
||||
X(DIB11)
|
||||
X(DIB12)
|
||||
X(DIB13)
|
||||
X(DIB14)
|
||||
X(DIB15)
|
||||
X(DIB16)
|
||||
X(DIB17)
|
||||
X(ADB0)
|
||||
X(ADB1)
|
||||
X(ADB2)
|
||||
X(ADB3)
|
||||
X(ADB4)
|
||||
X(ADB5)
|
||||
X(ADB6)
|
||||
X(ADB7)
|
||||
X(ADB8)
|
||||
X(ADB9)
|
||||
X(ADB10)
|
||||
X(ADB11)
|
||||
X(ADB12)
|
||||
X(ADB13)
|
||||
X(CEB)
|
||||
X(OCEB)
|
||||
X(CLKB)
|
||||
X(WEB)
|
||||
X(CSB2)
|
||||
X(CSB1)
|
||||
X(CSB0)
|
||||
X(RSTB)
|
||||
X(DOA0)
|
||||
X(DOA1)
|
||||
X(DOA2)
|
||||
X(DOA3)
|
||||
X(DOA4)
|
||||
X(DOA5)
|
||||
X(DOA6)
|
||||
X(DOA7)
|
||||
X(DOA8)
|
||||
X(DOA9)
|
||||
X(DOA10)
|
||||
X(DOA11)
|
||||
X(DOA12)
|
||||
X(DOA13)
|
||||
X(DOA14)
|
||||
X(DOA15)
|
||||
X(DOA16)
|
||||
X(DOA17)
|
||||
X(DOB0)
|
||||
X(DOB1)
|
||||
X(DOB2)
|
||||
X(DOB3)
|
||||
X(DOB4)
|
||||
X(DOB5)
|
||||
X(DOB6)
|
||||
X(DOB7)
|
||||
X(DOB8)
|
||||
X(DOB9)
|
||||
X(DOB10)
|
||||
X(DOB11)
|
||||
X(DOB12)
|
||||
X(DOB13)
|
||||
X(DOB14)
|
||||
X(DOB15)
|
||||
X(DOB16)
|
||||
X(DOB17)
|
@ -24,7 +24,7 @@
|
||||
#include "cells.h"
|
||||
#include "log.h"
|
||||
#include "nextpnr.h"
|
||||
|
||||
#include "place_common.h"
|
||||
#define fmt_str(x) (static_cast<const std::ostringstream &>(std::ostringstream() << x).str())
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
@ -260,15 +260,24 @@ class Ecp5GlobalRouter
|
||||
// Attempt to place a DCC
|
||||
void place_dcc(CellInfo *dcc)
|
||||
{
|
||||
BelId best_bel;
|
||||
wirelen_t best_wirelen = 9999999;
|
||||
for (auto bel : ctx->getBels()) {
|
||||
if (ctx->getBelType(bel) == id_DCCA && ctx->checkBelAvail(bel)) {
|
||||
if (ctx->isValidBelForCell(dcc, bel)) {
|
||||
ctx->bindBel(bel, dcc, STRENGTH_LOCKED);
|
||||
return;
|
||||
float tns;
|
||||
wirelen_t wirelen = get_net_metric(ctx, dcc->ports.at(id_CLKI).net, MetricType::WIRELENGTH, tns);
|
||||
if (wirelen < best_wirelen) {
|
||||
best_bel = bel;
|
||||
best_wirelen = wirelen;
|
||||
}
|
||||
ctx->unbindBel(bel);
|
||||
}
|
||||
}
|
||||
}
|
||||
NPNR_ASSERT_FALSE("failed to place dcca");
|
||||
NPNR_ASSERT(best_bel != BelId());
|
||||
ctx->bindBel(best_bel, dcc, STRENGTH_LOCKED);
|
||||
}
|
||||
|
||||
// Insert a DCC into a net to promote it to a global
|
||||
|
18
ecp5/main.cc
18
ecp5/main.cc
@ -50,6 +50,12 @@ po::options_description ECP5CommandHandler::getArchOptions()
|
||||
specific.add_options()("25k", "set device type to LFE5U-25F");
|
||||
specific.add_options()("45k", "set device type to LFE5U-45F");
|
||||
specific.add_options()("85k", "set device type to LFE5U-85F");
|
||||
specific.add_options()("um-25k", "set device type to LFE5UM-25F");
|
||||
specific.add_options()("um-45k", "set device type to LFE5UM-45F");
|
||||
specific.add_options()("um-85k", "set device type to LFE5UM-85F");
|
||||
specific.add_options()("um5g-25k", "set device type to LFE5UM5G-25F");
|
||||
specific.add_options()("um5g-45k", "set device type to LFE5UM5G-45F");
|
||||
specific.add_options()("um5g-85k", "set device type to LFE5UM5G-85F");
|
||||
specific.add_options()("package", po::value<std::string>(), "select device package (defaults to CABGA381)");
|
||||
specific.add_options()("basecfg", po::value<std::string>(), "base chip configuration in Trellis text format");
|
||||
specific.add_options()("textcfg", po::value<std::string>(), "textual configuration in Trellis format to write");
|
||||
@ -84,6 +90,18 @@ std::unique_ptr<Context> ECP5CommandHandler::createContext()
|
||||
chipArgs.type = ArchArgs::LFE5U_45F;
|
||||
if (vm.count("85k"))
|
||||
chipArgs.type = ArchArgs::LFE5U_85F;
|
||||
if (vm.count("um-25k"))
|
||||
chipArgs.type = ArchArgs::LFE5UM_25F;
|
||||
if (vm.count("um-45k"))
|
||||
chipArgs.type = ArchArgs::LFE5UM_45F;
|
||||
if (vm.count("um-85k"))
|
||||
chipArgs.type = ArchArgs::LFE5UM_85F;
|
||||
if (vm.count("um5g-25k"))
|
||||
chipArgs.type = ArchArgs::LFE5UM5G_25F;
|
||||
if (vm.count("um5g-45k"))
|
||||
chipArgs.type = ArchArgs::LFE5UM5G_45F;
|
||||
if (vm.count("um5g-85k"))
|
||||
chipArgs.type = ArchArgs::LFE5UM5G_85F;
|
||||
if (vm.count("package"))
|
||||
chipArgs.package = vm["package"].as<std::string>();
|
||||
else
|
||||
|
66
ecp5/pack.cc
66
ecp5/pack.cc
@ -844,6 +844,19 @@ class Ecp5Packer
|
||||
((!constval && str_or_default(uc->params, ctx->id("LSRMUX"), "LSR") == "LSR") ||
|
||||
(constval && str_or_default(uc->params, ctx->id("LSRMUX"), "LSR") == "INV"))) {
|
||||
uc->ports[user.port].net = nullptr;
|
||||
} else if (uc->type == id_DP16KD) {
|
||||
if (user.port == id_CLKA || user.port == id_CLKB || user.port == id_RSTA || user.port == id_RSTB ||
|
||||
user.port == id_WEA || user.port == id_WEB || user.port == id_CEA || user.port == id_CEB ||
|
||||
user.port == id_OCEA || user.port == id_OCEB || user.port == id_CSA0 || user.port == id_CSA1 ||
|
||||
user.port == id_CSA2 || user.port == id_CSB0 || user.port == id_CSB1 || user.port == id_CSB2) {
|
||||
// Connect to CIB CLK, LSR or CE. Default state is 1
|
||||
uc->params[ctx->id(user.port.str(ctx) + "MUX")] = constval ? user.port.str(ctx) : "INV";
|
||||
} else {
|
||||
// Connected to CIB ABCD. Default state is bitstream configurable
|
||||
uc->params[ctx->id(user.port.str(ctx) + "MUX")] = constval ? "1" : "0";
|
||||
}
|
||||
uc->ports[user.port].net = nullptr;
|
||||
|
||||
} else {
|
||||
uc->ports[user.port].net = constnet;
|
||||
constnet->users.push_back(user);
|
||||
@ -909,10 +922,63 @@ class Ecp5Packer
|
||||
}
|
||||
}
|
||||
|
||||
void autocreate_empty_port(CellInfo *cell, IdString port)
|
||||
{
|
||||
if (!cell->ports.count(port)) {
|
||||
cell->ports[port].name = port;
|
||||
cell->ports[port].net = nullptr;
|
||||
cell->ports[port].type = PORT_IN;
|
||||
}
|
||||
}
|
||||
|
||||
// Pack EBR
|
||||
void pack_ebr()
|
||||
{
|
||||
// Autoincrement WID (starting from 3 seems to match vendor behaviour?)
|
||||
int wid = 3;
|
||||
for (auto cell : sorted(ctx->cells)) {
|
||||
CellInfo *ci = cell.second;
|
||||
if (ci->type == id_DP16KD) {
|
||||
// Add ports, even if disconnected, to ensure correct tie-offs
|
||||
for (int i = 0; i < 14; i++) {
|
||||
autocreate_empty_port(ci, ctx->id("ADA" + std::to_string(i)));
|
||||
autocreate_empty_port(ci, ctx->id("ADB" + std::to_string(i)));
|
||||
}
|
||||
for (int i = 0; i < 18; i++) {
|
||||
autocreate_empty_port(ci, ctx->id("DIA" + std::to_string(i)));
|
||||
autocreate_empty_port(ci, ctx->id("DIB" + std::to_string(i)));
|
||||
}
|
||||
for (int i = 0; i < 3; i++) {
|
||||
autocreate_empty_port(ci, ctx->id("CSA" + std::to_string(i)));
|
||||
autocreate_empty_port(ci, ctx->id("CSB" + std::to_string(i)));
|
||||
}
|
||||
for (int i = 0; i < 3; i++) {
|
||||
autocreate_empty_port(ci, ctx->id("CSA" + std::to_string(i)));
|
||||
autocreate_empty_port(ci, ctx->id("CSB" + std::to_string(i)));
|
||||
}
|
||||
|
||||
autocreate_empty_port(ci, id_CLKA);
|
||||
autocreate_empty_port(ci, id_CEA);
|
||||
autocreate_empty_port(ci, id_OCEA);
|
||||
autocreate_empty_port(ci, id_WEA);
|
||||
autocreate_empty_port(ci, id_RSTA);
|
||||
|
||||
autocreate_empty_port(ci, id_CLKB);
|
||||
autocreate_empty_port(ci, id_CEB);
|
||||
autocreate_empty_port(ci, id_OCEB);
|
||||
autocreate_empty_port(ci, id_WEB);
|
||||
autocreate_empty_port(ci, id_RSTB);
|
||||
|
||||
ci->attrs[ctx->id("WID")] = std::to_string(wid++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void pack()
|
||||
{
|
||||
pack_io();
|
||||
pack_ebr();
|
||||
pack_constants();
|
||||
pack_dram();
|
||||
pack_carries();
|
||||
|
Loading…
Reference in New Issue
Block a user