2020-01-10 03:02:01 +08:00
|
|
|
/*
|
|
|
|
* nextpnr -- Next Generation Place and Route
|
|
|
|
*
|
|
|
|
* Copyright (C) 2020 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.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "log.h"
|
|
|
|
#include "nextpnr.h"
|
|
|
|
#include "util.h"
|
|
|
|
|
2020-10-23 02:25:17 +08:00
|
|
|
#include <queue>
|
|
|
|
|
2020-01-10 03:02:01 +08:00
|
|
|
NEXTPNR_NAMESPACE_BEGIN
|
|
|
|
namespace {
|
|
|
|
struct NexusFasmWriter
|
|
|
|
{
|
|
|
|
const Context *ctx;
|
|
|
|
std::ostream &out;
|
|
|
|
std::vector<std::string> fasm_ctx;
|
|
|
|
|
2020-10-13 15:49:02 +08:00
|
|
|
NexusFasmWriter(const Context *ctx, std::ostream &out) : ctx(ctx), out(out) {}
|
|
|
|
|
|
|
|
// Add a 'dot' prefix to the FASM context stack
|
2020-01-10 03:02:01 +08:00
|
|
|
void push(const std::string &x) { fasm_ctx.push_back(x); }
|
|
|
|
|
2020-10-13 15:49:02 +08:00
|
|
|
// Remove a prefix from the FASM context stack
|
2020-01-10 03:02:01 +08:00
|
|
|
void pop() { fasm_ctx.pop_back(); }
|
|
|
|
|
2020-10-13 15:49:02 +08:00
|
|
|
// Remove N prefices from the FASM context stack
|
2020-01-10 03:02:01 +08:00
|
|
|
void pop(int N)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < N; i++)
|
|
|
|
fasm_ctx.pop_back();
|
|
|
|
}
|
|
|
|
bool last_was_blank = true;
|
2020-10-13 15:49:02 +08:00
|
|
|
// Insert a blank line if the last wasn't blank
|
2020-01-10 03:02:01 +08:00
|
|
|
void blank()
|
|
|
|
{
|
|
|
|
if (!last_was_blank)
|
|
|
|
out << std::endl;
|
|
|
|
last_was_blank = true;
|
|
|
|
}
|
2020-10-13 15:49:02 +08:00
|
|
|
// Write out all prefices from the stack, interspersed with .
|
2020-01-10 03:02:01 +08:00
|
|
|
void write_prefix()
|
|
|
|
{
|
|
|
|
for (auto &x : fasm_ctx)
|
|
|
|
out << x << ".";
|
|
|
|
last_was_blank = false;
|
|
|
|
}
|
2020-10-13 15:49:02 +08:00
|
|
|
// Write a single config bit; if value is true
|
2020-01-10 03:02:01 +08:00
|
|
|
void write_bit(const std::string &name, bool value = true)
|
|
|
|
{
|
|
|
|
if (value) {
|
|
|
|
write_prefix();
|
|
|
|
out << name << std::endl;
|
|
|
|
}
|
|
|
|
}
|
2020-10-15 23:32:55 +08:00
|
|
|
// Write a FASM attribute
|
|
|
|
void write_attribute(const std::string &key, const std::string &value, bool str = true)
|
|
|
|
{
|
|
|
|
std::string qu = str ? "\"" : "";
|
|
|
|
out << "{ " << key << "=" << qu << value << qu << " }" << std::endl;
|
|
|
|
last_was_blank = false;
|
|
|
|
}
|
2020-10-13 15:49:02 +08:00
|
|
|
// Write a FASM comment
|
2020-01-10 03:02:01 +08:00
|
|
|
void write_comment(const std::string &cmt) { out << "# " << cmt << std::endl; }
|
2020-10-13 15:49:02 +08:00
|
|
|
// Write a FASM bitvector; optionally inverting the values in the process
|
2020-01-10 03:02:01 +08:00
|
|
|
void write_vector(const std::string &name, const std::vector<bool> &value, bool invert = false)
|
|
|
|
{
|
|
|
|
write_prefix();
|
|
|
|
out << name << " = " << int(value.size()) << "'b";
|
|
|
|
for (auto bit : boost::adaptors::reverse(value))
|
|
|
|
out << ((bit ^ invert) ? '1' : '0');
|
|
|
|
out << std::endl;
|
|
|
|
}
|
2020-10-13 15:49:02 +08:00
|
|
|
// Write a FASM bitvector given an integer value
|
2020-01-10 03:02:01 +08:00
|
|
|
void write_int_vector(const std::string &name, uint64_t value, int width, bool invert = false)
|
|
|
|
{
|
|
|
|
std::vector<bool> bits(width, false);
|
|
|
|
for (int i = 0; i < width; i++)
|
|
|
|
bits[i] = (value & (1ULL << i)) != 0;
|
|
|
|
write_vector(name, bits, invert);
|
|
|
|
}
|
2020-10-22 21:22:00 +08:00
|
|
|
// Write an int vector param
|
|
|
|
void write_int_vector_param(const CellInfo *cell, const std::string &name, uint64_t defval, int width,
|
|
|
|
bool invert = false)
|
|
|
|
{
|
|
|
|
uint64_t value = int_or_default(cell->params, ctx->id(name), defval);
|
|
|
|
std::vector<bool> bits(width, false);
|
|
|
|
for (int i = 0; i < width; i++)
|
|
|
|
bits[i] = (value & (1ULL << i)) != 0;
|
|
|
|
write_vector(stringf("%s[%d:0]", name.c_str(), width - 1), bits, invert);
|
|
|
|
}
|
2020-10-13 15:49:02 +08:00
|
|
|
// Look up an enum value in a cell's parameters and write it to the FASM in name.value format
|
2020-01-11 05:35:21 +08:00
|
|
|
void write_enum(const CellInfo *cell, const std::string &name, const std::string &defval = "")
|
|
|
|
{
|
|
|
|
auto fnd = cell->params.find(ctx->id(name));
|
|
|
|
if (fnd == cell->params.end()) {
|
|
|
|
if (!defval.empty())
|
|
|
|
write_bit(stringf("%s.%s", name.c_str(), defval.c_str()));
|
|
|
|
} else {
|
|
|
|
write_bit(stringf("%s.%s", name.c_str(), fnd->second.c_str()));
|
|
|
|
}
|
|
|
|
}
|
2020-10-13 15:49:02 +08:00
|
|
|
// Look up an IO attribute in the cell's attributes and write it to the FASM in name.value format
|
2020-10-12 20:40:24 +08:00
|
|
|
void write_ioattr(const CellInfo *cell, const std::string &name, const std::string &defval = "")
|
|
|
|
{
|
|
|
|
auto fnd = cell->attrs.find(ctx->id(name));
|
|
|
|
if (fnd == cell->attrs.end()) {
|
|
|
|
if (!defval.empty())
|
|
|
|
write_bit(stringf("%s.%s", name.c_str(), defval.c_str()));
|
|
|
|
} else {
|
|
|
|
write_bit(stringf("%s.%s", name.c_str(), fnd->second.c_str()));
|
|
|
|
}
|
|
|
|
}
|
2020-10-22 23:02:58 +08:00
|
|
|
void write_ioattr_postfix(const CellInfo *cell, const std::string &name, const std::string &postfix,
|
|
|
|
const std::string &defval = "")
|
|
|
|
{
|
|
|
|
auto fnd = cell->attrs.find(ctx->id(name));
|
|
|
|
if (fnd == cell->attrs.end()) {
|
|
|
|
if (!defval.empty())
|
|
|
|
write_bit(stringf("%s_%s.%s", name.c_str(), postfix.c_str(), defval.c_str()));
|
|
|
|
} else {
|
|
|
|
write_bit(stringf("%s_%s.%s", name.c_str(), postfix.c_str(), fnd->second.c_str()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-13 15:49:02 +08:00
|
|
|
// Gets the full name of a tile
|
2020-01-10 03:02:01 +08:00
|
|
|
std::string tile_name(int loc, const PhysicalTileInfoPOD &tile)
|
|
|
|
{
|
|
|
|
int r = loc / ctx->chip_info->width;
|
|
|
|
int c = loc % ctx->chip_info->width;
|
|
|
|
return stringf("%sR%dC%d__%s", ctx->nameOf(tile.prefix), r, c, ctx->nameOf(tile.tiletype));
|
|
|
|
}
|
2020-10-13 15:49:02 +08:00
|
|
|
// Look up a tile by location index and tile type
|
2020-01-10 03:02:01 +08:00
|
|
|
const PhysicalTileInfoPOD &tile_by_type_and_loc(int loc, IdString type)
|
|
|
|
{
|
|
|
|
auto &ploc = ctx->chip_info->grid[loc];
|
|
|
|
for (int i = 0; i < ploc.num_phys_tiles; i++) {
|
|
|
|
if (ploc.phys_tiles[i].tiletype == type.index)
|
|
|
|
return ploc.phys_tiles[i];
|
|
|
|
}
|
|
|
|
log_error("No tile of type %s found at location R%dC%d", ctx->nameOf(type), loc / ctx->chip_info->width,
|
|
|
|
loc % ctx->chip_info->width);
|
|
|
|
}
|
2020-10-13 15:49:02 +08:00
|
|
|
// Gets the single tile at a location
|
2020-01-11 18:20:21 +08:00
|
|
|
const PhysicalTileInfoPOD &tile_at_loc(int loc)
|
|
|
|
{
|
|
|
|
auto &ploc = ctx->chip_info->grid[loc];
|
|
|
|
NPNR_ASSERT(ploc.num_phys_tiles == 1);
|
|
|
|
return ploc.phys_tiles[0];
|
|
|
|
}
|
2020-10-13 15:49:02 +08:00
|
|
|
// Escape an internal prjoxide name for FASM by replacing : with __
|
2020-01-10 03:02:01 +08:00
|
|
|
std::string escape_name(const std::string &name)
|
|
|
|
{
|
|
|
|
std::string escaped;
|
|
|
|
for (char c : name) {
|
|
|
|
if (c == ':')
|
|
|
|
escaped += "__";
|
|
|
|
else
|
|
|
|
escaped += c;
|
|
|
|
}
|
|
|
|
return escaped;
|
|
|
|
}
|
2020-10-13 15:49:02 +08:00
|
|
|
// Push a tile name onto the prefix stack
|
2020-01-11 05:35:21 +08:00
|
|
|
void push_tile(int loc, IdString tile_type) { push(tile_name(loc, tile_by_type_and_loc(loc, tile_type))); }
|
2020-01-11 18:20:21 +08:00
|
|
|
void push_tile(int loc) { push(tile_name(loc, tile_at_loc(loc))); }
|
2020-10-13 15:49:02 +08:00
|
|
|
// Push a bel name onto the prefix stack
|
2020-01-11 18:20:21 +08:00
|
|
|
void push_belname(BelId bel) { push(ctx->nameOf(ctx->bel_data(bel).name)); }
|
2020-10-13 15:49:02 +08:00
|
|
|
// Push the tile group name corresponding to a bel onto the prefix stack
|
2020-10-12 22:57:33 +08:00
|
|
|
void push_belgroup(BelId bel)
|
|
|
|
{
|
|
|
|
int r = bel.tile / ctx->chip_info->width;
|
|
|
|
int c = bel.tile % ctx->chip_info->width;
|
|
|
|
auto bel_data = ctx->bel_data(bel);
|
|
|
|
r += bel_data.rel_y;
|
|
|
|
c += bel_data.rel_x;
|
|
|
|
std::string s = stringf("R%dC%d_%s", r, c, ctx->nameOf(ctx->bel_data(bel).name));
|
|
|
|
push(s);
|
|
|
|
}
|
2020-10-15 23:32:55 +08:00
|
|
|
// Push a bel's group and name
|
|
|
|
void push_bel(BelId bel)
|
|
|
|
{
|
|
|
|
push_belgroup(bel);
|
|
|
|
fasm_ctx.back() += stringf(".%s", ctx->nameOf(ctx->bel_data(bel).name));
|
|
|
|
}
|
2020-10-13 15:49:02 +08:00
|
|
|
// Write out a pip in tile.dst.src format
|
2020-01-10 03:02:01 +08:00
|
|
|
void write_pip(PipId pip)
|
|
|
|
{
|
|
|
|
auto &pd = ctx->pip_data(pip);
|
|
|
|
if (pd.flags & PIP_FIXED_CONN)
|
|
|
|
return;
|
|
|
|
std::string tile = tile_name(pip.tile, tile_by_type_and_loc(pip.tile, pd.tile_type));
|
|
|
|
std::string source_wire = escape_name(ctx->pip_src_wire_name(pip).str(ctx));
|
2020-01-12 03:49:37 +08:00
|
|
|
std::string dest_wire = escape_name(ctx->pip_dst_wire_name(pip).str(ctx));
|
2020-10-13 21:15:29 +08:00
|
|
|
out << stringf("%s.PIP.%s.%s", tile.c_str(), dest_wire.c_str(), source_wire.c_str()) << std::endl;
|
2020-01-10 03:02:01 +08:00
|
|
|
}
|
2020-10-13 15:49:02 +08:00
|
|
|
// Write out all the pips corresponding to a net
|
2020-01-10 03:02:01 +08:00
|
|
|
void write_net(const NetInfo *net)
|
|
|
|
{
|
|
|
|
write_comment(stringf("Net %s", ctx->nameOf(net)));
|
|
|
|
std::set<PipId> sorted_pips;
|
|
|
|
for (auto &w : net->wires)
|
2020-01-12 03:49:37 +08:00
|
|
|
if (w.second.pip != PipId())
|
|
|
|
sorted_pips.insert(w.second.pip);
|
2020-01-10 03:02:01 +08:00
|
|
|
for (auto p : sorted_pips)
|
|
|
|
write_pip(p);
|
|
|
|
blank();
|
|
|
|
}
|
2020-10-13 21:15:29 +08:00
|
|
|
// Find the CIBMUX output for a signal
|
|
|
|
WireId find_cibmux(const CellInfo *cell, IdString pin)
|
|
|
|
{
|
|
|
|
WireId cursor = ctx->getBelPinWire(cell->bel, pin);
|
|
|
|
if (cursor == WireId())
|
|
|
|
return WireId();
|
|
|
|
for (int i = 0; i < 10; i++) {
|
|
|
|
std::string cursor_name = IdString(ctx->wire_data(cursor).name).str(ctx);
|
|
|
|
if (cursor_name.find("JCIBMUXOUT") == 0) {
|
|
|
|
return cursor;
|
|
|
|
}
|
|
|
|
for (PipId pip : ctx->getPipsUphill(cursor))
|
|
|
|
if (ctx->checkPipAvail(pip)) {
|
|
|
|
cursor = ctx->getPipSrcWire(pip);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return WireId();
|
|
|
|
}
|
2020-10-13 17:07:28 +08:00
|
|
|
// Write out the mux config for a cell
|
|
|
|
void write_cell_muxes(const CellInfo *cell)
|
|
|
|
{
|
|
|
|
for (auto port : sorted_cref(cell->ports)) {
|
|
|
|
// Only relevant to inputs
|
|
|
|
if (port.second.type != PORT_IN)
|
|
|
|
continue;
|
|
|
|
auto pin_style = ctx->get_cell_pin_style(cell, port.first);
|
|
|
|
auto pin_mux = ctx->get_cell_pinmux(cell, port.first);
|
|
|
|
// Invertible pins
|
|
|
|
if (pin_style & PINOPT_INV) {
|
|
|
|
if (pin_mux == PINMUX_INV || pin_mux == PINMUX_0)
|
|
|
|
write_bit(stringf("%sMUX.INV", ctx->nameOf(port.first)));
|
|
|
|
else if (pin_mux == PINMUX_SIG)
|
|
|
|
write_bit(stringf("%sMUX.%s", ctx->nameOf(port.first), ctx->nameOf(port.first)));
|
|
|
|
}
|
|
|
|
// Pins that must be explictly enabled
|
|
|
|
if ((pin_style & PINBIT_GATED) && (pin_mux == PINMUX_SIG))
|
|
|
|
write_bit(stringf("%sMUX.%s", ctx->nameOf(port.first), ctx->nameOf(port.first)));
|
|
|
|
// Pins that must be explictly set to 1 rather than just left floating
|
|
|
|
if ((pin_style & PINBIT_1) && (pin_mux == PINMUX_1))
|
|
|
|
write_bit(stringf("%sMUX.1", ctx->nameOf(port.first)));
|
2020-10-13 21:15:29 +08:00
|
|
|
// Handle CIB muxes - these must be set such that floating pins really are floating to VCC and not connected
|
|
|
|
// to another CIB signal
|
|
|
|
if ((pin_style & PINBIT_CIBMUX) && port.second.net == nullptr) {
|
|
|
|
WireId cibmuxout = find_cibmux(cell, port.first);
|
|
|
|
if (cibmuxout != WireId()) {
|
|
|
|
write_comment(stringf("CIBMUX for unused pin %s", ctx->nameOf(port.first)));
|
|
|
|
bool found = false;
|
|
|
|
for (PipId pip : ctx->getPipsUphill(cibmuxout)) {
|
|
|
|
if (ctx->checkPipAvail(pip) && ctx->checkWireAvail(ctx->getPipSrcWire(pip))) {
|
|
|
|
write_pip(pip);
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
NPNR_ASSERT(found);
|
|
|
|
}
|
|
|
|
}
|
2020-10-13 17:07:28 +08:00
|
|
|
}
|
|
|
|
}
|
2020-10-13 21:15:29 +08:00
|
|
|
|
2020-10-13 15:49:02 +08:00
|
|
|
// Write config for an OXIDE_COMB cell
|
2020-01-11 05:35:21 +08:00
|
|
|
void write_comb(const CellInfo *cell)
|
|
|
|
{
|
|
|
|
BelId bel = cell->bel;
|
|
|
|
int z = ctx->bel_data(bel).z;
|
|
|
|
int k = z & 0x1;
|
2020-01-12 04:02:31 +08:00
|
|
|
char slice = 'A' + (z >> 3);
|
2020-01-11 05:35:21 +08:00
|
|
|
push_tile(bel.tile, id_PLC);
|
|
|
|
push(stringf("SLICE%c", slice));
|
|
|
|
if (cell->params.count(id_INIT))
|
2020-01-12 03:49:37 +08:00
|
|
|
write_int_vector(stringf("K%d.INIT[15:0]", k), int_or_default(cell->params, id_INIT, 0), 16);
|
2020-01-11 05:35:21 +08:00
|
|
|
if (cell->lutInfo.is_carry) {
|
|
|
|
write_bit("MODE.CCU2");
|
2020-10-23 02:25:17 +08:00
|
|
|
write_enum(cell, "CCU2.INJECT", "NO");
|
2020-01-11 05:35:21 +08:00
|
|
|
}
|
|
|
|
pop(2);
|
|
|
|
}
|
2020-10-13 15:49:02 +08:00
|
|
|
// Write config for an OXIDE_FF cell
|
2020-01-11 05:35:21 +08:00
|
|
|
void write_ff(const CellInfo *cell)
|
|
|
|
{
|
|
|
|
BelId bel = cell->bel;
|
|
|
|
int z = ctx->bel_data(bel).z;
|
|
|
|
int k = z & 0x1;
|
2020-01-12 04:02:31 +08:00
|
|
|
char slice = 'A' + (z >> 3);
|
2020-01-11 05:35:21 +08:00
|
|
|
push_tile(bel.tile, id_PLC);
|
|
|
|
push(stringf("SLICE%c", slice));
|
2020-01-12 04:12:50 +08:00
|
|
|
push(stringf("REG%d", k));
|
2020-01-11 05:35:21 +08:00
|
|
|
write_bit("USED.YES");
|
|
|
|
write_enum(cell, "REGSET", "RESET");
|
|
|
|
write_enum(cell, "LSRMODE", "LSR");
|
|
|
|
write_enum(cell, "SEL", "DF");
|
|
|
|
pop();
|
|
|
|
write_enum(cell, "REGDDR");
|
|
|
|
write_enum(cell, "SRMODE");
|
2020-10-13 21:15:29 +08:00
|
|
|
write_cell_muxes(cell);
|
2020-01-11 05:35:21 +08:00
|
|
|
pop(2);
|
|
|
|
}
|
2020-10-15 23:32:55 +08:00
|
|
|
|
2020-10-19 20:31:21 +08:00
|
|
|
// Write out config for an OXIDE_RAMW cell
|
|
|
|
void write_ramw(const CellInfo *cell)
|
|
|
|
{
|
|
|
|
BelId bel = cell->bel;
|
|
|
|
push_tile(bel.tile, id_PLC);
|
|
|
|
push("SLICEC");
|
|
|
|
write_bit("MODE.RAMW");
|
|
|
|
write_cell_muxes(cell);
|
|
|
|
pop(2);
|
|
|
|
}
|
|
|
|
|
2020-10-15 23:32:55 +08:00
|
|
|
std::unordered_set<BelId> used_io;
|
|
|
|
|
2020-10-22 23:02:58 +08:00
|
|
|
struct BankConfig
|
|
|
|
{
|
|
|
|
bool diff_used = false;
|
|
|
|
bool lvds_used = false;
|
|
|
|
bool slvs_used = false;
|
|
|
|
bool dphy_used = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
std::map<int, BankConfig> bank_cfg;
|
|
|
|
|
2020-10-13 15:49:02 +08:00
|
|
|
// Write config for an SEIO33_CORE cell
|
2020-01-11 18:20:21 +08:00
|
|
|
void write_io33(const CellInfo *cell)
|
|
|
|
{
|
|
|
|
BelId bel = cell->bel;
|
2020-10-15 23:32:55 +08:00
|
|
|
used_io.insert(bel);
|
|
|
|
push_bel(bel);
|
2020-01-11 18:20:21 +08:00
|
|
|
const NetInfo *t = get_net_or_empty(cell, id_T);
|
2020-10-12 20:40:24 +08:00
|
|
|
auto tmux = ctx->get_cell_pinmux(cell, id_T);
|
2020-01-11 18:20:21 +08:00
|
|
|
bool is_input = false, is_output = false;
|
2020-10-12 20:40:24 +08:00
|
|
|
if (tmux == PINMUX_0) {
|
2020-01-11 18:20:21 +08:00
|
|
|
is_output = true;
|
2020-10-12 20:40:24 +08:00
|
|
|
} else if (tmux == PINMUX_1 || t == nullptr) {
|
|
|
|
is_input = true;
|
2020-01-11 18:20:21 +08:00
|
|
|
}
|
|
|
|
const char *iodir = is_input ? "INPUT" : (is_output ? "OUTPUT" : "BIDIR");
|
|
|
|
write_bit(stringf("BASE_TYPE.%s_%s", iodir, str_or_default(cell->attrs, id_IO_TYPE, "LVCMOS33").c_str()));
|
2020-10-12 20:40:24 +08:00
|
|
|
write_ioattr(cell, "PULLMODE", "NONE");
|
2020-10-13 21:15:29 +08:00
|
|
|
write_cell_muxes(cell);
|
2020-10-15 23:32:55 +08:00
|
|
|
pop();
|
2020-01-11 18:20:21 +08:00
|
|
|
}
|
2020-10-13 15:49:02 +08:00
|
|
|
// Write config for an SEIO18_CORE cell
|
2020-01-13 20:17:34 +08:00
|
|
|
void write_io18(const CellInfo *cell)
|
|
|
|
{
|
|
|
|
BelId bel = cell->bel;
|
2020-10-15 23:32:55 +08:00
|
|
|
used_io.insert(bel);
|
|
|
|
push_bel(bel);
|
2020-01-13 20:17:34 +08:00
|
|
|
push("SEIO18");
|
|
|
|
const NetInfo *t = get_net_or_empty(cell, id_T);
|
2020-10-12 20:40:24 +08:00
|
|
|
auto tmux = ctx->get_cell_pinmux(cell, id_T);
|
2020-01-13 20:17:34 +08:00
|
|
|
bool is_input = false, is_output = false;
|
2020-10-12 20:40:24 +08:00
|
|
|
if (tmux == PINMUX_0) {
|
2020-01-13 20:17:34 +08:00
|
|
|
is_output = true;
|
2020-10-12 20:40:24 +08:00
|
|
|
} else if (tmux == PINMUX_1 || t == nullptr) {
|
|
|
|
is_input = true;
|
2020-01-13 20:17:34 +08:00
|
|
|
}
|
|
|
|
const char *iodir = is_input ? "INPUT" : (is_output ? "OUTPUT" : "BIDIR");
|
|
|
|
write_bit(stringf("BASE_TYPE.%s_%s", iodir, str_or_default(cell->attrs, id_IO_TYPE, "LVCMOS18H").c_str()));
|
2020-10-12 20:40:24 +08:00
|
|
|
write_ioattr(cell, "PULLMODE", "NONE");
|
2020-10-22 23:02:58 +08:00
|
|
|
pop();
|
|
|
|
write_cell_muxes(cell);
|
|
|
|
pop();
|
|
|
|
}
|
|
|
|
// Write config for an SEIO18_CORE cell
|
|
|
|
void write_diffio18(const CellInfo *cell)
|
|
|
|
{
|
|
|
|
BelId bel = cell->bel;
|
|
|
|
|
|
|
|
Loc bel_loc = ctx->getBelLocation(bel);
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
|
|
// Mark both A and B pins as used
|
|
|
|
used_io.insert(ctx->getBelByLocation(Loc(bel_loc.x, bel_loc.y, i)));
|
|
|
|
}
|
|
|
|
push_belgroup(bel);
|
|
|
|
push("PIOA");
|
|
|
|
push("DIFFIO18");
|
|
|
|
|
|
|
|
auto &bank = bank_cfg[ctx->get_bel_pad(ctx->getBelByLocation(Loc(bel_loc.x, bel_loc.y, 0)))->bank];
|
|
|
|
|
|
|
|
bank.diff_used = true;
|
|
|
|
|
|
|
|
const NetInfo *t = get_net_or_empty(cell, id_T);
|
|
|
|
auto tmux = ctx->get_cell_pinmux(cell, id_T);
|
|
|
|
bool is_input = false, is_output = false;
|
|
|
|
if (tmux == PINMUX_0) {
|
|
|
|
is_output = true;
|
|
|
|
} else if (tmux == PINMUX_1 || t == nullptr) {
|
|
|
|
is_input = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *iodir = is_input ? "INPUT" : (is_output ? "OUTPUT" : "BIDIR");
|
|
|
|
std::string type = str_or_default(cell->attrs, id_IO_TYPE, "LVDS");
|
|
|
|
write_bit(stringf("BASE_TYPE.%s_%s", iodir, type.c_str()));
|
|
|
|
if (type == "LVDS") {
|
|
|
|
write_ioattr_postfix(cell, "DIFFDRIVE", "LVDS", "3P5");
|
|
|
|
bank.lvds_used = true;
|
|
|
|
} else if (type == "SLVS") {
|
|
|
|
write_ioattr_postfix(cell, "DIFFDRIVE", "SLVS", "2P0");
|
|
|
|
bank.slvs_used = true;
|
|
|
|
} else if (type == "MIPI_DPHY") {
|
|
|
|
write_ioattr_postfix(cell, "DIFFDRIVE", "MIPI_DPHY", "2P0");
|
|
|
|
bank.dphy_used = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
write_ioattr(cell, "PULLMODE", "FAILSAFE");
|
|
|
|
write_ioattr(cell, "DIFFRESISTOR");
|
|
|
|
pop();
|
2020-10-13 21:15:29 +08:00
|
|
|
write_cell_muxes(cell);
|
2020-10-15 23:32:55 +08:00
|
|
|
pop(2);
|
2020-01-13 20:17:34 +08:00
|
|
|
}
|
2020-10-13 15:49:02 +08:00
|
|
|
// Write config for an OSC_CORE cell
|
2020-01-14 00:01:08 +08:00
|
|
|
void write_osc(const CellInfo *cell)
|
|
|
|
{
|
|
|
|
BelId bel = cell->bel;
|
|
|
|
push_tile(bel.tile);
|
|
|
|
push_belname(bel);
|
|
|
|
write_enum(cell, "HF_OSC_EN");
|
|
|
|
write_enum(cell, "HF_FABRIC_EN");
|
|
|
|
write_enum(cell, "HFDIV_FABRIC_EN", "ENABLED");
|
|
|
|
write_enum(cell, "LF_FABRIC_EN");
|
|
|
|
write_enum(cell, "LF_OUTPUT_EN");
|
2020-10-03 22:13:53 +08:00
|
|
|
write_enum(cell, "DEBUG_N", "DISABLED");
|
2020-01-22 23:53:45 +08:00
|
|
|
write_int_vector(stringf("HF_CLK_DIV[7:0]"), ctx->parse_lattice_param(cell, id_HF_CLK_DIV, 8, 0).intval, 8);
|
2020-10-13 21:15:29 +08:00
|
|
|
write_cell_muxes(cell);
|
2020-01-14 00:01:08 +08:00
|
|
|
pop(2);
|
|
|
|
}
|
2020-10-22 21:22:00 +08:00
|
|
|
// Write config for an OXIDE_EBR cell
|
|
|
|
void write_bram(const CellInfo *cell)
|
|
|
|
{
|
|
|
|
// EBR configuration
|
|
|
|
BelId bel = cell->bel;
|
|
|
|
push_bel(bel);
|
|
|
|
int wid = int_or_default(cell->params, id_WID, 0);
|
|
|
|
std::string mode = str_or_default(cell->params, id_MODE, "");
|
|
|
|
|
|
|
|
write_bit(stringf("MODE.%s_MODE", mode.c_str()));
|
2020-10-22 22:11:12 +08:00
|
|
|
write_enum(cell, "INIT_DATA", "STATIC");
|
2020-10-22 21:22:00 +08:00
|
|
|
write_enum(cell, "GSR", "DISABLED");
|
|
|
|
|
|
|
|
write_int_vector("WID[10:0]", wid, 11);
|
|
|
|
|
|
|
|
push(stringf("%s_MODE", mode.c_str()));
|
|
|
|
|
|
|
|
if (mode == "DP16K") {
|
2020-10-22 22:11:12 +08:00
|
|
|
write_int_vector_param(cell, "CSDECODE_A", 7, 3, true);
|
|
|
|
write_int_vector_param(cell, "CSDECODE_B", 7, 3, true);
|
2020-10-22 21:22:00 +08:00
|
|
|
write_enum(cell, "ASYNC_RST_RELEASE_A");
|
|
|
|
write_enum(cell, "ASYNC_RST_RELEASE_B");
|
|
|
|
write_enum(cell, "DATA_WIDTH_A");
|
|
|
|
write_enum(cell, "DATA_WIDTH_B");
|
|
|
|
write_enum(cell, "OUTREG_A");
|
|
|
|
write_enum(cell, "OUTREG_B");
|
|
|
|
write_enum(cell, "RESETMODE_A");
|
|
|
|
write_enum(cell, "RESETMODE_B");
|
|
|
|
} else if (mode == "PDP16K" || mode == "PDPSC16K") {
|
2020-10-22 22:11:12 +08:00
|
|
|
write_int_vector_param(cell, "CSDECODE_W", 7, 3, true);
|
|
|
|
write_int_vector_param(cell, "CSDECODE_R", 7, 3, true);
|
2020-10-22 21:22:00 +08:00
|
|
|
write_enum(cell, "ASYNC_RST_RELEASE");
|
|
|
|
write_enum(cell, "DATA_WIDTH_W");
|
|
|
|
write_enum(cell, "DATA_WIDTH_R");
|
|
|
|
write_enum(cell, "OUTREG");
|
|
|
|
write_enum(cell, "RESETMODE");
|
|
|
|
}
|
|
|
|
|
|
|
|
pop();
|
|
|
|
push("DP16K_MODE"); // muxes always use the DP16K perspective
|
|
|
|
write_cell_muxes(cell);
|
|
|
|
pop(2);
|
|
|
|
blank();
|
|
|
|
|
|
|
|
// EBR initialisation
|
|
|
|
if (wid > 0) {
|
|
|
|
push(stringf("IP_EBR_WID%d", wid));
|
|
|
|
for (int i = 0; i < 64; i++) {
|
|
|
|
IdString param = ctx->id(stringf("INITVAL_%02X", i));
|
|
|
|
if (!cell->params.count(param))
|
|
|
|
continue;
|
|
|
|
auto &prop = cell->params.at(param);
|
|
|
|
std::string value;
|
|
|
|
if (prop.is_string) {
|
|
|
|
NPNR_ASSERT(prop.str.substr(0, 2) == "0x");
|
|
|
|
// Lattice-style hex string
|
|
|
|
value = prop.str.substr(2);
|
|
|
|
value = stringf("320'h%s", value.c_str());
|
|
|
|
} else {
|
|
|
|
// True Verilog bitvector
|
|
|
|
value = stringf("320'b%s", prop.str.c_str());
|
|
|
|
}
|
|
|
|
write_bit(stringf("INITVAL_%02X[319:0] = %s", i, value.c_str()));
|
|
|
|
}
|
|
|
|
pop();
|
|
|
|
}
|
|
|
|
}
|
2020-11-13 23:25:57 +08:00
|
|
|
|
|
|
|
bool is_mux_param(const std::string &key)
|
|
|
|
{
|
|
|
|
return (key.size() >= 3 && (key.compare(key.size() - 3, 3, "MUX") == 0));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write config for some kind of DSP cell
|
|
|
|
void write_dsp(const CellInfo *cell)
|
|
|
|
{
|
|
|
|
BelId bel = cell->bel;
|
|
|
|
push_bel(bel);
|
2020-11-17 23:13:00 +08:00
|
|
|
if (cell->type != id_MULT18_CORE && cell->type != id_MULT18X36_CORE && cell->type != id_MULT36_CORE)
|
|
|
|
write_bit(stringf("MODE.%s", ctx->nameOf(cell->type)));
|
2020-11-13 23:25:57 +08:00
|
|
|
for (auto param : sorted_cref(cell->params)) {
|
|
|
|
const std::string ¶m_name = param.first.str(ctx);
|
|
|
|
if (is_mux_param(param_name))
|
|
|
|
continue;
|
2020-11-17 23:13:00 +08:00
|
|
|
if (param.first == id_ROUNDBIT) {
|
|
|
|
// currently unsupported in oxide, but appears rarely used
|
|
|
|
NPNR_ASSERT(param.second.as_string() == "ROUND_TO_BIT0");
|
|
|
|
continue;
|
|
|
|
}
|
2020-11-13 23:25:57 +08:00
|
|
|
write_enum(cell, param_name);
|
|
|
|
}
|
|
|
|
write_cell_muxes(cell);
|
|
|
|
pop();
|
|
|
|
}
|
2020-12-02 20:29:08 +08:00
|
|
|
|
|
|
|
// Which PLL params are 'word' values
|
|
|
|
/* clang-format off */
|
|
|
|
const std::unordered_map<std::string, int> pll_word_params = {
|
|
|
|
{"DIVA", 7}, {"DELA", 7}, {"PHIA", 3}, {"DIVB", 7},
|
|
|
|
{"DELB", 7}, {"PHIB", 3}, {"DIVC", 7}, {"DELC", 7},
|
|
|
|
{"PHIC", 3}, {"DIVD", 7}, {"DELD", 7}, {"PHID", 3},
|
|
|
|
{"DIVE", 7}, {"DELE", 7}, {"PHIE", 3}, {"DIVF", 7},
|
|
|
|
{"DELF", 7}, {"PHIF", 3}, {"BW_CTL_BIAS", 4},
|
|
|
|
{"CLKOP_TRIM", 4}, {"CLKOS_TRIM", 4}, {"CLKOS2_TRIM", 4},
|
|
|
|
{"CLKOS3_TRIM", 4}, {"CLKOS4_TRIM", 4}, {"CLKOS5_TRIM", 4},
|
|
|
|
{"DIV_DEL", 7}, {"DYN_SEL", 3}, {"FBK_CUR_BLE", 8}, {"FBK_IF_TIMING_CTL", 2},
|
|
|
|
{"FBK_MASK", 8}, {"FBK_MMD_DIG", 8}, {"FBK_MMD_PULS_CTL", 4},
|
|
|
|
{"FBK_MODE", 2}, {"FBK_PI_RC", 4}, {"FBK_PR_CC", 4},
|
|
|
|
{"FBK_PR_IC", 4}, {"FBK_RSV", 16},
|
|
|
|
{"IPI_CMP", 4}, {"IPI_CMPN", 4},
|
|
|
|
{"IPP_CTRL", 4}, {"IPP_SEL", 4},
|
|
|
|
{"KP_VCO", 5},
|
|
|
|
{"MFG_CTRL", 4}, {"MFGOUT1_SEL", 3}, {"MFGOUT2_SEL", 3},
|
|
|
|
{"REF_MASK", 8}, {"REF_MMD_DIG", 8}, {"REF_MMD_IN", 8},
|
|
|
|
{"REF_MMD_PULS_CTL", 4}, {"REF_TIMING_CTL", 2},
|
|
|
|
{"RESERVED", 7}, {"SSC_DELTA", 15},
|
|
|
|
{"SSC_DELTA_CTL", 2}, {"SSC_F_CODE", 15},
|
|
|
|
{"SSC_N_CODE", 9}, {"SSC_REG_WEIGHTING_SEL", 3},
|
|
|
|
{"SSC_STEP_IN", 7}, {"SSC_TBASE", 12},
|
|
|
|
{"V2I_PP_ICTRL", 5},
|
|
|
|
};
|
|
|
|
/* clang-format on */
|
|
|
|
|
|
|
|
// Write out config for some kind of PLL cell
|
|
|
|
void write_pll(const CellInfo *cell)
|
|
|
|
{
|
|
|
|
BelId bel = cell->bel;
|
|
|
|
push_bel(bel);
|
|
|
|
write_bit("MODE.PLL_CORE");
|
|
|
|
write_enum(cell, "CLKMUX_FB");
|
|
|
|
write_cell_muxes(cell);
|
|
|
|
pop();
|
|
|
|
push(stringf("IP_%s", ctx->nameOf(ctx->bel_data(bel).name)));
|
|
|
|
for (auto param : sorted_cref(cell->params)) {
|
|
|
|
const std::string &name = param.first.str(ctx);
|
|
|
|
if (is_mux_param(name) || name == "CLKMUX_FB" || name == "SEL_FBK")
|
|
|
|
continue;
|
|
|
|
auto fnd_word = pll_word_params.find(name);
|
|
|
|
if (fnd_word != pll_word_params.end()) {
|
|
|
|
write_int_vector(stringf("%s[%d:0]", name.c_str(), fnd_word->second - 1),
|
|
|
|
ctx->parse_lattice_param(cell, param.first, fnd_word->second, 0).as_int64(),
|
|
|
|
fnd_word->second);
|
|
|
|
} else {
|
|
|
|
write_bit(stringf("%s.%s", name.c_str(), param.second.as_string().c_str()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pop();
|
|
|
|
}
|
2020-10-15 23:32:55 +08:00
|
|
|
// Write out FASM for unused bels where needed
|
|
|
|
void write_unused()
|
|
|
|
{
|
|
|
|
write_comment("# Unused bels");
|
|
|
|
|
|
|
|
// DSP primitives are configured to a default mode; even if unused
|
|
|
|
static const std::unordered_map<IdString, std::vector<std::string>> dsp_defconf = {
|
|
|
|
{id_MULT9_CORE,
|
|
|
|
{
|
|
|
|
"GSR.ENABLED",
|
|
|
|
"MODE.NONE",
|
|
|
|
"RSTAMUX.RSTA",
|
|
|
|
"RSTPMUX.RSTP",
|
|
|
|
}},
|
|
|
|
{id_PREADD9_CORE,
|
|
|
|
{
|
|
|
|
"GSR.ENABLED",
|
|
|
|
"MODE.NONE",
|
|
|
|
"RSTBMUX.RSTB",
|
|
|
|
"RSTCLMUX.RSTCL",
|
|
|
|
}},
|
|
|
|
{id_REG18_CORE,
|
|
|
|
{
|
|
|
|
"GSR.ENABLED",
|
|
|
|
"MODE.NONE",
|
|
|
|
"RSTPMUX.RSTP",
|
|
|
|
}},
|
|
|
|
{id_ACC54_CORE,
|
|
|
|
{
|
|
|
|
"ACCUBYPS.BYPASS",
|
|
|
|
"MODE.NONE",
|
|
|
|
}},
|
|
|
|
};
|
|
|
|
|
|
|
|
for (BelId bel : ctx->getBels()) {
|
|
|
|
IdString type = ctx->getBelType(bel);
|
|
|
|
if (type == id_SEIO33_CORE && !used_io.count(bel)) {
|
|
|
|
push_bel(bel);
|
|
|
|
write_bit("BASE_TYPE.NONE");
|
|
|
|
pop();
|
|
|
|
blank();
|
|
|
|
} else if (type == id_SEIO18_CORE && !used_io.count(bel)) {
|
|
|
|
push_bel(bel);
|
|
|
|
push("SEIO18");
|
|
|
|
write_bit("BASE_TYPE.NONE");
|
|
|
|
pop(2);
|
|
|
|
blank();
|
|
|
|
} else if (dsp_defconf.count(type) && ctx->getBoundBelCell(bel) == nullptr) {
|
|
|
|
push_bel(bel);
|
|
|
|
for (const auto &cbit : dsp_defconf.at(type))
|
|
|
|
write_bit(cbit);
|
|
|
|
pop();
|
|
|
|
blank();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Write out placeholder bankref config
|
|
|
|
void write_bankcfg()
|
|
|
|
{
|
|
|
|
for (int i = 0; i < 8; i++) {
|
2020-10-22 23:02:58 +08:00
|
|
|
if (i >= 3 && i <= 5) {
|
|
|
|
// 1.8V banks
|
|
|
|
push(stringf("GLOBAL.BANK%d", i));
|
|
|
|
auto &bank = bank_cfg[i];
|
|
|
|
write_bit("DIFF_IO.ON", bank.diff_used);
|
|
|
|
write_bit("LVDS_IO.ON", bank.lvds_used);
|
|
|
|
write_bit("SLVS_IO.ON", bank.slvs_used);
|
|
|
|
write_bit("MIPI_DPHY_IO.ON", bank.dphy_used);
|
|
|
|
|
|
|
|
pop();
|
|
|
|
} else {
|
|
|
|
// 3.3V banks, this should eventually be set based on the bank config
|
|
|
|
write_bit(stringf("GLOBAL.BANK%d.VCC.3V3", i));
|
|
|
|
}
|
2020-10-15 23:32:55 +08:00
|
|
|
}
|
|
|
|
blank();
|
|
|
|
}
|
2020-10-13 15:49:02 +08:00
|
|
|
// Write out FASM for the whole design
|
2020-01-10 03:02:01 +08:00
|
|
|
void operator()()
|
|
|
|
{
|
2020-10-15 23:32:55 +08:00
|
|
|
// Write device config
|
|
|
|
write_attribute("oxide.device", ctx->device);
|
|
|
|
write_attribute("oxide.device_variant", ctx->variant);
|
|
|
|
blank();
|
2020-01-10 03:02:01 +08:00
|
|
|
// Write routing
|
|
|
|
for (auto n : sorted(ctx->nets)) {
|
|
|
|
write_net(n.second);
|
|
|
|
}
|
2020-01-11 05:35:21 +08:00
|
|
|
// Write cell config
|
|
|
|
for (auto c : sorted(ctx->cells)) {
|
|
|
|
const CellInfo *ci = c.second;
|
|
|
|
write_comment(stringf("# Cell %s", ctx->nameOf(ci)));
|
|
|
|
if (ci->type == id_OXIDE_COMB)
|
|
|
|
write_comb(ci);
|
|
|
|
else if (ci->type == id_OXIDE_FF)
|
|
|
|
write_ff(ci);
|
2020-10-19 20:31:21 +08:00
|
|
|
else if (ci->type == id_RAMW)
|
|
|
|
write_ramw(ci);
|
2020-01-11 18:20:21 +08:00
|
|
|
else if (ci->type == id_SEIO33_CORE)
|
|
|
|
write_io33(ci);
|
2020-01-13 20:17:34 +08:00
|
|
|
else if (ci->type == id_SEIO18_CORE)
|
|
|
|
write_io18(ci);
|
2020-10-22 23:02:58 +08:00
|
|
|
else if (ci->type == id_DIFFIO18_CORE)
|
|
|
|
write_diffio18(ci);
|
2020-01-14 00:01:08 +08:00
|
|
|
else if (ci->type == id_OSC_CORE)
|
|
|
|
write_osc(ci);
|
2020-10-22 21:22:00 +08:00
|
|
|
else if (ci->type == id_OXIDE_EBR)
|
|
|
|
write_bram(ci);
|
2020-11-13 23:25:57 +08:00
|
|
|
else if (ci->type == id_MULT9_CORE || ci->type == id_PREADD9_CORE || ci->type == id_MULT18_CORE ||
|
|
|
|
ci->type == id_MULT18X36_CORE || ci->type == id_MULT36_CORE || ci->type == id_REG18_CORE ||
|
|
|
|
ci->type == id_ACC54_CORE)
|
|
|
|
write_dsp(ci);
|
2020-12-02 20:29:08 +08:00
|
|
|
else if (ci->type == id_PLL_CORE)
|
|
|
|
write_pll(ci);
|
2020-01-11 05:35:21 +08:00
|
|
|
blank();
|
|
|
|
}
|
2020-10-15 23:32:55 +08:00
|
|
|
// Write config for unused bels
|
|
|
|
write_unused();
|
|
|
|
// Write bank config
|
|
|
|
write_bankcfg();
|
2020-01-10 03:02:01 +08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
void Arch::write_fasm(std::ostream &out) const { NexusFasmWriter(getCtx(), out)(); }
|
|
|
|
|
|
|
|
NEXTPNR_NAMESPACE_END
|