This commit is contained in:
Eddie Hung 2018-12-18 11:40:10 -08:00
commit 8d069c0115
29 changed files with 3601 additions and 24 deletions

View File

@ -8,4 +8,5 @@ task:
build_script: mkdir build && cd build && cmake .. -DARCH=all -DTRELLIS_ROOT=/usr/local/src/prjtrellis -DBUILD_TESTS=on && make -j $(nproc)
test_generic_script: cd build && ./nextpnr-generic-test
test_ice40_script: cd build && ./nextpnr-ice40-test
test_ecp5_script: cd build && ./nextpnr-ecp5-test
smoketest_ice40_script: export NEXTPNR=$(pwd)/build/nextpnr-ice40 && cd ice40/smoketest/attosoc && ./smoketest.sh
test_ecp5_script: cd build && ./nextpnr-ecp5-test

View File

@ -6,11 +6,21 @@ RUN set -e -x ;\
apt-get -y update ;\
apt-get -y upgrade ;\
apt-get -y install \
build-essential cmake clang python3-dev libboost-all-dev qt5-default git
build-essential autoconf cmake clang bison wget flex gperf \
libreadline-dev gawk tcl-dev libffi-dev graphviz xdot python3-dev \
libboost-all-dev qt5-default git libftdi-dev pkg-config
RUN set -e -x ;\
apt-get -y install \
libftdi-dev pkg-config
mkdir -p /usr/local/src ;\
cd /usr/local/src ;\
git clone --recursive https://github.com/steveicarus/iverilog.git ;\
cd iverilog ;\
git reset --hard 172d7eb0a3665f89b91d601b5912c33acedc81e5 ;\
sh autoconf.sh ;\
./configure ;\
make -j $(nproc) ;\
make install ;\
rm -rf /usr/local/src/iverilog
RUN set -e -x ;\
mkdir -p /usr/local/src ;\
@ -21,6 +31,16 @@ RUN set -e -x ;\
make -j $(nproc) ;\
make install
RUN set -e -x ;\
mkdir -p /usr/local/src ;\
cd /usr/local/src ;\
git clone --recursive https://github.com/YosysHQ/yosys.git ;\
cd yosys ;\
git reset --hard 47a5dfdaa4bd7d400c6e3d58476de80904df460d ;\
make -j $(nproc) ;\
make install ;\
rm -rf /usr/local/src/yosys
RUN set -e -x ;\
mkdir -p /usr/local/src ;\
cd /usr/local/src ;\
@ -31,3 +51,5 @@ RUN set -e -x ;\
cmake -DCMAKE_INSTALL_PREFIX=/usr . ;\
make -j $(nproc) ;\
make install

View File

@ -153,3 +153,25 @@ void pyinterpreter_release()
{
PyEval_ReleaseThread(m_threadState);
}
std::string pyinterpreter_execute_file(const char *python_file, int *errorCode)
{
PyEval_AcquireThread(m_threadState);
*errorCode = 0;
std::string res;
FILE *fp = fopen(python_file, "r");
if (fp == NULL) {
*errorCode = 1;
res = "Fatal error: file not found " + std::string(python_file) + "\n";
return res;
}
if (PyRun_SimpleFile(fp, python_file)==-1) {
*errorCode = 1;
PyErr_Print();
}
res = redirector_take_output(m_threadState);
PyEval_ReleaseThread(m_threadState);
return res;
}

View File

@ -33,4 +33,5 @@ void pyinterpreter_initialize();
void pyinterpreter_finalize();
void pyinterpreter_aquire();
void pyinterpreter_release();
std::string pyinterpreter_execute_file(const char *python_file, int *errorCode);
#endif // PYINTERPRETER_H

View File

@ -414,7 +414,6 @@ struct Timing
while (crit_net) {
const PortInfo *crit_ipin = nullptr;
delay_t max_arrival = std::numeric_limits<delay_t>::min();
// Look at all input ports on its driving cell
for (const auto &port : crit_net->driver.cell->ports) {
if (port.second.type != PORT_IN || !port.second.net)
@ -428,14 +427,21 @@ struct Timing
int port_clocks;
TimingPortClass portClass =
ctx->getPortTimingClass(crit_net->driver.cell, port.first, port_clocks);
if (portClass == TMG_REGISTER_INPUT || portClass == TMG_CLOCK_INPUT ||
portClass == TMG_ENDPOINT || portClass == TMG_IGNORE)
if (portClass == TMG_CLOCK_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE ||
portClass == TMG_REGISTER_INPUT)
continue;
// And find the fanin net with the latest arrival time
if (net_data.count(port.second.net) &&
net_data.at(port.second.net).count(crit_pair.first.start)) {
const auto net_arrival = net_data.at(port.second.net).at(crit_pair.first.start).max_arrival;
auto net_arrival = net_data.at(port.second.net).at(crit_pair.first.start).max_arrival;
if (net_delays) {
for (auto &user : port.second.net->users)
if (user.port == port.first && user.cell == crit_net->driver.cell) {
net_arrival += ctx->getNetinfoRouteDelay(port.second.net, user);
break;
}
}
net_arrival += comb_delay.maxDelay();
if (net_arrival > max_arrival) {
max_arrival = net_arrival;
crit_ipin = &port.second;
@ -445,7 +451,6 @@ struct Timing
if (!crit_ipin)
break;
// Now convert PortInfo* into a PortRef*
for (auto &usr : crit_ipin->net->users) {
if (usr.cell->name == crit_net->driver.cell->name && usr.port == crit_ipin->name) {
@ -779,6 +784,7 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p
int port_clocks;
auto portClass = ctx->getPortTimingClass(front_driver.cell, front_driver.port, port_clocks);
IdString last_port = front_driver.port;
int clock_start = -1;
if (portClass == TMG_REGISTER_OUTPUT) {
for (int i = 0; i < port_clocks; i++) {
TimingClockingInfo clockInfo = ctx->getPortClockingInfo(front_driver.cell, front_driver.port, i);
@ -786,8 +792,7 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p
if (clknet != nullptr && clknet->name == clocks.start.clock &&
clockInfo.edge == clocks.start.edge) {
last_port = clockInfo.clock_port;
total += clockInfo.clockToQ.maxDelay();
logic_total += clockInfo.clockToQ.maxDelay();
clock_start = i;
break;
}
}
@ -801,11 +806,15 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p
auto &driver = net->driver;
auto driver_cell = driver.cell;
DelayInfo comb_delay;
if (last_port == driver.port) {
if (clock_start != -1) {
auto clockInfo = ctx->getPortClockingInfo(driver_cell, driver.port, clock_start);
comb_delay = clockInfo.clockToQ;
clock_start = -1;
} else if (last_port == driver.port) {
// Case where we start with a STARTPOINT etc
comb_delay = ctx->getDelayFromNS(0);
} else {
ctx->getCellDelay(sink_cell, last_port, driver.port, comb_delay);
ctx->getCellDelay(driver_cell, last_port, driver.port, comb_delay);
}
total += comb_delay.maxDelay();
logic_total += comb_delay.maxDelay();

View File

@ -60,7 +60,7 @@ template <> struct hash<std::pair<int, NEXTPNR_NAMESPACE_PREFIX BelId>>
return seed;
}
};
#ifndef ARCH_GENERIC
template <> struct hash<std::pair<NEXTPNR_NAMESPACE_PREFIX IdString, NEXTPNR_NAMESPACE_PREFIX BelId>>
{
std::size_t
@ -72,6 +72,7 @@ template <> struct hash<std::pair<NEXTPNR_NAMESPACE_PREFIX IdString, NEXTPNR_NAM
return seed;
}
};
#endif
} // namespace std
NEXTPNR_NAMESPACE_BEGIN

View File

@ -579,6 +579,8 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort
return false;
} else if (cell->type == id_DP16KD) {
return false;
} else if (cell->type == id_IOLOGIC || cell->type == id_SIOLOGIC) {
return false;
} else {
return false;
}
@ -669,6 +671,16 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in
return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT;
}
return TMG_IGNORE;
} else if (cell->type == id_IOLOGIC || cell->type == id_SIOLOGIC) {
if (port == id_CLK || port == id_ECLK) {
return TMG_CLOCK_INPUT;
} else if (port == id_IOLDO || port == id_IOLDOI || port == id_IOLDOD || port == id_IOLTO || port == id_PADDI ||
port == id_DQSR90 || port == id_DQSW || port == id_DQSW270) {
return TMG_IGNORE;
} else {
clockInfoCount = 1;
return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT;
}
} else {
log_error("cell type '%s' is unsupported (instantiated as '%s')\n", cell->type.c_str(this),
cell->name.c_str(this));
@ -744,6 +756,14 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port
info.setup = getDelayFromNS(1);
info.hold = getDelayFromNS(0);
}
} else if (cell->type == id_IOLOGIC || cell->type == id_SIOLOGIC) {
info.clock_port = id_CLK;
if (cell->ports.at(port).type == PORT_OUT) {
info.clockToQ = getDelayFromNS(0.5);
} else {
info.setup = getDelayFromNS(0.1);
info.hold = getDelayFromNS(0);
}
}
return info;
}

View File

@ -745,6 +745,12 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
}
if (ci->attrs.count(ctx->id("SLEWRATE")))
cc.tiles[pio_tile].add_enum(pio + ".SLEWRATE", str_or_default(ci->attrs, ctx->id("SLEWRATE"), "SLOW"));
std::string datamux_oddr = str_or_default(ci->params, ctx->id("DATAMUX_ODDR"), "PADDO");
if (datamux_oddr != "PADDO")
cc.tiles[pic_tile].add_enum(pio + ".DATAMUX_ODDR", datamux_oddr);
std::string datamux_mddr = str_or_default(ci->params, ctx->id("DATAMUX_MDDR"), "PADDO");
if (datamux_mddr != "PADDO")
cc.tiles[pic_tile].add_enum(pio + ".DATAMUX_MDDR", datamux_mddr);
} else if (ci->type == ctx->id("DCCA")) {
// Nothing to do
} else if (ci->type == ctx->id("DP16KD")) {
@ -1078,6 +1084,18 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
int_to_bitvector(int_or_default(ci->attrs, ctx->id("MFG_ENABLE_FILTEROPAMP"), 0), 1));
cc.tilegroups.push_back(tg);
} else if (ci->type == id_IOLOGIC || ci->type == id_SIOLOGIC) {
Loc pio_loc = ctx->getBelLocation(ci->bel);
pio_loc.z -= ci->type == id_SIOLOGIC ? 2 : 4;
std::string pic_tile = get_pic_tile(ctx, ctx->getBelByLocation(pio_loc));
std::string prim = std::string("IOLOGIC") + "ABCD"[pio_loc.z];
for (auto &param : ci->params) {
if (param.first == ctx->id("DELAY.DEL_VALUE"))
cc.tiles[pic_tile].add_word(prim + "." + param.first.str(ctx),
int_to_bitvector(std::stoi(param.second), 7));
else
cc.tiles[pic_tile].add_enum(prim + "." + param.first.str(ctx), param.second);
}
} else if (ci->type == id_DCUA) {
TileGroup tg;
tg.tiles = get_dcu_tiles(ctx, ci->bel);

View File

@ -41,6 +41,22 @@ std::unique_ptr<CellInfo> create_ecp5_cell(Context *ctx, IdString type, std::str
new_cell->name = ctx->id(name);
}
new_cell->type = type;
auto copy_bel_ports = [&]() {
// First find a Bel of the target type
BelId tgt;
for (auto bel : ctx->getBels()) {
if (ctx->getBelType(bel) == type) {
tgt = bel;
break;
}
}
NPNR_ASSERT(tgt != BelId());
for (auto port : ctx->getBelPins(tgt)) {
add_port(ctx, new_cell.get(), port.str(ctx), ctx->getBelPinType(tgt, port));
}
};
if (type == ctx->id("TRELLIS_SLICE")) {
new_cell->params[ctx->id("MODE")] = "LOGIC";
new_cell->params[ctx->id("GSR")] = "DISABLED";
@ -111,11 +127,17 @@ std::unique_ptr<CellInfo> create_ecp5_cell(Context *ctx, IdString type, std::str
} else if (type == ctx->id("TRELLIS_IO")) {
new_cell->params[ctx->id("DIR")] = "INPUT";
new_cell->attrs[ctx->id("IO_TYPE")] = "LVCMOS33";
new_cell->params[ctx->id("DATAMUX_ODDR")] = "PADDO";
new_cell->params[ctx->id("DATAMUX_MDDR")] = "PADDO";
add_port(ctx, new_cell.get(), "B", PORT_INOUT);
add_port(ctx, new_cell.get(), "I", PORT_IN);
add_port(ctx, new_cell.get(), "T", PORT_IN);
add_port(ctx, new_cell.get(), "O", PORT_OUT);
add_port(ctx, new_cell.get(), "IOLDO", PORT_IN);
add_port(ctx, new_cell.get(), "IOLTO", PORT_IN);
} else if (type == ctx->id("LUT4")) {
new_cell->params[ctx->id("INIT")] = "0";
@ -150,6 +172,35 @@ std::unique_ptr<CellInfo> create_ecp5_cell(Context *ctx, IdString type, std::str
add_port(ctx, new_cell.get(), "CLKI", PORT_IN);
add_port(ctx, new_cell.get(), "CLKO", PORT_OUT);
add_port(ctx, new_cell.get(), "CE", PORT_IN);
} else if (type == id_IOLOGIC || type == id_SIOLOGIC) {
new_cell->params[ctx->id("MODE")] = "NONE";
new_cell->params[ctx->id("GSR")] = "DISABLED";
new_cell->params[ctx->id("CLKIMUX")] = "CLK";
new_cell->params[ctx->id("CLKOMUX")] = "CLK";
new_cell->params[ctx->id("LSRIMUX")] = "0";
new_cell->params[ctx->id("LSROMUX")] = "0";
new_cell->params[ctx->id("LSRMUX")] = "LSR";
new_cell->params[ctx->id("DELAY.OUTDEL")] = "DISABLED";
new_cell->params[ctx->id("DELAY.DEL_VALUE")] = "0";
new_cell->params[ctx->id("DELAY.WAIT_FOR_EDGE")] = "DISABLED";
if (type == id_IOLOGIC) {
new_cell->params[ctx->id("IDDRXN.MODE")] = "NONE";
new_cell->params[ctx->id("ODDRXN.MODE")] = "NONE";
new_cell->params[ctx->id("MIDDRX.MODE")] = "NONE";
new_cell->params[ctx->id("MODDRX.MODE")] = "NONE";
new_cell->params[ctx->id("MTDDRX.MODE")] = "NONE";
new_cell->params[ctx->id("IOLTOMUX")] = "NONE";
new_cell->params[ctx->id("MTDDRX.DQSW_INVERT")] = "DISABLED";
new_cell->params[ctx->id("MTDDRX.REGSET")] = "RESET";
new_cell->params[ctx->id("MIDDRX_MODDRX.WRCLKMUX")] = "NONE";
}
// Just copy ports from the Bel
copy_bel_ports();
} else {
log_error("unable to create ECP5 cell of type %s", type.c_str(ctx));
}

View File

@ -1142,3 +1142,43 @@ X(PAD)
X(PADDI)
X(PADDO)
X(PADDT)
X(IOLOGIC)
X(SIOLOGIC)
X(DI)
X(IOLDO)
X(IOLDOD)
X(IOLDOI)
X(IOLTO)
X(INDD)
X(LOADN)
X(MOVE)
X(DIRECTION)
X(TSDATA0)
X(TXDATA0)
X(TXDATA1)
X(RXDATA0)
X(RXDATA1)
X(INFF)
X(CFLAG)
X(ECLK)
X(TSDATA1)
X(TXDATA2)
X(TXDATA3)
X(RXDATA2)
X(RXDATA3)
X(TXDATA4)
X(TXDATA5)
X(TXDATA6)
X(RXDATA4)
X(RXDATA5)
X(RXDATA6)
X(DQSR90)
X(DQSW270)
X(DQSW)
X(RDPNTR0)
X(RDPNTR1)
X(RDPNTR2)
X(WRPNTR0)
X(WRPNTR1)
X(WRPNTR2)

View File

@ -58,6 +58,8 @@ class Ecp5GlobalRouter
if (user.cell->type == id_DCUA && (user.port == id_CH0_FF_RXI_CLK || user.port == id_CH1_FF_RXI_CLK ||
user.port == id_CH0_FF_TXI_CLK || user.port == id_CH1_FF_TXI_CLK))
return true;
if ((user.cell->type == id_IOLOGIC || user.cell->type == id_SIOLOGIC) && user.port == id_CLK)
return true;
return false;
}

View File

@ -1377,10 +1377,144 @@ class Ecp5Packer
}
}
// Check if two nets have identical constant drivers
bool equal_constant(NetInfo *a, NetInfo *b)
{
if (a->driver.cell == nullptr || b->driver.cell == nullptr)
return (a->driver.cell == nullptr && b->driver.cell == nullptr);
if (a->driver.cell->type != ctx->id("GND") && a->driver.cell->type != ctx->id("VCC"))
return false;
return a->driver.cell->type == b->driver.cell->type;
}
// Pack IOLOGIC
void pack_iologic()
{
std::unordered_map<IdString, CellInfo *> pio_iologic;
auto set_iologic_sclk = [&](CellInfo *iol, CellInfo *prim, IdString port, bool input) {
NetInfo *sclk = nullptr;
if (prim->ports.count(port))
sclk = prim->ports[port].net;
if (sclk == nullptr) {
iol->params[input ? ctx->id("CLKIMUX") : ctx->id("CLKOMUX")] = "0";
} else {
iol->params[input ? ctx->id("CLKIMUX") : ctx->id("CLKOMUX")] = "CLK";
if (iol->ports[id_CLK].net != nullptr) {
if (iol->ports[id_CLK].net != sclk && !equal_constant(iol->ports[id_CLK].net, sclk))
log_error("IOLOGIC '%s' has conflicting clocks '%s' and '%s'\n", iol->name.c_str(ctx),
iol->ports[id_CLK].net->name.c_str(ctx), sclk->name.c_str(ctx));
} else {
connect_port(ctx, sclk, iol, id_CLK);
}
}
if (prim->ports.count(port))
disconnect_port(ctx, prim, port);
};
auto set_iologic_lsr = [&](CellInfo *iol, CellInfo *prim, IdString port, bool input) {
NetInfo *lsr = nullptr;
if (prim->ports.count(port))
lsr = prim->ports[port].net;
if (lsr == nullptr) {
iol->params[input ? ctx->id("LSRIMUX") : ctx->id("LSROMUX")] = "0";
} else {
iol->params[input ? ctx->id("LSRIMUX") : ctx->id("LSROMUX")] = "LSRMUX";
if (iol->ports[id_LSR].net != nullptr && !equal_constant(iol->ports[id_LSR].net, lsr)) {
if (iol->ports[id_LSR].net != lsr)
log_error("IOLOGIC '%s' has conflicting LSR signals '%s' and '%s'\n", iol->name.c_str(ctx),
iol->ports[id_LSR].net->name.c_str(ctx), lsr->name.c_str(ctx));
} else {
connect_port(ctx, lsr, iol, id_LSR);
}
}
if (prim->ports.count(port))
disconnect_port(ctx, prim, port);
};
auto set_iologic_mode = [&](CellInfo *iol, std::string mode) {
auto &curr_mode = iol->params[ctx->id("MODE")];
if (curr_mode != "NONE" && curr_mode != mode)
log_error("IOLOGIC '%s' has conflicting modes '%s' and '%s'\n", iol->name.c_str(ctx), curr_mode.c_str(),
mode.c_str());
curr_mode = mode;
};
auto create_pio_iologic = [&](CellInfo *pio, CellInfo *curr) {
if (!pio->attrs.count(ctx->id("BEL")))
log_error("IOLOGIC functionality (DDR, DELAY, DQS, etc) can only be used with pin-constrained PIO "
"(while processing '%s').\n",
curr->name.c_str(ctx));
BelId bel = ctx->getBelByName(ctx->id(pio->attrs.at(ctx->id("BEL"))));
NPNR_ASSERT(bel != BelId());
log_info("IOLOGIC component %s connected to PIO Bel %s\n", curr->name.c_str(ctx),
ctx->getBelName(bel).c_str(ctx));
Loc loc = ctx->getBelLocation(bel);
bool s = false;
if (loc.y == 0 || loc.y == (ctx->chip_info->height - 1))
s = true;
std::unique_ptr<CellInfo> iol =
create_ecp5_cell(ctx, s ? id_SIOLOGIC : id_IOLOGIC, pio->name.str(ctx) + "$IOL");
loc.z += s ? 2 : 4;
iol->attrs[ctx->id("BEL")] = ctx->getBelName(ctx->getBelByLocation(loc)).str(ctx);
CellInfo *iol_ptr = iol.get();
pio_iologic[pio->name] = iol_ptr;
new_cells.push_back(std::move(iol));
return iol_ptr;
};
for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second;
if (ci->type == ctx->id("IDDRX1F")) {
CellInfo *pio = net_driven_by(ctx, ci->ports.at(ctx->id("D")).net, is_trellis_io, id_O);
if (pio == nullptr || ci->ports.at(ctx->id("D")).net->users.size() > 1)
log_error("IDDRX1F '%s' D input must be connected only to a top level input\n",
ci->name.c_str(ctx));
CellInfo *iol;
if (pio_iologic.count(pio->name))
iol = pio_iologic.at(pio->name);
else
iol = create_pio_iologic(pio, ci);
set_iologic_mode(iol, "IDDRX1_ODDRX1");
replace_port(ci, ctx->id("D"), iol, id_PADDI);
set_iologic_sclk(iol, ci, ctx->id("SCLK"), true);
set_iologic_lsr(iol, ci, ctx->id("RST"), true);
replace_port(ci, ctx->id("Q0"), iol, id_RXDATA0);
replace_port(ci, ctx->id("Q1"), iol, id_RXDATA1);
iol->params[ctx->id("GSR")] = str_or_default(ci->params, ctx->id("GSR"), "DISABLED");
packed_cells.insert(cell.first);
} else if (ci->type == ctx->id("ODDRX1F")) {
CellInfo *pio = net_only_drives(ctx, ci->ports.at(ctx->id("Q")).net, is_trellis_io, id_I, true);
if (pio == nullptr)
log_error("ODDRX1F '%s' Q output must be connected only to a top level output\n",
ci->name.c_str(ctx));
CellInfo *iol;
if (pio_iologic.count(pio->name))
iol = pio_iologic.at(pio->name);
else
iol = create_pio_iologic(pio, ci);
set_iologic_mode(iol, "IDDRX1_ODDRX1");
replace_port(ci, ctx->id("Q"), iol, id_IOLDO);
replace_port(pio, id_I, pio, id_IOLDO);
pio->params[ctx->id("DATAMUX_ODDR")] = "IOLDO";
set_iologic_sclk(iol, ci, ctx->id("SCLK"), false);
set_iologic_lsr(iol, ci, ctx->id("RST"), false);
replace_port(ci, ctx->id("D0"), iol, id_TXDATA0);
replace_port(ci, ctx->id("D1"), iol, id_TXDATA1);
iol->params[ctx->id("GSR")] = str_or_default(ci->params, ctx->id("GSR"), "DISABLED");
packed_cells.insert(cell.first);
}
}
flush_cells();
};
public:
void pack()
{
pack_io();
pack_iologic();
pack_ebr();
pack_dsps();
pack_dcus();

View File

@ -22,5 +22,6 @@
<file>resources/route.png</file>
<file>resources/time_add.png</file>
<file>resources/open_json.png</file>
<file>resources/py.png</file>
</qresource>
</RCC>

View File

@ -189,6 +189,12 @@ void BaseMainWindow::createMenusAndBars()
actionRoute->setEnabled(false);
connect(actionRoute, &QAction::triggered, task, &TaskManager::route);
actionExecutePy = new QAction("Execute Python", this);
actionExecutePy->setIcon(QIcon(":/icons/resources/py.png"));
actionExecutePy->setStatusTip("Execute Python script");
actionExecutePy->setEnabled(true);
connect(actionExecutePy, &QAction::triggered, this, &BaseMainWindow::execute_python);
// Worker control toolbar actions
actionPlay = new QAction("Play", this);
actionPlay->setIcon(QIcon(":/icons/resources/control_play.png"));
@ -249,6 +255,8 @@ void BaseMainWindow::createMenusAndBars()
menuDesign->addAction(actionAssignBudget);
menuDesign->addAction(actionPlace);
menuDesign->addAction(actionRoute);
menuDesign->addSeparator();
menuDesign->addAction(actionExecutePy);
// Add Help menu actions
menuHelp->addAction(actionAbout);
@ -268,6 +276,7 @@ void BaseMainWindow::createMenusAndBars()
mainActionBar->addAction(actionAssignBudget);
mainActionBar->addAction(actionPlace);
mainActionBar->addAction(actionRoute);
mainActionBar->addAction(actionExecutePy);
// Add worker control toolbar
QToolBar *workerControlToolBar = new QToolBar("Worker");
@ -412,6 +421,7 @@ void BaseMainWindow::disableActions()
actionAssignBudget->setEnabled(false);
actionPlace->setEnabled(false);
actionRoute->setEnabled(false);
actionExecutePy->setEnabled(true);
actionPlay->setEnabled(false);
actionPause->setEnabled(false);
@ -454,6 +464,14 @@ void BaseMainWindow::open_proj()
}
}
void BaseMainWindow::execute_python()
{
QString fileName = QFileDialog::getOpenFileName(this, QString("Execute Python"), QString(), QString("*.py"));
if (!fileName.isEmpty()) {
console->execute_python(fileName.toStdString());
}
}
void BaseMainWindow::notifyChangeContext() { Q_EMIT contextChanged(ctx.get()); }
void BaseMainWindow::save_proj()
{

View File

@ -78,6 +78,8 @@ class BaseMainWindow : public QMainWindow
void budget();
void place();
void execute_python();
void pack_finished(bool status);
void budget_finish(bool status);
void place_finished(bool status);
@ -122,6 +124,9 @@ class BaseMainWindow : public QMainWindow
QAction *actionAssignBudget;
QAction *actionPlace;
QAction *actionRoute;
QAction *actionExecutePy;
QAction *actionPlay;
QAction *actionPause;
QAction *actionStop;

View File

@ -76,4 +76,21 @@ void PythonConsole::moveCursorToEnd()
setTextCursor(cursor);
}
void PythonConsole::execute_python(std::string filename)
{
int errorCode = 0;
std::string res;
res = pyinterpreter_execute_file(filename.c_str(), &errorCode);
if (res.size()) {
if (errorCode) {
setTextColor(ERROR_COLOR);
} else {
setTextColor(OUTPUT_COLOR);
}
append(res.c_str());
setTextColor(NORMAL_COLOR);
moveCursorToEnd();
}
}
NEXTPNR_NAMESPACE_END

View File

@ -43,6 +43,7 @@ class PythonConsole : public QTextEdit, public ParseListener
void displayString(QString text);
void moveCursorToEnd();
virtual void parseEvent(const ParseMessage &message);
void execute_python(std::string filename);
protected:
static const QColor NORMAL_COLOR;

View File

@ -114,4 +114,6 @@ void PythonTab::clearBuffer() { console->clear(); }
void PythonTab::info(std::string str) { console->displayString(str.c_str()); }
void PythonTab::execute_python(std::string filename) { console->execute_python(filename); }
NEXTPNR_NAMESPACE_END

View File

@ -45,6 +45,7 @@ class PythonTab : public QWidget
void newContext(Context *ctx);
void info(std::string str);
void clearBuffer();
void execute_python(std::string filename);
private:
PythonConsole *console;

BIN
gui/resources/py.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

6
ice40/smoketest/attosoc/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
attosoc_pnr.v
attosoc.asc
attosoc.json
attosoc_pnr_tb
testbench.vcd
output.txt

View File

@ -0,0 +1,10 @@
set_io clk E4
set_io led[0] B2
set_io led[1] F5
set_io led[2] B1
set_io led[3] C1
set_io led[4] C2
set_io led[5] F4
set_io led[6] D2
set_io led[7] G5

View File

@ -0,0 +1,127 @@
/*
* ECP5 PicoRV32 demo
*
* Copyright (C) 2017 Clifford Wolf <clifford@clifford.at>
* Copyright (C) 2018 David Shah <dave@ds0.me>
*
* 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.
*
*/
`ifdef PICORV32_V
`error "attosoc.v must be read before picorv32.v!"
`endif
`define PICORV32_REGS picosoc_regs
module attosoc (
input clk,
output reg [7:0] led
);
reg [5:0] reset_cnt = 0;
wire resetn = &reset_cnt;
always @(posedge clk) begin
reset_cnt <= reset_cnt + !resetn;
end
parameter integer MEM_WORDS = 256;
parameter [31:0] STACKADDR = (4*MEM_WORDS); // end of memory
parameter [31:0] PROGADDR_RESET = 32'h 0000_0000; // ROM at 0x0
parameter integer ROM_BYTES = 256;
reg [7:0] rom [0:ROM_BYTES-1];
wire [31:0] rom_rdata = {rom[mem_addr+3], rom[mem_addr+2], rom[mem_addr+1], rom[mem_addr+0]};
initial $readmemh("firmware.hex", rom);
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 rom_ready = mem_valid && mem_addr[31:24] == 8'h00;
wire iomem_valid;
wire iomem_ready;
wire [31:0] iomem_addr;
wire [31:0] iomem_wdata;
wire [3:0] iomem_wstrb;
wire [31:0] iomem_rdata;
assign iomem_valid = mem_valid && (mem_addr[31:24] > 8'h 01);
assign iomem_ready = 1'b1;
assign iomem_wstrb = mem_wstrb;
assign iomem_addr = mem_addr;
assign iomem_wdata = mem_wdata;
wire [31:0] spimemio_cfgreg_do;
always @(posedge clk)
if (iomem_valid && iomem_wstrb[0])
led <= iomem_wdata[7:0];
assign mem_ready = (iomem_valid && iomem_ready) || rom_ready;
assign mem_rdata = rom_rdata;
picorv32 #(
.STACKADDR(STACKADDR),
.PROGADDR_RESET(PROGADDR_RESET),
.PROGADDR_IRQ(32'h 0000_0000),
.BARREL_SHIFTER(0),
.COMPRESSED_ISA(0),
.ENABLE_MUL(0),
.ENABLE_DIV(0),
.ENABLE_IRQ(0),
.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 )
);
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

View File

@ -0,0 +1,32 @@
module testbench();
integer out;
reg clk;
always #5 clk = (clk === 1'b0);
initial begin
out = $fopen("output.txt","w");
$dumpfile("testbench.vcd");
$dumpvars(0, testbench);
repeat (100) begin
repeat (256) @(posedge clk);
$display("+256 cycles");
end
$fclose(out);
#100;
$finish;
end
wire [7:0] led;
always @(led) begin
#1 $display("%b", led);
$fwrite(out, "%b\n", led);
end
attosoc uut (
.clk (clk ),
.led (led )
);
endmodule

View File

@ -0,0 +1,6 @@
@00000000
13 04 10 00 B7 04 00 02 93 09 00 10 13 04 14 00
63 44 34 01 13 04 20 00 13 09 20 00 63 5E 89 00
13 05 04 00 93 05 09 00 EF 00 80 01 63 08 05 00
13 09 19 00 6F F0 9F FE 23 A0 84 00 6F F0 1F FD
93 02 10 00 33 05 B5 40 E3 5E 55 FE 67 80 00 00

View File

@ -0,0 +1,16 @@
00000000
00000010
00000011
00000101
00000111
00001011
00001101
00010001
00010011
00010111
00011101
00011111
00100101
00101001
00101011
00101111

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -ex
yosys -q -p 'synth_ice40 -json attosoc.json -top attosoc' attosoc.v picorv32.v
$NEXTPNR --hx8k --json attosoc.json --pcf attosoc.pcf --asc attosoc.asc --freq 50
icetime -tmd hx8k -c 50 attosoc.asc
icebox_vlog -L -l -p attosoc.pcf -c -n attosoc attosoc.asc > attosoc_pnr.v
iverilog -o attosoc_pnr_tb attosoc_pnr.v attosoc_tb.v `yosys-config --datdir/ice40/cells_sim.v`
vvp attosoc_pnr_tb
diff output.txt golden.txt

View File

@ -627,7 +627,9 @@ static void insert_iobuf(Context *ctx, NetInfo *net, PortType type, const string
iobuf->ports[ctx->id("O")] = PortInfo{ctx->id("O"), net, PORT_OUT};
// Special case: input, etc, directly drives inout
if (net->driver.cell != nullptr) {
assert(net->driver.cell->type == ctx->id("$nextpnr_iobuf"));
if (net->driver.cell->type != ctx->id("$nextpnr_iobuf"))
log_error("Top-level input '%s' also driven by %s.%s.\n", name.c_str(),
net->driver.cell->name.c_str(ctx), net->driver.port.c_str(ctx));
net = net->driver.cell->ports.at(ctx->id("I")).net;
}
assert(net->driver.cell == nullptr);
@ -690,8 +692,28 @@ void json_import(Context *ctx, string modname, JsonNode *node)
log_info("Importing module %s\n", modname.c_str());
// Multiple labels might refer to the same net. For now we resolve conflicts thus:
// - names with fewer $ are always prefered
// - between equal $ counts, fewer .s are prefered
// - ties are resolved alphabetically
auto prefer_netlabel = [](const std::string &a, const std::string &b) {
if (b.empty())
return true;
long a_dollars = std::count(a.begin(), a.end(), '$'), b_dollars = std::count(b.begin(), b.end(), '$');
if (a_dollars < b_dollars)
return true;
else if (a_dollars > b_dollars)
return false;
long a_dots = std::count(a.begin(), a.end(), '.'), b_dots = std::count(b.begin(), b.end(), '.');
if (a_dots < b_dots)
return true;
else if (a_dots > b_dots)
return false;
return a < b;
};
// Import netnames
std::vector<IdString> netnames;
std::vector<std::string> netlabels;
if (node->data_dict.count("netnames")) {
JsonNode *cell_parent = node->data_dict.at("netnames");
for (int nnid = 0; nnid < GetSize(cell_parent->data_dict_keys); nnid++) {
@ -705,15 +727,19 @@ void json_import(Context *ctx, string modname, JsonNode *node)
size_t num_bits = bits->data_array.size();
for (size_t i = 0; i < num_bits; i++) {
int netid = bits->data_array.at(i)->data_number;
if (netid >= int(netnames.size()))
netnames.resize(netid + 1);
netnames.at(netid) = ctx->id(
basename + (num_bits == 1 ? "" : std::string("[") + std::to_string(i) + std::string("]")));
if (netid >= int(netlabels.size()))
netlabels.resize(netid + 1);
std::string name =
basename + (num_bits == 1 ? "" : std::string("[") + std::to_string(i) + std::string("]"));
if (prefer_netlabel(name, netlabels.at(netid)))
netlabels.at(netid) = name;
}
}
}
}
std::vector<IdString> netids;
std::transform(netlabels.begin(), netlabels.end(), std::back_inserter(netids),
[ctx](const std::string &s) { return ctx->id(s); });
if (node->data_dict.count("cells")) {
JsonNode *cell_parent = node->data_dict.at("cells");
//
@ -723,7 +749,7 @@ void json_import(Context *ctx, string modname, JsonNode *node)
//
for (int cellid = 0; cellid < GetSize(cell_parent->data_dict_keys); cellid++) {
JsonNode *here = cell_parent->data_dict.at(cell_parent->data_dict_keys[cellid]);
json_import_cell(ctx, modname, netnames, here, cell_parent->data_dict_keys[cellid]);
json_import_cell(ctx, modname, netids, here, cell_parent->data_dict_keys[cellid]);
}
}
@ -737,7 +763,7 @@ void json_import(Context *ctx, string modname, JsonNode *node)
JsonNode *here;
here = ports_parent->data_dict.at(ports_parent->data_dict_keys[portid]);
json_import_toplevel_port(ctx, modname, netnames, ports_parent->data_dict_keys[portid], here);
json_import_toplevel_port(ctx, modname, netids, ports_parent->data_dict_keys[portid], here);
}
}
check_all_nets_driven(ctx);