aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClifford Wolf <clifford@clifford.at>2018-06-13 12:38:28 +0200
committerClifford Wolf <clifford@clifford.at>2018-06-13 12:38:28 +0200
commit145c849596bdcd24f3b4e617eeb4ee06e1b93452 (patch)
tree243d63251d31498f4e92bfdae3ae0d0f087f9957
parent4d7f18dd98a7ef9540a279a8e27cb9dbef355af7 (diff)
parentde0918c28758b09f638e02ffc04fad989321da1b (diff)
downloadnextpnr-145c849596bdcd24f3b4e617eeb4ee06e1b93452.tar.gz
nextpnr-145c849596bdcd24f3b4e617eeb4ee06e1b93452.tar.bz2
nextpnr-145c849596bdcd24f3b4e617eeb4ee06e1b93452.zip
Merge branch 'master' of gitlab.com:SymbioticEDA/nextpnr
-rw-r--r--CMakeLists.txt22
-rw-r--r--common/design_utils.h33
-rw-r--r--common/pybindings.cc4
-rw-r--r--dummy/main.cc2
-rw-r--r--frontend/json/jsonparse.cc154
-rw-r--r--gui/mainwindow.cc83
-rw-r--r--gui/mainwindow.h8
-rw-r--r--ice40/cells.cc36
-rw-r--r--ice40/cells.h6
-rw-r--r--ice40/chip.cc24
-rw-r--r--ice40/chip.h20
-rw-r--r--ice40/chipdb.py38
-rw-r--r--ice40/main.cc92
-rw-r--r--ice40/pack.cc55
-rw-r--r--ice40/pack_tests/io_wrapper.v169
-rwxr-xr-xice40/pack_tests/test.sh4
-rw-r--r--ice40/pcf.cc71
-rw-r--r--ice40/pcf.h33
-rw-r--r--tests/dummy/main.cc9
-rw-r--r--tests/dummy/main.cpp18
-rw-r--r--tests/ice40/hx1k.cc87
-rw-r--r--tests/ice40/hx8k.cc87
-rw-r--r--tests/ice40/lp1k.cc87
-rw-r--r--tests/ice40/lp384.cc87
-rw-r--r--tests/ice40/lp8k.cc87
-rw-r--r--tests/ice40/main.cc8
-rw-r--r--tests/ice40/main.cpp18
-rw-r--r--tests/ice40/up5k.cc87
28 files changed, 1078 insertions, 351 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b0976d9a..74a94ae4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -79,36 +79,42 @@ aux_source_directory(frontend/json/ JSON_PARSER_FILES)
set(COMMON_FILES ${COMMON_SRC_FILES} ${JSON_PARSER_FILES})
set(CMAKE_BUILD_TYPE Debug)
+if(MINGW)
+ add_definitions("-Wa,-mbig-obj")
+endif(MINGW)
+
foreach (family ${FAMILIES})
string(TOUPPER ${family} ufamily)
aux_source_directory(${family}/ ${ufamily}_FILES)
+ aux_source_directory(tests/${family}/ ${ufamily}_TEST_FILES)
# Add the CLI binary target
add_executable(nextpnr-${family} ${COMMON_FILES} ${${ufamily}_FILES} ${GUI_SOURCE_FILES})
+ target_compile_definitions(nextpnr-${family} PRIVATE MAIN_EXECUTABLE)
+
# Add the importable Python module target
PYTHON_ADD_MODULE(nextpnrpy_${family} ${COMMON_FILES} ${${ufamily}_FILES})
- target_compile_definitions(nextpnrpy_${family} PRIVATE PYTHON_MODULE)
# Add any new per-architecture targets here
+ add_executable(nextpnr-${family}-test ${${ufamily}_TEST_FILES} ${COMMON_FILES} ${${ufamily}_FILES})
+ target_link_libraries(nextpnr-${family}-test PRIVATE gtest_main)
+ add_test(${family}-test ${CMAKE_CURRENT_BINARY_DIR}/nextpnr-${family}-test)
+
# Set ${family_targets} to the list of targets being build for this family
- set(family_targets nextpnr-${family} nextpnrpy_${family})
+ set(family_targets nextpnr-${family} nextpnrpy_${family} nextpnr-${family}-test)
# Include the family-specific CMakeFile
include(${family}/family.cmake)
foreach (target ${family_targets})
# Include family-specific source files to all family targets and set defines appropriately
target_include_directories(${target} PRIVATE ${family}/)
- target_compile_definitions(${target} PRIVATE NEXTPNR_NAMESPACE=nextpnr_${family} ARCH_${ufamily} ARCHNAME=${family} -DQT_NO_KEYWORDS)
+ target_compile_definitions(${target} PRIVATE NEXTPNR_NAMESPACE=nextpnr_${family} ARCH_${ufamily} ARCHNAME=${family} QT_NO_KEYWORDS)
target_link_libraries(${target} LINK_PUBLIC ${Boost_LIBRARIES} ${PYTHON_LIBRARIES} ${GUI_LIBRARY_FILES})
endforeach (target)
-
- add_executable(nextpnr-${family}-test "")
- target_sources(nextpnr-${family}-test PRIVATE tests/${family}/main.cpp)
- target_link_libraries(nextpnr-${family}-test PRIVATE gtest_main)
- add_test(${family}-test ${CMAKE_CURRENT_BINARY_DIR}/nextpnr-${family}-test)
endforeach (family)
file(GLOB_RECURSE CLANGFORMAT_FILES *.cc *.h)
string(REGEX REPLACE "[^;]*/ice40/chipdbs/chipdb-[^;]*.cc" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}")
string(REGEX REPLACE "[^;]*/3rdparty[^;]*" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}")
+string(REGEX REPLACE "[^;]*/generated[^;]*" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}")
add_custom_target(
clangformat
diff --git a/common/design_utils.h b/common/design_utils.h
index 2acc7d20..daf6e050 100644
--- a/common/design_utils.h
+++ b/common/design_utils.h
@@ -22,6 +22,8 @@
#ifndef DESIGN_UTILS_H
#define DESIGN_UTILS_H
+#include <algorithm>
+
NEXTPNR_NAMESPACE_BEGIN
/*
@@ -35,23 +37,36 @@ void replace_port(CellInfo *old_cell, IdString old_name, CellInfo *rep_cell,
// If a net drives a given port of a cell matching a predicate (in many
// cases more than one cell type, e.g. SB_DFFxx so a predicate is used), return
// the first instance of that cell (otherwise nullptr). If exclusive is set to
-// true, then this cell must be the only load
+// true, then this cell must be the only load. If ignore_cell is set, that cell
+// is not considered
template <typename F1>
CellInfo *net_only_drives(NetInfo *net, F1 cell_pred, IdString port,
- bool exclusive = false)
+ bool exclusive = false, CellInfo *exclude = nullptr)
{
if (net == nullptr)
return nullptr;
- if (exclusive && (net->users.size() != 1)) {
- return nullptr;
- } else {
- for (const auto &load : net->users) {
- if (cell_pred(load.cell) && load.port == port) {
- return load.cell;
+ if (exclusive) {
+ if (exclude == nullptr) {
+ if (net->users.size() != 1)
+ return nullptr;
+ } else {
+ if (net->users.size() > 2) {
+ return nullptr;
+ } else if (net->users.size() == 2) {
+ if (std::find_if(net->users.begin(), net->users.end(),
+ [exclude](const PortRef &ref) {
+ return ref.cell == exclude;
+ }) == net->users.end())
+ return nullptr;
}
}
- return nullptr;
}
+ for (const auto &load : net->users) {
+ if (load.cell != exclude && cell_pred(load.cell) && load.port == port) {
+ return load.cell;
+ }
+ }
+ return nullptr;
}
// If a net is driven by a given port of a cell matching a predicate, return
diff --git a/common/pybindings.cc b/common/pybindings.cc
index 7c43c84f..761d6571 100644
--- a/common/pybindings.cc
+++ b/common/pybindings.cc
@@ -140,7 +140,7 @@ static wchar_t *program;
void init_python(const char *executable)
{
-#ifndef PYTHON_MODULE
+#ifdef MAIN_EXECUTABLE
program = Py_DecodeLocale(executable, NULL);
if (program == NULL) {
fprintf(stderr, "Fatal error: cannot decode executable filename\n");
@@ -162,7 +162,7 @@ void init_python(const char *executable)
void deinit_python()
{
-#ifndef PYTHON_MODULE
+#ifdef MAIN_EXECUTABLE
Py_Finalize();
PyMem_RawFree(program);
#endif
diff --git a/dummy/main.cc b/dummy/main.cc
index 3b9e6ba3..7aa2f08f 100644
--- a/dummy/main.cc
+++ b/dummy/main.cc
@@ -17,7 +17,7 @@
*
*/
-#ifndef PYTHON_MODULE
+#ifdef MAIN_EXECUTABLE
#include <QApplication>
#include "mainwindow.h"
diff --git a/frontend/json/jsonparse.cc b/frontend/json/jsonparse.cc
index 3f965ce4..79ee0a4d 100644
--- a/frontend/json/jsonparse.cc
+++ b/frontend/json/jsonparse.cc
@@ -341,24 +341,25 @@ void json_import_cell_params(Design *design, string &modname, CellInfo *cell,
modname.c_str());
}
-void json_import_cell_ports(Design *design, string &modname, CellInfo *cell,
- string &port_name, JsonNode *dir_node,
- JsonNode *wire_group_node)
+template <typename F>
+void json_import_ports(Design *design, const string &modname,
+ const string &obj_name, const string &port_name,
+ JsonNode *dir_node, JsonNode *wire_group_node, F visitor)
{
- // Examine and connect a single port of the given cell to its nets,
- // generating them as necessary
-
+ // Examine a port of a cell or the design. For every bit of the port,
+ // the connected net will be processed and `visitor` will be called
+ // with (PortType dir, std::string name, NetInfo *net)
assert(dir_node);
if (json_debug)
log_info(" Examining port %s, node %s\n", port_name.c_str(),
- cell->name.c_str());
+ obj_name.c_str());
if (!wire_group_node)
log_error("JSON no connection match "
"for port_direction \'%s\' of node \'%s\' "
"in module \'%s\'\n",
- port_name.c_str(), cell->name.c_str(), modname.c_str());
+ port_name.c_str(), obj_name.c_str(), modname.c_str());
assert(wire_group_node);
@@ -377,7 +378,7 @@ void json_import_cell_ports(Design *design, string &modname, CellInfo *cell,
else
log_error("JSON unknown port direction \'%s\' in node \'%s\' "
"of module \'%s\'\n",
- dir_node->data_string.c_str(), cell->name.c_str(),
+ dir_node->data_string.c_str(), obj_name.c_str(),
modname.c_str());
//
// Find an update, or create a net to connect
@@ -398,18 +399,12 @@ void json_import_cell_ports(Design *design, string &modname, CellInfo *cell,
// There is/are no connections to this port.
//
// Create the port, but leave the net NULL
- PortInfo this_port;
-
- //
- this_port.name = port_info.name;
- this_port.type = port_info.type;
- this_port.net = NULL;
- cell->ports[this_port.name] = this_port;
+ visitor(port_info.type, port_info.name, nullptr);
if (json_debug)
log_info(" Port \'%s\' has no connection in \'%s\'\n",
- this_port.name.c_str(), cell->name.c_str());
+ port_info.name.c_str(), obj_name.c_str());
} else
for (int index = 0; index < wire_group_node->data_array.size();
@@ -417,13 +412,10 @@ void json_import_cell_ports(Design *design, string &modname, CellInfo *cell,
//
JsonNode *wire_node;
PortInfo this_port;
- PortRef port_ref;
bool const_input = false;
IdString net_id;
//
wire_node = wire_group_node->data_array[index];
- port_ref.cell = cell;
-
//
// Pick a name for this port
if (is_bus)
@@ -433,8 +425,6 @@ void json_import_cell_ports(Design *design, string &modname, CellInfo *cell,
this_port.name = port_info.name;
this_port.type = port_info.type;
- port_ref.port = this_port.name;
-
if (wire_node->type == 'N') {
int net_num;
@@ -500,7 +490,7 @@ void json_import_cell_ports(Design *design, string &modname, CellInfo *cell,
"\'%s\' of port \'%s\' "
"in cell \'%s\' of module \'%s\'\n",
wire_node->data_string.c_str(),
- port_name.c_str(), cell->name.c_str(),
+ port_name.c_str(), obj_name.c_str(),
modname.c_str());
} else
@@ -511,17 +501,8 @@ void json_import_cell_ports(Design *design, string &modname, CellInfo *cell,
if (json_debug)
log_info(" Inserting port \'%s\' into cell \'%s\'\n",
- this_port.name.c_str(), cell->name.c_str());
-
- this_port.net = this_net;
-
- cell->ports[this_port.name] = this_port;
-
- if (this_port.type == PORT_OUT) {
- assert(this_net->driver.cell == NULL);
- this_net->driver = port_ref;
- } else
- this_net->users.push_back(port_ref);
+ this_port.name.c_str(), obj_name.c_str());
+ visitor(this_port.type, this_port.name, this_net);
if (design->nets.count(this_net->name) == 0)
design->nets[this_net->name] = this_net;
@@ -632,14 +613,98 @@ void json_import_cell(Design *design, string modname, JsonNode *cell_node,
dir_node = pdir_node->data_dict.at(port_name);
wire_group_node = connections->data_dict.at(port_name);
- json_import_cell_ports(design, modname, cell, port_name, dir_node,
- wire_group_node);
+ json_import_ports(
+ design, modname, cell->name, port_name, dir_node,
+ wire_group_node,
+ [cell](PortType type, const std::string &name, NetInfo *net) {
+ cell->ports[name] = PortInfo{name, net, type};
+ PortRef pr;
+ pr.cell = cell;
+ pr.port = name;
+ if (net != nullptr) {
+ if (type == PORT_IN || type == PORT_INOUT) {
+ net->users.push_back(pr);
+ } else if (type == PORT_OUT) {
+ assert(net->driver.cell == nullptr);
+ net->driver = pr;
+ }
+ }
+ });
}
design->cells[cell->name] = cell;
// check_all_nets_driven(design);
}
+static void insert_iobuf(Design *design, NetInfo *net, PortType type,
+ const string &name)
+{
+ // Instantiate a architecture-independent IO buffer connected to a given
+ // net, of a given type, and named after the IO port.
+ //
+ // During packing, this generic IO buffer will be converted to an
+ // architecure primitive.
+ //
+ CellInfo *iobuf = new CellInfo();
+ iobuf->name = name;
+ std::copy(net->attrs.begin(), net->attrs.end(),
+ std::inserter(iobuf->attrs, iobuf->attrs.begin()));
+ if (type == PORT_IN) {
+ log_info("processing input port %s\n", name.c_str());
+ iobuf->type = "$nextpnr_ibuf";
+ iobuf->ports["O"] = PortInfo{"O", net, PORT_OUT};
+
+ assert(net->driver.cell == nullptr);
+ net->driver.port = "O";
+ net->driver.cell = iobuf;
+ } else if (type == PORT_OUT) {
+ log_info("processing output port %s\n", name.c_str());
+ iobuf->type = "$nextpnr_obuf";
+ iobuf->ports["I"] = PortInfo{"I", net, PORT_IN};
+ PortRef ref;
+ ref.cell = iobuf;
+ ref.port = "I";
+ net->users.push_back(ref);
+ } else if (type == PORT_INOUT) {
+ log_info("processing inout port %s\n", name.c_str());
+ iobuf->type = "$nextpnr_iobuf";
+ iobuf->ports["I"] = PortInfo{"I", nullptr, PORT_IN};
+ if (net->driver.cell != NULL) {
+ // Split the input and output nets for bidir ports
+ NetInfo *net2 = new NetInfo();
+ net2->name = "$" + net->name.str() + "$iobuf_i";
+ net2->driver = net->driver;
+ net2->driver.cell->ports[net2->driver.port].net = net2;
+ net->driver.cell = nullptr;
+ design->nets[net2->name] = net2;
+ iobuf->ports["I"].net = net2;
+ PortRef ref;
+ ref.cell = iobuf;
+ ref.port = "I";
+ net2->users.push_back(ref);
+ }
+ iobuf->ports["O"] = PortInfo{"O", net, PORT_OUT};
+ assert(net->driver.cell == nullptr);
+ net->driver.port = "O";
+ net->driver.cell = iobuf;
+ } else {
+ assert(false);
+ }
+ design->cells[iobuf->name] = iobuf;
+}
+
+void json_import_toplevel_port(Design *design, const string &modname,
+ const string &portname, JsonNode *node)
+{
+ JsonNode *dir_node = node->data_dict.at("direction");
+ JsonNode *nets_node = node->data_dict.at("bits");
+ json_import_ports(
+ design, modname, "Top Level IO", portname, dir_node, nets_node,
+ [design](PortType type, const std::string &name, NetInfo *net) {
+ insert_iobuf(design, net, type, name);
+ });
+}
+
void json_import(Design *design, string modname, JsonNode *node)
{
if (is_blackbox(node))
@@ -665,6 +730,23 @@ void json_import(Design *design, string modname, JsonNode *node)
}
}
+ if (node->data_dict.count("ports")) {
+ JsonNode *ports_parent = node->data_dict.at("ports");
+
+ // N.B. ports must be imported after cells for tristate behaviour
+ // to be correct
+ // Loop through all ports
+ for (int portid = 0; portid < GetSize(ports_parent->data_dict_keys);
+ portid++) {
+ JsonNode *here, *param_node;
+
+ here = ports_parent->data_dict.at(
+ ports_parent->data_dict_keys[portid]);
+ json_import_toplevel_port(design, modname,
+ ports_parent->data_dict_keys[portid],
+ here);
+ }
+ }
check_all_nets_driven(design);
}
diff --git a/gui/mainwindow.cc b/gui/mainwindow.cc
index c436fd6c..5f62ecec 100644
--- a/gui/mainwindow.cc
+++ b/gui/mainwindow.cc
@@ -4,9 +4,6 @@
#include <string>
#include "emb.h"
#include "pybindings.h"
-#include "qtpropertymanager.h"
-#include "qttreepropertybrowser.h"
-#include "qtvariantproperty.h"
#include "ui_mainwindow.h"
#include <QDate>
@@ -18,16 +15,43 @@ MainWindow::MainWindow(Design *_design, QWidget *parent)
ui->setupUi(this);
ui->treeWidget->setColumnCount(1);
ui->treeWidget->setHeaderLabel(QString("Items"));
- QTreeWidgetItem *belroot = new QTreeWidgetItem(ui->treeWidget);
- belroot->setText(0, QString("Bels"));
- ui->treeWidget->insertTopLevelItem(0, belroot);
- QList<QTreeWidgetItem *> items;
+ ui->treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(ui->treeWidget, &QTreeWidget::customContextMenuRequested, this,
+ &MainWindow::prepareMenu);
+
+ QTreeWidgetItem *bel_root = new QTreeWidgetItem(ui->treeWidget);
+ bel_root->setText(0, QString("Bels"));
+ ui->treeWidget->insertTopLevelItem(0, bel_root);
+ QList<QTreeWidgetItem *> bel_items;
for (auto bel : design->chip.getBels()) {
auto name = design->chip.getBelName(bel);
- items.append(new QTreeWidgetItem((QTreeWidget *)nullptr,
- QStringList(QString(name.c_str()))));
+ bel_items.append(new QTreeWidgetItem(
+ (QTreeWidget *)nullptr, QStringList(QString(name.c_str()))));
}
- belroot->addChildren(items);
+ bel_root->addChildren(bel_items);
+
+ QTreeWidgetItem *wire_root = new QTreeWidgetItem(ui->treeWidget);
+ QList<QTreeWidgetItem *> wire_items;
+ wire_root->setText(0, QString("Wires"));
+ ui->treeWidget->insertTopLevelItem(0, wire_root);
+ for (auto wire : design->chip.getWires()) {
+ auto name = design->chip.getWireName(wire);
+ wire_items.append(new QTreeWidgetItem(
+ (QTreeWidget *)nullptr, QStringList(QString(name.c_str()))));
+ }
+ wire_root->addChildren(wire_items);
+
+ QTreeWidgetItem *pip_root = new QTreeWidgetItem(ui->treeWidget);
+ QList<QTreeWidgetItem *> pip_items;
+ pip_root->setText(0, QString("Pips"));
+ ui->treeWidget->insertTopLevelItem(0, pip_root);
+ for (auto pip : design->chip.getPips()) {
+ auto name = design->chip.getPipName(pip);
+ pip_items.append(new QTreeWidgetItem(
+ (QTreeWidget *)nullptr, QStringList(QString(name.c_str()))));
+ }
+ pip_root->addChildren(pip_items);
+
PyImport_ImportModule("emb");
write = [this](std::string s) {
@@ -38,7 +62,8 @@ MainWindow::MainWindow(Design *_design, QWidget *parent)
emb::set_stdout(write);
std::string title = "nextpnr-ice40 - " + design->chip.getChipName();
setWindowTitle(title.c_str());
- QtVariantPropertyManager *variantManager = new QtVariantPropertyManager();
+
+ variantManager = new QtVariantPropertyManager();
int i = 0;
QtProperty *topItem = variantManager->addProperty(
@@ -197,9 +222,9 @@ MainWindow::MainWindow(Design *_design, QWidget *parent)
QString::number(i++) + QLatin1String(" Color Property"));
topItem->addSubProperty(item);
- QtVariantEditorFactory *variantFactory = new QtVariantEditorFactory();
+ variantFactory = new QtVariantEditorFactory();
- QtTreePropertyBrowser *variantEditor = new QtTreePropertyBrowser();
+ variantEditor = new QtTreePropertyBrowser();
variantEditor->setFactoryForManager(variantManager, variantFactory);
variantEditor->addProperty(topItem);
variantEditor->setPropertiesWithoutValueMarked(true);
@@ -210,13 +235,37 @@ MainWindow::MainWindow(Design *_design, QWidget *parent)
MainWindow::~MainWindow()
{
-
- // delete variantManager;
- // delete variantFactory;
- // delete variantEditor;
+ delete variantManager;
+ delete variantFactory;
+ delete variantEditor;
delete ui;
}
+void MainWindow::prepareMenu(const QPoint &pos)
+{
+ QTreeWidget *tree = ui->treeWidget;
+
+ QTreeWidgetItem *item = tree->itemAt(pos);
+
+ QAction *selectAction = new QAction("&Select", this);
+ selectAction->setStatusTip("Select item on view");
+ connect(selectAction, SIGNAL(triggered()), this, SLOT(selectObject(item)));
+
+ QMenu menu(this);
+ menu.addAction(selectAction);
+
+ QPoint pt(pos);
+ menu.exec(tree->mapToGlobal(pos));
+}
+
+void MainWindow::selectObject(QTreeWidgetItem *item)
+{
+ ui->plainTextEdit->moveCursor(QTextCursor::End);
+ ui->plainTextEdit->insertPlainText(
+ std::string("selected " + item->text(0).toStdString() + "\n").c_str());
+ ui->plainTextEdit->moveCursor(QTextCursor::End);
+}
+
void handle_system_exit() { exit(-1); }
int MainWindow::executePython(std::string command)
diff --git a/gui/mainwindow.h b/gui/mainwindow.h
index 6f3e515f..27918486 100644
--- a/gui/mainwindow.h
+++ b/gui/mainwindow.h
@@ -3,6 +3,9 @@
#include "emb.h"
#include "nextpnr.h"
+#include "qtpropertymanager.h"
+#include "qttreepropertybrowser.h"
+#include "qtvariantproperty.h"
#include <QMainWindow>
@@ -27,11 +30,16 @@ class MainWindow : public QMainWindow
private Q_SLOTS:
void on_lineEdit_returnPressed();
+ void prepareMenu(const QPoint &pos);
+ void selectObject(QTreeWidgetItem *item);
private:
Ui::MainWindow *ui;
emb::stdout_write_type write;
Design *design;
+ QtVariantPropertyManager *variantManager;
+ QtVariantEditorFactory *variantFactory;
+ QtTreePropertyBrowser *variantEditor;
};
#endif // MAINWINDOW_H
diff --git a/ice40/cells.cc b/ice40/cells.cc
index ad728d2c..b038db68 100644
--- a/ice40/cells.cc
+++ b/ice40/cells.cc
@@ -60,6 +60,26 @@ CellInfo *create_ice_cell(Design *design, IdString type, IdString name)
add_port(new_cell, "LO", PORT_OUT);
add_port(new_cell, "O", PORT_OUT);
add_port(new_cell, "OUT", PORT_OUT);
+ } else if (type == "SB_IO") {
+ new_cell->params["PIN_TYPE"] = "0";
+ new_cell->params["PULLUP"] = "0";
+ new_cell->params["NEG_TRIGGER"] = "0";
+ new_cell->params["IOSTANDARD"] = "SB_LVCMOS";
+
+ add_port(new_cell, "PACKAGE_PIN", PORT_INOUT);
+
+ add_port(new_cell, "LATCH_INPUT_VALUE", PORT_IN);
+ add_port(new_cell, "CLOCK_ENABLE", PORT_IN);
+ add_port(new_cell, "INPUT_CLK", PORT_IN);
+ add_port(new_cell, "OUTPUT_CLK", PORT_IN);
+
+ add_port(new_cell, "OUTPUT_ENABLE", PORT_IN);
+ add_port(new_cell, "D_OUT_0", PORT_IN);
+ add_port(new_cell, "D_OUT_1", PORT_IN);
+
+ add_port(new_cell, "D_IN_0", PORT_OUT);
+ add_port(new_cell, "D_IN_1", PORT_OUT);
+
} else {
log_error("unable to create iCE40 cell of type %s", type.c_str());
}
@@ -128,4 +148,20 @@ void dff_to_lc(CellInfo *dff, CellInfo *lc, bool pass_thru_lut)
replace_port(dff, "Q", lc, "O");
}
+void nxio_to_sb(CellInfo *nxio, CellInfo *sbio)
+{
+ if (nxio->type == "$nextpnr_ibuf") {
+ sbio->params["PIN_TYPE"] = "1";
+ auto pu_attr = nxio->attrs.find("PULLUP");
+ if (pu_attr != nxio->attrs.end())
+ sbio->params["PULLUP"] = pu_attr->second;
+ replace_port(nxio, "O", sbio, "D_IN_0");
+ } else if (nxio->type == "$nextpnr_obuf") {
+ sbio->params["PIN_TYPE"] = "25";
+ replace_port(nxio, "I", sbio, "D_OUT_0");
+ } else {
+ assert(false);
+ }
+}
+
NEXTPNR_NAMESPACE_END
diff --git a/ice40/cells.h b/ice40/cells.h
index 34a034cd..3cf0b718 100644
--- a/ice40/cells.h
+++ b/ice40/cells.h
@@ -47,6 +47,9 @@ inline bool is_ff(const CellInfo *cell)
cell->type == "SB_DFFNESS" || cell->type == "SB_DFFNES";
}
+// Return true if a cell is a SB_IO
+inline bool is_sb_io(const CellInfo *cell) { return cell->type == "SB_IO"; }
+
// Convert a SB_LUT primitive to (part of) an ICESTORM_LC, swapping ports
// as needed. Set no_dff if a DFF is not being used, so that the output
// can be reconnected
@@ -58,6 +61,9 @@ void lut_to_lc(CellInfo *lut, CellInfo *lc, bool no_dff = true);
// ignored
void dff_to_lc(CellInfo *dff, CellInfo *lc, bool pass_thru_lut = false);
+// Convert a nextpnr IO buffer to a SB_IO
+void nxio_to_sb(CellInfo *nxio, CellInfo *sbio);
+
NEXTPNR_NAMESPACE_END
#endif
diff --git a/ice40/chip.cc b/ice40/chip.cc
index de33b020..441e65f2 100644
--- a/ice40/chip.cc
+++ b/ice40/chip.cc
@@ -99,6 +99,16 @@ Chip::Chip(ChipArgs args) : args(args)
}
#endif
+ package_info = nullptr;
+ for (int i = 0; i < chip_info.num_packages; i++) {
+ if (chip_info.packages_data[i].name == args.package) {
+ package_info = &(chip_info.packages_data[i]);
+ break;
+ }
+ }
+ if (package_info == nullptr)
+ log_error("Unsupported package '%s'.\n", args.package.c_str());
+
bel_to_cell.resize(chip_info.num_bels);
wire_to_net.resize(chip_info.num_wires);
pip_to_net.resize(chip_info.num_pips);
@@ -231,6 +241,20 @@ PipId Chip::getPipByName(IdString name) const
// -----------------------------------------------------------------------
+BelId Chip::getPackagePinBel(const std::string &pin) const
+{
+ for (int i = 0; i < package_info->num_pins; i++) {
+ if (package_info->pins[i].name == pin) {
+ BelId id;
+ id.index = package_info->pins[i].bel_index;
+ return id;
+ }
+ }
+ return BelId();
+}
+
+// -----------------------------------------------------------------------
+
PosInfo Chip::getBelPosition(BelId bel) const
{
PosInfo pos;
diff --git a/ice40/chip.h b/ice40/chip.h
index 2c95bf4e..5eea1b8e 100644
--- a/ice40/chip.h
+++ b/ice40/chip.h
@@ -118,6 +118,19 @@ struct WireInfoPOD
float x, y;
};
+struct PackagePinPOD
+{
+ const char *name;
+ int32_t bel_index;
+};
+
+struct PackageInfoPOD
+{
+ const char *name;
+ int num_pins;
+ PackagePinPOD *pins;
+};
+
enum TileType
{
TILE_NONE = 0,
@@ -173,12 +186,13 @@ struct ChipInfoPOD
{
int width, height;
int num_bels, num_wires, num_pips;
- int num_switches;
+ int num_switches, num_packages;
BelInfoPOD *bel_data;
WireInfoPOD *wire_data;
PipInfoPOD *pip_data;
TileType *tile_grid;
BitstreamInfoPOD *bits_info;
+ PackageInfoPOD *packages_data;
};
extern ChipInfoPOD chip_info_384;
@@ -417,11 +431,13 @@ struct ChipArgs
HX8K,
UP5K
} type = NONE;
+ std::string package;
};
struct Chip
{
ChipInfoPOD chip_info;
+ PackageInfoPOD *package_info;
mutable std::unordered_map<IdString, int> bel_by_name;
mutable std::unordered_map<IdString, int> wire_by_name;
@@ -682,6 +698,8 @@ struct Chip
return range;
}
+ BelId getPackagePinBel(const std::string &pin) const;
+
// -------------------------------------------------
PosInfo getBelPosition(BelId bel) const;
diff --git a/ice40/chipdb.py b/ice40/chipdb.py
index 9b246f8b..946197d3 100644
--- a/ice40/chipdb.py
+++ b/ice40/chipdb.py
@@ -23,6 +23,8 @@ switches = list()
ierens = list()
+packages = list()
+
wire_uphill_belport = dict()
wire_downhill_belports = dict()
@@ -123,6 +125,11 @@ with open(sys.argv[1], "r") as f:
mode = ("ieren",)
continue
+ if line[0] == ".pins":
+ mode = ("pins", line[1])
+ packages.append((line[1], []))
+ continue
+
if (line[0][0] == ".") or (mode is None):
mode = None
continue
@@ -157,9 +164,16 @@ with open(sys.argv[1], "r") as f:
assert m
bits.append((int(m.group(1)), int(m.group(2))))
tile_bits[mode[1]].append((name, bits))
+ continue
if mode[0] == "ieren":
ierens.append(tuple([int(_) for _ in line]))
+ continue
+
+ if mode[0] == "pins":
+ packages[-1][1].append((line[0], int(line[1]), int(line[2]), int(line[3])))
+ continue
+
def add_bel_input(bel, wire, port):
if wire not in wire_downhill_belports:
wire_downhill_belports[wire] = set()
@@ -392,6 +406,21 @@ for wire in range(num_wires):
wireinfo.append(info)
+packageinfo = []
+
+for package in packages:
+ name, pins = package
+ pins_info = []
+ for pin in pins:
+ pinname, x, y, z = pin
+ pin_bel = "%d_%d_io%d" % (x, y, z)
+ bel_idx = bel_name.index(pin_bel)
+ pins_info.append('{"%s", %d}' % (pinname, bel_idx))
+ print("static PackagePinPOD package_%s_pins[%d] = {" % (name, len(pins_info)))
+ print(",\n".join(pins_info))
+ print("};")
+ packageinfo.append('{"%s", %d, package_%s_pins}' % (name, len(pins_info), name))
+
tilegrid = []
for y in range(dev_height):
for x in range(dev_width):
@@ -460,13 +489,18 @@ print("static TileType tile_grid_%s[%d] = {" % (dev_name, len(tilegrid)))
print(",\n".join(tilegrid))
print("};")
+
+print("static PackageInfoPOD package_info_%s[%d] = {" % (dev_name, len(packageinfo)))
+print(",\n".join(packageinfo))
+print("};")
+
print('}')
print('NEXTPNR_NAMESPACE_BEGIN')
print("ChipInfoPOD chip_info_%s = {" % dev_name)
-print(" %d, %d, %d, %d, %d, %d," % (dev_width, dev_height, len(bel_name), num_wires, len(pipinfo), len(switchinfo)))
+print(" %d, %d, %d, %d, %d, %d, %d," % (dev_width, dev_height, len(bel_name), num_wires, len(pipinfo), len(switchinfo), len(packageinfo)))
print(" bel_data_%s, wire_data_%s, pip_data_%s," % (dev_name, dev_name, dev_name))
-print(" tile_grid_%s, &bits_info_%s" % (dev_name, dev_name))
+print(" tile_grid_%s, &bits_info_%s, package_info_%s" % (dev_name, dev_name, dev_name))
print("};")
print('NEXTPNR_NAMESPACE_END')
diff --git a/ice40/main.cc b/ice40/main.cc
index 0e989819..8ccec77b 100644
--- a/ice40/main.cc
+++ b/ice40/main.cc
@@ -17,7 +17,7 @@
*
*/
-#ifndef PYTHON_MODULE
+#ifdef MAIN_EXECUTABLE
#include <QApplication>
#include <boost/filesystem/convenience.hpp>
@@ -30,6 +30,7 @@
#include "mainwindow.h"
#include "nextpnr.h"
#include "pack.h"
+#include "pcf.h"
#include "place.h"
#include "pybindings.h"
#include "route.h"
@@ -65,10 +66,8 @@ int main(int argc, char *argv[])
po::options_description options("Allowed options");
options.add_options()("help,h", "show help");
- options.add_options()("test", "just a check");
options.add_options()("gui", "start gui");
options.add_options()("svg", "dump SVG file");
- options.add_options()("pack", "pack design prior to place and route");
options.add_options()("pack-only",
"pack design only without placement or routing");
@@ -76,6 +75,8 @@ int main(int argc, char *argv[])
"python file to execute");
options.add_options()("json", po::value<std::string>(),
"JSON design file to ingest");
+ options.add_options()("pcf", po::value<std::string>(),
+ "PCF constraints file to ingest");
options.add_options()("asc", po::value<std::string>(),
"asc bitstream file to write");
options.add_options()("version,v", "show version");
@@ -85,7 +86,8 @@ int main(int argc, char *argv[])
options.add_options()("hx1k", "set device type to iCE40HX1K");
options.add_options()("hx8k", "set device type to iCE40HX8K");
options.add_options()("up5k", "set device type to iCE40UP5K");
-
+ options.add_options()("package", po::value<std::string>(),
+ "set device package");
po::positional_options_description pos;
pos.add("run", -1);
@@ -129,41 +131,48 @@ int main(int argc, char *argv[])
if (chipArgs.type != ChipArgs::NONE)
goto help;
chipArgs.type = ChipArgs::LP384;
+ chipArgs.package = "qn32";
}
if (vm.count("lp1k")) {
if (chipArgs.type != ChipArgs::NONE)
goto help;
chipArgs.type = ChipArgs::LP1K;
+ chipArgs.package = "tq144";
}
if (vm.count("lp8k")) {
if (chipArgs.type != ChipArgs::NONE)
goto help;
chipArgs.type = ChipArgs::LP8K;
+ chipArgs.package = "ct256";
}
if (vm.count("hx1k")) {
if (chipArgs.type != ChipArgs::NONE)
goto help;
chipArgs.type = ChipArgs::HX1K;
+ chipArgs.package = "tq144";
}
if (vm.count("hx8k")) {
if (chipArgs.type != ChipArgs::NONE)
goto help;
chipArgs.type = ChipArgs::HX8K;
+ chipArgs.package = "ct256";
}
if (vm.count("up5k")) {
if (chipArgs.type != ChipArgs::NONE)
goto help;
chipArgs.type = ChipArgs::UP5K;
+ chipArgs.package = "sg48";
}
- if (chipArgs.type == ChipArgs::NONE)
+ if (chipArgs.type == ChipArgs::NONE) {
chipArgs.type = ChipArgs::HX1K;
-
+ chipArgs.package = "tq144";
+ }
#ifdef ICE40_HX1K_ONLY
if (chipArgs.type != ChipArgs::HX1K) {
std::cout << "This version of nextpnr-ice40 is built with HX1K-support "
@@ -172,71 +181,14 @@ int main(int argc, char *argv[])
}
#endif
+ if (vm.count("package"))
+ chipArgs.package = vm["package"].as<std::string>();
+
Design design(chipArgs);
init_python(argv[0]);
python_export_global("design", design);
python_export_global("chip", design.chip);
- if (vm.count("test")) {
- int bel_count = 0, wire_count = 0, pip_count = 0;
-
- std::cout << "Checking bel names.\n";
- for (auto bel : design.chip.getBels()) {
- auto name = design.chip.getBelName(bel);
- assert(bel == design.chip.getBelByName(name));
- bel_count++;
- }
- std::cout << " checked " << bel_count << " bels.\n";
-
- std::cout << "Checking wire names.\n";
- for (auto wire : design.chip.getWires()) {
- auto name = design.chip.getWireName(wire);
- assert(wire == design.chip.getWireByName(name));
- wire_count++;
- }
- std::cout << " checked " << wire_count << " wires.\n";
-
- std::cout << "Checking pip names.\n";
- for (auto pip : design.chip.getPips()) {
- auto name = design.chip.getPipName(pip);
- assert(pip == design.chip.getPipByName(name));
- pip_count++;
- }
- std::cout << " checked " << pip_count << " pips.\n";
-
- std::cout << "Checking uphill -> downhill consistency.\n";
- for (auto dst : design.chip.getWires()) {
- for (auto uphill_pip : design.chip.getPipsUphill(dst)) {
- bool found_downhill = false;
- for (auto downhill_pip : design.chip.getPipsDownhill(
- design.chip.getPipSrcWire(uphill_pip))) {
- if (uphill_pip == downhill_pip) {
- assert(!found_downhill);
- found_downhill = true;
- }
- }
- assert(found_downhill);
- }
- }
-
- std::cout << "Checking downhill -> uphill consistency.\n";
- for (auto dst : design.chip.getWires()) {
- for (auto downhill_pip : design.chip.getPipsDownhill(dst)) {
- bool found_uphill = false;
- for (auto uphill_pip : design.chip.getPipsUphill(
- design.chip.getPipDstWire(downhill_pip))) {
- if (uphill_pip == downhill_pip) {
- assert(!found_uphill);
- found_uphill = true;
- }
- }
- assert(found_uphill);
- }
- }
-
- return 0;
- }
-
if (vm.count("svg")) {
std::cout << "<svg xmlns=\"http://www.w3.org/2000/svg\" "
"xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n";
@@ -256,9 +208,13 @@ int main(int argc, char *argv[])
std::istream *f = new std::ifstream(filename);
parse_json_file(f, filename, &design);
- if (vm.count("pack") || vm.count("pack-only")) {
- pack_design(&design);
+
+ if (vm.count("pcf")) {
+ std::ifstream pcf(vm["pcf"].as<std::string>());
+ apply_pcf(&design, pcf);
}
+
+ pack_design(&design);
if (!vm.count("pack-only")) {
place_design(&design);
route_design(&design);
diff --git a/ice40/pack.cc b/ice40/pack.cc
index 8f770a07..72dcadea 100644
--- a/ice40/pack.cc
+++ b/ice40/pack.cc
@@ -134,10 +134,65 @@ static void pack_constants(Design *design)
}
}
+static bool is_nextpnr_iob(CellInfo *cell)
+{
+ return cell->type == "$nextpnr_ibuf" || cell->type == "$nextpnr_obuf" ||
+ cell->type == "$nextpnr_iobuf";
+}
+
+// Pack IO buffers
+static void pack_io(Design *design)
+{
+ std::unordered_set<IdString> packed_cells;
+ std::vector<CellInfo *> new_cells;
+
+ for (auto cell : design->cells) {
+ CellInfo *ci = cell.second;
+ if (is_nextpnr_iob(ci)) {
+ CellInfo *sb = nullptr;
+ if (ci->type == "$nextpnr_ibuf" || ci->type == "$nextpnr_iobuf") {
+ sb = net_only_drives(ci->ports.at("O").net, is_sb_io,
+ "PACKAGE_PIN", true, ci);
+
+ } else if (ci->type == "$nextpnr_obuf") {
+ sb = net_only_drives(ci->ports.at("I").net, is_sb_io,
+ "PACKAGE_PIN", true, ci);
+ }
+ if (sb != nullptr) {
+ // Trivial case, SB_IO used. Just destroy the net and the
+ // iobuf
+ log_info("%s feeds SB_IO %s, removing %s %s.\n",
+ ci->name.c_str(), sb->name.c_str(), ci->type.c_str(),
+ ci->name.c_str());
+ NetInfo *net = sb->ports.at("PACKAGE_PIN").net;
+ if (net != nullptr) {
+ design->nets.erase(net->name);
+ sb->ports.at("PACKAGE_PIN").net = nullptr;
+ }
+ } else {
+ // Create a SB_IO buffer
+ sb = create_ice_cell(design, "SB_IO");
+ nxio_to_sb(ci, sb);
+ new_cells.push_back(sb);
+ }
+ packed_cells.insert(ci->name);
+ std::copy(ci->attrs.begin(), ci->attrs.end(),
+ std::inserter(sb->attrs, sb->attrs.begin()));
+ }
+ }
+ for (auto pcell : packed_cells) {
+ design->cells.erase(pcell);
+ }
+ for (auto ncell : new_cells) {
+ design->cells[ncell->name] = ncell;
+ }
+}
+
// Main pack function
void pack_design(Design *design)
{
pack_constants(design);
+ pack_io(design);
pack_lut_lutffs(design);
pack_nonlut_ffs(design);
}
diff --git a/ice40/pack_tests/io_wrapper.v b/ice40/pack_tests/io_wrapper.v
deleted file mode 100644
index b58d6c0c..00000000
--- a/ice40/pack_tests/io_wrapper.v
+++ /dev/null
@@ -1,169 +0,0 @@
-module io_wrapper(input clk_pin, cen_pin, rst_pin, ina_pin, inb_pin,
- output outa_pin, outb_pin, outc_pin, outd_pin);
-
- wire clk, cen, rst, ina, inb, outa, outb, outc, outd;
-
- (* BEL="0_14_io1" *)
- SB_IO #(
- .PIN_TYPE(6'b 0000_01),
- .PULLUP(1'b0),
- .NEG_TRIGGER(1'b0)
- ) clk_iob (
- .PACKAGE_PIN(clk_pin),
- .LATCH_INPUT_VALUE(),
- .CLOCK_ENABLE(),
- .INPUT_CLK(),
- .OUTPUT_CLK(),
- .OUTPUT_ENABLE(),
- .D_OUT_0(),
- .D_OUT_1(),
- .D_IN_0(clk),
- .D_IN_1()
- );
-
- (* BEL="0_14_io0" *)
- SB_IO #(
- .PIN_TYPE(6'b 0000_01),
- .PULLUP(1'b0),
- .NEG_TRIGGER(1'b0)
- ) cen_iob (
- .PACKAGE_PIN(cen_pin),
- .LATCH_INPUT_VALUE(),
- .CLOCK_ENABLE(),
- .INPUT_CLK(),
- .OUTPUT_CLK(),
- .OUTPUT_ENABLE(),
- .D_OUT_0(),
- .D_OUT_1(),
- .D_IN_0(cen),
- .D_IN_1()
- );
-
- (* BEL="0_13_io1" *)
- SB_IO #(
- .PIN_TYPE(6'b 0000_01),
- .PULLUP(1'b0),
- .NEG_TRIGGER(1'b0)
- ) rst_iob (
- .PACKAGE_PIN(rst_pin),
- .LATCH_INPUT_VALUE(),
- .CLOCK_ENABLE(),
- .INPUT_CLK(),
- .OUTPUT_CLK(),
- .OUTPUT_ENABLE(),
- .D_OUT_0(),
- .D_OUT_1(),
- .D_IN_0(rst),
- .D_IN_1()
- );
-
- (* BEL="0_13_io0" *)
- SB_IO #(
- .PIN_TYPE(6'b 0000_01),
- .PULLUP(1'b0),
- .NEG_TRIGGER(1'b0)
- ) ina_iob (
- .PACKAGE_PIN(ina_pin),
- .LATCH_INPUT_VALUE(),
- .CLOCK_ENABLE(),
- .INPUT_CLK(),
- .OUTPUT_CLK(),
- .OUTPUT_ENABLE(),
- .D_OUT_0(),
- .D_OUT_1(),
- .D_IN_0(ina),
- .D_IN_1()
- );
-
- (* BEL="0_12_io1" *)
- SB_IO #(
- .PIN_TYPE(6'b 0000_01),
- .PULLUP(1'b0),
- .NEG_TRIGGER(1'b0)
- ) inb_iob (
- .PACKAGE_PIN(inb_pin),
- .LATCH_INPUT_VALUE(),
- .CLOCK_ENABLE(),
- .INPUT_CLK(),
- .OUTPUT_CLK(),
- .OUTPUT_ENABLE(),
- .D_OUT_0(),
- .D_OUT_1(),
- .D_IN_0(inb),
- .D_IN_1()
- );
-
- (* BEL="0_12_io0" *)
- SB_IO #(
- .PIN_TYPE(6'b 0110_01),
- .PULLUP(1'b0),
- .NEG_TRIGGER(1'b0)
- ) outa_iob (
- .PACKAGE_PIN(outa_pin),
- .LATCH_INPUT_VALUE(),
- .CLOCK_ENABLE(),
- .INPUT_CLK(),
- .OUTPUT_CLK(),
- .OUTPUT_ENABLE(),
- .D_OUT_0(outa),
- .D_OUT_1(),
- .D_IN_0(),
- .D_IN_1()
- );
-
- (* BEL="0_11_io1" *)
- SB_IO #(
- .PIN_TYPE(6'b 0110_01),
- .PULLUP(1'b0),
- .NEG_TRIGGER(1'b0)
- ) outb_iob (
- .PACKAGE_PIN(outb_pin),
- .LATCH_INPUT_VALUE(),
- .CLOCK_ENABLE(),
- .INPUT_CLK(),
- .OUTPUT_CLK(),
- .OUTPUT_ENABLE(),
- .D_OUT_0(outb),
- .D_OUT_1(),
- .D_IN_0(),
- .D_IN_1()
- );
-
- (* BEL="0_11_io0" *)
- SB_IO #(
- .PIN_TYPE(6'b 0110_01),
- .PULLUP(1'b0),
- .NEG_TRIGGER(1'b0)
- ) outc_iob (
- .PACKAGE_PIN(outc_pin),
- .LATCH_INPUT_VALUE(),
- .CLOCK_ENABLE(),
- .INPUT_CLK(),
- .OUTPUT_CLK(),
- .OUTPUT_ENABLE(),
- .D_OUT_0(outc),
- .D_OUT_1(),
- .D_IN_0(),
- .D_IN_1()
- );
-
- (* BEL="0_10_io1" *)
- SB_IO #(
- .PIN_TYPE(6'b 0110_01),
- .PULLUP(1'b0),
- .NEG_TRIGGER(1'b0)
- ) outd_iob (
- .PACKAGE_PIN(outa_pin),
- .LATCH_INPUT_VALUE(),
- .CLOCK_ENABLE(),
- .INPUT_CLK(),
- .OUTPUT_CLK(),
- .OUTPUT_ENABLE(),
- .D_OUT_0(outd),
- .D_OUT_1(),
- .D_IN_0(),
- .D_IN_1()
- );
-
- top top_i(.clk(clk), .rst(rst), .cen(cen), .ina(ina), .inb(inb), .outa(outa), .outb(outb), .outc(outc), .outd(outd));
-endmodule
diff --git a/ice40/pack_tests/test.sh b/ice40/pack_tests/test.sh
index dd1f345c..b36c01dc 100755
--- a/ice40/pack_tests/test.sh
+++ b/ice40/pack_tests/test.sh
@@ -1,8 +1,8 @@
#!/usr/bin/env bash
set -ex
NAME=${1%.v}
-yosys -p "synth_ice40 -nocarry -top io_wrapper; write_json ${NAME}.json" $1 io_wrapper.v
-../../nextpnr-ice40 --json ${NAME}.json --pack --asc ${NAME}.asc
+yosys -p "synth_ice40 -nocarry -top top; write_json ${NAME}.json" $1
+../../nextpnr-ice40 --json ${NAME}.json --pcf test.pcf --asc ${NAME}.asc
icebox_vlog -p test.pcf ${NAME}.asc > ${NAME}_out.v
yosys -p "read_verilog +/ice40/cells_sim.v;\
diff --git a/ice40/pcf.cc b/ice40/pcf.cc
new file mode 100644
index 00000000..75c32731
--- /dev/null
+++ b/ice40/pcf.cc
@@ -0,0 +1,71 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2018 Clifford Wolf <clifford@clifford.at>
+ *
+ * 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 "pcf.h"
+#include <sstream>
+#include "log.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+// Read a w
+
+// Apply PCF constraints to a pre-packing design
+void apply_pcf(Design *design, std::istream &in)
+{
+ if (!in)
+ log_error("failed to open PCF file");
+ std::string line;
+ while (std::getline(in, line)) {
+ size_t cstart = line.find("#");
+ if (cstart != std::string::npos)
+ line = line.substr(0, cstart);
+ std::stringstream ss(line);
+ std::vector<std::string> words;
+ std::string tmp;
+ while (ss >> tmp)
+ words.push_back(tmp);
+ if (words.size() == 0)
+ continue;
+ std::string cmd = words.at(0);
+ if (cmd == "set_io") {
+ size_t args_end = 1;
+ while (args_end < words.size() && words.at(args_end).at(0) == '-')
+ args_end++;
+ std::string cell = words.at(args_end);
+ std::string pin = words.at(args_end + 1);
+ auto fnd_cell = design->cells.find(cell);
+ if (fnd_cell == design->cells.end()) {
+ log_warning("unmatched pcf constraint %s\n", cell.c_str());
+ } else {
+ BelId pin_bel = design->chip.getPackagePinBel(pin);
+ if (pin_bel == BelId())
+ log_error("package does not have a pin named %s\n",
+ pin.c_str());
+ fnd_cell->second->attrs["BEL"] =
+ design->chip.getBelName(pin_bel).str();
+ log_info("constrained '%s' to bel '%s'\n", cell.c_str(),
+ fnd_cell->second->attrs["BEL"].c_str());
+ }
+ } else {
+ log_error("unsupported pcf command '%s'\n", cmd.c_str());
+ }
+ }
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/ice40/pcf.h b/ice40/pcf.h
new file mode 100644
index 00000000..c4a7d991
--- /dev/null
+++ b/ice40/pcf.h
@@ -0,0 +1,33 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2018 Clifford Wolf <clifford@clifford.at>
+ *
+ * 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 PCF_H
+#define PCF_H
+
+#include <iostream>
+#include "nextpnr.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+// Apply PCF constraints to a pre-packing design
+void apply_pcf(Design *design, std::istream &in);
+
+NEXTPNR_NAMESPACE_END
+
+#endif // ROUTE_H
diff --git a/tests/dummy/main.cc b/tests/dummy/main.cc
new file mode 100644
index 00000000..1270ccdf
--- /dev/null
+++ b/tests/dummy/main.cc
@@ -0,0 +1,9 @@
+#include "gtest/gtest.h"
+
+#include <vector>
+
+int main(int argc, char **argv)
+{
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/tests/dummy/main.cpp b/tests/dummy/main.cpp
deleted file mode 100644
index b8d22138..00000000
--- a/tests/dummy/main.cpp
+++ /dev/null
@@ -1,18 +0,0 @@
-#include "gtest/gtest.h"
-
-#include <vector>
-
-int main(int argc, char **argv) {
- ::testing::InitGoogleTest(&argc, argv);
- return RUN_ALL_TESTS();
-}
-
-TEST(example, sum_zero) {
- auto result = 0;
- ASSERT_EQ(result, 0);
-}
-
-TEST(example, sum_five) {
- auto result = 15;
- ASSERT_EQ(result, 15);
-}
diff --git a/tests/ice40/hx1k.cc b/tests/ice40/hx1k.cc
new file mode 100644
index 00000000..ceae4c45
--- /dev/null
+++ b/tests/ice40/hx1k.cc
@@ -0,0 +1,87 @@
+#include <vector>
+#include "gtest/gtest.h"
+#include "nextpnr.h"
+
+USING_NEXTPNR_NAMESPACE
+
+class HX1KTest : public ::testing::Test
+{
+ protected:
+ virtual void SetUp()
+ {
+ chipArgs.type = ChipArgs::HX1K;
+ design = new Design(chipArgs);
+ }
+
+ virtual void TearDown() { delete design; }
+
+ ChipArgs chipArgs;
+ Design *design;
+};
+
+TEST_F(HX1KTest, bel_names)
+{
+ int bel_count = 0;
+ for (auto bel : design->chip.getBels()) {
+ auto name = design->chip.getBelName(bel);
+ ASSERT_EQ(bel, design->chip.getBelByName(name));
+ bel_count++;
+ }
+ ASSERT_EQ(bel_count, 1416);
+}
+
+TEST_F(HX1KTest, wire_names)
+{
+ int wire_count = 0;
+ for (auto wire : design->chip.getWires()) {
+ auto name = design->chip.getWireName(wire);
+ assert(wire == design->chip.getWireByName(name));
+ wire_count++;
+ }
+ ASSERT_EQ(wire_count, 27682);
+}
+
+TEST_F(HX1KTest, pip_names)
+{
+ int pip_count = 0;
+ for (auto pip : design->chip.getPips()) {
+ auto name = design->chip.getPipName(pip);
+ assert(pip == design->chip.getPipByName(name));
+ pip_count++;
+ }
+ ASSERT_EQ(pip_count, 319904);
+}
+
+TEST_F(HX1KTest, uphill_to_downhill)
+{
+ for (auto dst : design->chip.getWires()) {
+ for (auto uphill_pip : design->chip.getPipsUphill(dst)) {
+ bool found_downhill = false;
+ for (auto downhill_pip : design->chip.getPipsDownhill(
+ design->chip.getPipSrcWire(uphill_pip))) {
+ if (uphill_pip == downhill_pip) {
+ ASSERT_FALSE(found_downhill);
+ found_downhill = true;
+ }
+ }
+ ASSERT_TRUE(found_downhill);
+ }
+ }
+}
+
+TEST_F(HX1KTest, downhill_to_uphill)
+{
+ for (auto dst : design->chip.getWires()) {
+ for (auto downhill_pip : design->chip.getPipsDownhill(dst)) {
+ bool found_uphill = false;
+ for (auto uphill_pip : design->chip.getPipsUphill(
+ design->chip.getPipDstWire(downhill_pip))) {
+ if (uphill_pip == downhill_pip) {
+ ASSERT_FALSE(found_uphill);
+ found_uphill = true;
+ }
+ }
+ ASSERT_TRUE(found_uphill);
+ }
+ }
+}
diff --git a/tests/ice40/hx8k.cc b/tests/ice40/hx8k.cc
new file mode 100644
index 00000000..060e34dd
--- /dev/null
+++ b/tests/ice40/hx8k.cc
@@ -0,0 +1,87 @@
+#include <vector>
+#include "gtest/gtest.h"
+#include "nextpnr.h"
+
+USING_NEXTPNR_NAMESPACE
+
+class HX8KTest : public ::testing::Test
+{
+ protected:
+ virtual void SetUp()
+ {
+ chipArgs.type = ChipArgs::HX8K;
+ design = new Design(chipArgs);
+ }
+
+ virtual void TearDown() { delete design; }
+
+ ChipArgs chipArgs;
+ Design *design;
+};
+
+TEST_F(HX8KTest, bel_names)
+{
+ int bel_count = 0;
+ for (auto bel : design->chip.getBels()) {
+ auto name = design->chip.getBelName(bel);
+ ASSERT_EQ(bel, design->chip.getBelByName(name));
+ bel_count++;
+ }
+ ASSERT_EQ(bel_count, 7968);
+}
+
+TEST_F(HX8KTest, wire_names)
+{
+ int wire_count = 0;
+ for (auto wire : design->chip.getWires()) {
+ auto name = design->chip.getWireName(wire);
+ assert(wire == design->chip.getWireByName(name));
+ wire_count++;
+ }
+ ASSERT_EQ(wire_count, 135174);
+}
+
+TEST_F(HX8KTest, pip_names)
+{
+ int pip_count = 0;
+ for (auto pip : design->chip.getPips()) {
+ auto name = design->chip.getPipName(pip);
+ assert(pip == design->chip.getPipByName(name));
+ pip_count++;
+ }
+ ASSERT_EQ(pip_count, 1652480);
+}
+
+TEST_F(HX8KTest, uphill_to_downhill)
+{
+ for (auto dst : design->chip.getWires()) {
+ for (auto uphill_pip : design->chip.getPipsUphill(dst)) {
+ bool found_downhill = false;
+ for (auto downhill_pip : design->chip.getPipsDownhill(
+ design->chip.getPipSrcWire(uphill_pip))) {
+ if (uphill_pip == downhill_pip) {
+ ASSERT_FALSE(found_downhill);
+ found_downhill = true;
+ }
+ }
+ ASSERT_TRUE(found_downhill);
+ }
+ }
+}
+
+TEST_F(HX8KTest, downhill_to_uphill)
+{
+ for (auto dst : design->chip.getWires()) {
+ for (auto downhill_pip : design->chip.getPipsDownhill(dst)) {
+ bool found_uphill = false;
+ for (auto uphill_pip : design->chip.getPipsUphill(
+ design->chip.getPipDstWire(downhill_pip))) {
+ if (uphill_pip == downhill_pip) {
+ ASSERT_FALSE(found_uphill);
+ found_uphill = true;
+ }
+ }
+ ASSERT_TRUE(found_uphill);
+ }
+ }
+}
diff --git a/tests/ice40/lp1k.cc b/tests/ice40/lp1k.cc
new file mode 100644
index 00000000..85b35652
--- /dev/null
+++ b/tests/ice40/lp1k.cc
@@ -0,0 +1,87 @@
+#include <vector>
+#include "gtest/gtest.h"
+#include "nextpnr.h"
+
+USING_NEXTPNR_NAMESPACE
+
+class LP1KTest : public ::testing::Test
+{
+ protected:
+ virtual void SetUp()
+ {
+ chipArgs.type = ChipArgs::LP1K;
+ design = new Design(chipArgs);
+ }
+
+ virtual void TearDown() { delete design; }
+
+ ChipArgs chipArgs;
+ Design *design;
+};
+
+TEST_F(LP1KTest, bel_names)
+{
+ int bel_count = 0;
+ for (auto bel : design->chip.getBels()) {
+ auto name = design->chip.getBelName(bel);
+ ASSERT_EQ(bel, design->chip.getBelByName(name));
+ bel_count++;
+ }
+ ASSERT_EQ(bel_count, 1416);
+}
+
+TEST_F(LP1KTest, wire_names)
+{
+ int wire_count = 0;
+ for (auto wire : design->chip.getWires()) {
+ auto name = design->chip.getWireName(wire);
+ assert(wire == design->chip.getWireByName(name));
+ wire_count++;
+ }
+ ASSERT_EQ(wire_count, 27682);
+}
+
+TEST_F(LP1KTest, pip_names)
+{
+ int pip_count = 0;
+ for (auto pip : design->chip.getPips()) {
+ auto name = design->chip.getPipName(pip);
+ assert(pip == design->chip.getPipByName(name));
+ pip_count++;
+ }
+ ASSERT_EQ(pip_count, 319904);
+}
+
+TEST_F(LP1KTest, uphill_to_downhill)
+{
+ for (auto dst : design->chip.getWires()) {
+ for (auto uphill_pip : design->chip.getPipsUphill(dst)) {
+ bool found_downhill = false;
+ for (auto downhill_pip : design->chip.getPipsDownhill(
+ design->chip.getPipSrcWire(uphill_pip))) {
+ if (uphill_pip == downhill_pip) {
+ ASSERT_FALSE(found_downhill);
+ found_downhill = true;
+ }
+ }
+ ASSERT_TRUE(found_downhill);
+ }
+ }
+}
+
+TEST_F(LP1KTest, downhill_to_uphill)
+{
+ for (auto dst : design->chip.getWires()) {
+ for (auto downhill_pip : design->chip.getPipsDownhill(dst)) {
+ bool found_uphill = false;
+ for (auto uphill_pip : design->chip.getPipsUphill(
+ design->chip.getPipDstWire(downhill_pip))) {
+ if (uphill_pip == downhill_pip) {
+ ASSERT_FALSE(found_uphill);
+ found_uphill = true;
+ }
+ }
+ ASSERT_TRUE(found_uphill);
+ }
+ }
+}
diff --git a/tests/ice40/lp384.cc b/tests/ice40/lp384.cc
new file mode 100644
index 00000000..c29938f1
--- /dev/null
+++ b/tests/ice40/lp384.cc
@@ -0,0 +1,87 @@
+#include <vector>
+#include "gtest/gtest.h"
+#include "nextpnr.h"
+
+USING_NEXTPNR_NAMESPACE
+
+class LP384Test : public ::testing::Test
+{
+ protected:
+ virtual void SetUp()
+ {
+ chipArgs.type = ChipArgs::LP384;
+ design = new Design(chipArgs);
+ }
+
+ virtual void TearDown() { delete design; }
+
+ ChipArgs chipArgs;
+ Design *design;
+};
+
+TEST_F(LP384Test, bel_names)
+{
+ int bel_count = 0;
+ for (auto bel : design->chip.getBels()) {
+ auto name = design->chip.getBelName(bel);
+ ASSERT_EQ(bel, design->chip.getBelByName(name));
+ bel_count++;
+ }
+ ASSERT_EQ(bel_count, 440);
+}
+
+TEST_F(LP384Test, wire_names)
+{
+ int wire_count = 0;
+ for (auto wire : design->chip.getWires()) {
+ auto name = design->chip.getWireName(wire);
+ assert(wire == design->chip.getWireByName(name));
+ wire_count++;
+ }
+ ASSERT_EQ(wire_count, 8294);
+}
+
+TEST_F(LP384Test, pip_names)
+{
+ int pip_count = 0;
+ for (auto pip : design->chip.getPips()) {
+ auto name = design->chip.getPipName(pip);
+ assert(pip == design->chip.getPipByName(name));
+ pip_count++;
+ }
+ ASSERT_EQ(pip_count, 86864);
+}
+
+TEST_F(LP384Test, uphill_to_downhill)
+{
+ for (auto dst : design->chip.getWires()) {
+ for (auto uphill_pip : design->chip.getPipsUphill(dst)) {
+ bool found_downhill = false;
+ for (auto downhill_pip : design->chip.getPipsDownhill(
+ design->chip.getPipSrcWire(uphill_pip))) {
+ if (uphill_pip == downhill_pip) {
+ ASSERT_FALSE(found_downhill);
+ found_downhill = true;
+ }
+ }
+ ASSERT_TRUE(found_downhill);
+ }
+ }
+}
+
+TEST_F(LP384Test, downhill_to_uphill)
+{
+ for (auto dst : design->chip.getWires()) {
+ for (auto downhill_pip : design->chip.getPipsDownhill(dst)) {
+ bool found_uphill = false;
+ for (auto uphill_pip : design->chip.getPipsUphill(
+ design->chip.getPipDstWire(downhill_pip))) {
+ if (uphill_pip == downhill_pip) {
+ ASSERT_FALSE(found_uphill);
+ found_uphill = true;
+ }
+ }
+ ASSERT_TRUE(found_uphill);
+ }
+ }
+}
diff --git a/tests/ice40/lp8k.cc b/tests/ice40/lp8k.cc
new file mode 100644
index 00000000..ca2fc424
--- /dev/null
+++ b/tests/ice40/lp8k.cc
@@ -0,0 +1,87 @@
+#include <vector>
+#include "gtest/gtest.h"
+#include "nextpnr.h"
+
+USING_NEXTPNR_NAMESPACE
+
+class LP8KTest : public ::testing::Test
+{
+ protected:
+ virtual void SetUp()
+ {
+ chipArgs.type = ChipArgs::LP8K;
+ design = new Design(chipArgs);
+ }
+
+ virtual void TearDown() { delete design; }
+
+ ChipArgs chipArgs;
+ Design *design;
+};
+
+TEST_F(LP8KTest, bel_names)
+{
+ int bel_count = 0;
+ for (auto bel : design->chip.getBels()) {
+ auto name = design->chip.getBelName(bel);
+ ASSERT_EQ(bel, design->chip.getBelByName(name));
+ bel_count++;
+ }
+ ASSERT_EQ(bel_count, 7968);
+}
+
+TEST_F(LP8KTest, wire_names)
+{
+ int wire_count = 0;
+ for (auto wire : design->chip.getWires()) {
+ auto name = design->chip.getWireName(wire);
+ assert(wire == design->chip.getWireByName(name));
+ wire_count++;
+ }
+ ASSERT_EQ(wire_count, 135174);
+}
+
+TEST_F(LP8KTest, pip_names)
+{
+ int pip_count = 0;
+ for (auto pip : design->chip.getPips()) {
+ auto name = design->chip.getPipName(pip);
+ assert(pip == design->chip.getPipByName(name));
+ pip_count++;
+ }
+ ASSERT_EQ(pip_count, 1652480);
+}
+
+TEST_F(LP8KTest, uphill_to_downhill)
+{
+ for (auto dst : design->chip.getWires()) {
+ for (auto uphill_pip : design->chip.getPipsUphill(dst)) {
+ bool found_downhill = false;
+ for (auto downhill_pip : design->chip.getPipsDownhill(
+ design->chip.getPipSrcWire(uphill_pip))) {
+ if (uphill_pip == downhill_pip) {
+ ASSERT_FALSE(found_downhill);
+ found_downhill = true;
+ }
+ }
+ ASSERT_TRUE(found_downhill);
+ }
+ }
+}
+
+TEST_F(LP8KTest, downhill_to_uphill)
+{
+ for (auto dst : design->chip.getWires()) {
+ for (auto downhill_pip : design->chip.getPipsDownhill(dst)) {
+ bool found_uphill = false;
+ for (auto uphill_pip : design->chip.getPipsUphill(
+ design->chip.getPipDstWire(downhill_pip))) {
+ if (uphill_pip == downhill_pip) {
+ ASSERT_FALSE(found_uphill);
+ found_uphill = true;
+ }
+ }
+ ASSERT_TRUE(found_uphill);
+ }
+ }
+}
diff --git a/tests/ice40/main.cc b/tests/ice40/main.cc
new file mode 100644
index 00000000..e85c836a
--- /dev/null
+++ b/tests/ice40/main.cc
@@ -0,0 +1,8 @@
+#include <vector>
+#include "gtest/gtest.h"
+
+int main(int argc, char **argv)
+{
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/tests/ice40/main.cpp b/tests/ice40/main.cpp
deleted file mode 100644
index b8d22138..00000000
--- a/tests/ice40/main.cpp
+++ /dev/null
@@ -1,18 +0,0 @@
-#include "gtest/gtest.h"
-
-#include <vector>
-
-int main(int argc, char **argv) {
- ::testing::InitGoogleTest(&argc, argv);
- return RUN_ALL_TESTS();
-}
-
-TEST(example, sum_zero) {
- auto result = 0;
- ASSERT_EQ(result, 0);
-}
-
-TEST(example, sum_five) {
- auto result = 15;
- ASSERT_EQ(result, 15);
-}
diff --git a/tests/ice40/up5k.cc b/tests/ice40/up5k.cc
new file mode 100644
index 00000000..38bb30fd
--- /dev/null
+++ b/tests/ice40/up5k.cc
@@ -0,0 +1,87 @@
+#include <vector>
+#include "gtest/gtest.h"
+#include "nextpnr.h"
+
+USING_NEXTPNR_NAMESPACE
+
+class UP5KTest : public ::testing::Test
+{
+ protected:
+ virtual void SetUp()
+ {
+ chipArgs.type = ChipArgs::UP5K;
+ design = new Design(chipArgs);
+ }
+
+ virtual void TearDown() { delete design; }
+
+ ChipArgs chipArgs;
+ Design *design;
+};
+
+TEST_F(UP5KTest, bel_names)
+{
+ int bel_count = 0;
+ for (auto bel : design->chip.getBels()) {
+ auto name = design->chip.getBelName(bel);
+ ASSERT_EQ(bel, design->chip.getBelByName(name));
+ bel_count++;
+ }
+ ASSERT_EQ(bel_count, 5414);
+}
+
+TEST_F(UP5KTest, wire_names)
+{
+ int wire_count = 0;
+ for (auto wire : design->chip.getWires()) {
+ auto name = design->chip.getWireName(wire);
+ assert(wire == design->chip.getWireByName(name));
+ wire_count++;
+ }
+ ASSERT_EQ(wire_count, 103383);
+}
+
+TEST_F(UP5KTest, pip_names)
+{
+ int pip_count = 0;
+ for (auto pip : design->chip.getPips()) {
+ auto name = design->chip.getPipName(pip);
+ assert(pip == design->chip.getPipByName(name));
+ pip_count++;
+ }
+ ASSERT_EQ(pip_count, 1219104);
+}
+
+TEST_F(UP5KTest, uphill_to_downhill)
+{
+ for (auto dst : design->chip.getWires()) {
+ for (auto uphill_pip : design->chip.getPipsUphill(dst)) {
+ bool found_downhill = false;
+ for (auto downhill_pip : design->chip.getPipsDownhill(
+ design->chip.getPipSrcWire(uphill_pip))) {
+ if (uphill_pip == downhill_pip) {
+ ASSERT_FALSE(found_downhill);
+ found_downhill = true;
+ }
+ }
+ ASSERT_TRUE(found_downhill);
+ }
+ }
+}
+
+TEST_F(UP5KTest, downhill_to_uphill)
+{
+ for (auto dst : design->chip.getWires()) {
+ for (auto downhill_pip : design->chip.getPipsDownhill(dst)) {
+ bool found_uphill = false;
+ for (auto uphill_pip : design->chip.getPipsUphill(
+ design->chip.getPipDstWire(downhill_pip))) {
+ if (uphill_pip == downhill_pip) {
+ ASSERT_FALSE(found_uphill);
+ found_uphill = true;
+ }
+ }
+ ASSERT_TRUE(found_uphill);
+ }
+ }
+}