Merge branch 'redist_slack' into 'redist_slack'

Redist slack

See merge request eddiehung/nextpnr!10
This commit is contained in:
Eddie Hung 2018-07-27 05:54:18 +00:00
commit 56d551d407
71 changed files with 7372 additions and 1745 deletions

2
.gitignore vendored
View File

@ -25,3 +25,5 @@ build/
/Testing/*
CTestTestfile.cmake
install_manifest.txt
/bbasm
/ImportExecutables.cmake

View File

@ -54,7 +54,7 @@ QT_BEGIN_NAMESPACE
class QtPropertyPrivate
{
public:
QtPropertyPrivate(QtAbstractPropertyManager *manager) : m_enabled(true), m_modified(false), m_manager(manager) {}
QtPropertyPrivate(QtAbstractPropertyManager *manager) : m_enabled(true), m_selectable(true), m_modified(false), m_manager(manager) {}
QtProperty *q_ptr;
QSet<QtProperty *> m_parentItems;
@ -66,6 +66,7 @@ public:
QString m_name;
QString m_id;
bool m_enabled;
bool m_selectable;
bool m_modified;
QtAbstractPropertyManager * const m_manager;
@ -260,6 +261,11 @@ bool QtProperty::isEnabled() const
return d_ptr->m_enabled;
}
bool QtProperty::isSelectable() const
{
return d_ptr->m_selectable;
}
/*!
Returns whether the property is modified.
@ -409,6 +415,15 @@ void QtProperty::setEnabled(bool enable)
propertyChanged();
}
void QtProperty::setSelectable(bool selectable)
{
if (d_ptr->m_selectable == selectable)
return;
d_ptr->m_selectable = selectable;
propertyChanged();
}
/*!
Sets the property's modified state according to the passed \a modified value.

View File

@ -83,6 +83,7 @@ public:
QString propertyName() const;
QString propertyId() const;
bool isEnabled() const;
bool isSelectable() const;
bool isModified() const;
bool hasValue() const;
@ -97,6 +98,7 @@ public:
void setPropertyName(const QString &text);
void setPropertyId(const QString &text);
void setEnabled(bool enable);
void setSelectable(bool selectable);
void setModified(bool modified);
bool isSubProperty()const;

View File

@ -651,6 +651,11 @@ void QtTreePropertyBrowserPrivate::updateItem(QTreeWidgetItem *item)
else
disableItem(item);
}
if (property->isSelectable()) {
item->setFlags(item->flags() | Qt::ItemIsSelectable);
} else {
item->setFlags(item->flags() & ~Qt::ItemIsSelectable);
}
m_treeWidget->viewport()->update();
}

View File

@ -1339,6 +1339,7 @@ void addPropertyRecusively(QtVariantPropertyManager * manager,
newProp->setWhatsThis(prop->whatsThis());
newProp->setModified(prop->isModified());
newProp->setEnabled(prop->isEnabled());
newProp->setSelectable(prop->isSelectable());
newProp->setValue(prop->value());
foreach(QtProperty * subProp, prop->subProperties())

View File

@ -167,6 +167,8 @@ if(MINGW)
add_definitions("-Wa,-mbig-obj")
endif(MINGW)
include(bba/bba.cmake)
foreach (family ${ARCH})
message(STATUS "Configuring architecture : ${family}")
string(TOUPPER ${family} ufamily)

74
bba/README.md Normal file
View File

@ -0,0 +1,74 @@
Binary Blob Assembler (bba)
===========================
This tools read a text file describing binary data, and write that binary data
file. The usual flow is that the input to bbasm is generated by a python
script, and the output is linked or loaded into a C program, using (packed)
structs to interpret the binary data.
All references (pointers) are encoded als 32 bit byte offset relative to the
location of the pointer. This way the resulting binary blob is position
independent.
Valid commands for the input are as follows.
pre \<string\>
--------------
When a C file is generated as output, all the "pre" strings will be included
before the binary blob.
post \<string\>
---------------
When a C file is generated as output, all the "post" strings will be included
after the binary blob.
push \<name\>
-------------
All following commands up until the matching "pop" will be writen to stream
\<name\>. Everything written to the same stream will end up in a continous
region of the output. The statements `pop`, `label`, `ref`, `u8`, `u16`,
`u32`, and `str` are only valid within such a block. The name used in the
first push statement also determines the name of the variable in the generated
C output (when C is selected as output file format).
pop
---
End of a push..pop block.
label \<name\> \[\<comment\>\]
------------------------------
Add a label for the current position.
ref \<name\> \[\<comment\>\]
----------------------------
Add a 32-bit reference to the specified label. The reference will be a byte
offset relative to the memory location of the reference itself.
u8 \<value\> \[\<comment\>\]
----------------------------
Add a 8-bit value to the binary blob.
u16 \<value\> \[\<comment\>\]
-----------------------------
Add a 16-bit value to the binary blob. Note that the input must be structured
in a way that ensures that all u16 are aligned to 2-byte addresses.
u32 \<value\> \[\<comment\>\]
-----------------------------
Add a 32-bit value to the binary blob. Note that the input must be structured
in a way that ensures that all u32 are aligned to 4-byte addresses.
str "\<string\>" \[\<comment\>\]
--------------------------------
Add a reference to a zero-terminated copy of that string. Any character may be
used to quote the string, but the most common choices are `"` and `|`.

13
bba/bba.cmake Normal file
View File

@ -0,0 +1,13 @@
IF(CMAKE_CROSSCOMPILING)
SET(IMPORT_EXECUTABLES "IMPORTFILE-NOTFOUND" CACHE FILEPATH "Point it to the export file from a native build")
INCLUDE(${IMPORT_EXECUTABLES})
ENDIF(CMAKE_CROSSCOMPILING)
IF(NOT CMAKE_CROSSCOMPILING)
ADD_EXECUTABLE(bbasm bba/main.cc)
target_link_libraries(bbasm LINK_PUBLIC ${Boost_PROGRAM_OPTIONS_LIBRARY})
ENDIF(NOT CMAKE_CROSSCOMPILING)
IF(NOT CMAKE_CROSSCOMPILING)
EXPORT(TARGETS bbasm FILE ${CMAKE_BINARY_DIR}/ImportExecutables.cmake )
ENDIF(NOT CMAKE_CROSSCOMPILING)

427
bba/main.cc Normal file
View File

@ -0,0 +1,427 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com>
* 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 <assert.h>
#include <boost/program_options.hpp>
#include <iostream>
#include <map>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <vector>
enum TokenType : int8_t
{
TOK_LABEL,
TOK_REF,
TOK_U8,
TOK_U16,
TOK_U32
};
struct Stream
{
std::string name;
std::vector<TokenType> tokenTypes;
std::vector<uint32_t> tokenValues;
std::vector<std::string> tokenComments;
};
Stream stringStream;
std::vector<Stream> streams;
std::map<std::string, int> streamIndex;
std::vector<int> streamStack;
std::vector<int> labels;
std::vector<std::string> labelNames;
std::map<std::string, int> labelIndex;
std::vector<std::string> preText, postText;
const char *skipWhitespace(const char *p)
{
if (p == nullptr)
return "";
while (*p == ' ' || *p == '\t')
p++;
return p;
}
int main(int argc, char **argv)
{
bool debug = false;
bool verbose = false;
bool bigEndian = false;
bool writeC = false;
char buffer[512];
namespace po = boost::program_options;
po::positional_options_description pos;
po::options_description options("Allowed options");
options.add_options()("v", "verbose output");
options.add_options()("d", "debug output");
options.add_options()("b", "big endian");
options.add_options()("c", "write c strings");
options.add_options()("files", po::value<std::vector<std::string>>(), "file parameters");
pos.add("files", -1);
po::variables_map vm;
try {
po::parsed_options parsed = po::command_line_parser(argc, argv).options(options).positional(pos).run();
po::store(parsed, vm);
po::notify(vm);
} catch (std::exception &e) {
std::cout << e.what() << "\n";
return 1;
}
if (vm.count("v"))
verbose = true;
if (vm.count("d"))
debug = true;
if (vm.count("b"))
bigEndian = true;
if (vm.count("c"))
writeC = true;
if (vm.count("files") == 0) {
printf("File parameters are mandatory\n");
exit(-1);
}
std::vector<std::string> files = vm["files"].as<std::vector<std::string>>();
if (files.size() != 2) {
printf("Input and output parameters must be set\n");
exit(-1);
}
FILE *fileIn = fopen(files.at(0).c_str(), "rt");
assert(fileIn != nullptr);
FILE *fileOut = fopen(files.at(1).c_str(), writeC ? "wt" : "wb");
assert(fileOut != nullptr);
while (fgets(buffer, 512, fileIn) != nullptr) {
std::string cmd = strtok(buffer, " \t\r\n");
if (cmd == "pre") {
const char *p = skipWhitespace(strtok(nullptr, "\r\n"));
preText.push_back(p);
continue;
}
if (cmd == "post") {
const char *p = skipWhitespace(strtok(nullptr, "\r\n"));
postText.push_back(p);
continue;
}
if (cmd == "push") {
const char *p = strtok(nullptr, " \t\r\n");
if (streamIndex.count(p) == 0) {
streamIndex[p] = streams.size();
streams.resize(streams.size() + 1);
streams.back().name = p;
}
streamStack.push_back(streamIndex.at(p));
continue;
}
if (cmd == "pop") {
streamStack.pop_back();
continue;
}
if (cmd == "label" || cmd == "ref") {
const char *label = strtok(nullptr, " \t\r\n");
const char *comment = skipWhitespace(strtok(nullptr, "\r\n"));
Stream &s = streams.at(streamStack.back());
if (labelIndex.count(label) == 0) {
labelIndex[label] = labels.size();
if (debug)
labelNames.push_back(label);
labels.push_back(-1);
}
s.tokenTypes.push_back(cmd == "label" ? TOK_LABEL : TOK_REF);
s.tokenValues.push_back(labelIndex.at(label));
if (debug)
s.tokenComments.push_back(comment);
continue;
}
if (cmd == "u8" || cmd == "u16" || cmd == "u32") {
const char *value = strtok(nullptr, " \t\r\n");
const char *comment = skipWhitespace(strtok(nullptr, "\r\n"));
Stream &s = streams.at(streamStack.back());
s.tokenTypes.push_back(cmd == "u8" ? TOK_U8 : cmd == "u16" ? TOK_U16 : TOK_U32);
s.tokenValues.push_back(atoll(value));
if (debug)
s.tokenComments.push_back(comment);
continue;
}
if (cmd == "str") {
const char *value = skipWhitespace(strtok(nullptr, "\r\n"));
char terminator[2] = {*value, 0};
assert(terminator[0] != 0);
value = strtok((char *)value + 1, terminator);
const char *comment = skipWhitespace(strtok(nullptr, "\r\n"));
std::string label = std::string("str:") + value;
Stream &s = streams.at(streamStack.back());
if (labelIndex.count(label) == 0) {
labelIndex[label] = labels.size();
if (debug)
labelNames.push_back(label);
labels.push_back(-1);
}
s.tokenTypes.push_back(TOK_REF);
s.tokenValues.push_back(labelIndex.at(label));
if (debug)
s.tokenComments.push_back(comment);
stringStream.tokenTypes.push_back(TOK_LABEL);
stringStream.tokenValues.push_back(labelIndex.at(label));
stringStream.tokenComments.push_back("");
while (1) {
stringStream.tokenTypes.push_back(TOK_U8);
stringStream.tokenValues.push_back(*value);
if (debug) {
char char_comment[4] = {'\'', *value, '\'', 0};
if (*value < 32 || *value >= 127)
char_comment[0] = 0;
stringStream.tokenComments.push_back(char_comment);
}
if (*value == 0)
break;
value++;
}
continue;
}
assert(0);
}
if (verbose) {
printf("Constructed %d streams:\n", int(streams.size()));
for (auto &s : streams)
printf(" stream '%s' with %d tokens\n", s.name.c_str(), int(s.tokenTypes.size()));
}
assert(!streams.empty());
assert(streamStack.empty());
streams.push_back(Stream());
streams.back().name = "strings";
streams.back().tokenTypes.swap(stringStream.tokenTypes);
streams.back().tokenValues.swap(stringStream.tokenValues);
streams.back().tokenComments.swap(stringStream.tokenComments);
int cursor = 0;
for (auto &s : streams) {
for (int i = 0; i < int(s.tokenTypes.size()); i++) {
switch (s.tokenTypes[i]) {
case TOK_LABEL:
labels[s.tokenValues[i]] = cursor;
break;
case TOK_REF:
cursor += 4;
break;
case TOK_U8:
cursor += 1;
break;
case TOK_U16:
assert(cursor % 2 == 0);
cursor += 2;
break;
case TOK_U32:
assert(cursor % 4 == 0);
cursor += 4;
break;
default:
assert(0);
}
}
}
if (verbose) {
printf("resolved positions for %d labels.\n", int(labels.size()));
printf("total data (including strings): %.2f MB\n", double(cursor) / (1024 * 1024));
}
std::vector<uint8_t> data(cursor);
cursor = 0;
for (auto &s : streams) {
if (debug)
printf("-- %s --\n", s.name.c_str());
for (int i = 0; i < int(s.tokenTypes.size()); i++) {
uint32_t value = s.tokenValues[i];
int numBytes = 0;
switch (s.tokenTypes[i]) {
case TOK_LABEL:
break;
case TOK_REF:
value = labels[value] - cursor;
numBytes = 4;
break;
case TOK_U8:
numBytes = 1;
break;
case TOK_U16:
numBytes = 2;
break;
case TOK_U32:
numBytes = 4;
break;
default:
assert(0);
}
if (bigEndian) {
switch (numBytes) {
case 4:
data[cursor++] = value >> 24;
data[cursor++] = value >> 16;
/* fall-through */
case 2:
data[cursor++] = value >> 8;
/* fall-through */
case 1:
data[cursor++] = value;
/* fall-through */
case 0:
break;
default:
assert(0);
}
} else {
switch (numBytes) {
case 4:
data[cursor + 3] = value >> 24;
data[cursor + 2] = value >> 16;
/* fall-through */
case 2:
data[cursor + 1] = value >> 8;
/* fall-through */
case 1:
data[cursor] = value;
/* fall-through */
case 0:
break;
default:
assert(0);
}
cursor += numBytes;
}
if (debug) {
printf("%08x ", cursor - numBytes);
for (int k = cursor - numBytes; k < cursor; k++)
printf("%02x ", data[k]);
for (int k = numBytes; k < 4; k++)
printf(" ");
unsigned long long v = s.tokenValues[i];
switch (s.tokenTypes[i]) {
case TOK_LABEL:
if (s.tokenComments[i].empty())
printf("label %s\n", labelNames[v].c_str());
else
printf("label %-24s %s\n", labelNames[v].c_str(), s.tokenComments[i].c_str());
break;
case TOK_REF:
if (s.tokenComments[i].empty())
printf("ref %s\n", labelNames[v].c_str());
else
printf("ref %-26s %s\n", labelNames[v].c_str(), s.tokenComments[i].c_str());
break;
case TOK_U8:
if (s.tokenComments[i].empty())
printf("u8 %llu\n", v);
else
printf("u8 %-27llu %s\n", v, s.tokenComments[i].c_str());
break;
case TOK_U16:
if (s.tokenComments[i].empty())
printf("u16 %-26llu\n", v);
else
printf("u16 %-26llu %s\n", v, s.tokenComments[i].c_str());
break;
case TOK_U32:
if (s.tokenComments[i].empty())
printf("u32 %-26llu\n", v);
else
printf("u32 %-26llu %s\n", v, s.tokenComments[i].c_str());
break;
default:
assert(0);
}
}
}
}
assert(cursor == int(data.size()));
if (writeC) {
for (auto &s : preText)
fprintf(fileOut, "%s\n", s.c_str());
fprintf(fileOut, "const char %s[%d] =\n\"", streams[0].name.c_str(), int(data.size()) + 1);
cursor = 1;
for (int i = 0; i < int(data.size()); i++) {
auto d = data[i];
if (cursor > 70) {
fputc('\"', fileOut);
fputc('\n', fileOut);
cursor = 0;
}
if (cursor == 0) {
fputc('\"', fileOut);
cursor = 1;
}
if (d < 32 || d >= 127) {
if (i + 1 < int(data.size()) && (data[i + 1] < '0' || '9' < data[i + 1]))
cursor += fprintf(fileOut, "\\%o", int(d));
else
cursor += fprintf(fileOut, "\\%03o", int(d));
} else if (d == '\"' || d == '\'' || d == '\\') {
fputc('\\', fileOut);
fputc(d, fileOut);
cursor += 2;
} else {
fputc(d, fileOut);
cursor++;
}
}
fprintf(fileOut, "\";\n");
for (auto &s : postText)
fprintf(fileOut, "%s\n", s.c_str());
} else {
fwrite(data.data(), int(data.size()), 1, fileOut);
}
return 0;
}

View File

@ -17,8 +17,8 @@
*
*/
#include "nextpnr.h"
#include "log.h"
#include "nextpnr.h"
#if 0
#define dbg(...) log(__VA_ARGS__)
@ -84,8 +84,7 @@ void archcheck_locs(const Context *ctx)
log_info("Checking all locations..\n");
for (int x = 0; x < ctx->getGridDimX(); x++)
for (int y = 0; y < ctx->getGridDimY(); y++)
{
for (int y = 0; y < ctx->getGridDimY(); y++) {
dbg("> %d %d\n", x, y);
std::unordered_set<int> usedz;

View File

@ -23,10 +23,10 @@
#include <condition_variable>
#include <memory>
#include <mutex>
#include <pthread.h>
#include <stdexcept>
#include <stdint.h>
#include <string>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <vector>
@ -79,19 +79,19 @@ class assertion_failure : public std::runtime_error
};
NPNR_NORETURN
inline bool assert_fail_impl(const char *message, const char *expr_str, const char *filename, int line)
inline void assert_fail_impl(const char *message, const char *expr_str, const char *filename, int line)
{
throw assertion_failure(message, expr_str, filename, line);
}
NPNR_NORETURN
inline bool assert_fail_impl_str(std::string message, const char *expr_str, const char *filename, int line)
inline void assert_fail_impl_str(std::string message, const char *expr_str, const char *filename, int line)
{
throw assertion_failure(message, expr_str, filename, line);
}
#define NPNR_ASSERT(cond) ((void)((cond) || (assert_fail_impl(#cond, #cond, __FILE__, __LINE__))))
#define NPNR_ASSERT_MSG(cond, msg) ((void)((cond) || (assert_fail_impl(msg, #cond, __FILE__, __LINE__))))
#define NPNR_ASSERT(cond) (!(cond) ? assert_fail_impl(#cond, #cond, __FILE__, __LINE__) : (void)true)
#define NPNR_ASSERT_MSG(cond, msg) (!(cond) ? assert_fail_impl(msg, #cond, __FILE__, __LINE__) : (void)true)
#define NPNR_ASSERT_FALSE(msg) (assert_fail_impl(msg, "false", __FILE__, __LINE__))
#define NPNR_ASSERT_FALSE_STR(msg) (assert_fail_impl_str(msg, "false", __FILE__, __LINE__))
@ -145,20 +145,25 @@ struct GraphicElement
{
enum type_t
{
G_NONE,
G_LINE,
G_BOX,
G_CIRCLE,
G_LABEL
} type = G_NONE;
TYPE_NONE,
TYPE_LINE,
TYPE_ARROW,
TYPE_BOX,
TYPE_CIRCLE,
TYPE_LABEL,
TYPE_MAX
} type = TYPE_NONE;
enum style_t
{
G_FRAME,
G_HIDDEN,
G_INACTIVE,
G_ACTIVE,
} style = G_FRAME;
STYLE_FRAME, // Static "frame". Contrast between STYLE_INACTIVE and STYLE_ACTIVE
STYLE_HIDDEN, // Only display when object is selected or highlighted
STYLE_INACTIVE, // Render using low-contrast color
STYLE_ACTIVE, // Render using high-contast color
STYLE_MAX
} style = STYLE_FRAME;
float x1 = 0, y1 = 0, x2 = 0, y2 = 0, z = 0;
std::string text;
@ -272,6 +277,16 @@ struct CellInfo : ArchCellInfo
// cell_port -> bel_pin
std::unordered_map<IdString, IdString> pins;
// placement constraints
CellInfo *constr_parent;
std::vector<CellInfo *> constr_children;
const int UNCONSTR = INT_MIN;
int constr_x = UNCONSTR; // this.x - parent.x
int constr_y = UNCONSTR; // this.y - parent.y
int constr_z = UNCONSTR; // this.z - parent.z
bool constr_abs_z = false; // parent.z := 0
// parent.[xyz] := 0 when (constr_parent == nullptr)
};
struct DeterministicRNG
@ -343,7 +358,7 @@ struct BaseCtx
{
// Lock to perform mutating actions on the Context.
std::mutex mutex;
pthread_t mutex_owner;
std::thread::id mutex_owner;
// Lock to be taken by UI when wanting to access context - the yield()
// method will lock/unlock it when its' released the main mutex to make
@ -376,12 +391,12 @@ struct BaseCtx
void lock(void)
{
mutex.lock();
mutex_owner = pthread_self();
mutex_owner = std::this_thread::get_id();
}
void unlock(void)
{
NPNR_ASSERT(pthread_equal(pthread_self(), mutex_owner) != 0);
NPNR_ASSERT(std::this_thread::get_id() == mutex_owner);
mutex.unlock();
}
@ -455,6 +470,7 @@ struct Context : Arch, DeterministicRNG
bool force = false;
bool timing_driven = true;
float target_freq = 12e6;
bool user_freq = false;
Context(ArchArgs args) : Arch(args) {}

View File

@ -28,19 +28,20 @@ NEXTPNR_NAMESPACE_BEGIN
wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type, float &tns)
{
wirelen_t wirelength = 0;
int driver_x, driver_y;
Loc driver_loc;
bool driver_gb;
CellInfo *driver_cell = net->driver.cell;
if (!driver_cell)
return 0;
if (driver_cell->bel == BelId())
return 0;
ctx->estimatePosition(driver_cell->bel, driver_x, driver_y, driver_gb);
driver_gb = ctx->getBelGlobalBuf(driver_cell->bel);
driver_loc = ctx->getBelLocation(driver_cell->bel);
WireId drv_wire = ctx->getBelPinWire(driver_cell->bel, ctx->portPinFromId(net->driver.port));
if (driver_gb)
return 0;
float worst_slack = 1000;
int xmin = driver_x, xmax = driver_x, ymin = driver_y, ymax = driver_y;
int xmin = driver_loc.x, xmax = driver_loc.x, ymin = driver_loc.y, ymax = driver_loc.y;
for (auto load : net->users) {
if (load.cell == nullptr)
continue;
@ -56,15 +57,14 @@ wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type
worst_slack = std::min(slack, worst_slack);
}
int load_x, load_y;
bool load_gb;
ctx->estimatePosition(load_cell->bel, load_x, load_y, load_gb);
if (load_gb)
if (ctx->getBelGlobalBuf(load_cell->bel))
continue;
xmin = std::min(xmin, load_x);
ymin = std::min(ymin, load_y);
xmax = std::max(xmax, load_x);
ymax = std::max(ymax, load_y);
Loc load_loc = ctx->getBelLocation(load_cell->bel);
xmin = std::min(xmin, load_loc.x);
ymin = std::min(ymin, load_loc.y);
xmax = std::max(xmax, load_loc.x);
ymax = std::max(ymax, load_loc.y);
}
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)))));

View File

@ -50,9 +50,7 @@ class SAPlacer
{
int num_bel_types = 0;
for (auto bel : ctx->getBels()) {
int x, y;
bool gb;
ctx->estimatePosition(bel, x, y, gb);
Loc loc = ctx->getBelLocation(bel);
BelType type = ctx->getBelType(bel);
int type_idx;
if (bel_types.find(type) == bel_types.end()) {
@ -63,13 +61,13 @@ class SAPlacer
}
if (int(fast_bels.size()) < type_idx + 1)
fast_bels.resize(type_idx + 1);
if (int(fast_bels.at(type_idx).size()) < (x + 1))
fast_bels.at(type_idx).resize(x + 1);
if (int(fast_bels.at(type_idx).at(x).size()) < (y + 1))
fast_bels.at(type_idx).at(x).resize(y + 1);
max_x = std::max(max_x, x);
max_y = std::max(max_y, y);
fast_bels.at(type_idx).at(x).at(y).push_back(bel);
if (int(fast_bels.at(type_idx).size()) < (loc.x + 1))
fast_bels.at(type_idx).resize(loc.x + 1);
if (int(fast_bels.at(type_idx).at(loc.x).size()) < (loc.y + 1))
fast_bels.at(type_idx).at(loc.x).resize(loc.y + 1);
max_x = std::max(max_x, loc.x);
max_y = std::max(max_y, loc.y);
fast_bels.at(type_idx).at(loc.x).at(loc.y).push_back(bel);
}
diameter = std::max(max_x, max_y) + 1;
}
@ -96,7 +94,13 @@ class SAPlacer
BelType bel_type = ctx->getBelType(bel);
if (bel_type != ctx->belTypeFromId(cell->type)) {
log_error("Bel \'%s\' of type \'%s\' does not match cell "
"\'%s\' of type \'%s\'",
"\'%s\' of type \'%s\'\n",
loc_name.c_str(), ctx->belTypeToId(bel_type).c_str(ctx), cell->name.c_str(ctx),
cell->type.c_str(ctx));
}
if (!ctx->isValidBelForCell(cell, bel)) {
log_error("Bel \'%s\' of type \'%s\' is not valid for cell "
"\'%s\' of type \'%s\'\n",
loc_name.c_str(), ctx->belTypeToId(bel_type).c_str(ctx), cell->name.c_str(ctx),
cell->type.c_str(ctx));
}
@ -235,8 +239,7 @@ class SAPlacer
diameter *= post_legalise_dia_scale;
ctx->shuffle(autoplaced);
assign_budget(ctx);
}
else {
} else {
update_budget(ctx);
}
@ -272,6 +275,7 @@ class SAPlacer
}
}
}
compute_fmax(ctx, true /* print_fmax */);
ctx->unlock();
return true;
}
@ -387,8 +391,6 @@ class SAPlacer
// SA acceptance criterea
if (delta < 0 || (temp > 1e-6 && (ctx->rng() / float(0x3fffffff)) <= std::exp(-delta / temp))) {
n_accept++;
//if (delta < 2)
// improved = true;
} else {
if (other != IdString())
ctx->unbindBel(oldBel);
@ -413,12 +415,10 @@ class SAPlacer
BelId random_bel_for_cell(CellInfo *cell)
{
BelType targetType = ctx->belTypeFromId(cell->type);
int x, y;
bool gb;
ctx->estimatePosition(cell->bel, x, y, gb);
Loc curr_loc = ctx->getBelLocation(cell->bel);
while (true) {
int nx = ctx->rng(2 * diameter + 1) + std::max(x - diameter, 0);
int ny = ctx->rng(2 * diameter + 1) + std::max(y - diameter, 0);
int nx = ctx->rng(2 * diameter + 1) + std::max(curr_loc.x - diameter, 0);
int ny = ctx->rng(2 * diameter + 1) + std::max(curr_loc.y - diameter, 0);
int beltype_idx = bel_types.at(targetType);
if (nx >= int(fast_bels.at(beltype_idx).size()))
continue;

View File

@ -613,38 +613,10 @@ bool router1(Context *ctx)
std::unordered_set<IdString> normalRouteNets, ripupQueue;
if (iterCnt == 1) {
if (ctx->verbose)
log_info("routing queue contains %d jobs.\n", int(jobQueue.size()));
} else {
static auto actual_delay = [](Context *ctx, WireId src, WireId dst) {
delay_t total_delay = 0;
WireId last = dst;
auto net_name = ctx->getBoundWireNet(src);
if (net_name != IdString()) {
auto net = ctx->nets.at(net_name).get();
while (last != src) {
total_delay += ctx->getWireDelay(last).maxDelay();
auto pip = net->wires.at(last).pip;
NPNR_ASSERT(ctx->getBoundPipNet(pip) == net_name);
total_delay += ctx->getPipDelay(pip).maxDelay();
last = ctx->getPipSrcWire(pip);
if (ctx->getBoundWireNet(last) != net_name) {
log_warning("Wire %s bound to %s not %s!\n", ctx->getWireName(last).c_str(ctx), ctx->getBoundWireNet(last).c_str(ctx), net_name.c_str(ctx));
break;
}
NPNR_ASSERT(ctx->getBoundWireNet(last) == net_name);
}
NPNR_ASSERT(last != WireId());
}
if (last != src)
total_delay += ctx->estimateDelay(src, last);
else
total_delay += ctx->getWireDelay(last).maxDelay();
return total_delay;
};
update_budget(ctx, actual_delay);
}
if (ctx->verbose || iterCnt == 1)
log_info("routing queue contains %d jobs.\n", int(jobQueue.size()));
update_budget(ctx);
bool printNets = ctx->verbose && (jobQueue.size() < 10);
@ -841,14 +813,15 @@ bool router1(Context *ctx)
log_info("Checksum: 0x%08x\n", ctx->checksum());
#ifndef NDEBUG
ctx->check();
ctx->unlock();
#endif
compute_fmax(ctx, true /* print_fmax */, true /* print_path */);
ctx->unlock();
return true;
} catch (log_execution_error_exception) {
#ifndef NDEBUG
ctx->check();
ctx->unlock();
#endif
ctx->unlock();
return false;
}
}

View File

@ -26,16 +26,26 @@
NEXTPNR_NAMESPACE_BEGIN
static delay_t follow_net(Context *ctx, NetInfo *net, int path_length, delay_t slack);
typedef std::unordered_map<const PortInfo *, delay_t> UpdateMap;
typedef std::list<const PortRef *> PortRefList;
static delay_t follow_net(Context *ctx, NetInfo *net, int path_length, delay_t slack, UpdateMap *updates,
delay_t &min_slack, PortRefList *current_path, PortRefList *crit_path);
// Follow a path, returning budget to annotate
static delay_t follow_user_port(Context *ctx, PortRef &user, int path_length, delay_t slack)
static delay_t follow_user_port(Context *ctx, PortRef &user, int path_length, delay_t slack, UpdateMap *updates,
delay_t &min_slack, PortRefList *current_path, PortRefList *crit_path)
{
delay_t value;
if (ctx->getPortClock(user.cell, user.port) != IdString()) {
// At the end of a timing path (arguably, should check setup time
// here too)
value = slack / path_length;
if (slack < min_slack) {
min_slack = slack;
if (crit_path)
*crit_path = *current_path;
}
} else {
// Default to the path ending here, if no further paths found
value = slack / path_length;
@ -48,7 +58,8 @@ static delay_t follow_user_port(Context *ctx, PortRef &user, int path_length, de
if (is_path) {
NetInfo *net = port.second.net;
if (net) {
delay_t path_budget = follow_net(ctx, net, path_length, slack - comb_delay);
delay_t path_budget = follow_net(ctx, net, path_length, slack - comb_delay, updates, min_slack,
current_path, crit_path);
value = std::min(value, path_budget);
}
}
@ -56,21 +67,67 @@ static delay_t follow_user_port(Context *ctx, PortRef &user, int path_length, de
}
}
if (value < user.budget) {
user.budget = value;
if (updates) {
auto ret = updates->emplace(&user.cell->ports.at(user.port), value);
if (!ret.second)
ret.first->second = std::min(value, ret.first->second);
}
return value;
}
static delay_t follow_net(Context *ctx, NetInfo *net, int path_length, delay_t slack)
static delay_t follow_net(Context *ctx, NetInfo *net, int path_length, delay_t slack, UpdateMap *updates,
delay_t &min_slack, PortRefList *current_path, PortRefList *crit_path)
{
delay_t net_budget = slack / (path_length + 1);
for (auto &usr : net->users) {
net_budget = std::min(net_budget, follow_user_port(ctx, usr, path_length + 1, slack));
for (unsigned i = 0; i < net->users.size(); ++i) {
auto &usr = net->users[i];
if (crit_path)
current_path->push_back(&usr);
// If budget override is less than existing budget, then do not increment path length
int pl = path_length + 1;
auto budget = ctx->getBudgetOverride(net, i, net_budget);
if (budget < net_budget) {
net_budget = budget;
pl = std::max(1, path_length);
}
net_budget = std::min(net_budget,
follow_user_port(ctx, usr, pl, slack - ctx->getNetinfoRouteDelay(net, i),
updates, min_slack, current_path, crit_path));
if (crit_path)
current_path->pop_back();
}
return net_budget;
}
static delay_t compute_min_slack(Context *ctx, UpdateMap *updates, PortRefList *crit_path)
{
delay_t default_slack = delay_t(1.0e12 / ctx->target_freq);
delay_t min_slack = default_slack;
PortRefList current_path;
// Go through all clocked drivers and distribute the available path
// slack evenly into the budget of every sink on the path ---
// record this value into the UpdateMap
for (auto &cell : ctx->cells) {
for (auto port : cell.second->ports) {
if (port.second.type == PORT_OUT) {
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;
if (ctx->getCellDelay(cell.second.get(), clock_domain, port.first, clkToQ))
slack -= clkToQ;
if (port.second.net)
follow_net(ctx, port.second.net, 0, slack, updates, min_slack, &current_path, crit_path);
}
}
}
}
return min_slack;
}
void assign_budget(Context *ctx)
{
log_break();
@ -82,24 +139,35 @@ void assign_budget(Context *ctx)
usr.budget = default_slack;
}
}
// Go through all clocked drivers and set up paths
for (auto &cell : ctx->cells) {
for (auto port : cell.second->ports) {
if (port.second.type == PORT_OUT) {
IdString clock_domain = ctx->getPortClock(cell.second.get(), port.first);
if (clock_domain != IdString()) {
delay_t slack = delay_t(1.0e12 / ctx->target_freq); // TODO: clock constraints
if (port.second.net)
follow_net(ctx, port.second.net, 0, slack);
}
}
}
UpdateMap updates;
delay_t min_slack = compute_min_slack(ctx, &updates, nullptr);
// If user has not specified a frequency, adjust the target frequency dynamically
// TODO(eddieh): Tune these factors
if (!ctx->user_freq) {
if (min_slack < 0)
ctx->target_freq = 1e12 / (default_slack - 0.95 * min_slack);
else
ctx->target_freq = 1e12 / (default_slack - 1.2 * min_slack);
if (ctx->verbose)
log_info("minimum slack for this assign = %d, target Fmax for next update = %.2f MHz\n", min_slack,
ctx->target_freq / 1e6);
}
// Post-allocation check
// Update the budgets
for (auto &net : ctx->nets) {
for (auto user : net.second->users) {
if (user.budget < 0)
for (size_t i = 0; i < net.second->users.size(); ++i) {
auto &user = net.second->users[i];
auto pi = &user.cell->ports.at(user.port);
auto it = updates.find(pi);
if (it == updates.end())
continue;
auto budget = ctx->getNetinfoRouteDelay(net.second.get(), i) - it->second;
user.budget = ctx->getBudgetOverride(net.second.get(), i, budget);
// Post-update check
if (ctx->user_freq && user.budget < 0)
log_warning("port %s.%s, connected to net '%s', has negative "
"timing budget of %fns\n",
user.cell->name.c_str(ctx), user.port.c_str(ctx), net.first.c_str(ctx),
@ -115,112 +183,25 @@ void assign_budget(Context *ctx)
log_info("Checksum: 0x%08x\n", ctx->checksum());
}
typedef std::unordered_map<const PortInfo*, delay_t> updates_t;
typedef std::unordered_map<const PortInfo*, delay_t> delays_t;
static delay_t follow_net_update(Context *ctx, NetInfo *net, int path_length, delay_t slack, const delays_t& delays, updates_t& updates);
// Follow a path, returning budget to annotate
static delay_t follow_user_port_update(Context *ctx, PortRef &user, int path_length, delay_t slack, const delays_t& delays, updates_t& updates)
void update_budget(Context *ctx)
{
delay_t value;
if (ctx->getPortClock(user.cell, user.port) != IdString()) {
// At the end of a timing path (arguably, should check setup time
// here too)
value = slack / path_length;
} else {
// Default to the path ending here, if no further paths found
value = slack / path_length;
// Follow outputs of the user
for (auto& port : user.cell->ports) {
if (port.second.type == PORT_OUT) {
delay_t 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_update(ctx, net, path_length, slack - comb_delay, delays, updates);
value = std::min(value, path_budget);
}
}
}
}
}
auto ret = updates.emplace(&user.cell->ports.at(user.port), value);
if (!ret.second && value < ret.first->second) {
ret.first->second = value;
}
return value;
}
static delay_t follow_net_update(Context *ctx, NetInfo *net, int path_length, delay_t slack, const delays_t& delays,updates_t& updates)
{
delay_t net_budget = slack / (path_length + 1);
for (auto& usr : net->users) {
net_budget = std::min(net_budget, follow_user_port_update(ctx, usr, path_length + 1, slack - get_or_default(delays, &usr.cell->ports.at(usr.port), 0.), delays, updates));
}
return net_budget;
}
void update_budget(Context *ctx, std::function<delay_t(Context*,WireId,WireId)> delay_fn)
{
delays_t delays;
updates_t updates;
// Compute the delay for every pin on every net
for (auto &n : ctx->nets) {
auto net = n.second.get();
int driver_x, driver_y;
bool driver_gb;
CellInfo *driver_cell = net->driver.cell;
if (!driver_cell)
continue;
if (driver_cell->bel == BelId())
continue;
ctx->estimatePosition(driver_cell->bel, driver_x, driver_y, driver_gb);
WireId drv_wire = ctx->getWireBelPin(driver_cell->bel, ctx->portPinFromId(net->driver.port));
if (driver_gb)
continue;
for (auto& load : net->users) {
if (load.cell == nullptr)
continue;
CellInfo *load_cell = load.cell;
if (load_cell->bel == BelId())
continue;
WireId user_wire = ctx->getWireBelPin(load_cell->bel, ctx->portPinFromId(load.port));
delay_t raw_wl = delay_fn(ctx, drv_wire, user_wire);
delays.emplace(&load_cell->ports.at(load.port), raw_wl);
}
}
// Go through all clocked drivers and distribute the available path slack evenly into every budget
for (auto &cell : ctx->cells) {
for (auto& port : cell.second->ports) {
if (port.second.type == PORT_OUT) {
IdString clock_domain = ctx->getPortClock(cell.second.get(), port.first);
if (clock_domain != IdString()) {
if (port.second.net)
follow_net_update(ctx, port.second.net, 0, delay_t(1.0e12 / ctx->target_freq) - get_or_default(delays, &port.second, 0.), delays, updates);
}
}
}
}
UpdateMap updates;
delay_t min_slack = compute_min_slack(ctx, &updates, nullptr);
// Update the budgets
for (auto &net : ctx->nets) {
for (auto& user : net.second->users) {
for (size_t i = 0; i < net.second->users.size(); ++i) {
auto &user = net.second->users[i];
auto pi = &user.cell->ports.at(user.port);
auto budget = ctx->getNetinfoRouteDelay(net.second.get(), i);
auto it = updates.find(pi);
if (it == updates.end()) continue;
auto budget = delays.at(pi) + it->second;
user.budget = ctx->getBudgetOverride(net.second->driver, budget);
if (it != updates.end())
budget -= it->second;
user.budget = ctx->getBudgetOverride(net.second.get(), i, budget);
// Post-update check
if (ctx->verbose) {
if (user.budget < 0)
if (ctx->user_freq && user.budget < 0)
log_warning("port %s.%s, connected to net '%s', has negative "
"timing budget of %fns\n",
user.cell->name.c_str(ctx), user.port.c_str(ctx), net.first.c_str(ctx),
@ -233,6 +214,59 @@ void update_budget(Context *ctx, std::function<delay_t(Context*,WireId,WireId)>
}
}
}
// If user has not specified a frequency, adjust the frequency dynamically:
if (!ctx->user_freq) {
delay_t default_slack = delay_t(1.0e12 / ctx->target_freq);
ctx->target_freq = 1e12 / (default_slack - min_slack);
if (ctx->verbose)
log_info("minimum slack for this update = %d, target Fmax for next update = %.2f MHz\n", min_slack,
ctx->target_freq / 1e6);
}
}
void compute_fmax(Context *ctx, bool print_fmax, bool print_path)
{
delay_t default_slack = delay_t(1.0e12 / ctx->target_freq);
PortRefList crit_path;
delay_t min_slack = compute_min_slack(ctx, nullptr, &crit_path);
if (print_path) {
delay_t total = 0;
log_break();
log_info("Critical path report:\n");
log_info("curr total\n");
auto &front = crit_path.front();
auto &front_port = front->cell->ports.at(front->port);
auto &front_driver = front_port.net->driver;
auto last_port = ctx->getPortClock(front_driver.cell, front_driver.port);
for (auto sink : crit_path) {
auto sink_cell = sink->cell;
auto &port = sink_cell->ports.at(sink->port);
auto net = port.net;
unsigned i = 0;
for (auto &usr : net->users)
if (&usr == sink) break;
else ++i;
auto &driver = net->driver;
auto driver_cell = driver.cell;
delay_t 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),
driver.port.c_str(ctx));
delay_t 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);
log_info("%4d %4d Net %s budget %d (%d,%d) -> (%d,%d)\n", net_delay, total, net->name.c_str(ctx),
sink->budget, driver_loc.x, driver_loc.y, sink_loc.x, sink_loc.y);
log_info(" Sink %s.%s\n", sink_cell->name.c_str(ctx), sink->port.c_str(ctx));
last_port = sink->port;
}
log_break();
}
if (print_fmax)
log_info("estimated Fmax = %.2f MHz\n", 1e6 / (default_slack - min_slack));
}
NEXTPNR_NAMESPACE_END

View File

@ -27,7 +27,10 @@ NEXTPNR_NAMESPACE_BEGIN
// Assign "budget" values for all user ports in the design
void assign_budget(Context *ctx);
void update_budget(Context *ctx, std::function<delay_t(Context*,WireId,WireId)> delay_fn=&Context::estimateDelay);
// Evenly redistribute the total path slack amongst all sinks on each path
void update_budget(Context *ctx);
void compute_fmax(Context *ctx, bool print_fmax = false, bool print_path = false);
NEXTPNR_NAMESPACE_END

View File

@ -192,17 +192,20 @@ BelId Arch::getBelByName(IdString name) const
return ret;
}
BelRange Arch::getBelsAtSameTile(BelId bel) const
BelRange Arch::getBelsByTile(int x, int y) const
{
BelRange br;
NPNR_ASSERT(bel != BelId());
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_tile = y * chip_info->width + x;
br.e.cursor_tile = y * chip_info->width + x;
br.b.cursor_index = 0;
br.e.cursor_index = locInfo(bel)->num_bels - 1;
br.e.cursor_index = chip_info->locations[chip_info->location_type[br.b.cursor_tile]].num_bels - 1;
br.b.chip = chip_info;
br.e.chip = chip_info;
++br.e;
if (br.e.cursor_index == -1)
++br.e.cursor_index;
else
++br.e;
return br;
}
@ -278,6 +281,7 @@ PipId Arch::getPipByName(IdString name) const
Location loc;
std::string basename;
std::tie(loc.x, loc.y, basename) = split_identifier_name(name.str(this));
ret.location = loc;
const LocationTypePOD *loci = locInfo(ret);
for (int i = 0; i < loci->num_pips; i++) {
PipId curr;
@ -285,6 +289,8 @@ PipId Arch::getPipByName(IdString name) const
curr.index = i;
pip_by_name[getPipName(curr)] = curr;
}
if (pip_by_name.find(name) == pip_by_name.end())
NPNR_ASSERT_FALSE_STR("no pip named " + name.str(this));
return pip_by_name[name];
}
@ -322,13 +328,52 @@ BelId Arch::getPackagePinBel(const std::string &pin) const
std::string Arch::getBelPackagePin(BelId bel) const
{
for (int i = 0; i < package_info->num_pins; i++) {
if (package_info->pin_data[i].abs_loc == bel.location && package_info->pin_data[i].bel_index == bel.index) {
if (Location(package_info->pin_data[i].abs_loc) == bel.location &&
package_info->pin_data[i].bel_index == bel.index) {
return package_info->pin_data[i].name.get();
}
}
return "";
}
int Arch::getPioBelBank(BelId bel) const
{
for (int i = 0; i < chip_info->num_pios; i++) {
if (Location(chip_info->pio_info[i].abs_loc) == bel.location && chip_info->pio_info[i].bel_index == bel.index) {
return chip_info->pio_info[i].bank;
}
}
NPNR_ASSERT_FALSE("failed to find PIO");
}
std::string Arch::getPioFunctionName(BelId bel) const
{
for (int i = 0; i < chip_info->num_pios; i++) {
if (Location(chip_info->pio_info[i].abs_loc) == bel.location && chip_info->pio_info[i].bel_index == bel.index) {
const char *func = chip_info->pio_info[i].function_name.get();
if (func == nullptr)
return "";
else
return func;
}
}
NPNR_ASSERT_FALSE("failed to find PIO");
}
BelId Arch::getPioByFunctionName(const std::string &name) const
{
for (int i = 0; i < chip_info->num_pios; i++) {
const char *func = chip_info->pio_info[i].function_name.get();
if (func != nullptr && func == name) {
BelId bel;
bel.location = chip_info->pio_info[i].abs_loc;
bel.index = chip_info->pio_info[i].bel_index;
return bel;
}
}
return BelId();
}
std::vector<PortPin> Arch::getBelPins(BelId bel) const
{
@ -361,45 +406,14 @@ BelId Arch::getBelByLocation(Loc loc) const
return BelId();
}
BelRange Arch::getBelsByTile(int x, int y) const
{
BelRange br;
int num_bels = 0;
if (x < chip_info->width && y < chip_info->height) {
const LocationTypePOD &locI = chip_info->locations[chip_info->location_type[y * chip_info->width + x]];
num_bels = locI.num_bels;
}
br.b.cursor_tile = y * chip_info->width + x;
br.e.cursor_tile = y * chip_info->width + x;
br.b.cursor_index = 0;
br.e.cursor_index = num_bels - 1;
br.b.chip = chip_info;
br.e.chip = chip_info;
++br.e;
return br;
}
// -----------------------------------------------------------------------
void Arch::estimatePosition(BelId bel, int &x, int &y, bool &gb) const
{
x = bel.location.x;
y = bel.location.y;
gb = false;
}
delay_t Arch::estimateDelay(WireId src, WireId dst) const
{
return 200 * (abs(src.location.x - dst.location.x) + abs(src.location.y - dst.location.y));
}
delay_t Arch::getBudgetOverride(const PortRef& pr, delay_t v) const
{
return v;
}
delay_t Arch::getBudgetOverride(NetInfo *net_info, int user_idx, delay_t budget) const { return budget; }
// -----------------------------------------------------------------------

View File

@ -98,7 +98,7 @@ NPNR_PACKED_STRUCT(struct LocationTypePOD {
});
NPNR_PACKED_STRUCT(struct PIOInfoPOD {
Location abs_loc;
LocationPOD abs_loc;
int32_t bel_index;
RelPtr<char> function_name;
int16_t bank;
@ -107,7 +107,7 @@ NPNR_PACKED_STRUCT(struct PIOInfoPOD {
NPNR_PACKED_STRUCT(struct PackagePinPOD {
RelPtr<char> name;
Location abs_loc;
LocationPOD abs_loc;
int32_t bel_index;
});
@ -117,6 +117,26 @@ NPNR_PACKED_STRUCT(struct PackageInfoPOD {
RelPtr<PackagePinPOD> pin_data;
});
enum TapDirection : int8_t
{
TAP_DIR_LEFT = 0,
TAP_DIR_RIGHT = 1
};
enum GlobalQuadrant : int8_t
{
QUAD_UL = 0,
QUAD_UR = 1,
QUAD_LL = 2,
QUAD_LR = 3,
};
NPNR_PACKED_STRUCT(struct GlobalInfoPOD {
int16_t tap_col;
TapDirection tap_dir;
GlobalQuadrant quad;
});
NPNR_PACKED_STRUCT(struct ChipInfoPOD {
int32_t width, height;
int32_t num_tiles;
@ -124,6 +144,7 @@ NPNR_PACKED_STRUCT(struct ChipInfoPOD {
int32_t num_packages, num_pios;
RelPtr<LocationTypePOD> locations;
RelPtr<int32_t> location_type;
RelPtr<GlobalInfoPOD> location_glbinfo;
RelPtr<RelPtr<char>> tiletype_names;
RelPtr<PackageInfoPOD> package_info;
RelPtr<PIOInfoPOD> pio_info;
@ -482,8 +503,6 @@ struct Arch : BaseCtx
return range;
}
BelRange getBelsAtSameTile(BelId bel) const;
BelType getBelType(BelId bel) const
{
NPNR_ASSERT(bel != BelId());
@ -519,6 +538,8 @@ struct Arch : BaseCtx
return id(name.str());
}
IdString getWireType(WireId wire) const { return IdString(); }
uint32_t getWireChecksum(WireId wire) const { return wire.index; }
void bindWire(WireId wire, IdString net, PlaceStrength strength)
@ -597,6 +618,8 @@ struct Arch : BaseCtx
PipId getPipByName(IdString name) const;
IdString getPipName(PipId pip) const;
IdString getPipType(PipId pip) const { return IdString(); }
uint32_t getPipChecksum(PipId pip) const { return pip.index; }
void bindPip(PipId pip, IdString net, PlaceStrength strength)
@ -729,10 +752,14 @@ struct Arch : BaseCtx
return chip_info->tiletype_names[locInfo(pip)->pip_data[pip.index].tile_type].get();
}
int8_t getPipType(PipId pip) const { return locInfo(pip)->pip_data[pip.index].pip_type; }
int8_t getPipClass(PipId pip) const { return locInfo(pip)->pip_data[pip.index].pip_type; }
BelId getPackagePinBel(const std::string &pin) const;
std::string getBelPackagePin(BelId bel) const;
int getPioBelBank(BelId bel) const;
// For getting GCLK, PLL, Vref, etc, pins
std::string getPioFunctionName(BelId bel) const;
BelId getPioByFunctionName(const std::string &name) const;
PortType getBelPinType(BelId bel, PortPin pin) const;
@ -748,13 +775,12 @@ struct Arch : BaseCtx
// -------------------------------------------------
void estimatePosition(BelId bel, int &x, int &y, bool &gb) const;
delay_t estimateDelay(WireId src, WireId dst) const;
delay_t getDelayEpsilon() const { return 20; }
delay_t getRipupDelayPenalty() const { return 200; }
float getDelayNS(delay_t v) const { return v * 0.001; }
uint32_t getDelayChecksum(delay_t v) const { return v; }
delay_t getBudgetOverride(const PortRef& pr, delay_t v) const;
delay_t getBudgetOverride(NetInfo *net_info, int user_idx, delay_t budget) const;
// -------------------------------------------------

View File

@ -66,7 +66,8 @@ bool Arch::isBelLocationValid(BelId bel) const
{
if (getBelType(bel) == TYPE_TRELLIS_SLICE) {
std::vector<const CellInfo *> bel_cells;
for (auto bel_other : getBelsAtSameTile(bel)) {
Loc bel_loc = getBelLocation(bel);
for (auto bel_other : getBelsByTile(bel_loc.x, bel_loc.y)) {
IdString cell_other = getBoundBelCell(bel_other);
if (cell_other != IdString()) {
const CellInfo *ci_other = cells.at(cell_other).get();
@ -89,8 +90,8 @@ bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const
NPNR_ASSERT(getBelType(bel) == TYPE_TRELLIS_SLICE);
std::vector<const CellInfo *> bel_cells;
for (auto bel_other : getBelsAtSameTile(bel)) {
Loc bel_loc = getBelLocation(bel);
for (auto bel_other : getBelsByTile(bel_loc.x, bel_loc.y)) {
IdString cell_other = getBoundBelCell(bel_other);
if (cell_other != IdString() && bel_other != bel) {
const CellInfo *ci_other = cells.at(cell_other).get();

View File

@ -2,7 +2,7 @@
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com>
* Copyright (C) 2018 David Shah <dave@ds0.me>
* 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
@ -20,13 +20,132 @@
#ifndef NO_PYTHON
#include "arch_pybindings.h"
#include "nextpnr.h"
#include "pybindings.h"
NEXTPNR_NAMESPACE_BEGIN
void arch_wrap_python() {}
void arch_wrap_python()
{
using namespace PythonConversion;
class_<ArchArgs>("ArchArgs").def_readwrite("type", &ArchArgs::type);
class_<BelId>("BelId").def_readwrite("index", &BelId::index);
class_<WireId>("WireId").def_readwrite("index", &WireId::index);
class_<PipId>("PipId").def_readwrite("index", &PipId::index);
class_<BelPin>("BelPin").def_readwrite("bel", &BelPin::bel).def_readwrite("pin", &BelPin::pin);
enum_<PortPin>("PortPin")
#define X(t) .value("PIN_" #t, PIN_##t)
#include "portpins.inc"
;
#undef X
auto arch_cls = class_<Arch, Arch *, bases<BaseCtx>, boost::noncopyable>("Arch", init<ArchArgs>());
auto ctx_cls = class_<Context, Context *, bases<Arch>, boost::noncopyable>("Context", no_init)
.def("checksum", &Context::checksum)
.def("pack", &Context::pack)
.def("place", &Context::place)
.def("route", &Context::route);
fn_wrapper_1a<Context, decltype(&Context::getBelType), &Context::getBelType, conv_to_str<BelType>,
conv_from_str<BelId>>::def_wrap(ctx_cls, "getBelType");
fn_wrapper_1a<Context, decltype(&Context::checkBelAvail), &Context::checkBelAvail, pass_through<bool>,
conv_from_str<BelId>>::def_wrap(ctx_cls, "checkBelAvail");
fn_wrapper_1a<Context, decltype(&Context::getBelChecksum), &Context::getBelChecksum, pass_through<uint32_t>,
conv_from_str<BelId>>::def_wrap(ctx_cls, "getBelChecksum");
fn_wrapper_3a_v<Context, decltype(&Context::bindBel), &Context::bindBel, conv_from_str<BelId>,
conv_from_str<IdString>, pass_through<PlaceStrength>>::def_wrap(ctx_cls, "bindBel");
fn_wrapper_1a_v<Context, decltype(&Context::unbindBel), &Context::unbindBel, conv_from_str<BelId>>::def_wrap(
ctx_cls, "unbindBel");
fn_wrapper_1a<Context, decltype(&Context::getBoundBelCell), &Context::getBoundBelCell, conv_to_str<IdString>,
conv_from_str<BelId>>::def_wrap(ctx_cls, "getBoundBelCell");
fn_wrapper_1a<Context, decltype(&Context::getConflictingBelCell), &Context::getConflictingBelCell,
conv_to_str<IdString>, conv_from_str<BelId>>::def_wrap(ctx_cls, "getConflictingBelCell");
fn_wrapper_0a<Context, decltype(&Context::getBels), &Context::getBels, wrap_context<BelRange>>::def_wrap(ctx_cls,
"getBels");
fn_wrapper_2a<Context, decltype(&Context::getBelPinWire), &Context::getBelPinWire, conv_to_str<WireId>,
conv_from_str<BelId>, conv_from_str<PortPin>>::def_wrap(ctx_cls, "getBelPinWire");
fn_wrapper_1a<Context, decltype(&Context::getWireBelPins), &Context::getWireBelPins, wrap_context<BelPinRange>,
conv_from_str<WireId>>::def_wrap(ctx_cls, "getWireBelPins");
fn_wrapper_1a<Context, decltype(&Context::getWireChecksum), &Context::getWireChecksum, pass_through<uint32_t>,
conv_from_str<WireId>>::def_wrap(ctx_cls, "getWireChecksum");
fn_wrapper_3a_v<Context, decltype(&Context::bindWire), &Context::bindWire, conv_from_str<WireId>,
conv_from_str<IdString>, pass_through<PlaceStrength>>::def_wrap(ctx_cls, "bindWire");
fn_wrapper_1a_v<Context, decltype(&Context::unbindWire), &Context::unbindWire, conv_from_str<WireId>>::def_wrap(
ctx_cls, "unbindWire");
fn_wrapper_1a<Context, decltype(&Context::checkWireAvail), &Context::checkWireAvail, pass_through<bool>,
conv_from_str<WireId>>::def_wrap(ctx_cls, "checkWireAvail");
fn_wrapper_1a<Context, decltype(&Context::getBoundWireNet), &Context::getBoundWireNet, conv_to_str<IdString>,
conv_from_str<WireId>>::def_wrap(ctx_cls, "getBoundWireNet");
fn_wrapper_1a<Context, decltype(&Context::getConflictingWireNet), &Context::getConflictingWireNet,
conv_to_str<IdString>, conv_from_str<WireId>>::def_wrap(ctx_cls, "getConflictingWireNet");
fn_wrapper_0a<Context, decltype(&Context::getWires), &Context::getWires, wrap_context<WireRange>>::def_wrap(
ctx_cls, "getWires");
fn_wrapper_0a<Context, decltype(&Context::getPips), &Context::getPips, wrap_context<AllPipRange>>::def_wrap(
ctx_cls, "getPips");
fn_wrapper_1a<Context, decltype(&Context::getPipChecksum), &Context::getPipChecksum, pass_through<uint32_t>,
conv_from_str<PipId>>::def_wrap(ctx_cls, "getPipChecksum");
fn_wrapper_3a_v<Context, decltype(&Context::bindPip), &Context::bindPip, conv_from_str<PipId>,
conv_from_str<IdString>, pass_through<PlaceStrength>>::def_wrap(ctx_cls, "bindPip");
fn_wrapper_1a_v<Context, decltype(&Context::unbindPip), &Context::unbindPip, conv_from_str<PipId>>::def_wrap(
ctx_cls, "unbindPip");
fn_wrapper_1a<Context, decltype(&Context::checkPipAvail), &Context::checkPipAvail, pass_through<bool>,
conv_from_str<PipId>>::def_wrap(ctx_cls, "checkPipAvail");
fn_wrapper_1a<Context, decltype(&Context::getBoundPipNet), &Context::getBoundPipNet, conv_to_str<IdString>,
conv_from_str<PipId>>::def_wrap(ctx_cls, "getBoundPipNet");
fn_wrapper_1a<Context, decltype(&Context::getConflictingPipNet), &Context::getConflictingPipNet,
conv_to_str<IdString>, conv_from_str<PipId>>::def_wrap(ctx_cls, "getConflictingPipNet");
fn_wrapper_1a<Context, decltype(&Context::getPipsDownhill), &Context::getPipsDownhill, wrap_context<PipRange>,
conv_from_str<WireId>>::def_wrap(ctx_cls, "getPipsDownhill");
fn_wrapper_1a<Context, decltype(&Context::getPipsUphill), &Context::getPipsUphill, wrap_context<PipRange>,
conv_from_str<WireId>>::def_wrap(ctx_cls, "getPipsUphill");
fn_wrapper_1a<Context, decltype(&Context::getWireAliases), &Context::getWireAliases, wrap_context<PipRange>,
conv_from_str<WireId>>::def_wrap(ctx_cls, "getWireAliases");
fn_wrapper_1a<Context, decltype(&Context::getPipSrcWire), &Context::getPipSrcWire, conv_to_str<WireId>,
conv_from_str<PipId>>::def_wrap(ctx_cls, "getPipSrcWire");
fn_wrapper_1a<Context, decltype(&Context::getPipDstWire), &Context::getPipDstWire, conv_to_str<WireId>,
conv_from_str<PipId>>::def_wrap(ctx_cls, "getPipDstWire");
fn_wrapper_1a<Context, decltype(&Context::getPipDelay), &Context::getPipDelay, pass_through<DelayInfo>,
conv_from_str<PipId>>::def_wrap(ctx_cls, "getPipDelay");
fn_wrapper_1a<Context, decltype(&Context::getPackagePinBel), &Context::getPackagePinBel, conv_to_str<BelId>,
pass_through<std::string>>::def_wrap(ctx_cls, "getPackagePinBel");
fn_wrapper_1a<Context, decltype(&Context::getBelPackagePin), &Context::getBelPackagePin, pass_through<std::string>,
conv_from_str<BelId>>::def_wrap(ctx_cls, "getBelPackagePin");
fn_wrapper_0a<Context, decltype(&Context::getChipName), &Context::getChipName, pass_through<std::string>>::def_wrap(
ctx_cls, "getChipName");
fn_wrapper_0a<Context, decltype(&Context::archId), &Context::archId, conv_to_str<IdString>>::def_wrap(ctx_cls,
"archId");
typedef std::unordered_map<IdString, std::unique_ptr<CellInfo>> CellMap;
typedef std::unordered_map<IdString, std::unique_ptr<NetInfo>> NetMap;
readonly_wrapper<Context, decltype(&Context::cells), &Context::cells, wrap_context<CellMap &>>::def_wrap(ctx_cls,
"cells");
readonly_wrapper<Context, decltype(&Context::nets), &Context::nets, wrap_context<NetMap &>>::def_wrap(ctx_cls,
"nets");
WRAP_RANGE(Bel, conv_to_str<BelId>);
WRAP_RANGE(Wire, conv_to_str<WireId>);
WRAP_RANGE(AllPip, conv_to_str<PipId>);
WRAP_RANGE(Pip, conv_to_str<PipId>);
WRAP_MAP_UPTR(CellMap, "IdCellMap");
WRAP_MAP_UPTR(NetMap, "IdNetMap");
}
NEXTPNR_NAMESPACE_END
#endif
#endif // NO_PYTHON

View File

@ -30,6 +30,7 @@
#include <fstream>
#include <streambuf>
#include "io.h"
#include "log.h"
#include "util.h"
@ -173,7 +174,7 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
// Add all set, configurable pips to the config
for (auto pip : ctx->getPips()) {
if (ctx->getBoundPipNet(pip) != IdString()) {
if (ctx->getPipType(pip) == 0) { // ignore fixed pips
if (ctx->getPipClass(pip) == 0) { // ignore fixed pips
std::string tile = empty_chip.get_tile_by_position_and_type(pip.location.y, pip.location.x,
ctx->getPipTiletype(pip));
std::string source = get_trellis_wirename(ctx, pip.location, ctx->getPipSrcWire(pip));
@ -182,12 +183,47 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
}
}
}
// Find bank voltages
std::unordered_map<int, IOVoltage> bankVcc;
std::unordered_map<int, bool> bankLvds;
// Set all bankref tiles to 3.3V (TODO)
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (ci->bel != BelId() && ci->type == ctx->id("TRELLIS_IO")) {
int bank = ctx->getPioBelBank(ci->bel);
std::string dir = str_or_default(ci->params, ctx->id("DIR"), "INPUT");
std::string iotype = str_or_default(ci->attrs, ctx->id("IO_TYPE"), "LVCMOS33");
if (dir != "INPUT") {
IOVoltage vcc = get_vccio(ioType_from_str(iotype));
if (bankVcc.find(bank) != bankVcc.end()) {
// TODO: strong and weak constraints
if (bankVcc[bank] != vcc) {
log_error("Error processing '%s': incompatible IO voltages %s and %s on bank %d.",
cell.first.c_str(ctx), iovoltage_to_str(bankVcc[bank]).c_str(),
iovoltage_to_str(vcc).c_str(), bank);
}
} else {
bankVcc[bank] = vcc;
}
}
if (iotype == "LVDS")
bankLvds[bank] = true;
}
}
// Set all bankref tiles to appropriate VccIO
for (const auto &tile : empty_chip.tiles) {
std::string type = tile.second->info.type;
if (type.find("BANKREF") != std::string::npos && type != "BANKREF8") {
cc.tiles[tile.first].add_enum("BANK.VCCIO", "3V3");
int bank = std::stoi(type.substr(7));
if (bankVcc.find(bank) != bankVcc.end())
cc.tiles[tile.first].add_enum("BANK.VCCIO", iovoltage_to_str(bankVcc[bank]));
if (bankLvds[bank]) {
cc.tiles[tile.first].add_enum("BANK.DIFF_REF", "ON");
cc.tiles[tile.first].add_enum("BANK.LVDSO", "ON");
}
}
}
@ -235,6 +271,20 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
std::string pic_tile = get_pic_tile(ctx, empty_chip, bel);
cc.tiles[pio_tile].add_enum(pio + ".BASE_TYPE", dir + "_" + iotype);
cc.tiles[pic_tile].add_enum(pio + ".BASE_TYPE", dir + "_" + iotype);
if (is_differential(ioType_from_str(iotype))) {
// Explicitly disable other pair
std::string other;
if (pio == "PIOA")
other = "PIOB";
else if (pio == "PIOC")
other = "PIOD";
else
log_error("cannot place differential IO at location %s\n", pio.c_str());
// cc.tiles[pio_tile].add_enum(other + ".BASE_TYPE", "_NONE_");
// cc.tiles[pic_tile].add_enum(other + ".BASE_TYPE", "_NONE_");
cc.tiles[pio_tile].add_enum(other + ".PULLMODE", "NONE");
cc.tiles[pio_tile].add_enum(pio + ".PULLMODE", "NONE");
}
if (dir != "INPUT" &&
(ci->ports.find(ctx->id("T")) == ci->ports.end() || ci->ports.at(ctx->id("T")).net == nullptr)) {
// Tie tristate low if unconnected for outputs or bidir
@ -247,7 +297,7 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
std::string cib_wirename = ctx->locInfo(cib_wire)->wire_data[cib_wire.index].name.get();
cc.tiles[cib_tile].add_enum("CIB." + cib_wirename + "MUX", "0");
}
if (dir == "INPUT") {
if (dir == "INPUT" && !is_differential(ioType_from_str(iotype))) {
cc.tiles[pio_tile].add_enum(pio + ".HYSTERESIS", "ON");
}
} else {

View File

@ -24,11 +24,16 @@ if (MSVC)
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/ecp5/resources/chipdb.rc PROPERTIES LANGUAGE RC)
foreach (dev ${devices})
set(DEV_CC_DB ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/chipdbs/chipdb-${dev}.bin)
set(DEV_CC_BBA_DB ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/chipdbs/chipdb-${dev}.bba)
set(DEV_PORTS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/portpins.inc)
add_custom_command(OUTPUT ${DEV_CC_DB}
COMMAND ${ENV_CMD} python3 ${DB_PY} -b -p ${DEV_PORTS_INC} ${dev} ${DEV_CC_DB}
add_custom_command(OUTPUT ${DEV_CC_BBA_DB}
COMMAND ${ENV_CMD} python3 ${DB_PY} -p ${DEV_PORTS_INC} ${dev} > ${DEV_CC_BBA_DB}
DEPENDS ${DB_PY}
)
add_custom_command(OUTPUT ${DEV_CC_DB}
COMMAND bbasm ${DEV_CC_BBA_DB} ${DEV_CC_DB}
DEPENDS bbasm ${DEV_CC_BBA_DB}
)
target_sources(ecp5_chipdb PRIVATE ${DEV_CC_DB})
set_source_files_properties(${DEV_CC_DB} PROPERTIES HEADER_FILE_ONLY TRUE)
foreach (target ${family_targets})
@ -39,11 +44,18 @@ else()
target_compile_options(ecp5_chipdb PRIVATE -g0 -O0 -w)
foreach (dev ${devices})
set(DEV_CC_DB ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/chipdbs/chipdb-${dev}.cc)
set(DEV_CC_BBA_DB ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/chipdbs/chipdb-${dev}.bba)
set(DEV_PORTS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/portpins.inc)
add_custom_command(OUTPUT ${DEV_CC_DB}
COMMAND ${ENV_CMD} python3 ${DB_PY} -c -p ${DEV_PORTS_INC} ${dev} ${DEV_CC_DB}
add_custom_command(OUTPUT ${DEV_CC_BBA_DB}
COMMAND ${ENV_CMD} python3 ${DB_PY} -p ${DEV_PORTS_INC} ${dev} > ${DEV_CC_BBA_DB}.new
COMMAND mv ${DEV_CC_BBA_DB}.new ${DEV_CC_BBA_DB}
DEPENDS ${DB_PY}
)
add_custom_command(OUTPUT ${DEV_CC_DB}
COMMAND bbasm --c ${DEV_CC_BBA_DB} ${DEV_CC_DB}.new
COMMAND mv ${DEV_CC_DB}.new ${DEV_CC_DB}
DEPENDS bbasm ${DEV_CC_BBA_DB}
)
target_sources(ecp5_chipdb PRIVATE ${DEV_CC_DB})
foreach (target ${family_targets})
target_sources(${target} PRIVATE $<TARGET_OBJECTS:ecp5_chipdb>)

217
ecp5/io.cc Normal file
View File

@ -0,0 +1,217 @@
/*
* 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 "io.h"
NEXTPNR_NAMESPACE_BEGIN
std::string iovoltage_to_str(IOVoltage v)
{
switch (v) {
case IOVoltage::VCC_3V3:
return "3V3";
case IOVoltage::VCC_2V5:
return "2V5";
case IOVoltage::VCC_1V8:
return "1V8";
case IOVoltage::VCC_1V5:
return "1V5";
case IOVoltage::VCC_1V35:
return "1V35";
case IOVoltage::VCC_1V2:
return "1V2";
}
NPNR_ASSERT_FALSE("unknown IO voltage");
}
IOVoltage iovoltage_from_str(const std::string &name)
{
if (name == "3V3")
return IOVoltage::VCC_3V3;
if (name == "2V5")
return IOVoltage::VCC_2V5;
if (name == "1V8")
return IOVoltage::VCC_1V8;
if (name == "1V5")
return IOVoltage::VCC_1V5;
if (name == "1V35")
return IOVoltage::VCC_1V35;
if (name == "1V2")
return IOVoltage::VCC_1V2;
NPNR_ASSERT_FALSE("unknown IO voltage");
}
std::string iotype_to_str(IOType type)
{
if (type == IOType::TYPE_NONE)
return "NONE";
#define X(t) \
if (type == IOType::t) \
return #t;
#include "iotypes.inc"
#undef X
if (type == IOType::TYPE_UNKNOWN)
return "<unknown>";
NPNR_ASSERT_FALSE("unknown IO type");
}
IOType ioType_from_str(const std::string &name)
{
if (name == "NONE")
return IOType::TYPE_NONE;
#define X(t) \
if (name == #t) \
return IOType::t;
#include "iotypes.inc"
return IOType::TYPE_UNKNOWN;
}
IOVoltage get_vccio(IOType type)
{
switch (type) {
case IOType::LVTTL33:
case IOType::LVCMOS33:
case IOType::LVCMOS33D:
case IOType::LVPECL33:
case IOType::LVPECL33E:
return IOVoltage::VCC_3V3;
case IOType::LVCMOS25:
case IOType::LVCMOS25D:
case IOType::LVDS:
case IOType::SLVS:
case IOType::SUBLVDS:
case IOType::LVDS25E:
case IOType::MLVDS25:
case IOType::MLVDS25E:
case IOType::BLVDS25:
return IOVoltage::VCC_2V5;
case IOType::LVCMOS18:
case IOType::LVCMOS18D:
case IOType::SSTL18_I:
case IOType::SSTL18_II:
case IOType::SSTL18D_I:
case IOType::SSTL18D_II:
return IOVoltage::VCC_1V8;
case IOType::LVCMOS15:
case IOType::SSTL15_I:
case IOType::SSTL15_II:
case IOType::SSTL15D_I:
case IOType::SSTL15D_II:
return IOVoltage::VCC_1V5;
case IOType::SSTL135_I:
case IOType::SSTL135_II:
case IOType::SSTL135D_I:
case IOType::SSTL135D_II:
return IOVoltage::VCC_1V35;
case IOType::LVCMOS12:
case IOType::HSUL12:
case IOType::HSUL12D:
return IOVoltage::VCC_1V2;
default:
NPNR_ASSERT_FALSE("unknown IO type, unable to determine VccIO");
}
}
bool is_strong_vccio_constraint(IOType type, PortType dir, IOSide side)
{
if (dir == PORT_OUT || dir == PORT_INOUT)
return true;
switch (type) {
case IOType::TYPE_NONE:
case IOType::LVCMOS33D:
case IOType::LVPECL33:
case IOType::LVDS:
case IOType::MLVDS25:
case IOType::BLVDS25:
case IOType::SLVS:
case IOType::SUBLVDS:
case IOType::LVCMOS12:
case IOType::HSUL12:
case IOType::HSUL12D:
return false;
case IOType::LVCMOS33:
case IOType::LVTTL33:
case IOType::LVCMOS25:
return (side == IOSide::LEFT || side == IOSide::RIGHT);
default:
return true;
}
}
bool is_differential(IOType type)
{
switch (type) {
case IOType::LVCMOS33D:
case IOType::LVCMOS25D:
case IOType::LVPECL33:
case IOType::LVDS:
case IOType::MLVDS25:
case IOType::BLVDS25:
case IOType::SLVS:
case IOType::SUBLVDS:
case IOType::LVCMOS18D:
case IOType::SSTL18D_I:
case IOType::SSTL18D_II:
case IOType::SSTL15D_I:
case IOType::SSTL15D_II:
case IOType::SSTL135D_I:
case IOType::SSTL135D_II:
case IOType::HSUL12D:
return true;
default:
return false;
}
}
bool is_referenced(IOType type)
{
switch (type) {
case IOType::SSTL18_I:
case IOType::SSTL18_II:
case IOType::SSTL18D_I:
case IOType::SSTL18D_II:
case IOType::SSTL15_I:
case IOType::SSTL15_II:
case IOType::SSTL15D_I:
case IOType::SSTL15D_II:
case IOType::SSTL135_I:
case IOType::SSTL135_II:
case IOType::SSTL135D_I:
case IOType::SSTL135D_II:
case IOType::HSUL12:
case IOType::HSUL12D:
return true;
default:
return false;
}
}
bool valid_loc_for_io(IOType type, PortType dir, IOSide side, int z)
{
bool is_lr = side == IOSide::LEFT || side == IOSide::RIGHT;
if (is_referenced(type) && !is_lr)
return false;
if (is_differential(type) && (!is_lr || ((z % 2) == 1)))
return false;
if ((type == IOType::LVCMOS18D || type == IOType::LVDS) && (dir == PORT_OUT || PORT_INOUT) && z != 0)
return false;
return true;
}
NEXTPNR_NAMESPACE_END

70
ecp5/io.h Normal file
View File

@ -0,0 +1,70 @@
/*
* 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 IO_H
#define IO_H
#include "nextpnr.h"
NEXTPNR_NAMESPACE_BEGIN
enum class IOVoltage
{
VCC_3V3,
VCC_2V5,
VCC_1V8,
VCC_1V5,
VCC_1V35,
VCC_1V2
};
std::string iovoltage_to_str(IOVoltage v);
IOVoltage iovoltage_from_str(const std::string &name);
enum class IOType
{
TYPE_NONE,
#define X(t) t,
#include "iotypes.inc"
#undef X
TYPE_UNKNOWN,
};
enum class IOSide
{
LEFT,
RIGHT,
TOP,
BOTTOM,
};
std::string iotype_to_str(IOType type);
IOType ioType_from_str(const std::string &name);
// IO related functions
IOVoltage get_vccio(IOType type);
bool is_strong_vccio_constraint(IOType type, PortType dir, IOSide side);
bool is_differential(IOType type);
bool is_referenced(IOType type);
bool valid_loc_for_io(IOType type, PortType dir, IOSide side, int z);
NEXTPNR_NAMESPACE_END
#endif

37
ecp5/iotypes.inc Normal file
View File

@ -0,0 +1,37 @@
X(LVTTL33)
X(LVCMOS33)
X(LVCMOS25)
X(LVCMOS18)
X(LVCMOS15)
X(LVCMOS12)
X(SSTL18_I)
X(SSTL18_II)
X(SSTL15_I)
X(SSTL15_II)
X(SSTL135_I)
X(SSTL135_II)
X(HSUL12)
X(SSTL18D_I)
X(SSTL18D_II)
X(SSTL135D_I)
X(SSTL135D_II)
X(SSTL15D_I)
X(SSTL15D_II)
X(HSUL12D)
X(LVCMOS33D)
X(LVCMOS25D)
X(LVDS)
X(BLVDS25)
X(MLVDS25)
X(LVPECL33)
X(SLVS)
X(SUBLVDS)
X(LVCMOS18D)
X(LVDS25E)
X(BLVDS25E)
X(MLVDS25E)
X(LVPECL33E)

View File

@ -63,6 +63,7 @@ int main(int argc, char *argv[])
#ifndef NO_GUI
options.add_options()("gui", "start gui");
#endif
options.add_options()("test", "check architecture database integrity");
options.add_options()("25k", "set device type to LFE5U-25F");
options.add_options()("45k", "set device type to LFE5U-45F");
@ -99,18 +100,16 @@ int main(int argc, char *argv[])
}
if (vm.count("help") || argc == 1) {
std::cout << boost::filesystem::basename(argv[0])
<< " -- Next Generation Place and Route (git "
"sha1 " GIT_COMMIT_HASH_STR ")\n";
std::cout << boost::filesystem::basename(argv[0]) << " -- Next Generation Place and Route (git "
"sha1 " GIT_COMMIT_HASH_STR ")\n";
std::cout << "\n";
std::cout << options << "\n";
return argc != 1;
}
if (vm.count("version")) {
std::cout << boost::filesystem::basename(argv[0])
<< " -- Next Generation Place and Route (git "
"sha1 " GIT_COMMIT_HASH_STR ")\n";
std::cout << boost::filesystem::basename(argv[0]) << " -- Next Generation Place and Route (git "
"sha1 " GIT_COMMIT_HASH_STR ")\n";
return 1;
}
@ -148,6 +147,9 @@ int main(int argc, char *argv[])
if (vm.count("no-tmdriv"))
ctx->timing_driven = false;
if (vm.count("test"))
ctx->archcheck();
#ifndef NO_GUI
if (vm.count("gui")) {
Application a(argc, argv);
@ -165,8 +167,12 @@ int main(int argc, char *argv[])
if (!ctx->pack() && !ctx->force)
log_error("Packing design failed.\n");
if (vm.count("freq"))
if (vm.count("freq")) {
ctx->target_freq = vm["freq"].as<double>() * 1e6;
ctx->user_freq = true;
} else {
log_warning("Target frequency not specified. Will optimise for max frequency.\n");
}
assign_budget(ctx.get());
ctx->check();
print_utilisation(ctx.get());

View File

@ -10,11 +10,7 @@ type_at_location = dict()
tiletype_names = dict()
parser = argparse.ArgumentParser(description="import ECP5 routing and bels from Project Trellis")
group = parser.add_mutually_exclusive_group()
group.add_argument("-b", "--binary", action="store_true")
group.add_argument("-c", "--c_file", action="store_true")
parser.add_argument("device", type=str, help="target device")
parser.add_argument("outfile", type=argparse.FileType('w'), help="output filename")
parser.add_argument("-p", "--portspins", type=str, help="path to portpins.inc")
args = parser.parse_args()
@ -36,284 +32,50 @@ portpins = dict()
class BinaryBlobAssembler:
def __init__(self, cname, endianness, nodebug=False):
assert endianness in ["le", "be"]
self.cname = cname
self.endianness = endianness
self.finalized = False
self.data = bytearray()
self.comments = dict()
self.labels = dict()
self.exports = set()
self.labels_byaddr = dict()
self.ltypes_byaddr = dict()
self.strings = dict()
self.refs = dict()
self.nodebug = nodebug
def l(self, name, ltype=None, export=False):
assert not self.finalized
assert name not in self.labels
assert len(self.data) not in self.labels_byaddr
self.labels[name] = len(self.data)
if ltype is not None:
self.ltypes_byaddr[len(self.data)] = ltype
self.labels_byaddr[len(self.data)] = name
if export:
assert ltype is not None
self.exports.add(len(self.data))
def l(self, name, ltype = None, export = False):
if ltype is None:
print("label %s" % (name,))
else:
print("label %s %s" % (name, ltype))
def r(self, name, comment):
assert not self.finalized
assert len(self.data) % 4 == 0
assert len(self.data) not in self.refs
if self.nodebug:
comment = None
if name is not None:
self.refs[len(self.data)] = (name, comment)
self.data.append(0)
self.data.append(0)
self.data.append(0)
self.data.append(0)
if (name is None) and (comment is not None):
self.comments[len(self.data)] = comment + " (null reference)"
if comment is None:
print("ref %s" % (name,))
else:
print("ref %s %s" % (name, comment))
def s(self, s, comment):
assert not self.finalized
if self.nodebug:
comment = None
if s not in self.strings:
index = len(self.strings)
self.strings[s] = index
else:
index = self.strings[s]
if comment is not None:
self.r("str%d" % index, '%s: "%s"' % (comment, s))
else:
self.r("str%d" % index, None)
print("str %s" % s)
def u8(self, v, comment):
assert not self.finalized
if self.nodebug:
comment = None
self.data.append(v)
if comment is not None:
self.comments[len(self.data)] = comment
if comment is None:
print("u8 %d" % (v,))
else:
print("u8 %d %s" % (v, comment))
def u16(self, v, comment):
assert not self.finalized
assert len(self.data) % 2 == 0
if self.nodebug:
comment = None
if self.endianness == "le":
self.data.append(v & 255)
self.data.append((v >> 8) & 255)
elif self.endianness == "be":
self.data.append((v >> 8) & 255)
self.data.append(v & 255)
if comment is None:
print("u16 %d" % (v,))
else:
assert 0
if comment is not None:
self.comments[len(self.data)] = comment
def s16(self, v, comment):
assert not self.finalized
assert len(self.data) % 2 == 0
if self.nodebug:
comment = None
c2val = (((-v) ^ 0xffff) + 1) if v < 0 else v
if self.endianness == "le":
self.data.append(c2val & 255)
self.data.append((c2val >> 8) & 255)
elif self.endianness == "be":
self.data.append((c2val >> 8) & 255)
self.data.append(c2val & 255)
else:
assert 0
if comment is not None:
self.comments[len(self.data)] = comment
print("u16 %d %s" % (v, comment))
def u32(self, v, comment):
assert not self.finalized
assert len(self.data) % 4 == 0
if self.nodebug:
comment = None
if self.endianness == "le":
self.data.append(v & 255)
self.data.append((v >> 8) & 255)
self.data.append((v >> 16) & 255)
self.data.append((v >> 24) & 255)
elif self.endianness == "be":
self.data.append((v >> 24) & 255)
self.data.append((v >> 16) & 255)
self.data.append((v >> 8) & 255)
self.data.append(v & 255)
if comment is None:
print("u32 %d" % (v,))
else:
assert 0
if comment is not None:
self.comments[len(self.data)] = comment
print("u32 %d %s" % (v, comment))
def finalize(self):
assert not self.finalized
for s, index in sorted(self.strings.items()):
self.l("str%d" % index, "char")
for c in s:
self.data.append(ord(c))
self.data.append(0)
self.finalized = True
cursor = 0
while cursor < len(self.data):
if cursor in self.refs:
v = self.labels[self.refs[cursor][0]] - cursor
if self.endianness == "le":
self.data[cursor + 0] = (v & 255)
self.data[cursor + 1] = ((v >> 8) & 255)
self.data[cursor + 2] = ((v >> 16) & 255)
self.data[cursor + 3] = ((v >> 24) & 255)
elif self.endianness == "be":
self.data[cursor + 0] = ((v >> 24) & 255)
self.data[cursor + 1] = ((v >> 16) & 255)
self.data[cursor + 2] = ((v >> 8) & 255)
self.data[cursor + 3] = (v & 255)
else:
assert 0
cursor += 4
else:
cursor += 1
def pre(self, s):
print("pre %s" % s)
def write_verbose_c(self, f, ctype="const unsigned char"):
assert self.finalized
print("%s %s[%d] = {" % (ctype, self.cname, len(self.data)), file=f)
cursor = 0
bytecnt = 0
while cursor < len(self.data):
if cursor in self.comments:
if bytecnt == 0:
print(" ", end="", file=f)
print(" // %s" % self.comments[cursor], file=f)
bytecnt = 0
if cursor in self.labels_byaddr:
if bytecnt != 0:
print(file=f)
if cursor in self.exports:
print("#define %s ((%s*)(%s+%d))" % (
self.labels_byaddr[cursor], self.ltypes_byaddr[cursor], self.cname, cursor), file=f)
else:
print(" // [%d] %s" % (cursor, self.labels_byaddr[cursor]), file=f)
bytecnt = 0
if cursor in self.refs:
if bytecnt != 0:
print(file=f)
print(" ", end="", file=f)
print(" %-4s" % ("%d," % self.data[cursor + 0]), end="", file=f)
print(" %-4s" % ("%d," % self.data[cursor + 1]), end="", file=f)
print(" %-4s" % ("%d," % self.data[cursor + 2]), end="", file=f)
print(" %-4s" % ("%d," % self.data[cursor + 3]), end="", file=f)
print(" // [%d] %s (reference to %s)" % (cursor, self.refs[cursor][1], self.refs[cursor][0]), file=f)
bytecnt = 0
cursor += 4
else:
if bytecnt == 0:
print(" ", end="", file=f)
print(" %-4s" % ("%d," % self.data[cursor]), end=("" if bytecnt < 15 else "\n"), file=f)
bytecnt = (bytecnt + 1) & 15
cursor += 1
if bytecnt != 0:
print(file=f)
print("};", file=f)
def post(self, s):
print("post %s" % s)
def write_compact_c(self, f, ctype="const unsigned char"):
assert self.finalized
print("%s %s[%d] = {" % (ctype, self.cname, len(self.data)), file=f)
column = 0
for v in self.data:
if column == 0:
print(" ", end="", file=f)
column += 2
s = "%d," % v
print(s, end="", file=f)
column += len(s)
if column > 75:
print(file=f)
column = 0
if column != 0:
print(file=f)
for cursor in self.exports:
print("#define %s ((%s*)(%s+%d))" % (
self.labels_byaddr[cursor], self.ltypes_byaddr[cursor], self.cname, cursor), file=f)
print("};", file=f)
def write_uint64_c(self, f, ctype="const uint64_t"):
assert self.finalized
print("%s %s[%d] = {" % (ctype, self.cname, (len(self.data) + 7) // 8), file=f)
column = 0
for i in range((len(self.data) + 7) // 8):
v0 = self.data[8 * i + 0] if 8 * i + 0 < len(self.data) else 0
v1 = self.data[8 * i + 1] if 8 * i + 1 < len(self.data) else 0
v2 = self.data[8 * i + 2] if 8 * i + 2 < len(self.data) else 0
v3 = self.data[8 * i + 3] if 8 * i + 3 < len(self.data) else 0
v4 = self.data[8 * i + 4] if 8 * i + 4 < len(self.data) else 0
v5 = self.data[8 * i + 5] if 8 * i + 5 < len(self.data) else 0
v6 = self.data[8 * i + 6] if 8 * i + 6 < len(self.data) else 0
v7 = self.data[8 * i + 7] if 8 * i + 7 < len(self.data) else 0
if self.endianness == "le":
v = v0 << 0
v |= v1 << 8
v |= v2 << 16
v |= v3 << 24
v |= v4 << 32
v |= v5 << 40
v |= v6 << 48
v |= v7 << 56
elif self.endianness == "be":
v = v7 << 0
v |= v6 << 8
v |= v5 << 16
v |= v4 << 24
v |= v3 << 32
v |= v2 << 40
v |= v1 << 48
v |= v0 << 56
else:
assert 0
if column == 3:
print(" 0x%016x," % v, file=f)
column = 0
else:
if column == 0:
print(" ", end="", file=f)
print(" 0x%016x," % v, end="", file=f)
column += 1
if column != 0:
print("", file=f)
print("};", file=f)
def write_string_c(self, f, ctype="const char"):
assert self.finalized
assert self.data[len(self.data) - 1] == 0
print("%s %s[%d] =" % (ctype, self.cname, len(self.data)), file=f)
print(" \"", end="", file=f)
column = 0
for i in range(len(self.data) - 1):
if (self.data[i] < 32) or (self.data[i] > 126):
print("\\%03o" % self.data[i], end="", file=f)
column += 4
elif self.data[i] == ord('"') or self.data[i] == ord('\\'):
print("\\" + chr(self.data[i]), end="", file=f)
column += 2
else:
print(chr(self.data[i]), end="", file=f)
column += 1
if column > 70 and (i != len(self.data) - 2):
print("\"\n \"", end="", file=f)
column = 0
print("\";", file=f)
def write_binary(self, f):
assert self.finalized
assert self.data[len(self.data) - 1] == 0
f.buffer.write(self.data)
def push(self, name):
print("push %s" % name)
def pop(self):
print("pop")
bel_types = {
"NONE": 0,
@ -364,13 +126,25 @@ def process_pio_db(ddrg, device):
if bel_idx is not None:
pindata.append((loc, bel_idx, bank, pinfunc))
global_data = {}
quadrants = ["UL", "UR", "LL", "LR"]
def process_loc_globals(chip):
for y in range(0, max_row+1):
for x in range(0, max_col+1):
quad = chip.global_data.get_quadrant(y, x)
tapdrv = chip.global_data.get_tap_driver(y, x)
global_data[x, y] = (quadrants.index(quad), int(tapdrv.dir), tapdrv.col)
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.u16(loc.x, "%s.x" % sym_name)
bba.u16(loc.y, "%s.y" % sym_name)
bba = BinaryBlobAssembler("chipdb_blob_%s" % dev_name, endianness)
bba = BinaryBlobAssembler()
bba.pre('#include "nextpnr.h"')
bba.pre('NEXTPNR_NAMESPACE_BEGIN')
bba.post('NEXTPNR_NAMESPACE_END')
bba.push("chipdb_blob_%s" % dev_name)
bba.r("chip_info", "chip_info")
loctypes = list([_.key() for _ in ddrg.locationTypes])
@ -450,6 +224,14 @@ def write_database(dev_name, ddrg, endianness):
for y in range(0, max_row+1):
for x in range(0, max_col+1):
bba.u32(loctypes.index(ddrg.typeAtLocation[pytrellis.Location(x, y)]), "loctype")
bba.l("location_glbinfo", "GlobalInfoPOD")
for y in range(0, max_row+1):
for x in range(0, max_col+1):
bba.u16(global_data[x, y][2], "tap_col")
bba.u8(global_data[x, y][1], "tap_dir")
bba.u8(global_data[x, y][0], "quad")
for package, pkgdata in sorted(packages.items()):
bba.l("package_data_%s" % package, "PackagePinPOD")
for pin in pkgdata:
@ -491,11 +273,12 @@ def write_database(dev_name, ddrg, endianness):
bba.r("locations", "locations")
bba.r("location_types", "location_type")
bba.r("location_glbinfo", "location_glbinfo")
bba.r("tiletype_names", "tiletype_names")
bba.r("package_data", "package_info")
bba.r("pio_info", "pio_info")
bba.finalize()
bba.pop()
return bba
dev_names = {"25k": "LFE5U-25F", "45k": "LFE5U-45F", "85k": "LFE5U-85F"}
@ -518,30 +301,18 @@ def main():
idx = len(portpins) + 1
portpins[line[1]] = idx
print("Initialising chip...")
# print("Initialising chip...")
chip = pytrellis.Chip(dev_names[args.device])
print("Building routing graph...")
# print("Building routing graph...")
ddrg = pytrellis.make_dedup_chipdb(chip)
max_row = chip.get_max_row()
max_col = chip.get_max_col()
process_pio_db(ddrg, args.device)
print("{} unique location types".format(len(ddrg.locationTypes)))
process_loc_globals(chip)
# print("{} unique location types".format(len(ddrg.locationTypes)))
bba = write_database(args.device, ddrg, "le")
if args.c_file:
print('#include "nextpnr.h"', file=args.outfile)
print('NEXTPNR_NAMESPACE_BEGIN', file=args.outfile)
if args.binary:
bba.write_binary(args.outfile)
if args.c_file:
bba.write_string_c(args.outfile)
if args.c_file:
print('NEXTPNR_NAMESPACE_END', file=args.outfile)
if __name__ == "__main__":
main()

View File

@ -24,22 +24,24 @@
NEXTPNR_NAMESPACE_BEGIN
void Arch::addWire(IdString name, int x, int y)
void Arch::addWire(IdString name, IdString type, int x, int y)
{
NPNR_ASSERT(wires.count(name) == 0);
WireInfo &wi = wires[name];
wi.name = name;
wi.type = type;
wi.x = x;
wi.y = y;
wire_ids.push_back(name);
}
void Arch::addPip(IdString name, IdString srcWire, IdString dstWire, DelayInfo delay)
void Arch::addPip(IdString name, IdString type, IdString srcWire, IdString dstWire, DelayInfo delay)
{
NPNR_ASSERT(pips.count(name) == 0);
PipInfo &pi = pips[name];
pi.name = name;
pi.type = type;
pi.srcWire = srcWire;
pi.dstWire = dstWire;
pi.delay = delay;
@ -49,11 +51,12 @@ void Arch::addPip(IdString name, IdString srcWire, IdString dstWire, DelayInfo d
pip_ids.push_back(name);
}
void Arch::addAlias(IdString name, IdString srcWire, IdString dstWire, DelayInfo delay)
void Arch::addAlias(IdString name, IdString type, IdString srcWire, IdString dstWire, DelayInfo delay)
{
NPNR_ASSERT(pips.count(name) == 0);
PipInfo &pi = pips[name];
pi.name = name;
pi.type = type;
pi.srcWire = srcWire;
pi.dstWire = dstWire;
pi.delay = delay;
@ -77,18 +80,18 @@ void Arch::addBel(IdString name, IdString type, Loc loc, bool gb)
bel_ids.push_back(name);
bel_by_loc[loc] = name;
if (bels_by_tile.size() <= loc.x)
if (int(bels_by_tile.size()) <= loc.x)
bels_by_tile.resize(loc.x + 1);
if (bels_by_tile[loc.x].size() <= loc.y)
if (int(bels_by_tile[loc.x].size()) <= loc.y)
bels_by_tile[loc.x].resize(loc.y + 1);
bels_by_tile[loc.x][loc.y].push_back(name);
if (tileDimZ.size() <= loc.x)
if (int(tileDimZ.size()) <= loc.x)
tileDimZ.resize(loc.x + 1);
if (tileDimZ[loc.x].size() <= loc.y)
if (int(tileDimZ[loc.x].size()) <= loc.y)
tileDimZ[loc.x].resize(loc.y + 1);
gridDimX = std::max(gridDimX, loc.x + 1);
@ -193,6 +196,24 @@ BelId Arch::getBelByName(IdString name) const
IdString Arch::getBelName(BelId bel) const { return bel; }
Loc Arch::getBelLocation(BelId bel) const
{
auto &info = bels.at(bel);
return Loc(info.x, info.y, info.z);
}
BelId Arch::getBelByLocation(Loc loc) const
{
auto it = bel_by_loc.find(loc);
if (it != bel_by_loc.end())
return it->second;
return BelId();
}
const std::vector<BelId> &Arch::getBelsByTile(int x, int y) const { return bels_by_tile.at(x).at(y); }
bool Arch::getBelGlobalBuf(BelId bel) const { return bels.at(bel).gb; }
uint32_t Arch::getBelChecksum(BelId bel) const
{
// FIXME
@ -234,6 +255,7 @@ std::vector<PortPin> Arch::getBelPins(BelId bel) const
std::vector<PortPin> ret;
for (auto &it : bels.at(bel).pins)
ret.push_back(it.first);
return ret;
}
// ---------------------------------------------------------------
@ -247,6 +269,8 @@ WireId Arch::getWireByName(IdString name) const
IdString Arch::getWireName(WireId wire) const { return wire; }
IdString Arch::getWireType(WireId wire) const { return wires.at(wire).type; }
uint32_t Arch::getWireChecksum(WireId wire) const
{
// FIXME
@ -297,6 +321,8 @@ PipId Arch::getPipByName(IdString name) const
IdString Arch::getPipName(PipId pip) const { return pip; }
IdString Arch::getPipType(PipId pip) const { return pips.at(pip).type; }
uint32_t Arch::getPipChecksum(PipId wire) const
{
// FIXME
@ -368,13 +394,6 @@ const std::vector<GroupId> &Arch::getGroupGroups(GroupId group) const { return g
// ---------------------------------------------------------------
void Arch::estimatePosition(BelId bel, int &x, int &y, bool &gb) const
{
x = bels.at(bel).x;
y = bels.at(bel).y;
gb = bels.at(bel).gb;
}
delay_t Arch::estimateDelay(WireId src, WireId dst) const
{
const WireInfo &s = wires.at(src);
@ -384,10 +403,7 @@ delay_t Arch::estimateDelay(WireId src, WireId dst) const
return (dx + dy) * grid_distance_to_delay;
}
delay_t Arch::getBudgetOverride(const PortRef& pr, delay_t v) const
{
return v;
}
delay_t Arch::getBudgetOverride(NetInfo *net_info, int user_idx, delay_t budget) const { return budget; }
// ---------------------------------------------------------------

View File

@ -31,7 +31,7 @@ struct WireInfo;
struct PipInfo
{
IdString name, bound_net;
IdString name, type, bound_net;
WireId srcWire, dstWire;
DelayInfo delay;
DecalXY decalxy;
@ -39,7 +39,7 @@ struct PipInfo
struct WireInfo
{
IdString name, bound_net;
IdString name, type, bound_net;
std::vector<PipId> downhill, uphill, aliases;
BelPin uphill_bel_pin;
std::vector<BelPin> downhill_bel_pins;
@ -96,9 +96,9 @@ struct Arch : BaseCtx
float grid_distance_to_delay;
void addWire(IdString name, int x, int y);
void addPip(IdString name, IdString srcWire, IdString dstWire, DelayInfo delay);
void addAlias(IdString name, IdString srcWire, IdString dstWire, DelayInfo delay);
void addWire(IdString name, IdString type, int x, int y);
void addPip(IdString name, IdString type, IdString srcWire, IdString dstWire, DelayInfo delay);
void addAlias(IdString name, IdString type, IdString srcWire, IdString dstWire, DelayInfo delay);
void addBel(IdString name, IdString type, Loc loc, bool gb);
void addBelInput(IdString bel, IdString name, IdString wire);
@ -141,7 +141,7 @@ struct Arch : BaseCtx
IdString getBelName(BelId bel) const;
Loc getBelLocation(BelId bel) const;
BelId getBelByLocation(Loc loc) const;
std::vector<BelId> getBelsByTile(int x, int y) const;
const std::vector<BelId> &getBelsByTile(int x, int y) const;
bool getBelGlobalBuf(BelId bel) const;
uint32_t getBelChecksum(BelId bel) const;
void bindBel(BelId bel, IdString cell, PlaceStrength strength);
@ -157,6 +157,7 @@ struct Arch : BaseCtx
WireId getWireByName(IdString name) const;
IdString getWireName(WireId wire) const;
IdString getWireType(WireId wire) const;
uint32_t getWireChecksum(WireId wire) const;
void bindWire(WireId wire, IdString net, PlaceStrength strength);
void unbindWire(WireId wire);
@ -169,6 +170,7 @@ struct Arch : BaseCtx
PipId getPipByName(IdString name) const;
IdString getPipName(PipId pip) const;
IdString getPipType(PipId pip) const;
uint32_t getPipChecksum(PipId pip) const;
void bindPip(PipId pip, IdString net, PlaceStrength strength);
void unbindPip(PipId pip);
@ -191,13 +193,12 @@ struct Arch : BaseCtx
const std::vector<PipId> &getGroupPips(GroupId group) const;
const std::vector<GroupId> &getGroupGroups(GroupId group) const;
void estimatePosition(BelId bel, int &x, int &y, bool &gb) const;
delay_t estimateDelay(WireId src, WireId dst) const;
delay_t getDelayEpsilon() const { return 0.01; }
delay_t getRipupDelayPenalty() const { return 1.0; }
float getDelayNS(delay_t v) const { return v; }
uint32_t getDelayChecksum(delay_t v) const { return 0; }
delay_t getBudgetOverride(const PortRef& pr, delay_t v) const;
delay_t getBudgetOverride(NetInfo *net_info, int user_idx, delay_t budget) const;
bool pack() { return true; }
bool place();

View File

@ -20,12 +20,22 @@
#ifndef NO_PYTHON
#include "arch_pybindings.h"
#include "nextpnr.h"
#include "pybindings.h"
NEXTPNR_NAMESPACE_BEGIN
void arch_wrap_python() { class_<ArchArgs>("ArchArgs"); }
void arch_wrap_python()
{
using namespace PythonConversion;
auto arch_cls = class_<Arch, Arch *, bases<BaseCtx>, boost::noncopyable>("Arch", init<ArchArgs>());
auto ctx_cls = class_<Context, Context *, bases<Arch>, boost::noncopyable>("Context", no_init)
.def("checksum", &Context::checksum)
.def("pack", &Context::pack)
.def("place", &Context::place)
.def("route", &Context::route);
}
NEXTPNR_NAMESPACE_END

View File

@ -75,18 +75,16 @@ int main(int argc, char *argv[])
}
if (vm.count("help") || argc == 1) {
std::cout << boost::filesystem::basename(argv[0])
<< " -- Next Generation Place and Route (git "
"sha1 " GIT_COMMIT_HASH_STR ")\n";
std::cout << boost::filesystem::basename(argv[0]) << " -- Next Generation Place and Route (git "
"sha1 " GIT_COMMIT_HASH_STR ")\n";
std::cout << "\n";
std::cout << options << "\n";
return argc != 1;
}
if (vm.count("version")) {
std::cout << boost::filesystem::basename(argv[0])
<< " -- Next Generation Place and Route (git "
"sha1 " GIT_COMMIT_HASH_STR ")\n";
std::cout << boost::filesystem::basename(argv[0]) << " -- Next Generation Place and Route (git "
"sha1 " GIT_COMMIT_HASH_STR ")\n";
return 1;
}

View File

@ -10,5 +10,9 @@
<file>resources/resultset_next.png</file>
<file>resources/resultset_last.png</file>
<file>resources/cross.png</file>
<file>resources/zoom_in.png</file>
<file>resources/zoom_out.png</file>
<file>resources/shape_handles.png</file>
<file>resources/shape_square.png</file>
</qresource>
</RCC>

View File

@ -25,7 +25,6 @@
#include <QSplitter>
#include "designwidget.h"
#include "fpgaviewwidget.h"
#include "jsonparse.h"
#include "log.h"
#include "mainwindow.h"
#include "pythontab.h"
@ -76,7 +75,7 @@ BaseMainWindow::BaseMainWindow(std::unique_ptr<Context> context, QWidget *parent
centralTabWidget->setTabsClosable(true);
connect(centralTabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(closeTab(int)));
FPGAViewWidget *fpgaView = new FPGAViewWidget();
fpgaView = new FPGAViewWidget();
centralTabWidget->addTab(fpgaView, "Graphics");
centralTabWidget->tabBar()->tabButton(0, QTabBar::RightSide)->resize(0, 0);
@ -163,4 +162,30 @@ void BaseMainWindow::createMenusAndBars()
mainToolBar->addAction(actionSave);
}
void BaseMainWindow::createGraphicsBar()
{
QAction *actionZoomIn = new QAction("Zoom In", this);
actionZoomIn->setIcon(QIcon(":/icons/resources/zoom_in.png"));
connect(actionZoomIn, SIGNAL(triggered()), fpgaView, SLOT(zoomIn()));
QAction *actionZoomOut = new QAction("Zoom Out", this);
actionZoomOut->setIcon(QIcon(":/icons/resources/zoom_out.png"));
connect(actionZoomOut, SIGNAL(triggered()), fpgaView, SLOT(zoomOut()));
QAction *actionZoomSelected = new QAction("Zoom Selected", this);
actionZoomSelected->setIcon(QIcon(":/icons/resources/shape_handles.png"));
connect(actionZoomSelected, SIGNAL(triggered()), fpgaView, SLOT(zoomSelected()));
QAction *actionZoomOutbound = new QAction("Zoom Outbound", this);
actionZoomOutbound->setIcon(QIcon(":/icons/resources/shape_square.png"));
connect(actionZoomOutbound, SIGNAL(triggered()), fpgaView, SLOT(zoomOutbound()));
graphicsToolBar = new QToolBar();
addToolBar(Qt::TopToolBarArea, graphicsToolBar);
graphicsToolBar->addAction(actionZoomIn);
graphicsToolBar->addAction(actionZoomOut);
graphicsToolBar->addAction(actionZoomSelected);
graphicsToolBar->addAction(actionZoomOutbound);
}
NEXTPNR_NAMESPACE_END

View File

@ -37,6 +37,7 @@ NEXTPNR_NAMESPACE_BEGIN
class PythonTab;
class DesignWidget;
class FPGAViewWidget;
class BaseMainWindow : public QMainWindow
{
@ -49,6 +50,7 @@ class BaseMainWindow : public QMainWindow
protected:
void createMenusAndBars();
void createGraphicsBar();
protected Q_SLOTS:
void writeInfo(std::string text);
@ -70,12 +72,14 @@ class BaseMainWindow : public QMainWindow
QMenuBar *menuBar;
QToolBar *mainToolBar;
QToolBar *graphicsToolBar;
QStatusBar *statusBar;
QAction *actionNew;
QAction *actionOpen;
QAction *actionSave;
QProgressBar *progressBar;
DesignWidget *designview;
FPGAViewWidget *fpgaView;
};
NEXTPNR_NAMESPACE_END

View File

@ -35,6 +35,7 @@ class ElementTreeItem : public QTreeWidgetItem
ElementTreeItem(ElementType t, QString str, QTreeWidgetItem *parent)
: QTreeWidgetItem(parent, QStringList(str)), type(t)
{
this->setFlags(this->flags() & ~Qt::ItemIsSelectable);
}
virtual ~ElementTreeItem(){};
@ -49,6 +50,7 @@ class IdStringTreeItem : public ElementTreeItem
public:
IdStringTreeItem(IdString d, ElementType t, QString str, QTreeWidgetItem *parent) : ElementTreeItem(t, str, parent)
{
this->setFlags(this->flags() | Qt::ItemIsSelectable);
this->data = d;
}
virtual ~IdStringTreeItem(){};
@ -68,6 +70,7 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), net
treeWidget->setColumnCount(1);
treeWidget->setHeaderLabel("Items");
treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
treeWidget->setSelectionMode(QAbstractItemView::ExtendedSelection);
// Add property view
variantManager = new QtVariantPropertyManager(this);
@ -79,6 +82,7 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), net
propertyEditor->setPropertiesWithoutValueMarked(true);
propertyEditor->show();
propertyEditor->treeWidget()->setContextMenuPolicy(Qt::CustomContextMenu);
propertyEditor->treeWidget()->setSelectionMode(QAbstractItemView::ExtendedSelection);
QLineEdit *lineEdit = new QLineEdit();
lineEdit->setClearButtonEnabled(true);
@ -229,6 +233,7 @@ void DesignWidget::addToHistory(QTreeWidgetItem *item)
void DesignWidget::newContext(Context *ctx)
{
highlightSelected.clear();
treeWidget->clear();
// reset pointers since they are not valid after clear
nets_root = nullptr;
@ -247,6 +252,7 @@ void DesignWidget::newContext(Context *ctx)
QTreeWidgetItem *bel_root = new QTreeWidgetItem(treeWidget);
QMap<QString, QTreeWidgetItem *> bel_items;
bel_root->setText(0, "Bels");
bel_root->setFlags(bel_root->flags() & ~Qt::ItemIsSelectable);
treeWidget->insertTopLevelItem(0, bel_root);
if (ctx) {
for (auto bel : ctx->getBels()) {
@ -279,6 +285,7 @@ void DesignWidget::newContext(Context *ctx)
QTreeWidgetItem *wire_root = new QTreeWidgetItem(treeWidget);
QMap<QString, QTreeWidgetItem *> wire_items;
wire_root->setText(0, "Wires");
wire_root->setFlags(wire_root->flags() & ~Qt::ItemIsSelectable);
treeWidget->insertTopLevelItem(0, wire_root);
if (ctx) {
for (auto wire : ctx->getWires()) {
@ -310,7 +317,9 @@ void DesignWidget::newContext(Context *ctx)
QTreeWidgetItem *pip_root = new QTreeWidgetItem(treeWidget);
QMap<QString, QTreeWidgetItem *> pip_items;
pip_root->setText(0, "Pips");
pip_root->setFlags(pip_root->flags() & ~Qt::ItemIsSelectable);
treeWidget->insertTopLevelItem(0, pip_root);
#ifndef ARCH_ECP5
if (ctx) {
for (auto pip : ctx->getPips()) {
auto id = ctx->getPipName(pip);
@ -337,54 +346,88 @@ void DesignWidget::newContext(Context *ctx)
for (auto pip : nameToItem[2].toStdMap()) {
pip_root->addChild(pip.second);
}
#endif
nets_root = new QTreeWidgetItem(treeWidget);
nets_root->setText(0, "Nets");
nets_root->setFlags(nets_root->flags() & ~Qt::ItemIsSelectable);
treeWidget->insertTopLevelItem(0, nets_root);
cells_root = new QTreeWidgetItem(treeWidget);
cells_root->setText(0, "Cells");
cells_root->setFlags(cells_root->flags() & ~Qt::ItemIsSelectable);
treeWidget->insertTopLevelItem(0, cells_root);
updateTree();
}
void DesignWidget::updateTree()
{
clearProperties();
delete nets_root;
delete cells_root;
nameToItem[3].clear();
nameToItem[4].clear();
if (!ctx)
return;
clearProperties();
// treeWidget->setSortingEnabled(false);
// Remove nets not existing any more
QMap<QString, QTreeWidgetItem *>::iterator i = nameToItem[3].begin();
while (i != nameToItem[3].end()) {
QMap<QString, QTreeWidgetItem *>::iterator prev = i;
++i;
if (ctx->nets.find(ctx->id(prev.key().toStdString())) == ctx->nets.end()) {
if (treeWidget->currentItem() == prev.value())
treeWidget->setCurrentItem(nets_root);
if (highlightSelected.contains(prev.value()))
highlightSelected.remove(prev.value());
delete prev.value();
nameToItem[3].erase(prev);
}
}
// Add nets to tree
nets_root = new QTreeWidgetItem(treeWidget);
nets_root->setText(0, "Nets");
treeWidget->insertTopLevelItem(0, nets_root);
if (ctx) {
for (auto &item : ctx->nets) {
auto id = item.first;
QString name = QString(id.c_str(ctx));
for (auto &item : ctx->nets) {
auto id = item.first;
QString name = QString(id.c_str(ctx));
if (!nameToItem[3].contains(name)) {
IdStringTreeItem *newItem = new IdStringTreeItem(id, ElementType::NET, name, nullptr);
nets_root->addChild(newItem);
nameToItem[3].insert(name, newItem);
}
}
for (auto item : nameToItem[3].toStdMap()) {
nets_root->addChild(item.second);
}
// Remove cells not existing any more
i = nameToItem[4].begin();
while (i != nameToItem[4].end()) {
QMap<QString, QTreeWidgetItem *>::iterator prev = i;
++i;
if (ctx->cells.find(ctx->id(prev.key().toStdString())) == ctx->cells.end()) {
if (treeWidget->currentItem() == prev.value())
treeWidget->setCurrentItem(cells_root);
if (highlightSelected.contains(prev.value()))
highlightSelected.remove(prev.value());
delete prev.value();
nameToItem[4].erase(prev);
}
}
// Add cells to tree
cells_root = new QTreeWidgetItem(treeWidget);
cells_root->setText(0, "Cells");
treeWidget->insertTopLevelItem(0, cells_root);
if (ctx) {
for (auto &item : ctx->cells) {
auto id = item.first;
QString name = QString(id.c_str(ctx));
for (auto &item : ctx->cells) {
auto id = item.first;
QString name = QString(id.c_str(ctx));
if (!nameToItem[4].contains(name)) {
IdStringTreeItem *newItem = new IdStringTreeItem(id, ElementType::CELL, name, nullptr);
cells_root->addChild(newItem);
nameToItem[4].insert(name, newItem);
}
}
for (auto item : nameToItem[4].toStdMap()) {
cells_root->addChild(item.second);
}
// treeWidget->sortByColumn(0, Qt::AscendingOrder);
// treeWidget->setSortingEnabled(true);
}
QtProperty *DesignWidget::addTopLevelProperty(const QString &id)
{
QtProperty *topItem = groupManager->addProperty(id);
propertyToId[topItem] = id;
idToProperty[id] = topItem;
topItem->setSelectable(false);
propertyEditor->addProperty(topItem);
return topItem;
}
@ -452,12 +495,14 @@ void DesignWidget::addProperty(QtProperty *topItem, int propertyType, const QStr
QtVariantProperty *item = readOnlyManager->addProperty(propertyType, name);
item->setValue(value);
item->setPropertyId(getElementTypeName(type));
item->setSelectable(type != ElementType::NONE);
topItem->addSubProperty(item);
}
QtProperty *DesignWidget::addSubGroup(QtProperty *topItem, const QString &name)
{
QtProperty *item = groupManager->addProperty(name);
item->setSelectable(false);
topItem->addSubProperty(item);
return item;
}
@ -467,6 +512,18 @@ void DesignWidget::onItemSelectionChanged()
if (treeWidget->selectedItems().size() == 0)
return;
if (treeWidget->selectedItems().size() > 1) {
std::vector<DecalXY> decals;
for (auto clickItem : treeWidget->selectedItems()) {
IdString value = static_cast<IdStringTreeItem *>(clickItem)->getData();
ElementType type = static_cast<ElementTreeItem *>(clickItem)->getType();
std::vector<DecalXY> d = getDecals(type, value);
std::move(d.begin(), d.end(), std::back_inserter(decals));
}
Q_EMIT selected(decals);
return;
}
QTreeWidgetItem *clickItem = treeWidget->selectedItems().at(0);
if (!clickItem->parent())
@ -510,6 +567,7 @@ void DesignWidget::onItemSelectionChanged()
QtProperty *topItem = addTopLevelProperty("Wire");
addProperty(topItem, QVariant::String, "Name", c.c_str(ctx));
addProperty(topItem, QVariant::String, "Type", ctx->getWireType(wire).c_str(ctx));
addProperty(topItem, QVariant::Bool, "Available", ctx->checkWireAvail(wire));
addProperty(topItem, QVariant::String, "Bound Net", ctx->getBoundWireNet(wire).c_str(ctx), ElementType::NET);
addProperty(topItem, QVariant::String, "Conflicting Net", ctx->getConflictingWireNet(wire).c_str(ctx),
@ -561,6 +619,7 @@ void DesignWidget::onItemSelectionChanged()
QtProperty *topItem = addTopLevelProperty("Pip");
addProperty(topItem, QVariant::String, "Name", c.c_str(ctx));
addProperty(topItem, QVariant::String, "Type", ctx->getPipType(pip).c_str(ctx));
addProperty(topItem, QVariant::Bool, "Available", ctx->checkPipAvail(pip));
addProperty(topItem, QVariant::String, "Bound Net", ctx->getBoundPipNet(pip).c_str(ctx), ElementType::NET);
addProperty(topItem, QVariant::String, "Conflicting Net", ctx->getConflictingPipNet(pip).c_str(ctx),
@ -718,52 +777,64 @@ std::vector<DecalXY> DesignWidget::getDecals(ElementType type, IdString value)
return decals;
}
void DesignWidget::updateHighlightGroup(QTreeWidgetItem *item, int group)
void DesignWidget::updateHighlightGroup(QList<QTreeWidgetItem *> items, int group)
{
if (highlightSelected.contains(item)) {
if (highlightSelected[item] == group) {
highlightSelected.remove(item);
const bool shouldClear = items.size() == 1;
for (auto item : items) {
if (highlightSelected.contains(item)) {
if (shouldClear && highlightSelected[item] == group) {
highlightSelected.remove(item);
} else
highlightSelected[item] = group;
} else
highlightSelected[item] = group;
} else
highlightSelected.insert(item, group);
std::vector<DecalXY> decals;
highlightSelected.insert(item, group);
}
std::vector<DecalXY> decals[8];
for (auto it : highlightSelected.toStdMap()) {
if (it.second == group) {
ElementType type = static_cast<ElementTreeItem *>(it.first)->getType();
IdString value = static_cast<IdStringTreeItem *>(it.first)->getData();
std::vector<DecalXY> d = getDecals(type, value);
std::move(d.begin(), d.end(), std::back_inserter(decals));
}
ElementType type = static_cast<ElementTreeItem *>(it.first)->getType();
IdString value = static_cast<IdStringTreeItem *>(it.first)->getData();
std::vector<DecalXY> d = getDecals(type, value);
std::move(d.begin(), d.end(), std::back_inserter(decals[it.second]));
}
Q_EMIT highlight(decals, group);
for (int i = 0; i < 8; i++)
Q_EMIT highlight(decals[i], i);
}
void DesignWidget::prepareMenuProperty(const QPoint &pos)
{
QTreeWidget *tree = propertyEditor->treeWidget();
itemContextMenu = tree->itemAt(pos);
if (itemContextMenu->parent() == nullptr)
return;
QtBrowserItem *browserItem = propertyEditor->itemToBrowserItem(itemContextMenu);
if (!browserItem)
return;
QtProperty *selectedProperty = browserItem->property();
ElementType type = getElementTypeByName(selectedProperty->propertyId());
if (type == ElementType::NONE)
return;
IdString value = ctx->id(selectedProperty->valueText().toStdString());
QTreeWidgetItem *item = nameToItem[getElementIndex(type)].value(value.c_str(ctx));
QList<QTreeWidgetItem *> items;
for (auto itemContextMenu : tree->selectedItems()) {
QtBrowserItem *browserItem = propertyEditor->itemToBrowserItem(itemContextMenu);
if (!browserItem)
continue;
QtProperty *selectedProperty = browserItem->property();
ElementType type = getElementTypeByName(selectedProperty->propertyId());
if (type == ElementType::NONE)
continue;
IdString value = ctx->id(selectedProperty->valueText().toStdString());
items.append(nameToItem[getElementIndex(type)].value(value.c_str(ctx)));
}
int selectedIndex = -1;
if (items.size() == 1) {
QTreeWidgetItem *item = items.at(0);
if (highlightSelected.contains(item))
selectedIndex = highlightSelected[item];
}
QMenu menu(this);
QAction *selectAction = new QAction("&Select", this);
connect(selectAction, &QAction::triggered, this, [this, type, value] { Q_EMIT selected(getDecals(type, value)); });
connect(selectAction, &QAction::triggered, this, [this, items] {
std::vector<DecalXY> decals;
for (auto clickItem : items) {
IdString value = static_cast<IdStringTreeItem *>(clickItem)->getData();
ElementType type = static_cast<ElementTreeItem *>(clickItem)->getType();
std::vector<DecalXY> d = getDecals(type, value);
std::move(d.begin(), d.end(), std::back_inserter(decals));
}
Q_EMIT selected(decals);
});
menu.addAction(selectAction);
QMenu *subMenu = menu.addMenu("Highlight");
@ -776,27 +847,24 @@ void DesignWidget::prepareMenuProperty(const QPoint &pos)
action->setCheckable(true);
subMenu->addAction(action);
group->addAction(action);
if (highlightSelected.contains(item) && highlightSelected[item] == i)
if (selectedIndex == i)
action->setChecked(true);
connect(action, &QAction::triggered, this, [this, i, item] { updateHighlightGroup(item, i); });
connect(action, &QAction::triggered, this, [this, i, items] { updateHighlightGroup(items, i); });
}
menu.exec(tree->mapToGlobal(pos));
}
void DesignWidget::prepareMenuTree(const QPoint &pos)
{
QTreeWidget *tree = treeWidget;
itemContextMenu = tree->itemAt(pos);
ElementType type = static_cast<ElementTreeItem *>(itemContextMenu)->getType();
IdString value = static_cast<IdStringTreeItem *>(itemContextMenu)->getData();
if (type == ElementType::NONE)
if (treeWidget->selectedItems().size() == 0)
return;
QTreeWidgetItem *item = nameToItem[getElementIndex(type)].value(value.c_str(ctx));
int selectedIndex = -1;
QList<QTreeWidgetItem *> items = treeWidget->selectedItems();
if (treeWidget->selectedItems().size() == 1) {
QTreeWidgetItem *item = treeWidget->selectedItems().at(0);
if (highlightSelected.contains(item))
selectedIndex = highlightSelected[item];
}
QMenu menu(this);
QMenu *subMenu = menu.addMenu("Highlight");
QActionGroup *group = new QActionGroup(this);
@ -808,11 +876,11 @@ void DesignWidget::prepareMenuTree(const QPoint &pos)
action->setCheckable(true);
subMenu->addAction(action);
group->addAction(action);
if (highlightSelected.contains(item) && highlightSelected[item] == i)
if (selectedIndex == i)
action->setChecked(true);
connect(action, &QAction::triggered, this, [this, i, item] { updateHighlightGroup(item, i); });
connect(action, &QAction::triggered, this, [this, i, items] { updateHighlightGroup(items, i); });
}
menu.exec(tree->mapToGlobal(pos));
menu.exec(treeWidget->mapToGlobal(pos));
}
void DesignWidget::onItemDoubleClicked(QTreeWidgetItem *item, int column)
@ -821,14 +889,8 @@ void DesignWidget::onItemDoubleClicked(QTreeWidgetItem *item, int column)
ElementType type = getElementTypeByName(selectedProperty->propertyId());
QString value = selectedProperty->valueText();
int index = getElementIndex(type);
switch (type) {
case ElementType::NONE:
return;
default: {
if (nameToItem[index].contains(value))
treeWidget->setCurrentItem(nameToItem[index].value(value));
} break;
}
if (type != ElementType::NONE && nameToItem[index].contains(value))
treeWidget->setCurrentItem(nameToItem[index].value(value));
}
NEXTPNR_NAMESPACE_END

View File

@ -60,7 +60,7 @@ class DesignWidget : public QWidget
void updateButtons();
void addToHistory(QTreeWidgetItem *item);
std::vector<DecalXY> getDecals(ElementType type, IdString value);
void updateHighlightGroup(QTreeWidgetItem *item, int group);
void updateHighlightGroup(QList<QTreeWidgetItem *> item, int group);
Q_SIGNALS:
void info(std::string text);
void selected(std::vector<DecalXY> decal);
@ -85,7 +85,6 @@ class DesignWidget : public QWidget
QtGroupPropertyManager *groupManager;
QtVariantEditorFactory *variantFactory;
QtTreePropertyBrowser *propertyEditor;
QTreeWidgetItem *itemContextMenu;
QMap<QtProperty *, QString> propertyToId;
QMap<QString, QtProperty *> idToProperty;

View File

@ -31,222 +31,15 @@
NEXTPNR_NAMESPACE_BEGIN
void PolyLine::buildPoint(LineShaderData *building, const QVector2D *prev, const QVector2D *cur,
const QVector2D *next) const
{
// buildPoint emits two vertices per line point, along with normals to move
// them the right directio when rendering and miter to compensate for
// bends.
if (cur == nullptr) {
// BUG
return;
}
if (prev == nullptr && next == nullptr) {
// BUG
return;
}
// TODO(q3k): fast path for vertical/horizontal lines?
// TODO(q3k): consider moving some of the linear algebra to the GPU,
// they're better at this than poor old CPUs.
// Build two unit vectors pointing in the direction of the two segments
// defined by (prev, cur) and (cur, next)
QVector2D dprev, dnext;
if (prev == nullptr) {
dnext = *next - *cur;
dprev = dnext;
} else if (next == nullptr) {
dprev = *cur - *prev;
dnext = dprev;
} else {
dprev = *cur - *prev;
dnext = *next - *cur;
}
dprev.normalize();
dnext.normalize();
// Calculate tangent unit vector.
QVector2D tangent(dprev + dnext);
tangent.normalize();
// Calculate normal to tangent - this is the line on which the vectors need
// to be pushed to build a thickened line.
const QVector2D tangent_normal = QVector2D(-tangent.y(), tangent.x());
// Calculate normal to one of the lines.
const QVector2D dprev_normal = QVector2D(-dprev.y(), dprev.x());
// https://people.eecs.berkeley.edu/~sequin/CS184/IMGS/Sweep_PolyLine.jpg
// (the ^-1 is performed in the shader)
const float miter = QVector2D::dotProduct(tangent_normal, dprev_normal);
const float x = cur->x();
const float y = cur->y();
const float mx = tangent_normal.x();
const float my = tangent_normal.y();
// Push back 'left' vertex.
building->vertices.push_back(Vertex2DPOD(x, y));
building->normals.push_back(Vertex2DPOD(mx, my));
building->miters.push_back(miter);
// Push back 'right' vertex.
building->vertices.push_back(Vertex2DPOD(x, y));
building->normals.push_back(Vertex2DPOD(mx, my));
building->miters.push_back(-miter);
}
void PolyLine::build(LineShaderData &target) const
{
if (points_.size() < 2) {
return;
}
const QVector2D *first = &points_.front();
const QVector2D *last = &points_.back();
// Index number of vertices, used to build the index buffer.
unsigned int startIndex = target.vertices.size();
unsigned int index = startIndex;
// For every point on the line, call buildPoint with (prev, point, next).
// If we're building a closed line, prev/next wrap around. Otherwise
// they are passed as nullptr and buildPoint interprets that accordinglu.
const QVector2D *prev = nullptr;
// Loop iterator used to ensure next is valid.
unsigned int i = 0;
for (const QVector2D &point : points_) {
const QVector2D *next = nullptr;
if (++i < points_.size()) {
next = (&point + 1);
}
// If the line is closed, wrap around. Otherwise, pass nullptr.
if (prev == nullptr && closed_) {
buildPoint(&target, last, &point, next);
} else if (next == nullptr && closed_) {
buildPoint(&target, prev, &point, first);
} else {
buildPoint(&target, prev, &point, next);
}
// If we have a prev point relative to cur, build a pair of triangles
// to render vertices into lines.
if (prev != nullptr) {
target.indices.push_back(index);
target.indices.push_back(index + 1);
target.indices.push_back(index + 2);
target.indices.push_back(index + 2);
target.indices.push_back(index + 1);
target.indices.push_back(index + 3);
index += 2;
}
prev = &point;
}
// If we're closed, build two more vertices that loop the line around.
if (closed_) {
target.indices.push_back(index);
target.indices.push_back(index + 1);
target.indices.push_back(startIndex);
target.indices.push_back(startIndex);
target.indices.push_back(index + 1);
target.indices.push_back(startIndex + 1);
}
}
bool LineShader::compile(void)
{
program_ = new QOpenGLShaderProgram(parent_);
program_->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource_);
program_->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource_);
if (!program_->link()) {
printf("could not link program: %s\n", program_->log().toStdString().c_str());
return false;
}
if (!vao_.create())
log_abort();
vao_.bind();
if (!buffers_.position.create())
log_abort();
if (!buffers_.normal.create())
log_abort();
if (!buffers_.miter.create())
log_abort();
if (!buffers_.index.create())
log_abort();
attributes_.position = program_->attributeLocation("position");
attributes_.normal = program_->attributeLocation("normal");
attributes_.miter = program_->attributeLocation("miter");
uniforms_.thickness = program_->uniformLocation("thickness");
uniforms_.projection = program_->uniformLocation("projection");
uniforms_.color = program_->uniformLocation("color");
vao_.release();
return true;
}
void LineShader::draw(const LineShaderData &line, const QColor &color, float thickness, const QMatrix4x4 &projection)
{
auto gl = QOpenGLContext::currentContext()->functions();
vao_.bind();
program_->bind();
buffers_.position.bind();
buffers_.position.allocate(&line.vertices[0], sizeof(Vertex2DPOD) * line.vertices.size());
buffers_.normal.bind();
buffers_.normal.allocate(&line.normals[0], sizeof(Vertex2DPOD) * line.normals.size());
buffers_.miter.bind();
buffers_.miter.allocate(&line.miters[0], sizeof(GLfloat) * line.miters.size());
buffers_.index.bind();
buffers_.index.allocate(&line.indices[0], sizeof(GLuint) * line.indices.size());
program_->setUniformValue(uniforms_.projection, projection);
program_->setUniformValue(uniforms_.thickness, thickness);
program_->setUniformValue(uniforms_.color, color.redF(), color.greenF(), color.blueF(), color.alphaF());
buffers_.position.bind();
program_->enableAttributeArray("position");
gl->glVertexAttribPointer(attributes_.position, 2, GL_FLOAT, GL_FALSE, 0, (void *)0);
buffers_.normal.bind();
program_->enableAttributeArray("normal");
gl->glVertexAttribPointer(attributes_.normal, 2, GL_FLOAT, GL_FALSE, 0, (void *)0);
buffers_.miter.bind();
program_->enableAttributeArray("miter");
gl->glVertexAttribPointer(attributes_.miter, 1, GL_FLOAT, GL_FALSE, 0, (void *)0);
buffers_.index.bind();
gl->glDrawElements(GL_TRIANGLES, line.indices.size(), GL_UNSIGNED_INT, (void *)0);
program_->disableAttributeArray("miter");
program_->disableAttributeArray("normal");
program_->disableAttributeArray("position");
program_->release();
vao_.release();
}
FPGAViewWidget::FPGAViewWidget(QWidget *parent)
: QOpenGLWidget(parent), lineShader_(this), zoom_(500.f), ctx_(nullptr), paintTimer_(this),
rendererData_(new FPGAViewWidget::RendererData), rendererArgs_(new FPGAViewWidget::RendererArgs)
FPGAViewWidget::FPGAViewWidget(QWidget *parent) :
QOpenGLWidget(parent), ctx_(nullptr), paintTimer_(this),
lineShader_(this), zoom_(500.0f),
rendererData_(new FPGAViewWidget::RendererData),
rendererArgs_(new FPGAViewWidget::RendererArgs)
{
colors_.background = QColor("#000000");
colors_.grid = QColor("#333");
colors_.frame = QColor("#d0d0d0");
colors_.frame = QColor("#808080");
colors_.hidden = QColor("#606060");
colors_.inactive = QColor("#303030");
colors_.active = QColor("#f0f0f0");
@ -289,6 +82,9 @@ FPGAViewWidget::~FPGAViewWidget() {}
void FPGAViewWidget::newContext(Context *ctx)
{
ctx_ = ctx;
onSelectedArchItem(std::vector<DecalXY>());
for (int i = 0; i < 8; i++)
onHighlightGroupChanged(std::vector<DecalXY>(), i);
pokeRenderer();
}
@ -306,69 +102,49 @@ void FPGAViewWidget::initializeGL()
0.0);
}
void FPGAViewWidget::drawDecal(LineShaderData &out, const DecalXY &decal)
void FPGAViewWidget::drawGraphicElement(LineShaderData &out, const GraphicElement &el, float x, float y)
{
const float scale = 1.0;
float offsetX = 0.0, offsetY = 0.0;
for (auto &el : ctx_->getDecalGraphics(decal.decal)) {
offsetX = decal.x;
offsetY = decal.y;
if (el.type == GraphicElement::TYPE_BOX) {
auto line = PolyLine(true);
line.point(x + scale * el.x1, y + scale * el.y1);
line.point(x + scale * el.x2, y + scale * el.y1);
line.point(x + scale * el.x2, y + scale * el.y2);
line.point(x + scale * el.x1, y + scale * el.y2);
line.build(out);
}
if (el.type == GraphicElement::G_BOX) {
auto line = PolyLine(true);
line.point(offsetX + scale * el.x1, offsetY + scale * el.y1);
line.point(offsetX + scale * el.x2, offsetY + scale * el.y1);
line.point(offsetX + scale * el.x2, offsetY + scale * el.y2);
line.point(offsetX + scale * el.x1, offsetY + scale * el.y2);
line.build(out);
}
if (el.type == GraphicElement::G_LINE) {
PolyLine(offsetX + scale * el.x1, offsetY + scale * el.y1, offsetX + scale * el.x2, offsetY + scale * el.y2)
.build(out);
}
if (el.type == GraphicElement::TYPE_LINE || el.type == GraphicElement::TYPE_ARROW) {
PolyLine(x + scale * el.x1, y + scale * el.y1, x + scale * el.x2, y + scale * el.y2)
.build(out);
}
}
void FPGAViewWidget::drawDecal(LineShaderData out[], const DecalXY &decal)
void FPGAViewWidget::drawDecal(LineShaderData &out, const DecalXY &decal)
{
const float scale = 1.0;
float offsetX = 0.0, offsetY = 0.0;
float offsetX = decal.x;
float offsetY = decal.y;
for (auto &el : ctx_->getDecalGraphics(decal.decal)) {
offsetX = decal.x;
offsetY = decal.y;
drawGraphicElement(out, el, offsetX, offsetY);
}
}
if (el.type == GraphicElement::G_BOX) {
auto line = PolyLine(true);
line.point(offsetX + scale * el.x1, offsetY + scale * el.y1);
line.point(offsetX + scale * el.x2, offsetY + scale * el.y1);
line.point(offsetX + scale * el.x2, offsetY + scale * el.y2);
line.point(offsetX + scale * el.x1, offsetY + scale * el.y2);
switch (el.style) {
case GraphicElement::G_FRAME:
case GraphicElement::G_INACTIVE:
case GraphicElement::G_ACTIVE:
line.build(out[el.style]);
break;
default:
break;
}
}
void FPGAViewWidget::drawArchDecal(LineShaderData out[GraphicElement::STYLE_MAX], const DecalXY &decal)
{
float offsetX = decal.x;
float offsetY = decal.y;
if (el.type == GraphicElement::G_LINE) {
auto line = PolyLine(offsetX + scale * el.x1, offsetY + scale * el.y1, offsetX + scale * el.x2,
offsetY + scale * el.y2);
switch (el.style) {
case GraphicElement::G_FRAME:
case GraphicElement::G_INACTIVE:
case GraphicElement::G_ACTIVE:
line.build(out[el.style]);
break;
default:
break;
}
for (auto &el : ctx_->getDecalGraphics(decal.decal)) {
switch (el.style) {
case GraphicElement::STYLE_FRAME:
case GraphicElement::STYLE_INACTIVE:
case GraphicElement::STYLE_ACTIVE:
drawGraphicElement(out[el.style], el, offsetX, offsetY);
break;
default:
break;
}
}
}
@ -398,24 +174,28 @@ void FPGAViewWidget::paintGL()
float thick1Px = mouseToWorldCoordinates(1, 0).x();
float thick11Px = mouseToWorldCoordinates(1.1, 0).x();
// Draw grid.
// Render grid.
auto grid = LineShaderData();
for (float i = -100.0f; i < 100.0f; i += 1.0f) {
PolyLine(-100.0f, i, 100.0f, i).build(grid);
PolyLine(i, -100.0f, i, 100.0f).build(grid);
}
// Draw grid.
lineShader_.draw(grid, colors_.grid, thick1Px, matrix);
rendererDataLock_.lock();
lineShader_.draw(rendererData_->decals[0], colors_.frame, thick11Px, matrix);
lineShader_.draw(rendererData_->decals[1], colors_.hidden, thick11Px, matrix);
lineShader_.draw(rendererData_->decals[2], colors_.inactive, thick11Px, matrix);
lineShader_.draw(rendererData_->decals[3], colors_.active, thick11Px, matrix);
// Render Arch graphics.
lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_FRAME], colors_.frame, thick11Px, matrix);
lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_HIDDEN], colors_.hidden, thick11Px, matrix);
lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_INACTIVE], colors_.inactive, thick11Px, matrix);
lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_ACTIVE], colors_.active, thick11Px, matrix);
// Draw highlighted items.
for (int i = 0; i < 8; i++)
lineShader_.draw(rendererData_->highlighted[i], colors_.highlight[i], thick11Px, matrix);
lineShader_.draw(rendererData_->gfxHighlighted[i], colors_.highlight[i], thick11Px, matrix);
lineShader_.draw(rendererData_->selected, colors_.selected, thick11Px, matrix);
lineShader_.draw(rendererData_->gfxSelected, colors_.selected, thick11Px, matrix);
rendererDataLock_.unlock();
}
@ -426,126 +206,154 @@ void FPGAViewWidget::renderLines(void)
if (ctx_ == nullptr)
return;
ctx_->lock_ui();
// For now, collapse any decal changes into change of all decals.
// TODO(q3k): fix this
bool decalsChanged = false;
if (ctx_->allUiReload) {
ctx_->allUiReload = false;
decalsChanged = true;
}
if (ctx_->frameUiReload) {
ctx_->frameUiReload = false;
decalsChanged = true;
}
if (ctx_->belUiReload.size() > 0) {
ctx_->belUiReload.clear();
decalsChanged = true;
}
if (ctx_->wireUiReload.size() > 0) {
ctx_->wireUiReload.clear();
decalsChanged = true;
}
if (ctx_->pipUiReload.size() > 0) {
ctx_->pipUiReload.clear();
decalsChanged = true;
}
if (ctx_->groupUiReload.size() > 0) {
ctx_->groupUiReload.clear();
decalsChanged = true;
}
// Local copy of decals, taken as fast as possible to not block the P&R.
// Data from Context needed to render all decals.
std::vector<DecalXY> belDecals;
std::vector<DecalXY> wireDecals;
std::vector<DecalXY> pipDecals;
std::vector<DecalXY> groupDecals;
if (decalsChanged) {
for (auto bel : ctx_->getBels()) {
belDecals.push_back(ctx_->getBelDecal(bel));
bool decalsChanged = false;
{
// Take the UI/Normal mutex on the Context, copy over all we need as
// fast as we can.
std::lock_guard<std::mutex> lock_ui(ctx_->ui_mutex);
std::lock_guard<std::mutex> lock(ctx_->mutex);
// For now, collapse any decal changes into change of all decals.
// TODO(q3k): fix this
if (ctx_->allUiReload) {
ctx_->allUiReload = false;
decalsChanged = true;
}
for (auto wire : ctx_->getWires()) {
wireDecals.push_back(ctx_->getWireDecal(wire));
if (ctx_->frameUiReload) {
ctx_->frameUiReload = false;
decalsChanged = true;
}
for (auto pip : ctx_->getPips()) {
pipDecals.push_back(ctx_->getPipDecal(pip));
if (ctx_->belUiReload.size() > 0) {
ctx_->belUiReload.clear();
decalsChanged = true;
}
for (auto group : ctx_->getGroups()) {
groupDecals.push_back(ctx_->getGroupDecal(group));
if (ctx_->wireUiReload.size() > 0) {
ctx_->wireUiReload.clear();
decalsChanged = true;
}
if (ctx_->pipUiReload.size() > 0) {
ctx_->pipUiReload.clear();
decalsChanged = true;
}
if (ctx_->groupUiReload.size() > 0) {
ctx_->groupUiReload.clear();
decalsChanged = true;
}
// Local copy of decals, taken as fast as possible to not block the P&R.
if (decalsChanged) {
for (auto bel : ctx_->getBels()) {
belDecals.push_back(ctx_->getBelDecal(bel));
}
for (auto wire : ctx_->getWires()) {
wireDecals.push_back(ctx_->getWireDecal(wire));
}
for (auto pip : ctx_->getPips()) {
pipDecals.push_back(ctx_->getPipDecal(pip));
}
for (auto group : ctx_->getGroups()) {
groupDecals.push_back(ctx_->getGroupDecal(group));
}
}
}
ctx_->unlock_ui();
rendererArgsLock_.lock();
auto selectedItems = rendererArgs_->selectedItems;
auto highlightedItems = rendererArgs_->highlightedItems;
auto highlightedOrSelectedChanged = rendererArgs_->highlightedOrSelectedChanged;
rendererArgs_->highlightedOrSelectedChanged = false;
rendererArgsLock_.unlock();
// Arguments from the main UI thread on what we should render.
std::vector<DecalXY> selectedDecals;
std::vector<DecalXY> highlightedDecals[8];
bool highlightedOrSelectedChanged;
{
// Take the renderer arguments lock, copy over all we need.
QMutexLocker lock(&rendererArgsLock_);
selectedDecals = rendererArgs_->selectedDecals;
for (int i = 0; i < 8; i++)
highlightedDecals[i] = rendererArgs_->highlightedDecals[i];
highlightedOrSelectedChanged = rendererArgs_->highlightedOrSelectedChanged;
rendererArgs_->highlightedOrSelectedChanged = false;
}
// Render decals if necessary.
if (decalsChanged) {
auto data = std::unique_ptr<FPGAViewWidget::RendererData>(new FPGAViewWidget::RendererData);
// Draw Bels.
for (auto const &decal : belDecals) {
drawDecal(data->decals, decal);
drawArchDecal(data->gfxByStyle, decal);
}
// Draw Wires.
for (auto const &decal : wireDecals) {
drawDecal(data->decals, decal);
drawArchDecal(data->gfxByStyle, decal);
}
// Draw Pips.
for (auto const &decal : pipDecals) {
drawDecal(data->decals, decal);
drawArchDecal(data->gfxByStyle, decal);
}
// Draw Groups.
for (auto const &decal : groupDecals) {
drawDecal(data->decals, decal);
drawArchDecal(data->gfxByStyle, decal);
}
// Swap over.
rendererDataLock_.lock();
rendererData_ = std::move(data);
rendererDataLock_.unlock();
{
QMutexLocker lock(&rendererDataLock_);
// If we're not re-rendering any highlights/selections, let's
// copy them over from teh current object.
if (!highlightedOrSelectedChanged) {
data->gfxSelected = rendererData_->gfxSelected;
for (int i = 0; i < 8; i++)
data->gfxHighlighted[i] = rendererData_->gfxHighlighted[i];
}
rendererData_ = std::move(data);
}
}
rendererDataLock_.lock();
if (decalsChanged || highlightedOrSelectedChanged) {
rendererData_->selected.clear();
for (auto &decal : selectedItems) {
drawDecal(rendererData_->selected, decal);
if (highlightedOrSelectedChanged) {
QMutexLocker locker(&rendererDataLock_);
// Render selected.
rendererData_->gfxSelected.clear();
for (auto &decal : selectedDecals) {
drawDecal(rendererData_->gfxSelected, decal);
}
// Render highlighted.
for (int i = 0; i < 8; i++) {
rendererData_->highlighted[i].clear();
for (auto &decal : highlightedItems[i]) {
drawDecal(rendererData_->highlighted[i], decal);
rendererData_->gfxHighlighted[i].clear();
for (auto &decal : highlightedDecals[i]) {
drawDecal(rendererData_->gfxHighlighted[i], decal);
}
}
}
rendererDataLock_.unlock();
}
void FPGAViewWidget::onSelectedArchItem(std::vector<DecalXY> decals)
{
rendererArgsLock_.lock();
rendererArgs_->selectedItems = decals;
rendererArgs_->highlightedOrSelectedChanged = true;
rendererArgsLock_.unlock();
{
QMutexLocker locker(&rendererArgsLock_);
rendererArgs_->selectedDecals = decals;
rendererArgs_->highlightedOrSelectedChanged = true;
}
pokeRenderer();
}
void FPGAViewWidget::onHighlightGroupChanged(std::vector<DecalXY> decals, int group)
{
rendererArgsLock_.lock();
rendererArgs_->highlightedItems[group] = decals;
rendererArgs_->highlightedOrSelectedChanged = true;
rendererArgsLock_.unlock();
{
QMutexLocker locker(&rendererArgsLock_);
rendererArgs_->highlightedDecals[group] = decals;
rendererArgs_->highlightedOrSelectedChanged = true;
}
pokeRenderer();
}
void FPGAViewWidget::resizeGL(int width, int height) {}
void FPGAViewWidget::mousePressEvent(QMouseEvent *event) { lastPos_ = event->pos(); }
void FPGAViewWidget::mousePressEvent(QMouseEvent *event) { lastDragPos_ = event->pos(); }
// Invert the projection matrix to calculate screen/mouse to world/grid
// coordinates.
@ -561,9 +369,9 @@ QVector4D FPGAViewWidget::mouseToWorldCoordinates(int x, int y)
void FPGAViewWidget::mouseMoveEvent(QMouseEvent *event)
{
const int dx = event->x() - lastPos_.x();
const int dy = event->y() - lastPos_.y();
lastPos_ = event->pos();
const int dx = event->x() - lastDragPos_.x();
const int dy = event->y() - lastDragPos_.y();
lastDragPos_ = event->pos();
auto world = mouseToWorldCoordinates(dx, dy);
viewMove_.translate(world.x(), -world.y());
@ -575,21 +383,32 @@ void FPGAViewWidget::wheelEvent(QWheelEvent *event)
{
QPoint degree = event->angleDelta() / 8;
if (!degree.isNull()) {
if (zoom_ < zoomNear_) {
zoom_ = zoomNear_;
} else if (zoom_ < zoomLvl1_) {
zoom_ -= degree.y() / 10.0;
} else if (zoom_ < zoomLvl2_) {
zoom_ -= degree.y() / 5.0;
} else if (zoom_ < zoomFar_) {
zoom_ -= degree.y();
} else {
zoom_ = zoomFar_;
}
update();
}
if (!degree.isNull())
zoom(degree.y());
}
void FPGAViewWidget::zoom(int level)
{
if (zoom_ < zoomNear_) {
zoom_ = zoomNear_;
} else if (zoom_ < zoomLvl1_) {
zoom_ -= level / 10.0;
} else if (zoom_ < zoomLvl2_) {
zoom_ -= level / 5.0;
} else if (zoom_ < zoomFar_) {
zoom_ -= level;
} else {
zoom_ = zoomFar_;
}
update();
}
void FPGAViewWidget::zoomIn() { zoom(10); }
void FPGAViewWidget::zoomOut() { zoom(-10); }
void FPGAViewWidget::zoomSelected() {}
void FPGAViewWidget::zoomOutbound() {}
NEXTPNR_NAMESPACE_END

View File

@ -33,183 +33,10 @@
#include <QWaitCondition>
#include "nextpnr.h"
#include "lineshader.h"
NEXTPNR_NAMESPACE_BEGIN
// Vertex2DPOD is a structure of X, Y coordinates that can be passed to OpenGL
// directly.
NPNR_PACKED_STRUCT(struct Vertex2DPOD {
GLfloat x;
GLfloat y;
Vertex2DPOD(GLfloat X, GLfloat Y) : x(X), y(Y) {}
});
// LineShaderData is a built set of vertices that can be rendered by the
// LineShader.
// Each LineShaderData can have its' own color and thickness.
struct LineShaderData
{
std::vector<Vertex2DPOD> vertices;
std::vector<Vertex2DPOD> normals;
std::vector<GLfloat> miters;
std::vector<GLuint> indices;
LineShaderData(void) {}
void clear(void)
{
vertices.clear();
normals.clear();
miters.clear();
indices.clear();
}
};
// PolyLine is a set of segments defined by points, that can be built to a
// ShaderLine for GPU rendering.
class PolyLine
{
private:
std::vector<QVector2D> points_;
bool closed_;
void buildPoint(LineShaderData *building, const QVector2D *prev, const QVector2D *cur, const QVector2D *next) const;
public:
// Create an empty PolyLine.
PolyLine(bool closed = false) : closed_(closed) {}
// Create a non-closed polyline consisting of one segment.
PolyLine(float x0, float y0, float x1, float y1) : closed_(false)
{
point(x0, y0);
point(x1, y1);
}
// Add a point to the PolyLine.
void point(float x, float y) { points_.push_back(QVector2D(x, y)); }
// Built PolyLine to shader data.
void build(LineShaderData &target) const;
// Set whether line is closed (ie. a loop).
void setClosed(bool closed) { closed_ = closed; }
};
// LineShader is an OpenGL shader program that renders LineShaderData on the
// GPU.
// The LineShader expects two vertices per line point. It will push those
// vertices along the given normal * miter. This is used to 'stretch' the line
// to be as wide as the given thickness. The normal and miter are calculated
// by the PolyLine build method in order to construct a constant thickness line
// with miter edge joints.
//
// +------+------+
//
// |
// PolyLine.build()
// |
// V
//
// ^ ^ ^
// | | | <--- normal vectors (x2, pointing in the same
// +/+----+/+----+/+ direction)
//
// |
// vertex shader
// |
// V
//
// +------+------+ ^ by normal * miter * thickness/2
// | | |
// +------+------+ V by normal * miter * thickness/2
//
// (miter is flipped for every second vertex generated)
class LineShader
{
private:
QObject *parent_;
QOpenGLShaderProgram *program_;
// GL attribute locations.
struct
{
// original position of line vertex
GLuint position;
// normal by which vertex should be translated
GLuint normal;
// scalar defining:
// - how stretched the normal vector should be to
// compensate for bends
// - which way the normal should be applied (+1 for one vertex, -1
// for the other)
GLuint miter;
} attributes_;
// GL buffers
struct
{
QOpenGLBuffer position;
QOpenGLBuffer normal;
QOpenGLBuffer miter;
QOpenGLBuffer index;
} buffers_;
// GL uniform locations.
struct
{
// combines m/v/p matrix to apply
GLuint projection;
// desired thickness of line
GLuint thickness;
// color of line
GLuint color;
} uniforms_;
QOpenGLVertexArrayObject vao_;
public:
LineShader(QObject *parent) : parent_(parent), program_(nullptr)
{
buffers_.position = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
buffers_.position.setUsagePattern(QOpenGLBuffer::StaticDraw);
buffers_.normal = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
buffers_.normal.setUsagePattern(QOpenGLBuffer::StaticDraw);
buffers_.miter = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
buffers_.miter.setUsagePattern(QOpenGLBuffer::StaticDraw);
buffers_.index = QOpenGLBuffer(QOpenGLBuffer::IndexBuffer);
buffers_.index.setUsagePattern(QOpenGLBuffer::StaticDraw);
}
static constexpr const char *vertexShaderSource_ =
"#version 110\n"
"attribute highp vec2 position;\n"
"attribute highp vec2 normal;\n"
"attribute highp float miter;\n"
"uniform highp float thickness;\n"
"uniform highp mat4 projection;\n"
"void main() {\n"
" vec2 p = position.xy + vec2(normal * thickness/2.0 / miter);\n"
" gl_Position = projection * vec4(p, 0.0, 1.0);\n"
"}\n";
static constexpr const char *fragmentShaderSource_ = "#version 110\n"
"uniform lowp vec4 color;\n"
"void main() {\n"
" gl_FragColor = color;\n"
"}\n";
// Must be called on initialization.
bool compile(void);
// Render a LineShaderData with a given M/V/P transformation.
void draw(const LineShaderData &data, const QColor &color, float thickness, const QMatrix4x4 &projection);
};
class PeriodicRunner : public QThread
{
Q_OBJECT
@ -267,53 +94,43 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions
FPGAViewWidget(QWidget *parent = 0);
~FPGAViewWidget();
QSize minimumSizeHint() const override;
QSize sizeHint() const override;
void setXTranslation(float t_x);
void setYTranslation(float t_y);
void setZoom(float t_z);
void xRotationChanged(int angle);
void yRotationChanged(int angle);
void zRotationChanged(int angle);
protected:
// Qt callbacks.
void initializeGL() Q_DECL_OVERRIDE;
void paintGL() Q_DECL_OVERRIDE;
void resizeGL(int width, int height) Q_DECL_OVERRIDE;
void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
void mouseMoveEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
void wheelEvent(QWheelEvent *event) Q_DECL_OVERRIDE;
void drawDecal(LineShaderData &data, const DecalXY &decal);
void drawDecal(LineShaderData out[], const DecalXY &decal);
QSize minimumSizeHint() const override;
QSize sizeHint() const override;
public Q_SLOTS:
void newContext(Context *ctx);
void onSelectedArchItem(std::vector<DecalXY> decals);
void onHighlightGroupChanged(std::vector<DecalXY> decals, int group);
void pokeRenderer(void);
void zoomIn();
void zoomOut();
void zoomSelected();
void zoomOutbound();
private:
void renderLines(void);
QPoint lastPos_;
LineShader lineShader_;
QMatrix4x4 viewMove_;
float zoom_;
QMatrix4x4 getProjection(void);
QVector4D mouseToWorldCoordinates(int x, int y);
const float zoomNear_ = 1.0f; // do not zoom closer than this
const float zoomFar_ = 10000.0f; // do not zoom further than this
const float zoomLvl1_ = 100.0f;
const float zoomLvl2_ = 50.0f;
Context *ctx_;
QTimer paintTimer_;
std::unique_ptr<PeriodicRunner> renderRunner_;
QPoint lastDragPos_;
LineShader lineShader_;
QMatrix4x4 viewMove_;
float zoom_;
struct
{
QColor background;
@ -328,22 +145,29 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions
struct RendererData
{
LineShaderData decals[4];
LineShaderData selected;
LineShaderData highlighted[8];
LineShaderData gfxByStyle[GraphicElement::STYLE_MAX];
LineShaderData gfxSelected;
LineShaderData gfxHighlighted[8];
};
std::unique_ptr<RendererData> rendererData_;
QMutex rendererDataLock_;
struct RendererArgs
{
std::vector<DecalXY> selectedItems;
std::vector<DecalXY> highlightedItems[8];
std::vector<DecalXY> selectedDecals;
std::vector<DecalXY> highlightedDecals[8];
bool highlightedOrSelectedChanged;
};
std::unique_ptr<RendererData> rendererData_;
QMutex rendererDataLock_;
std::unique_ptr<RendererArgs> rendererArgs_;
QMutex rendererArgsLock_;
void zoom(int level);
void renderLines(void);
void drawGraphicElement(LineShaderData &out, const GraphicElement &el, float x, float y);
void drawDecal(LineShaderData &out, const DecalXY &decal);
void drawArchDecal(LineShaderData out[GraphicElement::STYLE_MAX], const DecalXY &decal);
QVector4D mouseToWorldCoordinates(int x, int y);
QMatrix4x4 getProjection(void);
};
NEXTPNR_NAMESPACE_END

View File

@ -159,6 +159,8 @@ void MainWindow::createMenu()
taskToolBar->addAction(actionPlay);
taskToolBar->addAction(actionPause);
taskToolBar->addAction(actionStop);
createGraphicsBar();
}
#if defined(_MSC_VER)

View File

@ -109,7 +109,6 @@ void Worker::budget(double freq)
Q_EMIT taskStarted();
try {
ctx->target_freq = freq;
assign_budget(ctx);
Q_EMIT budget_finish(true);
} catch (WorkerInterruptionRequested) {
Q_EMIT taskCanceled();
@ -121,6 +120,8 @@ void Worker::place(bool timing_driven)
Q_EMIT taskStarted();
try {
ctx->timing_driven = timing_driven;
log_info("Assigned budget %0.2f MHz", ctx->target_freq / 1e6);
assign_budget(ctx);
Q_EMIT place_finished(ctx->place());
} catch (WorkerInterruptionRequested) {
Q_EMIT taskCanceled();

236
gui/lineshader.cc Normal file
View File

@ -0,0 +1,236 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Serge Bazanski <q3k@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 "log.h"
#include "lineshader.h"
NEXTPNR_NAMESPACE_BEGIN
void PolyLine::buildPoint(LineShaderData *building, const QVector2D *prev, const QVector2D *cur,
const QVector2D *next) const
{
// buildPoint emits two vertices per line point, along with normals to move
// them the right directio when rendering and miter to compensate for
// bends.
if (cur == nullptr) {
// BUG
return;
}
if (prev == nullptr && next == nullptr) {
// BUG
return;
}
// TODO(q3k): fast path for vertical/horizontal lines?
// TODO(q3k): consider moving some of the linear algebra to the GPU,
// they're better at this than poor old CPUs.
// Build two unit vectors pointing in the direction of the two segments
// defined by (prev, cur) and (cur, next)
QVector2D dprev, dnext;
if (prev == nullptr) {
dnext = *next - *cur;
dprev = dnext;
} else if (next == nullptr) {
dprev = *cur - *prev;
dnext = dprev;
} else {
dprev = *cur - *prev;
dnext = *next - *cur;
}
dprev.normalize();
dnext.normalize();
// Calculate tangent unit vector.
QVector2D tangent(dprev + dnext);
tangent.normalize();
// Calculate normal to tangent - this is the line on which the vectors need
// to be pushed to build a thickened line.
const QVector2D tangent_normal = QVector2D(-tangent.y(), tangent.x());
// Calculate normal to one of the lines.
const QVector2D dprev_normal = QVector2D(-dprev.y(), dprev.x());
// https://people.eecs.berkeley.edu/~sequin/CS184/IMGS/Sweep_PolyLine.jpg
// (the ^-1 is performed in the shader)
const float miter = QVector2D::dotProduct(tangent_normal, dprev_normal);
const float x = cur->x();
const float y = cur->y();
const float mx = tangent_normal.x();
const float my = tangent_normal.y();
// Push back 'left' vertex.
building->vertices.push_back(Vertex2DPOD(x, y));
building->normals.push_back(Vertex2DPOD(mx, my));
building->miters.push_back(miter);
// Push back 'right' vertex.
building->vertices.push_back(Vertex2DPOD(x, y));
building->normals.push_back(Vertex2DPOD(mx, my));
building->miters.push_back(-miter);
}
void PolyLine::build(LineShaderData &target) const
{
if (points_.size() < 2) {
return;
}
const QVector2D *first = &points_.front();
const QVector2D *last = &points_.back();
// Index number of vertices, used to build the index buffer.
unsigned int startIndex = target.vertices.size();
unsigned int index = startIndex;
// For every point on the line, call buildPoint with (prev, point, next).
// If we're building a closed line, prev/next wrap around. Otherwise
// they are passed as nullptr and buildPoint interprets that accordinglu.
const QVector2D *prev = nullptr;
// Loop iterator used to ensure next is valid.
unsigned int i = 0;
for (const QVector2D &point : points_) {
const QVector2D *next = nullptr;
if (++i < points_.size()) {
next = (&point + 1);
}
// If the line is closed, wrap around. Otherwise, pass nullptr.
if (prev == nullptr && closed_) {
buildPoint(&target, last, &point, next);
} else if (next == nullptr && closed_) {
buildPoint(&target, prev, &point, first);
} else {
buildPoint(&target, prev, &point, next);
}
// If we have a prev point relative to cur, build a pair of triangles
// to render vertices into lines.
if (prev != nullptr) {
target.indices.push_back(index);
target.indices.push_back(index + 1);
target.indices.push_back(index + 2);
target.indices.push_back(index + 2);
target.indices.push_back(index + 1);
target.indices.push_back(index + 3);
index += 2;
}
prev = &point;
}
// If we're closed, build two more vertices that loop the line around.
if (closed_) {
target.indices.push_back(index);
target.indices.push_back(index + 1);
target.indices.push_back(startIndex);
target.indices.push_back(startIndex);
target.indices.push_back(index + 1);
target.indices.push_back(startIndex + 1);
}
}
bool LineShader::compile(void)
{
program_ = new QOpenGLShaderProgram(parent_);
program_->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource_);
program_->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource_);
if (!program_->link()) {
printf("could not link program: %s\n", program_->log().toStdString().c_str());
return false;
}
if (!vao_.create())
log_abort();
vao_.bind();
if (!buffers_.position.create())
log_abort();
if (!buffers_.normal.create())
log_abort();
if (!buffers_.miter.create())
log_abort();
if (!buffers_.index.create())
log_abort();
attributes_.position = program_->attributeLocation("position");
attributes_.normal = program_->attributeLocation("normal");
attributes_.miter = program_->attributeLocation("miter");
uniforms_.thickness = program_->uniformLocation("thickness");
uniforms_.projection = program_->uniformLocation("projection");
uniforms_.color = program_->uniformLocation("color");
vao_.release();
return true;
}
void LineShader::draw(const LineShaderData &line, const QColor &color, float thickness, const QMatrix4x4 &projection)
{
auto gl = QOpenGLContext::currentContext()->functions();
if (line.vertices.size() == 0)
return;
vao_.bind();
program_->bind();
buffers_.position.bind();
buffers_.position.allocate(&line.vertices[0], sizeof(Vertex2DPOD) * line.vertices.size());
buffers_.normal.bind();
buffers_.normal.allocate(&line.normals[0], sizeof(Vertex2DPOD) * line.normals.size());
buffers_.miter.bind();
buffers_.miter.allocate(&line.miters[0], sizeof(GLfloat) * line.miters.size());
buffers_.index.bind();
buffers_.index.allocate(&line.indices[0], sizeof(GLuint) * line.indices.size());
program_->setUniformValue(uniforms_.projection, projection);
program_->setUniformValue(uniforms_.thickness, thickness);
program_->setUniformValue(uniforms_.color, color.redF(), color.greenF(), color.blueF(), color.alphaF());
buffers_.position.bind();
program_->enableAttributeArray("position");
gl->glVertexAttribPointer(attributes_.position, 2, GL_FLOAT, GL_FALSE, 0, (void *)0);
buffers_.normal.bind();
program_->enableAttributeArray("normal");
gl->glVertexAttribPointer(attributes_.normal, 2, GL_FLOAT, GL_FALSE, 0, (void *)0);
buffers_.miter.bind();
program_->enableAttributeArray("miter");
gl->glVertexAttribPointer(attributes_.miter, 1, GL_FLOAT, GL_FALSE, 0, (void *)0);
buffers_.index.bind();
gl->glDrawElements(GL_TRIANGLES, line.indices.size(), GL_UNSIGNED_INT, (void *)0);
program_->disableAttributeArray("miter");
program_->disableAttributeArray("normal");
program_->disableAttributeArray("position");
program_->release();
vao_.release();
}
NEXTPNR_NAMESPACE_END

209
gui/lineshader.h Normal file
View File

@ -0,0 +1,209 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Serge Bazanski <q3k@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 LINESHADER_H
#define LINESHADER_H
#include <QOpenGLBuffer>
#include <QOpenGLFunctions>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLWidget>
#include "nextpnr.h"
NEXTPNR_NAMESPACE_BEGIN
// Vertex2DPOD is a structure of X, Y coordinates that can be passed to OpenGL
// directly.
NPNR_PACKED_STRUCT(struct Vertex2DPOD {
GLfloat x;
GLfloat y;
Vertex2DPOD(GLfloat X, GLfloat Y) : x(X), y(Y) {}
});
// LineShaderData is a built set of vertices that can be rendered by the
// LineShader.
// Each LineShaderData can have its' own color and thickness.
struct LineShaderData
{
std::vector<Vertex2DPOD> vertices;
std::vector<Vertex2DPOD> normals;
std::vector<GLfloat> miters;
std::vector<GLuint> indices;
LineShaderData(void) {}
void clear(void)
{
vertices.clear();
normals.clear();
miters.clear();
indices.clear();
}
};
// PolyLine is a set of segments defined by points, that can be built to a
// ShaderLine for GPU rendering.
class PolyLine
{
private:
std::vector<QVector2D> points_;
bool closed_;
void buildPoint(LineShaderData *building, const QVector2D *prev, const QVector2D *cur, const QVector2D *next) const;
public:
// Create an empty PolyLine.
PolyLine(bool closed = false) : closed_(closed) {}
// Create a non-closed polyline consisting of one segment.
PolyLine(float x0, float y0, float x1, float y1) : closed_(false)
{
point(x0, y0);
point(x1, y1);
}
// Add a point to the PolyLine.
void point(float x, float y) { points_.push_back(QVector2D(x, y)); }
// Built PolyLine to shader data.
void build(LineShaderData &target) const;
// Set whether line is closed (ie. a loop).
void setClosed(bool closed) { closed_ = closed; }
};
// LineShader is an OpenGL shader program that renders LineShaderData on the
// GPU.
// The LineShader expects two vertices per line point. It will push those
// vertices along the given normal * miter. This is used to 'stretch' the line
// to be as wide as the given thickness. The normal and miter are calculated
// by the PolyLine build method in order to construct a constant thickness line
// with miter edge joints.
//
// +------+------+
//
// |
// PolyLine.build()
// |
// V
//
// ^ ^ ^
// | | | <--- normal vectors (x2, pointing in the same
// +/+----+/+----+/+ direction)
//
// |
// vertex shader
// |
// V
//
// +------+------+ ^ by normal * miter * thickness/2
// | | |
// +------+------+ V by normal * miter * thickness/2
//
// (miter is flipped for every second vertex generated)
class LineShader
{
private:
QObject *parent_;
QOpenGLShaderProgram *program_;
// GL attribute locations.
struct
{
// original position of line vertex
GLuint position;
// normal by which vertex should be translated
GLuint normal;
// scalar defining:
// - how stretched the normal vector should be to
// compensate for bends
// - which way the normal should be applied (+1 for one vertex, -1
// for the other)
GLuint miter;
} attributes_;
// GL buffers
struct
{
QOpenGLBuffer position;
QOpenGLBuffer normal;
QOpenGLBuffer miter;
QOpenGLBuffer index;
} buffers_;
// GL uniform locations.
struct
{
// combines m/v/p matrix to apply
GLuint projection;
// desired thickness of line
GLuint thickness;
// color of line
GLuint color;
} uniforms_;
QOpenGLVertexArrayObject vao_;
public:
LineShader(QObject *parent) : parent_(parent), program_(nullptr)
{
buffers_.position = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
buffers_.position.setUsagePattern(QOpenGLBuffer::StaticDraw);
buffers_.normal = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
buffers_.normal.setUsagePattern(QOpenGLBuffer::StaticDraw);
buffers_.miter = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
buffers_.miter.setUsagePattern(QOpenGLBuffer::StaticDraw);
buffers_.index = QOpenGLBuffer(QOpenGLBuffer::IndexBuffer);
buffers_.index.setUsagePattern(QOpenGLBuffer::StaticDraw);
}
static constexpr const char *vertexShaderSource_ =
"#version 110\n"
"attribute highp vec2 position;\n"
"attribute highp vec2 normal;\n"
"attribute highp float miter;\n"
"uniform highp float thickness;\n"
"uniform highp mat4 projection;\n"
"void main() {\n"
" vec2 p = position.xy + vec2(normal * thickness/2.0 / miter);\n"
" gl_Position = projection * vec4(p, 0.0, 1.0);\n"
"}\n";
static constexpr const char *fragmentShaderSource_ = "#version 110\n"
"uniform lowp vec4 color;\n"
"void main() {\n"
" gl_FragColor = color;\n"
"}\n";
// Must be called on initialization.
bool compile(void);
// Render a LineShaderData with a given M/V/P transformation.
void draw(const LineShaderData &data, const QColor &color, float thickness, const QMatrix4x4 &projection);
};
NEXTPNR_NAMESPACE_END
#endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

BIN
gui/resources/zoom_in.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 725 B

BIN
gui/resources/zoom_out.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 708 B

View File

@ -2,6 +2,7 @@
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com>
* Copyright (C) 2018 Serge Bazanski <q3k@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
@ -187,6 +188,13 @@ Arch::Arch(ArchArgs args) : args(args)
id_i3 = id("I3");
id_dff_en = id("DFF_ENABLE");
id_neg_clk = id("NEG_CLK");
id_cin = id("CIN");
id_cout = id("COUT");
id_o = id("O");
id_lo = id("LO");
id_icestorm_ram = id("ICESTORM_RAM");
id_rclk = id("RCLK");
id_wclk = id("WCLK");
}
// -----------------------------------------------------------------------
@ -291,23 +299,6 @@ BelRange Arch::getBelsByTile(int x, int y) const
return br;
}
BelRange Arch::getBelsAtSameTile(BelId bel) const
{
BelRange br;
NPNR_ASSERT(bel != BelId());
int x = chip_info->bel_data[bel.index].x;
int y = chip_info->bel_data[bel.index].y;
int start = bel.index, end = bel.index;
while (start >= 0 && chip_info->bel_data[start].x == x && chip_info->bel_data[start].y == y)
start--;
start++;
br.b.cursor = start;
while (end < chip_info->num_bels && chip_info->bel_data[end].x == x && chip_info->bel_data[end].y == y)
end++;
br.e.cursor = end;
return br;
}
PortType Arch::getBelPinType(BelId bel, PortPin pin) const
{
NPNR_ASSERT(bel != BelId());
@ -331,11 +322,12 @@ WireId Arch::getBelPinWire(BelId bel, PortPin pin) const
int num_bel_wires = chip_info->bel_data[bel.index].num_bel_wires;
const BelWirePOD *bel_wires = chip_info->bel_data[bel.index].bel_wires.get();
for (int i = 0; i < num_bel_wires; i++)
for (int i = 0; i < num_bel_wires; i++) {
if (bel_wires[i].port == pin) {
ret.index = bel_wires[i].wire_index;
break;
}
}
return ret;
}
@ -448,11 +440,103 @@ GroupId Arch::getGroupByName(IdString name) const
return GroupId();
}
IdString Arch::getGroupName(GroupId group) const { return IdString(); }
IdString Arch::getGroupName(GroupId group) const
{
std::string suffix;
switch (group.type) {
case GroupId::TYPE_FRAME:
suffix = "tile";
break;
case GroupId::TYPE_MAIN_SW:
suffix = "main_sw";
break;
case GroupId::TYPE_LOCAL_SW:
suffix = "local_sw";
break;
case GroupId::TYPE_LC0_SW:
suffix = "lc0_sw";
break;
case GroupId::TYPE_LC1_SW:
suffix = "lc1_sw";
break;
case GroupId::TYPE_LC2_SW:
suffix = "lc2_sw";
break;
case GroupId::TYPE_LC3_SW:
suffix = "lc3_sw";
break;
case GroupId::TYPE_LC4_SW:
suffix = "lc4_sw";
break;
case GroupId::TYPE_LC5_SW:
suffix = "lc5_sw";
break;
case GroupId::TYPE_LC6_SW:
suffix = "lc6_sw";
break;
case GroupId::TYPE_LC7_SW:
suffix = "lc7_sw";
break;
default:
return IdString();
}
return id("X" + std::to_string(group.x) + "/Y" + std::to_string(group.y) + "/" + suffix);
}
std::vector<GroupId> Arch::getGroups() const
{
std::vector<GroupId> ret;
for (int y = 0; y < chip_info->height; y++) {
for (int x = 0; x < chip_info->width; x++) {
TileType type = chip_info->tile_grid[y * chip_info->width + x];
if (type == TILE_NONE)
continue;
GroupId group;
group.type = GroupId::TYPE_FRAME;
group.x = x;
group.y = y;
// ret.push_back(group);
group.type = GroupId::TYPE_MAIN_SW;
ret.push_back(group);
group.type = GroupId::TYPE_LOCAL_SW;
ret.push_back(group);
#if 0
if (type == TILE_LOGIC)
{
group.type = GroupId::TYPE_LC0_SW;
ret.push_back(group);
group.type = GroupId::TYPE_LC1_SW;
ret.push_back(group);
group.type = GroupId::TYPE_LC2_SW;
ret.push_back(group);
group.type = GroupId::TYPE_LC3_SW;
ret.push_back(group);
group.type = GroupId::TYPE_LC4_SW;
ret.push_back(group);
group.type = GroupId::TYPE_LC5_SW;
ret.push_back(group);
group.type = GroupId::TYPE_LC6_SW;
ret.push_back(group);
group.type = GroupId::TYPE_LC7_SW;
ret.push_back(group);
}
#endif
}
}
return ret;
}
@ -482,14 +566,6 @@ std::vector<GroupId> Arch::getGroupGroups(GroupId group) const
// -----------------------------------------------------------------------
void Arch::estimatePosition(BelId bel, int &x, int &y, bool &gb) const
{
NPNR_ASSERT(bel != BelId());
x = chip_info->bel_data[bel.index].x;
y = chip_info->bel_data[bel.index].y;
gb = chip_info->bel_data[bel.index].type == TYPE_SB_GB;
}
delay_t Arch::estimateDelay(WireId src, WireId dst) const
{
NPNR_ASSERT(src != WireId());
@ -511,10 +587,18 @@ delay_t Arch::estimateDelay(WireId src, WireId dst) const
return xscale * abs(xd) + yscale * abs(yd) + offset;
}
delay_t Arch::getBudgetOverride(const PortRef& pr, delay_t v) const
delay_t Arch::getBudgetOverride(NetInfo *net_info, int user_idx, delay_t budget) const
{
if (pr.port == id("COUT")) return 0;
return v;
const auto& driver = net_info->driver;
if (driver.port == id_cout) {
const auto& sink = net_info->users[user_idx];
auto driver_loc = getBelLocation(driver.cell->bel);
auto sink_loc = getBelLocation(sink.cell->bel);
if (driver_loc.y == sink_loc.y)
return 0;
return 250;
}
return budget;
}
// -----------------------------------------------------------------------
@ -574,22 +658,70 @@ std::vector<GraphicElement> Arch::getDecalGraphics(DecalId decal) const
std::vector<GraphicElement> ret;
if (decal.type == DecalId::TYPE_FRAME) {
for (int x = 0; x <= chip_info->width; x++)
for (int y = 0; y <= chip_info->height; y++) {
GraphicElement el;
el.type = GraphicElement::G_LINE;
el.x1 = x - 0.05, el.x2 = x + 0.05, el.y1 = y, el.y2 = y, el.z = 0;
ret.push_back(el);
el.x1 = x, el.x2 = x, el.y1 = y - 0.05, el.y2 = y + 0.05, el.z = 0;
ret.push_back(el);
}
/* nothing */
}
if (decal.type == DecalId::TYPE_GROUP) {
int type = (decal.index >> 16) & 255;
int x = (decal.index >> 8) & 255;
int y = decal.index & 255;
if (type == GroupId::TYPE_FRAME) {
GraphicElement el;
el.type = GraphicElement::TYPE_LINE;
el.style = GraphicElement::STYLE_FRAME;
el.x1 = x + 0.01, el.x2 = x + 0.02, el.y1 = y + 0.01, el.y2 = y + 0.01;
ret.push_back(el);
el.x1 = x + 0.01, el.x2 = x + 0.01, el.y1 = y + 0.01, el.y2 = y + 0.02;
ret.push_back(el);
el.x1 = x + 0.99, el.x2 = x + 0.98, el.y1 = y + 0.01, el.y2 = y + 0.01;
ret.push_back(el);
el.x1 = x + 0.99, el.x2 = x + 0.99, el.y1 = y + 0.01, el.y2 = y + 0.02;
ret.push_back(el);
el.x1 = x + 0.99, el.x2 = x + 0.98, el.y1 = y + 0.99, el.y2 = y + 0.99;
ret.push_back(el);
el.x1 = x + 0.99, el.x2 = x + 0.99, el.y1 = y + 0.99, el.y2 = y + 0.98;
ret.push_back(el);
el.x1 = x + 0.01, el.x2 = x + 0.02, el.y1 = y + 0.99, el.y2 = y + 0.99;
ret.push_back(el);
el.x1 = x + 0.01, el.x2 = x + 0.01, el.y1 = y + 0.99, el.y2 = y + 0.98;
ret.push_back(el);
}
if (type == GroupId::TYPE_MAIN_SW) {
GraphicElement el;
el.type = GraphicElement::TYPE_BOX;
el.style = GraphicElement::STYLE_FRAME;
el.x1 = x + main_swbox_x1;
el.x2 = x + main_swbox_x2;
el.y1 = y + main_swbox_y1;
el.y2 = y + main_swbox_y2;
ret.push_back(el);
}
if (type == GroupId::TYPE_LOCAL_SW) {
GraphicElement el;
el.type = GraphicElement::TYPE_BOX;
el.style = GraphicElement::STYLE_FRAME;
el.x1 = x + local_swbox_x1;
el.x2 = x + local_swbox_x2;
el.y1 = y + local_swbox_y1;
el.y2 = y + local_swbox_y2;
ret.push_back(el);
}
}
if (decal.type == DecalId::TYPE_WIRE) {
int n = chip_info->wire_data[decal.index].num_segments;
const WireSegmentPOD *p = chip_info->wire_data[decal.index].segments.get();
GraphicElement::style_t style = decal.active ? GraphicElement::G_ACTIVE : GraphicElement::G_INACTIVE;
GraphicElement::style_t style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_INACTIVE;
for (int i = 0; i < n; i++)
gfxTileWire(ret, p[i].x, p[i].y, GfxTileWireId(p[i].index), style);
@ -597,7 +729,7 @@ std::vector<GraphicElement> Arch::getDecalGraphics(DecalId decal) const
if (decal.type == DecalId::TYPE_PIP) {
const PipInfoPOD &p = chip_info->pip_data[decal.index];
GraphicElement::style_t style = decal.active ? GraphicElement::G_ACTIVE : GraphicElement::G_HIDDEN;
GraphicElement::style_t style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_HIDDEN;
gfxTilePip(ret, p.x, p.y, GfxTileWireId(p.src_seg), GfxTileWireId(p.dst_seg), style);
}
@ -609,111 +741,40 @@ std::vector<GraphicElement> Arch::getDecalGraphics(DecalId decal) const
if (bel_type == TYPE_ICESTORM_LC) {
GraphicElement el;
el.type = GraphicElement::G_BOX;
el.style = decal.active ? GraphicElement::G_ACTIVE : GraphicElement::G_INACTIVE;
el.type = GraphicElement::TYPE_BOX;
el.style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_INACTIVE;
el.x1 = chip_info->bel_data[bel.index].x + logic_cell_x1;
el.x2 = chip_info->bel_data[bel.index].x + logic_cell_x2;
el.y1 = chip_info->bel_data[bel.index].y + logic_cell_y1 +
(chip_info->bel_data[bel.index].z) * logic_cell_pitch;
el.y2 = chip_info->bel_data[bel.index].y + logic_cell_y2 +
(chip_info->bel_data[bel.index].z) * logic_cell_pitch;
el.z = 0;
ret.push_back(el);
if (chip_info->bel_data[bel.index].z == 0) {
int tx = chip_info->bel_data[bel.index].x;
int ty = chip_info->bel_data[bel.index].y;
// Main switchbox
GraphicElement main_sw;
main_sw.type = GraphicElement::G_BOX;
main_sw.style = GraphicElement::G_FRAME;
main_sw.x1 = tx + main_swbox_x1;
main_sw.x2 = tx + main_swbox_x2;
main_sw.y1 = ty + main_swbox_y1;
main_sw.y2 = ty + main_swbox_y2;
ret.push_back(main_sw);
// Local tracks to LUT input switchbox
GraphicElement local_sw;
local_sw.type = GraphicElement::G_BOX;
local_sw.style = GraphicElement::G_FRAME;
local_sw.x1 = tx + local_swbox_x1;
local_sw.x2 = tx + local_swbox_x2;
local_sw.y1 = ty + local_swbox_y1;
local_sw.y2 = ty + local_swbox_y2;
local_sw.z = 0;
ret.push_back(local_sw);
}
}
if (bel_type == TYPE_SB_IO) {
if (chip_info->bel_data[bel.index].x == 0 || chip_info->bel_data[bel.index].x == chip_info->width - 1) {
GraphicElement el;
el.type = GraphicElement::G_BOX;
el.x1 = chip_info->bel_data[bel.index].x + 0.1;
el.x2 = chip_info->bel_data[bel.index].x + 0.9;
if (chip_info->bel_data[bel.index].z == 0) {
el.y1 = chip_info->bel_data[bel.index].y + 0.10;
el.y2 = chip_info->bel_data[bel.index].y + 0.45;
} else {
el.y1 = chip_info->bel_data[bel.index].y + 0.55;
el.y2 = chip_info->bel_data[bel.index].y + 0.90;
}
el.z = 0;
ret.push_back(el);
} else {
GraphicElement el;
el.type = GraphicElement::G_BOX;
if (chip_info->bel_data[bel.index].z == 0) {
el.x1 = chip_info->bel_data[bel.index].x + 0.10;
el.x2 = chip_info->bel_data[bel.index].x + 0.45;
} else {
el.x1 = chip_info->bel_data[bel.index].x + 0.55;
el.x2 = chip_info->bel_data[bel.index].x + 0.90;
}
el.y1 = chip_info->bel_data[bel.index].y + 0.1;
el.y2 = chip_info->bel_data[bel.index].y + 0.9;
el.z = 0;
ret.push_back(el);
}
GraphicElement el;
el.type = GraphicElement::TYPE_BOX;
el.style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_INACTIVE;
el.x1 = chip_info->bel_data[bel.index].x + logic_cell_x1;
el.x2 = chip_info->bel_data[bel.index].x + logic_cell_x2;
el.y1 = chip_info->bel_data[bel.index].y + logic_cell_y1 +
(4 * chip_info->bel_data[bel.index].z) * logic_cell_pitch;
el.y2 = chip_info->bel_data[bel.index].y + logic_cell_y2 +
(4 * chip_info->bel_data[bel.index].z + 3) * logic_cell_pitch;
ret.push_back(el);
}
if (bel_type == TYPE_ICESTORM_RAM) {
for (int i = 0; i < 2; i++) {
int tx = chip_info->bel_data[bel.index].x;
int ty = chip_info->bel_data[bel.index].y + i;
GraphicElement el;
el.type = GraphicElement::G_BOX;
el.style = decal.active ? GraphicElement::G_ACTIVE : GraphicElement::G_INACTIVE;
el.type = GraphicElement::TYPE_BOX;
el.style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_INACTIVE;
el.x1 = chip_info->bel_data[bel.index].x + logic_cell_x1;
el.x2 = chip_info->bel_data[bel.index].x + logic_cell_x2;
el.y1 = chip_info->bel_data[bel.index].y + logic_cell_y1;
el.y2 = chip_info->bel_data[bel.index].y + logic_cell_y2 + 7 * logic_cell_pitch;
el.z = 0;
el.y1 = chip_info->bel_data[bel.index].y + logic_cell_y1 + i;
el.y2 = chip_info->bel_data[bel.index].y + logic_cell_y2 + i + 7 * logic_cell_pitch;
ret.push_back(el);
// Main switchbox
GraphicElement main_sw;
main_sw.type = GraphicElement::G_BOX;
main_sw.style = GraphicElement::G_FRAME;
main_sw.x1 = tx + main_swbox_x1;
main_sw.x2 = tx + main_swbox_x2;
main_sw.y1 = ty + main_swbox_y1;
main_sw.y2 = ty + main_swbox_y2;
ret.push_back(main_sw);
// Local tracks to LUT input switchbox
GraphicElement local_sw;
local_sw.type = GraphicElement::G_BOX;
local_sw.style = GraphicElement::G_FRAME;
local_sw.x1 = tx + local_swbox_x1;
local_sw.x2 = tx + local_swbox_x2;
local_sw.y1 = ty + local_swbox_y1;
local_sw.y2 = ty + local_swbox_y2;
local_sw.z = 0;
ret.push_back(local_sw);
}
}
}
@ -725,20 +786,28 @@ std::vector<GraphicElement> Arch::getDecalGraphics(DecalId decal) const
bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, delay_t &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"))) {
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;
return true;
} else if (fromPort == id("CIN") && toPort == id("COUT")) {
} else if (fromPort == id_cin && toPort == id_cout) {
delay = 120;
return true;
} else if (fromPort == id("I1") && toPort == id("COUT")) {
} else if (fromPort == id_i1 && toPort == id_cout) {
delay = 260;
return true;
} else if (fromPort == id("I2") && toPort == id("COUT")) {
} else if (fromPort == id_i2 && toPort == id_cout) {
delay = 230;
return true;
} else if (fromPort == id_clk && toPort == id_o) {
delay = 540;
return true;
}
} else if (cell->type == id_icestorm_ram) {
if (fromPort == id_rclk) {
delay = 2140;
return true;
}
}
return false;
@ -746,9 +815,14 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort
IdString Arch::getPortClock(const CellInfo *cell, IdString port) const
{
if (cell->type == id("ICESTORM_LC") && bool_or_default(cell->params, id("DFF_ENABLE"))) {
if (port != id("LO") && port != id("CIN") && port != id("COUT"))
return id("CLK");
if (cell->type == id_icestorm_lc && cell->lcInfo.dffEnable) {
if (port != id_lo && port != id_cin && port != id_cout)
return id_clk;
} else if (cell->type == id_icestorm_ram) {
if (port.str(this)[0] == 'R')
return id_rclk;
else
return id_wclk;
}
return IdString();
}
@ -757,6 +831,8 @@ bool Arch::isClockPort(const CellInfo *cell, IdString port) const
{
if (cell->type == id("ICESTORM_LC") && port == id("CLK"))
return true;
if (cell->type == id("ICESTORM_RAM") && (port == id("RCLK") || (port == id("WCLK"))))
return true;
return false;
}

View File

@ -83,10 +83,6 @@ NPNR_PACKED_STRUCT(struct WireInfoPOD {
int32_t num_uphill, num_downhill;
RelPtr<int32_t> pips_uphill, pips_downhill;
int32_t num_bels_downhill;
BelPortPOD bel_uphill;
RelPtr<BelPortPOD> bels_downhill;
int32_t num_bel_pins;
RelPtr<BelPortPOD> bel_pins;
@ -453,8 +449,6 @@ struct Arch : BaseCtx
bool getBelGlobalBuf(BelId bel) const { return chip_info->bel_data[bel.index].type == TYPE_SB_GB; }
BelRange getBelsAtSameTile(BelId bel) const NPNR_DEPRECATED;
BelType getBelType(BelId bel) const
{
NPNR_ASSERT(bel != BelId());
@ -475,6 +469,8 @@ struct Arch : BaseCtx
return id(chip_info->wire_data[wire.index].name.get());
}
IdString getWireType(WireId wire) const { return IdString(); }
uint32_t getWireChecksum(WireId wire) const { return wire.index; }
void bindWire(WireId wire, IdString net, PlaceStrength strength)
@ -617,6 +613,8 @@ struct Arch : BaseCtx
IdString getPipName(PipId pip) const;
IdString getPipType(PipId pip) const { return IdString(); }
uint32_t getPipChecksum(PipId pip) const { return pip.index; }
WireId getPipSrcWire(PipId pip) const
@ -685,13 +683,12 @@ struct Arch : BaseCtx
// -------------------------------------------------
void estimatePosition(BelId bel, int &x, int &y, bool &gb) const NPNR_DEPRECATED;
delay_t estimateDelay(WireId src, WireId dst) const;
delay_t getDelayEpsilon() const { return 20; }
delay_t getRipupDelayPenalty() const { return 200; }
float getDelayNS(delay_t v) const { return v * 0.001; }
uint32_t getDelayChecksum(delay_t v) const { return v; }
delay_t getBudgetOverride(const PortRef& pr, delay_t v) const;
delay_t getBudgetOverride(NetInfo *net_info, int user_idx, delay_t budget) const;
// -------------------------------------------------
@ -747,6 +744,21 @@ struct Arch : BaseCtx
IdString id_cen, id_clk, id_sr;
IdString id_i0, id_i1, id_i2, id_i3;
IdString id_dff_en, id_neg_clk;
IdString id_cin, id_cout;
IdString id_o, id_lo;
IdString id_icestorm_ram, id_rclk, id_wclk;
// -------------------------------------------------
BelPin getIOBSharingPLLPin(BelId pll, PortPin pll_pin) const
{
auto wire = getBelPinWire(pll, pll_pin);
for (auto src_bel : getWireBelPins(wire)) {
if (getBelType(src_bel.bel) == TYPE_SB_IO && src_bel.pin == PIN_D_IN_0) {
return src_bel;
}
}
NPNR_ASSERT_FALSE("Expected PLL pin to share an output with an SB_IO D_IN_{0,1}");
}
};
NEXTPNR_NAMESPACE_END

View File

@ -3,6 +3,7 @@
*
* Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com>
* Copyright (C) 2018 David Shah <david@symbioticeda.com>
* Copyright (C) 2018 Serge Bazanski <q3k@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
@ -71,7 +72,8 @@ bool Arch::isBelLocationValid(BelId bel) const
{
if (getBelType(bel) == TYPE_ICESTORM_LC) {
std::vector<const CellInfo *> bel_cells;
for (auto bel_other : getBelsAtSameTile(bel)) {
Loc bel_loc = getBelLocation(bel);
for (auto bel_other : getBelsByTile(bel_loc.x, bel_loc.y)) {
IdString cell_other = getBoundBelCell(bel_other);
if (cell_other != IdString()) {
const CellInfo *ci_other = cells.at(cell_other).get();
@ -94,8 +96,8 @@ bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const
NPNR_ASSERT(getBelType(bel) == TYPE_ICESTORM_LC);
std::vector<const CellInfo *> bel_cells;
for (auto bel_other : getBelsAtSameTile(bel)) {
Loc bel_loc = getBelLocation(bel);
for (auto bel_other : getBelsByTile(bel_loc.x, bel_loc.y)) {
IdString cell_other = getBoundBelCell(bel_other);
if (cell_other != IdString() && bel_other != bel) {
const CellInfo *ci_other = cells.at(cell_other).get();
@ -106,6 +108,36 @@ bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const
bel_cells.push_back(cell);
return logicCellsCompatible(bel_cells);
} else if (cell->type == id_sb_io) {
// Do not allow placement of input SB_IOs on blocks where there a PLL is outputting to.
// Find shared PLL by looking for driving bel siblings from D_IN_0
// that are a PLL clock output.
auto wire = getBelPinWire(bel, PIN_D_IN_0);
PortPin pll_bel_pin;
BelId pll_bel;
for (auto pin : getWireBelPins(wire)) {
if (pin.pin == PIN_PLLOUT_A || pin.pin == PIN_PLLOUT_B) {
pll_bel = pin.bel;
pll_bel_pin = pin.pin;
break;
}
}
// Is there a PLL that shares this IO buffer?
if (pll_bel.index != -1) {
auto pll_cell = getBoundBelCell(pll_bel);
// Is a PLL placed in this PLL bel?
if (pll_cell != IdString()) {
// Is the shared port driving a net?
auto pi = cells.at(pll_cell)->ports[portPinToId(pll_bel_pin)];
if (pi.net != nullptr) {
// Are we perhaps a PAD INPUT Bel that can be placed here?
if (cells.at(pll_cell)->attrs[id("BEL_PAD_INPUT")] == getBelName(bel).str(this)) {
return true;
}
return false;
}
}
}
return getBelPackagePin(bel) != "";
} else if (cell->type == id_sb_gb) {
NPNR_ASSERT(cell->ports.at(id_glb_buf_out).net != nullptr);

View File

@ -79,8 +79,6 @@ void arch_wrap_python()
conv_to_str<IdString>, conv_from_str<BelId>>::def_wrap(ctx_cls, "getConflictingBelCell");
fn_wrapper_0a<Context, decltype(&Context::getBels), &Context::getBels, wrap_context<BelRange>>::def_wrap(ctx_cls,
"getBels");
fn_wrapper_1a<Context, decltype(&Context::getBelsAtSameTile), &Context::getBelsAtSameTile, wrap_context<BelRange>,
conv_from_str<BelId>>::def_wrap(ctx_cls, "getBelsAtSameTile");
fn_wrapper_2a<Context, decltype(&Context::getBelPinWire), &Context::getBelPinWire, conv_to_str<WireId>,
conv_from_str<BelId>, conv_from_str<PortPin>>::def_wrap(ctx_cls, "getBelPinWire");

6
ice40/benchmark/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
hx8kdemo.log
hx8kdemo.blif
hx8kdemo.json
hx8kdemo_[an][0-9].asc
hx8kdemo_[an][0-9].log
report_[an][0-9].txt

27
ice40/benchmark/Makefile Normal file
View File

@ -0,0 +1,27 @@
reports::
define mkreport
reports:: report_a$1.txt report_n$1.txt
report_a$1.txt: hx8kdemo_a$1.asc
icetime -m -r report_a$1.txt -d hx8k hx8kdemo_a$1.asc
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
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
endef
$(foreach i,0 1 2 3 4 5 6 7 8 9,$(eval $(call mkreport,$(i))))
hx8kdemo.blif: hx8kdemo.json
hx8kdemo.json: hx8kdemo.v spimemio.v simpleuart.v picosoc.v picorv32.v
yosys -ql hx8kdemo.log -p 'synth_ice40 -top hx8kdemo -blif hx8kdemo.blif -json hx8kdemo.json' $^
clean:
rm -f hx8kdemo.log hx8kdemo.blif hx8kdemo.json
rm -f hx8kdemo_[an][0-9].asc hx8kdemo_[an][0-9].log report_[an][0-9].txt

View File

@ -0,0 +1,40 @@
# Pinout for the iCE40-HX8K Breakout Board
set_io clk J3
set_io flash_csb R12
set_io flash_clk R11
set_io flash_io0 P12
set_io flash_io1 P11
# for QSPI mode the flash chip on the iCE40-HX8K Breakout Board
# must be replaced with one that supports QSPI and the IO2 and IO3
# pins must be soldered to T9 and P8 (center on J3)
set_io flash_io2 T9
set_io flash_io3 P8
set_io ser_tx B12
set_io ser_rx B10
# left on J3
set_io debug_ser_tx T1
set_io debug_ser_rx R3
# right on J3
set_io debug_flash_csb T15
set_io debug_flash_clk R16
set_io debug_flash_io0 N12
set_io debug_flash_io1 P13
set_io debug_flash_io2 T13
set_io debug_flash_io3 T14
set_io leds[7] B5 # D9
set_io leds[6] B4 # D8
set_io leds[5] A2 # D7
set_io leds[4] A1 # D6
set_io leds[3] C5 # D5
set_io leds[2] C4 # D4
set_io leds[1] B3 # D3
set_io leds[0] C3 # D2

139
ice40/benchmark/hx8kdemo.v Normal file
View File

@ -0,0 +1,139 @@
/*
* PicoSoC - A simple example SoC using PicoRV32
*
* Copyright (C) 2017 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.
*
*/
module hx8kdemo (
input clk,
output ser_tx,
input ser_rx,
output [7:0] leds,
output flash_csb,
output flash_clk,
inout flash_io0,
inout flash_io1,
inout flash_io2,
inout flash_io3,
output debug_ser_tx,
output debug_ser_rx,
output debug_flash_csb,
output debug_flash_clk,
output debug_flash_io0,
output debug_flash_io1,
output debug_flash_io2,
output debug_flash_io3
);
reg [5:0] reset_cnt = 0;
wire resetn = &reset_cnt;
always @(posedge clk) begin
reset_cnt <= reset_cnt + !resetn;
end
wire flash_io0_oe, flash_io0_do, flash_io0_di;
wire flash_io1_oe, flash_io1_do, flash_io1_di;
wire flash_io2_oe, flash_io2_do, flash_io2_di;
wire flash_io3_oe, flash_io3_do, flash_io3_di;
SB_IO #(
.PIN_TYPE(6'b 1010_01),
.PULLUP(1'b 0)
) flash_io_buf [3:0] (
.PACKAGE_PIN({flash_io3, flash_io2, flash_io1, flash_io0}),
.OUTPUT_ENABLE({flash_io3_oe, flash_io2_oe, flash_io1_oe, flash_io0_oe}),
.D_OUT_0({flash_io3_do, flash_io2_do, flash_io1_do, flash_io0_do}),
.D_IN_0({flash_io3_di, flash_io2_di, flash_io1_di, flash_io0_di})
);
wire iomem_valid;
reg iomem_ready;
wire [3:0] iomem_wstrb;
wire [31:0] iomem_addr;
wire [31:0] iomem_wdata;
reg [31:0] iomem_rdata;
reg [31:0] gpio;
assign leds = gpio;
always @(posedge clk) begin
if (!resetn) begin
gpio <= 0;
end else begin
iomem_ready <= 0;
if (iomem_valid && !iomem_ready && iomem_addr[31:24] == 8'h 03) begin
iomem_ready <= 1;
iomem_rdata <= gpio;
if (iomem_wstrb[0]) gpio[ 7: 0] <= iomem_wdata[ 7: 0];
if (iomem_wstrb[1]) gpio[15: 8] <= iomem_wdata[15: 8];
if (iomem_wstrb[2]) gpio[23:16] <= iomem_wdata[23:16];
if (iomem_wstrb[3]) gpio[31:24] <= iomem_wdata[31:24];
end
end
end
picosoc soc (
.clk (clk ),
.resetn (resetn ),
.ser_tx (ser_tx ),
.ser_rx (ser_rx ),
.flash_csb (flash_csb ),
.flash_clk (flash_clk ),
.flash_io0_oe (flash_io0_oe),
.flash_io1_oe (flash_io1_oe),
.flash_io2_oe (flash_io2_oe),
.flash_io3_oe (flash_io3_oe),
.flash_io0_do (flash_io0_do),
.flash_io1_do (flash_io1_do),
.flash_io2_do (flash_io2_do),
.flash_io3_do (flash_io3_do),
.flash_io0_di (flash_io0_di),
.flash_io1_di (flash_io1_di),
.flash_io2_di (flash_io2_di),
.flash_io3_di (flash_io3_di),
.irq_5 (1'b0 ),
.irq_6 (1'b0 ),
.irq_7 (1'b0 ),
.iomem_valid (iomem_valid ),
.iomem_ready (iomem_ready ),
.iomem_wstrb (iomem_wstrb ),
.iomem_addr (iomem_addr ),
.iomem_wdata (iomem_wdata ),
.iomem_rdata (iomem_rdata )
);
assign debug_ser_tx = ser_tx;
assign debug_ser_rx = ser_rx;
assign debug_flash_csb = flash_csb;
assign debug_flash_clk = flash_clk;
assign debug_flash_io0 = flash_io0_di;
assign debug_flash_io1 = flash_io1_di;
assign debug_flash_io2 = flash_io2_di;
assign debug_flash_io3 = flash_io3_di;
endmodule

2977
ice40/benchmark/picorv32.v Normal file

File diff suppressed because it is too large Load Diff

241
ice40/benchmark/picosoc.v Normal file
View File

@ -0,0 +1,241 @@
/*
* PicoSoC - A simple example SoC using PicoRV32
*
* Copyright (C) 2017 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 PICORV32_REGS
`ifdef PICORV32_V
`error "picosoc.v must be read before picorv32.v!"
`endif
`define PICORV32_REGS picosoc_regs
`endif
module picosoc (
input clk,
input resetn,
output iomem_valid,
input iomem_ready,
output [ 3:0] iomem_wstrb,
output [31:0] iomem_addr,
output [31:0] iomem_wdata,
input [31:0] iomem_rdata,
input irq_5,
input irq_6,
input irq_7,
output ser_tx,
input ser_rx,
output flash_csb,
output flash_clk,
output flash_io0_oe,
output flash_io1_oe,
output flash_io2_oe,
output flash_io3_oe,
output flash_io0_do,
output flash_io1_do,
output flash_io2_do,
output flash_io3_do,
input flash_io0_di,
input flash_io1_di,
input flash_io2_di,
input flash_io3_di
);
parameter integer MEM_WORDS = 256;
parameter [31:0] STACKADDR = (4*MEM_WORDS); // end of memory
parameter [31:0] PROGADDR_RESET = 32'h 0010_0000; // 1 MB into flash
reg [31:0] irq;
wire irq_stall = 0;
wire irq_uart = 0;
always @* begin
irq = 0;
irq[3] = irq_stall;
irq[4] = irq_uart;
irq[5] = irq_5;
irq[6] = irq_6;
irq[7] = irq_7;
end
wire mem_valid;
wire mem_instr;
wire mem_ready;
wire [31:0] mem_addr;
wire [31:0] mem_wdata;
wire [3:0] mem_wstrb;
wire [31:0] mem_rdata;
wire spimem_ready;
wire [31:0] spimem_rdata;
reg ram_ready;
wire [31:0] ram_rdata;
assign iomem_valid = mem_valid && (mem_addr[31:24] > 8'h 01);
assign iomem_wstrb = mem_wstrb;
assign iomem_addr = mem_addr;
assign iomem_wdata = mem_wdata;
wire spimemio_cfgreg_sel = mem_valid && (mem_addr == 32'h 0200_0000);
wire [31:0] spimemio_cfgreg_do;
wire simpleuart_reg_div_sel = mem_valid && (mem_addr == 32'h 0200_0004);
wire [31:0] simpleuart_reg_div_do;
wire simpleuart_reg_dat_sel = mem_valid && (mem_addr == 32'h 0200_0008);
wire [31:0] simpleuart_reg_dat_do;
wire simpleuart_reg_dat_wait;
assign mem_ready = (iomem_valid && iomem_ready) || spimem_ready || ram_ready || spimemio_cfgreg_sel ||
simpleuart_reg_div_sel || (simpleuart_reg_dat_sel && !simpleuart_reg_dat_wait);
assign mem_rdata = (iomem_valid && iomem_ready) ? iomem_rdata : spimem_ready ? spimem_rdata : ram_ready ? ram_rdata :
spimemio_cfgreg_sel ? spimemio_cfgreg_do : simpleuart_reg_div_sel ? simpleuart_reg_div_do :
simpleuart_reg_dat_sel ? simpleuart_reg_dat_do : 32'h 0000_0000;
picorv32 #(
.STACKADDR(STACKADDR),
.PROGADDR_RESET(PROGADDR_RESET),
.PROGADDR_IRQ(32'h 0000_0000),
.BARREL_SHIFTER(1),
.COMPRESSED_ISA(1),
.ENABLE_MUL(1),
.ENABLE_DIV(1),
.ENABLE_IRQ(1),
.ENABLE_IRQ_QREGS(0)
) cpu (
.clk (clk ),
.resetn (resetn ),
.mem_valid (mem_valid ),
.mem_instr (mem_instr ),
.mem_ready (mem_ready ),
.mem_addr (mem_addr ),
.mem_wdata (mem_wdata ),
.mem_wstrb (mem_wstrb ),
.mem_rdata (mem_rdata ),
.irq (irq )
);
spimemio spimemio (
.clk (clk),
.resetn (resetn),
.valid (mem_valid && mem_addr >= 4*MEM_WORDS && mem_addr < 32'h 0200_0000),
.ready (spimem_ready),
.addr (mem_addr[23:0]),
.rdata (spimem_rdata),
.flash_csb (flash_csb ),
.flash_clk (flash_clk ),
.flash_io0_oe (flash_io0_oe),
.flash_io1_oe (flash_io1_oe),
.flash_io2_oe (flash_io2_oe),
.flash_io3_oe (flash_io3_oe),
.flash_io0_do (flash_io0_do),
.flash_io1_do (flash_io1_do),
.flash_io2_do (flash_io2_do),
.flash_io3_do (flash_io3_do),
.flash_io0_di (flash_io0_di),
.flash_io1_di (flash_io1_di),
.flash_io2_di (flash_io2_di),
.flash_io3_di (flash_io3_di),
.cfgreg_we(spimemio_cfgreg_sel ? mem_wstrb : 4'b 0000),
.cfgreg_di(mem_wdata),
.cfgreg_do(spimemio_cfgreg_do)
);
simpleuart simpleuart (
.clk (clk ),
.resetn (resetn ),
.ser_tx (ser_tx ),
.ser_rx (ser_rx ),
.reg_div_we (simpleuart_reg_div_sel ? mem_wstrb : 4'b 0000),
.reg_div_di (mem_wdata),
.reg_div_do (simpleuart_reg_div_do),
.reg_dat_we (simpleuart_reg_dat_sel ? mem_wstrb[0] : 1'b 0),
.reg_dat_re (simpleuart_reg_dat_sel && !mem_wstrb),
.reg_dat_di (mem_wdata),
.reg_dat_do (simpleuart_reg_dat_do),
.reg_dat_wait(simpleuart_reg_dat_wait)
);
always @(posedge clk)
ram_ready <= mem_valid && !mem_ready && mem_addr < 4*MEM_WORDS;
picosoc_mem #(.WORDS(MEM_WORDS)) memory (
.clk(clk),
.wen((mem_valid && !mem_ready && mem_addr < 4*MEM_WORDS) ? mem_wstrb : 4'b0),
.addr(mem_addr[23:2]),
.wdata(mem_wdata),
.rdata(ram_rdata)
);
endmodule
// Implementation note:
// Replace the following two modules with wrappers for your SRAM cells.
module picosoc_regs (
input clk, wen,
input [5:0] waddr,
input [5:0] raddr1,
input [5:0] raddr2,
input [31:0] wdata,
output [31:0] rdata1,
output [31:0] rdata2
);
reg [31:0] regs [0:31];
always @(posedge clk)
if (wen) regs[waddr[4:0]] <= wdata;
assign rdata1 = regs[raddr1[4:0]];
assign rdata2 = regs[raddr2[4:0]];
endmodule
module picosoc_mem #(
parameter integer WORDS = 256
) (
input clk,
input [3:0] wen,
input [21:0] addr,
input [31:0] wdata,
output reg [31:0] rdata
);
reg [31:0] mem [0:WORDS-1];
always @(posedge clk) begin
rdata <= mem[addr];
if (wen[0]) mem[addr][ 7: 0] <= wdata[ 7: 0];
if (wen[1]) mem[addr][15: 8] <= wdata[15: 8];
if (wen[2]) mem[addr][23:16] <= wdata[23:16];
if (wen[3]) mem[addr][31:24] <= wdata[31:24];
end
endmodule

View File

@ -0,0 +1,69 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"%matplotlib inline\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import subprocess\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",
"\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",
" except:\n",
" pass\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",
" except:\n",
" pass\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.ylabel('Longest path (ns)')\n",
"plt.xticks([5, 20], [\"arachne-pnr\", \"nextpnr\"])\n",
"plt.xlim(-2, 27)\n",
"plt.show()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.5.2"
}
},
"nbformat": 4,
"nbformat_minor": 1
}

View File

@ -0,0 +1,137 @@
/*
* PicoSoC - A simple example SoC using PicoRV32
*
* Copyright (C) 2017 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.
*
*/
module simpleuart (
input clk,
input resetn,
output ser_tx,
input ser_rx,
input [3:0] reg_div_we,
input [31:0] reg_div_di,
output [31:0] reg_div_do,
input reg_dat_we,
input reg_dat_re,
input [31:0] reg_dat_di,
output [31:0] reg_dat_do,
output reg_dat_wait
);
reg [31:0] cfg_divider;
reg [3:0] recv_state;
reg [31:0] recv_divcnt;
reg [7:0] recv_pattern;
reg [7:0] recv_buf_data;
reg recv_buf_valid;
reg [9:0] send_pattern;
reg [3:0] send_bitcnt;
reg [31:0] send_divcnt;
reg send_dummy;
assign reg_div_do = cfg_divider;
assign reg_dat_wait = reg_dat_we && (send_bitcnt || send_dummy);
assign reg_dat_do = recv_buf_valid ? recv_buf_data : ~0;
always @(posedge clk) begin
if (!resetn) begin
cfg_divider <= 1;
end else begin
if (reg_div_we[0]) cfg_divider[ 7: 0] <= reg_div_di[ 7: 0];
if (reg_div_we[1]) cfg_divider[15: 8] <= reg_div_di[15: 8];
if (reg_div_we[2]) cfg_divider[23:16] <= reg_div_di[23:16];
if (reg_div_we[3]) cfg_divider[31:24] <= reg_div_di[31:24];
end
end
always @(posedge clk) begin
if (!resetn) begin
recv_state <= 0;
recv_divcnt <= 0;
recv_pattern <= 0;
recv_buf_data <= 0;
recv_buf_valid <= 0;
end else begin
recv_divcnt <= recv_divcnt + 1;
if (reg_dat_re)
recv_buf_valid <= 0;
case (recv_state)
0: begin
if (!ser_rx)
recv_state <= 1;
recv_divcnt <= 0;
end
1: begin
if (2*recv_divcnt > cfg_divider) begin
recv_state <= 2;
recv_divcnt <= 0;
end
end
10: begin
if (recv_divcnt > cfg_divider) begin
recv_buf_data <= recv_pattern;
recv_buf_valid <= 1;
recv_state <= 0;
end
end
default: begin
if (recv_divcnt > cfg_divider) begin
recv_pattern <= {ser_rx, recv_pattern[7:1]};
recv_state <= recv_state + 1;
recv_divcnt <= 0;
end
end
endcase
end
end
assign ser_tx = send_pattern[0];
always @(posedge clk) begin
if (reg_div_we)
send_dummy <= 1;
send_divcnt <= send_divcnt + 1;
if (!resetn) begin
send_pattern <= ~0;
send_bitcnt <= 0;
send_divcnt <= 0;
send_dummy <= 1;
end else begin
if (send_dummy && !send_bitcnt) begin
send_pattern <= ~0;
send_bitcnt <= 15;
send_divcnt <= 0;
send_dummy <= 0;
end else
if (reg_dat_we && !send_bitcnt) begin
send_pattern <= {1'b1, reg_dat_di[7:0], 1'b0};
send_bitcnt <= 10;
send_divcnt <= 0;
end else
if (send_divcnt > cfg_divider && send_bitcnt) begin
send_pattern <= {1'b1, send_pattern[9:1]};
send_bitcnt <= send_bitcnt - 1;
send_divcnt <= 0;
end
end
end
endmodule

579
ice40/benchmark/spimemio.v Normal file
View File

@ -0,0 +1,579 @@
/*
* PicoSoC - A simple example SoC using PicoRV32
*
* Copyright (C) 2017 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.
*
*/
module spimemio (
input clk, resetn,
input valid,
output ready,
input [23:0] addr,
output reg [31:0] rdata,
output flash_csb,
output flash_clk,
output flash_io0_oe,
output flash_io1_oe,
output flash_io2_oe,
output flash_io3_oe,
output flash_io0_do,
output flash_io1_do,
output flash_io2_do,
output flash_io3_do,
input flash_io0_di,
input flash_io1_di,
input flash_io2_di,
input flash_io3_di,
input [3:0] cfgreg_we,
input [31:0] cfgreg_di,
output [31:0] cfgreg_do
);
reg xfer_resetn;
reg din_valid;
wire din_ready;
reg [7:0] din_data;
reg [3:0] din_tag;
reg din_cont;
reg din_qspi;
reg din_ddr;
reg din_rd;
wire dout_valid;
wire [7:0] dout_data;
wire [3:0] dout_tag;
reg [23:0] buffer;
reg [23:0] rd_addr;
reg rd_valid;
reg rd_wait;
reg rd_inc;
assign ready = valid && (addr == rd_addr) && rd_valid;
wire jump = valid && !ready && (addr != rd_addr+4) && rd_valid;
reg softreset;
reg config_en; // cfgreg[31]
reg config_ddr; // cfgreg[22]
reg config_qspi; // cfgreg[21]
reg config_cont; // cfgreg[20]
reg [3:0] config_dummy; // cfgreg[19:16]
reg [3:0] config_oe; // cfgreg[11:8]
reg config_csb; // cfgreg[5]
reg config_clk; // cfgref[4]
reg [3:0] config_do; // cfgreg[3:0]
assign cfgreg_do[31] = config_en;
assign cfgreg_do[30:23] = 0;
assign cfgreg_do[22] = config_ddr;
assign cfgreg_do[21] = config_qspi;
assign cfgreg_do[20] = config_cont;
assign cfgreg_do[19:16] = config_dummy;
assign cfgreg_do[15:12] = 0;
assign cfgreg_do[11:8] = {flash_io3_oe, flash_io2_oe, flash_io1_oe, flash_io0_oe};
assign cfgreg_do[7:6] = 0;
assign cfgreg_do[5] = flash_csb;
assign cfgreg_do[4] = flash_clk;
assign cfgreg_do[3:0] = {flash_io3_di, flash_io2_di, flash_io1_di, flash_io0_di};
always @(posedge clk) begin
softreset <= !config_en || cfgreg_we;
if (!resetn) begin
softreset <= 1;
config_en <= 1;
config_csb <= 0;
config_clk <= 0;
config_oe <= 0;
config_do <= 0;
config_ddr <= 0;
config_qspi <= 0;
config_cont <= 0;
config_dummy <= 8;
end else begin
if (cfgreg_we[0]) begin
config_csb <= cfgreg_di[5];
config_clk <= cfgreg_di[4];
config_do <= cfgreg_di[3:0];
end
if (cfgreg_we[1]) begin
config_oe <= cfgreg_di[11:8];
end
if (cfgreg_we[2]) begin
config_ddr <= cfgreg_di[22];
config_qspi <= cfgreg_di[21];
config_cont <= cfgreg_di[20];
config_dummy <= cfgreg_di[19:16];
end
if (cfgreg_we[3]) begin
config_en <= cfgreg_di[31];
end
end
end
wire xfer_csb;
wire xfer_clk;
wire xfer_io0_oe;
wire xfer_io1_oe;
wire xfer_io2_oe;
wire xfer_io3_oe;
wire xfer_io0_do;
wire xfer_io1_do;
wire xfer_io2_do;
wire xfer_io3_do;
reg xfer_io0_90;
reg xfer_io1_90;
reg xfer_io2_90;
reg xfer_io3_90;
always @(negedge clk) begin
xfer_io0_90 <= xfer_io0_do;
xfer_io1_90 <= xfer_io1_do;
xfer_io2_90 <= xfer_io2_do;
xfer_io3_90 <= xfer_io3_do;
end
assign flash_csb = config_en ? xfer_csb : config_csb;
assign flash_clk = config_en ? xfer_clk : config_clk;
assign flash_io0_oe = config_en ? xfer_io0_oe : config_oe[0];
assign flash_io1_oe = config_en ? xfer_io1_oe : config_oe[1];
assign flash_io2_oe = config_en ? xfer_io2_oe : config_oe[2];
assign flash_io3_oe = config_en ? xfer_io3_oe : config_oe[3];
assign flash_io0_do = config_en ? (config_ddr ? xfer_io0_90 : xfer_io0_do) : config_do[0];
assign flash_io1_do = config_en ? (config_ddr ? xfer_io1_90 : xfer_io1_do) : config_do[1];
assign flash_io2_do = config_en ? (config_ddr ? xfer_io2_90 : xfer_io2_do) : config_do[2];
assign flash_io3_do = config_en ? (config_ddr ? xfer_io3_90 : xfer_io3_do) : config_do[3];
wire xfer_dspi = din_ddr && !din_qspi;
wire xfer_ddr = din_ddr && din_qspi;
spimemio_xfer xfer (
.clk (clk ),
.resetn (xfer_resetn ),
.din_valid (din_valid ),
.din_ready (din_ready ),
.din_data (din_data ),
.din_tag (din_tag ),
.din_cont (din_cont ),
.din_dspi (xfer_dspi ),
.din_qspi (din_qspi ),
.din_ddr (xfer_ddr ),
.din_rd (din_rd ),
.dout_valid (dout_valid ),
.dout_data (dout_data ),
.dout_tag (dout_tag ),
.flash_csb (xfer_csb ),
.flash_clk (xfer_clk ),
.flash_io0_oe (xfer_io0_oe ),
.flash_io1_oe (xfer_io1_oe ),
.flash_io2_oe (xfer_io2_oe ),
.flash_io3_oe (xfer_io3_oe ),
.flash_io0_do (xfer_io0_do ),
.flash_io1_do (xfer_io1_do ),
.flash_io2_do (xfer_io2_do ),
.flash_io3_do (xfer_io3_do ),
.flash_io0_di (flash_io0_di),
.flash_io1_di (flash_io1_di),
.flash_io2_di (flash_io2_di),
.flash_io3_di (flash_io3_di)
);
reg [3:0] state;
always @(posedge clk) begin
xfer_resetn <= 1;
din_valid <= 0;
if (!resetn || softreset) begin
state <= 0;
xfer_resetn <= 0;
rd_valid <= 0;
din_tag <= 0;
din_cont <= 0;
din_qspi <= 0;
din_ddr <= 0;
din_rd <= 0;
end else begin
if (dout_valid && dout_tag == 1) buffer[ 7: 0] <= dout_data;
if (dout_valid && dout_tag == 2) buffer[15: 8] <= dout_data;
if (dout_valid && dout_tag == 3) buffer[23:16] <= dout_data;
if (dout_valid && dout_tag == 4) begin
rdata <= {dout_data, buffer};
rd_addr <= rd_inc ? rd_addr + 4 : addr;
rd_valid <= 1;
rd_wait <= rd_inc;
rd_inc <= 1;
end
if (valid)
rd_wait <= 0;
case (state)
0: begin
din_valid <= 1;
din_data <= 8'h ff;
din_tag <= 0;
if (din_ready) begin
din_valid <= 0;
state <= 1;
end
end
1: begin
if (dout_valid) begin
xfer_resetn <= 0;
state <= 2;
end
end
2: begin
din_valid <= 1;
din_data <= 8'h ab;
din_tag <= 0;
if (din_ready) begin
din_valid <= 0;
state <= 3;
end
end
3: begin
if (dout_valid) begin
xfer_resetn <= 0;
state <= 4;
end
end
4: begin
rd_inc <= 0;
din_valid <= 1;
din_tag <= 0;
case ({config_ddr, config_qspi})
2'b11: din_data <= 8'h ED;
2'b01: din_data <= 8'h EB;
2'b10: din_data <= 8'h BB;
2'b00: din_data <= 8'h 03;
endcase
if (din_ready) begin
din_valid <= 0;
state <= 5;
end
end
5: begin
if (valid && !ready) begin
din_valid <= 1;
din_tag <= 0;
din_data <= addr[23:16];
din_qspi <= config_qspi;
din_ddr <= config_ddr;
if (din_ready) begin
din_valid <= 0;
state <= 6;
end
end
end
6: begin
din_valid <= 1;
din_tag <= 0;
din_data <= addr[15:8];
if (din_ready) begin
din_valid <= 0;
state <= 7;
end
end
7: begin
din_valid <= 1;
din_tag <= 0;
din_data <= addr[7:0];
if (din_ready) begin
din_valid <= 0;
din_data <= 0;
state <= config_qspi || config_ddr ? 8 : 9;
end
end
8: begin
din_valid <= 1;
din_tag <= 0;
din_data <= config_cont ? 8'h A5 : 8'h FF;
if (din_ready) begin
din_rd <= 1;
din_data <= config_dummy;
din_valid <= 0;
state <= 9;
end
end
9: begin
din_valid <= 1;
din_tag <= 1;
if (din_ready) begin
din_valid <= 0;
state <= 10;
end
end
10: begin
din_valid <= 1;
din_data <= 8'h 00;
din_tag <= 2;
if (din_ready) begin
din_valid <= 0;
state <= 11;
end
end
11: begin
din_valid <= 1;
din_tag <= 3;
if (din_ready) begin
din_valid <= 0;
state <= 12;
end
end
12: begin
if (!rd_wait || valid) begin
din_valid <= 1;
din_tag <= 4;
if (din_ready) begin
din_valid <= 0;
state <= 9;
end
end
end
endcase
if (jump) begin
rd_inc <= 0;
rd_valid <= 0;
xfer_resetn <= 0;
if (config_cont) begin
state <= 5;
end else begin
state <= 4;
din_qspi <= 0;
din_ddr <= 0;
end
din_rd <= 0;
end
end
end
endmodule
module spimemio_xfer (
input clk, resetn,
input din_valid,
output din_ready,
input [7:0] din_data,
input [3:0] din_tag,
input din_cont,
input din_dspi,
input din_qspi,
input din_ddr,
input din_rd,
output dout_valid,
output [7:0] dout_data,
output [3:0] dout_tag,
output reg flash_csb,
output reg flash_clk,
output reg flash_io0_oe,
output reg flash_io1_oe,
output reg flash_io2_oe,
output reg flash_io3_oe,
output reg flash_io0_do,
output reg flash_io1_do,
output reg flash_io2_do,
output reg flash_io3_do,
input flash_io0_di,
input flash_io1_di,
input flash_io2_di,
input flash_io3_di
);
reg [7:0] obuffer;
reg [7:0] ibuffer;
reg [3:0] count;
reg [3:0] dummy_count;
reg xfer_cont;
reg xfer_dspi;
reg xfer_qspi;
reg xfer_ddr;
reg xfer_ddr_q;
reg xfer_rd;
reg [3:0] xfer_tag;
reg [3:0] xfer_tag_q;
reg [7:0] next_obuffer;
reg [7:0] next_ibuffer;
reg [3:0] next_count;
reg fetch;
reg next_fetch;
reg last_fetch;
always @(posedge clk) begin
xfer_ddr_q <= xfer_ddr;
xfer_tag_q <= xfer_tag;
end
assign din_ready = din_valid && resetn && next_fetch;
assign dout_valid = (xfer_ddr_q ? fetch && !last_fetch : next_fetch && !fetch) && resetn;
assign dout_data = ibuffer;
assign dout_tag = xfer_tag_q;
always @* begin
flash_io0_oe = 0;
flash_io1_oe = 0;
flash_io2_oe = 0;
flash_io3_oe = 0;
flash_io0_do = 0;
flash_io1_do = 0;
flash_io2_do = 0;
flash_io3_do = 0;
next_obuffer = obuffer;
next_ibuffer = ibuffer;
next_count = count;
next_fetch = 0;
if (dummy_count == 0) begin
casez ({xfer_ddr, xfer_qspi, xfer_dspi})
3'b 000: begin
flash_io0_oe = 1;
flash_io0_do = obuffer[7];
if (flash_clk) begin
next_obuffer = {obuffer[6:0], 1'b 0};
next_count = count - |count;
end else begin
next_ibuffer = {ibuffer[6:0], flash_io1_di};
end
next_fetch = (next_count == 0);
end
3'b 01?: begin
flash_io0_oe = !xfer_rd;
flash_io1_oe = !xfer_rd;
flash_io2_oe = !xfer_rd;
flash_io3_oe = !xfer_rd;
flash_io0_do = obuffer[4];
flash_io1_do = obuffer[5];
flash_io2_do = obuffer[6];
flash_io3_do = obuffer[7];
if (flash_clk) begin
next_obuffer = {obuffer[3:0], 4'b 0000};
next_count = count - {|count, 2'b00};
end else begin
next_ibuffer = {ibuffer[3:0], flash_io3_di, flash_io2_di, flash_io1_di, flash_io0_di};
end
next_fetch = (next_count == 0);
end
3'b 11?: begin
flash_io0_oe = !xfer_rd;
flash_io1_oe = !xfer_rd;
flash_io2_oe = !xfer_rd;
flash_io3_oe = !xfer_rd;
flash_io0_do = obuffer[4];
flash_io1_do = obuffer[5];
flash_io2_do = obuffer[6];
flash_io3_do = obuffer[7];
next_obuffer = {obuffer[3:0], 4'b 0000};
next_ibuffer = {ibuffer[3:0], flash_io3_di, flash_io2_di, flash_io1_di, flash_io0_di};
next_count = count - {|count, 2'b00};
next_fetch = (next_count == 0);
end
3'b ??1: begin
flash_io0_oe = !xfer_rd;
flash_io1_oe = !xfer_rd;
flash_io0_do = obuffer[6];
flash_io1_do = obuffer[7];
if (flash_clk) begin
next_obuffer = {obuffer[5:0], 2'b 00};
next_count = count - {|count, 1'b0};
end else begin
next_ibuffer = {ibuffer[5:0], flash_io1_di, flash_io0_di};
end
next_fetch = (next_count == 0);
end
endcase
end
end
always @(posedge clk) begin
if (!resetn) begin
fetch <= 1;
last_fetch <= 1;
flash_csb <= 1;
flash_clk <= 0;
count <= 0;
dummy_count <= 0;
xfer_tag <= 0;
xfer_cont <= 0;
xfer_dspi <= 0;
xfer_qspi <= 0;
xfer_ddr <= 0;
xfer_rd <= 0;
end else begin
fetch <= next_fetch;
last_fetch <= xfer_ddr ? fetch : 1;
if (dummy_count) begin
flash_clk <= !flash_clk && !flash_csb;
dummy_count <= dummy_count - flash_clk;
end else
if (count) begin
flash_clk <= !flash_clk && !flash_csb;
obuffer <= next_obuffer;
ibuffer <= next_ibuffer;
count <= next_count;
end
if (din_valid && din_ready) begin
flash_csb <= 0;
flash_clk <= 0;
count <= 8;
dummy_count <= din_rd ? din_data : 0;
obuffer <= din_data;
xfer_tag <= din_tag;
xfer_cont <= din_cont;
xfer_dspi <= din_dspi;
xfer_qspi <= din_qspi;
xfer_ddr <= din_ddr;
xfer_rd <= din_rd;
end
end
end
endmodule

View File

@ -3,6 +3,7 @@
*
* Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com>
* Copyright (C) 2018 David Shah <david@symbioticeda.com>
* Copyright (C) 2018 Serge Bazanski <q3k@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
@ -74,14 +75,26 @@ void set_config(const TileInfoPOD &ti, std::vector<std::vector<int8_t>> &tile_cf
for (int i = 0; i < cfg.num_bits; i++) {
int8_t &cbit = tile_cfg.at(cfg.bits[i].row).at(cfg.bits[i].col);
if (cbit && !value)
log_error("clearing already set config bit %s", name.c_str());
log_error("clearing already set config bit %s\n", name.c_str());
cbit = value;
}
} else {
int8_t &cbit = tile_cfg.at(cfg.bits[index].row).at(cfg.bits[index].col);
cbit = value;
if (cbit && !value)
log_error("clearing already set config bit %s[%d]", name.c_str(), index);
log_error("clearing already set config bit %s[%d]\n", name.c_str(), index);
}
}
// Set an IE_{EN,REN} logical bit in a tile config. Logical means enabled.
// On {HX,LP}1K devices these bits are active low, so we need to invert them.
void set_ie_bit_logical(const Context *ctx, const TileInfoPOD &ti, std::vector<std::vector<int8_t>> &tile_cfg,
const std::string &name, bool value)
{
if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) {
set_config(ti, tile_cfg, name, !value);
} else {
set_config(ti, tile_cfg, name, value);
}
}
@ -117,7 +130,7 @@ static const BelConfigPOD &get_ec_config(const ChipInfoPOD *chip, BelId bel)
typedef std::vector<std::vector<std::vector<std::vector<int8_t>>>> chipconfig_t;
static void set_ec_cbit(chipconfig_t &config, const Context *ctx, const BelConfigPOD &cell_cbits, std::string name,
bool value)
bool value, std::string prefix)
{
const ChipInfoPOD *chip = ctx->chip_info;
@ -125,7 +138,7 @@ static void set_ec_cbit(chipconfig_t &config, const Context *ctx, const BelConfi
const auto &cbit = cell_cbits.entries[i];
if (cbit.entry_name.get() == name) {
const auto &ti = chip->bits_info->tiles_nonrouting[tile_at(ctx, cbit.x, cbit.y)];
set_config(ti, config.at(cbit.y).at(cbit.x), std::string("IpConfig.") + cbit.cbit_name.get(), value);
set_config(ti, config.at(cbit.y).at(cbit.x), prefix + cbit.cbit_name.get(), value);
return;
}
}
@ -133,7 +146,7 @@ static void set_ec_cbit(chipconfig_t &config, const Context *ctx, const BelConfi
}
void configure_extra_cell(chipconfig_t &config, const Context *ctx, CellInfo *cell,
const std::vector<std::pair<std::string, int>> &params, bool string_style)
const std::vector<std::pair<std::string, int>> &params, bool string_style, std::string prefix)
{
const ChipInfoPOD *chip = ctx->chip_info;
const auto &bc = get_ec_config(chip, cell->bel);
@ -163,10 +176,10 @@ void configure_extra_cell(chipconfig_t &config, const Context *ctx, CellInfo *ce
value.resize(p.second);
if (p.second == 1) {
set_ec_cbit(config, ctx, bc, p.first, value.at(0));
set_ec_cbit(config, ctx, bc, p.first, value.at(0), prefix);
} else {
for (int i = 0; i < p.second; i++) {
set_ec_cbit(config, ctx, bc, p.first + "_" + std::to_string(i), value.at(i));
set_ec_cbit(config, ctx, bc, p.first + "_" + std::to_string(i), value.at(i), prefix);
}
}
}
@ -258,8 +271,13 @@ void write_asc(const Context *ctx, std::ostream &out)
}
}
}
std::unordered_set<Loc> sb_io_used_by_pll;
std::unordered_set<Loc> sb_io_used_by_io;
// Set logic cell config
for (auto &cell : ctx->cells) {
BelId bel = cell.second.get()->bel;
if (bel == BelId()) {
std::cout << "Found unplaced cell " << cell.first.str(ctx) << " while generating bitstream!" << std::endl;
@ -304,6 +322,7 @@ void write_asc(const Context *ctx, std::ostream &out)
} else if (cell.second->type == ctx->id("SB_IO")) {
const BelInfoPOD &beli = ci.bel_data[bel.index];
int x = beli.x, y = beli.y, z = beli.z;
sb_io_used_by_io.insert(Loc(x, y, z));
const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO];
unsigned pin_type = get_param_or_def(cell.second.get(), ctx->id("PIN_TYPE"));
bool neg_trigger = get_param_or_def(cell.second.get(), ctx->id("NEG_TRIGGER"));
@ -405,7 +424,70 @@ void write_asc(const Context *ctx, std::ostream &out)
{"MODE_8x8", 1},
{"A_SIGNED", 1},
{"B_SIGNED", 1}};
configure_extra_cell(config, ctx, cell.second.get(), mac16_params, false);
configure_extra_cell(config, ctx, cell.second.get(), mac16_params, false, std::string("IpConfig."));
} else if (cell.second->type == ctx->id("ICESTORM_PLL")) {
const std::vector<std::pair<std::string, int>> pll_params = {{"DELAY_ADJMODE_FB", 1},
{"DELAY_ADJMODE_REL", 1},
{"DIVF", 7},
{"DIVQ", 3},
{"DIVR", 4},
{"FDA_FEEDBACK", 4},
{"FDA_RELATIVE", 4},
{"FEEDBACK_PATH", 3},
{"FILTER_RANGE", 3},
{"PLLOUT_SELECT_A", 2},
{"PLLOUT_SELECT_B", 2},
{"PLLTYPE", 3},
{"SHIFTREG_DIV_MODE", 1},
{"TEST_MODE", 1}};
configure_extra_cell(config, ctx, cell.second.get(), pll_params, false, std::string("PLL."));
// Configure the SB_IOs that the clock outputs are going through.
for (auto &port : cell.second->ports) {
// If this port is not a PLLOUT port, ignore it.
if (port.second.name != ctx->id("PLLOUT_A") && port.second.name != ctx->id("PLLOUT_B"))
continue;
// If the output is not driving any net, ignore it.
if (port.second.net == nullptr)
continue;
// Get IO Bel that this PLL port goes through by finding sibling
// Bel driving the same wire via PIN_D_IN_0.
auto wire = ctx->getBelPinWire(cell.second->bel, ctx->portPinFromId(port.second.name));
BelId io_bel;
for (auto pin : ctx->getWireBelPins(wire)) {
if (pin.pin == PIN_D_IN_0) {
io_bel = pin.bel;
break;
}
}
NPNR_ASSERT(io_bel.index != -1);
auto io_bel_loc = ctx->getBelLocation(io_bel);
// Check that this SB_IO is either unused or just used as an output.
if (sb_io_used_by_io.count(io_bel_loc)) {
log_error("SB_IO '%s' already in use, cannot route PLL through\n", ctx->getBelName(bel).c_str(ctx));
}
sb_io_used_by_pll.insert(io_bel_loc);
// Get IE/REN config location (cf. http://www.clifford.at/icestorm/io_tile.html)
auto ieren = get_ieren(bi, io_bel_loc.x, io_bel_loc.y, io_bel_loc.z);
int iex, iey, iez;
std::tie(iex, iey, iez) = ieren;
NPNR_ASSERT(iez != -1);
// Write config.
const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO];
// Enable input buffer and disable pull-up resistor in block
// (this is used by the PLL).
set_ie_bit_logical(ctx, ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), true);
set_ie_bit_logical(ctx, ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), false);
// PINTYPE[0] passes the PLL through to the fabric.
set_config(ti, config.at(io_bel_loc.y).at(io_bel_loc.x),
"IOB_" + std::to_string(io_bel_loc.z) + ".PINTYPE_0", true);
}
} else {
NPNR_ASSERT(false);
}
@ -416,14 +498,16 @@ void write_asc(const Context *ctx, std::ostream &out)
const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO];
const BelInfoPOD &beli = ci.bel_data[bel.index];
int x = beli.x, y = beli.y, z = beli.z;
if (sb_io_used_by_pll.count(Loc(x, y, z))) {
continue;
}
auto ieren = get_ieren(bi, x, y, z);
int iex, iey, iez;
std::tie(iex, iey, iez) = ieren;
if (iez != -1) {
if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) {
set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), true);
set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), false);
}
set_ie_bit_logical(ctx, ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), true);
set_ie_bit_logical(ctx, ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), false);
}
} else if (ctx->bel_to_cell[bel.index] == IdString() && ctx->getBelType(bel) == TYPE_ICESTORM_RAM) {
const BelInfoPOD &beli = ci.bel_data[bel.index];
@ -482,9 +566,8 @@ void write_asc(const Context *ctx, std::ostream &out)
set_config(ti, config.at(y).at(x),
"Cascade.IPCON_LC0" + std::to_string(lc_idx) + "_inmux02_5", true);
else
set_config(ti, config.at(y).at(x),
"Cascade.MULT" + std::to_string(int(tile - TILE_DSP0)) + "_LC0" +
std::to_string(lc_idx) + "_inmux02_5",
set_config(ti, config.at(y).at(x), "Cascade.MULT" + std::to_string(int(tile - TILE_DSP0)) +
"_LC0" + std::to_string(lc_idx) + "_inmux02_5",
true);
}
}

View File

@ -3,6 +3,7 @@
*
* Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com>
* Copyright (C) 2018 David Shah <david@symbioticeda.com>
* Copyright (C) 2018 Serge Bazanski <q3k@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
@ -207,6 +208,40 @@ std::unique_ptr<CellInfo> create_ice_cell(Context *ctx, IdString type, std::stri
add_port(ctx, new_cell.get(), "ACCUMCO", PORT_OUT);
add_port(ctx, new_cell.get(), "SIGNEXTOUT", PORT_OUT);
} else if (type == ctx->id("ICESTORM_PLL")) {
new_cell->params[ctx->id("DELAY_ADJMODE_FB")] = "0";
new_cell->params[ctx->id("DELAY_ADJMODE_REL")] = "0";
new_cell->params[ctx->id("DIVF")] = "0";
new_cell->params[ctx->id("DIVQ")] = "0";
new_cell->params[ctx->id("DIVR")] = "0";
new_cell->params[ctx->id("FDA_FEEDBACK")] = "0";
new_cell->params[ctx->id("FDA_RELATIVE")] = "0";
new_cell->params[ctx->id("FEEDBACK_PATH")] = "0";
new_cell->params[ctx->id("FILTER_RANGE")] = "0";
new_cell->params[ctx->id("PLLOUT_SELECT_A")] = "0";
new_cell->params[ctx->id("PLLOUT_SELECT_B")] = "0";
new_cell->params[ctx->id("PLLTYPE")] = "0";
new_cell->params[ctx->id("SHIFTREG_DIVMODE")] = "0";
new_cell->params[ctx->id("TEST_MODE")] = "0";
add_port(ctx, new_cell.get(), "BYPASS", PORT_IN);
add_port(ctx, new_cell.get(), "DYNAMICDELAY", PORT_IN);
add_port(ctx, new_cell.get(), "EXTFEEDBACK", PORT_IN);
add_port(ctx, new_cell.get(), "LATCHINPUTVALUE", PORT_IN);
add_port(ctx, new_cell.get(), "REFERENCECLK", PORT_IN);
add_port(ctx, new_cell.get(), "RESETB", PORT_IN);
add_port(ctx, new_cell.get(), "SCLK", PORT_IN);
add_port(ctx, new_cell.get(), "SDI", PORT_IN);
add_port(ctx, new_cell.get(), "SDI", PORT_OUT);
add_port(ctx, new_cell.get(), "LOCK", PORT_OUT);
add_port(ctx, new_cell.get(), "PLLOUT_A", PORT_OUT);
add_port(ctx, new_cell.get(), "PLLOUT_B", PORT_OUT);
} else {
log_error("unable to create iCE40 cell of type %s", type.c_str(ctx));
}
@ -312,6 +347,21 @@ void nxio_to_sb(Context *ctx, CellInfo *nxio, CellInfo *sbio)
}
}
uint8_t sb_pll40_type(const BaseCtx *ctx, const CellInfo *cell)
{
if (cell->type == ctx->id("SB_PLL40_PAD"))
return 2;
if (cell->type == ctx->id("SB_PLL40_2_PAD"))
return 4;
if (cell->type == ctx->id("SB_PLL40_2F_PAD"))
return 5;
if (cell->type == ctx->id("SB_PLL40_CORE"))
return 3;
if (cell->type == ctx->id("SB_PLL40_2F_CORE"))
return 7;
NPNR_ASSERT(0);
}
bool is_clock_port(const BaseCtx *ctx, const PortRef &port)
{
if (port.cell == nullptr)

View File

@ -71,6 +71,21 @@ inline bool is_sb_spram(const BaseCtx *ctx, const CellInfo *cell) { return cell-
inline bool is_sb_mac16(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_MAC16"); }
inline bool is_sb_pll40(const BaseCtx *ctx, const CellInfo *cell)
{
return cell->type == ctx->id("SB_PLL40_PAD") || cell->type == ctx->id("SB_PLL40_2_PAD") ||
cell->type == ctx->id("SB_PLL40_2F_PAD") || cell->type == ctx->id("SB_PLL40_CORE") ||
cell->type == ctx->id("SB_PLL40_2F_CORE");
}
inline bool is_sb_pll40_pad(const BaseCtx *ctx, const CellInfo *cell)
{
return cell->type == ctx->id("SB_PLL40_PAD") || cell->type == ctx->id("SB_PLL40_2_PAD") ||
cell->type == ctx->id("SB_PLL40_2F_PAD");
}
uint8_t sb_pll40_type(const BaseCtx *ctx, const CellInfo *cell);
// 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

View File

@ -6,17 +6,11 @@ import textwrap
import argparse
parser = argparse.ArgumentParser(description="convert ICE40 chip database")
group = parser.add_mutually_exclusive_group()
group.add_argument("-b", "--binary", action="store_true")
group.add_argument("-c", "--c_file", action="store_true")
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")
args = parser.parse_args()
endianness = "le"
nodebug = True
dev_name = None
dev_width = None
dev_height = None
@ -41,8 +35,6 @@ extra_cells = dict()
extra_cell_config = dict()
packages = list()
wire_uphill_belport = dict()
wire_downhill_belports = dict()
wire_belports = dict()
wire_names = dict()
@ -184,6 +176,8 @@ def wire_type(name):
wt = "LOCAL"
elif name in ("WCLK", "WCLKE", "WE", "RCLK", "RCLKE", "RE"):
wt = "LOCAL"
elif name in ("PLLOUT_A", "PLLOUT_B"):
wt = "LOCAL"
if wt is None:
print("No type for wire: %s (%s)" % (longname, name), file=sys.stderr)
@ -451,17 +445,12 @@ for i in range(8):
add_wire(0, 0, "padin_%d" % i)
def add_bel_input(bel, wire, port):
if wire not in wire_downhill_belports:
wire_downhill_belports[wire] = set()
wire_downhill_belports[wire].add((bel, port))
if wire not in wire_belports:
wire_belports[wire] = set()
wire_belports[wire].add((bel, port))
bel_wires[bel].append((wire, port, 0))
def add_bel_output(bel, wire, port):
assert wire not in wire_uphill_belport
wire_uphill_belport[wire] = (bel, port)
if wire not in wire_belports:
wire_belports[wire] = set()
wire_belports[wire].add((bel, port))
@ -591,6 +580,9 @@ def is_ec_output(ec_entry):
if "glb_netwk_" in wirename: return True
return False
def is_ec_pll_clock_output(ec, ec_entry):
return ec[0] == 'PLL' and ec_entry[0] in ('PLLOUT_A', 'PLLOUT_B')
def add_bel_ec(ec):
ectype, x, y, z = ec
bel = len(bel_name)
@ -605,6 +597,10 @@ def add_bel_ec(ec):
add_bel_output(bel, wire_names[entry[1]], entry[0])
else:
add_bel_input(bel, wire_names[entry[1]], entry[0])
elif is_ec_pll_clock_output(ec, entry):
x, y, z = entry[1]
z = 'io_{}/D_IN_0'.format(z)
add_bel_output(bel, wire_names[(x, y, z)], entry[0])
else:
extra_cell_config[bel].append(entry)
@ -662,270 +658,61 @@ for tile_xy, tile_type in sorted(tiles.items()):
add_bel_ec(ec)
for ec in sorted(extra_cells.keys()):
if ec[1] == 0 and ec[2] == 0:
if ec[1] in (0, dev_width - 1) and ec[2] in (0, dev_height - 1):
add_bel_ec(ec)
class BinaryBlobAssembler:
def __init__(self, cname, endianness, nodebug = False):
assert endianness in ["le", "be"]
self.cname = cname
self.endianness = endianness
self.finalized = False
self.data = bytearray()
self.comments = dict()
self.labels = dict()
self.exports = set()
self.labels_byaddr = dict()
self.ltypes_byaddr = dict()
self.strings = dict()
self.refs = dict()
self.nodebug = nodebug
def l(self, name, ltype = None, export = False):
assert not self.finalized
assert name not in self.labels
assert len(self.data) not in self.labels_byaddr
self.labels[name] = len(self.data)
if ltype is not None:
self.ltypes_byaddr[len(self.data)] = ltype
self.labels_byaddr[len(self.data)] = name
if export:
assert ltype is not None
self.exports.add(len(self.data))
if ltype is None:
print("label %s" % (name,))
else:
print("label %s %s" % (name, ltype))
def r(self, name, comment):
assert not self.finalized
assert len(self.data) % 4 == 0
assert len(self.data) not in self.refs
if self.nodebug:
comment = None
if name is not None:
self.refs[len(self.data)] = (name, comment)
self.data.append(0)
self.data.append(0)
self.data.append(0)
self.data.append(0)
if (name is None) and (comment is not None):
self.comments[len(self.data)] = comment + " (null reference)"
if comment is None:
print("ref %s" % (name,))
else:
print("ref %s %s" % (name, comment))
def s(self, s, comment):
assert not self.finalized
if self.nodebug:
comment = None
if s not in self.strings:
index = len(self.strings)
self.strings[s] = index
else:
index = self.strings[s]
if comment is not None:
self.r("str%d" % index, '%s: "%s"' % (comment, s))
else:
self.r("str%d" % index, None)
assert "|" not in s
print("str |%s| %s" % (s, comment))
def u8(self, v, comment):
assert not self.finalized
if self.nodebug:
comment = None
self.data.append(v)
if comment is not None:
self.comments[len(self.data)] = comment
if comment is None:
print("u8 %d" % (v,))
else:
print("u8 %d %s" % (v, comment))
def u16(self, v, comment):
assert not self.finalized
assert len(self.data) % 2 == 0
if self.nodebug:
comment = None
if self.endianness == "le":
self.data.append(v & 255)
self.data.append((v >> 8) & 255)
elif self.endianness == "be":
self.data.append((v >> 8) & 255)
self.data.append(v & 255)
if comment is None:
print("u16 %d" % (v,))
else:
assert 0
if comment is not None:
self.comments[len(self.data)] = comment
print("u16 %d %s" % (v, comment))
def u32(self, v, comment):
assert not self.finalized
assert len(self.data) % 4 == 0
if self.nodebug:
comment = None
if self.endianness == "le":
self.data.append(v & 255)
self.data.append((v >> 8) & 255)
self.data.append((v >> 16) & 255)
self.data.append((v >> 24) & 255)
elif self.endianness == "be":
self.data.append((v >> 24) & 255)
self.data.append((v >> 16) & 255)
self.data.append((v >> 8) & 255)
self.data.append(v & 255)
if comment is None:
print("u32 %d" % (v,))
else:
assert 0
if comment is not None:
self.comments[len(self.data)] = comment
print("u32 %d %s" % (v, comment))
def finalize(self):
assert not self.finalized
for s, index in sorted(self.strings.items()):
self.l("str%d" % index, "char")
for c in s:
self.data.append(ord(c))
self.data.append(0)
self.finalized = True
cursor = 0
while cursor < len(self.data):
if cursor in self.refs:
v = self.labels[self.refs[cursor][0]] - cursor
if self.endianness == "le":
self.data[cursor+0] = (v & 255)
self.data[cursor+1] = ((v >> 8) & 255)
self.data[cursor+2] = ((v >> 16) & 255)
self.data[cursor+3] = ((v >> 24) & 255)
elif self.endianness == "be":
self.data[cursor+0] = ((v >> 24) & 255)
self.data[cursor+1] = ((v >> 16) & 255)
self.data[cursor+2] = ((v >> 8) & 255)
self.data[cursor+3] = (v & 255)
else:
assert 0
cursor += 4
else:
cursor += 1
def pre(self, s):
print("pre %s" % s)
def write_verbose_c(self, f, ctype = "const unsigned char"):
assert self.finalized
print("%s %s[%d] = {" % (ctype, self.cname, len(self.data)), file=f)
cursor = 0
bytecnt = 0
while cursor < len(self.data):
if cursor in self.comments:
if bytecnt == 0:
print(" ", end="", file=f)
print(" // %s" % self.comments[cursor], file=f)
bytecnt = 0
if cursor in self.labels_byaddr:
if bytecnt != 0:
print(file=f)
if cursor in self.exports:
print("#define %s ((%s*)(%s+%d))" % (self.labels_byaddr[cursor], self.ltypes_byaddr[cursor], self.cname, cursor), file=f)
else:
print(" // [%d] %s" % (cursor, self.labels_byaddr[cursor]), file=f)
bytecnt = 0
if cursor in self.refs:
if bytecnt != 0:
print(file=f)
print(" ", end="", file=f)
print(" %-4s" % ("%d," % self.data[cursor+0]), end="", file=f)
print(" %-4s" % ("%d," % self.data[cursor+1]), end="", file=f)
print(" %-4s" % ("%d," % self.data[cursor+2]), end="", file=f)
print(" %-4s" % ("%d," % self.data[cursor+3]), end="", file=f)
print(" // [%d] %s (reference to %s)" % (cursor, self.refs[cursor][1], self.refs[cursor][0]), file=f)
bytecnt = 0
cursor += 4
else:
if bytecnt == 0:
print(" ", end="", file=f)
print(" %-4s" % ("%d," % self.data[cursor]), end=("" if bytecnt < 15 else "\n"), file=f)
bytecnt = (bytecnt + 1) & 15
cursor += 1
if bytecnt != 0:
print(file=f)
print("};", file=f)
def post(self, s):
print("post %s" % s)
def write_compact_c(self, f, ctype = "const unsigned char"):
assert self.finalized
print("%s %s[%d] = {" % (ctype, self.cname, len(self.data)), file=f)
column = 0
for v in self.data:
if column == 0:
print(" ", end="", file=f)
column += 2
s = "%d," % v
print(s, end="", file=f)
column += len(s)
if column > 75:
print(file=f)
column = 0
if column != 0:
print(file=f)
for cursor in self.exports:
print("#define %s ((%s*)(%s+%d))" % (self.labels_byaddr[cursor], self.ltypes_byaddr[cursor], self.cname, cursor), file=f)
print("};", file=f)
def push(self, name):
print("push %s" % name)
def write_uint64_c(self, f, ctype = "const uint64_t"):
assert self.finalized
print("%s %s[%d] = {" % (ctype, self.cname, (len(self.data)+7) // 8), file=f)
column = 0
for i in range((len(self.data)+7) // 8):
v0 = self.data[8*i+0] if 8*i+0 < len(self.data) else 0
v1 = self.data[8*i+1] if 8*i+1 < len(self.data) else 0
v2 = self.data[8*i+2] if 8*i+2 < len(self.data) else 0
v3 = self.data[8*i+3] if 8*i+3 < len(self.data) else 0
v4 = self.data[8*i+4] if 8*i+4 < len(self.data) else 0
v5 = self.data[8*i+5] if 8*i+5 < len(self.data) else 0
v6 = self.data[8*i+6] if 8*i+6 < len(self.data) else 0
v7 = self.data[8*i+7] if 8*i+7 < len(self.data) else 0
if self.endianness == "le":
v = v0 << 0
v |= v1 << 8
v |= v2 << 16
v |= v3 << 24
v |= v4 << 32
v |= v5 << 40
v |= v6 << 48
v |= v7 << 56
elif self.endianness == "be":
v = v7 << 0
v |= v6 << 8
v |= v5 << 16
v |= v4 << 24
v |= v3 << 32
v |= v2 << 40
v |= v1 << 48
v |= v0 << 56
else:
assert 0
if column == 3:
print(" 0x%016x," % v, file=f)
column = 0
else:
if column == 0:
print(" ", end="", file=f)
print(" 0x%016x," % v, end="", file=f)
column += 1
if column != 0:
print("", file=f)
print("};", file=f)
def pop(self):
print("pop")
def write_string_c(self, f, ctype = "const char"):
assert self.finalized
assert self.data[len(self.data)-1] == 0
print("%s %s[%d] =" % (ctype, self.cname, len(self.data)), file=f)
print(" \"", end="", file=f)
column = 0
for i in range(len(self.data)-1):
if (self.data[i] < 32) or (self.data[i] > 126):
print("\\%03o" % self.data[i], end="", file=f)
column += 4
elif self.data[i] == ord('"') or self.data[i] == ord('\\'):
print("\\" + chr(self.data[i]), end="", file=f)
column += 2
else:
print(chr(self.data[i]), end="", file=f)
column += 1
if column > 70 and (i != len(self.data)-2):
print("\"\n \"", end="", file=f)
column = 0
print("\";", file=f)
def write_binary(self, f):
assert self.finalized
assert self.data[len(self.data)-1] == 0
f.buffer.write(self.data)
bba = BinaryBlobAssembler("chipdb_blob_%s" % dev_name, endianness)
bba = BinaryBlobAssembler()
bba.pre('#include "nextpnr.h"')
bba.pre('NEXTPNR_NAMESPACE_BEGIN')
bba.post('NEXTPNR_NAMESPACE_END')
bba.push("chipdb_blob_%s" % dev_name)
bba.r("chip_info_%s" % dev_name, "chip_info")
index = 0
@ -1001,15 +788,6 @@ for wire in range(num_wires):
num_downhill = 0
list_downhill = None
if wire in wire_downhill_belports:
num_bels_downhill = len(wire_downhill_belports[wire])
bba.l("wire%d_downbels" % wire, "BelPortPOD")
for belport in sorted(wire_downhill_belports[wire]):
bba.u32(belport[0], "bel_index")
bba.u32(portpins[belport[1]], "port")
else:
num_bels_downhill = 0
if wire in wire_belports:
num_bel_pins = len(wire_belports[wire])
bba.l("wire%d_bels" % wire, "BelPortPOD")
@ -1028,19 +806,9 @@ for wire in range(num_wires):
info["num_downhill"] = num_downhill
info["list_downhill"] = list_downhill
info["num_bels_downhill"] = num_bels_downhill
info["list_bels_downhill"] = ("wire%d_downbels" % wire) if num_bels_downhill > 0 else None
info["num_bel_pins"] = num_bel_pins
info["list_bel_pins"] = ("wire%d_bels" % wire) if num_bel_pins > 0 else None
if wire in wire_uphill_belport:
info["uphill_bel"] = wire_uphill_belport[wire][0]
info["uphill_pin"] = portpins[wire_uphill_belport[wire][1]]
else:
info["uphill_bel"] = -1
info["uphill_pin"] = 0
avg_x, avg_y = 0, 0
if wire in wire_xy:
for x, y in wire_xy[wire]:
@ -1115,10 +883,6 @@ for wire, info in enumerate(wireinfo):
bba.u32(info["num_downhill"], "num_downhill")
bba.r(info["list_uphill"], "pips_uphill")
bba.r(info["list_downhill"], "pips_downhill")
bba.u32(info["num_bels_downhill"], "num_bels_downhill")
bba.u32(info["uphill_bel"], "bel_uphill.bel_index")
bba.u32(info["uphill_pin"], "bel_uphill.port")
bba.r(info["list_bels_downhill"], "bels_downhill")
bba.u32(info["num_bel_pins"], "num_bel_pins")
bba.r(info["list_bel_pins"], "bel_pins")
bba.u32(len(wire_segments[wire]), "num_segments")
@ -1264,23 +1028,4 @@ bba.r("bits_info_%s" % dev_name, "bits_info")
bba.r("bel_config_%s" % dev_name if len(extra_cell_config) > 0 else None, "bel_config")
bba.r("package_info_%s" % dev_name, "packages_data")
bba.finalize()
if args.c_file:
print('#include "nextpnr.h"')
print('NEXTPNR_NAMESPACE_BEGIN')
if args.binary:
bba.write_binary(sys.stdout)
if args.c_file:
bba.write_string_c(sys.stdout)
# bba.write_uint64_c(sys.stdout)
# bba.write_compact_c(sys.stdout, "uint8_t")
# bba.write_verbose_c(sys.stdout, "uint8_t")
if args.c_file:
print('NEXTPNR_NAMESPACE_END')
bba.pop()

View File

@ -19,13 +19,18 @@ if (MSVC)
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/ice40/resources/chipdb.rc PROPERTIES LANGUAGE RC)
foreach (dev ${devices})
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_DB}
COMMAND ${PYTHON_EXECUTABLE} ${DB_PY} -b -p ${DEV_PORTS_INC} -g ${DEV_GFXH} ${DEV_TXT_DB} > ${DEV_CC_DB}
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}
DEPENDS ${DEV_TXT_DB} ${DB_PY}
)
add_custom_command(OUTPUT ${DEV_CC_DB}
COMMAND bbasm ${DEV_CC_BBA_DB} ${DEV_CC_DB}
DEPENDS bbasm ${DEV_CC_BBA_DB}
)
target_sources(ice40_chipdb PRIVATE ${DEV_CC_DB})
set_source_files_properties(${DEV_CC_DB} PROPERTIES HEADER_FILE_ONLY TRUE)
foreach (target ${family_targets})
@ -36,14 +41,20 @@ else()
target_compile_options(ice40_chipdb PRIVATE -g0 -O0 -w)
foreach (dev ${devices})
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_DB}
COMMAND ${PYTHON_EXECUTABLE} ${DB_PY} -c -p ${DEV_PORTS_INC} -g ${DEV_GFXH} ${DEV_TXT_DB} > ${DEV_CC_DB}.new
COMMAND mv ${DEV_CC_DB}.new ${DEV_CC_DB}
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 mv ${DEV_CC_BBA_DB}.new ${DEV_CC_BBA_DB}
DEPENDS ${DEV_TXT_DB} ${DB_PY}
)
)
add_custom_command(OUTPUT ${DEV_CC_DB}
COMMAND bbasm --c ${DEV_CC_BBA_DB} ${DEV_CC_DB}.new
COMMAND mv ${DEV_CC_DB}.new ${DEV_CC_DB}
DEPENDS bbasm ${DEV_CC_BBA_DB}
)
target_sources(ice40_chipdb PRIVATE ${DEV_CC_DB})
foreach (target ${family_targets})
target_sources(${target} PRIVATE $<TARGET_OBJECTS:ice40_chipdb>)

View File

@ -24,7 +24,7 @@ NEXTPNR_NAMESPACE_BEGIN
void gfxTileWire(std::vector<GraphicElement> &g, int x, int y, GfxTileWireId id, GraphicElement::style_t style)
{
GraphicElement el;
el.type = GraphicElement::G_LINE;
el.type = GraphicElement::TYPE_LINE;
el.style = style;
// Horizontal Span-4 Wires
@ -647,7 +647,7 @@ void pipGfx(std::vector<GraphicElement> &g, int x, int y, float x1, float y1, fl
float ty = 0.5 * (y1 + y2);
GraphicElement el;
el.type = GraphicElement::G_LINE;
el.type = GraphicElement::TYPE_ARROW;
el.style = style;
if (fabsf(x1 - swx1) < 0.001 && fabsf(x2 - swx1) < 0.001) {
@ -701,6 +701,17 @@ void gfxTilePip(std::vector<GraphicElement> &g, int x, int y, GfxTileWireId src,
if (getWireXY_local(src, x1, y1) && getWireXY_local(dst, x2, y2))
pipGfx(g, x, y, x1, y1, x2, y2, local_swbox_x1, local_swbox_y1, local_swbox_x2, local_swbox_y2, style);
if (src == TILE_WIRE_CARRY_IN && dst == TILE_WIRE_CARRY_IN_MUX) {
GraphicElement el;
el.type = GraphicElement::TYPE_ARROW;
el.style = style;
el.x1 = x + logic_cell_x1 + 0.005 * 3;
el.x2 = el.x1;
el.y1 = y + 0.01;
el.y2 = y + 0.02;
g.push_back(el);
}
}
NEXTPNR_NAMESPACE_END

View File

@ -464,7 +464,11 @@ enum GfxTileWireId
TILE_WIRE_SP12_H_R_23,
TILE_WIRE_SP12_H_L_22,
TILE_WIRE_SP12_H_L_23
TILE_WIRE_SP12_H_L_23,
TILE_WIRE_PLLIN,
TILE_WIRE_PLLOUT_A,
TILE_WIRE_PLLOUT_B
};
void gfxTileWire(std::vector<GraphicElement> &g, int x, int y, GfxTileWireId id, GraphicElement::style_t style);

View File

@ -52,13 +52,13 @@ void svg_dump_decal(const Context *ctx, const DecalXY &decal)
const std::string style = "stroke=\"black\" stroke-width=\"0.1\" fill=\"none\"";
for (auto &el : ctx->getDecalGraphics(decal.decal)) {
if (el.type == GraphicElement::G_BOX) {
if (el.type == GraphicElement::TYPE_BOX) {
std::cout << "<rect x=\"" << (offset + scale * (decal.x + el.x1)) << "\" y=\""
<< (offset + scale * (decal.y + el.y1)) << "\" height=\"" << (scale * (el.y2 - el.y1))
<< "\" width=\"" << (scale * (el.x2 - el.x1)) << "\" " << style << "/>\n";
}
if (el.type == GraphicElement::G_LINE) {
if (el.type == GraphicElement::TYPE_LINE) {
std::cout << "<line x1=\"" << (offset + scale * (decal.x + el.x1)) << "\" y1=\""
<< (offset + scale * (decal.y + el.y1)) << "\" x2=\"" << (offset + scale * (decal.x + el.x2))
<< "\" y2=\"" << (offset + scale * (decal.y + el.y2)) << "\" " << style << "/>\n";
@ -144,18 +144,16 @@ int main(int argc, char *argv[])
#endif
if (vm.count("help") || argc == 1) {
help:
std::cout << boost::filesystem::basename(argv[0])
<< " -- Next Generation Place and Route (git "
"sha1 " GIT_COMMIT_HASH_STR ")\n";
std::cout << boost::filesystem::basename(argv[0]) << " -- Next Generation Place and Route (git "
"sha1 " GIT_COMMIT_HASH_STR ")\n";
std::cout << "\n";
std::cout << options << "\n";
return argc != 1;
}
if (vm.count("version")) {
std::cout << boost::filesystem::basename(argv[0])
<< " -- Next Generation Place and Route (git "
"sha1 " GIT_COMMIT_HASH_STR ")\n";
std::cout << boost::filesystem::basename(argv[0]) << " -- Next Generation Place and Route (git "
"sha1 " GIT_COMMIT_HASH_STR ")\n";
return 1;
}
@ -365,8 +363,12 @@ int main(int argc, char *argv[])
}
}
if (vm.count("freq"))
if (vm.count("freq")) {
ctx->target_freq = vm["freq"].as<double>() * 1e6;
ctx->user_freq = true;
} else {
log_warning("Target frequency not specified. Will optimise for max frequency.\n");
}
ctx->timing_driven = true;
if (vm.count("no-tmdriv"))

View File

@ -3,6 +3,7 @@
*
* Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com>
* Copyright (C) 2018 David Shah <david@symbioticeda.com>
* Copyright (C) 2018 Serge Bazanski <q3k@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
@ -311,6 +312,10 @@ static void set_net_constant(const Context *ctx, NetInfo *orig, NetInfo *constne
(user.port != ctx->id("CLK") &&
((constval && user.port == ctx->id("CE")) || (!constval && user.port != ctx->id("CE"))))) {
uc->ports[user.port].net = nullptr;
} else if (is_ram(ctx, uc) && !constval && user.port != ctx->id("RCLK") && user.port != ctx->id("RCLKN") &&
user.port != ctx->id("WCLK") && user.port != ctx->id("WCLKN") && user.port != ctx->id("RCLKE") &&
user.port != ctx->id("WCLKE")) {
uc->ports[user.port].net = nullptr;
} else {
uc->ports[user.port].net = constnet;
constnet->users.push_back(user);
@ -536,6 +541,56 @@ static void promote_globals(Context *ctx)
}
}
// spliceLUT adds a pass-through LUT LC between the given cell's output port
// and either all users or only non_LUT users.
static std::unique_ptr<CellInfo> spliceLUT(Context *ctx, CellInfo *ci, IdString portId, bool onlyNonLUTs)
{
auto port = ci->ports[portId];
NPNR_ASSERT(port.net != nullptr);
// Create pass-through LUT.
std::unique_ptr<CellInfo> pt = create_ice_cell(ctx, ctx->id("ICESTORM_LC"),
ci->name.str(ctx) + "$nextpnr_" + portId.str(ctx) + "_lut_through");
pt->params[ctx->id("LUT_INIT")] = "65280"; // output is always I3
// Create LUT output net.
std::unique_ptr<NetInfo> out_net = std::unique_ptr<NetInfo>(new NetInfo);
out_net->name = ctx->id(ci->name.str(ctx) + "$nextnr_" + portId.str(ctx) + "_lut_through_net");
out_net->driver.cell = pt.get();
out_net->driver.port = ctx->id("O");
pt->ports.at(ctx->id("O")).net = out_net.get();
// New users of the original cell's port
std::vector<PortRef> new_users;
for (const auto &user : port.net->users) {
if (onlyNonLUTs && user.cell->type == ctx->id("ICESTORM_LC")) {
new_users.push_back(user);
continue;
}
// Rewrite pointer into net in user.
user.cell->ports[user.port].net = out_net.get();
// Add user to net.
PortRef pr;
pr.cell = user.cell;
pr.port = user.port;
out_net->users.push_back(pr);
}
// Add LUT to new users.
PortRef pr;
pr.cell = pt.get();
pr.port = ctx->id("I3");
new_users.push_back(pr);
pt->ports.at(ctx->id("I3")).net = port.net;
// Replace users of the original net.
port.net->users = new_users;
ctx->nets[out_net->name] = std::move(out_net);
return pt;
}
// Pack special functions
static void pack_special(Context *ctx)
{
@ -590,6 +645,184 @@ static void pack_special(Context *ctx)
}
replace_port(ci, ctx->id(pi.name.c_str(ctx)), packed.get(), ctx->id(newname));
}
new_cells.push_back(std::move(packed));
} else if (is_sb_pll40(ctx, ci)) {
bool is_pad = is_sb_pll40_pad(ctx, ci);
bool is_core = !is_pad;
std::unique_ptr<CellInfo> packed =
create_ice_cell(ctx, ctx->id("ICESTORM_PLL"), ci->name.str(ctx) + "_PLL");
packed->attrs[ctx->id("TYPE")] = ci->type.str(ctx);
packed_cells.insert(ci->name);
for (auto attr : ci->attrs)
packed->attrs[attr.first] = attr.second;
for (auto param : ci->params)
packed->params[param.first] = param.second;
auto feedback_path = packed->params[ctx->id("FEEDBACK_PATH")];
packed->params[ctx->id("FEEDBACK_PATH")] =
feedback_path == "DELAY" ? "0" : feedback_path == "SIMPLE"
? "1"
: feedback_path == "PHASE_AND_DELAY"
? "2"
: feedback_path == "EXTERNAL" ? "6"
: feedback_path;
packed->params[ctx->id("PLLTYPE")] = std::to_string(sb_pll40_type(ctx, ci));
NetInfo *pad_packagepin_net = nullptr;
for (auto port : ci->ports) {
PortInfo &pi = port.second;
std::string newname = pi.name.str(ctx);
size_t bpos = newname.find('[');
if (bpos != std::string::npos) {
newname = newname.substr(0, bpos) + "_" + newname.substr(bpos + 1, (newname.size() - bpos) - 2);
}
if (pi.name == ctx->id("PLLOUTCOREA"))
newname = "PLLOUT_A";
if (pi.name == ctx->id("PLLOUTCOREB"))
newname = "PLLOUT_B";
if (pi.name == ctx->id("PLLOUTCORE"))
newname = "PLLOUT_A";
if (pi.name == ctx->id("PACKAGEPIN")) {
if (!is_pad) {
log_error(" PLL '%s' has a PACKAGEPIN but is not a PAD PLL", ci->name.c_str(ctx));
} else {
// We drop this port and instead place the PLL adequately below.
pad_packagepin_net = port.second.net;
NPNR_ASSERT(pad_packagepin_net != nullptr);
continue;
}
}
if (pi.name == ctx->id("REFERENCECLK")) {
if (!is_core)
log_error(" PLL '%s' has a REFERENCECLK but is not a CORE PLL", ci->name.c_str(ctx));
}
replace_port(ci, ctx->id(pi.name.c_str(ctx)), packed.get(), ctx->id(newname));
}
// If PLL is not constrained already, do that - we need this
// information to then constrain the LOCK LUT.
BelId pll_bel;
bool constrained = false;
if (packed->attrs.find(ctx->id("BEL")) == packed->attrs.end()) {
for (auto bel : ctx->getBels()) {
if (ctx->getBelType(bel) != TYPE_ICESTORM_PLL)
continue;
// A PAD PLL must have its' PACKAGEPIN on the SB_IO that's shared
// with PLLOUT_A.
if (is_pad) {
auto pll_sb_io_belpin = ctx->getIOBSharingPLLPin(bel, PIN_PLLOUT_A);
NPNR_ASSERT(pad_packagepin_net != nullptr);
auto pll_packagepin_driver = pad_packagepin_net->driver;
NPNR_ASSERT(pll_packagepin_driver.cell != nullptr);
if (pll_packagepin_driver.cell->type != ctx->id("SB_IO")) {
log_error(" PLL '%s' has a PACKAGEPIN driven by "
"an %s, should be directly connected to an input SB_IO\n",
ci->name.c_str(ctx), pll_packagepin_driver.cell->type.c_str(ctx));
}
auto packagepin_cell = pll_packagepin_driver.cell;
auto packagepin_bel_name = packagepin_cell->attrs.find(ctx->id("BEL"));
if (packagepin_bel_name == packagepin_cell->attrs.end()) {
log_error(" PLL '%s' PACKAGEPIN SB_IO '%s' is unconstrained\n", ci->name.c_str(ctx),
packagepin_cell->name.c_str(ctx));
}
auto packagepin_bel = ctx->getBelByName(ctx->id(packagepin_bel_name->second));
if (pll_sb_io_belpin.bel != packagepin_bel) {
log_error(" PLL '%s' PACKAGEPIN is connected to pin %s, can only be pin %s\n",
ci->name.c_str(ctx), ctx->getBelPackagePin(packagepin_bel).c_str(),
ctx->getBelPackagePin(pll_sb_io_belpin.bel).c_str());
}
if (pad_packagepin_net->users.size() != 1) {
log_error(" PLL '%s' clock input '%s' can only drive PLL\n", ci->name.c_str(ctx),
pad_packagepin_net->name.c_str(ctx));
}
// Set an attribute about this PLL's PAD SB_IO.
packed->attrs[ctx->id("BEL_PAD_INPUT")] = packagepin_bel_name->second;
// Remove the connection from the SB_IO to the PLL.
packagepin_cell->ports.erase(pll_packagepin_driver.port);
}
log_info(" constrained '%s' to %s\n", packed->name.c_str(ctx), ctx->getBelName(bel).c_str(ctx));
packed->attrs[ctx->id("BEL")] = ctx->getBelName(bel).str(ctx);
pll_bel = bel;
constrained = true;
}
if (!constrained) {
log_error(" could not constrain '%s' to any PLL Bel\n", packed->name.c_str(ctx));
}
}
// Delete the original PACKAGEPIN net if needed.
if (pad_packagepin_net != nullptr) {
for (auto user : pad_packagepin_net->users) {
user.cell->ports.erase(user.port);
}
ctx->nets.erase(pad_packagepin_net->name);
pad_packagepin_net = nullptr;
}
// The LOCK signal on iCE40 PLLs goes through the neigh_op_bnl_1 wire.
// In practice, this means the LOCK signal can only directly reach LUT
// inputs.
// If we have a net connected to LOCK, make sure it only drives LUTs.
auto port = packed->ports[ctx->id("LOCK")];
if (port.net != nullptr) {
bool found_lut = false;
bool all_luts = true;
unsigned int lut_count = 0;
for (const auto &user : port.net->users) {
NPNR_ASSERT(user.cell != nullptr);
if (user.cell->type == ctx->id("ICESTORM_LC")) {
found_lut = true;
lut_count++;
} else {
all_luts = false;
}
}
if (found_lut && all_luts) {
// Every user is a LUT, carry on now.
} else if (found_lut && !all_luts && lut_count < 8) {
// Strategy: create a pass-through LUT, move all non-LUT users behind it.
log_info(" LUT strategy for %s: move non-LUT users to new LUT\n", port.name.c_str(ctx));
auto pt = spliceLUT(ctx, packed.get(), port.name, true);
new_cells.push_back(std::move(pt));
} else {
// Strategy: create a pass-through LUT, move every user behind it.
log_info(" LUT strategy for %s: move all users to new LUT\n", port.name.c_str(ctx));
auto pt = spliceLUT(ctx, packed.get(), port.name, false);
new_cells.push_back(std::move(pt));
}
// Find wire that will be driven by this port.
const auto pll_out_wire = ctx->getBelPinWire(pll_bel, ctx->portPinFromId(port.name));
NPNR_ASSERT(pll_out_wire.index != -1);
// Now, constrain all LUTs on the output of the signal to be at
// the correct Bel relative to the PLL Bel.
int x = ctx->chip_info->wire_data[pll_out_wire.index].x;
int y = ctx->chip_info->wire_data[pll_out_wire.index].y;
int z = 0;
for (const auto &user : port.net->users) {
NPNR_ASSERT(user.cell != nullptr);
NPNR_ASSERT(user.cell->type == ctx->id("ICESTORM_LC"));
// TODO(q3k): handle when the Bel might be already the
// target of another constraint.
NPNR_ASSERT(z < 8);
auto target_bel = ctx->getBelByLocation(Loc(x, y, z++));
auto target_bel_name = ctx->getBelName(target_bel).str(ctx);
user.cell->attrs[ctx->id("BEL")] = target_bel_name;
log_info(" constrained '%s' to %s\n", user.cell->name.c_str(ctx), target_bel_name.c_str());
}
}
new_cells.push_back(std::move(packed));
}
}

42
ice40/picorv32_benchmark.py Executable file
View File

@ -0,0 +1,42 @@
#!/usr/bin/env python3
import os, sys, threading
from os import path
import subprocess
import re
num_runs = 8
if not path.exists("picorv32.json"):
subprocess.run(["wget", "https://raw.githubusercontent.com/cliffordwolf/picorv32/master/picorv32.v"], check=True)
subprocess.run(["yosys", "-q", "-p", "synth_ice40 -json picorv32.json -top top", "picorv32.v", "picorv32_top.v"], check=True)
fmax = {}
if not path.exists("picorv32_work"):
os.mkdir("picorv32_work")
threads = []
for i in range(num_runs):
def runner(run):
ascfile = "picorv32_work/picorv32_s{}.asc".format(run)
if path.exists(ascfile):
os.remove(ascfile)
result = subprocess.run(["../nextpnr-ice40", "--hx8k", "--seed", str(run), "--json", "picorv32.json", "--asc", ascfile, "--freq", "70"], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
if result.returncode != 0:
print("Run {} failed!".format(run))
else:
icetime_res = subprocess.check_output(["icetime", "-d", "hx8k", ascfile])
fmax_m = re.search(r'\(([0-9.]+) MHz\)', icetime_res.decode('utf-8'))
fmax[run] = float(fmax_m.group(1))
threads.append(threading.Thread(target=runner, args=[i+1]))
for t in threads: t.start()
for t in threads: t.join()
fmax_min = min(fmax.values())
fmax_max = max(fmax.values())
fmax_avg = sum(fmax.values()) / len(fmax)
print("{}/{} runs passed".format(len(fmax), num_runs))
print("icetime: min = {} MHz, avg = {} MHz, max = {} MHz".format(fmax_min, fmax_avg, fmax_max))

View File

@ -75,11 +75,9 @@ static void get_chain_midpoint(const Context *ctx, const CellChain &chain, float
for (auto cell : chain.cells) {
if (cell->bel == BelId())
continue;
int bel_x, bel_y;
bool bel_gb;
ctx->estimatePosition(cell->bel, bel_x, bel_y, bel_gb);
total_x += bel_x;
total_y += bel_y;
Loc bel_loc = ctx->getBelLocation(cell->bel);
total_x += bel_loc.x;
total_y += bel_loc.y;
N++;
}
NPNR_ASSERT(N > 0);
@ -92,21 +90,20 @@ static int get_cell_evilness(const Context *ctx, const CellInfo *cell)
// This returns how "evil" a logic cell is, and thus how likely it is to be ripped up
// during logic tile legalisation
int score = 0;
if (get_net_or_empty(cell, ctx->id("I0")))
if (get_net_or_empty(cell, ctx->id_i0))
++score;
if (get_net_or_empty(cell, ctx->id("I1")))
if (get_net_or_empty(cell, ctx->id_i1))
++score;
if (get_net_or_empty(cell, ctx->id("I2")))
if (get_net_or_empty(cell, ctx->id_i2))
++score;
if (get_net_or_empty(cell, ctx->id("I3")))
if (get_net_or_empty(cell, ctx->id_i3))
++score;
if (bool_or_default(cell->params, ctx->id("DFF_ENABLE"))) {
const NetInfo *cen = get_net_or_empty(cell, ctx->id("CEN")), *sr = get_net_or_empty(cell, ctx->id("SR"));
if (cen)
if (cell->lcInfo.dffEnable) {
if (cell->lcInfo.cen)
score += 10;
if (sr)
if (cell->lcInfo.sr)
score += 10;
if (bool_or_default(cell->params, ctx->id("NEG_CLK")))
if (cell->lcInfo.negClk)
score += 5;
}
return score;

View File

@ -118,6 +118,8 @@ X(DYNAMICDELAY_5)
X(DYNAMICDELAY_6)
X(DYNAMICDELAY_7)
X(LOCK)
X(PLLOUT_A)
X(PLLOUT_B)
X(BYPASS)
X(RESETB)
X(LATCHINPUTVALUE)