Merge branch 'redist_slack' into 'redist_slack'
Redist slack See merge request eddiehung/nextpnr!10
This commit is contained in:
commit
56d551d407
2
.gitignore
vendored
2
.gitignore
vendored
@ -25,3 +25,5 @@ build/
|
|||||||
/Testing/*
|
/Testing/*
|
||||||
CTestTestfile.cmake
|
CTestTestfile.cmake
|
||||||
install_manifest.txt
|
install_manifest.txt
|
||||||
|
/bbasm
|
||||||
|
/ImportExecutables.cmake
|
||||||
|
@ -54,7 +54,7 @@ QT_BEGIN_NAMESPACE
|
|||||||
class QtPropertyPrivate
|
class QtPropertyPrivate
|
||||||
{
|
{
|
||||||
public:
|
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;
|
QtProperty *q_ptr;
|
||||||
|
|
||||||
QSet<QtProperty *> m_parentItems;
|
QSet<QtProperty *> m_parentItems;
|
||||||
@ -66,6 +66,7 @@ public:
|
|||||||
QString m_name;
|
QString m_name;
|
||||||
QString m_id;
|
QString m_id;
|
||||||
bool m_enabled;
|
bool m_enabled;
|
||||||
|
bool m_selectable;
|
||||||
bool m_modified;
|
bool m_modified;
|
||||||
|
|
||||||
QtAbstractPropertyManager * const m_manager;
|
QtAbstractPropertyManager * const m_manager;
|
||||||
@ -260,6 +261,11 @@ bool QtProperty::isEnabled() const
|
|||||||
return d_ptr->m_enabled;
|
return d_ptr->m_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool QtProperty::isSelectable() const
|
||||||
|
{
|
||||||
|
return d_ptr->m_selectable;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Returns whether the property is modified.
|
Returns whether the property is modified.
|
||||||
|
|
||||||
@ -409,6 +415,15 @@ void QtProperty::setEnabled(bool enable)
|
|||||||
propertyChanged();
|
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.
|
Sets the property's modified state according to the passed \a modified value.
|
||||||
|
|
||||||
|
@ -83,6 +83,7 @@ public:
|
|||||||
QString propertyName() const;
|
QString propertyName() const;
|
||||||
QString propertyId() const;
|
QString propertyId() const;
|
||||||
bool isEnabled() const;
|
bool isEnabled() const;
|
||||||
|
bool isSelectable() const;
|
||||||
bool isModified() const;
|
bool isModified() const;
|
||||||
|
|
||||||
bool hasValue() const;
|
bool hasValue() const;
|
||||||
@ -97,6 +98,7 @@ public:
|
|||||||
void setPropertyName(const QString &text);
|
void setPropertyName(const QString &text);
|
||||||
void setPropertyId(const QString &text);
|
void setPropertyId(const QString &text);
|
||||||
void setEnabled(bool enable);
|
void setEnabled(bool enable);
|
||||||
|
void setSelectable(bool selectable);
|
||||||
void setModified(bool modified);
|
void setModified(bool modified);
|
||||||
|
|
||||||
bool isSubProperty()const;
|
bool isSubProperty()const;
|
||||||
|
@ -651,6 +651,11 @@ void QtTreePropertyBrowserPrivate::updateItem(QTreeWidgetItem *item)
|
|||||||
else
|
else
|
||||||
disableItem(item);
|
disableItem(item);
|
||||||
}
|
}
|
||||||
|
if (property->isSelectable()) {
|
||||||
|
item->setFlags(item->flags() | Qt::ItemIsSelectable);
|
||||||
|
} else {
|
||||||
|
item->setFlags(item->flags() & ~Qt::ItemIsSelectable);
|
||||||
|
}
|
||||||
m_treeWidget->viewport()->update();
|
m_treeWidget->viewport()->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1339,6 +1339,7 @@ void addPropertyRecusively(QtVariantPropertyManager * manager,
|
|||||||
newProp->setWhatsThis(prop->whatsThis());
|
newProp->setWhatsThis(prop->whatsThis());
|
||||||
newProp->setModified(prop->isModified());
|
newProp->setModified(prop->isModified());
|
||||||
newProp->setEnabled(prop->isEnabled());
|
newProp->setEnabled(prop->isEnabled());
|
||||||
|
newProp->setSelectable(prop->isSelectable());
|
||||||
newProp->setValue(prop->value());
|
newProp->setValue(prop->value());
|
||||||
|
|
||||||
foreach(QtProperty * subProp, prop->subProperties())
|
foreach(QtProperty * subProp, prop->subProperties())
|
||||||
|
@ -167,6 +167,8 @@ if(MINGW)
|
|||||||
add_definitions("-Wa,-mbig-obj")
|
add_definitions("-Wa,-mbig-obj")
|
||||||
endif(MINGW)
|
endif(MINGW)
|
||||||
|
|
||||||
|
include(bba/bba.cmake)
|
||||||
|
|
||||||
foreach (family ${ARCH})
|
foreach (family ${ARCH})
|
||||||
message(STATUS "Configuring architecture : ${family}")
|
message(STATUS "Configuring architecture : ${family}")
|
||||||
string(TOUPPER ${family} ufamily)
|
string(TOUPPER ${family} ufamily)
|
||||||
|
74
bba/README.md
Normal file
74
bba/README.md
Normal 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
13
bba/bba.cmake
Normal 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
427
bba/main.cc
Normal 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;
|
||||||
|
}
|
@ -17,8 +17,8 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "nextpnr.h"
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
#include "nextpnr.h"
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
#define dbg(...) log(__VA_ARGS__)
|
#define dbg(...) log(__VA_ARGS__)
|
||||||
@ -84,8 +84,7 @@ void archcheck_locs(const Context *ctx)
|
|||||||
|
|
||||||
log_info("Checking all locations..\n");
|
log_info("Checking all locations..\n");
|
||||||
for (int x = 0; x < ctx->getGridDimX(); x++)
|
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);
|
dbg("> %d %d\n", x, y);
|
||||||
std::unordered_set<int> usedz;
|
std::unordered_set<int> usedz;
|
||||||
|
|
||||||
|
@ -23,10 +23,10 @@
|
|||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <pthread.h>
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@ -79,19 +79,19 @@ class assertion_failure : public std::runtime_error
|
|||||||
};
|
};
|
||||||
|
|
||||||
NPNR_NORETURN
|
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);
|
throw assertion_failure(message, expr_str, filename, line);
|
||||||
}
|
}
|
||||||
|
|
||||||
NPNR_NORETURN
|
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);
|
throw assertion_failure(message, expr_str, filename, line);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define NPNR_ASSERT(cond) ((void)((cond) || (assert_fail_impl(#cond, #cond, __FILE__, __LINE__))))
|
#define NPNR_ASSERT(cond) (!(cond) ? assert_fail_impl(#cond, #cond, __FILE__, __LINE__) : (void)true)
|
||||||
#define NPNR_ASSERT_MSG(cond, msg) ((void)((cond) || (assert_fail_impl(msg, #cond, __FILE__, __LINE__))))
|
#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(msg) (assert_fail_impl(msg, "false", __FILE__, __LINE__))
|
||||||
#define NPNR_ASSERT_FALSE_STR(msg) (assert_fail_impl_str(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
|
enum type_t
|
||||||
{
|
{
|
||||||
G_NONE,
|
TYPE_NONE,
|
||||||
G_LINE,
|
TYPE_LINE,
|
||||||
G_BOX,
|
TYPE_ARROW,
|
||||||
G_CIRCLE,
|
TYPE_BOX,
|
||||||
G_LABEL
|
TYPE_CIRCLE,
|
||||||
} type = G_NONE;
|
TYPE_LABEL,
|
||||||
|
|
||||||
|
TYPE_MAX
|
||||||
|
} type = TYPE_NONE;
|
||||||
|
|
||||||
enum style_t
|
enum style_t
|
||||||
{
|
{
|
||||||
G_FRAME,
|
STYLE_FRAME, // Static "frame". Contrast between STYLE_INACTIVE and STYLE_ACTIVE
|
||||||
G_HIDDEN,
|
STYLE_HIDDEN, // Only display when object is selected or highlighted
|
||||||
G_INACTIVE,
|
STYLE_INACTIVE, // Render using low-contrast color
|
||||||
G_ACTIVE,
|
STYLE_ACTIVE, // Render using high-contast color
|
||||||
} style = G_FRAME;
|
|
||||||
|
STYLE_MAX
|
||||||
|
} style = STYLE_FRAME;
|
||||||
|
|
||||||
float x1 = 0, y1 = 0, x2 = 0, y2 = 0, z = 0;
|
float x1 = 0, y1 = 0, x2 = 0, y2 = 0, z = 0;
|
||||||
std::string text;
|
std::string text;
|
||||||
@ -272,6 +277,16 @@ struct CellInfo : ArchCellInfo
|
|||||||
|
|
||||||
// cell_port -> bel_pin
|
// cell_port -> bel_pin
|
||||||
std::unordered_map<IdString, IdString> pins;
|
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
|
struct DeterministicRNG
|
||||||
@ -343,7 +358,7 @@ struct BaseCtx
|
|||||||
{
|
{
|
||||||
// Lock to perform mutating actions on the Context.
|
// Lock to perform mutating actions on the Context.
|
||||||
std::mutex mutex;
|
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()
|
// 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
|
// method will lock/unlock it when its' released the main mutex to make
|
||||||
@ -376,12 +391,12 @@ struct BaseCtx
|
|||||||
void lock(void)
|
void lock(void)
|
||||||
{
|
{
|
||||||
mutex.lock();
|
mutex.lock();
|
||||||
mutex_owner = pthread_self();
|
mutex_owner = std::this_thread::get_id();
|
||||||
}
|
}
|
||||||
|
|
||||||
void unlock(void)
|
void unlock(void)
|
||||||
{
|
{
|
||||||
NPNR_ASSERT(pthread_equal(pthread_self(), mutex_owner) != 0);
|
NPNR_ASSERT(std::this_thread::get_id() == mutex_owner);
|
||||||
mutex.unlock();
|
mutex.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,6 +470,7 @@ struct Context : Arch, DeterministicRNG
|
|||||||
bool force = false;
|
bool force = false;
|
||||||
bool timing_driven = true;
|
bool timing_driven = true;
|
||||||
float target_freq = 12e6;
|
float target_freq = 12e6;
|
||||||
|
bool user_freq = false;
|
||||||
|
|
||||||
Context(ArchArgs args) : Arch(args) {}
|
Context(ArchArgs args) : Arch(args) {}
|
||||||
|
|
||||||
|
@ -28,19 +28,20 @@ NEXTPNR_NAMESPACE_BEGIN
|
|||||||
wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type, float &tns)
|
wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type, float &tns)
|
||||||
{
|
{
|
||||||
wirelen_t wirelength = 0;
|
wirelen_t wirelength = 0;
|
||||||
int driver_x, driver_y;
|
Loc driver_loc;
|
||||||
bool driver_gb;
|
bool driver_gb;
|
||||||
CellInfo *driver_cell = net->driver.cell;
|
CellInfo *driver_cell = net->driver.cell;
|
||||||
if (!driver_cell)
|
if (!driver_cell)
|
||||||
return 0;
|
return 0;
|
||||||
if (driver_cell->bel == BelId())
|
if (driver_cell->bel == BelId())
|
||||||
return 0;
|
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));
|
WireId drv_wire = ctx->getBelPinWire(driver_cell->bel, ctx->portPinFromId(net->driver.port));
|
||||||
if (driver_gb)
|
if (driver_gb)
|
||||||
return 0;
|
return 0;
|
||||||
float worst_slack = 1000;
|
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) {
|
for (auto load : net->users) {
|
||||||
if (load.cell == nullptr)
|
if (load.cell == nullptr)
|
||||||
continue;
|
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);
|
worst_slack = std::min(slack, worst_slack);
|
||||||
}
|
}
|
||||||
|
|
||||||
int load_x, load_y;
|
if (ctx->getBelGlobalBuf(load_cell->bel))
|
||||||
bool load_gb;
|
|
||||||
ctx->estimatePosition(load_cell->bel, load_x, load_y, load_gb);
|
|
||||||
if (load_gb)
|
|
||||||
continue;
|
continue;
|
||||||
xmin = std::min(xmin, load_x);
|
Loc load_loc = ctx->getBelLocation(load_cell->bel);
|
||||||
ymin = std::min(ymin, load_y);
|
|
||||||
xmax = std::max(xmax, load_x);
|
xmin = std::min(xmin, load_loc.x);
|
||||||
ymax = std::max(ymax, load_y);
|
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) {
|
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)))));
|
wirelength = wirelen_t((((ymax - ymin) + (xmax - xmin)) * std::min(5.0, (1.0 + std::exp(-worst_slack / 5)))));
|
||||||
|
@ -50,9 +50,7 @@ class SAPlacer
|
|||||||
{
|
{
|
||||||
int num_bel_types = 0;
|
int num_bel_types = 0;
|
||||||
for (auto bel : ctx->getBels()) {
|
for (auto bel : ctx->getBels()) {
|
||||||
int x, y;
|
Loc loc = ctx->getBelLocation(bel);
|
||||||
bool gb;
|
|
||||||
ctx->estimatePosition(bel, x, y, gb);
|
|
||||||
BelType type = ctx->getBelType(bel);
|
BelType type = ctx->getBelType(bel);
|
||||||
int type_idx;
|
int type_idx;
|
||||||
if (bel_types.find(type) == bel_types.end()) {
|
if (bel_types.find(type) == bel_types.end()) {
|
||||||
@ -63,13 +61,13 @@ class SAPlacer
|
|||||||
}
|
}
|
||||||
if (int(fast_bels.size()) < type_idx + 1)
|
if (int(fast_bels.size()) < type_idx + 1)
|
||||||
fast_bels.resize(type_idx + 1);
|
fast_bels.resize(type_idx + 1);
|
||||||
if (int(fast_bels.at(type_idx).size()) < (x + 1))
|
if (int(fast_bels.at(type_idx).size()) < (loc.x + 1))
|
||||||
fast_bels.at(type_idx).resize(x + 1);
|
fast_bels.at(type_idx).resize(loc.x + 1);
|
||||||
if (int(fast_bels.at(type_idx).at(x).size()) < (y + 1))
|
if (int(fast_bels.at(type_idx).at(loc.x).size()) < (loc.y + 1))
|
||||||
fast_bels.at(type_idx).at(x).resize(y + 1);
|
fast_bels.at(type_idx).at(loc.x).resize(loc.y + 1);
|
||||||
max_x = std::max(max_x, x);
|
max_x = std::max(max_x, loc.x);
|
||||||
max_y = std::max(max_y, y);
|
max_y = std::max(max_y, loc.y);
|
||||||
fast_bels.at(type_idx).at(x).at(y).push_back(bel);
|
fast_bels.at(type_idx).at(loc.x).at(loc.y).push_back(bel);
|
||||||
}
|
}
|
||||||
diameter = std::max(max_x, max_y) + 1;
|
diameter = std::max(max_x, max_y) + 1;
|
||||||
}
|
}
|
||||||
@ -96,7 +94,13 @@ class SAPlacer
|
|||||||
BelType bel_type = ctx->getBelType(bel);
|
BelType bel_type = ctx->getBelType(bel);
|
||||||
if (bel_type != ctx->belTypeFromId(cell->type)) {
|
if (bel_type != ctx->belTypeFromId(cell->type)) {
|
||||||
log_error("Bel \'%s\' of type \'%s\' does not match cell "
|
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),
|
loc_name.c_str(), ctx->belTypeToId(bel_type).c_str(ctx), cell->name.c_str(ctx),
|
||||||
cell->type.c_str(ctx));
|
cell->type.c_str(ctx));
|
||||||
}
|
}
|
||||||
@ -235,8 +239,7 @@ class SAPlacer
|
|||||||
diameter *= post_legalise_dia_scale;
|
diameter *= post_legalise_dia_scale;
|
||||||
ctx->shuffle(autoplaced);
|
ctx->shuffle(autoplaced);
|
||||||
assign_budget(ctx);
|
assign_budget(ctx);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
update_budget(ctx);
|
update_budget(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,6 +275,7 @@ class SAPlacer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
compute_fmax(ctx, true /* print_fmax */);
|
||||||
ctx->unlock();
|
ctx->unlock();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -387,8 +391,6 @@ class SAPlacer
|
|||||||
// SA acceptance criterea
|
// SA acceptance criterea
|
||||||
if (delta < 0 || (temp > 1e-6 && (ctx->rng() / float(0x3fffffff)) <= std::exp(-delta / temp))) {
|
if (delta < 0 || (temp > 1e-6 && (ctx->rng() / float(0x3fffffff)) <= std::exp(-delta / temp))) {
|
||||||
n_accept++;
|
n_accept++;
|
||||||
//if (delta < 2)
|
|
||||||
// improved = true;
|
|
||||||
} else {
|
} else {
|
||||||
if (other != IdString())
|
if (other != IdString())
|
||||||
ctx->unbindBel(oldBel);
|
ctx->unbindBel(oldBel);
|
||||||
@ -413,12 +415,10 @@ class SAPlacer
|
|||||||
BelId random_bel_for_cell(CellInfo *cell)
|
BelId random_bel_for_cell(CellInfo *cell)
|
||||||
{
|
{
|
||||||
BelType targetType = ctx->belTypeFromId(cell->type);
|
BelType targetType = ctx->belTypeFromId(cell->type);
|
||||||
int x, y;
|
Loc curr_loc = ctx->getBelLocation(cell->bel);
|
||||||
bool gb;
|
|
||||||
ctx->estimatePosition(cell->bel, x, y, gb);
|
|
||||||
while (true) {
|
while (true) {
|
||||||
int nx = ctx->rng(2 * diameter + 1) + std::max(x - 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(y - diameter, 0);
|
int ny = ctx->rng(2 * diameter + 1) + std::max(curr_loc.y - diameter, 0);
|
||||||
int beltype_idx = bel_types.at(targetType);
|
int beltype_idx = bel_types.at(targetType);
|
||||||
if (nx >= int(fast_bels.at(beltype_idx).size()))
|
if (nx >= int(fast_bels.at(beltype_idx).size()))
|
||||||
continue;
|
continue;
|
||||||
|
@ -613,38 +613,10 @@ bool router1(Context *ctx)
|
|||||||
|
|
||||||
std::unordered_set<IdString> normalRouteNets, ripupQueue;
|
std::unordered_set<IdString> normalRouteNets, ripupQueue;
|
||||||
|
|
||||||
if (iterCnt == 1) {
|
if (ctx->verbose || iterCnt == 1)
|
||||||
if (ctx->verbose)
|
log_info("routing queue contains %d jobs.\n", int(jobQueue.size()));
|
||||||
log_info("routing queue contains %d jobs.\n", int(jobQueue.size()));
|
|
||||||
} else {
|
update_budget(ctx);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool printNets = ctx->verbose && (jobQueue.size() < 10);
|
bool printNets = ctx->verbose && (jobQueue.size() < 10);
|
||||||
|
|
||||||
@ -841,14 +813,15 @@ bool router1(Context *ctx)
|
|||||||
log_info("Checksum: 0x%08x\n", ctx->checksum());
|
log_info("Checksum: 0x%08x\n", ctx->checksum());
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
ctx->check();
|
ctx->check();
|
||||||
ctx->unlock();
|
|
||||||
#endif
|
#endif
|
||||||
|
compute_fmax(ctx, true /* print_fmax */, true /* print_path */);
|
||||||
|
ctx->unlock();
|
||||||
return true;
|
return true;
|
||||||
} catch (log_execution_error_exception) {
|
} catch (log_execution_error_exception) {
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
ctx->check();
|
ctx->check();
|
||||||
ctx->unlock();
|
|
||||||
#endif
|
#endif
|
||||||
|
ctx->unlock();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
274
common/timing.cc
274
common/timing.cc
@ -26,16 +26,26 @@
|
|||||||
|
|
||||||
NEXTPNR_NAMESPACE_BEGIN
|
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
|
// 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;
|
delay_t value;
|
||||||
if (ctx->getPortClock(user.cell, user.port) != IdString()) {
|
if (ctx->getPortClock(user.cell, user.port) != IdString()) {
|
||||||
// At the end of a timing path (arguably, should check setup time
|
// At the end of a timing path (arguably, should check setup time
|
||||||
// here too)
|
// here too)
|
||||||
value = slack / path_length;
|
value = slack / path_length;
|
||||||
|
if (slack < min_slack) {
|
||||||
|
min_slack = slack;
|
||||||
|
if (crit_path)
|
||||||
|
*crit_path = *current_path;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Default to the path ending here, if no further paths found
|
// Default to the path ending here, if no further paths found
|
||||||
value = slack / path_length;
|
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) {
|
if (is_path) {
|
||||||
NetInfo *net = port.second.net;
|
NetInfo *net = port.second.net;
|
||||||
if (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);
|
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) {
|
if (updates) {
|
||||||
user.budget = value;
|
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;
|
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);
|
delay_t net_budget = slack / (path_length + 1);
|
||||||
for (auto &usr : net->users) {
|
for (unsigned i = 0; i < net->users.size(); ++i) {
|
||||||
net_budget = std::min(net_budget, follow_user_port(ctx, usr, path_length + 1, slack));
|
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;
|
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, ¤t_path, crit_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return min_slack;
|
||||||
|
}
|
||||||
|
|
||||||
void assign_budget(Context *ctx)
|
void assign_budget(Context *ctx)
|
||||||
{
|
{
|
||||||
log_break();
|
log_break();
|
||||||
@ -82,24 +139,35 @@ void assign_budget(Context *ctx)
|
|||||||
usr.budget = default_slack;
|
usr.budget = default_slack;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Go through all clocked drivers and set up paths
|
|
||||||
for (auto &cell : ctx->cells) {
|
UpdateMap updates;
|
||||||
for (auto port : cell.second->ports) {
|
delay_t min_slack = compute_min_slack(ctx, &updates, nullptr);
|
||||||
if (port.second.type == PORT_OUT) {
|
|
||||||
IdString clock_domain = ctx->getPortClock(cell.second.get(), port.first);
|
// If user has not specified a frequency, adjust the target frequency dynamically
|
||||||
if (clock_domain != IdString()) {
|
// TODO(eddieh): Tune these factors
|
||||||
delay_t slack = delay_t(1.0e12 / ctx->target_freq); // TODO: clock constraints
|
if (!ctx->user_freq) {
|
||||||
if (port.second.net)
|
if (min_slack < 0)
|
||||||
follow_net(ctx, port.second.net, 0, slack);
|
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 &net : ctx->nets) {
|
||||||
for (auto user : net.second->users) {
|
for (size_t i = 0; i < net.second->users.size(); ++i) {
|
||||||
if (user.budget < 0)
|
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 "
|
log_warning("port %s.%s, connected to net '%s', has negative "
|
||||||
"timing budget of %fns\n",
|
"timing budget of %fns\n",
|
||||||
user.cell->name.c_str(ctx), user.port.c_str(ctx), net.first.c_str(ctx),
|
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());
|
log_info("Checksum: 0x%08x\n", ctx->checksum());
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef std::unordered_map<const PortInfo*, delay_t> updates_t;
|
void update_budget(Context *ctx)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
delay_t value;
|
UpdateMap updates;
|
||||||
if (ctx->getPortClock(user.cell, user.port) != IdString()) {
|
delay_t min_slack = compute_min_slack(ctx, &updates, nullptr);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the budgets
|
// Update the budgets
|
||||||
for (auto &net : ctx->nets) {
|
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 pi = &user.cell->ports.at(user.port);
|
||||||
|
auto budget = ctx->getNetinfoRouteDelay(net.second.get(), i);
|
||||||
auto it = updates.find(pi);
|
auto it = updates.find(pi);
|
||||||
if (it == updates.end()) continue;
|
if (it != updates.end())
|
||||||
auto budget = delays.at(pi) + it->second;
|
budget -= it->second;
|
||||||
user.budget = ctx->getBudgetOverride(net.second->driver, budget);
|
user.budget = ctx->getBudgetOverride(net.second.get(), i, budget);
|
||||||
|
|
||||||
// Post-update check
|
// Post-update check
|
||||||
if (ctx->verbose) {
|
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 "
|
log_warning("port %s.%s, connected to net '%s', has negative "
|
||||||
"timing budget of %fns\n",
|
"timing budget of %fns\n",
|
||||||
user.cell->name.c_str(ctx), user.port.c_str(ctx), net.first.c_str(ctx),
|
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
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -27,7 +27,10 @@ NEXTPNR_NAMESPACE_BEGIN
|
|||||||
// Assign "budget" values for all user ports in the design
|
// Assign "budget" values for all user ports in the design
|
||||||
void assign_budget(Context *ctx);
|
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
|
NEXTPNR_NAMESPACE_END
|
||||||
|
|
||||||
|
92
ecp5/arch.cc
92
ecp5/arch.cc
@ -192,17 +192,20 @@ BelId Arch::getBelByName(IdString name) const
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
BelRange Arch::getBelsAtSameTile(BelId bel) const
|
BelRange Arch::getBelsByTile(int x, int y) const
|
||||||
{
|
{
|
||||||
BelRange br;
|
BelRange br;
|
||||||
NPNR_ASSERT(bel != BelId());
|
|
||||||
br.b.cursor_tile = bel.location.y * chip_info->width + bel.location.x;
|
br.b.cursor_tile = y * chip_info->width + x;
|
||||||
br.e.cursor_tile = bel.location.y * chip_info->width + bel.location.x;
|
br.e.cursor_tile = y * chip_info->width + x;
|
||||||
br.b.cursor_index = 0;
|
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.b.chip = chip_info;
|
||||||
br.e.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;
|
return br;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,6 +281,7 @@ PipId Arch::getPipByName(IdString name) const
|
|||||||
Location loc;
|
Location loc;
|
||||||
std::string basename;
|
std::string basename;
|
||||||
std::tie(loc.x, loc.y, basename) = split_identifier_name(name.str(this));
|
std::tie(loc.x, loc.y, basename) = split_identifier_name(name.str(this));
|
||||||
|
ret.location = loc;
|
||||||
const LocationTypePOD *loci = locInfo(ret);
|
const LocationTypePOD *loci = locInfo(ret);
|
||||||
for (int i = 0; i < loci->num_pips; i++) {
|
for (int i = 0; i < loci->num_pips; i++) {
|
||||||
PipId curr;
|
PipId curr;
|
||||||
@ -285,6 +289,8 @@ PipId Arch::getPipByName(IdString name) const
|
|||||||
curr.index = i;
|
curr.index = i;
|
||||||
pip_by_name[getPipName(curr)] = curr;
|
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];
|
return pip_by_name[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,13 +328,52 @@ BelId Arch::getPackagePinBel(const std::string &pin) const
|
|||||||
std::string Arch::getBelPackagePin(BelId bel) const
|
std::string Arch::getBelPackagePin(BelId bel) const
|
||||||
{
|
{
|
||||||
for (int i = 0; i < package_info->num_pins; i++) {
|
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 package_info->pin_data[i].name.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "";
|
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
|
std::vector<PortPin> Arch::getBelPins(BelId bel) const
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -361,45 +406,14 @@ BelId Arch::getBelByLocation(Loc loc) const
|
|||||||
return BelId();
|
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
|
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));
|
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
|
delay_t Arch::getBudgetOverride(NetInfo *net_info, int user_idx, delay_t budget) const { return budget; }
|
||||||
{
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
40
ecp5/arch.h
40
ecp5/arch.h
@ -98,7 +98,7 @@ NPNR_PACKED_STRUCT(struct LocationTypePOD {
|
|||||||
});
|
});
|
||||||
|
|
||||||
NPNR_PACKED_STRUCT(struct PIOInfoPOD {
|
NPNR_PACKED_STRUCT(struct PIOInfoPOD {
|
||||||
Location abs_loc;
|
LocationPOD abs_loc;
|
||||||
int32_t bel_index;
|
int32_t bel_index;
|
||||||
RelPtr<char> function_name;
|
RelPtr<char> function_name;
|
||||||
int16_t bank;
|
int16_t bank;
|
||||||
@ -107,7 +107,7 @@ NPNR_PACKED_STRUCT(struct PIOInfoPOD {
|
|||||||
|
|
||||||
NPNR_PACKED_STRUCT(struct PackagePinPOD {
|
NPNR_PACKED_STRUCT(struct PackagePinPOD {
|
||||||
RelPtr<char> name;
|
RelPtr<char> name;
|
||||||
Location abs_loc;
|
LocationPOD abs_loc;
|
||||||
int32_t bel_index;
|
int32_t bel_index;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -117,6 +117,26 @@ NPNR_PACKED_STRUCT(struct PackageInfoPOD {
|
|||||||
RelPtr<PackagePinPOD> pin_data;
|
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 {
|
NPNR_PACKED_STRUCT(struct ChipInfoPOD {
|
||||||
int32_t width, height;
|
int32_t width, height;
|
||||||
int32_t num_tiles;
|
int32_t num_tiles;
|
||||||
@ -124,6 +144,7 @@ NPNR_PACKED_STRUCT(struct ChipInfoPOD {
|
|||||||
int32_t num_packages, num_pios;
|
int32_t num_packages, num_pios;
|
||||||
RelPtr<LocationTypePOD> locations;
|
RelPtr<LocationTypePOD> locations;
|
||||||
RelPtr<int32_t> location_type;
|
RelPtr<int32_t> location_type;
|
||||||
|
RelPtr<GlobalInfoPOD> location_glbinfo;
|
||||||
RelPtr<RelPtr<char>> tiletype_names;
|
RelPtr<RelPtr<char>> tiletype_names;
|
||||||
RelPtr<PackageInfoPOD> package_info;
|
RelPtr<PackageInfoPOD> package_info;
|
||||||
RelPtr<PIOInfoPOD> pio_info;
|
RelPtr<PIOInfoPOD> pio_info;
|
||||||
@ -482,8 +503,6 @@ struct Arch : BaseCtx
|
|||||||
return range;
|
return range;
|
||||||
}
|
}
|
||||||
|
|
||||||
BelRange getBelsAtSameTile(BelId bel) const;
|
|
||||||
|
|
||||||
BelType getBelType(BelId bel) const
|
BelType getBelType(BelId bel) const
|
||||||
{
|
{
|
||||||
NPNR_ASSERT(bel != BelId());
|
NPNR_ASSERT(bel != BelId());
|
||||||
@ -519,6 +538,8 @@ struct Arch : BaseCtx
|
|||||||
return id(name.str());
|
return id(name.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IdString getWireType(WireId wire) const { return IdString(); }
|
||||||
|
|
||||||
uint32_t getWireChecksum(WireId wire) const { return wire.index; }
|
uint32_t getWireChecksum(WireId wire) const { return wire.index; }
|
||||||
|
|
||||||
void bindWire(WireId wire, IdString net, PlaceStrength strength)
|
void bindWire(WireId wire, IdString net, PlaceStrength strength)
|
||||||
@ -597,6 +618,8 @@ struct Arch : BaseCtx
|
|||||||
PipId getPipByName(IdString name) const;
|
PipId getPipByName(IdString name) const;
|
||||||
IdString getPipName(PipId pip) const;
|
IdString getPipName(PipId pip) const;
|
||||||
|
|
||||||
|
IdString getPipType(PipId pip) const { return IdString(); }
|
||||||
|
|
||||||
uint32_t getPipChecksum(PipId pip) const { return pip.index; }
|
uint32_t getPipChecksum(PipId pip) const { return pip.index; }
|
||||||
|
|
||||||
void bindPip(PipId pip, IdString net, PlaceStrength strength)
|
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();
|
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;
|
BelId getPackagePinBel(const std::string &pin) const;
|
||||||
std::string getBelPackagePin(BelId bel) 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;
|
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 estimateDelay(WireId src, WireId dst) const;
|
||||||
delay_t getDelayEpsilon() const { return 20; }
|
delay_t getDelayEpsilon() const { return 20; }
|
||||||
delay_t getRipupDelayPenalty() const { return 200; }
|
delay_t getRipupDelayPenalty() const { return 200; }
|
||||||
float getDelayNS(delay_t v) const { return v * 0.001; }
|
float getDelayNS(delay_t v) const { return v * 0.001; }
|
||||||
uint32_t getDelayChecksum(delay_t v) const { return v; }
|
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;
|
||||||
|
|
||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
|
|
||||||
|
@ -66,7 +66,8 @@ bool Arch::isBelLocationValid(BelId bel) const
|
|||||||
{
|
{
|
||||||
if (getBelType(bel) == TYPE_TRELLIS_SLICE) {
|
if (getBelType(bel) == TYPE_TRELLIS_SLICE) {
|
||||||
std::vector<const CellInfo *> bel_cells;
|
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);
|
IdString cell_other = getBoundBelCell(bel_other);
|
||||||
if (cell_other != IdString()) {
|
if (cell_other != IdString()) {
|
||||||
const CellInfo *ci_other = cells.at(cell_other).get();
|
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);
|
NPNR_ASSERT(getBelType(bel) == TYPE_TRELLIS_SLICE);
|
||||||
|
|
||||||
std::vector<const CellInfo *> bel_cells;
|
std::vector<const CellInfo *> bel_cells;
|
||||||
|
Loc bel_loc = getBelLocation(bel);
|
||||||
for (auto bel_other : getBelsAtSameTile(bel)) {
|
for (auto bel_other : getBelsByTile(bel_loc.x, bel_loc.y)) {
|
||||||
IdString cell_other = getBoundBelCell(bel_other);
|
IdString cell_other = getBoundBelCell(bel_other);
|
||||||
if (cell_other != IdString() && bel_other != bel) {
|
if (cell_other != IdString() && bel_other != bel) {
|
||||||
const CellInfo *ci_other = cells.at(cell_other).get();
|
const CellInfo *ci_other = cells.at(cell_other).get();
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
* nextpnr -- Next Generation Place and Route
|
* nextpnr -- Next Generation Place and Route
|
||||||
*
|
*
|
||||||
* Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com>
|
* 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
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
@ -20,13 +20,132 @@
|
|||||||
|
|
||||||
#ifndef NO_PYTHON
|
#ifndef NO_PYTHON
|
||||||
|
|
||||||
|
#include "arch_pybindings.h"
|
||||||
#include "nextpnr.h"
|
#include "nextpnr.h"
|
||||||
#include "pybindings.h"
|
#include "pybindings.h"
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_BEGIN
|
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
|
NEXTPNR_NAMESPACE_END
|
||||||
|
|
||||||
#endif
|
#endif // NO_PYTHON
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <streambuf>
|
#include <streambuf>
|
||||||
|
|
||||||
|
#include "io.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "util.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
|
// Add all set, configurable pips to the config
|
||||||
for (auto pip : ctx->getPips()) {
|
for (auto pip : ctx->getPips()) {
|
||||||
if (ctx->getBoundPipNet(pip) != IdString()) {
|
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,
|
std::string tile = empty_chip.get_tile_by_position_and_type(pip.location.y, pip.location.x,
|
||||||
ctx->getPipTiletype(pip));
|
ctx->getPipTiletype(pip));
|
||||||
std::string source = get_trellis_wirename(ctx, pip.location, ctx->getPipSrcWire(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) {
|
for (const auto &tile : empty_chip.tiles) {
|
||||||
std::string type = tile.second->info.type;
|
std::string type = tile.second->info.type;
|
||||||
if (type.find("BANKREF") != std::string::npos && type != "BANKREF8") {
|
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);
|
std::string pic_tile = get_pic_tile(ctx, empty_chip, bel);
|
||||||
cc.tiles[pio_tile].add_enum(pio + ".BASE_TYPE", dir + "_" + iotype);
|
cc.tiles[pio_tile].add_enum(pio + ".BASE_TYPE", dir + "_" + iotype);
|
||||||
cc.tiles[pic_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" &&
|
if (dir != "INPUT" &&
|
||||||
(ci->ports.find(ctx->id("T")) == ci->ports.end() || ci->ports.at(ctx->id("T")).net == nullptr)) {
|
(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
|
// 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();
|
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");
|
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");
|
cc.tiles[pio_tile].add_enum(pio + ".HYSTERESIS", "ON");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -24,11 +24,16 @@ if (MSVC)
|
|||||||
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/ecp5/resources/chipdb.rc PROPERTIES LANGUAGE RC)
|
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/ecp5/resources/chipdb.rc PROPERTIES LANGUAGE RC)
|
||||||
foreach (dev ${devices})
|
foreach (dev ${devices})
|
||||||
set(DEV_CC_DB ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/chipdbs/chipdb-${dev}.bin)
|
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)
|
set(DEV_PORTS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/portpins.inc)
|
||||||
add_custom_command(OUTPUT ${DEV_CC_DB}
|
add_custom_command(OUTPUT ${DEV_CC_BBA_DB}
|
||||||
COMMAND ${ENV_CMD} python3 ${DB_PY} -b -p ${DEV_PORTS_INC} ${dev} ${DEV_CC_DB}
|
COMMAND ${ENV_CMD} python3 ${DB_PY} -p ${DEV_PORTS_INC} ${dev} > ${DEV_CC_BBA_DB}
|
||||||
DEPENDS ${DB_PY}
|
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})
|
target_sources(ecp5_chipdb PRIVATE ${DEV_CC_DB})
|
||||||
set_source_files_properties(${DEV_CC_DB} PROPERTIES HEADER_FILE_ONLY TRUE)
|
set_source_files_properties(${DEV_CC_DB} PROPERTIES HEADER_FILE_ONLY TRUE)
|
||||||
foreach (target ${family_targets})
|
foreach (target ${family_targets})
|
||||||
@ -39,11 +44,18 @@ else()
|
|||||||
target_compile_options(ecp5_chipdb PRIVATE -g0 -O0 -w)
|
target_compile_options(ecp5_chipdb PRIVATE -g0 -O0 -w)
|
||||||
foreach (dev ${devices})
|
foreach (dev ${devices})
|
||||||
set(DEV_CC_DB ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/chipdbs/chipdb-${dev}.cc)
|
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)
|
set(DEV_PORTS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/portpins.inc)
|
||||||
add_custom_command(OUTPUT ${DEV_CC_DB}
|
add_custom_command(OUTPUT ${DEV_CC_BBA_DB}
|
||||||
COMMAND ${ENV_CMD} python3 ${DB_PY} -c -p ${DEV_PORTS_INC} ${dev} ${DEV_CC_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}
|
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})
|
target_sources(ecp5_chipdb PRIVATE ${DEV_CC_DB})
|
||||||
foreach (target ${family_targets})
|
foreach (target ${family_targets})
|
||||||
target_sources(${target} PRIVATE $<TARGET_OBJECTS:ecp5_chipdb>)
|
target_sources(${target} PRIVATE $<TARGET_OBJECTS:ecp5_chipdb>)
|
||||||
|
217
ecp5/io.cc
Normal file
217
ecp5/io.cc
Normal 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
70
ecp5/io.h
Normal 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
37
ecp5/iotypes.inc
Normal 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)
|
20
ecp5/main.cc
20
ecp5/main.cc
@ -63,6 +63,7 @@ int main(int argc, char *argv[])
|
|||||||
#ifndef NO_GUI
|
#ifndef NO_GUI
|
||||||
options.add_options()("gui", "start gui");
|
options.add_options()("gui", "start gui");
|
||||||
#endif
|
#endif
|
||||||
|
options.add_options()("test", "check architecture database integrity");
|
||||||
|
|
||||||
options.add_options()("25k", "set device type to LFE5U-25F");
|
options.add_options()("25k", "set device type to LFE5U-25F");
|
||||||
options.add_options()("45k", "set device type to LFE5U-45F");
|
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) {
|
if (vm.count("help") || argc == 1) {
|
||||||
std::cout << boost::filesystem::basename(argv[0])
|
std::cout << boost::filesystem::basename(argv[0]) << " -- Next Generation Place and Route (git "
|
||||||
<< " -- Next Generation Place and Route (git "
|
"sha1 " GIT_COMMIT_HASH_STR ")\n";
|
||||||
"sha1 " GIT_COMMIT_HASH_STR ")\n";
|
|
||||||
std::cout << "\n";
|
std::cout << "\n";
|
||||||
std::cout << options << "\n";
|
std::cout << options << "\n";
|
||||||
return argc != 1;
|
return argc != 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vm.count("version")) {
|
if (vm.count("version")) {
|
||||||
std::cout << boost::filesystem::basename(argv[0])
|
std::cout << boost::filesystem::basename(argv[0]) << " -- Next Generation Place and Route (git "
|
||||||
<< " -- Next Generation Place and Route (git "
|
"sha1 " GIT_COMMIT_HASH_STR ")\n";
|
||||||
"sha1 " GIT_COMMIT_HASH_STR ")\n";
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,6 +147,9 @@ int main(int argc, char *argv[])
|
|||||||
if (vm.count("no-tmdriv"))
|
if (vm.count("no-tmdriv"))
|
||||||
ctx->timing_driven = false;
|
ctx->timing_driven = false;
|
||||||
|
|
||||||
|
if (vm.count("test"))
|
||||||
|
ctx->archcheck();
|
||||||
|
|
||||||
#ifndef NO_GUI
|
#ifndef NO_GUI
|
||||||
if (vm.count("gui")) {
|
if (vm.count("gui")) {
|
||||||
Application a(argc, argv);
|
Application a(argc, argv);
|
||||||
@ -165,8 +167,12 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
if (!ctx->pack() && !ctx->force)
|
if (!ctx->pack() && !ctx->force)
|
||||||
log_error("Packing design failed.\n");
|
log_error("Packing design failed.\n");
|
||||||
if (vm.count("freq"))
|
if (vm.count("freq")) {
|
||||||
ctx->target_freq = vm["freq"].as<double>() * 1e6;
|
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());
|
assign_budget(ctx.get());
|
||||||
ctx->check();
|
ctx->check();
|
||||||
print_utilisation(ctx.get());
|
print_utilisation(ctx.get());
|
||||||
|
@ -10,11 +10,7 @@ type_at_location = dict()
|
|||||||
tiletype_names = dict()
|
tiletype_names = dict()
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="import ECP5 routing and bels from Project Trellis")
|
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("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")
|
parser.add_argument("-p", "--portspins", type=str, help="path to portpins.inc")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
@ -36,284 +32,50 @@ portpins = dict()
|
|||||||
|
|
||||||
|
|
||||||
class BinaryBlobAssembler:
|
class BinaryBlobAssembler:
|
||||||
def __init__(self, cname, endianness, nodebug=False):
|
def l(self, name, ltype = None, export = False):
|
||||||
assert endianness in ["le", "be"]
|
if ltype is None:
|
||||||
self.cname = cname
|
print("label %s" % (name,))
|
||||||
self.endianness = endianness
|
else:
|
||||||
self.finalized = False
|
print("label %s %s" % (name, ltype))
|
||||||
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 r(self, name, comment):
|
def r(self, name, comment):
|
||||||
assert not self.finalized
|
if comment is None:
|
||||||
assert len(self.data) % 4 == 0
|
print("ref %s" % (name,))
|
||||||
assert len(self.data) not in self.refs
|
else:
|
||||||
if self.nodebug:
|
print("ref %s %s" % (name, comment))
|
||||||
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)"
|
|
||||||
|
|
||||||
def s(self, s, comment):
|
def s(self, s, comment):
|
||||||
assert not self.finalized
|
print("str %s" % s)
|
||||||
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)
|
|
||||||
|
|
||||||
def u8(self, v, comment):
|
def u8(self, v, comment):
|
||||||
assert not self.finalized
|
if comment is None:
|
||||||
if self.nodebug:
|
print("u8 %d" % (v,))
|
||||||
comment = None
|
else:
|
||||||
self.data.append(v)
|
print("u8 %d %s" % (v, comment))
|
||||||
if comment is not None:
|
|
||||||
self.comments[len(self.data)] = comment
|
|
||||||
|
|
||||||
def u16(self, v, comment):
|
def u16(self, v, comment):
|
||||||
assert not self.finalized
|
if comment is None:
|
||||||
assert len(self.data) % 2 == 0
|
print("u16 %d" % (v,))
|
||||||
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)
|
|
||||||
else:
|
else:
|
||||||
assert 0
|
print("u16 %d %s" % (v, comment))
|
||||||
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
|
|
||||||
|
|
||||||
def u32(self, v, comment):
|
def u32(self, v, comment):
|
||||||
assert not self.finalized
|
if comment is None:
|
||||||
assert len(self.data) % 4 == 0
|
print("u32 %d" % (v,))
|
||||||
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)
|
|
||||||
else:
|
else:
|
||||||
assert 0
|
print("u32 %d %s" % (v, comment))
|
||||||
if comment is not None:
|
|
||||||
self.comments[len(self.data)] = comment
|
|
||||||
|
|
||||||
def finalize(self):
|
def pre(self, s):
|
||||||
assert not self.finalized
|
print("pre %s" % s)
|
||||||
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 write_verbose_c(self, f, ctype="const unsigned char"):
|
def post(self, s):
|
||||||
assert self.finalized
|
print("post %s" % s)
|
||||||
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 write_compact_c(self, f, ctype="const unsigned char"):
|
def push(self, name):
|
||||||
assert self.finalized
|
print("push %s" % name)
|
||||||
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 pop(self):
|
||||||
|
print("pop")
|
||||||
|
|
||||||
bel_types = {
|
bel_types = {
|
||||||
"NONE": 0,
|
"NONE": 0,
|
||||||
@ -364,13 +126,25 @@ def process_pio_db(ddrg, device):
|
|||||||
if bel_idx is not None:
|
if bel_idx is not None:
|
||||||
pindata.append((loc, bel_idx, bank, pinfunc))
|
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_database(dev_name, ddrg, endianness):
|
||||||
def write_loc(loc, sym_name):
|
def write_loc(loc, sym_name):
|
||||||
bba.s16(loc.x, "%s.x" % sym_name)
|
bba.u16(loc.x, "%s.x" % sym_name)
|
||||||
bba.s16(loc.y, "%s.y" % 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")
|
bba.r("chip_info", "chip_info")
|
||||||
|
|
||||||
loctypes = list([_.key() for _ in ddrg.locationTypes])
|
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 y in range(0, max_row+1):
|
||||||
for x in range(0, max_col+1):
|
for x in range(0, max_col+1):
|
||||||
bba.u32(loctypes.index(ddrg.typeAtLocation[pytrellis.Location(x, y)]), "loctype")
|
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()):
|
for package, pkgdata in sorted(packages.items()):
|
||||||
bba.l("package_data_%s" % package, "PackagePinPOD")
|
bba.l("package_data_%s" % package, "PackagePinPOD")
|
||||||
for pin in pkgdata:
|
for pin in pkgdata:
|
||||||
@ -491,11 +273,12 @@ def write_database(dev_name, ddrg, endianness):
|
|||||||
|
|
||||||
bba.r("locations", "locations")
|
bba.r("locations", "locations")
|
||||||
bba.r("location_types", "location_type")
|
bba.r("location_types", "location_type")
|
||||||
|
bba.r("location_glbinfo", "location_glbinfo")
|
||||||
bba.r("tiletype_names", "tiletype_names")
|
bba.r("tiletype_names", "tiletype_names")
|
||||||
bba.r("package_data", "package_info")
|
bba.r("package_data", "package_info")
|
||||||
bba.r("pio_info", "pio_info")
|
bba.r("pio_info", "pio_info")
|
||||||
|
|
||||||
bba.finalize()
|
bba.pop()
|
||||||
return bba
|
return bba
|
||||||
|
|
||||||
dev_names = {"25k": "LFE5U-25F", "45k": "LFE5U-45F", "85k": "LFE5U-85F"}
|
dev_names = {"25k": "LFE5U-25F", "45k": "LFE5U-45F", "85k": "LFE5U-85F"}
|
||||||
@ -518,30 +301,18 @@ def main():
|
|||||||
idx = len(portpins) + 1
|
idx = len(portpins) + 1
|
||||||
portpins[line[1]] = idx
|
portpins[line[1]] = idx
|
||||||
|
|
||||||
print("Initialising chip...")
|
# print("Initialising chip...")
|
||||||
chip = pytrellis.Chip(dev_names[args.device])
|
chip = pytrellis.Chip(dev_names[args.device])
|
||||||
print("Building routing graph...")
|
# print("Building routing graph...")
|
||||||
ddrg = pytrellis.make_dedup_chipdb(chip)
|
ddrg = pytrellis.make_dedup_chipdb(chip)
|
||||||
max_row = chip.get_max_row()
|
max_row = chip.get_max_row()
|
||||||
max_col = chip.get_max_col()
|
max_col = chip.get_max_col()
|
||||||
process_pio_db(ddrg, args.device)
|
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")
|
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__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
@ -24,22 +24,24 @@
|
|||||||
|
|
||||||
NEXTPNR_NAMESPACE_BEGIN
|
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);
|
NPNR_ASSERT(wires.count(name) == 0);
|
||||||
WireInfo &wi = wires[name];
|
WireInfo &wi = wires[name];
|
||||||
wi.name = name;
|
wi.name = name;
|
||||||
|
wi.type = type;
|
||||||
wi.x = x;
|
wi.x = x;
|
||||||
wi.y = y;
|
wi.y = y;
|
||||||
|
|
||||||
wire_ids.push_back(name);
|
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);
|
NPNR_ASSERT(pips.count(name) == 0);
|
||||||
PipInfo &pi = pips[name];
|
PipInfo &pi = pips[name];
|
||||||
pi.name = name;
|
pi.name = name;
|
||||||
|
pi.type = type;
|
||||||
pi.srcWire = srcWire;
|
pi.srcWire = srcWire;
|
||||||
pi.dstWire = dstWire;
|
pi.dstWire = dstWire;
|
||||||
pi.delay = delay;
|
pi.delay = delay;
|
||||||
@ -49,11 +51,12 @@ void Arch::addPip(IdString name, IdString srcWire, IdString dstWire, DelayInfo d
|
|||||||
pip_ids.push_back(name);
|
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);
|
NPNR_ASSERT(pips.count(name) == 0);
|
||||||
PipInfo &pi = pips[name];
|
PipInfo &pi = pips[name];
|
||||||
pi.name = name;
|
pi.name = name;
|
||||||
|
pi.type = type;
|
||||||
pi.srcWire = srcWire;
|
pi.srcWire = srcWire;
|
||||||
pi.dstWire = dstWire;
|
pi.dstWire = dstWire;
|
||||||
pi.delay = delay;
|
pi.delay = delay;
|
||||||
@ -77,18 +80,18 @@ void Arch::addBel(IdString name, IdString type, Loc loc, bool gb)
|
|||||||
bel_ids.push_back(name);
|
bel_ids.push_back(name);
|
||||||
bel_by_loc[loc] = 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);
|
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].resize(loc.y + 1);
|
||||||
|
|
||||||
bels_by_tile[loc.x][loc.y].push_back(name);
|
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);
|
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);
|
tileDimZ[loc.x].resize(loc.y + 1);
|
||||||
|
|
||||||
gridDimX = std::max(gridDimX, loc.x + 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; }
|
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
|
uint32_t Arch::getBelChecksum(BelId bel) const
|
||||||
{
|
{
|
||||||
// FIXME
|
// FIXME
|
||||||
@ -234,6 +255,7 @@ std::vector<PortPin> Arch::getBelPins(BelId bel) const
|
|||||||
std::vector<PortPin> ret;
|
std::vector<PortPin> ret;
|
||||||
for (auto &it : bels.at(bel).pins)
|
for (auto &it : bels.at(bel).pins)
|
||||||
ret.push_back(it.first);
|
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::getWireName(WireId wire) const { return wire; }
|
||||||
|
|
||||||
|
IdString Arch::getWireType(WireId wire) const { return wires.at(wire).type; }
|
||||||
|
|
||||||
uint32_t Arch::getWireChecksum(WireId wire) const
|
uint32_t Arch::getWireChecksum(WireId wire) const
|
||||||
{
|
{
|
||||||
// FIXME
|
// FIXME
|
||||||
@ -297,6 +321,8 @@ PipId Arch::getPipByName(IdString name) const
|
|||||||
|
|
||||||
IdString Arch::getPipName(PipId pip) const { return pip; }
|
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
|
uint32_t Arch::getPipChecksum(PipId wire) const
|
||||||
{
|
{
|
||||||
// FIXME
|
// 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
|
delay_t Arch::estimateDelay(WireId src, WireId dst) const
|
||||||
{
|
{
|
||||||
const WireInfo &s = wires.at(src);
|
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;
|
return (dx + dy) * grid_distance_to_delay;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 { return budget; }
|
||||||
{
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ struct WireInfo;
|
|||||||
|
|
||||||
struct PipInfo
|
struct PipInfo
|
||||||
{
|
{
|
||||||
IdString name, bound_net;
|
IdString name, type, bound_net;
|
||||||
WireId srcWire, dstWire;
|
WireId srcWire, dstWire;
|
||||||
DelayInfo delay;
|
DelayInfo delay;
|
||||||
DecalXY decalxy;
|
DecalXY decalxy;
|
||||||
@ -39,7 +39,7 @@ struct PipInfo
|
|||||||
|
|
||||||
struct WireInfo
|
struct WireInfo
|
||||||
{
|
{
|
||||||
IdString name, bound_net;
|
IdString name, type, bound_net;
|
||||||
std::vector<PipId> downhill, uphill, aliases;
|
std::vector<PipId> downhill, uphill, aliases;
|
||||||
BelPin uphill_bel_pin;
|
BelPin uphill_bel_pin;
|
||||||
std::vector<BelPin> downhill_bel_pins;
|
std::vector<BelPin> downhill_bel_pins;
|
||||||
@ -96,9 +96,9 @@ struct Arch : BaseCtx
|
|||||||
|
|
||||||
float grid_distance_to_delay;
|
float grid_distance_to_delay;
|
||||||
|
|
||||||
void addWire(IdString name, int x, int y);
|
void addWire(IdString name, IdString type, int x, int y);
|
||||||
void addPip(IdString name, IdString srcWire, IdString dstWire, DelayInfo delay);
|
void addPip(IdString name, IdString type, IdString srcWire, IdString dstWire, DelayInfo delay);
|
||||||
void addAlias(IdString name, 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 addBel(IdString name, IdString type, Loc loc, bool gb);
|
||||||
void addBelInput(IdString bel, IdString name, IdString wire);
|
void addBelInput(IdString bel, IdString name, IdString wire);
|
||||||
@ -141,7 +141,7 @@ struct Arch : BaseCtx
|
|||||||
IdString getBelName(BelId bel) const;
|
IdString getBelName(BelId bel) const;
|
||||||
Loc getBelLocation(BelId bel) const;
|
Loc getBelLocation(BelId bel) const;
|
||||||
BelId getBelByLocation(Loc loc) 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;
|
bool getBelGlobalBuf(BelId bel) const;
|
||||||
uint32_t getBelChecksum(BelId bel) const;
|
uint32_t getBelChecksum(BelId bel) const;
|
||||||
void bindBel(BelId bel, IdString cell, PlaceStrength strength);
|
void bindBel(BelId bel, IdString cell, PlaceStrength strength);
|
||||||
@ -157,6 +157,7 @@ struct Arch : BaseCtx
|
|||||||
|
|
||||||
WireId getWireByName(IdString name) const;
|
WireId getWireByName(IdString name) const;
|
||||||
IdString getWireName(WireId wire) const;
|
IdString getWireName(WireId wire) const;
|
||||||
|
IdString getWireType(WireId wire) const;
|
||||||
uint32_t getWireChecksum(WireId wire) const;
|
uint32_t getWireChecksum(WireId wire) const;
|
||||||
void bindWire(WireId wire, IdString net, PlaceStrength strength);
|
void bindWire(WireId wire, IdString net, PlaceStrength strength);
|
||||||
void unbindWire(WireId wire);
|
void unbindWire(WireId wire);
|
||||||
@ -169,6 +170,7 @@ struct Arch : BaseCtx
|
|||||||
|
|
||||||
PipId getPipByName(IdString name) const;
|
PipId getPipByName(IdString name) const;
|
||||||
IdString getPipName(PipId pip) const;
|
IdString getPipName(PipId pip) const;
|
||||||
|
IdString getPipType(PipId pip) const;
|
||||||
uint32_t getPipChecksum(PipId pip) const;
|
uint32_t getPipChecksum(PipId pip) const;
|
||||||
void bindPip(PipId pip, IdString net, PlaceStrength strength);
|
void bindPip(PipId pip, IdString net, PlaceStrength strength);
|
||||||
void unbindPip(PipId pip);
|
void unbindPip(PipId pip);
|
||||||
@ -191,13 +193,12 @@ struct Arch : BaseCtx
|
|||||||
const std::vector<PipId> &getGroupPips(GroupId group) const;
|
const std::vector<PipId> &getGroupPips(GroupId group) const;
|
||||||
const std::vector<GroupId> &getGroupGroups(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 estimateDelay(WireId src, WireId dst) const;
|
||||||
delay_t getDelayEpsilon() const { return 0.01; }
|
delay_t getDelayEpsilon() const { return 0.01; }
|
||||||
delay_t getRipupDelayPenalty() const { return 1.0; }
|
delay_t getRipupDelayPenalty() const { return 1.0; }
|
||||||
float getDelayNS(delay_t v) const { return v; }
|
float getDelayNS(delay_t v) const { return v; }
|
||||||
uint32_t getDelayChecksum(delay_t v) const { return 0; }
|
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 pack() { return true; }
|
||||||
bool place();
|
bool place();
|
||||||
|
@ -20,12 +20,22 @@
|
|||||||
|
|
||||||
#ifndef NO_PYTHON
|
#ifndef NO_PYTHON
|
||||||
|
|
||||||
|
#include "arch_pybindings.h"
|
||||||
#include "nextpnr.h"
|
#include "nextpnr.h"
|
||||||
#include "pybindings.h"
|
#include "pybindings.h"
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_BEGIN
|
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
|
NEXTPNR_NAMESPACE_END
|
||||||
|
|
||||||
|
@ -75,18 +75,16 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (vm.count("help") || argc == 1) {
|
if (vm.count("help") || argc == 1) {
|
||||||
std::cout << boost::filesystem::basename(argv[0])
|
std::cout << boost::filesystem::basename(argv[0]) << " -- Next Generation Place and Route (git "
|
||||||
<< " -- Next Generation Place and Route (git "
|
"sha1 " GIT_COMMIT_HASH_STR ")\n";
|
||||||
"sha1 " GIT_COMMIT_HASH_STR ")\n";
|
|
||||||
std::cout << "\n";
|
std::cout << "\n";
|
||||||
std::cout << options << "\n";
|
std::cout << options << "\n";
|
||||||
return argc != 1;
|
return argc != 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vm.count("version")) {
|
if (vm.count("version")) {
|
||||||
std::cout << boost::filesystem::basename(argv[0])
|
std::cout << boost::filesystem::basename(argv[0]) << " -- Next Generation Place and Route (git "
|
||||||
<< " -- Next Generation Place and Route (git "
|
"sha1 " GIT_COMMIT_HASH_STR ")\n";
|
||||||
"sha1 " GIT_COMMIT_HASH_STR ")\n";
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,5 +10,9 @@
|
|||||||
<file>resources/resultset_next.png</file>
|
<file>resources/resultset_next.png</file>
|
||||||
<file>resources/resultset_last.png</file>
|
<file>resources/resultset_last.png</file>
|
||||||
<file>resources/cross.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>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
#include <QSplitter>
|
#include <QSplitter>
|
||||||
#include "designwidget.h"
|
#include "designwidget.h"
|
||||||
#include "fpgaviewwidget.h"
|
#include "fpgaviewwidget.h"
|
||||||
#include "jsonparse.h"
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "pythontab.h"
|
#include "pythontab.h"
|
||||||
@ -76,7 +75,7 @@ BaseMainWindow::BaseMainWindow(std::unique_ptr<Context> context, QWidget *parent
|
|||||||
centralTabWidget->setTabsClosable(true);
|
centralTabWidget->setTabsClosable(true);
|
||||||
connect(centralTabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(closeTab(int)));
|
connect(centralTabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(closeTab(int)));
|
||||||
|
|
||||||
FPGAViewWidget *fpgaView = new FPGAViewWidget();
|
fpgaView = new FPGAViewWidget();
|
||||||
centralTabWidget->addTab(fpgaView, "Graphics");
|
centralTabWidget->addTab(fpgaView, "Graphics");
|
||||||
centralTabWidget->tabBar()->tabButton(0, QTabBar::RightSide)->resize(0, 0);
|
centralTabWidget->tabBar()->tabButton(0, QTabBar::RightSide)->resize(0, 0);
|
||||||
|
|
||||||
@ -163,4 +162,30 @@ void BaseMainWindow::createMenusAndBars()
|
|||||||
mainToolBar->addAction(actionSave);
|
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
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -37,6 +37,7 @@ NEXTPNR_NAMESPACE_BEGIN
|
|||||||
|
|
||||||
class PythonTab;
|
class PythonTab;
|
||||||
class DesignWidget;
|
class DesignWidget;
|
||||||
|
class FPGAViewWidget;
|
||||||
|
|
||||||
class BaseMainWindow : public QMainWindow
|
class BaseMainWindow : public QMainWindow
|
||||||
{
|
{
|
||||||
@ -49,6 +50,7 @@ class BaseMainWindow : public QMainWindow
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
void createMenusAndBars();
|
void createMenusAndBars();
|
||||||
|
void createGraphicsBar();
|
||||||
|
|
||||||
protected Q_SLOTS:
|
protected Q_SLOTS:
|
||||||
void writeInfo(std::string text);
|
void writeInfo(std::string text);
|
||||||
@ -70,12 +72,14 @@ class BaseMainWindow : public QMainWindow
|
|||||||
|
|
||||||
QMenuBar *menuBar;
|
QMenuBar *menuBar;
|
||||||
QToolBar *mainToolBar;
|
QToolBar *mainToolBar;
|
||||||
|
QToolBar *graphicsToolBar;
|
||||||
QStatusBar *statusBar;
|
QStatusBar *statusBar;
|
||||||
QAction *actionNew;
|
QAction *actionNew;
|
||||||
QAction *actionOpen;
|
QAction *actionOpen;
|
||||||
QAction *actionSave;
|
QAction *actionSave;
|
||||||
QProgressBar *progressBar;
|
QProgressBar *progressBar;
|
||||||
DesignWidget *designview;
|
DesignWidget *designview;
|
||||||
|
FPGAViewWidget *fpgaView;
|
||||||
};
|
};
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -35,6 +35,7 @@ class ElementTreeItem : public QTreeWidgetItem
|
|||||||
ElementTreeItem(ElementType t, QString str, QTreeWidgetItem *parent)
|
ElementTreeItem(ElementType t, QString str, QTreeWidgetItem *parent)
|
||||||
: QTreeWidgetItem(parent, QStringList(str)), type(t)
|
: QTreeWidgetItem(parent, QStringList(str)), type(t)
|
||||||
{
|
{
|
||||||
|
this->setFlags(this->flags() & ~Qt::ItemIsSelectable);
|
||||||
}
|
}
|
||||||
virtual ~ElementTreeItem(){};
|
virtual ~ElementTreeItem(){};
|
||||||
|
|
||||||
@ -49,6 +50,7 @@ class IdStringTreeItem : public ElementTreeItem
|
|||||||
public:
|
public:
|
||||||
IdStringTreeItem(IdString d, ElementType t, QString str, QTreeWidgetItem *parent) : ElementTreeItem(t, str, parent)
|
IdStringTreeItem(IdString d, ElementType t, QString str, QTreeWidgetItem *parent) : ElementTreeItem(t, str, parent)
|
||||||
{
|
{
|
||||||
|
this->setFlags(this->flags() | Qt::ItemIsSelectable);
|
||||||
this->data = d;
|
this->data = d;
|
||||||
}
|
}
|
||||||
virtual ~IdStringTreeItem(){};
|
virtual ~IdStringTreeItem(){};
|
||||||
@ -68,6 +70,7 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), net
|
|||||||
treeWidget->setColumnCount(1);
|
treeWidget->setColumnCount(1);
|
||||||
treeWidget->setHeaderLabel("Items");
|
treeWidget->setHeaderLabel("Items");
|
||||||
treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
|
treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
treeWidget->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||||
|
|
||||||
// Add property view
|
// Add property view
|
||||||
variantManager = new QtVariantPropertyManager(this);
|
variantManager = new QtVariantPropertyManager(this);
|
||||||
@ -79,6 +82,7 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), net
|
|||||||
propertyEditor->setPropertiesWithoutValueMarked(true);
|
propertyEditor->setPropertiesWithoutValueMarked(true);
|
||||||
propertyEditor->show();
|
propertyEditor->show();
|
||||||
propertyEditor->treeWidget()->setContextMenuPolicy(Qt::CustomContextMenu);
|
propertyEditor->treeWidget()->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
propertyEditor->treeWidget()->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||||
|
|
||||||
QLineEdit *lineEdit = new QLineEdit();
|
QLineEdit *lineEdit = new QLineEdit();
|
||||||
lineEdit->setClearButtonEnabled(true);
|
lineEdit->setClearButtonEnabled(true);
|
||||||
@ -229,6 +233,7 @@ void DesignWidget::addToHistory(QTreeWidgetItem *item)
|
|||||||
|
|
||||||
void DesignWidget::newContext(Context *ctx)
|
void DesignWidget::newContext(Context *ctx)
|
||||||
{
|
{
|
||||||
|
highlightSelected.clear();
|
||||||
treeWidget->clear();
|
treeWidget->clear();
|
||||||
// reset pointers since they are not valid after clear
|
// reset pointers since they are not valid after clear
|
||||||
nets_root = nullptr;
|
nets_root = nullptr;
|
||||||
@ -247,6 +252,7 @@ void DesignWidget::newContext(Context *ctx)
|
|||||||
QTreeWidgetItem *bel_root = new QTreeWidgetItem(treeWidget);
|
QTreeWidgetItem *bel_root = new QTreeWidgetItem(treeWidget);
|
||||||
QMap<QString, QTreeWidgetItem *> bel_items;
|
QMap<QString, QTreeWidgetItem *> bel_items;
|
||||||
bel_root->setText(0, "Bels");
|
bel_root->setText(0, "Bels");
|
||||||
|
bel_root->setFlags(bel_root->flags() & ~Qt::ItemIsSelectable);
|
||||||
treeWidget->insertTopLevelItem(0, bel_root);
|
treeWidget->insertTopLevelItem(0, bel_root);
|
||||||
if (ctx) {
|
if (ctx) {
|
||||||
for (auto bel : ctx->getBels()) {
|
for (auto bel : ctx->getBels()) {
|
||||||
@ -279,6 +285,7 @@ void DesignWidget::newContext(Context *ctx)
|
|||||||
QTreeWidgetItem *wire_root = new QTreeWidgetItem(treeWidget);
|
QTreeWidgetItem *wire_root = new QTreeWidgetItem(treeWidget);
|
||||||
QMap<QString, QTreeWidgetItem *> wire_items;
|
QMap<QString, QTreeWidgetItem *> wire_items;
|
||||||
wire_root->setText(0, "Wires");
|
wire_root->setText(0, "Wires");
|
||||||
|
wire_root->setFlags(wire_root->flags() & ~Qt::ItemIsSelectable);
|
||||||
treeWidget->insertTopLevelItem(0, wire_root);
|
treeWidget->insertTopLevelItem(0, wire_root);
|
||||||
if (ctx) {
|
if (ctx) {
|
||||||
for (auto wire : ctx->getWires()) {
|
for (auto wire : ctx->getWires()) {
|
||||||
@ -310,7 +317,9 @@ void DesignWidget::newContext(Context *ctx)
|
|||||||
QTreeWidgetItem *pip_root = new QTreeWidgetItem(treeWidget);
|
QTreeWidgetItem *pip_root = new QTreeWidgetItem(treeWidget);
|
||||||
QMap<QString, QTreeWidgetItem *> pip_items;
|
QMap<QString, QTreeWidgetItem *> pip_items;
|
||||||
pip_root->setText(0, "Pips");
|
pip_root->setText(0, "Pips");
|
||||||
|
pip_root->setFlags(pip_root->flags() & ~Qt::ItemIsSelectable);
|
||||||
treeWidget->insertTopLevelItem(0, pip_root);
|
treeWidget->insertTopLevelItem(0, pip_root);
|
||||||
|
#ifndef ARCH_ECP5
|
||||||
if (ctx) {
|
if (ctx) {
|
||||||
for (auto pip : ctx->getPips()) {
|
for (auto pip : ctx->getPips()) {
|
||||||
auto id = ctx->getPipName(pip);
|
auto id = ctx->getPipName(pip);
|
||||||
@ -337,54 +346,88 @@ void DesignWidget::newContext(Context *ctx)
|
|||||||
for (auto pip : nameToItem[2].toStdMap()) {
|
for (auto pip : nameToItem[2].toStdMap()) {
|
||||||
pip_root->addChild(pip.second);
|
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();
|
updateTree();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DesignWidget::updateTree()
|
void DesignWidget::updateTree()
|
||||||
{
|
{
|
||||||
clearProperties();
|
if (!ctx)
|
||||||
delete nets_root;
|
return;
|
||||||
delete cells_root;
|
|
||||||
nameToItem[3].clear();
|
|
||||||
nameToItem[4].clear();
|
|
||||||
|
|
||||||
|
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
|
// Add nets to tree
|
||||||
nets_root = new QTreeWidgetItem(treeWidget);
|
for (auto &item : ctx->nets) {
|
||||||
nets_root->setText(0, "Nets");
|
auto id = item.first;
|
||||||
treeWidget->insertTopLevelItem(0, nets_root);
|
QString name = QString(id.c_str(ctx));
|
||||||
if (ctx) {
|
if (!nameToItem[3].contains(name)) {
|
||||||
for (auto &item : ctx->nets) {
|
|
||||||
auto id = item.first;
|
|
||||||
QString name = QString(id.c_str(ctx));
|
|
||||||
IdStringTreeItem *newItem = new IdStringTreeItem(id, ElementType::NET, name, nullptr);
|
IdStringTreeItem *newItem = new IdStringTreeItem(id, ElementType::NET, name, nullptr);
|
||||||
|
nets_root->addChild(newItem);
|
||||||
nameToItem[3].insert(name, 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
|
// Add cells to tree
|
||||||
cells_root = new QTreeWidgetItem(treeWidget);
|
for (auto &item : ctx->cells) {
|
||||||
cells_root->setText(0, "Cells");
|
auto id = item.first;
|
||||||
treeWidget->insertTopLevelItem(0, cells_root);
|
QString name = QString(id.c_str(ctx));
|
||||||
if (ctx) {
|
if (!nameToItem[4].contains(name)) {
|
||||||
for (auto &item : ctx->cells) {
|
|
||||||
auto id = item.first;
|
|
||||||
QString name = QString(id.c_str(ctx));
|
|
||||||
IdStringTreeItem *newItem = new IdStringTreeItem(id, ElementType::CELL, name, nullptr);
|
IdStringTreeItem *newItem = new IdStringTreeItem(id, ElementType::CELL, name, nullptr);
|
||||||
|
cells_root->addChild(newItem);
|
||||||
nameToItem[4].insert(name, newItem);
|
nameToItem[4].insert(name, newItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (auto item : nameToItem[4].toStdMap()) {
|
// treeWidget->sortByColumn(0, Qt::AscendingOrder);
|
||||||
cells_root->addChild(item.second);
|
// treeWidget->setSortingEnabled(true);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
QtProperty *DesignWidget::addTopLevelProperty(const QString &id)
|
QtProperty *DesignWidget::addTopLevelProperty(const QString &id)
|
||||||
{
|
{
|
||||||
QtProperty *topItem = groupManager->addProperty(id);
|
QtProperty *topItem = groupManager->addProperty(id);
|
||||||
propertyToId[topItem] = id;
|
propertyToId[topItem] = id;
|
||||||
idToProperty[id] = topItem;
|
idToProperty[id] = topItem;
|
||||||
|
topItem->setSelectable(false);
|
||||||
propertyEditor->addProperty(topItem);
|
propertyEditor->addProperty(topItem);
|
||||||
return topItem;
|
return topItem;
|
||||||
}
|
}
|
||||||
@ -452,12 +495,14 @@ void DesignWidget::addProperty(QtProperty *topItem, int propertyType, const QStr
|
|||||||
QtVariantProperty *item = readOnlyManager->addProperty(propertyType, name);
|
QtVariantProperty *item = readOnlyManager->addProperty(propertyType, name);
|
||||||
item->setValue(value);
|
item->setValue(value);
|
||||||
item->setPropertyId(getElementTypeName(type));
|
item->setPropertyId(getElementTypeName(type));
|
||||||
|
item->setSelectable(type != ElementType::NONE);
|
||||||
topItem->addSubProperty(item);
|
topItem->addSubProperty(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
QtProperty *DesignWidget::addSubGroup(QtProperty *topItem, const QString &name)
|
QtProperty *DesignWidget::addSubGroup(QtProperty *topItem, const QString &name)
|
||||||
{
|
{
|
||||||
QtProperty *item = groupManager->addProperty(name);
|
QtProperty *item = groupManager->addProperty(name);
|
||||||
|
item->setSelectable(false);
|
||||||
topItem->addSubProperty(item);
|
topItem->addSubProperty(item);
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@ -467,6 +512,18 @@ void DesignWidget::onItemSelectionChanged()
|
|||||||
if (treeWidget->selectedItems().size() == 0)
|
if (treeWidget->selectedItems().size() == 0)
|
||||||
return;
|
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);
|
QTreeWidgetItem *clickItem = treeWidget->selectedItems().at(0);
|
||||||
|
|
||||||
if (!clickItem->parent())
|
if (!clickItem->parent())
|
||||||
@ -510,6 +567,7 @@ void DesignWidget::onItemSelectionChanged()
|
|||||||
QtProperty *topItem = addTopLevelProperty("Wire");
|
QtProperty *topItem = addTopLevelProperty("Wire");
|
||||||
|
|
||||||
addProperty(topItem, QVariant::String, "Name", c.c_str(ctx));
|
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::Bool, "Available", ctx->checkWireAvail(wire));
|
||||||
addProperty(topItem, QVariant::String, "Bound Net", ctx->getBoundWireNet(wire).c_str(ctx), ElementType::NET);
|
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),
|
addProperty(topItem, QVariant::String, "Conflicting Net", ctx->getConflictingWireNet(wire).c_str(ctx),
|
||||||
@ -561,6 +619,7 @@ void DesignWidget::onItemSelectionChanged()
|
|||||||
QtProperty *topItem = addTopLevelProperty("Pip");
|
QtProperty *topItem = addTopLevelProperty("Pip");
|
||||||
|
|
||||||
addProperty(topItem, QVariant::String, "Name", c.c_str(ctx));
|
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::Bool, "Available", ctx->checkPipAvail(pip));
|
||||||
addProperty(topItem, QVariant::String, "Bound Net", ctx->getBoundPipNet(pip).c_str(ctx), ElementType::NET);
|
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),
|
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;
|
return decals;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DesignWidget::updateHighlightGroup(QTreeWidgetItem *item, int group)
|
void DesignWidget::updateHighlightGroup(QList<QTreeWidgetItem *> items, int group)
|
||||||
{
|
{
|
||||||
if (highlightSelected.contains(item)) {
|
const bool shouldClear = items.size() == 1;
|
||||||
if (highlightSelected[item] == group) {
|
for (auto item : items) {
|
||||||
highlightSelected.remove(item);
|
if (highlightSelected.contains(item)) {
|
||||||
|
if (shouldClear && highlightSelected[item] == group) {
|
||||||
|
highlightSelected.remove(item);
|
||||||
|
} else
|
||||||
|
highlightSelected[item] = group;
|
||||||
} else
|
} else
|
||||||
highlightSelected[item] = group;
|
highlightSelected.insert(item, group);
|
||||||
} else
|
}
|
||||||
highlightSelected.insert(item, group);
|
std::vector<DecalXY> decals[8];
|
||||||
|
|
||||||
std::vector<DecalXY> decals;
|
|
||||||
|
|
||||||
for (auto it : highlightSelected.toStdMap()) {
|
for (auto it : highlightSelected.toStdMap()) {
|
||||||
if (it.second == group) {
|
ElementType type = static_cast<ElementTreeItem *>(it.first)->getType();
|
||||||
ElementType type = static_cast<ElementTreeItem *>(it.first)->getType();
|
IdString value = static_cast<IdStringTreeItem *>(it.first)->getData();
|
||||||
IdString value = static_cast<IdStringTreeItem *>(it.first)->getData();
|
std::vector<DecalXY> d = getDecals(type, value);
|
||||||
std::vector<DecalXY> d = getDecals(type, value);
|
std::move(d.begin(), d.end(), std::back_inserter(decals[it.second]));
|
||||||
std::move(d.begin(), d.end(), std::back_inserter(decals));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
Q_EMIT highlight(decals, group);
|
Q_EMIT highlight(decals[i], i);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DesignWidget::prepareMenuProperty(const QPoint &pos)
|
void DesignWidget::prepareMenuProperty(const QPoint &pos)
|
||||||
{
|
{
|
||||||
QTreeWidget *tree = propertyEditor->treeWidget();
|
QTreeWidget *tree = propertyEditor->treeWidget();
|
||||||
|
QList<QTreeWidgetItem *> items;
|
||||||
itemContextMenu = tree->itemAt(pos);
|
for (auto itemContextMenu : tree->selectedItems()) {
|
||||||
if (itemContextMenu->parent() == nullptr)
|
QtBrowserItem *browserItem = propertyEditor->itemToBrowserItem(itemContextMenu);
|
||||||
return;
|
if (!browserItem)
|
||||||
|
continue;
|
||||||
QtBrowserItem *browserItem = propertyEditor->itemToBrowserItem(itemContextMenu);
|
QtProperty *selectedProperty = browserItem->property();
|
||||||
if (!browserItem)
|
ElementType type = getElementTypeByName(selectedProperty->propertyId());
|
||||||
return;
|
if (type == ElementType::NONE)
|
||||||
QtProperty *selectedProperty = browserItem->property();
|
continue;
|
||||||
ElementType type = getElementTypeByName(selectedProperty->propertyId());
|
IdString value = ctx->id(selectedProperty->valueText().toStdString());
|
||||||
if (type == ElementType::NONE)
|
items.append(nameToItem[getElementIndex(type)].value(value.c_str(ctx)));
|
||||||
return;
|
}
|
||||||
IdString value = ctx->id(selectedProperty->valueText().toStdString());
|
int selectedIndex = -1;
|
||||||
|
if (items.size() == 1) {
|
||||||
QTreeWidgetItem *item = nameToItem[getElementIndex(type)].value(value.c_str(ctx));
|
QTreeWidgetItem *item = items.at(0);
|
||||||
|
if (highlightSelected.contains(item))
|
||||||
|
selectedIndex = highlightSelected[item];
|
||||||
|
}
|
||||||
|
|
||||||
QMenu menu(this);
|
QMenu menu(this);
|
||||||
QAction *selectAction = new QAction("&Select", 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);
|
menu.addAction(selectAction);
|
||||||
|
|
||||||
QMenu *subMenu = menu.addMenu("Highlight");
|
QMenu *subMenu = menu.addMenu("Highlight");
|
||||||
@ -776,27 +847,24 @@ void DesignWidget::prepareMenuProperty(const QPoint &pos)
|
|||||||
action->setCheckable(true);
|
action->setCheckable(true);
|
||||||
subMenu->addAction(action);
|
subMenu->addAction(action);
|
||||||
group->addAction(action);
|
group->addAction(action);
|
||||||
if (highlightSelected.contains(item) && highlightSelected[item] == i)
|
if (selectedIndex == i)
|
||||||
action->setChecked(true);
|
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(tree->mapToGlobal(pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
void DesignWidget::prepareMenuTree(const QPoint &pos)
|
void DesignWidget::prepareMenuTree(const QPoint &pos)
|
||||||
{
|
{
|
||||||
QTreeWidget *tree = treeWidget;
|
if (treeWidget->selectedItems().size() == 0)
|
||||||
|
|
||||||
itemContextMenu = tree->itemAt(pos);
|
|
||||||
|
|
||||||
ElementType type = static_cast<ElementTreeItem *>(itemContextMenu)->getType();
|
|
||||||
IdString value = static_cast<IdStringTreeItem *>(itemContextMenu)->getData();
|
|
||||||
|
|
||||||
if (type == ElementType::NONE)
|
|
||||||
return;
|
return;
|
||||||
|
int selectedIndex = -1;
|
||||||
QTreeWidgetItem *item = nameToItem[getElementIndex(type)].value(value.c_str(ctx));
|
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 menu(this);
|
||||||
QMenu *subMenu = menu.addMenu("Highlight");
|
QMenu *subMenu = menu.addMenu("Highlight");
|
||||||
QActionGroup *group = new QActionGroup(this);
|
QActionGroup *group = new QActionGroup(this);
|
||||||
@ -808,11 +876,11 @@ void DesignWidget::prepareMenuTree(const QPoint &pos)
|
|||||||
action->setCheckable(true);
|
action->setCheckable(true);
|
||||||
subMenu->addAction(action);
|
subMenu->addAction(action);
|
||||||
group->addAction(action);
|
group->addAction(action);
|
||||||
if (highlightSelected.contains(item) && highlightSelected[item] == i)
|
if (selectedIndex == i)
|
||||||
action->setChecked(true);
|
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)
|
void DesignWidget::onItemDoubleClicked(QTreeWidgetItem *item, int column)
|
||||||
@ -821,14 +889,8 @@ void DesignWidget::onItemDoubleClicked(QTreeWidgetItem *item, int column)
|
|||||||
ElementType type = getElementTypeByName(selectedProperty->propertyId());
|
ElementType type = getElementTypeByName(selectedProperty->propertyId());
|
||||||
QString value = selectedProperty->valueText();
|
QString value = selectedProperty->valueText();
|
||||||
int index = getElementIndex(type);
|
int index = getElementIndex(type);
|
||||||
switch (type) {
|
if (type != ElementType::NONE && nameToItem[index].contains(value))
|
||||||
case ElementType::NONE:
|
treeWidget->setCurrentItem(nameToItem[index].value(value));
|
||||||
return;
|
|
||||||
default: {
|
|
||||||
if (nameToItem[index].contains(value))
|
|
||||||
treeWidget->setCurrentItem(nameToItem[index].value(value));
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -60,7 +60,7 @@ class DesignWidget : public QWidget
|
|||||||
void updateButtons();
|
void updateButtons();
|
||||||
void addToHistory(QTreeWidgetItem *item);
|
void addToHistory(QTreeWidgetItem *item);
|
||||||
std::vector<DecalXY> getDecals(ElementType type, IdString value);
|
std::vector<DecalXY> getDecals(ElementType type, IdString value);
|
||||||
void updateHighlightGroup(QTreeWidgetItem *item, int group);
|
void updateHighlightGroup(QList<QTreeWidgetItem *> item, int group);
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void info(std::string text);
|
void info(std::string text);
|
||||||
void selected(std::vector<DecalXY> decal);
|
void selected(std::vector<DecalXY> decal);
|
||||||
@ -85,7 +85,6 @@ class DesignWidget : public QWidget
|
|||||||
QtGroupPropertyManager *groupManager;
|
QtGroupPropertyManager *groupManager;
|
||||||
QtVariantEditorFactory *variantFactory;
|
QtVariantEditorFactory *variantFactory;
|
||||||
QtTreePropertyBrowser *propertyEditor;
|
QtTreePropertyBrowser *propertyEditor;
|
||||||
QTreeWidgetItem *itemContextMenu;
|
|
||||||
|
|
||||||
QMap<QtProperty *, QString> propertyToId;
|
QMap<QtProperty *, QString> propertyToId;
|
||||||
QMap<QString, QtProperty *> idToProperty;
|
QMap<QString, QtProperty *> idToProperty;
|
||||||
|
@ -31,222 +31,15 @@
|
|||||||
|
|
||||||
NEXTPNR_NAMESPACE_BEGIN
|
NEXTPNR_NAMESPACE_BEGIN
|
||||||
|
|
||||||
void PolyLine::buildPoint(LineShaderData *building, const QVector2D *prev, const QVector2D *cur,
|
FPGAViewWidget::FPGAViewWidget(QWidget *parent) :
|
||||||
const QVector2D *next) const
|
QOpenGLWidget(parent), ctx_(nullptr), paintTimer_(this),
|
||||||
{
|
lineShader_(this), zoom_(500.0f),
|
||||||
// buildPoint emits two vertices per line point, along with normals to move
|
rendererData_(new FPGAViewWidget::RendererData),
|
||||||
// them the right directio when rendering and miter to compensate for
|
rendererArgs_(new FPGAViewWidget::RendererArgs)
|
||||||
// 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)
|
|
||||||
{
|
{
|
||||||
colors_.background = QColor("#000000");
|
colors_.background = QColor("#000000");
|
||||||
colors_.grid = QColor("#333");
|
colors_.grid = QColor("#333");
|
||||||
colors_.frame = QColor("#d0d0d0");
|
colors_.frame = QColor("#808080");
|
||||||
colors_.hidden = QColor("#606060");
|
colors_.hidden = QColor("#606060");
|
||||||
colors_.inactive = QColor("#303030");
|
colors_.inactive = QColor("#303030");
|
||||||
colors_.active = QColor("#f0f0f0");
|
colors_.active = QColor("#f0f0f0");
|
||||||
@ -289,6 +82,9 @@ FPGAViewWidget::~FPGAViewWidget() {}
|
|||||||
void FPGAViewWidget::newContext(Context *ctx)
|
void FPGAViewWidget::newContext(Context *ctx)
|
||||||
{
|
{
|
||||||
ctx_ = ctx;
|
ctx_ = ctx;
|
||||||
|
onSelectedArchItem(std::vector<DecalXY>());
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
onHighlightGroupChanged(std::vector<DecalXY>(), i);
|
||||||
pokeRenderer();
|
pokeRenderer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,69 +102,49 @@ void FPGAViewWidget::initializeGL()
|
|||||||
0.0);
|
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;
|
const float scale = 1.0;
|
||||||
float offsetX = 0.0, offsetY = 0.0;
|
|
||||||
|
|
||||||
for (auto &el : ctx_->getDecalGraphics(decal.decal)) {
|
if (el.type == GraphicElement::TYPE_BOX) {
|
||||||
offsetX = decal.x;
|
auto line = PolyLine(true);
|
||||||
offsetY = decal.y;
|
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) {
|
if (el.type == GraphicElement::TYPE_LINE || el.type == GraphicElement::TYPE_ARROW) {
|
||||||
auto line = PolyLine(true);
|
PolyLine(x + scale * el.x1, y + scale * el.y1, x + scale * el.x2, y + scale * el.y2)
|
||||||
line.point(offsetX + scale * el.x1, offsetY + scale * el.y1);
|
.build(out);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FPGAViewWidget::drawDecal(LineShaderData out[], const DecalXY &decal)
|
void FPGAViewWidget::drawDecal(LineShaderData &out, const DecalXY &decal)
|
||||||
{
|
{
|
||||||
const float scale = 1.0;
|
float offsetX = decal.x;
|
||||||
float offsetX = 0.0, offsetY = 0.0;
|
float offsetY = decal.y;
|
||||||
|
|
||||||
for (auto &el : ctx_->getDecalGraphics(decal.decal)) {
|
for (auto &el : ctx_->getDecalGraphics(decal.decal)) {
|
||||||
offsetX = decal.x;
|
drawGraphicElement(out, el, offsetX, offsetY);
|
||||||
offsetY = decal.y;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (el.type == GraphicElement::G_BOX) {
|
void FPGAViewWidget::drawArchDecal(LineShaderData out[GraphicElement::STYLE_MAX], const DecalXY &decal)
|
||||||
auto line = PolyLine(true);
|
{
|
||||||
line.point(offsetX + scale * el.x1, offsetY + scale * el.y1);
|
float offsetX = decal.x;
|
||||||
line.point(offsetX + scale * el.x2, offsetY + scale * el.y1);
|
float offsetY = decal.y;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (el.type == GraphicElement::G_LINE) {
|
for (auto &el : ctx_->getDecalGraphics(decal.decal)) {
|
||||||
auto line = PolyLine(offsetX + scale * el.x1, offsetY + scale * el.y1, offsetX + scale * el.x2,
|
switch (el.style) {
|
||||||
offsetY + scale * el.y2);
|
case GraphicElement::STYLE_FRAME:
|
||||||
switch (el.style) {
|
case GraphicElement::STYLE_INACTIVE:
|
||||||
case GraphicElement::G_FRAME:
|
case GraphicElement::STYLE_ACTIVE:
|
||||||
case GraphicElement::G_INACTIVE:
|
drawGraphicElement(out[el.style], el, offsetX, offsetY);
|
||||||
case GraphicElement::G_ACTIVE:
|
break;
|
||||||
line.build(out[el.style]);
|
default:
|
||||||
break;
|
break;
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -398,24 +174,28 @@ void FPGAViewWidget::paintGL()
|
|||||||
float thick1Px = mouseToWorldCoordinates(1, 0).x();
|
float thick1Px = mouseToWorldCoordinates(1, 0).x();
|
||||||
float thick11Px = mouseToWorldCoordinates(1.1, 0).x();
|
float thick11Px = mouseToWorldCoordinates(1.1, 0).x();
|
||||||
|
|
||||||
// Draw grid.
|
// Render grid.
|
||||||
auto grid = LineShaderData();
|
auto grid = LineShaderData();
|
||||||
for (float i = -100.0f; i < 100.0f; i += 1.0f) {
|
for (float i = -100.0f; i < 100.0f; i += 1.0f) {
|
||||||
PolyLine(-100.0f, i, 100.0f, i).build(grid);
|
PolyLine(-100.0f, i, 100.0f, i).build(grid);
|
||||||
PolyLine(i, -100.0f, i, 100.0f).build(grid);
|
PolyLine(i, -100.0f, i, 100.0f).build(grid);
|
||||||
}
|
}
|
||||||
|
// Draw grid.
|
||||||
lineShader_.draw(grid, colors_.grid, thick1Px, matrix);
|
lineShader_.draw(grid, colors_.grid, thick1Px, matrix);
|
||||||
|
|
||||||
rendererDataLock_.lock();
|
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++)
|
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();
|
rendererDataLock_.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -426,126 +206,154 @@ void FPGAViewWidget::renderLines(void)
|
|||||||
if (ctx_ == nullptr)
|
if (ctx_ == nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ctx_->lock_ui();
|
// Data from Context needed to render all decals.
|
||||||
|
|
||||||
// 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.
|
|
||||||
std::vector<DecalXY> belDecals;
|
std::vector<DecalXY> belDecals;
|
||||||
std::vector<DecalXY> wireDecals;
|
std::vector<DecalXY> wireDecals;
|
||||||
std::vector<DecalXY> pipDecals;
|
std::vector<DecalXY> pipDecals;
|
||||||
std::vector<DecalXY> groupDecals;
|
std::vector<DecalXY> groupDecals;
|
||||||
if (decalsChanged) {
|
bool decalsChanged = false;
|
||||||
for (auto bel : ctx_->getBels()) {
|
{
|
||||||
belDecals.push_back(ctx_->getBelDecal(bel));
|
// 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()) {
|
if (ctx_->frameUiReload) {
|
||||||
wireDecals.push_back(ctx_->getWireDecal(wire));
|
ctx_->frameUiReload = false;
|
||||||
|
decalsChanged = true;
|
||||||
}
|
}
|
||||||
for (auto pip : ctx_->getPips()) {
|
if (ctx_->belUiReload.size() > 0) {
|
||||||
pipDecals.push_back(ctx_->getPipDecal(pip));
|
ctx_->belUiReload.clear();
|
||||||
|
decalsChanged = true;
|
||||||
}
|
}
|
||||||
for (auto group : ctx_->getGroups()) {
|
if (ctx_->wireUiReload.size() > 0) {
|
||||||
groupDecals.push_back(ctx_->getGroupDecal(group));
|
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();
|
// Arguments from the main UI thread on what we should render.
|
||||||
auto selectedItems = rendererArgs_->selectedItems;
|
std::vector<DecalXY> selectedDecals;
|
||||||
auto highlightedItems = rendererArgs_->highlightedItems;
|
std::vector<DecalXY> highlightedDecals[8];
|
||||||
auto highlightedOrSelectedChanged = rendererArgs_->highlightedOrSelectedChanged;
|
bool highlightedOrSelectedChanged;
|
||||||
rendererArgs_->highlightedOrSelectedChanged = false;
|
{
|
||||||
rendererArgsLock_.unlock();
|
// 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) {
|
if (decalsChanged) {
|
||||||
auto data = std::unique_ptr<FPGAViewWidget::RendererData>(new FPGAViewWidget::RendererData);
|
auto data = std::unique_ptr<FPGAViewWidget::RendererData>(new FPGAViewWidget::RendererData);
|
||||||
// Draw Bels.
|
// Draw Bels.
|
||||||
for (auto const &decal : belDecals) {
|
for (auto const &decal : belDecals) {
|
||||||
drawDecal(data->decals, decal);
|
drawArchDecal(data->gfxByStyle, decal);
|
||||||
}
|
}
|
||||||
// Draw Wires.
|
// Draw Wires.
|
||||||
for (auto const &decal : wireDecals) {
|
for (auto const &decal : wireDecals) {
|
||||||
drawDecal(data->decals, decal);
|
drawArchDecal(data->gfxByStyle, decal);
|
||||||
}
|
}
|
||||||
// Draw Pips.
|
// Draw Pips.
|
||||||
for (auto const &decal : pipDecals) {
|
for (auto const &decal : pipDecals) {
|
||||||
drawDecal(data->decals, decal);
|
drawArchDecal(data->gfxByStyle, decal);
|
||||||
}
|
}
|
||||||
// Draw Groups.
|
// Draw Groups.
|
||||||
for (auto const &decal : groupDecals) {
|
for (auto const &decal : groupDecals) {
|
||||||
drawDecal(data->decals, decal);
|
drawArchDecal(data->gfxByStyle, decal);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Swap over.
|
// Swap over.
|
||||||
rendererDataLock_.lock();
|
{
|
||||||
rendererData_ = std::move(data);
|
QMutexLocker lock(&rendererDataLock_);
|
||||||
rendererDataLock_.unlock();
|
|
||||||
|
// 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 (highlightedOrSelectedChanged) {
|
||||||
if (decalsChanged || highlightedOrSelectedChanged) {
|
QMutexLocker locker(&rendererDataLock_);
|
||||||
rendererData_->selected.clear();
|
|
||||||
for (auto &decal : selectedItems) {
|
// Render selected.
|
||||||
drawDecal(rendererData_->selected, decal);
|
rendererData_->gfxSelected.clear();
|
||||||
|
for (auto &decal : selectedDecals) {
|
||||||
|
drawDecal(rendererData_->gfxSelected, decal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render highlighted.
|
||||||
for (int i = 0; i < 8; i++) {
|
for (int i = 0; i < 8; i++) {
|
||||||
rendererData_->highlighted[i].clear();
|
rendererData_->gfxHighlighted[i].clear();
|
||||||
for (auto &decal : highlightedItems[i]) {
|
for (auto &decal : highlightedDecals[i]) {
|
||||||
drawDecal(rendererData_->highlighted[i], decal);
|
drawDecal(rendererData_->gfxHighlighted[i], decal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rendererDataLock_.unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FPGAViewWidget::onSelectedArchItem(std::vector<DecalXY> decals)
|
void FPGAViewWidget::onSelectedArchItem(std::vector<DecalXY> decals)
|
||||||
{
|
{
|
||||||
rendererArgsLock_.lock();
|
{
|
||||||
rendererArgs_->selectedItems = decals;
|
QMutexLocker locker(&rendererArgsLock_);
|
||||||
rendererArgs_->highlightedOrSelectedChanged = true;
|
rendererArgs_->selectedDecals = decals;
|
||||||
rendererArgsLock_.unlock();
|
rendererArgs_->highlightedOrSelectedChanged = true;
|
||||||
|
}
|
||||||
pokeRenderer();
|
pokeRenderer();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FPGAViewWidget::onHighlightGroupChanged(std::vector<DecalXY> decals, int group)
|
void FPGAViewWidget::onHighlightGroupChanged(std::vector<DecalXY> decals, int group)
|
||||||
{
|
{
|
||||||
rendererArgsLock_.lock();
|
{
|
||||||
rendererArgs_->highlightedItems[group] = decals;
|
QMutexLocker locker(&rendererArgsLock_);
|
||||||
rendererArgs_->highlightedOrSelectedChanged = true;
|
rendererArgs_->highlightedDecals[group] = decals;
|
||||||
rendererArgsLock_.unlock();
|
rendererArgs_->highlightedOrSelectedChanged = true;
|
||||||
|
}
|
||||||
pokeRenderer();
|
pokeRenderer();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FPGAViewWidget::resizeGL(int width, int height) {}
|
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
|
// Invert the projection matrix to calculate screen/mouse to world/grid
|
||||||
// coordinates.
|
// coordinates.
|
||||||
@ -561,9 +369,9 @@ QVector4D FPGAViewWidget::mouseToWorldCoordinates(int x, int y)
|
|||||||
|
|
||||||
void FPGAViewWidget::mouseMoveEvent(QMouseEvent *event)
|
void FPGAViewWidget::mouseMoveEvent(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
const int dx = event->x() - lastPos_.x();
|
const int dx = event->x() - lastDragPos_.x();
|
||||||
const int dy = event->y() - lastPos_.y();
|
const int dy = event->y() - lastDragPos_.y();
|
||||||
lastPos_ = event->pos();
|
lastDragPos_ = event->pos();
|
||||||
|
|
||||||
auto world = mouseToWorldCoordinates(dx, dy);
|
auto world = mouseToWorldCoordinates(dx, dy);
|
||||||
viewMove_.translate(world.x(), -world.y());
|
viewMove_.translate(world.x(), -world.y());
|
||||||
@ -575,21 +383,32 @@ void FPGAViewWidget::wheelEvent(QWheelEvent *event)
|
|||||||
{
|
{
|
||||||
QPoint degree = event->angleDelta() / 8;
|
QPoint degree = event->angleDelta() / 8;
|
||||||
|
|
||||||
if (!degree.isNull()) {
|
if (!degree.isNull())
|
||||||
|
zoom(degree.y());
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -33,183 +33,10 @@
|
|||||||
#include <QWaitCondition>
|
#include <QWaitCondition>
|
||||||
|
|
||||||
#include "nextpnr.h"
|
#include "nextpnr.h"
|
||||||
|
#include "lineshader.h"
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_BEGIN
|
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
|
class PeriodicRunner : public QThread
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -267,53 +94,43 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions
|
|||||||
FPGAViewWidget(QWidget *parent = 0);
|
FPGAViewWidget(QWidget *parent = 0);
|
||||||
~FPGAViewWidget();
|
~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:
|
protected:
|
||||||
|
// Qt callbacks.
|
||||||
void initializeGL() Q_DECL_OVERRIDE;
|
void initializeGL() Q_DECL_OVERRIDE;
|
||||||
void paintGL() Q_DECL_OVERRIDE;
|
void paintGL() Q_DECL_OVERRIDE;
|
||||||
void resizeGL(int width, int height) Q_DECL_OVERRIDE;
|
void resizeGL(int width, int height) Q_DECL_OVERRIDE;
|
||||||
void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
|
void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
|
||||||
void mouseMoveEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
|
void mouseMoveEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
|
||||||
void wheelEvent(QWheelEvent *event) Q_DECL_OVERRIDE;
|
void wheelEvent(QWheelEvent *event) Q_DECL_OVERRIDE;
|
||||||
void drawDecal(LineShaderData &data, const DecalXY &decal);
|
QSize minimumSizeHint() const override;
|
||||||
void drawDecal(LineShaderData out[], const DecalXY &decal);
|
QSize sizeHint() const override;
|
||||||
|
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void newContext(Context *ctx);
|
void newContext(Context *ctx);
|
||||||
void onSelectedArchItem(std::vector<DecalXY> decals);
|
void onSelectedArchItem(std::vector<DecalXY> decals);
|
||||||
void onHighlightGroupChanged(std::vector<DecalXY> decals, int group);
|
void onHighlightGroupChanged(std::vector<DecalXY> decals, int group);
|
||||||
void pokeRenderer(void);
|
void pokeRenderer(void);
|
||||||
|
void zoomIn();
|
||||||
|
void zoomOut();
|
||||||
|
void zoomSelected();
|
||||||
|
void zoomOutbound();
|
||||||
|
|
||||||
private:
|
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 zoomNear_ = 1.0f; // do not zoom closer than this
|
||||||
const float zoomFar_ = 10000.0f; // do not zoom further than this
|
const float zoomFar_ = 10000.0f; // do not zoom further than this
|
||||||
|
|
||||||
const float zoomLvl1_ = 100.0f;
|
const float zoomLvl1_ = 100.0f;
|
||||||
const float zoomLvl2_ = 50.0f;
|
const float zoomLvl2_ = 50.0f;
|
||||||
|
|
||||||
Context *ctx_;
|
Context *ctx_;
|
||||||
QTimer paintTimer_;
|
QTimer paintTimer_;
|
||||||
|
|
||||||
std::unique_ptr<PeriodicRunner> renderRunner_;
|
std::unique_ptr<PeriodicRunner> renderRunner_;
|
||||||
|
|
||||||
|
QPoint lastDragPos_;
|
||||||
|
LineShader lineShader_;
|
||||||
|
QMatrix4x4 viewMove_;
|
||||||
|
float zoom_;
|
||||||
|
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
QColor background;
|
QColor background;
|
||||||
@ -328,22 +145,29 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions
|
|||||||
|
|
||||||
struct RendererData
|
struct RendererData
|
||||||
{
|
{
|
||||||
LineShaderData decals[4];
|
LineShaderData gfxByStyle[GraphicElement::STYLE_MAX];
|
||||||
LineShaderData selected;
|
LineShaderData gfxSelected;
|
||||||
LineShaderData highlighted[8];
|
LineShaderData gfxHighlighted[8];
|
||||||
};
|
};
|
||||||
|
std::unique_ptr<RendererData> rendererData_;
|
||||||
|
QMutex rendererDataLock_;
|
||||||
|
|
||||||
struct RendererArgs
|
struct RendererArgs
|
||||||
{
|
{
|
||||||
std::vector<DecalXY> selectedItems;
|
std::vector<DecalXY> selectedDecals;
|
||||||
std::vector<DecalXY> highlightedItems[8];
|
std::vector<DecalXY> highlightedDecals[8];
|
||||||
bool highlightedOrSelectedChanged;
|
bool highlightedOrSelectedChanged;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<RendererData> rendererData_;
|
|
||||||
QMutex rendererDataLock_;
|
|
||||||
std::unique_ptr<RendererArgs> rendererArgs_;
|
std::unique_ptr<RendererArgs> rendererArgs_;
|
||||||
QMutex rendererArgsLock_;
|
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
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -159,6 +159,8 @@ void MainWindow::createMenu()
|
|||||||
taskToolBar->addAction(actionPlay);
|
taskToolBar->addAction(actionPlay);
|
||||||
taskToolBar->addAction(actionPause);
|
taskToolBar->addAction(actionPause);
|
||||||
taskToolBar->addAction(actionStop);
|
taskToolBar->addAction(actionStop);
|
||||||
|
|
||||||
|
createGraphicsBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(_MSC_VER)
|
#if defined(_MSC_VER)
|
||||||
|
@ -109,7 +109,6 @@ void Worker::budget(double freq)
|
|||||||
Q_EMIT taskStarted();
|
Q_EMIT taskStarted();
|
||||||
try {
|
try {
|
||||||
ctx->target_freq = freq;
|
ctx->target_freq = freq;
|
||||||
assign_budget(ctx);
|
|
||||||
Q_EMIT budget_finish(true);
|
Q_EMIT budget_finish(true);
|
||||||
} catch (WorkerInterruptionRequested) {
|
} catch (WorkerInterruptionRequested) {
|
||||||
Q_EMIT taskCanceled();
|
Q_EMIT taskCanceled();
|
||||||
@ -121,6 +120,8 @@ void Worker::place(bool timing_driven)
|
|||||||
Q_EMIT taskStarted();
|
Q_EMIT taskStarted();
|
||||||
try {
|
try {
|
||||||
ctx->timing_driven = timing_driven;
|
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());
|
Q_EMIT place_finished(ctx->place());
|
||||||
} catch (WorkerInterruptionRequested) {
|
} catch (WorkerInterruptionRequested) {
|
||||||
Q_EMIT taskCanceled();
|
Q_EMIT taskCanceled();
|
||||||
|
236
gui/lineshader.cc
Normal file
236
gui/lineshader.cc
Normal 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
209
gui/lineshader.h
Normal 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
|
BIN
gui/resources/shape_handles.png
Normal file
BIN
gui/resources/shape_handles.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 538 B |
BIN
gui/resources/shape_square.png
Normal file
BIN
gui/resources/shape_square.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 353 B |
BIN
gui/resources/zoom_in.png
Normal file
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
BIN
gui/resources/zoom_out.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 708 B |
350
ice40/arch.cc
350
ice40/arch.cc
@ -2,6 +2,7 @@
|
|||||||
* nextpnr -- Next Generation Place and Route
|
* nextpnr -- Next Generation Place and Route
|
||||||
*
|
*
|
||||||
* Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com>
|
* 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
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
* 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_i3 = id("I3");
|
||||||
id_dff_en = id("DFF_ENABLE");
|
id_dff_en = id("DFF_ENABLE");
|
||||||
id_neg_clk = id("NEG_CLK");
|
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;
|
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
|
PortType Arch::getBelPinType(BelId bel, PortPin pin) const
|
||||||
{
|
{
|
||||||
NPNR_ASSERT(bel != BelId());
|
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;
|
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();
|
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) {
|
if (bel_wires[i].port == pin) {
|
||||||
ret.index = bel_wires[i].wire_index;
|
ret.index = bel_wires[i].wire_index;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -448,11 +440,103 @@ GroupId Arch::getGroupByName(IdString name) const
|
|||||||
return GroupId();
|
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> Arch::getGroups() const
|
||||||
{
|
{
|
||||||
std::vector<GroupId> ret;
|
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;
|
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
|
delay_t Arch::estimateDelay(WireId src, WireId dst) const
|
||||||
{
|
{
|
||||||
NPNR_ASSERT(src != WireId());
|
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;
|
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;
|
const auto& driver = net_info->driver;
|
||||||
return v;
|
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;
|
std::vector<GraphicElement> ret;
|
||||||
|
|
||||||
if (decal.type == DecalId::TYPE_FRAME) {
|
if (decal.type == DecalId::TYPE_FRAME) {
|
||||||
for (int x = 0; x <= chip_info->width; x++)
|
/* nothing */
|
||||||
for (int y = 0; y <= chip_info->height; y++) {
|
}
|
||||||
GraphicElement el;
|
|
||||||
el.type = GraphicElement::G_LINE;
|
if (decal.type == DecalId::TYPE_GROUP) {
|
||||||
el.x1 = x - 0.05, el.x2 = x + 0.05, el.y1 = y, el.y2 = y, el.z = 0;
|
int type = (decal.index >> 16) & 255;
|
||||||
ret.push_back(el);
|
int x = (decal.index >> 8) & 255;
|
||||||
el.x1 = x, el.x2 = x, el.y1 = y - 0.05, el.y2 = y + 0.05, el.z = 0;
|
int y = decal.index & 255;
|
||||||
ret.push_back(el);
|
|
||||||
}
|
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) {
|
if (decal.type == DecalId::TYPE_WIRE) {
|
||||||
int n = chip_info->wire_data[decal.index].num_segments;
|
int n = chip_info->wire_data[decal.index].num_segments;
|
||||||
const WireSegmentPOD *p = chip_info->wire_data[decal.index].segments.get();
|
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++)
|
for (int i = 0; i < n; i++)
|
||||||
gfxTileWire(ret, p[i].x, p[i].y, GfxTileWireId(p[i].index), style);
|
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) {
|
if (decal.type == DecalId::TYPE_PIP) {
|
||||||
const PipInfoPOD &p = chip_info->pip_data[decal.index];
|
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);
|
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) {
|
if (bel_type == TYPE_ICESTORM_LC) {
|
||||||
GraphicElement el;
|
GraphicElement el;
|
||||||
el.type = GraphicElement::G_BOX;
|
el.type = GraphicElement::TYPE_BOX;
|
||||||
el.style = decal.active ? GraphicElement::G_ACTIVE : GraphicElement::G_INACTIVE;
|
el.style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_INACTIVE;
|
||||||
el.x1 = chip_info->bel_data[bel.index].x + logic_cell_x1;
|
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.x2 = chip_info->bel_data[bel.index].x + logic_cell_x2;
|
||||||
el.y1 = chip_info->bel_data[bel.index].y + logic_cell_y1 +
|
el.y1 = chip_info->bel_data[bel.index].y + logic_cell_y1 +
|
||||||
(chip_info->bel_data[bel.index].z) * logic_cell_pitch;
|
(chip_info->bel_data[bel.index].z) * logic_cell_pitch;
|
||||||
el.y2 = chip_info->bel_data[bel.index].y + logic_cell_y2 +
|
el.y2 = chip_info->bel_data[bel.index].y + logic_cell_y2 +
|
||||||
(chip_info->bel_data[bel.index].z) * logic_cell_pitch;
|
(chip_info->bel_data[bel.index].z) * logic_cell_pitch;
|
||||||
el.z = 0;
|
|
||||||
ret.push_back(el);
|
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 (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;
|
||||||
GraphicElement el;
|
el.type = GraphicElement::TYPE_BOX;
|
||||||
el.type = GraphicElement::G_BOX;
|
el.style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_INACTIVE;
|
||||||
el.x1 = chip_info->bel_data[bel.index].x + 0.1;
|
el.x1 = chip_info->bel_data[bel.index].x + logic_cell_x1;
|
||||||
el.x2 = chip_info->bel_data[bel.index].x + 0.9;
|
el.x2 = chip_info->bel_data[bel.index].x + logic_cell_x2;
|
||||||
if (chip_info->bel_data[bel.index].z == 0) {
|
el.y1 = chip_info->bel_data[bel.index].y + logic_cell_y1 +
|
||||||
el.y1 = chip_info->bel_data[bel.index].y + 0.10;
|
(4 * chip_info->bel_data[bel.index].z) * logic_cell_pitch;
|
||||||
el.y2 = chip_info->bel_data[bel.index].y + 0.45;
|
el.y2 = chip_info->bel_data[bel.index].y + logic_cell_y2 +
|
||||||
} else {
|
(4 * chip_info->bel_data[bel.index].z + 3) * logic_cell_pitch;
|
||||||
el.y1 = chip_info->bel_data[bel.index].y + 0.55;
|
ret.push_back(el);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bel_type == TYPE_ICESTORM_RAM) {
|
if (bel_type == TYPE_ICESTORM_RAM) {
|
||||||
for (int i = 0; i < 2; i++) {
|
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;
|
GraphicElement el;
|
||||||
el.type = GraphicElement::G_BOX;
|
el.type = GraphicElement::TYPE_BOX;
|
||||||
el.style = decal.active ? GraphicElement::G_ACTIVE : GraphicElement::G_INACTIVE;
|
el.style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_INACTIVE;
|
||||||
el.x1 = chip_info->bel_data[bel.index].x + logic_cell_x1;
|
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.x2 = chip_info->bel_data[bel.index].x + logic_cell_x2;
|
||||||
el.y1 = chip_info->bel_data[bel.index].y + logic_cell_y1;
|
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 + 7 * logic_cell_pitch;
|
el.y2 = chip_info->bel_data[bel.index].y + logic_cell_y2 + i + 7 * logic_cell_pitch;
|
||||||
el.z = 0;
|
|
||||||
ret.push_back(el);
|
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
|
bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, delay_t &delay) const
|
||||||
{
|
{
|
||||||
if (cell->type == id("ICESTORM_LC")) {
|
if (cell->type == id_icestorm_lc) {
|
||||||
if ((fromPort == id("I0") || fromPort == id("I1") || fromPort == id("I2") || fromPort == id("I3")) &&
|
if ((fromPort == id_i0 || fromPort == id_i1 || fromPort == id_i2 || fromPort == id_i3) &&
|
||||||
(toPort == id("O") || toPort == id("LO"))) {
|
(toPort == id_o || toPort == id_lo)) {
|
||||||
delay = 450;
|
delay = 450;
|
||||||
return true;
|
return true;
|
||||||
} else if (fromPort == id("CIN") && toPort == id("COUT")) {
|
} else if (fromPort == id_cin && toPort == id_cout) {
|
||||||
delay = 120;
|
delay = 120;
|
||||||
return true;
|
return true;
|
||||||
} else if (fromPort == id("I1") && toPort == id("COUT")) {
|
} else if (fromPort == id_i1 && toPort == id_cout) {
|
||||||
delay = 260;
|
delay = 260;
|
||||||
return true;
|
return true;
|
||||||
} else if (fromPort == id("I2") && toPort == id("COUT")) {
|
} else if (fromPort == id_i2 && toPort == id_cout) {
|
||||||
delay = 230;
|
delay = 230;
|
||||||
return true;
|
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;
|
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
|
IdString Arch::getPortClock(const CellInfo *cell, IdString port) const
|
||||||
{
|
{
|
||||||
if (cell->type == id("ICESTORM_LC") && bool_or_default(cell->params, id("DFF_ENABLE"))) {
|
if (cell->type == id_icestorm_lc && cell->lcInfo.dffEnable) {
|
||||||
if (port != id("LO") && port != id("CIN") && port != id("COUT"))
|
if (port != id_lo && port != id_cin && port != id_cout)
|
||||||
return id("CLK");
|
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();
|
return IdString();
|
||||||
}
|
}
|
||||||
@ -757,6 +831,8 @@ bool Arch::isClockPort(const CellInfo *cell, IdString port) const
|
|||||||
{
|
{
|
||||||
if (cell->type == id("ICESTORM_LC") && port == id("CLK"))
|
if (cell->type == id("ICESTORM_LC") && port == id("CLK"))
|
||||||
return true;
|
return true;
|
||||||
|
if (cell->type == id("ICESTORM_RAM") && (port == id("RCLK") || (port == id("WCLK"))))
|
||||||
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
28
ice40/arch.h
28
ice40/arch.h
@ -83,10 +83,6 @@ NPNR_PACKED_STRUCT(struct WireInfoPOD {
|
|||||||
int32_t num_uphill, num_downhill;
|
int32_t num_uphill, num_downhill;
|
||||||
RelPtr<int32_t> pips_uphill, pips_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;
|
int32_t num_bel_pins;
|
||||||
RelPtr<BelPortPOD> 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; }
|
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
|
BelType getBelType(BelId bel) const
|
||||||
{
|
{
|
||||||
NPNR_ASSERT(bel != BelId());
|
NPNR_ASSERT(bel != BelId());
|
||||||
@ -475,6 +469,8 @@ struct Arch : BaseCtx
|
|||||||
return id(chip_info->wire_data[wire.index].name.get());
|
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; }
|
uint32_t getWireChecksum(WireId wire) const { return wire.index; }
|
||||||
|
|
||||||
void bindWire(WireId wire, IdString net, PlaceStrength strength)
|
void bindWire(WireId wire, IdString net, PlaceStrength strength)
|
||||||
@ -617,6 +613,8 @@ struct Arch : BaseCtx
|
|||||||
|
|
||||||
IdString getPipName(PipId pip) const;
|
IdString getPipName(PipId pip) const;
|
||||||
|
|
||||||
|
IdString getPipType(PipId pip) const { return IdString(); }
|
||||||
|
|
||||||
uint32_t getPipChecksum(PipId pip) const { return pip.index; }
|
uint32_t getPipChecksum(PipId pip) const { return pip.index; }
|
||||||
|
|
||||||
WireId getPipSrcWire(PipId pip) const
|
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 estimateDelay(WireId src, WireId dst) const;
|
||||||
delay_t getDelayEpsilon() const { return 20; }
|
delay_t getDelayEpsilon() const { return 20; }
|
||||||
delay_t getRipupDelayPenalty() const { return 200; }
|
delay_t getRipupDelayPenalty() const { return 200; }
|
||||||
float getDelayNS(delay_t v) const { return v * 0.001; }
|
float getDelayNS(delay_t v) const { return v * 0.001; }
|
||||||
uint32_t getDelayChecksum(delay_t v) const { return v; }
|
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_cen, id_clk, id_sr;
|
||||||
IdString id_i0, id_i1, id_i2, id_i3;
|
IdString id_i0, id_i1, id_i2, id_i3;
|
||||||
IdString id_dff_en, id_neg_clk;
|
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
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
*
|
*
|
||||||
* Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com>
|
* Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com>
|
||||||
* Copyright (C) 2018 David Shah <david@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
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
* 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) {
|
if (getBelType(bel) == TYPE_ICESTORM_LC) {
|
||||||
std::vector<const CellInfo *> bel_cells;
|
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);
|
IdString cell_other = getBoundBelCell(bel_other);
|
||||||
if (cell_other != IdString()) {
|
if (cell_other != IdString()) {
|
||||||
const CellInfo *ci_other = cells.at(cell_other).get();
|
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);
|
NPNR_ASSERT(getBelType(bel) == TYPE_ICESTORM_LC);
|
||||||
|
|
||||||
std::vector<const CellInfo *> bel_cells;
|
std::vector<const CellInfo *> bel_cells;
|
||||||
|
Loc bel_loc = getBelLocation(bel);
|
||||||
for (auto bel_other : getBelsAtSameTile(bel)) {
|
for (auto bel_other : getBelsByTile(bel_loc.x, bel_loc.y)) {
|
||||||
IdString cell_other = getBoundBelCell(bel_other);
|
IdString cell_other = getBoundBelCell(bel_other);
|
||||||
if (cell_other != IdString() && bel_other != bel) {
|
if (cell_other != IdString() && bel_other != bel) {
|
||||||
const CellInfo *ci_other = cells.at(cell_other).get();
|
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);
|
bel_cells.push_back(cell);
|
||||||
return logicCellsCompatible(bel_cells);
|
return logicCellsCompatible(bel_cells);
|
||||||
} else if (cell->type == id_sb_io) {
|
} 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) != "";
|
return getBelPackagePin(bel) != "";
|
||||||
} else if (cell->type == id_sb_gb) {
|
} else if (cell->type == id_sb_gb) {
|
||||||
NPNR_ASSERT(cell->ports.at(id_glb_buf_out).net != nullptr);
|
NPNR_ASSERT(cell->ports.at(id_glb_buf_out).net != nullptr);
|
||||||
|
@ -79,8 +79,6 @@ void arch_wrap_python()
|
|||||||
conv_to_str<IdString>, conv_from_str<BelId>>::def_wrap(ctx_cls, "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,
|
fn_wrapper_0a<Context, decltype(&Context::getBels), &Context::getBels, wrap_context<BelRange>>::def_wrap(ctx_cls,
|
||||||
"getBels");
|
"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>,
|
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");
|
conv_from_str<BelId>, conv_from_str<PortPin>>::def_wrap(ctx_cls, "getBelPinWire");
|
||||||
|
6
ice40/benchmark/.gitignore
vendored
Normal file
6
ice40/benchmark/.gitignore
vendored
Normal 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
27
ice40/benchmark/Makefile
Normal 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
|
40
ice40/benchmark/hx8kdemo.pcf
Normal file
40
ice40/benchmark/hx8kdemo.pcf
Normal 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
139
ice40/benchmark/hx8kdemo.v
Normal 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
2977
ice40/benchmark/picorv32.v
Normal file
File diff suppressed because it is too large
Load Diff
241
ice40/benchmark/picosoc.v
Normal file
241
ice40/benchmark/picosoc.v
Normal 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
|
||||||
|
|
69
ice40/benchmark/report.ipynb
Normal file
69
ice40/benchmark/report.ipynb
Normal 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
|
||||||
|
}
|
137
ice40/benchmark/simpleuart.v
Normal file
137
ice40/benchmark/simpleuart.v
Normal 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
579
ice40/benchmark/spimemio.v
Normal 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
|
@ -3,6 +3,7 @@
|
|||||||
*
|
*
|
||||||
* Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com>
|
* Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com>
|
||||||
* Copyright (C) 2018 David Shah <david@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
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
* 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++) {
|
for (int i = 0; i < cfg.num_bits; i++) {
|
||||||
int8_t &cbit = tile_cfg.at(cfg.bits[i].row).at(cfg.bits[i].col);
|
int8_t &cbit = tile_cfg.at(cfg.bits[i].row).at(cfg.bits[i].col);
|
||||||
if (cbit && !value)
|
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;
|
cbit = value;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int8_t &cbit = tile_cfg.at(cfg.bits[index].row).at(cfg.bits[index].col);
|
int8_t &cbit = tile_cfg.at(cfg.bits[index].row).at(cfg.bits[index].col);
|
||||||
cbit = value;
|
cbit = value;
|
||||||
if (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;
|
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,
|
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;
|
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];
|
const auto &cbit = cell_cbits.entries[i];
|
||||||
if (cbit.entry_name.get() == name) {
|
if (cbit.entry_name.get() == name) {
|
||||||
const auto &ti = chip->bits_info->tiles_nonrouting[tile_at(ctx, cbit.x, cbit.y)];
|
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;
|
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,
|
void configure_extra_cell(chipconfig_t &config, const Context *ctx, CellInfo *cell,
|
||||||
const std::vector<std::pair<std::string, int>> ¶ms, bool string_style)
|
const std::vector<std::pair<std::string, int>> ¶ms, bool string_style, std::string prefix)
|
||||||
{
|
{
|
||||||
const ChipInfoPOD *chip = ctx->chip_info;
|
const ChipInfoPOD *chip = ctx->chip_info;
|
||||||
const auto &bc = get_ec_config(chip, cell->bel);
|
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);
|
value.resize(p.second);
|
||||||
if (p.second == 1) {
|
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 {
|
} else {
|
||||||
for (int i = 0; i < p.second; i++) {
|
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
|
// Set logic cell config
|
||||||
for (auto &cell : ctx->cells) {
|
for (auto &cell : ctx->cells) {
|
||||||
|
|
||||||
BelId bel = cell.second.get()->bel;
|
BelId bel = cell.second.get()->bel;
|
||||||
if (bel == BelId()) {
|
if (bel == BelId()) {
|
||||||
std::cout << "Found unplaced cell " << cell.first.str(ctx) << " while generating bitstream!" << std::endl;
|
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")) {
|
} else if (cell.second->type == ctx->id("SB_IO")) {
|
||||||
const BelInfoPOD &beli = ci.bel_data[bel.index];
|
const BelInfoPOD &beli = ci.bel_data[bel.index];
|
||||||
int x = beli.x, y = beli.y, z = beli.z;
|
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];
|
const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO];
|
||||||
unsigned pin_type = get_param_or_def(cell.second.get(), ctx->id("PIN_TYPE"));
|
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"));
|
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},
|
{"MODE_8x8", 1},
|
||||||
{"A_SIGNED", 1},
|
{"A_SIGNED", 1},
|
||||||
{"B_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 {
|
} else {
|
||||||
NPNR_ASSERT(false);
|
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 TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO];
|
||||||
const BelInfoPOD &beli = ci.bel_data[bel.index];
|
const BelInfoPOD &beli = ci.bel_data[bel.index];
|
||||||
int x = beli.x, y = beli.y, z = beli.z;
|
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);
|
auto ieren = get_ieren(bi, x, y, z);
|
||||||
int iex, iey, iez;
|
int iex, iey, iez;
|
||||||
std::tie(iex, iey, iez) = ieren;
|
std::tie(iex, iey, iez) = ieren;
|
||||||
if (iez != -1) {
|
if (iez != -1) {
|
||||||
if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) {
|
set_ie_bit_logical(ctx, ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), true);
|
||||||
set_config(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);
|
||||||
set_config(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) {
|
} else if (ctx->bel_to_cell[bel.index] == IdString() && ctx->getBelType(bel) == TYPE_ICESTORM_RAM) {
|
||||||
const BelInfoPOD &beli = ci.bel_data[bel.index];
|
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),
|
set_config(ti, config.at(y).at(x),
|
||||||
"Cascade.IPCON_LC0" + std::to_string(lc_idx) + "_inmux02_5", true);
|
"Cascade.IPCON_LC0" + std::to_string(lc_idx) + "_inmux02_5", true);
|
||||||
else
|
else
|
||||||
set_config(ti, config.at(y).at(x),
|
set_config(ti, config.at(y).at(x), "Cascade.MULT" + std::to_string(int(tile - TILE_DSP0)) +
|
||||||
"Cascade.MULT" + std::to_string(int(tile - TILE_DSP0)) + "_LC0" +
|
"_LC0" + std::to_string(lc_idx) + "_inmux02_5",
|
||||||
std::to_string(lc_idx) + "_inmux02_5",
|
|
||||||
true);
|
true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
*
|
*
|
||||||
* Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com>
|
* Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com>
|
||||||
* Copyright (C) 2018 David Shah <david@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
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
* 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(), "ACCUMCO", PORT_OUT);
|
||||||
add_port(ctx, new_cell.get(), "SIGNEXTOUT", 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 {
|
} else {
|
||||||
log_error("unable to create iCE40 cell of type %s", type.c_str(ctx));
|
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)
|
bool is_clock_port(const BaseCtx *ctx, const PortRef &port)
|
||||||
{
|
{
|
||||||
if (port.cell == nullptr)
|
if (port.cell == nullptr)
|
||||||
|
@ -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_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
|
// 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
|
// as needed. Set no_dff if a DFF is not being used, so that the output
|
||||||
// can be reconnected
|
// can be reconnected
|
||||||
|
343
ice40/chipdb.py
343
ice40/chipdb.py
@ -6,17 +6,11 @@ import textwrap
|
|||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="convert ICE40 chip database")
|
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("filename", type=str, help="chipdb input filename")
|
||||||
parser.add_argument("-p", "--portspins", type=str, help="path to portpins.inc")
|
parser.add_argument("-p", "--portspins", type=str, help="path to portpins.inc")
|
||||||
parser.add_argument("-g", "--gfxh", type=str, help="path to gfx.h")
|
parser.add_argument("-g", "--gfxh", type=str, help="path to gfx.h")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
endianness = "le"
|
|
||||||
nodebug = True
|
|
||||||
|
|
||||||
dev_name = None
|
dev_name = None
|
||||||
dev_width = None
|
dev_width = None
|
||||||
dev_height = None
|
dev_height = None
|
||||||
@ -41,8 +35,6 @@ extra_cells = dict()
|
|||||||
extra_cell_config = dict()
|
extra_cell_config = dict()
|
||||||
packages = list()
|
packages = list()
|
||||||
|
|
||||||
wire_uphill_belport = dict()
|
|
||||||
wire_downhill_belports = dict()
|
|
||||||
wire_belports = dict()
|
wire_belports = dict()
|
||||||
|
|
||||||
wire_names = dict()
|
wire_names = dict()
|
||||||
@ -184,6 +176,8 @@ def wire_type(name):
|
|||||||
wt = "LOCAL"
|
wt = "LOCAL"
|
||||||
elif name in ("WCLK", "WCLKE", "WE", "RCLK", "RCLKE", "RE"):
|
elif name in ("WCLK", "WCLKE", "WE", "RCLK", "RCLKE", "RE"):
|
||||||
wt = "LOCAL"
|
wt = "LOCAL"
|
||||||
|
elif name in ("PLLOUT_A", "PLLOUT_B"):
|
||||||
|
wt = "LOCAL"
|
||||||
|
|
||||||
if wt is None:
|
if wt is None:
|
||||||
print("No type for wire: %s (%s)" % (longname, name), file=sys.stderr)
|
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)
|
add_wire(0, 0, "padin_%d" % i)
|
||||||
|
|
||||||
def add_bel_input(bel, wire, port):
|
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:
|
if wire not in wire_belports:
|
||||||
wire_belports[wire] = set()
|
wire_belports[wire] = set()
|
||||||
wire_belports[wire].add((bel, port))
|
wire_belports[wire].add((bel, port))
|
||||||
bel_wires[bel].append((wire, port, 0))
|
bel_wires[bel].append((wire, port, 0))
|
||||||
|
|
||||||
def add_bel_output(bel, wire, port):
|
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:
|
if wire not in wire_belports:
|
||||||
wire_belports[wire] = set()
|
wire_belports[wire] = set()
|
||||||
wire_belports[wire].add((bel, port))
|
wire_belports[wire].add((bel, port))
|
||||||
@ -591,6 +580,9 @@ def is_ec_output(ec_entry):
|
|||||||
if "glb_netwk_" in wirename: return True
|
if "glb_netwk_" in wirename: return True
|
||||||
return False
|
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):
|
def add_bel_ec(ec):
|
||||||
ectype, x, y, z = ec
|
ectype, x, y, z = ec
|
||||||
bel = len(bel_name)
|
bel = len(bel_name)
|
||||||
@ -605,6 +597,10 @@ def add_bel_ec(ec):
|
|||||||
add_bel_output(bel, wire_names[entry[1]], entry[0])
|
add_bel_output(bel, wire_names[entry[1]], entry[0])
|
||||||
else:
|
else:
|
||||||
add_bel_input(bel, wire_names[entry[1]], entry[0])
|
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:
|
else:
|
||||||
extra_cell_config[bel].append(entry)
|
extra_cell_config[bel].append(entry)
|
||||||
|
|
||||||
@ -662,270 +658,61 @@ for tile_xy, tile_type in sorted(tiles.items()):
|
|||||||
add_bel_ec(ec)
|
add_bel_ec(ec)
|
||||||
|
|
||||||
for ec in sorted(extra_cells.keys()):
|
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)
|
add_bel_ec(ec)
|
||||||
|
|
||||||
class BinaryBlobAssembler:
|
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):
|
def l(self, name, ltype = None, export = False):
|
||||||
assert not self.finalized
|
if ltype is None:
|
||||||
assert name not in self.labels
|
print("label %s" % (name,))
|
||||||
assert len(self.data) not in self.labels_byaddr
|
else:
|
||||||
self.labels[name] = len(self.data)
|
print("label %s %s" % (name, ltype))
|
||||||
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 r(self, name, comment):
|
def r(self, name, comment):
|
||||||
assert not self.finalized
|
if comment is None:
|
||||||
assert len(self.data) % 4 == 0
|
print("ref %s" % (name,))
|
||||||
assert len(self.data) not in self.refs
|
else:
|
||||||
if self.nodebug:
|
print("ref %s %s" % (name, comment))
|
||||||
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)"
|
|
||||||
|
|
||||||
def s(self, s, comment):
|
def s(self, s, comment):
|
||||||
assert not self.finalized
|
assert "|" not in s
|
||||||
if self.nodebug:
|
print("str |%s| %s" % (s, comment))
|
||||||
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)
|
|
||||||
|
|
||||||
def u8(self, v, comment):
|
def u8(self, v, comment):
|
||||||
assert not self.finalized
|
if comment is None:
|
||||||
if self.nodebug:
|
print("u8 %d" % (v,))
|
||||||
comment = None
|
else:
|
||||||
self.data.append(v)
|
print("u8 %d %s" % (v, comment))
|
||||||
if comment is not None:
|
|
||||||
self.comments[len(self.data)] = comment
|
|
||||||
|
|
||||||
def u16(self, v, comment):
|
def u16(self, v, comment):
|
||||||
assert not self.finalized
|
if comment is None:
|
||||||
assert len(self.data) % 2 == 0
|
print("u16 %d" % (v,))
|
||||||
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)
|
|
||||||
else:
|
else:
|
||||||
assert 0
|
print("u16 %d %s" % (v, comment))
|
||||||
if comment is not None:
|
|
||||||
self.comments[len(self.data)] = comment
|
|
||||||
|
|
||||||
def u32(self, v, comment):
|
def u32(self, v, comment):
|
||||||
assert not self.finalized
|
if comment is None:
|
||||||
assert len(self.data) % 4 == 0
|
print("u32 %d" % (v,))
|
||||||
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)
|
|
||||||
else:
|
else:
|
||||||
assert 0
|
print("u32 %d %s" % (v, comment))
|
||||||
if comment is not None:
|
|
||||||
self.comments[len(self.data)] = comment
|
|
||||||
|
|
||||||
def finalize(self):
|
def pre(self, s):
|
||||||
assert not self.finalized
|
print("pre %s" % s)
|
||||||
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 write_verbose_c(self, f, ctype = "const unsigned char"):
|
def post(self, s):
|
||||||
assert self.finalized
|
print("post %s" % s)
|
||||||
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 write_compact_c(self, f, ctype = "const unsigned char"):
|
def push(self, name):
|
||||||
assert self.finalized
|
print("push %s" % name)
|
||||||
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"):
|
def pop(self):
|
||||||
assert self.finalized
|
print("pop")
|
||||||
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"):
|
bba = BinaryBlobAssembler()
|
||||||
assert self.finalized
|
bba.pre('#include "nextpnr.h"')
|
||||||
assert self.data[len(self.data)-1] == 0
|
bba.pre('NEXTPNR_NAMESPACE_BEGIN')
|
||||||
print("%s %s[%d] =" % (ctype, self.cname, len(self.data)), file=f)
|
bba.post('NEXTPNR_NAMESPACE_END')
|
||||||
print(" \"", end="", file=f)
|
bba.push("chipdb_blob_%s" % dev_name)
|
||||||
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.r("chip_info_%s" % dev_name, "chip_info")
|
bba.r("chip_info_%s" % dev_name, "chip_info")
|
||||||
|
|
||||||
index = 0
|
index = 0
|
||||||
@ -1001,15 +788,6 @@ for wire in range(num_wires):
|
|||||||
num_downhill = 0
|
num_downhill = 0
|
||||||
list_downhill = None
|
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:
|
if wire in wire_belports:
|
||||||
num_bel_pins = len(wire_belports[wire])
|
num_bel_pins = len(wire_belports[wire])
|
||||||
bba.l("wire%d_bels" % wire, "BelPortPOD")
|
bba.l("wire%d_bels" % wire, "BelPortPOD")
|
||||||
@ -1028,19 +806,9 @@ for wire in range(num_wires):
|
|||||||
info["num_downhill"] = num_downhill
|
info["num_downhill"] = num_downhill
|
||||||
info["list_downhill"] = list_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["num_bel_pins"] = num_bel_pins
|
||||||
info["list_bel_pins"] = ("wire%d_bels" % wire) if num_bel_pins > 0 else None
|
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
|
avg_x, avg_y = 0, 0
|
||||||
if wire in wire_xy:
|
if wire in wire_xy:
|
||||||
for x, y in wire_xy[wire]:
|
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.u32(info["num_downhill"], "num_downhill")
|
||||||
bba.r(info["list_uphill"], "pips_uphill")
|
bba.r(info["list_uphill"], "pips_uphill")
|
||||||
bba.r(info["list_downhill"], "pips_downhill")
|
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.u32(info["num_bel_pins"], "num_bel_pins")
|
||||||
bba.r(info["list_bel_pins"], "bel_pins")
|
bba.r(info["list_bel_pins"], "bel_pins")
|
||||||
bba.u32(len(wire_segments[wire]), "num_segments")
|
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("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.r("package_info_%s" % dev_name, "packages_data")
|
||||||
|
|
||||||
bba.finalize()
|
bba.pop()
|
||||||
|
|
||||||
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')
|
|
||||||
|
|
||||||
|
@ -19,13 +19,18 @@ if (MSVC)
|
|||||||
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/ice40/resources/chipdb.rc PROPERTIES LANGUAGE RC)
|
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/ice40/resources/chipdb.rc PROPERTIES LANGUAGE RC)
|
||||||
foreach (dev ${devices})
|
foreach (dev ${devices})
|
||||||
set(DEV_TXT_DB ${ICEBOX_ROOT}/chipdb-${dev}.txt)
|
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_CC_DB ${CMAKE_CURRENT_SOURCE_DIR}/ice40/chipdbs/chipdb-${dev}.bin)
|
||||||
set(DEV_PORTS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ice40/portpins.inc)
|
set(DEV_PORTS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ice40/portpins.inc)
|
||||||
set(DEV_GFXH ${CMAKE_CURRENT_SOURCE_DIR}/ice40/gfx.h)
|
set(DEV_GFXH ${CMAKE_CURRENT_SOURCE_DIR}/ice40/gfx.h)
|
||||||
add_custom_command(OUTPUT ${DEV_CC_DB}
|
add_custom_command(OUTPUT ${DEV_CC_BBA_DB}
|
||||||
COMMAND ${PYTHON_EXECUTABLE} ${DB_PY} -b -p ${DEV_PORTS_INC} -g ${DEV_GFXH} ${DEV_TXT_DB} > ${DEV_CC_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}
|
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})
|
target_sources(ice40_chipdb PRIVATE ${DEV_CC_DB})
|
||||||
set_source_files_properties(${DEV_CC_DB} PROPERTIES HEADER_FILE_ONLY TRUE)
|
set_source_files_properties(${DEV_CC_DB} PROPERTIES HEADER_FILE_ONLY TRUE)
|
||||||
foreach (target ${family_targets})
|
foreach (target ${family_targets})
|
||||||
@ -36,14 +41,20 @@ else()
|
|||||||
target_compile_options(ice40_chipdb PRIVATE -g0 -O0 -w)
|
target_compile_options(ice40_chipdb PRIVATE -g0 -O0 -w)
|
||||||
foreach (dev ${devices})
|
foreach (dev ${devices})
|
||||||
set(DEV_TXT_DB ${ICEBOX_ROOT}/chipdb-${dev}.txt)
|
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_CC_DB ${CMAKE_CURRENT_SOURCE_DIR}/ice40/chipdbs/chipdb-${dev}.cc)
|
||||||
set(DEV_PORTS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ice40/portpins.inc)
|
set(DEV_PORTS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ice40/portpins.inc)
|
||||||
set(DEV_GFXH ${CMAKE_CURRENT_SOURCE_DIR}/ice40/gfx.h)
|
set(DEV_GFXH ${CMAKE_CURRENT_SOURCE_DIR}/ice40/gfx.h)
|
||||||
add_custom_command(OUTPUT ${DEV_CC_DB}
|
add_custom_command(OUTPUT ${DEV_CC_BBA_DB}
|
||||||
COMMAND ${PYTHON_EXECUTABLE} ${DB_PY} -c -p ${DEV_PORTS_INC} -g ${DEV_GFXH} ${DEV_TXT_DB} > ${DEV_CC_DB}.new
|
COMMAND ${PYTHON_EXECUTABLE} ${DB_PY} -p ${DEV_PORTS_INC} -g ${DEV_GFXH} ${DEV_TXT_DB} > ${DEV_CC_BBA_DB}.new
|
||||||
COMMAND mv ${DEV_CC_DB}.new ${DEV_CC_DB}
|
COMMAND mv ${DEV_CC_BBA_DB}.new ${DEV_CC_BBA_DB}
|
||||||
DEPENDS ${DEV_TXT_DB} ${DB_PY}
|
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})
|
target_sources(ice40_chipdb PRIVATE ${DEV_CC_DB})
|
||||||
foreach (target ${family_targets})
|
foreach (target ${family_targets})
|
||||||
target_sources(${target} PRIVATE $<TARGET_OBJECTS:ice40_chipdb>)
|
target_sources(${target} PRIVATE $<TARGET_OBJECTS:ice40_chipdb>)
|
||||||
|
15
ice40/gfx.cc
15
ice40/gfx.cc
@ -24,7 +24,7 @@ NEXTPNR_NAMESPACE_BEGIN
|
|||||||
void gfxTileWire(std::vector<GraphicElement> &g, int x, int y, GfxTileWireId id, GraphicElement::style_t style)
|
void gfxTileWire(std::vector<GraphicElement> &g, int x, int y, GfxTileWireId id, GraphicElement::style_t style)
|
||||||
{
|
{
|
||||||
GraphicElement el;
|
GraphicElement el;
|
||||||
el.type = GraphicElement::G_LINE;
|
el.type = GraphicElement::TYPE_LINE;
|
||||||
el.style = style;
|
el.style = style;
|
||||||
|
|
||||||
// Horizontal Span-4 Wires
|
// 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);
|
float ty = 0.5 * (y1 + y2);
|
||||||
|
|
||||||
GraphicElement el;
|
GraphicElement el;
|
||||||
el.type = GraphicElement::G_LINE;
|
el.type = GraphicElement::TYPE_ARROW;
|
||||||
el.style = style;
|
el.style = style;
|
||||||
|
|
||||||
if (fabsf(x1 - swx1) < 0.001 && fabsf(x2 - swx1) < 0.001) {
|
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))
|
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);
|
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
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -464,7 +464,11 @@ enum GfxTileWireId
|
|||||||
TILE_WIRE_SP12_H_R_23,
|
TILE_WIRE_SP12_H_R_23,
|
||||||
|
|
||||||
TILE_WIRE_SP12_H_L_22,
|
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);
|
void gfxTileWire(std::vector<GraphicElement> &g, int x, int y, GfxTileWireId id, GraphicElement::style_t style);
|
||||||
|
@ -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\"";
|
const std::string style = "stroke=\"black\" stroke-width=\"0.1\" fill=\"none\"";
|
||||||
|
|
||||||
for (auto &el : ctx->getDecalGraphics(decal.decal)) {
|
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=\""
|
std::cout << "<rect x=\"" << (offset + scale * (decal.x + el.x1)) << "\" y=\""
|
||||||
<< (offset + scale * (decal.y + el.y1)) << "\" height=\"" << (scale * (el.y2 - el.y1))
|
<< (offset + scale * (decal.y + el.y1)) << "\" height=\"" << (scale * (el.y2 - el.y1))
|
||||||
<< "\" width=\"" << (scale * (el.x2 - el.x1)) << "\" " << style << "/>\n";
|
<< "\" 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=\""
|
std::cout << "<line x1=\"" << (offset + scale * (decal.x + el.x1)) << "\" y1=\""
|
||||||
<< (offset + scale * (decal.y + el.y1)) << "\" x2=\"" << (offset + scale * (decal.x + el.x2))
|
<< (offset + scale * (decal.y + el.y1)) << "\" x2=\"" << (offset + scale * (decal.x + el.x2))
|
||||||
<< "\" y2=\"" << (offset + scale * (decal.y + el.y2)) << "\" " << style << "/>\n";
|
<< "\" y2=\"" << (offset + scale * (decal.y + el.y2)) << "\" " << style << "/>\n";
|
||||||
@ -144,18 +144,16 @@ int main(int argc, char *argv[])
|
|||||||
#endif
|
#endif
|
||||||
if (vm.count("help") || argc == 1) {
|
if (vm.count("help") || argc == 1) {
|
||||||
help:
|
help:
|
||||||
std::cout << boost::filesystem::basename(argv[0])
|
std::cout << boost::filesystem::basename(argv[0]) << " -- Next Generation Place and Route (git "
|
||||||
<< " -- Next Generation Place and Route (git "
|
"sha1 " GIT_COMMIT_HASH_STR ")\n";
|
||||||
"sha1 " GIT_COMMIT_HASH_STR ")\n";
|
|
||||||
std::cout << "\n";
|
std::cout << "\n";
|
||||||
std::cout << options << "\n";
|
std::cout << options << "\n";
|
||||||
return argc != 1;
|
return argc != 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vm.count("version")) {
|
if (vm.count("version")) {
|
||||||
std::cout << boost::filesystem::basename(argv[0])
|
std::cout << boost::filesystem::basename(argv[0]) << " -- Next Generation Place and Route (git "
|
||||||
<< " -- Next Generation Place and Route (git "
|
"sha1 " GIT_COMMIT_HASH_STR ")\n";
|
||||||
"sha1 " GIT_COMMIT_HASH_STR ")\n";
|
|
||||||
return 1;
|
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->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;
|
ctx->timing_driven = true;
|
||||||
if (vm.count("no-tmdriv"))
|
if (vm.count("no-tmdriv"))
|
||||||
|
233
ice40/pack.cc
233
ice40/pack.cc
@ -3,6 +3,7 @@
|
|||||||
*
|
*
|
||||||
* Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com>
|
* Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com>
|
||||||
* Copyright (C) 2018 David Shah <david@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
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
* 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") &&
|
(user.port != ctx->id("CLK") &&
|
||||||
((constval && user.port == ctx->id("CE")) || (!constval && user.port != ctx->id("CE"))))) {
|
((constval && user.port == ctx->id("CE")) || (!constval && user.port != ctx->id("CE"))))) {
|
||||||
uc->ports[user.port].net = nullptr;
|
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 {
|
} else {
|
||||||
uc->ports[user.port].net = constnet;
|
uc->ports[user.port].net = constnet;
|
||||||
constnet->users.push_back(user);
|
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
|
// Pack special functions
|
||||||
static void pack_special(Context *ctx)
|
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));
|
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));
|
new_cells.push_back(std::move(packed));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
42
ice40/picorv32_benchmark.py
Executable file
42
ice40/picorv32_benchmark.py
Executable 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))
|
@ -75,11 +75,9 @@ static void get_chain_midpoint(const Context *ctx, const CellChain &chain, float
|
|||||||
for (auto cell : chain.cells) {
|
for (auto cell : chain.cells) {
|
||||||
if (cell->bel == BelId())
|
if (cell->bel == BelId())
|
||||||
continue;
|
continue;
|
||||||
int bel_x, bel_y;
|
Loc bel_loc = ctx->getBelLocation(cell->bel);
|
||||||
bool bel_gb;
|
total_x += bel_loc.x;
|
||||||
ctx->estimatePosition(cell->bel, bel_x, bel_y, bel_gb);
|
total_y += bel_loc.y;
|
||||||
total_x += bel_x;
|
|
||||||
total_y += bel_y;
|
|
||||||
N++;
|
N++;
|
||||||
}
|
}
|
||||||
NPNR_ASSERT(N > 0);
|
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
|
// This returns how "evil" a logic cell is, and thus how likely it is to be ripped up
|
||||||
// during logic tile legalisation
|
// during logic tile legalisation
|
||||||
int score = 0;
|
int score = 0;
|
||||||
if (get_net_or_empty(cell, ctx->id("I0")))
|
if (get_net_or_empty(cell, ctx->id_i0))
|
||||||
++score;
|
++score;
|
||||||
if (get_net_or_empty(cell, ctx->id("I1")))
|
if (get_net_or_empty(cell, ctx->id_i1))
|
||||||
++score;
|
++score;
|
||||||
if (get_net_or_empty(cell, ctx->id("I2")))
|
if (get_net_or_empty(cell, ctx->id_i2))
|
||||||
++score;
|
++score;
|
||||||
if (get_net_or_empty(cell, ctx->id("I3")))
|
if (get_net_or_empty(cell, ctx->id_i3))
|
||||||
++score;
|
++score;
|
||||||
if (bool_or_default(cell->params, ctx->id("DFF_ENABLE"))) {
|
if (cell->lcInfo.dffEnable) {
|
||||||
const NetInfo *cen = get_net_or_empty(cell, ctx->id("CEN")), *sr = get_net_or_empty(cell, ctx->id("SR"));
|
if (cell->lcInfo.cen)
|
||||||
if (cen)
|
|
||||||
score += 10;
|
score += 10;
|
||||||
if (sr)
|
if (cell->lcInfo.sr)
|
||||||
score += 10;
|
score += 10;
|
||||||
if (bool_or_default(cell->params, ctx->id("NEG_CLK")))
|
if (cell->lcInfo.negClk)
|
||||||
score += 5;
|
score += 5;
|
||||||
}
|
}
|
||||||
return score;
|
return score;
|
||||||
|
@ -118,6 +118,8 @@ X(DYNAMICDELAY_5)
|
|||||||
X(DYNAMICDELAY_6)
|
X(DYNAMICDELAY_6)
|
||||||
X(DYNAMICDELAY_7)
|
X(DYNAMICDELAY_7)
|
||||||
X(LOCK)
|
X(LOCK)
|
||||||
|
X(PLLOUT_A)
|
||||||
|
X(PLLOUT_B)
|
||||||
X(BYPASS)
|
X(BYPASS)
|
||||||
X(RESETB)
|
X(RESETB)
|
||||||
X(LATCHINPUTVALUE)
|
X(LATCHINPUTVALUE)
|
||||||
|
Loading…
Reference in New Issue
Block a user