Merge branch 'master' of gitlab.com:SymbioticEDA/nextpnr into q3k/lock-2-electric-boogaloo
This commit is contained in:
commit
498bef3f3e
@ -214,7 +214,10 @@ foreach (family ${ARCH})
|
||||
# Include family-specific source files to all family targets and set defines appropriately
|
||||
target_include_directories(${target} PRIVATE ${family}/ ${CMAKE_CURRENT_BINARY_DIR}/generated/)
|
||||
target_compile_definitions(${target} PRIVATE NEXTPNR_NAMESPACE=nextpnr_${family} ARCH_${ufamily} ARCHNAME=${family})
|
||||
target_link_libraries(${target} LINK_PUBLIC ${Boost_LIBRARIES} pthread)
|
||||
target_link_libraries(${target} LINK_PUBLIC ${Boost_LIBRARIES})
|
||||
if (NOT MSVC)
|
||||
target_link_libraries(${target} LINK_PUBLIC pthread)
|
||||
endif()
|
||||
add_sanitizers(${target})
|
||||
if (BUILD_GUI)
|
||||
target_include_directories(${target} PRIVATE gui/${family}/ gui/)
|
||||
|
@ -73,6 +73,8 @@ template <typename F1> CellInfo *net_driven_by(const Context *ctx, const NetInfo
|
||||
{
|
||||
if (net == nullptr)
|
||||
return nullptr;
|
||||
if (net->driver.cell == nullptr)
|
||||
return nullptr;
|
||||
if (cell_pred(ctx, net->driver.cell) && net->driver.port == port) {
|
||||
return net->driver.cell;
|
||||
} else {
|
||||
|
@ -25,7 +25,7 @@
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
// Get the total estimated wirelength for a net
|
||||
wirelen_t get_net_wirelength(const Context *ctx, const NetInfo *net, float &tns)
|
||||
wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type, float &tns)
|
||||
{
|
||||
wirelen_t wirelength = 0;
|
||||
int driver_x, driver_y;
|
||||
@ -47,7 +47,7 @@ wirelen_t get_net_wirelength(const Context *ctx, const NetInfo *net, float &tns)
|
||||
CellInfo *load_cell = load.cell;
|
||||
if (load_cell->bel == BelId())
|
||||
continue;
|
||||
if (ctx->timing_driven) {
|
||||
if (ctx->timing_driven && type == MetricType::COST) {
|
||||
WireId user_wire = ctx->getWireBelPin(load_cell->bel, ctx->portPinFromId(load.port));
|
||||
delay_t raw_wl = ctx->estimateDelay(drv_wire, user_wire);
|
||||
float slack = ctx->getDelayNS(load.budget) - ctx->getDelayNS(raw_wl);
|
||||
@ -66,7 +66,7 @@ wirelen_t get_net_wirelength(const Context *ctx, const NetInfo *net, float &tns)
|
||||
xmax = std::max(xmax, load_x);
|
||||
ymax = std::max(ymax, load_y);
|
||||
}
|
||||
if (ctx->timing_driven) {
|
||||
if (ctx->timing_driven && type == MetricType::COST) {
|
||||
wirelength = wirelen_t((((ymax - ymin) + (xmax - xmin)) * std::min(5.0, (1.0 + std::exp(-worst_slack / 5)))));
|
||||
} else {
|
||||
wirelength = wirelen_t((ymax - ymin) + (xmax - xmin));
|
||||
@ -76,7 +76,7 @@ wirelen_t get_net_wirelength(const Context *ctx, const NetInfo *net, float &tns)
|
||||
}
|
||||
|
||||
// Get the total wirelength for a cell
|
||||
wirelen_t get_cell_wirelength(const Context *ctx, const CellInfo *cell)
|
||||
wirelen_t get_cell_metric(const Context *ctx, const CellInfo *cell, MetricType type)
|
||||
{
|
||||
std::set<IdString> nets;
|
||||
for (auto p : cell->ports) {
|
||||
@ -86,16 +86,16 @@ wirelen_t get_cell_wirelength(const Context *ctx, const CellInfo *cell)
|
||||
wirelen_t wirelength = 0;
|
||||
float tns = 0;
|
||||
for (auto n : nets) {
|
||||
wirelength += get_net_wirelength(ctx, ctx->nets.at(n).get(), tns);
|
||||
wirelength += get_net_metric(ctx, ctx->nets.at(n).get(), type, tns);
|
||||
}
|
||||
return wirelength;
|
||||
}
|
||||
|
||||
wirelen_t get_cell_wirelength_at_bel(const Context *ctx, CellInfo *cell, BelId bel)
|
||||
wirelen_t get_cell_metric_at_bel(const Context *ctx, CellInfo *cell, BelId bel, MetricType type)
|
||||
{
|
||||
BelId oldBel = cell->bel;
|
||||
cell->bel = bel;
|
||||
wirelen_t wirelen = get_cell_wirelength(ctx, cell);
|
||||
wirelen_t wirelen = get_cell_metric(ctx, cell, type);
|
||||
cell->bel = oldBel;
|
||||
return wirelen;
|
||||
}
|
||||
@ -118,7 +118,7 @@ bool place_single_cell(Context *ctx, CellInfo *cell, bool require_legality)
|
||||
for (auto bel : ctx->getBels()) {
|
||||
if (ctx->getBelType(bel) == targetType && (!require_legality || ctx->isValidBelForCell(cell, bel))) {
|
||||
if (ctx->checkBelAvail(bel)) {
|
||||
wirelen_t wirelen = get_cell_wirelength_at_bel(ctx, cell, bel);
|
||||
wirelen_t wirelen = get_cell_metric_at_bel(ctx, cell, bel, MetricType::COST);
|
||||
if (iters >= 4)
|
||||
wirelen += ctx->rng(25);
|
||||
if (wirelen <= best_wirelen) {
|
||||
@ -126,7 +126,7 @@ bool place_single_cell(Context *ctx, CellInfo *cell, bool require_legality)
|
||||
best_bel = bel;
|
||||
}
|
||||
} else {
|
||||
wirelen_t wirelen = get_cell_wirelength_at_bel(ctx, cell, bel);
|
||||
wirelen_t wirelen = get_cell_metric_at_bel(ctx, cell, bel, MetricType::COST);
|
||||
if (iters >= 4)
|
||||
wirelen += ctx->rng(25);
|
||||
if (wirelen <= best_ripup_wirelen) {
|
||||
|
@ -26,14 +26,20 @@ NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
typedef int64_t wirelen_t;
|
||||
|
||||
enum class MetricType
|
||||
{
|
||||
COST,
|
||||
WIRELENGTH
|
||||
};
|
||||
|
||||
// Return the wirelength of a net
|
||||
wirelen_t get_net_wirelength(const Context *ctx, const NetInfo *net, float &tns);
|
||||
wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type, float &tns);
|
||||
|
||||
// Return the wirelength of all nets connected to a cell
|
||||
wirelen_t get_cell_wirelength(const Context *ctx, const CellInfo *cell);
|
||||
wirelen_t get_cell_metric(const Context *ctx, const CellInfo *cell, MetricType type);
|
||||
|
||||
// Return the wirelength of all nets connected to a cell, when the cell is at a given bel
|
||||
wirelen_t get_cell_wirelength_at_bel(const Context *ctx, CellInfo *cell, BelId bel);
|
||||
wirelen_t get_cell_metric_at_bel(const Context *ctx, CellInfo *cell, BelId bel, MetricType type);
|
||||
|
||||
// Place a single cell in the lowest wirelength Bel available, optionally requiring validity check
|
||||
bool place_single_cell(Context *ctx, CellInfo *cell, bool require_legality);
|
||||
|
@ -135,17 +135,17 @@ class SAPlacer
|
||||
|
||||
log_info("Running simulated annealing placer.\n");
|
||||
|
||||
// Calculate wirelength after initial placement
|
||||
curr_wirelength = 0;
|
||||
// Calculate metric after initial placement
|
||||
curr_metric = 0;
|
||||
curr_tns = 0;
|
||||
for (auto &net : ctx->nets) {
|
||||
wirelen_t wl = get_net_wirelength(ctx, net.second.get(), curr_tns);
|
||||
wirelengths[net.first] = wl;
|
||||
curr_wirelength += wl;
|
||||
wirelen_t wl = get_net_metric(ctx, net.second.get(), MetricType::COST, curr_tns);
|
||||
metrics[net.first] = wl;
|
||||
curr_metric += wl;
|
||||
}
|
||||
|
||||
int n_no_progress = 0;
|
||||
double avg_wirelength = curr_wirelength;
|
||||
double avg_metric = curr_metric;
|
||||
temp = 10000;
|
||||
|
||||
// Main simulated annealing loop
|
||||
@ -154,9 +154,9 @@ class SAPlacer
|
||||
improved = false;
|
||||
|
||||
if (iter % 5 == 0 || iter == 1)
|
||||
log_info(" at iteration #%d: temp = %f, wire length = "
|
||||
log_info(" at iteration #%d: temp = %f, cost = "
|
||||
"%.0f, est tns = %.02fns\n",
|
||||
iter, temp, double(curr_wirelength), curr_tns);
|
||||
iter, temp, double(curr_metric), curr_tns);
|
||||
|
||||
for (int m = 0; m < 15; ++m) {
|
||||
// Loop through all automatically placed cells
|
||||
@ -177,7 +177,7 @@ class SAPlacer
|
||||
|
||||
if (temp <= 1e-3 && n_no_progress >= 5) {
|
||||
if (iter % 5 != 0)
|
||||
log_info(" at iteration #%d: temp = %f, wire length = %f\n", iter, temp, double(curr_wirelength));
|
||||
log_info(" at iteration #%d: temp = %f, cost = %f\n", iter, temp, double(curr_metric));
|
||||
break;
|
||||
}
|
||||
|
||||
@ -187,8 +187,8 @@ class SAPlacer
|
||||
|
||||
double upper = 0.6, lower = 0.4;
|
||||
|
||||
if (curr_wirelength < 0.95 * avg_wirelength) {
|
||||
avg_wirelength = 0.8 * avg_wirelength + 0.2 * curr_wirelength;
|
||||
if (curr_metric < 0.95 * avg_metric) {
|
||||
avg_metric = 0.8 * avg_metric + 0.2 * curr_metric;
|
||||
} else {
|
||||
if (Raccept >= 0.8) {
|
||||
temp *= 0.7;
|
||||
@ -223,14 +223,14 @@ class SAPlacer
|
||||
assign_budget(ctx);
|
||||
}
|
||||
|
||||
// Recalculate total wirelength entirely to avoid rounding errors
|
||||
// Recalculate total metric entirely to avoid rounding errors
|
||||
// accumulating over time
|
||||
curr_wirelength = 0;
|
||||
curr_metric = 0;
|
||||
curr_tns = 0;
|
||||
for (auto &net : ctx->nets) {
|
||||
wirelen_t wl = get_net_wirelength(ctx, net.second.get(), curr_tns);
|
||||
wirelengths[net.first] = wl;
|
||||
curr_wirelength += wl;
|
||||
wirelen_t wl = get_net_metric(ctx, net.second.get(), MetricType::COST, curr_tns);
|
||||
metrics[net.first] = wl;
|
||||
curr_metric += wl;
|
||||
}
|
||||
}
|
||||
// Final post-pacement validitiy check
|
||||
@ -320,7 +320,7 @@ class SAPlacer
|
||||
if (other_cell->belStrength > STRENGTH_WEAK)
|
||||
return false;
|
||||
}
|
||||
wirelen_t new_wirelength = 0, delta;
|
||||
wirelen_t new_metric = 0, delta;
|
||||
ctx->unbindBel(oldBel);
|
||||
if (other != IdString()) {
|
||||
ctx->unbindBel(newBel);
|
||||
@ -350,17 +350,17 @@ class SAPlacer
|
||||
}
|
||||
}
|
||||
|
||||
new_wirelength = curr_wirelength;
|
||||
new_metric = curr_metric;
|
||||
|
||||
// Recalculate wirelengths for all nets touched by the peturbation
|
||||
// Recalculate metrics for all nets touched by the peturbation
|
||||
for (auto net : update) {
|
||||
new_wirelength -= wirelengths.at(net->name);
|
||||
new_metric -= metrics.at(net->name);
|
||||
float temp_tns = 0;
|
||||
wirelen_t net_new_wl = get_net_wirelength(ctx, net, temp_tns);
|
||||
new_wirelength += net_new_wl;
|
||||
wirelen_t net_new_wl = get_net_metric(ctx, net, MetricType::COST, temp_tns);
|
||||
new_metric += net_new_wl;
|
||||
new_lengths.push_back(std::make_pair(net->name, net_new_wl));
|
||||
}
|
||||
delta = new_wirelength - curr_wirelength;
|
||||
delta = new_metric - curr_metric;
|
||||
n_move++;
|
||||
// SA acceptance criterea
|
||||
if (delta < 0 || (temp > 1e-6 && (ctx->rng() / float(0x3fffffff)) <= std::exp(-delta / temp))) {
|
||||
@ -373,9 +373,9 @@ class SAPlacer
|
||||
ctx->unbindBel(newBel);
|
||||
goto swap_fail;
|
||||
}
|
||||
curr_wirelength = new_wirelength;
|
||||
curr_metric = new_metric;
|
||||
for (auto new_wl : new_lengths)
|
||||
wirelengths.at(new_wl.first) = new_wl.second;
|
||||
metrics.at(new_wl.first) = new_wl.second;
|
||||
|
||||
return true;
|
||||
swap_fail:
|
||||
@ -413,8 +413,8 @@ class SAPlacer
|
||||
}
|
||||
|
||||
Context *ctx;
|
||||
std::unordered_map<IdString, wirelen_t> wirelengths;
|
||||
wirelen_t curr_wirelength = std::numeric_limits<wirelen_t>::max();
|
||||
std::unordered_map<IdString, wirelen_t> metrics;
|
||||
wirelen_t curr_metric = std::numeric_limits<wirelen_t>::max();
|
||||
float curr_tns = 0;
|
||||
float temp = 1000;
|
||||
bool improved = false;
|
||||
|
21
ecp5/arch.cc
21
ecp5/arch.cc
@ -94,7 +94,7 @@ static const ChipInfoPOD *get_chip_info(const RelPtr<ChipInfoPOD> *ptr) { return
|
||||
void load_chipdb();
|
||||
#endif
|
||||
|
||||
#define LFE5U_45F_ONLY
|
||||
//#define LFE5U_45F_ONLY
|
||||
|
||||
Arch::Arch(ArchArgs args) : args(args)
|
||||
{
|
||||
@ -118,6 +118,14 @@ Arch::Arch(ArchArgs args) : args(args)
|
||||
log_error("Unsupported ECP5 chip type.\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
id_trellis_slice = id("TRELLIS_SLICE");
|
||||
id_clk = id("CLK");
|
||||
id_lsr = id("LSR");
|
||||
id_clkmux = id("CLKMUX");
|
||||
id_lsrmux = id("LSRMUX");
|
||||
id_srmode = id("SRMODE");
|
||||
id_mode = id("MODE");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
@ -181,7 +189,10 @@ BelRange Arch::getBelsAtSameTile(BelId bel) const
|
||||
br.b.cursor_tile = bel.location.y * chip_info->width + bel.location.x;
|
||||
br.e.cursor_tile = bel.location.y * chip_info->width + bel.location.x;
|
||||
br.b.cursor_index = 0;
|
||||
br.e.cursor_index = locInfo(bel)->num_bels;
|
||||
br.e.cursor_index = locInfo(bel)->num_bels - 1;
|
||||
br.b.chip = chip_info;
|
||||
br.e.chip = chip_info;
|
||||
++br.e;
|
||||
return br;
|
||||
}
|
||||
|
||||
@ -315,12 +326,6 @@ DecalXY Arch::getGroupDecal(GroupId pip) const { return {}; };
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const { return true; }
|
||||
|
||||
bool Arch::isBelLocationValid(BelId bel) const { return true; }
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, delay_t &delay) const
|
||||
{
|
||||
return false;
|
||||
|
@ -760,6 +760,14 @@ struct Arch : BaseCtx
|
||||
// Placement validity checks
|
||||
bool isValidBelForCell(CellInfo *cell, BelId bel) const;
|
||||
bool isBelLocationValid(BelId bel) const;
|
||||
|
||||
// Helper function for above
|
||||
bool slicesCompatible(const std::vector<const CellInfo *> &cells) const;
|
||||
|
||||
IdString id_trellis_slice;
|
||||
IdString id_clk, id_lsr;
|
||||
IdString id_clkmux, id_lsrmux;
|
||||
IdString id_srmode, id_mode;
|
||||
};
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
109
ecp5/arch_place.cc
Normal file
109
ecp5/arch_place.cc
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* nextpnr -- Next Generation Place and Route
|
||||
*
|
||||
* Copyright (C) 2018 David Shah <david@symbioticeda.com>
|
||||
*
|
||||
* 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 "cells.h"
|
||||
#include "log.h"
|
||||
#include "nextpnr.h"
|
||||
#include "util.h"
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
inline NetInfo *port_or_nullptr(const CellInfo *cell, IdString name)
|
||||
{
|
||||
auto found = cell->ports.find(name);
|
||||
if (found == cell->ports.end())
|
||||
return nullptr;
|
||||
return found->second.net;
|
||||
}
|
||||
|
||||
bool Arch::slicesCompatible(const std::vector<const CellInfo *> &cells) const
|
||||
{
|
||||
// TODO: allow different LSR/CLK and MUX/SRMODE settings once
|
||||
// routing details are worked out
|
||||
NetInfo *clk_sig = nullptr, *lsr_sig = nullptr;
|
||||
std::string CLKMUX, LSRMUX, SRMODE;
|
||||
bool first = true;
|
||||
for (auto cell : cells) {
|
||||
if (first) {
|
||||
clk_sig = port_or_nullptr(cell, id_clk);
|
||||
lsr_sig = port_or_nullptr(cell, id_lsr);
|
||||
CLKMUX = str_or_default(cell->params, id_clkmux, "CLK");
|
||||
LSRMUX = str_or_default(cell->params, id_lsrmux, "LSR");
|
||||
SRMODE = str_or_default(cell->params, id_srmode, "CE_OVER_LSR");
|
||||
} else {
|
||||
if (port_or_nullptr(cell, id_clk) != clk_sig)
|
||||
return false;
|
||||
if (port_or_nullptr(cell, id_lsr) != lsr_sig)
|
||||
return false;
|
||||
if (str_or_default(cell->params, id_clkmux, "CLK") != CLKMUX)
|
||||
return false;
|
||||
if (str_or_default(cell->params, id_lsrmux, "LSR") != LSRMUX)
|
||||
return false;
|
||||
if (str_or_default(cell->params, id_srmode, "CE_OVER_LSR") != SRMODE)
|
||||
return false;
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Arch::isBelLocationValid(BelId bel) const
|
||||
{
|
||||
if (getBelType(bel) == TYPE_TRELLIS_SLICE) {
|
||||
std::vector<const CellInfo *> bel_cells;
|
||||
for (auto bel_other : getBelsAtSameTile(bel)) {
|
||||
IdString cell_other = getBoundBelCell(bel_other);
|
||||
if (cell_other != IdString()) {
|
||||
const CellInfo *ci_other = cells.at(cell_other).get();
|
||||
bel_cells.push_back(ci_other);
|
||||
}
|
||||
}
|
||||
return slicesCompatible(bel_cells);
|
||||
} else {
|
||||
IdString cellId = getBoundBelCell(bel);
|
||||
if (cellId == IdString())
|
||||
return true;
|
||||
else
|
||||
return isValidBelForCell(cells.at(cellId).get(), bel);
|
||||
}
|
||||
}
|
||||
|
||||
bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const
|
||||
{
|
||||
if (cell->type == id_trellis_slice) {
|
||||
NPNR_ASSERT(getBelType(bel) == TYPE_TRELLIS_SLICE);
|
||||
|
||||
std::vector<const CellInfo *> bel_cells;
|
||||
|
||||
for (auto bel_other : getBelsAtSameTile(bel)) {
|
||||
IdString cell_other = getBoundBelCell(bel_other);
|
||||
if (cell_other != IdString() && bel_other != bel) {
|
||||
const CellInfo *ci_other = cells.at(cell_other).get();
|
||||
bel_cells.push_back(ci_other);
|
||||
}
|
||||
}
|
||||
|
||||
bel_cells.push_back(cell);
|
||||
return slicesCompatible(bel_cells);
|
||||
} else {
|
||||
// other checks
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
@ -214,6 +214,18 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
|
||||
cc.tiles[tname].add_enum(slice + ".REG1.REGSET",
|
||||
str_or_default(ci->params, ctx->id("REG1_REGSET"), "RESET"));
|
||||
cc.tiles[tname].add_enum(slice + ".CEMUX", str_or_default(ci->params, ctx->id("CEMUX"), "1"));
|
||||
IdString lsrnet;
|
||||
if (ci->ports.find(ctx->id("LSR")) != ci->ports.end() && ci->ports.at(ctx->id("LSR")).net != nullptr)
|
||||
lsrnet = ci->ports.at(ctx->id("LSR")).net->name;
|
||||
if (ctx->getBoundWireNet(ctx->getWireByName(
|
||||
ctx->id(fmt_str("X" << bel.location.x << "/Y" << bel.location.y << "/LSR0")))) == lsrnet) {
|
||||
cc.tiles[tname].add_enum("LSR0.SRMODE", str_or_default(ci->params, ctx->id("SRMODE"), "LSR_OVER_CE"));
|
||||
cc.tiles[tname].add_enum("LSR0.LSRMUX", str_or_default(ci->params, ctx->id("LSRMUX"), "LSR"));
|
||||
} else if (ctx->getBoundWireNet(ctx->getWireByName(ctx->id(
|
||||
fmt_str("X" << bel.location.x << "/Y" << bel.location.y << "/LSR1")))) == lsrnet) {
|
||||
cc.tiles[tname].add_enum("LSR1.SRMODE", str_or_default(ci->params, ctx->id("SRMODE"), "LSR_OVER_CE"));
|
||||
cc.tiles[tname].add_enum("LSR1.LSRMUX", str_or_default(ci->params, ctx->id("LSRMUX"), "LSR"));
|
||||
}
|
||||
// TODO: CLKMUX, CEMUX, carry
|
||||
} else if (ci->type == ctx->id("TRELLIS_IO")) {
|
||||
std::string pio = ctx->locInfo(bel)->bel_data[bel.index].name.get();
|
||||
|
180
ecp5/cells.cc
Normal file
180
ecp5/cells.cc
Normal file
@ -0,0 +1,180 @@
|
||||
/*
|
||||
* nextpnr -- Next Generation Place and Route
|
||||
*
|
||||
* Copyright (C) 2018 David Shah <david@symbioticeda.com>
|
||||
*
|
||||
* 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 "cells.h"
|
||||
#include <algorithm>
|
||||
#include "design_utils.h"
|
||||
#include "log.h"
|
||||
#include "util.h"
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
void add_port(const Context *ctx, CellInfo *cell, std::string name, PortType dir)
|
||||
{
|
||||
IdString id = ctx->id(name);
|
||||
cell->ports[id] = PortInfo{id, nullptr, dir};
|
||||
}
|
||||
|
||||
std::unique_ptr<CellInfo> create_ecp5_cell(Context *ctx, IdString type, std::string name)
|
||||
{
|
||||
static int auto_idx = 0;
|
||||
std::unique_ptr<CellInfo> new_cell = std::unique_ptr<CellInfo>(new CellInfo());
|
||||
if (name.empty()) {
|
||||
new_cell->name = ctx->id("$nextpnr_" + type.str(ctx) + "_" + std::to_string(auto_idx++));
|
||||
} else {
|
||||
new_cell->name = ctx->id(name);
|
||||
}
|
||||
new_cell->type = type;
|
||||
if (type == ctx->id("TRELLIS_SLICE")) {
|
||||
new_cell->params[ctx->id("MODE")] = "LOGIC";
|
||||
new_cell->params[ctx->id("GSR")] = "DISABLED";
|
||||
new_cell->params[ctx->id("SRMODE")] = "LSR_OVER_CE";
|
||||
new_cell->params[ctx->id("CEMUX")] = "1";
|
||||
new_cell->params[ctx->id("CLKMUX")] = "CLK";
|
||||
new_cell->params[ctx->id("LSRMUX")] = "LSR";
|
||||
new_cell->params[ctx->id("LUT0_INITVAL")] = "0";
|
||||
new_cell->params[ctx->id("LUT1_INITVAL")] = "0";
|
||||
new_cell->params[ctx->id("REG0_SD")] = "0";
|
||||
new_cell->params[ctx->id("REG1_SD")] = "0";
|
||||
new_cell->params[ctx->id("REG0_REGSET")] = "RESET";
|
||||
new_cell->params[ctx->id("REG1_REGSET")] = "RESET";
|
||||
new_cell->params[ctx->id("CCU2_INJECT1_0")] = "NO";
|
||||
new_cell->params[ctx->id("CCU2_INJECT1_1")] = "NO";
|
||||
new_cell->params[ctx->id("WREMUX")] = "WRE";
|
||||
|
||||
add_port(ctx, new_cell.get(), "A0", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "B0", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "C0", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "D0", PORT_IN);
|
||||
|
||||
add_port(ctx, new_cell.get(), "A1", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "B1", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "C1", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "D1", PORT_IN);
|
||||
|
||||
add_port(ctx, new_cell.get(), "M0", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "M1", PORT_IN);
|
||||
|
||||
add_port(ctx, new_cell.get(), "FCI", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "FXA", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "FXB", PORT_IN);
|
||||
|
||||
add_port(ctx, new_cell.get(), "CLK", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "LSR", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "CE", PORT_IN);
|
||||
|
||||
add_port(ctx, new_cell.get(), "DI0", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "DI1", PORT_IN);
|
||||
|
||||
add_port(ctx, new_cell.get(), "WD0", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "WD1", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "WAD0", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "WAD1", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "WAD2", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "WAD3", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "WRE", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "WCK", PORT_IN);
|
||||
|
||||
add_port(ctx, new_cell.get(), "F0", PORT_OUT);
|
||||
add_port(ctx, new_cell.get(), "Q0", PORT_OUT);
|
||||
add_port(ctx, new_cell.get(), "F1", PORT_OUT);
|
||||
add_port(ctx, new_cell.get(), "Q1", PORT_OUT);
|
||||
|
||||
add_port(ctx, new_cell.get(), "FCO", PORT_OUT);
|
||||
add_port(ctx, new_cell.get(), "OFX0", PORT_OUT);
|
||||
add_port(ctx, new_cell.get(), "OFX1", PORT_OUT);
|
||||
|
||||
add_port(ctx, new_cell.get(), "WDO0", PORT_OUT);
|
||||
add_port(ctx, new_cell.get(), "WDO1", PORT_OUT);
|
||||
add_port(ctx, new_cell.get(), "WDO2", PORT_OUT);
|
||||
add_port(ctx, new_cell.get(), "WDO3", PORT_OUT);
|
||||
add_port(ctx, new_cell.get(), "WADO0", PORT_OUT);
|
||||
add_port(ctx, new_cell.get(), "WADO1", PORT_OUT);
|
||||
add_port(ctx, new_cell.get(), "WADO2", PORT_OUT);
|
||||
add_port(ctx, new_cell.get(), "WADO3", PORT_OUT);
|
||||
} else if (type == ctx->id("TRELLIS_IO")) {
|
||||
new_cell->params[ctx->id("DIR")] = "INPUT";
|
||||
new_cell->attrs[ctx->id("IO_TYPE")] = "LVCMOS33";
|
||||
|
||||
add_port(ctx, new_cell.get(), "B", PORT_INOUT);
|
||||
add_port(ctx, new_cell.get(), "I", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "T", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "O", PORT_OUT);
|
||||
} else {
|
||||
log_error("unable to create ECP5 cell of type %s", type.c_str(ctx));
|
||||
}
|
||||
return new_cell;
|
||||
}
|
||||
|
||||
static void set_param_safe(bool has_ff, CellInfo *lc, IdString name, const std::string &value)
|
||||
{
|
||||
NPNR_ASSERT(!has_ff || lc->params.at(name) == value);
|
||||
lc->params[name] = value;
|
||||
}
|
||||
|
||||
static void replace_port_safe(bool has_ff, CellInfo *ff, IdString ff_port, CellInfo *lc, IdString lc_port)
|
||||
{
|
||||
if (has_ff) {
|
||||
NPNR_ASSERT(lc->ports.at(lc_port).net == ff->ports.at(ff_port).net);
|
||||
NetInfo *ffnet = ff->ports.at(ff_port).net;
|
||||
if (ffnet != nullptr)
|
||||
ffnet->users.erase(
|
||||
std::remove_if(ffnet->users.begin(), ffnet->users.end(),
|
||||
[ff, ff_port](PortRef port) { return port.cell == ff && port.port == ff_port; }),
|
||||
ffnet->users.end());
|
||||
} else {
|
||||
replace_port(ff, ff_port, lc, lc_port);
|
||||
}
|
||||
}
|
||||
|
||||
void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool driven_by_lut)
|
||||
{
|
||||
bool has_ff = lc->ports.at(ctx->id("Q0")).net != nullptr || lc->ports.at(ctx->id("Q1")).net != nullptr;
|
||||
std::string reg = "REG" + std::to_string(index);
|
||||
set_param_safe(has_ff, lc, ctx->id("SRMODE"), str_or_default(ff->params, ctx->id("SRMODE"), "LSR_OVER_CE"));
|
||||
set_param_safe(has_ff, lc, ctx->id("GSR"), str_or_default(ff->params, ctx->id("GSR"), "DISABLED"));
|
||||
set_param_safe(has_ff, lc, ctx->id("CEMUX"), str_or_default(ff->params, ctx->id("CEMUX"), "1"));
|
||||
set_param_safe(has_ff, lc, ctx->id("LSRMUX"), str_or_default(ff->params, ctx->id("LSRMUX"), "LSR"));
|
||||
lc->params[ctx->id(reg + "_SD")] = driven_by_lut ? "1" : "0";
|
||||
lc->params[ctx->id(reg + "_REGSET")] = str_or_default(ff->params, ctx->id("REGSET"), "RESET");
|
||||
replace_port_safe(has_ff, ff, ctx->id("CLK"), lc, ctx->id("CLK"));
|
||||
if (ff->ports.find(ctx->id("LSR")) != ff->ports.end())
|
||||
replace_port_safe(has_ff, ff, ctx->id("LSR"), lc, ctx->id("LSR"));
|
||||
if (ff->ports.find(ctx->id("CE")) != ff->ports.end())
|
||||
replace_port_safe(has_ff, ff, ctx->id("CE"), lc, ctx->id("CE"));
|
||||
|
||||
replace_port(ff, ctx->id("Q"), lc, ctx->id("Q" + std::to_string(index)));
|
||||
if (driven_by_lut) {
|
||||
replace_port(ff, ctx->id("DI"), lc, ctx->id("DI" + std::to_string(index)));
|
||||
} else {
|
||||
replace_port(ff, ctx->id("DI"), lc, ctx->id("M" + std::to_string(index)));
|
||||
}
|
||||
}
|
||||
|
||||
void lut_to_slice(Context *ctx, CellInfo *lut, CellInfo *lc, int index)
|
||||
{
|
||||
lc->params[ctx->id("LUT" + std::to_string(index) + "_INITVAL")] = str_or_default(lc->params, ctx->id("INIT"), "0");
|
||||
replace_port(lut, ctx->id("A"), lc, ctx->id("A" + std::to_string(index)));
|
||||
replace_port(lut, ctx->id("B"), lc, ctx->id("B" + std::to_string(index)));
|
||||
replace_port(lut, ctx->id("C"), lc, ctx->id("C" + std::to_string(index)));
|
||||
replace_port(lut, ctx->id("D"), lc, ctx->id("D" + std::to_string(index)));
|
||||
replace_port(lut, ctx->id("Z"), lc, ctx->id("F" + std::to_string(index)));
|
||||
}
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
54
ecp5/cells.h
Normal file
54
ecp5/cells.h
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* nextpnr -- Next Generation Place and Route
|
||||
*
|
||||
* Copyright (C) 2018 David Shah <david@symbioticeda.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ECP5_CELLS_H
|
||||
#define ECP5_CELLS_H
|
||||
|
||||
#include "nextpnr.h"
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
// Create a standard ECP5 cell and return it
|
||||
// Name will be automatically assigned if not specified
|
||||
std::unique_ptr<CellInfo> create_ecp5_cell(Context *ctx, IdString type, std::string name = "");
|
||||
|
||||
// Return true if a cell is a LUT
|
||||
inline bool is_lut(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("LUT4"); }
|
||||
|
||||
// Return true if a cell is a flipflop
|
||||
inline bool is_ff(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("TRELLIS_FF"); }
|
||||
|
||||
inline bool is_carry(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("CCU2C"); }
|
||||
|
||||
inline bool is_lc(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("TRELLIS_LC"); }
|
||||
|
||||
inline bool is_trellis_io(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("TRELLIS_IO"); }
|
||||
|
||||
inline bool is_dpram(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("TRELLIS_DPR16X4"); }
|
||||
|
||||
inline bool is_pfumx(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("PFUMX"); }
|
||||
|
||||
inline bool is_l6mux(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("L6MUX21"); }
|
||||
|
||||
void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool driven_by_lut);
|
||||
void lut_to_slice(Context *ctx, CellInfo *lut, CellInfo *lc, int index);
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
||||
#endif
|
@ -1,5 +1,5 @@
|
||||
|
||||
set(devices 45k)
|
||||
set(devices 25k 45k 85k)
|
||||
|
||||
if (NOT DEFINED TRELLIS_ROOT)
|
||||
message(FATAL_ERROR "you must define TRELLIS_ROOT using -DTRELLIS_ROOT=/path/to/prjtrellis for ECP5 support")
|
||||
|
13
ecp5/main.cc
13
ecp5/main.cc
@ -63,6 +63,11 @@ int main(int argc, char *argv[])
|
||||
#ifndef NO_GUI
|
||||
options.add_options()("gui", "start gui");
|
||||
#endif
|
||||
|
||||
options.add_options()("25k", "set device type to LFE5U-25F");
|
||||
options.add_options()("45k", "set device type to LFE5U-45F");
|
||||
options.add_options()("85k", "set device type to LFE5U-85F");
|
||||
|
||||
options.add_options()("json", po::value<std::string>(), "JSON design file to ingest");
|
||||
options.add_options()("seed", po::value<int>(), "seed value for random number generator");
|
||||
|
||||
@ -111,6 +116,14 @@ int main(int argc, char *argv[])
|
||||
|
||||
ArchArgs args;
|
||||
args.type = ArchArgs::LFE5U_45F;
|
||||
|
||||
if (vm.count("25k"))
|
||||
args.type = ArchArgs::LFE5U_25F;
|
||||
if (vm.count("45k"))
|
||||
args.type = ArchArgs::LFE5U_45F;
|
||||
if (vm.count("85k"))
|
||||
args.type = ArchArgs::LFE5U_85F;
|
||||
|
||||
args.package = "CABGA381";
|
||||
args.speed = 6;
|
||||
std::unique_ptr<Context> ctx = std::unique_ptr<Context>(new Context(args));
|
||||
|
330
ecp5/pack.cc
330
ecp5/pack.cc
@ -20,6 +20,7 @@
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <unordered_set>
|
||||
#include "cells.h"
|
||||
#include "design_utils.h"
|
||||
#include "log.h"
|
||||
#include "util.h"
|
||||
@ -32,13 +33,165 @@ static bool is_nextpnr_iob(Context *ctx, CellInfo *cell)
|
||||
cell->type == ctx->id("$nextpnr_iobuf");
|
||||
}
|
||||
|
||||
static bool is_trellis_io(const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("TRELLIS_IO"); }
|
||||
class Ecp5Packer
|
||||
{
|
||||
public:
|
||||
Ecp5Packer(Context *ctx) : ctx(ctx){};
|
||||
|
||||
private:
|
||||
// Process the contents of packed_cells and new_cells
|
||||
void flush_cells()
|
||||
{
|
||||
for (auto pcell : packed_cells) {
|
||||
ctx->cells.erase(pcell);
|
||||
}
|
||||
for (auto &ncell : new_cells) {
|
||||
ctx->cells[ncell->name] = std::move(ncell);
|
||||
}
|
||||
packed_cells.clear();
|
||||
new_cells.clear();
|
||||
}
|
||||
|
||||
// Find FFs associated with LUTs, or LUT expansion muxes
|
||||
void find_lutff_pairs()
|
||||
{
|
||||
log_info("Finding LUTFF pairs...\n");
|
||||
for (auto cell : sorted(ctx->cells)) {
|
||||
CellInfo *ci = cell.second;
|
||||
if (is_lut(ctx, ci) || is_pfumx(ctx, ci) || is_l6mux(ctx, ci)) {
|
||||
NetInfo *znet = ci->ports.at(ctx->id("Z")).net;
|
||||
if (znet != nullptr) {
|
||||
CellInfo *ff = net_only_drives(ctx, znet, is_ff, ctx->id("DI"), false);
|
||||
if (ff != nullptr) {
|
||||
lutffPairs[ci->name] = ff->name;
|
||||
fflutPairs[ff->name] = ci->name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return whether two FFs can be packed together in the same slice
|
||||
bool can_pack_ffs(CellInfo *ff0, CellInfo *ff1)
|
||||
{
|
||||
if (str_or_default(ff0->params, ctx->id("GSR"), "DISABLED") !=
|
||||
str_or_default(ff1->params, ctx->id("GSR"), "DISABLED"))
|
||||
return false;
|
||||
if (str_or_default(ff0->params, ctx->id("SRMODE"), "LSR_OVER_CE") !=
|
||||
str_or_default(ff1->params, ctx->id("SRMODE"), "LSR_OVER_CE"))
|
||||
return false;
|
||||
if (str_or_default(ff0->params, ctx->id("CEMUX"), "1") != str_or_default(ff1->params, ctx->id("CEMUX"), "1"))
|
||||
return false;
|
||||
if (str_or_default(ff0->params, ctx->id("LSRMUX"), "LSR") !=
|
||||
str_or_default(ff1->params, ctx->id("LSRMUX"), "LSR"))
|
||||
return false;
|
||||
if (str_or_default(ff0->params, ctx->id("CLKMUX"), "CLK") !=
|
||||
str_or_default(ff1->params, ctx->id("CLKMUX"), "CLK"))
|
||||
return false;
|
||||
if (ff0->ports.at(ctx->id("CLK")).net != ff1->ports.at(ctx->id("CLK")).net)
|
||||
return false;
|
||||
if (ff0->ports.at(ctx->id("CE")).net != ff1->ports.at(ctx->id("CE")).net)
|
||||
return false;
|
||||
if (ff0->ports.at(ctx->id("LSR")).net != ff1->ports.at(ctx->id("LSR")).net)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Return true if two LUTs can be paired considering FF compatibility
|
||||
bool can_pack_lutff(IdString lut0, IdString lut1)
|
||||
{
|
||||
auto ff0 = lutffPairs.find(lut0), ff1 = lutffPairs.find(lut1);
|
||||
if (ff0 != lutffPairs.end() && ff1 != lutffPairs.end()) {
|
||||
return can_pack_ffs(ctx->cells.at(ff0->second).get(), ctx->cells.at(ff1->second).get());
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Find "closely connected" LUTs and pair them together
|
||||
void pair_luts()
|
||||
{
|
||||
log_info("Finding LUT-LUT pairs...\n");
|
||||
std::unordered_set<IdString> procdLuts;
|
||||
for (auto cell : sorted(ctx->cells)) {
|
||||
CellInfo *ci = cell.second;
|
||||
if (is_lut(ctx, ci) && procdLuts.find(cell.first) == procdLuts.end()) {
|
||||
NetInfo *znet = ci->ports.at(ctx->id("Z")).net;
|
||||
if (znet != nullptr) {
|
||||
for (auto user : znet->users) {
|
||||
if (is_lut(ctx, user.cell) && user.cell != ci &&
|
||||
procdLuts.find(user.cell->name) == procdLuts.end()) {
|
||||
if (can_pack_lutff(ci->name, user.cell->name)) {
|
||||
procdLuts.insert(ci->name);
|
||||
procdLuts.insert(user.cell->name);
|
||||
lutPairs[ci->name] = user.cell->name;
|
||||
goto paired;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (false) {
|
||||
paired:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (lutffPairs.find(ci->name) != lutffPairs.end()) {
|
||||
NetInfo *qnet = ctx->cells.at(lutffPairs[ci->name])->ports.at(ctx->id("Q")).net;
|
||||
if (qnet != nullptr) {
|
||||
for (auto user : qnet->users) {
|
||||
if (is_lut(ctx, user.cell) && user.cell != ci &&
|
||||
procdLuts.find(user.cell->name) == procdLuts.end()) {
|
||||
if (can_pack_lutff(ci->name, user.cell->name)) {
|
||||
procdLuts.insert(ci->name);
|
||||
procdLuts.insert(user.cell->name);
|
||||
lutPairs[ci->name] = user.cell->name;
|
||||
goto paired_ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (false) {
|
||||
paired_ff:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const char *inp : {"A", "B", "C", "D"}) {
|
||||
NetInfo *innet = ci->ports.at(ctx->id(inp)).net;
|
||||
if (innet != nullptr && innet->driver.cell != nullptr) {
|
||||
CellInfo *drv = innet->driver.cell;
|
||||
if (is_lut(ctx, drv) && drv != ci && innet->driver.port == ctx->id("Z")) {
|
||||
if (procdLuts.find(drv->name) == procdLuts.end()) {
|
||||
if (can_pack_lutff(ci->name, drv->name)) {
|
||||
procdLuts.insert(ci->name);
|
||||
procdLuts.insert(drv->name);
|
||||
lutPairs[ci->name] = drv->name;
|
||||
goto paired_inlut;
|
||||
}
|
||||
}
|
||||
} else if (is_ff(ctx, drv) && innet->driver.port == ctx->id("Q")) {
|
||||
auto fflut = fflutPairs.find(drv->name);
|
||||
if (fflut != fflutPairs.end() && fflut->second != ci->name &&
|
||||
procdLuts.find(fflut->second) == procdLuts.end()) {
|
||||
if (can_pack_lutff(ci->name, fflut->second)) {
|
||||
procdLuts.insert(ci->name);
|
||||
procdLuts.insert(fflut->second);
|
||||
lutPairs[ci->name] = fflut->second;
|
||||
goto paired_inlut;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (false) {
|
||||
paired_inlut:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Simple "packer" to remove nextpnr IOBUFs, this assumes IOBUFs are manually instantiated
|
||||
void pack_io(Context *ctx)
|
||||
void pack_io()
|
||||
{
|
||||
std::unordered_set<IdString> packed_cells;
|
||||
std::vector<std::unique_ptr<CellInfo>> new_cells;
|
||||
log_info("Packing IOs..\n");
|
||||
|
||||
for (auto cell : sorted(ctx->cells)) {
|
||||
@ -74,21 +227,176 @@ void pack_io(Context *ctx)
|
||||
std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(trio->attrs, trio->attrs.begin()));
|
||||
}
|
||||
}
|
||||
for (auto pcell : packed_cells) {
|
||||
ctx->cells.erase(pcell);
|
||||
}
|
||||
for (auto &ncell : new_cells) {
|
||||
ctx->cells[ncell->name] = std::move(ncell);
|
||||
}
|
||||
flush_cells();
|
||||
}
|
||||
|
||||
// Pass to pack LUT5s into a newly created slice
|
||||
void pack_lut5s()
|
||||
{
|
||||
log_info("Packing LUT5s...\n");
|
||||
for (auto cell : sorted(ctx->cells)) {
|
||||
CellInfo *ci = cell.second;
|
||||
if (is_pfumx(ctx, ci)) {
|
||||
std::unique_ptr<CellInfo> packed =
|
||||
create_ecp5_cell(ctx, ctx->id("TRELLIS_SLICE"), ci->name.str(ctx) + "_SLICE");
|
||||
NetInfo *f0 = ci->ports.at(ctx->id("BLUT")).net;
|
||||
if (f0 == nullptr)
|
||||
log_error("PFUMX '%s' has disconnected port 'BLUT'\n", ci->name.c_str(ctx));
|
||||
NetInfo *f1 = ci->ports.at(ctx->id("ALUT")).net;
|
||||
if (f1 == nullptr)
|
||||
log_error("PFUMX '%s' has disconnected port 'ALUT'\n", ci->name.c_str(ctx));
|
||||
CellInfo *lut0 = net_driven_by(ctx, f0, is_lut, ctx->id("Z"));
|
||||
CellInfo *lut1 = net_driven_by(ctx, f1, is_lut, ctx->id("Z"));
|
||||
if (lut0 == nullptr)
|
||||
log_error("PFUMX '%s' has BLUT driven by cell other than a LUT\n", ci->name.c_str(ctx));
|
||||
if (lut1 == nullptr)
|
||||
log_error("PFUMX '%s' has ALUT driven by cell other than a LUT\n", ci->name.c_str(ctx));
|
||||
replace_port(lut0, ctx->id("A"), packed.get(), ctx->id("A0"));
|
||||
replace_port(lut0, ctx->id("B"), packed.get(), ctx->id("B0"));
|
||||
replace_port(lut0, ctx->id("C"), packed.get(), ctx->id("C0"));
|
||||
replace_port(lut0, ctx->id("D"), packed.get(), ctx->id("D0"));
|
||||
replace_port(lut1, ctx->id("A"), packed.get(), ctx->id("A1"));
|
||||
replace_port(lut1, ctx->id("B"), packed.get(), ctx->id("B1"));
|
||||
replace_port(lut1, ctx->id("C"), packed.get(), ctx->id("C1"));
|
||||
replace_port(lut1, ctx->id("D"), packed.get(), ctx->id("D1"));
|
||||
replace_port(ci, ctx->id("C0"), packed.get(), ctx->id("M0"));
|
||||
replace_port(ci, ctx->id("Z"), packed.get(), ctx->id("OFX0"));
|
||||
packed->params[ctx->id("LUT0_INITVAL")] = str_or_default(lut0->params, ctx->id("INIT"), "0");
|
||||
packed->params[ctx->id("LUT1_INITVAL")] = str_or_default(lut1->params, ctx->id("INIT"), "0");
|
||||
|
||||
ctx->nets.erase(f0->name);
|
||||
ctx->nets.erase(f1->name);
|
||||
sliceUsage[packed->name].lut0_used = true;
|
||||
sliceUsage[packed->name].lut1_used = true;
|
||||
sliceUsage[packed->name].mux5_used = true;
|
||||
|
||||
if (lutffPairs.find(ci->name) != lutffPairs.end()) {
|
||||
CellInfo *ff = ctx->cells.at(lutffPairs[ci->name]).get();
|
||||
ff_to_slice(ctx, ff, packed.get(), 0, true);
|
||||
packed_cells.insert(ff->name);
|
||||
sliceUsage[packed->name].ff0_used = true;
|
||||
}
|
||||
|
||||
new_cells.push_back(std::move(packed));
|
||||
packed_cells.insert(lut0->name);
|
||||
packed_cells.insert(lut1->name);
|
||||
packed_cells.insert(ci->name);
|
||||
}
|
||||
}
|
||||
flush_cells();
|
||||
}
|
||||
|
||||
// Pack LUTs that have been paired together
|
||||
void pack_lut_pairs()
|
||||
{
|
||||
log_info("Packing paired LUTs into a SLICE...\n");
|
||||
for (auto pair : lutPairs) {
|
||||
CellInfo *lut0 = ctx->cells.at(pair.first).get();
|
||||
CellInfo *lut1 = ctx->cells.at(pair.second).get();
|
||||
std::unique_ptr<CellInfo> slice =
|
||||
create_ecp5_cell(ctx, ctx->id("TRELLIS_SLICE"), lut0->name.str(ctx) + "_SLICE");
|
||||
|
||||
lut_to_slice(ctx, lut0, slice.get(), 0);
|
||||
lut_to_slice(ctx, lut1, slice.get(), 1);
|
||||
|
||||
auto ff0 = lutffPairs.find(lut0->name), ff1 = lutffPairs.find(lut1->name);
|
||||
|
||||
if (ff0 != lutffPairs.end()) {
|
||||
ff_to_slice(ctx, ctx->cells.at(ff0->second).get(), slice.get(), 0, true);
|
||||
packed_cells.insert(ff0->second);
|
||||
}
|
||||
if (ff1 != lutffPairs.end()) {
|
||||
ff_to_slice(ctx, ctx->cells.at(ff1->second).get(), slice.get(), 1, true);
|
||||
packed_cells.insert(ff1->second);
|
||||
}
|
||||
|
||||
new_cells.push_back(std::move(slice));
|
||||
packed_cells.insert(lut0->name);
|
||||
packed_cells.insert(lut1->name);
|
||||
}
|
||||
flush_cells();
|
||||
}
|
||||
|
||||
// Pack single LUTs that weren't paired into their own slice,
|
||||
// with an optional FF also
|
||||
void pack_remaining_luts()
|
||||
{
|
||||
log_info("Packing unpaired LUTs into a SLICE...\n");
|
||||
for (auto cell : sorted(ctx->cells)) {
|
||||
CellInfo *ci = cell.second;
|
||||
if (is_lut(ctx, ci)) {
|
||||
std::unique_ptr<CellInfo> slice =
|
||||
create_ecp5_cell(ctx, ctx->id("TRELLIS_SLICE"), ci->name.str(ctx) + "_SLICE");
|
||||
lut_to_slice(ctx, ci, slice.get(), 0);
|
||||
auto ff = lutffPairs.find(ci->name);
|
||||
|
||||
if (ff != lutffPairs.end()) {
|
||||
ff_to_slice(ctx, ctx->cells.at(ff->second).get(), slice.get(), 0, true);
|
||||
packed_cells.insert(ff->second);
|
||||
}
|
||||
|
||||
new_cells.push_back(std::move(slice));
|
||||
packed_cells.insert(ci->name);
|
||||
}
|
||||
}
|
||||
flush_cells();
|
||||
}
|
||||
|
||||
// Pack flipflops that weren't paired with a LUT
|
||||
void pack_remaining_ffs()
|
||||
{
|
||||
log_info("Packing unpaired FFs into a SLICE...\n");
|
||||
for (auto cell : sorted(ctx->cells)) {
|
||||
CellInfo *ci = cell.second;
|
||||
if (is_ff(ctx, ci)) {
|
||||
std::unique_ptr<CellInfo> slice =
|
||||
create_ecp5_cell(ctx, ctx->id("TRELLIS_SLICE"), ci->name.str(ctx) + "_SLICE");
|
||||
ff_to_slice(ctx, ci, slice.get(), 0, false);
|
||||
new_cells.push_back(std::move(slice));
|
||||
packed_cells.insert(ci->name);
|
||||
}
|
||||
}
|
||||
flush_cells();
|
||||
}
|
||||
|
||||
public:
|
||||
void pack()
|
||||
{
|
||||
pack_io();
|
||||
find_lutff_pairs();
|
||||
pack_lut5s();
|
||||
pair_luts();
|
||||
pack_lut_pairs();
|
||||
pack_remaining_luts();
|
||||
pack_remaining_ffs();
|
||||
}
|
||||
|
||||
private:
|
||||
Context *ctx;
|
||||
|
||||
std::unordered_set<IdString> packed_cells;
|
||||
std::vector<std::unique_ptr<CellInfo>> new_cells;
|
||||
|
||||
struct SliceUsage
|
||||
{
|
||||
bool lut0_used = false, lut1_used = false;
|
||||
bool ccu2_used = false, dpram_used = false, ramw_used = false;
|
||||
bool ff0_used = false, ff1_used = false;
|
||||
bool mux5_used = false, muxx_used = false;
|
||||
};
|
||||
|
||||
std::unordered_map<IdString, SliceUsage> sliceUsage;
|
||||
std::unordered_map<IdString, IdString> lutffPairs;
|
||||
std::unordered_map<IdString, IdString> fflutPairs;
|
||||
std::unordered_map<IdString, IdString> lutPairs;
|
||||
};
|
||||
// Main pack function
|
||||
bool Arch::pack()
|
||||
{
|
||||
Context *ctx = getCtx();
|
||||
try {
|
||||
log_break();
|
||||
pack_io(ctx);
|
||||
Ecp5Packer(ctx).pack();
|
||||
log_info("Checksum: 0x%08x\n", ctx->checksum());
|
||||
return true;
|
||||
} catch (log_execution_error_exception) {
|
||||
|
2
ecp5/synth/blinky_nopack.ys
Normal file
2
ecp5/synth/blinky_nopack.ys
Normal file
@ -0,0 +1,2 @@
|
||||
read_verilog blinky.v
|
||||
synth_ecp5 -noccu2 -nomux -nodram -json blinky.json
|
@ -32,213 +32,6 @@ def get_tiletype_index(name):
|
||||
|
||||
portpins = dict()
|
||||
|
||||
loc_wire_indices = dict()
|
||||
loc_wires = dict()
|
||||
|
||||
loc_bels = dict()
|
||||
wire_bel_pins_uphill = dict()
|
||||
wire_bel_pins_downhill = dict()
|
||||
|
||||
|
||||
# Import all wire names at all locations
|
||||
def import_location_wires(rg, x, y):
|
||||
loc_wire_indices[x, y] = dict()
|
||||
loc_wires[x, y] = list()
|
||||
wire_bel_pins_uphill[x, y] = list()
|
||||
wire_bel_pins_downhill[x, y] = list()
|
||||
rtile = rg.tiles[pytrellis.Location(x, y)]
|
||||
for wire in rtile.wires:
|
||||
name = rg.to_str(wire.key())
|
||||
idx = len(loc_wires[x, y])
|
||||
loc_wires[x, y].append(name)
|
||||
loc_wire_indices[x, y][name] = idx
|
||||
wire_bel_pins_uphill[x, y].append([])
|
||||
wire_bel_pins_downhill[x, y].append([])
|
||||
|
||||
|
||||
# Take a RoutingId from Trellis and make into a (relx, rely, name) tuple
|
||||
def resolve_wirename(rg, rid, cur_x, cur_y):
|
||||
if is_global(rid.loc):
|
||||
return (cur_x, cur_y, rg.to_str(rid.id))
|
||||
else:
|
||||
x = rid.loc.x
|
||||
y = rid.loc.y
|
||||
widx = loc_wire_indices[x, y][rg.to_str(rid.id)]
|
||||
return (x - cur_x, y - cur_y, widx)
|
||||
|
||||
|
||||
loc_arc_indices = dict() # Map RoutingId index to nextpnr index
|
||||
loc_arcs = dict()
|
||||
|
||||
|
||||
# Import all arc indices at a location
|
||||
def index_location_arcs(rg, x, y):
|
||||
loc_arc_indices[x, y] = dict()
|
||||
loc_arcs[x, y] = list()
|
||||
rtile = rg.tiles[pytrellis.Location(x, y)]
|
||||
for arc in rtile.arcs:
|
||||
idx = len(loc_arcs[x, y])
|
||||
trid = arc.key()
|
||||
loc_arcs[x, y].append(trid)
|
||||
loc_arc_indices[x, y][trid] = idx
|
||||
|
||||
|
||||
def add_bel_input(bel_x, bel_y, bel_idx, bel_pin, wire_x, wire_y, wire_name):
|
||||
bel_pin = portpins[bel_pin]
|
||||
loc_bels[bel_x, bel_y][bel_idx][2].append((bel_pin, (wire_x, wire_y, loc_wire_indices[wire_x, wire_y][wire_name])))
|
||||
wire_bel_pins_downhill[wire_x, wire_y][loc_wire_indices[wire_x, wire_y][wire_name]].append((
|
||||
(bel_x, bel_y, bel_idx), bel_pin))
|
||||
|
||||
|
||||
def add_bel_output(bel_x, bel_y, bel_idx, bel_pin, wire_x, wire_y, wire_name):
|
||||
bel_pin = portpins[bel_pin]
|
||||
loc_bels[bel_x, bel_y][bel_idx][2].append((bel_pin, (wire_x, wire_y, loc_wire_indices[wire_x, wire_y][wire_name])))
|
||||
wire_bel_pins_uphill[wire_x, wire_y][loc_wire_indices[wire_x, wire_y][wire_name]].append((
|
||||
(bel_x, bel_y, bel_idx), bel_pin))
|
||||
|
||||
|
||||
def add_slice(x, y, z):
|
||||
idx = len(loc_bels[x, y])
|
||||
l = ("A", "B", "C", "D")[z]
|
||||
name = "SLICE" + l
|
||||
loc_bels[x, y].append((name, "SLICE", []))
|
||||
lc0 = z * 2
|
||||
lc1 = z * 2 + 1
|
||||
add_bel_input(x, y, idx, "A0", x, y, "A{}_SLICE".format(lc0))
|
||||
add_bel_input(x, y, idx, "B0", x, y, "B{}_SLICE".format(lc0))
|
||||
add_bel_input(x, y, idx, "C0", x, y, "C{}_SLICE".format(lc0))
|
||||
add_bel_input(x, y, idx, "D0", x, y, "D{}_SLICE".format(lc0))
|
||||
add_bel_input(x, y, idx, "M0", x, y, "M{}_SLICE".format(lc0))
|
||||
|
||||
add_bel_input(x, y, idx, "A1", x, y, "A{}_SLICE".format(lc1))
|
||||
add_bel_input(x, y, idx, "B1", x, y, "B{}_SLICE".format(lc1))
|
||||
add_bel_input(x, y, idx, "C1", x, y, "C{}_SLICE".format(lc1))
|
||||
add_bel_input(x, y, idx, "D1", x, y, "D{}_SLICE".format(lc1))
|
||||
add_bel_input(x, y, idx, "M1", x, y, "M{}_SLICE".format(lc1))
|
||||
|
||||
add_bel_input(x, y, idx, "FCI", x, y, "FCI{}_SLICE".format(l if z > 0 else ""))
|
||||
add_bel_input(x, y, idx, "FXA", x, y, "FXA{}_SLICE".format(l))
|
||||
add_bel_input(x, y, idx, "FXB", x, y, "FXB{}_SLICE".format(l))
|
||||
|
||||
add_bel_input(x, y, idx, "CLK", x, y, "CLK{}_SLICE".format(z))
|
||||
add_bel_input(x, y, idx, "LSR", x, y, "LSR{}_SLICE".format(z))
|
||||
add_bel_input(x, y, idx, "CE", x, y, "CE{}_SLICE".format(z))
|
||||
|
||||
add_bel_input(x, y, idx, "DI0", x, y, "DI{}_SLICE".format(lc0))
|
||||
add_bel_input(x, y, idx, "DI1", x, y, "DI{}_SLICE".format(lc1))
|
||||
|
||||
if z == 0 or z == 1:
|
||||
add_bel_input(x, y, idx, "WD0", x, y, "WD0{}_SLICE".format(l))
|
||||
add_bel_input(x, y, idx, "WD1", x, y, "WD1{}_SLICE".format(l))
|
||||
|
||||
add_bel_input(x, y, idx, "WAD0", x, y, "WAD0{}_SLICE".format(l))
|
||||
add_bel_input(x, y, idx, "WAD1", x, y, "WAD1{}_SLICE".format(l))
|
||||
add_bel_input(x, y, idx, "WAD2", x, y, "WAD2{}_SLICE".format(l))
|
||||
add_bel_input(x, y, idx, "WAD3", x, y, "WAD3{}_SLICE".format(l))
|
||||
|
||||
add_bel_input(x, y, idx, "WRE", x, y, "WRE{}_SLICE".format(z))
|
||||
add_bel_input(x, y, idx, "WCK", x, y, "WCK{}_SLICE".format(z))
|
||||
|
||||
add_bel_output(x, y, idx, "F0", x, y, "F{}_SLICE".format(lc0))
|
||||
add_bel_output(x, y, idx, "Q0", x, y, "Q{}_SLICE".format(lc0))
|
||||
|
||||
add_bel_output(x, y, idx, "F1", x, y, "F{}_SLICE".format(lc1))
|
||||
add_bel_output(x, y, idx, "Q1", x, y, "Q{}_SLICE".format(lc1))
|
||||
|
||||
add_bel_output(x, y, idx, "OFX0", x, y, "F5{}_SLICE".format(l))
|
||||
add_bel_output(x, y, idx, "OFX1", x, y, "FX{}_SLICE".format(l))
|
||||
|
||||
add_bel_output(x, y, idx, "FCO", x, y, "FCO{}_SLICE".format(l if z < 3 else ""))
|
||||
|
||||
if z == 2:
|
||||
add_bel_output(x, y, idx, "WDO0", x, y, "WDO0C_SLICE")
|
||||
add_bel_output(x, y, idx, "WDO1", x, y, "WDO1C_SLICE")
|
||||
add_bel_output(x, y, idx, "WDO2", x, y, "WDO2C_SLICE")
|
||||
add_bel_output(x, y, idx, "WDO3", x, y, "WDO3C_SLICE")
|
||||
|
||||
add_bel_output(x, y, idx, "WADO0", x, y, "WADO0C_SLICE")
|
||||
add_bel_output(x, y, idx, "WADO1", x, y, "WADO1C_SLICE")
|
||||
add_bel_output(x, y, idx, "WADO2", x, y, "WADO2C_SLICE")
|
||||
add_bel_output(x, y, idx, "WADO3", x, y, "WADO3C_SLICE")
|
||||
|
||||
|
||||
def add_pio(x, y, z):
|
||||
idx = len(loc_bels[x, y])
|
||||
l = ("A", "B", "C", "D")[z]
|
||||
name = "PIO" + l
|
||||
loc_bels[x, y].append((name, "PIO", []))
|
||||
add_bel_input(x, y, idx, "I", x, y, "PADDO{}_PIO".format(l))
|
||||
add_bel_input(x, y, idx, "T", x, y, "PADDT{}_PIO".format(l))
|
||||
add_bel_output(x, y, idx, "O", x, y, "JPADDI{}_PIO".format(l))
|
||||
|
||||
|
||||
def add_bels(chip, x, y):
|
||||
loc_bels[x, y] = []
|
||||
tiles = chip.get_tiles_by_position(y, x)
|
||||
num_slices = 0
|
||||
num_pios = 0
|
||||
for tile in tiles:
|
||||
tt = tile.info.type
|
||||
if tt == "PLC2":
|
||||
num_slices = 4
|
||||
elif "PICL0" in tt or "PICR0" in tt:
|
||||
num_pios = 4
|
||||
elif "PIOT0" in tt or ("PICB0" in tt and "SPICB" not in tt):
|
||||
num_pios = 2
|
||||
for i in range(num_slices):
|
||||
add_slice(x, y, i)
|
||||
for i in range(num_pios):
|
||||
add_pio(x, y, i)
|
||||
|
||||
|
||||
# Import a location, deduplicating if appropriate
|
||||
def import_location(rg, x, y):
|
||||
rtile = rg.tiles[pytrellis.Location(x, y)]
|
||||
arcs = [] # (src, dst, configurable, tiletype)
|
||||
wires = [] # (name, uphill, downhill, belpin_uphill, belpins_downhill)
|
||||
bels = [] # (name, [(pin, wire)])
|
||||
for name in loc_wires[x, y]:
|
||||
w = rtile.wires[rg.ident(name)]
|
||||
arcs_uphill = []
|
||||
arcs_downhill = []
|
||||
belpins_uphill = []
|
||||
belpins_downhill = []
|
||||
for uh in w.uphill:
|
||||
arcidx = loc_arc_indices[uh.loc.x, uh.loc.y][uh.id]
|
||||
arcs_uphill.append((uh.loc.x - x, uh.loc.y - y, arcidx))
|
||||
for dh in w.downhill:
|
||||
arcidx = loc_arc_indices[dh.loc.x, dh.loc.y][dh.id]
|
||||
arcs_downhill.append((dh.loc.x - x, dh.loc.y - y, arcidx))
|
||||
for bp in wire_bel_pins_uphill[x, y][loc_wire_indices[x, y][name]]:
|
||||
bel, pin = bp
|
||||
bel_x, bel_y, bel_idx = bel
|
||||
belpins_uphill.append(((bel_x - x, bel_y - y, bel_idx), pin))
|
||||
for bp in wire_bel_pins_downhill[x, y][loc_wire_indices[x, y][name]]:
|
||||
bel, pin = bp
|
||||
bel_x, bel_y, bel_idx = bel
|
||||
belpins_downhill.append(((bel_x - x, bel_y - y, bel_idx), pin))
|
||||
assert len(belpins_uphill) <= 1
|
||||
wires.append((name, tuple(arcs_downhill), tuple(arcs_uphill), tuple(belpins_uphill), tuple(belpins_downhill)))
|
||||
|
||||
for bel in loc_bels[x, y]:
|
||||
name, beltype, pins = bel
|
||||
xformed_pins = tuple((p[0], (p[1][0] - x, p[1][1] - y, p[1][2])) for p in pins)
|
||||
bels.append((name, beltype, xformed_pins))
|
||||
|
||||
for arcidx in loc_arcs[x, y]:
|
||||
a = rtile.arcs[arcidx]
|
||||
source_wire = resolve_wirename(rg, a.source, x, y)
|
||||
dest_wire = resolve_wirename(rg, a.sink, x, y)
|
||||
arcs.append((source_wire, dest_wire, a.configurable, get_tiletype_index(rg.to_str(a.tiletype))))
|
||||
|
||||
tile_data = (tuple(wires), tuple(arcs), tuple(bels))
|
||||
if tile_data in location_types:
|
||||
type_at_location[x, y] = location_types[tile_data]
|
||||
else:
|
||||
idx = len(location_types)
|
||||
location_types[tile_data] = idx
|
||||
type_at_location[x, y] = idx
|
||||
|
||||
|
||||
class BinaryBlobAssembler:
|
||||
def __init__(self, cname, endianness, nodebug=False):
|
||||
@ -526,100 +319,96 @@ bel_types = {
|
||||
"PIO": 2
|
||||
}
|
||||
|
||||
def write_database(dev_name, endianness):
|
||||
def write_loc(x, y, sym_name):
|
||||
bba.s16(x, "%s.x" % sym_name)
|
||||
bba.s16(y, "%s.y" % sym_name)
|
||||
|
||||
def write_database(dev_name, ddrg, endianness):
|
||||
def write_loc(loc, sym_name):
|
||||
bba.s16(loc.x, "%s.x" % sym_name)
|
||||
bba.s16(loc.y, "%s.y" % sym_name)
|
||||
|
||||
bba = BinaryBlobAssembler("chipdb_blob_%s" % dev_name, endianness)
|
||||
bba.r("chip_info", "chip_info")
|
||||
|
||||
for loctype, idx in sorted(location_types.items(), key=lambda x: x[1]):
|
||||
wires, arcs, bels = loctype
|
||||
if len(arcs) > 0:
|
||||
loctypes = list([_.key() for _ in ddrg.locationTypes])
|
||||
|
||||
for idx in range(len(loctypes)):
|
||||
loctype = ddrg.locationTypes[loctypes[idx]]
|
||||
if len(loctype.arcs) > 0:
|
||||
bba.l("loc%d_pips" % idx, "PipInfoPOD")
|
||||
for arc in arcs:
|
||||
src_wire, dst_wire, configurable, tile_type = arc
|
||||
write_loc(src_wire[0], src_wire[1], "src")
|
||||
write_loc(dst_wire[0], dst_wire[1], "dst")
|
||||
bba.u32(src_wire[2], "src_idx")
|
||||
bba.u32(dst_wire[2], "dst_idx")
|
||||
bba.u32(1, "delay") # TODO:delay
|
||||
bba.u16(tile_type, "tile_type")
|
||||
bba.u8(1 if not configurable else 0, "pip_type")
|
||||
for arc in loctype.arcs:
|
||||
write_loc(arc.srcWire.rel, "src")
|
||||
write_loc(arc.sinkWire.rel, "dst")
|
||||
bba.u32(arc.srcWire.id, "src_idx")
|
||||
bba.u32(arc.sinkWire.id, "dst_idx")
|
||||
bba.u32(arc.delay, "delay") # TODO:delay
|
||||
bba.u16(get_tiletype_index(ddrg.to_str(arc.tiletype)), "tile_type")
|
||||
bba.u8(int(arc.cls), "pip_type")
|
||||
bba.u8(0, "padding")
|
||||
if len(wires) > 0:
|
||||
for wire_idx in range(len(wires)):
|
||||
wire = wires[wire_idx]
|
||||
name, downpips, uppips, downbels, upbels = wire
|
||||
if len(downpips) > 0:
|
||||
if len(loctype.wires) > 0:
|
||||
for wire_idx in range(len(loctype.wires)):
|
||||
wire = loctype.wires[wire_idx]
|
||||
if len(wire.arcsDownhill) > 0:
|
||||
bba.l("loc%d_wire%d_downpips" % (idx, wire_idx), "PipLocatorPOD")
|
||||
for dp in downpips:
|
||||
write_loc(dp[0], dp[1], "rel_loc")
|
||||
bba.u32(dp[2], "index")
|
||||
if len(uppips) > 0:
|
||||
for dp in wire.arcsDownhill:
|
||||
write_loc(dp.rel, "rel_loc")
|
||||
bba.u32(dp.id, "index")
|
||||
if len(wire.arcsUphill) > 0:
|
||||
bba.l("loc%d_wire%d_uppips" % (idx, wire_idx), "PipLocatorPOD")
|
||||
for up in uppips:
|
||||
write_loc(up[0], up[1], "rel_loc")
|
||||
bba.u32(up[2], "index")
|
||||
if len(downbels) > 0:
|
||||
for up in wire.arcsUphill:
|
||||
write_loc(up.rel, "rel_loc")
|
||||
bba.u32(up.id, "index")
|
||||
if len(wire.belsDownhill) > 0:
|
||||
bba.l("loc%d_wire%d_downbels" % (idx, wire_idx), "BelPortPOD")
|
||||
for db in downbels:
|
||||
bel, pin = db
|
||||
write_loc(bel[0], bel[1], "rel_bel_loc")
|
||||
bba.u32(bel[2], "bel_index")
|
||||
bba.u32(pin, "port")
|
||||
for db in wire.belsDownhill:
|
||||
write_loc(db.bel.rel, "rel_bel_loc")
|
||||
bba.u32(db.bel.id, "bel_index")
|
||||
bba.u32(portpins[ddrg.to_str(db.pin)], "port")
|
||||
bba.l("loc%d_wires" % idx, "WireInfoPOD")
|
||||
for wire_idx in range(len(wires)):
|
||||
wire = wires[wire_idx]
|
||||
name, downpips, uppips, downbels, upbels = wire
|
||||
bba.s(name, "name")
|
||||
bba.u32(len(uppips), "num_uphill")
|
||||
bba.u32(len(downpips), "num_downhill")
|
||||
bba.r("loc%d_wire%d_uppips" % (idx, wire_idx) if len(uppips) > 0 else None, "pips_uphill")
|
||||
bba.r("loc%d_wire%d_downpips" % (idx, wire_idx) if len(downpips) > 0 else None, "pips_downhill")
|
||||
bba.u32(len(downbels), "num_bels_downhill")
|
||||
if len(upbels) == 1:
|
||||
bel, pin = upbels[0]
|
||||
write_loc(bel[0], bel[1], "uphill_bel_loc")
|
||||
bba.u32(bel[2], "uphill_bel_idx")
|
||||
bba.u32(pin, "uphill_bel_pin")
|
||||
for wire_idx in range(len(loctype.wires)):
|
||||
wire = loctype.wires[wire_idx]
|
||||
bba.s(ddrg.to_str(wire.name), "name")
|
||||
bba.u32(len(wire.arcsUphill), "num_uphill")
|
||||
bba.u32(len(wire.arcsDownhill), "num_downhill")
|
||||
bba.r("loc%d_wire%d_uppips" % (idx, wire_idx) if len(wire.arcsUphill) > 0 else None, "pips_uphill")
|
||||
bba.r("loc%d_wire%d_downpips" % (idx, wire_idx) if len(wire.arcsDownhill) > 0 else None, "pips_downhill")
|
||||
bba.u32(len(wire.belsDownhill), "num_bels_downhill")
|
||||
write_loc(wire.belUphill.bel.rel, "uphill_bel_loc")
|
||||
if wire.belUphill.pin != -1:
|
||||
bba.u32(wire.belUphill.bel.id, "uphill_bel_idx")
|
||||
bba.u32(portpins[ddrg.to_str(wire.belUphill.pin)], "uphill_bel_pin")
|
||||
else:
|
||||
write_loc(-1, -1, "bel_uphill.rel_bel_loc")
|
||||
bba.u32(0xFFFFFFFF, "bel_uphill.bel_index")
|
||||
bba.u32(0, "bel_uphill.port")
|
||||
bba.r("loc%d_wire%d_downbels" % (idx, wire_idx) if len(downbels) > 0 else None, "bels_downhill")
|
||||
if len(bels) > 0:
|
||||
for bel_idx in range(len(bels)):
|
||||
bel, beltype, pins = bels[bel_idx]
|
||||
bba.r("loc%d_wire%d_downbels" % (idx, wire_idx) if len(wire.belsDownhill) > 0 else None, "bels_downhill")
|
||||
if len(loctype.bels) > 0:
|
||||
for bel_idx in range(len(loctype.bels)):
|
||||
bel = loctype.bels[bel_idx]
|
||||
bba.l("loc%d_bel%d_wires" % (idx, bel_idx), "BelPortPOD")
|
||||
for pin in pins:
|
||||
port, wire = pin
|
||||
write_loc(wire[0], wire[1], "rel_wire_loc")
|
||||
bba.u32(wire[2], "wire_index")
|
||||
bba.u32(port, "port")
|
||||
for pin in bel.wires:
|
||||
write_loc(pin.wire.rel, "rel_wire_loc")
|
||||
bba.u32(pin.wire.id, "wire_index")
|
||||
bba.u32(portpins[ddrg.to_str(pin.pin)], "port")
|
||||
bba.l("loc%d_bels" % idx, "BelInfoPOD")
|
||||
for bel_idx in range(len(bels)):
|
||||
bel, beltype, pins = bels[bel_idx]
|
||||
bba.s(bel, "name")
|
||||
bba.u32(bel_types[beltype], "type")
|
||||
bba.u32(len(pins), "num_bel_wires")
|
||||
for bel_idx in range(len(loctype.bels)):
|
||||
bel = loctype.bels[bel_idx]
|
||||
bba.s(ddrg.to_str(bel.name), "name")
|
||||
bba.u32(bel_types[ddrg.to_str(bel.type)], "type")
|
||||
bba.u32(len(bel.wires), "num_bel_wires")
|
||||
bba.r("loc%d_bel%d_wires" % (idx, bel_idx), "bel_wires")
|
||||
|
||||
bba.l("locations", "LocationTypePOD")
|
||||
for loctype, idx in sorted(location_types.items(), key=lambda x: x[1]):
|
||||
wires, arcs, bels = loctype
|
||||
bba.u32(len(bels), "num_bels")
|
||||
bba.u32(len(wires), "num_wires")
|
||||
bba.u32(len(arcs), "num_pips")
|
||||
bba.r("loc%d_bels" % idx if len(bels) > 0 else None, "bel_data")
|
||||
bba.r("loc%d_wires" % idx if len(wires) > 0 else None, "wire_data")
|
||||
bba.r("loc%d_pips" % idx if len(arcs) > 0 else None, "pips_data")
|
||||
for idx in range(len(loctypes)):
|
||||
loctype = ddrg.locationTypes[loctypes[idx]]
|
||||
bba.u32(len(loctype.bels), "num_bels")
|
||||
bba.u32(len(loctype.wires), "num_wires")
|
||||
bba.u32(len(loctype.arcs), "num_pips")
|
||||
bba.r("loc%d_bels" % idx if len(loctype.bels) > 0 else None, "bel_data")
|
||||
bba.r("loc%d_wires" % idx if len(loctype.wires) > 0 else None, "wire_data")
|
||||
bba.r("loc%d_pips" % idx if len(loctype.arcs) > 0 else None, "pips_data")
|
||||
|
||||
bba.l("location_types", "int32_t")
|
||||
for y in range(0, max_row+1):
|
||||
for x in range(0, max_col+1):
|
||||
bba.u32(type_at_location[x, y], "loctype")
|
||||
bba.u32(loctypes.index(ddrg.typeAtLocation[pytrellis.Location(x, y)]), "loctype")
|
||||
|
||||
bba.l("tiletype_names", "RelPtr<char>")
|
||||
for tt in tiletype_names:
|
||||
@ -659,28 +448,11 @@ def main():
|
||||
print("Initialising chip...")
|
||||
chip = pytrellis.Chip(dev_names[args.device])
|
||||
print("Building routing graph...")
|
||||
rg = chip.get_routing_graph()
|
||||
ddrg = pytrellis.make_dedup_chipdb(chip)
|
||||
max_row = chip.get_max_row()
|
||||
max_col = chip.get_max_col()
|
||||
print("Indexing wires...")
|
||||
for y in range(0, max_row + 1):
|
||||
for x in range(0, max_col + 1):
|
||||
import_location_wires(rg, x, y)
|
||||
print("Indexing arcs...")
|
||||
for y in range(0, max_row + 1):
|
||||
for x in range(0, max_col + 1):
|
||||
index_location_arcs(rg, x, y)
|
||||
print("Adding bels...")
|
||||
for y in range(0, max_row + 1):
|
||||
for x in range(0, max_col + 1):
|
||||
add_bels(chip, x, y)
|
||||
print("Importing tiles...")
|
||||
for y in range(0, max_row + 1):
|
||||
for x in range(0, max_col + 1):
|
||||
print(" At R{}C{}".format(y, x))
|
||||
import_location(rg, x, y)
|
||||
print("{} unique location types".format(len(location_types)))
|
||||
bba = write_database(args.device, "le")
|
||||
print("{} unique location types".format(len(ddrg.locationTypes)))
|
||||
bba = write_database(args.device, ddrg, "le")
|
||||
|
||||
|
||||
if args.c_file:
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "log.h"
|
||||
#include "mainwindow.h"
|
||||
#include "pythontab.h"
|
||||
#include "yosystab.h"
|
||||
|
||||
static void initBasenameResource() { Q_INIT_RESOURCE(base); }
|
||||
|
||||
@ -73,8 +74,12 @@ BaseMainWindow::BaseMainWindow(std::unique_ptr<Context> context, QWidget *parent
|
||||
connect(this, SIGNAL(contextChanged(Context *)), console, SLOT(newContext(Context *)));
|
||||
|
||||
centralTabWidget = new QTabWidget();
|
||||
centralTabWidget->setTabsClosable(true);
|
||||
connect(centralTabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(closeTab(int)));
|
||||
|
||||
FPGAViewWidget *fpgaView = new FPGAViewWidget();
|
||||
centralTabWidget->addTab(fpgaView, "Graphics");
|
||||
centralTabWidget->tabBar()->tabButton(0, QTabBar::RightSide)->resize(0, 0);
|
||||
|
||||
connect(this, SIGNAL(contextChanged(Context *)), fpgaView, SLOT(newContext(Context *)));
|
||||
connect(designview, SIGNAL(selected(std::vector<DecalXY>)), fpgaView,
|
||||
@ -95,6 +100,8 @@ BaseMainWindow::BaseMainWindow(std::unique_ptr<Context> context, QWidget *parent
|
||||
|
||||
BaseMainWindow::~BaseMainWindow() {}
|
||||
|
||||
void BaseMainWindow::closeTab(int index) { delete centralTabWidget->widget(index); }
|
||||
|
||||
void BaseMainWindow::displaySplash()
|
||||
{
|
||||
splash = new QSplashScreen();
|
||||
@ -140,6 +147,10 @@ void BaseMainWindow::createMenusAndBars()
|
||||
actionExit->setStatusTip("Exit the application");
|
||||
connect(actionExit, SIGNAL(triggered()), this, SLOT(close()));
|
||||
|
||||
QAction *actionYosys = new QAction("Yosys", this);
|
||||
actionYosys->setStatusTip("Run Yosys");
|
||||
connect(actionYosys, SIGNAL(triggered()), this, SLOT(yosys()));
|
||||
|
||||
QAction *actionAbout = new QAction("About", this);
|
||||
|
||||
menuBar = new QMenuBar();
|
||||
@ -172,6 +183,19 @@ void BaseMainWindow::createMenusAndBars()
|
||||
mainToolBar->addAction(actionNew);
|
||||
mainToolBar->addAction(actionOpen);
|
||||
mainToolBar->addAction(actionSave);
|
||||
mainToolBar->addAction(actionYosys);
|
||||
}
|
||||
|
||||
void BaseMainWindow::yosys()
|
||||
{
|
||||
QString folder = QFileDialog::getExistingDirectory(0, ("Select Work Folder"), QDir::currentPath(),
|
||||
QFileDialog::ShowDirsOnly);
|
||||
if (!folder.isEmpty() && !folder.isNull()) {
|
||||
YosysTab *yosysTab = new YosysTab(folder);
|
||||
yosysTab->setAttribute(Qt::WA_DeleteOnClose);
|
||||
centralTabWidget->addTab(yosysTab, "Yosys");
|
||||
centralTabWidget->setCurrentWidget(yosysTab);
|
||||
centralTabWidget->setTabToolTip(centralTabWidget->indexOf(yosysTab), folder);
|
||||
}
|
||||
}
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
@ -55,10 +55,12 @@ class BaseMainWindow : public QMainWindow
|
||||
protected Q_SLOTS:
|
||||
void writeInfo(std::string text);
|
||||
void displaySplashMessage(std::string msg);
|
||||
void closeTab(int index);
|
||||
|
||||
virtual void new_proj() = 0;
|
||||
virtual void open_proj() = 0;
|
||||
virtual bool save_proj() = 0;
|
||||
void yosys();
|
||||
|
||||
Q_SIGNALS:
|
||||
void contextChanged(Context *ctx);
|
||||
|
@ -400,10 +400,7 @@ void FPGAViewWidget::paintGL()
|
||||
}
|
||||
lineShader_.draw(grid, gridColor_, thick1Px, matrix);
|
||||
|
||||
LineShaderData shaders[4] = {[GraphicElement::G_FRAME] = LineShaderData(),
|
||||
[GraphicElement::G_HIDDEN] = LineShaderData(),
|
||||
[GraphicElement::G_INACTIVE] = LineShaderData(),
|
||||
[GraphicElement::G_ACTIVE] = LineShaderData()};
|
||||
LineShaderData shaders[4] = {LineShaderData(), LineShaderData(), LineShaderData(), LineShaderData()};
|
||||
|
||||
if (ctx_) {
|
||||
// Draw Bels.
|
||||
|
@ -48,7 +48,6 @@ void LineEditor::keyPressEvent(QKeyEvent *ev)
|
||||
QToolTip::hideText();
|
||||
if (lines.empty())
|
||||
return;
|
||||
printf("Key_Up\n");
|
||||
if (ev->key() == Qt::Key_Up)
|
||||
index--;
|
||||
if (ev->key() == Qt::Key_Down)
|
||||
|
96
gui/yosys_edit.cc
Normal file
96
gui/yosys_edit.cc
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* nextpnr -- Next Generation Place and Route
|
||||
*
|
||||
* Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.com>
|
||||
* Copyright (C) 2018 Alex Tsui
|
||||
*
|
||||
* 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 "yosys_edit.h"
|
||||
#include <QKeyEvent>
|
||||
#include <QToolTip>
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
YosysLineEditor::YosysLineEditor(QWidget *parent) : QLineEdit(parent), index(0)
|
||||
{
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
QAction *clearAction = new QAction("Clear &history", this);
|
||||
clearAction->setStatusTip("Clears line edit history");
|
||||
connect(clearAction, SIGNAL(triggered()), this, SLOT(clearHistory()));
|
||||
contextMenu = createStandardContextMenu();
|
||||
contextMenu->addSeparator();
|
||||
contextMenu->addAction(clearAction);
|
||||
|
||||
connect(this, SIGNAL(returnPressed()), SLOT(textInserted()));
|
||||
connect(this, SIGNAL(customContextMenuRequested(const QPoint)), this, SLOT(showContextMenu(const QPoint)));
|
||||
}
|
||||
|
||||
void YosysLineEditor::keyPressEvent(QKeyEvent *ev)
|
||||
{
|
||||
|
||||
if (ev->key() == Qt::Key_Up || ev->key() == Qt::Key_Down) {
|
||||
QToolTip::hideText();
|
||||
if (lines.empty())
|
||||
return;
|
||||
if (ev->key() == Qt::Key_Up)
|
||||
index--;
|
||||
if (ev->key() == Qt::Key_Down)
|
||||
index++;
|
||||
|
||||
if (index < 0)
|
||||
index = 0;
|
||||
if (index >= lines.size()) {
|
||||
index = lines.size();
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
setText(lines[index]);
|
||||
} else if (ev->key() == Qt::Key_Escape) {
|
||||
QToolTip::hideText();
|
||||
clear();
|
||||
return;
|
||||
} else if (ev->key() == Qt::Key_Tab) {
|
||||
return;
|
||||
}
|
||||
QToolTip::hideText();
|
||||
|
||||
QLineEdit::keyPressEvent(ev);
|
||||
}
|
||||
|
||||
// This makes TAB work
|
||||
bool YosysLineEditor::focusNextPrevChild(bool next) { return false; }
|
||||
|
||||
void YosysLineEditor::textInserted()
|
||||
{
|
||||
if (lines.empty() || lines.back() != text())
|
||||
lines += text();
|
||||
if (lines.size() > 100)
|
||||
lines.removeFirst();
|
||||
index = lines.size();
|
||||
clear();
|
||||
Q_EMIT textLineInserted(lines.back());
|
||||
}
|
||||
|
||||
void YosysLineEditor::showContextMenu(const QPoint &pt) { contextMenu->exec(mapToGlobal(pt)); }
|
||||
|
||||
void YosysLineEditor::clearHistory()
|
||||
{
|
||||
lines.clear();
|
||||
index = 0;
|
||||
clear();
|
||||
}
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
57
gui/yosys_edit.h
Normal file
57
gui/yosys_edit.h
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* nextpnr -- Next Generation Place and Route
|
||||
*
|
||||
* Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.com>
|
||||
* Copyright (C) 2018 Alex Tsui
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef YOSYS_EDIT_H
|
||||
#define YOSYS_EDIT_H
|
||||
|
||||
#include <QLineEdit>
|
||||
#include <QMenu>
|
||||
#include "nextpnr.h"
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
class YosysLineEditor : public QLineEdit
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit YosysLineEditor(QWidget *parent = 0);
|
||||
|
||||
private Q_SLOTS:
|
||||
void textInserted();
|
||||
void showContextMenu(const QPoint &pt);
|
||||
void clearHistory();
|
||||
|
||||
Q_SIGNALS:
|
||||
void textLineInserted(QString);
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *) Q_DECL_OVERRIDE;
|
||||
bool focusNextPrevChild(bool next) Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
int index;
|
||||
QStringList lines;
|
||||
QMenu *contextMenu;
|
||||
};
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
||||
#endif // YOSYS_EDIT_H
|
108
gui/yosystab.cc
Normal file
108
gui/yosystab.cc
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* nextpnr -- Next Generation Place and Route
|
||||
*
|
||||
* Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.com>
|
||||
*
|
||||
* 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 "yosystab.h"
|
||||
#include <QGridLayout>
|
||||
#include <QMessageBox>
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
YosysTab::YosysTab(QString folder, QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
QFont f("unexistent");
|
||||
f.setStyleHint(QFont::Monospace);
|
||||
|
||||
console = new QPlainTextEdit();
|
||||
console->setMinimumHeight(100);
|
||||
console->setReadOnly(true);
|
||||
console->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
|
||||
console->setFont(f);
|
||||
|
||||
console->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
QAction *clearAction = new QAction("Clear &buffer", this);
|
||||
clearAction->setStatusTip("Clears display buffer");
|
||||
connect(clearAction, SIGNAL(triggered()), this, SLOT(clearBuffer()));
|
||||
contextMenu = console->createStandardContextMenu();
|
||||
contextMenu->addSeparator();
|
||||
contextMenu->addAction(clearAction);
|
||||
connect(console, SIGNAL(customContextMenuRequested(const QPoint)), this, SLOT(showContextMenu(const QPoint)));
|
||||
|
||||
lineEdit = new YosysLineEditor();
|
||||
lineEdit->setMinimumHeight(30);
|
||||
lineEdit->setMaximumHeight(30);
|
||||
lineEdit->setFont(f);
|
||||
lineEdit->setFocus();
|
||||
lineEdit->setEnabled(false);
|
||||
lineEdit->setPlaceholderText("yosys>");
|
||||
connect(lineEdit, SIGNAL(textLineInserted(QString)), this, SLOT(editLineReturnPressed(QString)));
|
||||
|
||||
QGridLayout *mainLayout = new QGridLayout();
|
||||
mainLayout->addWidget(console, 0, 0);
|
||||
mainLayout->addWidget(lineEdit, 1, 0);
|
||||
setLayout(mainLayout);
|
||||
|
||||
process = new QProcess();
|
||||
connect(process, SIGNAL(readyReadStandardError()), this, SLOT(onReadyReadStandardError()));
|
||||
connect(process, SIGNAL(readyReadStandardOutput()), this, SLOT(onReadyReadStandardOutput()));
|
||||
connect(process, &QProcess::started, this, [this] { lineEdit->setEnabled(true); });
|
||||
/*
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
|
||||
connect(process, &QProcess::error, this, [this](QProcess::ProcessError error) {
|
||||
#else
|
||||
connect(process, &QProcess::errorOccurred, this, [this](QProcess::ProcessError error) {
|
||||
#endif
|
||||
if (error == QProcess::FailedToStart) {
|
||||
QMessageBox::critical(
|
||||
this, QString::fromUtf8("Yosys cannot be started!"),
|
||||
QString::fromUtf8("<p>Please make sure you have Yosys installed and available in path</p>"));
|
||||
Q_EMIT deleteLater();
|
||||
}
|
||||
});
|
||||
*/
|
||||
process->setWorkingDirectory(folder);
|
||||
process->start("yosys");
|
||||
}
|
||||
|
||||
YosysTab::~YosysTab()
|
||||
{
|
||||
process->terminate();
|
||||
process->waitForFinished(1000); // in ms
|
||||
process->kill();
|
||||
process->close();
|
||||
}
|
||||
|
||||
void YosysTab::displayString(QString text)
|
||||
{
|
||||
QTextCursor cursor = console->textCursor();
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
cursor.insertText(text);
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
console->setTextCursor(cursor);
|
||||
}
|
||||
|
||||
void YosysTab::onReadyReadStandardOutput() { displayString(process->readAllStandardOutput()); }
|
||||
void YosysTab::onReadyReadStandardError() { displayString(process->readAllStandardError()); }
|
||||
|
||||
void YosysTab::editLineReturnPressed(QString text) { process->write(text.toLatin1() + "\n"); }
|
||||
|
||||
void YosysTab::showContextMenu(const QPoint &pt) { contextMenu->exec(mapToGlobal(pt)); }
|
||||
|
||||
void YosysTab::clearBuffer() { console->clear(); }
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
59
gui/yosystab.h
Normal file
59
gui/yosystab.h
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* nextpnr -- Next Generation Place and Route
|
||||
*
|
||||
* Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef YOSYSTAB_H
|
||||
#define YOSYSTAB_H
|
||||
|
||||
#include <QLineEdit>
|
||||
#include <QMenu>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QProcess>
|
||||
#include "nextpnr.h"
|
||||
#include "yosys_edit.h"
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
class YosysTab : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit YosysTab(QString folder, QWidget *parent = 0);
|
||||
~YosysTab();
|
||||
|
||||
private:
|
||||
void displayString(QString text);
|
||||
private Q_SLOTS:
|
||||
void showContextMenu(const QPoint &pt);
|
||||
void editLineReturnPressed(QString text);
|
||||
void onReadyReadStandardOutput();
|
||||
void onReadyReadStandardError();
|
||||
public Q_SLOTS:
|
||||
void clearBuffer();
|
||||
|
||||
private:
|
||||
QPlainTextEdit *console;
|
||||
YosysLineEditor *lineEdit;
|
||||
QMenu *contextMenu;
|
||||
QProcess *process;
|
||||
};
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
||||
#endif // YOSYSTAB_H
|
@ -246,7 +246,7 @@ class PlacementLegaliser
|
||||
std::tuple<int, int, int> find_closest_bel(float target_x, float target_y, CellChain &chain)
|
||||
{
|
||||
std::tuple<int, int, int> best_origin = std::make_tuple(-1, -1, -1);
|
||||
wirelen_t best_wirelength = std::numeric_limits<wirelen_t>::max();
|
||||
wirelen_t best_metric = std::numeric_limits<wirelen_t>::max();
|
||||
int width = ctx->chip_info->width, height = ctx->chip_info->height;
|
||||
// Slow, should radiate outwards from target position - TODO
|
||||
int chain_size = int(chain.cells.size());
|
||||
@ -260,11 +260,11 @@ class PlacementLegaliser
|
||||
valid = false;
|
||||
break;
|
||||
} else {
|
||||
wirelen += get_cell_wirelength_at_bel(ctx, chain.cells.at(k), lb.first);
|
||||
wirelen += get_cell_metric_at_bel(ctx, chain.cells.at(k), lb.first, MetricType::COST);
|
||||
}
|
||||
}
|
||||
if (valid && wirelen < best_wirelength) {
|
||||
best_wirelength = wirelen;
|
||||
if (valid && wirelen < best_metric) {
|
||||
best_metric = wirelen;
|
||||
best_origin = std::make_tuple(x, y, 0);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user