Merge remote-tracking branch 'origin/master' into redist_slack
This commit is contained in:
commit
46b7469652
@ -182,12 +182,7 @@ foreach (family ${ARCH})
|
||||
add_executable(nextpnr-${family} ${COMMON_FILES} ${${ufamily}_FILES})
|
||||
install(TARGETS nextpnr-${family} RUNTIME DESTINATION bin)
|
||||
target_compile_definitions(nextpnr-${family} PRIVATE MAIN_EXECUTABLE)
|
||||
|
||||
if (BUILD_PYTHON)
|
||||
# Add the importable Python module target
|
||||
PYTHON_ADD_MODULE(nextpnrpy_${family} ${COMMON_FILES} ${${ufamily}_FILES})
|
||||
endif()
|
||||
|
||||
|
||||
# Add any new per-architecture targets here
|
||||
if (BUILD_TESTS)
|
||||
aux_source_directory(tests/${family}/ ${ufamily}_TEST_FILES)
|
||||
@ -210,10 +205,6 @@ foreach (family ${ARCH})
|
||||
set(family_targets ${family_targets} nextpnr-${family}-test)
|
||||
endif()
|
||||
|
||||
if (BUILD_PYTHON)
|
||||
set(family_targets ${family_targets} nextpnrpy_${family})
|
||||
endif()
|
||||
|
||||
# Include the family-specific CMakeFile
|
||||
include(${family}/family.cmake)
|
||||
foreach (target ${family_targets})
|
||||
|
26
README.md
26
README.md
@ -1,10 +1,21 @@
|
||||
nextpnr -- a portable FPGA place and route tool
|
||||
===============================================
|
||||
|
||||
nextpnr is an FPGA place and route tool with emphasis on supporting a wide
|
||||
range of real-world FPGA devices. It currently supports Lattice iCE40 devices
|
||||
and Lattice ECP5 devices, as well as a "generic" back-end for user-defined
|
||||
architectures. (ECP5 and "generic" support are still experimental.)
|
||||
nextpnr is an FPGA place and route tool with emphasis on supporting
|
||||
timing-driven place and route for a wide range of real-world FPGA devices.
|
||||
It currently supports Lattice iCE40 devices and Lattice ECP5 devices,
|
||||
as well as a "generic" back-end for user-defined architectures.
|
||||
(ECP5 and "generic" support are still experimental.)
|
||||
|
||||
Currently nextpnr is beta software at best. But we aim at replacing
|
||||
arachne-pnr as official place-and-route tool for the icestorm flow soon.
|
||||
|
||||
Here is a screenshot of nextpnr for iCE40. Build instructions and getting
|
||||
started notes can be found below.
|
||||
|
||||
|
||||
<img src="https://i.imgur.com/0spmlBa.png" width="640"/>
|
||||
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
@ -144,9 +155,10 @@ Links and references
|
||||
- [Project X-Ray (Xilinx 7-Series)](https://symbiflow.github.io/prjxray-db/)
|
||||
- [Project Chibi (Intel MAX-V)](https://github.com/rqou/project-chibi)
|
||||
|
||||
### Other FOSS place and route tools (FPGA and ASIC)
|
||||
### Other FOSS FPGA place and route projects
|
||||
|
||||
- [Arachne PNR](https://github.com/cseed/arachne-pnr)
|
||||
- [VPR/VTR](https://verilogtorouting.org/)
|
||||
- [graywolf/timberwolf](https://github.com/rubund/graywolf)
|
||||
- [qrouter](http://opencircuitdesign.com/qrouter/)
|
||||
- [SymbiFlow](https://github.com/SymbiFlow/symbiflow-arch-defs)
|
||||
- [Gaffe](https://github.com/kc8apf/gaffe)
|
||||
- [KinglerPAR](https://github.com/rqou/KinglerPAR)
|
||||
|
@ -91,6 +91,8 @@ WireId Context::getNetinfoSinkWire(NetInfo *net_info, int user_idx) const
|
||||
delay_t Context::getNetinfoRouteDelay(NetInfo *net_info, int user_idx) const
|
||||
{
|
||||
WireId src_wire = getNetinfoSourceWire(net_info);
|
||||
if (src_wire == WireId())
|
||||
return 0;
|
||||
WireId cursor = getNetinfoSinkWire(net_info, user_idx);
|
||||
delay_t delay = 0;
|
||||
|
||||
|
@ -51,13 +51,13 @@ static delay_t follow_user_port(Context *ctx, PortRef &user, int path_length, de
|
||||
// Follow outputs of the user
|
||||
for (auto port : user.cell->ports) {
|
||||
if (port.second.type == PORT_OUT) {
|
||||
delay_t comb_delay;
|
||||
DelayInfo comb_delay;
|
||||
// Look up delay through this path
|
||||
bool is_path = ctx->getCellDelay(user.cell, user.port, port.first, comb_delay);
|
||||
if (is_path) {
|
||||
NetInfo *net = port.second.net;
|
||||
if (net) {
|
||||
delay_t path_budget = follow_net(ctx, net, path_length, slack - comb_delay, update, min_slack,
|
||||
delay_t path_budget = follow_net(ctx, net, path_length, slack - comb_delay.maxDelay(), update, min_slack,
|
||||
current_path, crit_path);
|
||||
value = std::min(value, path_budget);
|
||||
}
|
||||
@ -110,9 +110,9 @@ static delay_t walk_paths(Context *ctx, bool update, PortRefList *crit_path)
|
||||
IdString clock_domain = ctx->getPortClock(cell.second.get(), port.first);
|
||||
if (clock_domain != IdString()) {
|
||||
delay_t slack = default_slack; // TODO: clock constraints
|
||||
delay_t clkToQ;
|
||||
DelayInfo clkToQ;
|
||||
if (ctx->getCellDelay(cell.second.get(), clock_domain, port.first, clkToQ))
|
||||
slack -= clkToQ;
|
||||
slack -= clkToQ.maxDelay();
|
||||
if (port.second.net)
|
||||
follow_net(ctx, port.second.net, 0, slack, update, min_slack, ¤t_path, crit_path);
|
||||
}
|
||||
@ -197,12 +197,12 @@ delay_t timing_analysis(Context *ctx, bool print_fmax, bool print_path)
|
||||
++i;
|
||||
auto &driver = net->driver;
|
||||
auto driver_cell = driver.cell;
|
||||
delay_t comb_delay;
|
||||
DelayInfo comb_delay;
|
||||
ctx->getCellDelay(sink_cell, last_port, driver.port, comb_delay);
|
||||
total += comb_delay;
|
||||
log_info("%4d %4d Source %s.%s\n", comb_delay, total, driver_cell->name.c_str(ctx),
|
||||
total += comb_delay.maxDelay();
|
||||
log_info("%4d %4d Source %s.%s\n", comb_delay.maxDelay(), total, driver_cell->name.c_str(ctx),
|
||||
driver.port.c_str(ctx));
|
||||
delay_t net_delay = ctx->getNetinfoRouteDelay(net, i);
|
||||
auto net_delay = ctx->getNetinfoRouteDelay(net, i);
|
||||
total += net_delay;
|
||||
auto driver_loc = ctx->getBelLocation(driver_cell->bel);
|
||||
auto sink_loc = ctx->getBelLocation(sink_cell->bel);
|
||||
|
@ -442,7 +442,7 @@ DecalXY Arch::getGroupDecal(GroupId pip) const { return {}; };
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, delay_t &delay) const
|
||||
bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -802,7 +802,7 @@ struct Arch : BaseCtx
|
||||
|
||||
// Get the delay through a cell from one port to another, returning false
|
||||
// if no path exists
|
||||
bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, delay_t &delay) const;
|
||||
bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const;
|
||||
// Get the associated clock to a port, or empty if the port is combinational
|
||||
IdString getPortClock(const CellInfo *cell, IdString port) const;
|
||||
// Return true if a port is a clock
|
||||
|
@ -427,7 +427,7 @@ DecalXY Arch::getGroupDecal(GroupId group) const { return groups.at(group).decal
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, delay_t &delay) const
|
||||
bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -211,7 +211,7 @@ struct Arch : BaseCtx
|
||||
DecalXY getPipDecal(PipId pip) const;
|
||||
DecalXY getGroupDecal(GroupId group) const;
|
||||
|
||||
bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, delay_t &delay) const;
|
||||
bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const;
|
||||
IdString getPortClock(const CellInfo *cell, IdString port) const;
|
||||
bool isClockPort(const CellInfo *cell, IdString port) const;
|
||||
|
||||
|
@ -51,10 +51,11 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), sel
|
||||
propertyEditor->treeWidget()->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
propertyEditor->treeWidget()->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
|
||||
QLineEdit *lineEdit = new QLineEdit();
|
||||
lineEdit->setClearButtonEnabled(true);
|
||||
lineEdit->addAction(QIcon(":/icons/resources/zoom.png"), QLineEdit::LeadingPosition);
|
||||
lineEdit->setPlaceholderText("Search...");
|
||||
searchEdit = new QLineEdit();
|
||||
searchEdit->setClearButtonEnabled(true);
|
||||
searchEdit->addAction(QIcon(":/icons/resources/zoom.png"), QLineEdit::LeadingPosition);
|
||||
searchEdit->setPlaceholderText("Search...");
|
||||
connect(searchEdit, SIGNAL(returnPressed()), this, SLOT(onSearchInserted()));
|
||||
|
||||
actionFirst = new QAction("", this);
|
||||
actionFirst->setIcon(QIcon(":/icons/resources/resultset_first.png"));
|
||||
@ -123,7 +124,7 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), sel
|
||||
topWidget->setLayout(vbox1);
|
||||
vbox1->setSpacing(5);
|
||||
vbox1->setContentsMargins(0, 0, 0, 0);
|
||||
vbox1->addWidget(lineEdit);
|
||||
vbox1->addWidget(searchEdit);
|
||||
vbox1->addWidget(treeView);
|
||||
|
||||
QWidget *toolbarWidget = new QWidget();
|
||||
@ -714,4 +715,19 @@ void DesignWidget::onItemDoubleClicked(QTreeWidgetItem *item, int column)
|
||||
}
|
||||
|
||||
void DesignWidget::onDoubleClicked(const QModelIndex &index) { Q_EMIT zoomSelected(); }
|
||||
|
||||
void DesignWidget::onSearchInserted()
|
||||
{
|
||||
if (currentSearch == searchEdit->text()) {
|
||||
currentIndex++;
|
||||
if (currentIndex >= currentSearchIndexes.size())
|
||||
currentIndex = 0;
|
||||
} else {
|
||||
currentSearch = searchEdit->text();
|
||||
currentSearchIndexes = treeModel->search(searchEdit->text());
|
||||
currentIndex = 0;
|
||||
}
|
||||
if (currentSearchIndexes.size() > 0 && currentIndex < currentSearchIndexes.size())
|
||||
selectionModel->setCurrentIndex(currentSearchIndexes.at(currentIndex), QItemSelectionModel::ClearAndSelect);
|
||||
}
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
@ -64,6 +64,7 @@ class DesignWidget : public QWidget
|
||||
void onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
|
||||
void onItemDoubleClicked(QTreeWidgetItem *item, int column);
|
||||
void onDoubleClicked(const QModelIndex &index);
|
||||
void onSearchInserted();
|
||||
public Q_SLOTS:
|
||||
void newContext(Context *ctx);
|
||||
void updateTree();
|
||||
@ -77,6 +78,7 @@ class DesignWidget : public QWidget
|
||||
QTreeView *treeView;
|
||||
QItemSelectionModel *selectionModel;
|
||||
ContextTreeModel *treeModel;
|
||||
QLineEdit *searchEdit;
|
||||
QtVariantPropertyManager *variantManager;
|
||||
QtVariantPropertyManager *readOnlyManager;
|
||||
QtGroupPropertyManager *groupManager;
|
||||
@ -98,6 +100,10 @@ class DesignWidget : public QWidget
|
||||
|
||||
QColor highlightColors[8];
|
||||
QMap<ContextTreeItem *, int> highlightSelected;
|
||||
|
||||
QString currentSearch;
|
||||
QList<QModelIndex> currentSearchIndexes;
|
||||
int currentIndex;
|
||||
};
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
@ -326,4 +326,19 @@ Qt::ItemFlags ContextTreeModel::flags(const QModelIndex &index) const
|
||||
ContextTreeItem *node = nodeFromIndex(index);
|
||||
return Qt::ItemIsEnabled | (node->type() != ElementType::NONE ? Qt::ItemIsSelectable : Qt::NoItemFlags);
|
||||
}
|
||||
|
||||
QList<QModelIndex> ContextTreeModel::search(QString text)
|
||||
{
|
||||
QList<QModelIndex> list;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
for (auto key : nameToItem[i].keys()) {
|
||||
if (key.contains(text, Qt::CaseInsensitive)) {
|
||||
list.append(indexFromNode(nameToItem[i].value(key)));
|
||||
if (list.count() > 500)
|
||||
break; // limit to 500 results
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
NEXTPNR_NAMESPACE_END
|
@ -72,6 +72,7 @@ class ContextTreeModel : public QAbstractItemModel
|
||||
ContextTreeItem *nodeFromIndex(const QModelIndex &idx) const;
|
||||
QModelIndex indexFromNode(ContextTreeItem *node);
|
||||
ContextTreeItem *nodeForIdType(const ElementType type, const QString name) const;
|
||||
QList<QModelIndex> search(QString text);
|
||||
// Override QAbstractItemModel methods
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
|
||||
|
@ -141,18 +141,23 @@ Arch::Arch(ArchArgs args) : args(args)
|
||||
|
||||
#ifdef ICE40_HX1K_ONLY
|
||||
if (args.type == ArchArgs::HX1K) {
|
||||
fast_part = true;
|
||||
chip_info = get_chip_info(reinterpret_cast<const RelPtr<ChipInfoPOD> *>(chipdb_blob_1k));
|
||||
} else {
|
||||
log_error("Unsupported iCE40 chip type.\n");
|
||||
}
|
||||
#else
|
||||
if (args.type == ArchArgs::LP384) {
|
||||
fast_part = false;
|
||||
chip_info = get_chip_info(reinterpret_cast<const RelPtr<ChipInfoPOD> *>(chipdb_blob_384));
|
||||
} else if (args.type == ArchArgs::LP1K || args.type == ArchArgs::HX1K) {
|
||||
fast_part = args.type == ArchArgs::HX1K;
|
||||
chip_info = get_chip_info(reinterpret_cast<const RelPtr<ChipInfoPOD> *>(chipdb_blob_1k));
|
||||
} else if (args.type == ArchArgs::UP5K) {
|
||||
fast_part = false;
|
||||
chip_info = get_chip_info(reinterpret_cast<const RelPtr<ChipInfoPOD> *>(chipdb_blob_5k));
|
||||
} else if (args.type == ArchArgs::LP8K || args.type == ArchArgs::HX8K) {
|
||||
fast_part = args.type == ArchArgs::HX8K;
|
||||
chip_info = get_chip_info(reinterpret_cast<const RelPtr<ChipInfoPOD> *>(chipdb_blob_8k));
|
||||
} else {
|
||||
log_error("Unsupported iCE40 chip type.\n");
|
||||
@ -801,29 +806,29 @@ std::vector<GraphicElement> Arch::getDecalGraphics(DecalId decal) const
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, delay_t &delay) const
|
||||
bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const
|
||||
{
|
||||
if (cell->type == id_icestorm_lc) {
|
||||
if ((fromPort == id_i0 || fromPort == id_i1 || fromPort == id_i2 || fromPort == id_i3) &&
|
||||
(toPort == id_o || toPort == id_lo)) {
|
||||
delay = 450;
|
||||
delay.delay = 450;
|
||||
return true;
|
||||
} else if (fromPort == id_cin && toPort == id_cout) {
|
||||
delay = 120;
|
||||
delay.delay = 120;
|
||||
return true;
|
||||
} else if (fromPort == id_i1 && toPort == id_cout) {
|
||||
delay = 260;
|
||||
delay.delay = 260;
|
||||
return true;
|
||||
} else if (fromPort == id_i2 && toPort == id_cout) {
|
||||
delay = 230;
|
||||
delay.delay = 230;
|
||||
return true;
|
||||
} else if (fromPort == id_clk && toPort == id_o) {
|
||||
delay = 540;
|
||||
delay.delay = 540;
|
||||
return true;
|
||||
}
|
||||
} else if (cell->type == id_icestorm_ram) {
|
||||
if (fromPort == id_rclk) {
|
||||
delay = 2140;
|
||||
delay.delay = 2140;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
19
ice40/arch.h
19
ice40/arch.h
@ -66,7 +66,8 @@ NPNR_PACKED_STRUCT(struct BelPortPOD {
|
||||
NPNR_PACKED_STRUCT(struct PipInfoPOD {
|
||||
// RelPtr<char> name;
|
||||
int32_t src, dst;
|
||||
int32_t delay;
|
||||
int32_t fast_delay;
|
||||
int32_t slow_delay;
|
||||
int8_t x, y;
|
||||
int16_t src_seg, dst_seg;
|
||||
int16_t switch_mask;
|
||||
@ -89,6 +90,9 @@ NPNR_PACKED_STRUCT(struct WireInfoPOD {
|
||||
int32_t num_segments;
|
||||
RelPtr<WireSegmentPOD> segments;
|
||||
|
||||
int32_t fast_delay;
|
||||
int32_t slow_delay;
|
||||
|
||||
int8_t x, y;
|
||||
WireType type;
|
||||
int8_t padding_0;
|
||||
@ -344,6 +348,7 @@ struct ArchArgs
|
||||
|
||||
struct Arch : BaseCtx
|
||||
{
|
||||
bool fast_part;
|
||||
const ChipInfoPOD *chip_info;
|
||||
const PackageInfoPOD *package_info;
|
||||
|
||||
@ -524,6 +529,11 @@ struct Arch : BaseCtx
|
||||
DelayInfo getWireDelay(WireId wire) const
|
||||
{
|
||||
DelayInfo delay;
|
||||
NPNR_ASSERT(wire != WireId());
|
||||
if (fast_part)
|
||||
delay.delay = chip_info->wire_data[wire.index].fast_delay;
|
||||
else
|
||||
delay.delay = chip_info->wire_data[wire.index].slow_delay;
|
||||
return delay;
|
||||
}
|
||||
|
||||
@ -637,7 +647,10 @@ struct Arch : BaseCtx
|
||||
{
|
||||
DelayInfo delay;
|
||||
NPNR_ASSERT(pip != PipId());
|
||||
delay.delay = chip_info->pip_data[pip.index].delay;
|
||||
if (fast_part)
|
||||
delay.delay = chip_info->pip_data[pip.index].fast_delay;
|
||||
else
|
||||
delay.delay = chip_info->pip_data[pip.index].slow_delay;
|
||||
return delay;
|
||||
}
|
||||
|
||||
@ -710,7 +723,7 @@ struct Arch : BaseCtx
|
||||
|
||||
// Get the delay through a cell from one port to another, returning false
|
||||
// if no path exists
|
||||
bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, delay_t &delay) const;
|
||||
bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const;
|
||||
// Get the associated clock to a port, or empty if the port is combinational
|
||||
IdString getPortClock(const CellInfo *cell, IdString port) const;
|
||||
// Return true if a port is a clock
|
||||
|
@ -1,3 +1,5 @@
|
||||
SHELL = /bin/bash
|
||||
|
||||
reports::
|
||||
|
||||
define mkreport
|
||||
@ -10,10 +12,10 @@ report_n$1.txt: hx8kdemo_n$1.asc
|
||||
icetime -m -r report_n$1.txt -d hx8k hx8kdemo_n$1.asc
|
||||
|
||||
hx8kdemo_a$1.asc: hx8kdemo.blif
|
||||
arachne-pnr -d 8k -p hx8kdemo.pcf -o hx8kdemo_a$1.asc -s 1$1 hx8kdemo.blif > hx8kdemo_a$1.log 2>&1
|
||||
{ time arachne-pnr -d 8k -p hx8kdemo.pcf -o hx8kdemo_a$1.asc -s 1$1 hx8kdemo.blif; } > hx8kdemo_a$1.log 2>&1
|
||||
|
||||
hx8kdemo_n$1.asc: hx8kdemo.json
|
||||
../../nextpnr-ice40 --asc hx8kdemo_n$1.asc --json hx8kdemo.json --pcf hx8kdemo.pcf --hx8k --seed 1$1 > hx8kdemo_n$1.log 2>&1
|
||||
{ time ../../nextpnr-ice40 --asc hx8kdemo_n$1.asc --json hx8kdemo.json --pcf hx8kdemo.pcf --hx8k --seed 1$1; } > hx8kdemo_n$1.log 2>&1
|
||||
endef
|
||||
|
||||
$(foreach i,0 1 2 3 4 5 6 7 8 9,$(eval $(call mkreport,$(i))))
|
||||
|
@ -11,36 +11,58 @@
|
||||
"%matplotlib inline\n",
|
||||
"import numpy as np\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"import subprocess\n",
|
||||
"import subprocess, re\n",
|
||||
"\n",
|
||||
"gitrev = subprocess.getoutput(\"git rev-parse --short HEAD\")\n",
|
||||
"\n",
|
||||
"data_a = 1 + np.zeros(10)\n",
|
||||
"data_n = 1 + np.zeros(10)\n",
|
||||
"data_a = np.zeros((10, 2))\n",
|
||||
"data_n = np.zeros((10, 2))\n",
|
||||
"\n",
|
||||
"for i in range(10):\n",
|
||||
" try:\n",
|
||||
" with open(\"report_a%d.txt\" % i, \"r\") as f:\n",
|
||||
" for line in f:\n",
|
||||
" if line.startswith(\"Total path delay:\"):\n",
|
||||
" data_a[i] = float(line.split()[3])\n",
|
||||
" data_a[i, 0] = float(line.split()[3])\n",
|
||||
" except:\n",
|
||||
" pass\n",
|
||||
" data_a[i, 0] = 1.0\n",
|
||||
" \n",
|
||||
" try:\n",
|
||||
" with open(\"report_n%d.txt\" % i, \"r\") as f:\n",
|
||||
" for line in f:\n",
|
||||
" if line.startswith(\"Total path delay:\"):\n",
|
||||
" data_n[i] = float(line.split()[3])\n",
|
||||
" data_n[i, 0] = float(line.split()[3])\n",
|
||||
" except:\n",
|
||||
" pass\n",
|
||||
" data_n[i, 0] = 1.0\n",
|
||||
" \n",
|
||||
" with open(\"hx8kdemo_a%d.log\" % i, \"r\") as f:\n",
|
||||
" for line in f:\n",
|
||||
" match = re.match(r\"real\\s+(\\d+)m(\\d+)\", line)\n",
|
||||
" if match:\n",
|
||||
" data_a[i, 1] = float(match.group(1)) + float(match.group(2))/60\n",
|
||||
" \n",
|
||||
" with open(\"hx8kdemo_n%d.log\" % i, \"r\") as f:\n",
|
||||
" for line in f:\n",
|
||||
" match = re.match(r\"real\\s+(\\d+)m(\\d+)\", line)\n",
|
||||
" if match:\n",
|
||||
" data_n[i, 1] = float(match.group(1)) + float(match.group(2))/60\n",
|
||||
"\n",
|
||||
"plt.figure(figsize=(9,3))\n",
|
||||
"plt.title(\"nextpnr -- ice40/benchmark/ -- %s\" % gitrev)\n",
|
||||
"plt.bar(np.arange(10), data_a, color='blue')\n",
|
||||
"plt.bar(15+np.arange(10), data_n, color='red')\n",
|
||||
"plt.bar(np.arange(10), data_a[:, 0], color='blue')\n",
|
||||
"plt.bar(15+np.arange(10), data_n[:, 0], color='red')\n",
|
||||
"plt.ylabel('Longest path (ns)')\n",
|
||||
"plt.xticks([5, 20], [\"arachne-pnr\", \"nextpnr\"])\n",
|
||||
"plt.xlim(-2, 27)\n",
|
||||
"plt.show()\n",
|
||||
"\n",
|
||||
"plt.figure(figsize=(9,3))\n",
|
||||
"plt.title(\"nextpnr -- ice40/benchmark/ -- %s\" % gitrev)\n",
|
||||
"plt.bar(np.arange(10), data_a[:, 1], color='blue')\n",
|
||||
"plt.bar(15+np.arange(10), data_n[:, 1], color='red')\n",
|
||||
"plt.ylabel('Runtime (minutes)')\n",
|
||||
"plt.xticks([5, 20], [\"arachne-pnr\", \"nextpnr\"])\n",
|
||||
"plt.xlim(-2, 27)\n",
|
||||
"plt.show()"
|
||||
]
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ parser = argparse.ArgumentParser(description="convert ICE40 chip database")
|
||||
parser.add_argument("filename", type=str, help="chipdb input filename")
|
||||
parser.add_argument("-p", "--portspins", type=str, help="path to portpins.inc")
|
||||
parser.add_argument("-g", "--gfxh", type=str, help="path to gfx.h")
|
||||
parser.add_argument("--fast", type=str, help="path to timing data for fast part")
|
||||
parser.add_argument("--slow", type=str, help="path to timing data for slow part")
|
||||
args = parser.parse_args()
|
||||
|
||||
dev_name = None
|
||||
@ -51,6 +53,9 @@ wiretypes = dict()
|
||||
gfx_wire_ids = dict()
|
||||
wire_segments = dict()
|
||||
|
||||
fast_timings = None
|
||||
slow_timings = None
|
||||
|
||||
with open(args.portspins) as f:
|
||||
for line in f:
|
||||
line = line.replace("(", " ")
|
||||
@ -77,6 +82,31 @@ with open(args.gfxh) as f:
|
||||
name = line.strip().rstrip(",")
|
||||
gfx_wire_ids[name] = idx
|
||||
|
||||
def read_timings(filename):
|
||||
db = dict()
|
||||
with open(filename) as f:
|
||||
cell = None
|
||||
for line in f:
|
||||
line = line.split()
|
||||
if len(line) == 0:
|
||||
continue
|
||||
if line[0] == "CELL":
|
||||
cell = line[1]
|
||||
if line[0] == "IOPATH":
|
||||
key = "%s.%s.%s" % (cell, line[1], line[2])
|
||||
v1 = line[3].split(":")[2]
|
||||
v2 = line[4].split(":")[2]
|
||||
v1 = 0 if v1 == "*" else float(v1)
|
||||
v2 = 0 if v2 == "*" else float(v2)
|
||||
db[key] = max(v1, v2)
|
||||
return db
|
||||
|
||||
if args.fast is not None:
|
||||
fast_timings = read_timings(args.fast)
|
||||
|
||||
if args.slow is not None:
|
||||
slow_timings = read_timings(args.slow)
|
||||
|
||||
beltypes["ICESTORM_LC"] = 1
|
||||
beltypes["ICESTORM_RAM"] = 2
|
||||
beltypes["SB_IO"] = 3
|
||||
@ -184,12 +214,18 @@ def wire_type(name):
|
||||
assert 0
|
||||
return wt
|
||||
|
||||
def pipdelay(src, dst):
|
||||
def pipdelay(src, dst, db):
|
||||
if db is None:
|
||||
return 0
|
||||
|
||||
src = wire_names_r[src]
|
||||
dst = wire_names_r[dst]
|
||||
src_type = wire_type(src[2])
|
||||
dst_type = wire_type(dst[2])
|
||||
|
||||
if dst[2].startswith("local_"):
|
||||
return db["LocalMux.I.O"]
|
||||
|
||||
if src_type == "LOCAL" and dst_type == "LOCAL":
|
||||
return 250
|
||||
|
||||
@ -223,7 +259,15 @@ def pipdelay(src, dst):
|
||||
# print(src, dst, src_type, dst_type, file=sys.stderr)
|
||||
assert 0
|
||||
|
||||
def wiredelay(wire, db):
|
||||
if db is None:
|
||||
return 0
|
||||
|
||||
wire = wire_names_r[wire]
|
||||
wtype = wire_type(wire[2])
|
||||
|
||||
# FIXME
|
||||
return 0
|
||||
|
||||
def init_tiletypes(device):
|
||||
global num_tile_types, tile_sizes, tile_bits
|
||||
@ -748,7 +792,8 @@ for wire in range(num_wires):
|
||||
pi = dict()
|
||||
pi["src"] = src
|
||||
pi["dst"] = wire
|
||||
pi["delay"] = pipdelay(src, wire)
|
||||
pi["fast_delay"] = pipdelay(src, wire, fast_timings)
|
||||
pi["slow_delay"] = pipdelay(src, wire, slow_timings)
|
||||
pi["x"] = pip_xy[(src, wire)][0]
|
||||
pi["y"] = pip_xy[(src, wire)][1]
|
||||
pi["switch_mask"] = pip_xy[(src, wire)][2]
|
||||
@ -772,7 +817,8 @@ for wire in range(num_wires):
|
||||
pi = dict()
|
||||
pi["src"] = wire
|
||||
pi["dst"] = dst
|
||||
pi["delay"] = pipdelay(wire, dst)
|
||||
pi["fast_delay"] = pipdelay(wire, dst, fast_timings)
|
||||
pi["slow_delay"] = pipdelay(wire, dst, slow_timings)
|
||||
pi["x"] = pip_xy[(wire, dst)][0]
|
||||
pi["y"] = pip_xy[(wire, dst)][1]
|
||||
pi["switch_mask"] = pip_xy[(wire, dst)][2]
|
||||
@ -891,6 +937,9 @@ for wire, info in enumerate(wireinfo):
|
||||
else:
|
||||
bba.u32(0, "segments")
|
||||
|
||||
bba.u32(wiredelay(wire, fast_timings), "fast_delay")
|
||||
bba.u32(wiredelay(wire, slow_timings), "slow_delay")
|
||||
|
||||
bba.u8(info["x"], "x")
|
||||
bba.u8(info["y"], "y")
|
||||
bba.u8(wiretypes[wire_type(info["name"])], "type")
|
||||
@ -923,7 +972,8 @@ for info in pipinfo:
|
||||
# bba.s("X%d/Y%d/%s->%s" % (info["x"], info["y"], src_segname, dst_segname), "name")
|
||||
bba.u32(info["src"], "src")
|
||||
bba.u32(info["dst"], "dst")
|
||||
bba.u32(info["delay"], "delay")
|
||||
bba.u32(info["fast_delay"], "fast_delay")
|
||||
bba.u32(info["slow_delay"], "slow_delay")
|
||||
bba.u8(info["x"], "x")
|
||||
bba.u8(info["y"], "y")
|
||||
bba.u16(src_seg, "src_seg")
|
||||
|
@ -14,17 +14,28 @@ file(MAKE_DIRECTORY ice40/chipdbs/)
|
||||
add_library(ice40_chipdb OBJECT ice40/chipdbs/)
|
||||
target_compile_definitions(ice40_chipdb PRIVATE NEXTPNR_NAMESPACE=nextpnr_${family})
|
||||
target_include_directories(ice40_chipdb PRIVATE ${family}/)
|
||||
|
||||
if (MSVC)
|
||||
target_sources(ice40_chipdb PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/ice40/resource/embed.cc)
|
||||
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/ice40/resources/chipdb.rc PROPERTIES LANGUAGE RC)
|
||||
foreach (dev ${devices})
|
||||
if (dev EQUAL "5k")
|
||||
set(OPT_FAST "")
|
||||
set(OPT_SLOW --slow ${ICEBOX_ROOT}/timings-up5k.txt)
|
||||
elseif(dev EQUAL "384")
|
||||
set(OPT_FAST "")
|
||||
set(OPT_SLOW --slow ${ICEBOX_ROOT}/timings-lp384.txt)
|
||||
else()
|
||||
set(OPT_FAST --fast ${ICEBOX_ROOT}/timings-hx${dev}.txt)
|
||||
set(OPT_SLOW --slow ${ICEBOX_ROOT}/timings-lp${dev}.txt)
|
||||
endif()
|
||||
set(DEV_TXT_DB ${ICEBOX_ROOT}/chipdb-${dev}.txt)
|
||||
set(DEV_CC_BBA_DB ${CMAKE_CURRENT_SOURCE_DIR}/ice40/chipdbs/chipdb-${dev}.bba)
|
||||
set(DEV_CC_DB ${CMAKE_CURRENT_SOURCE_DIR}/ice40/chipdbs/chipdb-${dev}.bin)
|
||||
set(DEV_PORTS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ice40/portpins.inc)
|
||||
set(DEV_GFXH ${CMAKE_CURRENT_SOURCE_DIR}/ice40/gfx.h)
|
||||
add_custom_command(OUTPUT ${DEV_CC_BBA_DB}
|
||||
COMMAND ${PYTHON_EXECUTABLE} ${DB_PY} -p ${DEV_PORTS_INC} -g ${DEV_GFXH} ${DEV_TXT_DB} > ${DEV_CC_BBA_DB}
|
||||
COMMAND ${PYTHON_EXECUTABLE} ${DB_PY} -p ${DEV_PORTS_INC} -g ${DEV_GFXH} ${OPT_FAST} ${OPT_SLOW} ${DEV_TXT_DB} > ${DEV_CC_BBA_DB}
|
||||
DEPENDS ${DEV_TXT_DB} ${DB_PY}
|
||||
)
|
||||
add_custom_command(OUTPUT ${DEV_CC_DB}
|
||||
@ -40,13 +51,23 @@ if (MSVC)
|
||||
else()
|
||||
target_compile_options(ice40_chipdb PRIVATE -g0 -O0 -w)
|
||||
foreach (dev ${devices})
|
||||
if (dev EQUAL "5k")
|
||||
set(OPT_FAST "")
|
||||
set(OPT_SLOW --slow ${ICEBOX_ROOT}/timings_up5k.txt)
|
||||
elseif(dev EQUAL "384")
|
||||
set(OPT_FAST "")
|
||||
set(OPT_SLOW --slow ${ICEBOX_ROOT}/timings_lp384.txt)
|
||||
else()
|
||||
set(OPT_FAST --fast ${ICEBOX_ROOT}/timings_hx${dev}.txt)
|
||||
set(OPT_SLOW --slow ${ICEBOX_ROOT}/timings_lp${dev}.txt)
|
||||
endif()
|
||||
set(DEV_TXT_DB ${ICEBOX_ROOT}/chipdb-${dev}.txt)
|
||||
set(DEV_CC_BBA_DB ${CMAKE_CURRENT_SOURCE_DIR}/ice40/chipdbs/chipdb-${dev}.bba)
|
||||
set(DEV_CC_DB ${CMAKE_CURRENT_SOURCE_DIR}/ice40/chipdbs/chipdb-${dev}.cc)
|
||||
set(DEV_PORTS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ice40/portpins.inc)
|
||||
set(DEV_GFXH ${CMAKE_CURRENT_SOURCE_DIR}/ice40/gfx.h)
|
||||
add_custom_command(OUTPUT ${DEV_CC_BBA_DB}
|
||||
COMMAND ${PYTHON_EXECUTABLE} ${DB_PY} -p ${DEV_PORTS_INC} -g ${DEV_GFXH} ${DEV_TXT_DB} > ${DEV_CC_BBA_DB}.new
|
||||
COMMAND ${PYTHON_EXECUTABLE} ${DB_PY} -p ${DEV_PORTS_INC} -g ${DEV_GFXH} ${OPT_FAST} ${OPT_SLOW} ${DEV_TXT_DB} > ${DEV_CC_BBA_DB}.new
|
||||
COMMAND mv ${DEV_CC_BBA_DB}.new ${DEV_CC_BBA_DB}
|
||||
DEPENDS ${DEV_TXT_DB} ${DB_PY}
|
||||
)
|
||||
|
@ -114,17 +114,58 @@ class PlacementLegaliser
|
||||
public:
|
||||
PlacementLegaliser(Context *ctx) : ctx(ctx){};
|
||||
|
||||
void print_stats(const char *point)
|
||||
{
|
||||
float distance_sum = 0;
|
||||
float max_distance = 0;
|
||||
int moved_cells = 0;
|
||||
int unplaced_cells = 0;
|
||||
for (auto orig : originalPositions) {
|
||||
if (ctx->cells.at(orig.first)->bel == BelId()) {
|
||||
unplaced_cells++;
|
||||
continue;
|
||||
}
|
||||
Loc newLoc = ctx->getBelLocation(ctx->cells.at(orig.first)->bel);
|
||||
if (newLoc != orig.second) {
|
||||
float distance = std::sqrt(std::pow(newLoc.x - orig.second.x, 2) + pow(newLoc.y - orig.second.y, 2));
|
||||
moved_cells++;
|
||||
distance_sum += distance;
|
||||
if (distance > max_distance)
|
||||
max_distance = distance;
|
||||
}
|
||||
}
|
||||
log_info(" moved %d cells, %d unplaced (after %s)\n", moved_cells, unplaced_cells, point);
|
||||
if (moved_cells > 0) {
|
||||
log_info(" average distance %f\n", (distance_sum / moved_cells));
|
||||
log_info(" maximum distance %f\n", max_distance);
|
||||
}
|
||||
}
|
||||
|
||||
bool legalise()
|
||||
{
|
||||
log_info("Legalising design..\n");
|
||||
for (auto &cell : ctx->cells) {
|
||||
CellInfo *ci = cell.second.get();
|
||||
if (!ctx->getBelGlobalBuf(ci->bel) && cell.second->type == ctx->id("ICESTORM_LC")) {
|
||||
originalPositions[cell.first] = ctx->getBelLocation(ci->bel);
|
||||
}
|
||||
}
|
||||
init_logic_cells();
|
||||
bool legalised_carries = legalise_carries();
|
||||
if (!legalised_carries && !ctx->force)
|
||||
return false;
|
||||
print_stats("carry legalisation");
|
||||
legalise_others();
|
||||
print_stats("misc. cell legalisation");
|
||||
legalise_logic_tiles();
|
||||
print_stats("logic cell legalisation");
|
||||
bool replaced_cells = replace_cells();
|
||||
print_stats("cell replacement");
|
||||
|
||||
ctx->assignArchInfo();
|
||||
|
||||
|
||||
|
||||
return legalised_carries && replaced_cells;
|
||||
}
|
||||
|
||||
@ -501,6 +542,7 @@ class PlacementLegaliser
|
||||
Context *ctx;
|
||||
std::unordered_set<IdString> rippedCells;
|
||||
std::unordered_set<IdString> createdCells;
|
||||
std::unordered_map<IdString, Loc> originalPositions;
|
||||
// Go from X and Y position to logic cells, setting occupied to true if a Bel is unavailable
|
||||
std::vector<std::vector<std::vector<std::pair<BelId, bool>>>> logic_bels;
|
||||
};
|
||||
|
@ -1,21 +0,0 @@
|
||||
def get_drivers(wire):
|
||||
wid = chip.getWireByName(wire)
|
||||
assert not wid.nil(), "wire {} not found".format(wire)
|
||||
bp = chip.getBelPinUphill(wid)
|
||||
if not bp.bel.nil():
|
||||
print("Bel pin: {}.{}".format(chip.getBelName(bp.bel), str(bp.pin)))
|
||||
for pip in sorted(chip.getPipsUphill(wid), key=lambda x: x.index):
|
||||
print("Pip: {}".format(chip.getWireName(chip.getPipSrcWire(pip))))
|
||||
|
||||
|
||||
def get_loads(wire):
|
||||
wid = chip.getWireByName(wire)
|
||||
assert not wid.nil(), "wire {} not found".format(wire)
|
||||
for bp in sorted(chip.getBelPinsDownhill(wid), key=lambda x: (x.bel.index, x.pin)):
|
||||
print("Bel pin: {}.{}".format(chip.getBelName(bp.bel), str(bp.pin)))
|
||||
for pip in sorted(chip.getPipsDownhill(wid), key=lambda x: x.index):
|
||||
print("Pip: {}".format(chip.getWireName(chip.getPipDstWire(pip))))
|
||||
|
||||
|
||||
#get_drivers("12_14_lutff_7/in_3")
|
||||
#get_loads("12_14_lutff_global/clk")
|
@ -1,7 +0,0 @@
|
||||
# Run: PYTHONPATH=. python3 python/python_mod_test.py
|
||||
from nextpnrpy_ice40 import Chip, ChipArgs, iCE40Type
|
||||
args = ChipArgs()
|
||||
args.type = iCE40Type.HX1K
|
||||
chip = Chip(args)
|
||||
for wire in chip.getWires():
|
||||
print(chip.getWireName(wire))
|
@ -1,2 +0,0 @@
|
||||
for wire in chip.getWires():
|
||||
print(chip.getWireName(wire))
|
Loading…
Reference in New Issue
Block a user