Merge http://github.com/YosysHQ/nextpnr into xc7
This commit is contained in:
commit
8d069c0115
@ -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)
|
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_generic_script: cd build && ./nextpnr-generic-test
|
||||||
test_ice40_script: cd build && ./nextpnr-ice40-test
|
test_ice40_script: cd build && ./nextpnr-ice40-test
|
||||||
|
smoketest_ice40_script: export NEXTPNR=$(pwd)/build/nextpnr-ice40 && cd ice40/smoketest/attosoc && ./smoketest.sh
|
||||||
test_ecp5_script: cd build && ./nextpnr-ecp5-test
|
test_ecp5_script: cd build && ./nextpnr-ecp5-test
|
@ -6,11 +6,21 @@ RUN set -e -x ;\
|
|||||||
apt-get -y update ;\
|
apt-get -y update ;\
|
||||||
apt-get -y upgrade ;\
|
apt-get -y upgrade ;\
|
||||||
apt-get -y install \
|
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 ;\
|
RUN set -e -x ;\
|
||||||
apt-get -y install \
|
mkdir -p /usr/local/src ;\
|
||||||
libftdi-dev pkg-config
|
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 ;\
|
RUN set -e -x ;\
|
||||||
mkdir -p /usr/local/src ;\
|
mkdir -p /usr/local/src ;\
|
||||||
@ -21,6 +31,16 @@ RUN set -e -x ;\
|
|||||||
make -j $(nproc) ;\
|
make -j $(nproc) ;\
|
||||||
make install
|
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 ;\
|
RUN set -e -x ;\
|
||||||
mkdir -p /usr/local/src ;\
|
mkdir -p /usr/local/src ;\
|
||||||
cd /usr/local/src ;\
|
cd /usr/local/src ;\
|
||||||
@ -31,3 +51,5 @@ RUN set -e -x ;\
|
|||||||
cmake -DCMAKE_INSTALL_PREFIX=/usr . ;\
|
cmake -DCMAKE_INSTALL_PREFIX=/usr . ;\
|
||||||
make -j $(nproc) ;\
|
make -j $(nproc) ;\
|
||||||
make install
|
make install
|
||||||
|
|
||||||
|
|
||||||
|
@ -153,3 +153,25 @@ void pyinterpreter_release()
|
|||||||
{
|
{
|
||||||
PyEval_ReleaseThread(m_threadState);
|
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;
|
||||||
|
}
|
||||||
|
@ -33,4 +33,5 @@ void pyinterpreter_initialize();
|
|||||||
void pyinterpreter_finalize();
|
void pyinterpreter_finalize();
|
||||||
void pyinterpreter_aquire();
|
void pyinterpreter_aquire();
|
||||||
void pyinterpreter_release();
|
void pyinterpreter_release();
|
||||||
|
std::string pyinterpreter_execute_file(const char *python_file, int *errorCode);
|
||||||
#endif // PYINTERPRETER_H
|
#endif // PYINTERPRETER_H
|
||||||
|
@ -414,7 +414,6 @@ struct Timing
|
|||||||
while (crit_net) {
|
while (crit_net) {
|
||||||
const PortInfo *crit_ipin = nullptr;
|
const PortInfo *crit_ipin = nullptr;
|
||||||
delay_t max_arrival = std::numeric_limits<delay_t>::min();
|
delay_t max_arrival = std::numeric_limits<delay_t>::min();
|
||||||
|
|
||||||
// Look at all input ports on its driving cell
|
// Look at all input ports on its driving cell
|
||||||
for (const auto &port : crit_net->driver.cell->ports) {
|
for (const auto &port : crit_net->driver.cell->ports) {
|
||||||
if (port.second.type != PORT_IN || !port.second.net)
|
if (port.second.type != PORT_IN || !port.second.net)
|
||||||
@ -428,14 +427,21 @@ struct Timing
|
|||||||
int port_clocks;
|
int port_clocks;
|
||||||
TimingPortClass portClass =
|
TimingPortClass portClass =
|
||||||
ctx->getPortTimingClass(crit_net->driver.cell, port.first, port_clocks);
|
ctx->getPortTimingClass(crit_net->driver.cell, port.first, port_clocks);
|
||||||
if (portClass == TMG_REGISTER_INPUT || portClass == TMG_CLOCK_INPUT ||
|
if (portClass == TMG_CLOCK_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE ||
|
||||||
portClass == TMG_ENDPOINT || portClass == TMG_IGNORE)
|
portClass == TMG_REGISTER_INPUT)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// And find the fanin net with the latest arrival time
|
// And find the fanin net with the latest arrival time
|
||||||
if (net_data.count(port.second.net) &&
|
if (net_data.count(port.second.net) &&
|
||||||
net_data.at(port.second.net).count(crit_pair.first.start)) {
|
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) {
|
if (net_arrival > max_arrival) {
|
||||||
max_arrival = net_arrival;
|
max_arrival = net_arrival;
|
||||||
crit_ipin = &port.second;
|
crit_ipin = &port.second;
|
||||||
@ -445,7 +451,6 @@ struct Timing
|
|||||||
|
|
||||||
if (!crit_ipin)
|
if (!crit_ipin)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Now convert PortInfo* into a PortRef*
|
// Now convert PortInfo* into a PortRef*
|
||||||
for (auto &usr : crit_ipin->net->users) {
|
for (auto &usr : crit_ipin->net->users) {
|
||||||
if (usr.cell->name == crit_net->driver.cell->name && usr.port == crit_ipin->name) {
|
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;
|
int port_clocks;
|
||||||
auto portClass = ctx->getPortTimingClass(front_driver.cell, front_driver.port, port_clocks);
|
auto portClass = ctx->getPortTimingClass(front_driver.cell, front_driver.port, port_clocks);
|
||||||
IdString last_port = front_driver.port;
|
IdString last_port = front_driver.port;
|
||||||
|
int clock_start = -1;
|
||||||
if (portClass == TMG_REGISTER_OUTPUT) {
|
if (portClass == TMG_REGISTER_OUTPUT) {
|
||||||
for (int i = 0; i < port_clocks; i++) {
|
for (int i = 0; i < port_clocks; i++) {
|
||||||
TimingClockingInfo clockInfo = ctx->getPortClockingInfo(front_driver.cell, front_driver.port, 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 &&
|
if (clknet != nullptr && clknet->name == clocks.start.clock &&
|
||||||
clockInfo.edge == clocks.start.edge) {
|
clockInfo.edge == clocks.start.edge) {
|
||||||
last_port = clockInfo.clock_port;
|
last_port = clockInfo.clock_port;
|
||||||
total += clockInfo.clockToQ.maxDelay();
|
clock_start = i;
|
||||||
logic_total += clockInfo.clockToQ.maxDelay();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -801,11 +806,15 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p
|
|||||||
auto &driver = net->driver;
|
auto &driver = net->driver;
|
||||||
auto driver_cell = driver.cell;
|
auto driver_cell = driver.cell;
|
||||||
DelayInfo comb_delay;
|
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
|
// Case where we start with a STARTPOINT etc
|
||||||
comb_delay = ctx->getDelayFromNS(0);
|
comb_delay = ctx->getDelayFromNS(0);
|
||||||
} else {
|
} 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();
|
total += comb_delay.maxDelay();
|
||||||
logic_total += comb_delay.maxDelay();
|
logic_total += comb_delay.maxDelay();
|
||||||
|
@ -60,7 +60,7 @@ template <> struct hash<std::pair<int, NEXTPNR_NAMESPACE_PREFIX BelId>>
|
|||||||
return seed;
|
return seed;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
#ifndef ARCH_GENERIC
|
||||||
template <> struct hash<std::pair<NEXTPNR_NAMESPACE_PREFIX IdString, NEXTPNR_NAMESPACE_PREFIX BelId>>
|
template <> struct hash<std::pair<NEXTPNR_NAMESPACE_PREFIX IdString, NEXTPNR_NAMESPACE_PREFIX BelId>>
|
||||||
{
|
{
|
||||||
std::size_t
|
std::size_t
|
||||||
@ -72,6 +72,7 @@ template <> struct hash<std::pair<NEXTPNR_NAMESPACE_PREFIX IdString, NEXTPNR_NAM
|
|||||||
return seed;
|
return seed;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
} // namespace std
|
} // namespace std
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_BEGIN
|
NEXTPNR_NAMESPACE_BEGIN
|
||||||
|
20
ecp5/arch.cc
20
ecp5/arch.cc
@ -579,6 +579,8 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort
|
|||||||
return false;
|
return false;
|
||||||
} else if (cell->type == id_DP16KD) {
|
} else if (cell->type == id_DP16KD) {
|
||||||
return false;
|
return false;
|
||||||
|
} else if (cell->type == id_IOLOGIC || cell->type == id_SIOLOGIC) {
|
||||||
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
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 (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT;
|
||||||
}
|
}
|
||||||
return TMG_IGNORE;
|
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 {
|
} else {
|
||||||
log_error("cell type '%s' is unsupported (instantiated as '%s')\n", cell->type.c_str(this),
|
log_error("cell type '%s' is unsupported (instantiated as '%s')\n", cell->type.c_str(this),
|
||||||
cell->name.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.setup = getDelayFromNS(1);
|
||||||
info.hold = getDelayFromNS(0);
|
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;
|
return info;
|
||||||
}
|
}
|
||||||
|
@ -745,6 +745,12 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
|
|||||||
}
|
}
|
||||||
if (ci->attrs.count(ctx->id("SLEWRATE")))
|
if (ci->attrs.count(ctx->id("SLEWRATE")))
|
||||||
cc.tiles[pio_tile].add_enum(pio + ".SLEWRATE", str_or_default(ci->attrs, ctx->id("SLEWRATE"), "SLOW"));
|
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")) {
|
} else if (ci->type == ctx->id("DCCA")) {
|
||||||
// Nothing to do
|
// Nothing to do
|
||||||
} else if (ci->type == ctx->id("DP16KD")) {
|
} 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));
|
int_to_bitvector(int_or_default(ci->attrs, ctx->id("MFG_ENABLE_FILTEROPAMP"), 0), 1));
|
||||||
|
|
||||||
cc.tilegroups.push_back(tg);
|
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 ¶m : 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) {
|
} else if (ci->type == id_DCUA) {
|
||||||
TileGroup tg;
|
TileGroup tg;
|
||||||
tg.tiles = get_dcu_tiles(ctx, ci->bel);
|
tg.tiles = get_dcu_tiles(ctx, ci->bel);
|
||||||
|
@ -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->name = ctx->id(name);
|
||||||
}
|
}
|
||||||
new_cell->type = type;
|
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")) {
|
if (type == ctx->id("TRELLIS_SLICE")) {
|
||||||
new_cell->params[ctx->id("MODE")] = "LOGIC";
|
new_cell->params[ctx->id("MODE")] = "LOGIC";
|
||||||
new_cell->params[ctx->id("GSR")] = "DISABLED";
|
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")) {
|
} else if (type == ctx->id("TRELLIS_IO")) {
|
||||||
new_cell->params[ctx->id("DIR")] = "INPUT";
|
new_cell->params[ctx->id("DIR")] = "INPUT";
|
||||||
new_cell->attrs[ctx->id("IO_TYPE")] = "LVCMOS33";
|
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(), "B", PORT_INOUT);
|
||||||
add_port(ctx, new_cell.get(), "I", PORT_IN);
|
add_port(ctx, new_cell.get(), "I", PORT_IN);
|
||||||
add_port(ctx, new_cell.get(), "T", 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(), "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")) {
|
} else if (type == ctx->id("LUT4")) {
|
||||||
new_cell->params[ctx->id("INIT")] = "0";
|
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(), "CLKI", PORT_IN);
|
||||||
add_port(ctx, new_cell.get(), "CLKO", PORT_OUT);
|
add_port(ctx, new_cell.get(), "CLKO", PORT_OUT);
|
||||||
add_port(ctx, new_cell.get(), "CE", PORT_IN);
|
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 {
|
} else {
|
||||||
log_error("unable to create ECP5 cell of type %s", type.c_str(ctx));
|
log_error("unable to create ECP5 cell of type %s", type.c_str(ctx));
|
||||||
}
|
}
|
||||||
|
@ -1142,3 +1142,43 @@ X(PAD)
|
|||||||
X(PADDI)
|
X(PADDI)
|
||||||
X(PADDO)
|
X(PADDO)
|
||||||
X(PADDT)
|
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)
|
||||||
|
@ -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 ||
|
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))
|
user.port == id_CH0_FF_TXI_CLK || user.port == id_CH1_FF_TXI_CLK))
|
||||||
return true;
|
return true;
|
||||||
|
if ((user.cell->type == id_IOLOGIC || user.cell->type == id_SIOLOGIC) && user.port == id_CLK)
|
||||||
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
134
ecp5/pack.cc
134
ecp5/pack.cc
@ -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:
|
public:
|
||||||
void pack()
|
void pack()
|
||||||
{
|
{
|
||||||
pack_io();
|
pack_io();
|
||||||
|
pack_iologic();
|
||||||
pack_ebr();
|
pack_ebr();
|
||||||
pack_dsps();
|
pack_dsps();
|
||||||
pack_dcus();
|
pack_dcus();
|
||||||
|
@ -22,5 +22,6 @@
|
|||||||
<file>resources/route.png</file>
|
<file>resources/route.png</file>
|
||||||
<file>resources/time_add.png</file>
|
<file>resources/time_add.png</file>
|
||||||
<file>resources/open_json.png</file>
|
<file>resources/open_json.png</file>
|
||||||
|
<file>resources/py.png</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
@ -189,6 +189,12 @@ void BaseMainWindow::createMenusAndBars()
|
|||||||
actionRoute->setEnabled(false);
|
actionRoute->setEnabled(false);
|
||||||
connect(actionRoute, &QAction::triggered, task, &TaskManager::route);
|
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
|
// Worker control toolbar actions
|
||||||
actionPlay = new QAction("Play", this);
|
actionPlay = new QAction("Play", this);
|
||||||
actionPlay->setIcon(QIcon(":/icons/resources/control_play.png"));
|
actionPlay->setIcon(QIcon(":/icons/resources/control_play.png"));
|
||||||
@ -249,6 +255,8 @@ void BaseMainWindow::createMenusAndBars()
|
|||||||
menuDesign->addAction(actionAssignBudget);
|
menuDesign->addAction(actionAssignBudget);
|
||||||
menuDesign->addAction(actionPlace);
|
menuDesign->addAction(actionPlace);
|
||||||
menuDesign->addAction(actionRoute);
|
menuDesign->addAction(actionRoute);
|
||||||
|
menuDesign->addSeparator();
|
||||||
|
menuDesign->addAction(actionExecutePy);
|
||||||
|
|
||||||
// Add Help menu actions
|
// Add Help menu actions
|
||||||
menuHelp->addAction(actionAbout);
|
menuHelp->addAction(actionAbout);
|
||||||
@ -268,6 +276,7 @@ void BaseMainWindow::createMenusAndBars()
|
|||||||
mainActionBar->addAction(actionAssignBudget);
|
mainActionBar->addAction(actionAssignBudget);
|
||||||
mainActionBar->addAction(actionPlace);
|
mainActionBar->addAction(actionPlace);
|
||||||
mainActionBar->addAction(actionRoute);
|
mainActionBar->addAction(actionRoute);
|
||||||
|
mainActionBar->addAction(actionExecutePy);
|
||||||
|
|
||||||
// Add worker control toolbar
|
// Add worker control toolbar
|
||||||
QToolBar *workerControlToolBar = new QToolBar("Worker");
|
QToolBar *workerControlToolBar = new QToolBar("Worker");
|
||||||
@ -412,6 +421,7 @@ void BaseMainWindow::disableActions()
|
|||||||
actionAssignBudget->setEnabled(false);
|
actionAssignBudget->setEnabled(false);
|
||||||
actionPlace->setEnabled(false);
|
actionPlace->setEnabled(false);
|
||||||
actionRoute->setEnabled(false);
|
actionRoute->setEnabled(false);
|
||||||
|
actionExecutePy->setEnabled(true);
|
||||||
|
|
||||||
actionPlay->setEnabled(false);
|
actionPlay->setEnabled(false);
|
||||||
actionPause->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::notifyChangeContext() { Q_EMIT contextChanged(ctx.get()); }
|
||||||
void BaseMainWindow::save_proj()
|
void BaseMainWindow::save_proj()
|
||||||
{
|
{
|
||||||
|
@ -78,6 +78,8 @@ class BaseMainWindow : public QMainWindow
|
|||||||
void budget();
|
void budget();
|
||||||
void place();
|
void place();
|
||||||
|
|
||||||
|
void execute_python();
|
||||||
|
|
||||||
void pack_finished(bool status);
|
void pack_finished(bool status);
|
||||||
void budget_finish(bool status);
|
void budget_finish(bool status);
|
||||||
void place_finished(bool status);
|
void place_finished(bool status);
|
||||||
@ -122,6 +124,9 @@ class BaseMainWindow : public QMainWindow
|
|||||||
QAction *actionAssignBudget;
|
QAction *actionAssignBudget;
|
||||||
QAction *actionPlace;
|
QAction *actionPlace;
|
||||||
QAction *actionRoute;
|
QAction *actionRoute;
|
||||||
|
|
||||||
|
QAction *actionExecutePy;
|
||||||
|
|
||||||
QAction *actionPlay;
|
QAction *actionPlay;
|
||||||
QAction *actionPause;
|
QAction *actionPause;
|
||||||
QAction *actionStop;
|
QAction *actionStop;
|
||||||
|
@ -76,4 +76,21 @@ void PythonConsole::moveCursorToEnd()
|
|||||||
setTextCursor(cursor);
|
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
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -43,6 +43,7 @@ class PythonConsole : public QTextEdit, public ParseListener
|
|||||||
void displayString(QString text);
|
void displayString(QString text);
|
||||||
void moveCursorToEnd();
|
void moveCursorToEnd();
|
||||||
virtual void parseEvent(const ParseMessage &message);
|
virtual void parseEvent(const ParseMessage &message);
|
||||||
|
void execute_python(std::string filename);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static const QColor NORMAL_COLOR;
|
static const QColor NORMAL_COLOR;
|
||||||
|
@ -114,4 +114,6 @@ void PythonTab::clearBuffer() { console->clear(); }
|
|||||||
|
|
||||||
void PythonTab::info(std::string str) { console->displayString(str.c_str()); }
|
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
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -45,6 +45,7 @@ class PythonTab : public QWidget
|
|||||||
void newContext(Context *ctx);
|
void newContext(Context *ctx);
|
||||||
void info(std::string str);
|
void info(std::string str);
|
||||||
void clearBuffer();
|
void clearBuffer();
|
||||||
|
void execute_python(std::string filename);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PythonConsole *console;
|
PythonConsole *console;
|
||||||
|
BIN
gui/resources/py.png
Normal file
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
6
ice40/smoketest/attosoc/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
attosoc_pnr.v
|
||||||
|
attosoc.asc
|
||||||
|
attosoc.json
|
||||||
|
attosoc_pnr_tb
|
||||||
|
testbench.vcd
|
||||||
|
output.txt
|
10
ice40/smoketest/attosoc/attosoc.pcf
Normal file
10
ice40/smoketest/attosoc/attosoc.pcf
Normal 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
|
||||||
|
|
127
ice40/smoketest/attosoc/attosoc.v
Normal file
127
ice40/smoketest/attosoc/attosoc.v
Normal 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
|
32
ice40/smoketest/attosoc/attosoc_tb.v
Normal file
32
ice40/smoketest/attosoc/attosoc_tb.v
Normal 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
|
6
ice40/smoketest/attosoc/firmware.hex
Normal file
6
ice40/smoketest/attosoc/firmware.hex
Normal 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
|
16
ice40/smoketest/attosoc/golden.txt
Normal file
16
ice40/smoketest/attosoc/golden.txt
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
00000000
|
||||||
|
00000010
|
||||||
|
00000011
|
||||||
|
00000101
|
||||||
|
00000111
|
||||||
|
00001011
|
||||||
|
00001101
|
||||||
|
00010001
|
||||||
|
00010011
|
||||||
|
00010111
|
||||||
|
00011101
|
||||||
|
00011111
|
||||||
|
00100101
|
||||||
|
00101001
|
||||||
|
00101011
|
||||||
|
00101111
|
2979
ice40/smoketest/attosoc/picorv32.v
Normal file
2979
ice40/smoketest/attosoc/picorv32.v
Normal file
File diff suppressed because it is too large
Load Diff
9
ice40/smoketest/attosoc/smoketest.sh
Executable file
9
ice40/smoketest/attosoc/smoketest.sh
Executable 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
|
@ -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};
|
iobuf->ports[ctx->id("O")] = PortInfo{ctx->id("O"), net, PORT_OUT};
|
||||||
// Special case: input, etc, directly drives inout
|
// Special case: input, etc, directly drives inout
|
||||||
if (net->driver.cell != nullptr) {
|
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;
|
net = net->driver.cell->ports.at(ctx->id("I")).net;
|
||||||
}
|
}
|
||||||
assert(net->driver.cell == nullptr);
|
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());
|
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
|
// Import netnames
|
||||||
std::vector<IdString> netnames;
|
std::vector<std::string> netlabels;
|
||||||
if (node->data_dict.count("netnames")) {
|
if (node->data_dict.count("netnames")) {
|
||||||
JsonNode *cell_parent = node->data_dict.at("netnames");
|
JsonNode *cell_parent = node->data_dict.at("netnames");
|
||||||
for (int nnid = 0; nnid < GetSize(cell_parent->data_dict_keys); nnid++) {
|
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();
|
size_t num_bits = bits->data_array.size();
|
||||||
for (size_t i = 0; i < num_bits; i++) {
|
for (size_t i = 0; i < num_bits; i++) {
|
||||||
int netid = bits->data_array.at(i)->data_number;
|
int netid = bits->data_array.at(i)->data_number;
|
||||||
if (netid >= int(netnames.size()))
|
if (netid >= int(netlabels.size()))
|
||||||
netnames.resize(netid + 1);
|
netlabels.resize(netid + 1);
|
||||||
netnames.at(netid) = ctx->id(
|
std::string name =
|
||||||
basename + (num_bits == 1 ? "" : std::string("[") + std::to_string(i) + std::string("]")));
|
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")) {
|
if (node->data_dict.count("cells")) {
|
||||||
JsonNode *cell_parent = node->data_dict.at("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++) {
|
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]);
|
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;
|
JsonNode *here;
|
||||||
|
|
||||||
here = ports_parent->data_dict.at(ports_parent->data_dict_keys[portid]);
|
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);
|
check_all_nets_driven(ctx);
|
||||||
|
Loading…
Reference in New Issue
Block a user