/* * nextpnr -- Next Generation Place and Route * * Copyright (C) 2020 gatecat * * * 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" #include #include #include NEXTPNR_NAMESPACE_BEGIN namespace { struct NexusFasmWriter { const Context *ctx; std::ostream &out; std::vector fasm_ctx; bool is_lifcl_17; NexusFasmWriter(const Context *ctx, std::ostream &out) : ctx(ctx), out(out), is_lifcl_17(ctx->args.device.find("LIFCL-17") != std::string::npos) { } // Add a 'dot' prefix to the FASM context stack void push(const std::string &x) { fasm_ctx.push_back(x); } // Remove a prefix from the FASM context stack void pop() { fasm_ctx.pop_back(); } // Remove N prefices from the FASM context stack void pop(int N) { for (int i = 0; i < N; i++) fasm_ctx.pop_back(); } bool last_was_blank = true; // Insert a blank line if the last wasn't blank void blank() { if (!last_was_blank) out << std::endl; last_was_blank = true; } // Write out all prefices from the stack, interspersed with . void write_prefix() { for (auto &x : fasm_ctx) out << x << "."; last_was_blank = false; } // Write a single config bit; if value is true void write_bit(const std::string &name, bool value = true) { if (value) { write_prefix(); out << name << std::endl; } } // 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; } // Write a FASM comment void write_comment(const std::string &cmt) { out << "# " << cmt << std::endl; } // Write a FASM bitvector; optionally inverting the values in the process void write_vector(const std::string &name, const std::vector &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; } // Write a FASM bitvector given an integer value void write_int_vector(const std::string &name, uint64_t value, int width, bool invert = false) { std::vector bits(width, false); for (int i = 0; i < width; i++) bits[i] = (value & (1ULL << i)) != 0; write_vector(name, bits, invert); } // 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 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); } // Look up an enum value in a cell's parameters and write it to the FASM in name.value format 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())); } } // Look up an IO attribute in the cell's attributes and write it to the FASM in name.value format 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())); } } 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())); } } // Gets the full name of a tile 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(IdString(tile.prefix)), r, c, ctx->nameOf(IdString(tile.tiletype))); } // Look up a tile by location index and tile type const PhysicalTileInfoPOD &tile_by_type_and_loc(int loc, IdString type) { auto &ploc = ctx->chip_info->grid[loc]; for (auto &pt : ploc.phys_tiles) { if (pt.tiletype == type.index) return pt; } 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); } // Gets the single tile at a location const PhysicalTileInfoPOD &tile_at_loc(int loc) { auto &ploc = ctx->chip_info->grid[loc]; NPNR_ASSERT(ploc.phys_tiles.size() == 1); return ploc.phys_tiles[0]; } // Escape an internal prjoxide name for FASM by replacing : with __ std::string escape_name(const std::string &name) { std::string escaped; for (char c : name) { if (c == ':') escaped += "__"; else escaped += c; } return escaped; } // Push a tile name onto the prefix stack void push_tile(int loc, IdString tile_type) { push(tile_name(loc, tile_by_type_and_loc(loc, tile_type))); } void push_tile(int loc) { push(tile_name(loc, tile_at_loc(loc))); } // Push a bel name onto the prefix stack void push_belname(BelId bel) { push(ctx->nameOf(IdString(ctx->bel_data(bel).name))); } // Push the tile group name corresponding to a bel onto the prefix stack 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(IdString(ctx->bel_data(bel).name))); push(s); } // Push a bel's group and name void push_bel(BelId bel) { push_belgroup(bel); fasm_ctx.back() += stringf(".%s", ctx->nameOf(IdString(ctx->bel_data(bel).name))); } // Write out a pip in tile.dst.src format void write_pip(PipId pip) { auto &pd = ctx->pip_data(pip); if ((pd.flags & PIP_FIXED_CONN) || (pd.flags & PIP_LUT_PERM)) return; std::string tile = tile_name(pip.tile, tile_by_type_and_loc(pip.tile, IdString(pd.tile_type))); std::string source_wire = escape_name(ctx->pip_src_wire_name(pip).str(ctx)); if (source_wire == "LOCAL_VCC") source_wire = "G__VCC"; std::string dest_wire = escape_name(ctx->pip_dst_wire_name(pip).str(ctx)); out << stringf("%s.PIP.%s.%s", tile.c_str(), dest_wire.c_str(), source_wire.c_str()) << std::endl; } // Write out all the pips corresponding to a net void write_net(const NetInfo *net) { write_comment(stringf("Net %s", ctx->nameOf(net))); std::set sorted_pips; for (auto &w : net->wires) if (w.second.pip != PipId()) sorted_pips.insert(w.second.pip); for (auto p : sorted_pips) write_pip(p); blank(); } // Find the CIBMUX output for a signal WireId find_cibmux(WireId cursor) { 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(); } // Write out the mux config for a cell void write_cell_muxes(const CellInfo *cell) { for (auto &port : 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 && !(pin_style & PINBIT_GATED)) 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) && (port.second.net != nullptr)) 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))); // 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(ctx->getBelPinWire(cell->bel, 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); } } } } // Handle route-through DCCs void write_dcc_thru() { for (auto bel : ctx->getBels()) { if (ctx->getBelType(bel) != id_DCC) continue; if (!ctx->checkBelAvail(bel)) continue; WireId dst = ctx->getBelPinWire(bel, id_CLKO); if (ctx->getBoundWireNet(dst) == nullptr) continue; // Set up the CIBMUX so CE is guaranteed to be tied high WireId ce = ctx->getBelPinWire(bel, id_CE); WireId cibmuxout = find_cibmux(ce); NPNR_ASSERT(cibmuxout != WireId()); write_comment(stringf("CE CIBMUX for DCC route-thru %s", ctx->nameOfBel(bel))); 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); } } unsigned permute_init(const CellInfo *cell) { unsigned orig_init = int_or_default(cell->params, id_INIT, 0); std::array, 4> phys_to_log; const std::array ports{id_A, id_B, id_C, id_D}; for (unsigned i = 0; i < 4; i++) { WireId pin_wire = ctx->getBelPinWire(cell->bel, ports[i]); for (PipId pip : ctx->getPipsUphill(pin_wire)) { if (!ctx->getBoundPipNet(pip)) continue; const auto &data = ctx->pip_data(pip); if (data.flags & PIP_FIXED_CONN) { // non-permuting phys_to_log[i].push_back(i); } else { // permuting NPNR_ASSERT(data.flags & PIP_LUT_PERM); unsigned from_pin = (data.flags >> 4) & 0xF; unsigned to_pin = (data.flags >> 0) & 0xF; NPNR_ASSERT(to_pin == i); phys_to_log[from_pin].push_back(i); } } } unsigned permuted_init = 0; for (unsigned i = 0; i < 16; i++) { unsigned log_idx = 0; for (unsigned j = 0; j < 4; j++) { if ((i >> j) & 0x1) { for (auto log_pin : phys_to_log[j]) log_idx |= (1 << log_pin); } } if ((orig_init >> log_idx) & 0x1) permuted_init |= (1 << i); } return permuted_init; } // Write config for an OXIDE_COMB cell void write_comb(const CellInfo *cell) { BelId bel = cell->bel; int z = ctx->bel_data(bel).z; int k = z & 0x1; char slice = 'A' + (z >> 3); push_tile(bel.tile, id_PLC); push(stringf("SLICE%c", slice)); if (cell->params.count(id_INIT)) write_int_vector(stringf("K%d.INIT[15:0]", k), permute_init(cell), 16); if (cell->lutInfo.is_carry) { write_bit("MODE.CCU2"); write_enum(cell, "CCU2.INJECT", "NO"); } pop(2); } // Write config for an OXIDE_FF cell void write_ff(const CellInfo *cell) { BelId bel = cell->bel; int z = ctx->bel_data(bel).z; int k = z & 0x1; char slice = 'A' + (z >> 3); push_tile(bel.tile, id_PLC); push(stringf("SLICE%c", slice)); push(stringf("REG%d", k)); 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"); write_cell_muxes(cell); pop(2); } // 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); } pool used_io; struct BankConfig { bool diff_used = false; bool lvds_used = false; bool slvs_used = false; bool dphy_used = false; }; std::map bank_cfg; // Write config for an SEIO33_CORE cell void write_io33(const CellInfo *cell) { BelId bel = cell->bel; used_io.insert(bel); push_bel(bel); const NetInfo *t = cell->getPort(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"); write_bit(stringf("BASE_TYPE.%s_%s", iodir, str_or_default(cell->attrs, id_IO_TYPE, "LVCMOS33").c_str())); write_ioattr(cell, "PULLMODE", "NONE"); write_ioattr(cell, "GLITCHFILTER", "OFF"); write_ioattr(cell, "SLEWRATE", str_or_default(cell->attrs, id_SLEWRATE, "MED").c_str()); write_cell_muxes(cell); pop(); } // Write config for an SEIO18_CORE cell void write_io18(const CellInfo *cell) { BelId bel = cell->bel; used_io.insert(bel); push_bel(bel); push("SEIO18"); const NetInfo *t = cell->getPort(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; } auto &bank = bank_cfg[ctx->get_bel_pad(bel)->bank]; if (is_lifcl_17 && (is_output || !is_input)) bank.diff_used = true; // what exactly should this bit be called? 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())); write_ioattr(cell, "PULLMODE", "NONE"); write_ioattr(cell, "SLEWRATE", str_or_default(cell->attrs, id_SLEWRATE, "MED").c_str()); 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 = cell->getPort(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(); write_cell_muxes(cell); pop(2); } // Write config for an OSC_CORE cell void write_osc(const CellInfo *cell) { BelId bel = cell->bel; push_tile(bel.tile); push_belname(bel); write_enum(cell, "HF_OSC_EN", "ENABLED"); 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"); write_enum(cell, "DTR_EN", "ENABLED"); write_enum(cell, "DEBUG_N", "DISABLED"); write_int_vector(stringf("HF_CLK_DIV[7:0]"), ctx->parse_lattice_param_from_cell(cell, id_HF_CLK_DIV, 8, 0).intval, 8); write_int_vector(stringf("HF_SED_SEC_DIV[7:0]"), 1, 8); write_cell_muxes(cell); pop(2); } // Write config for DCC void write_dcc(const CellInfo *cell) { BelId bel = cell->bel; push_bel(bel); write_bit("DCCEN.1"); // Explicit DCC cell implies a clock buffer write_cell_muxes(cell); pop(); } // Write config for DCS void write_dcs(const CellInfo *cell) { BelId bel = cell->bel; push_tile(bel.tile, ctx->id("CMUX_0")); push_belname(bel); write_enum(cell, "DCSMODE", "VCC"); pop(2); } // 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())); write_enum(cell, "INIT_DATA", "STATIC"); write_enum(cell, "GSR", "DISABLED"); write_int_vector("WID[10:0]", wid, 11); push(stringf("%s_MODE", mode.c_str())); if (mode == "DP16K") { write_int_vector_param(cell, "CSDECODE_A", 7, 3, true); write_int_vector_param(cell, "CSDECODE_B", 7, 3, true); 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") { write_int_vector_param(cell, "CSDECODE_W", 7, 3, true); write_int_vector_param(cell, "CSDECODE_R", 7, 3, true); 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->idf("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(); } } 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 IOLOGIC cell void write_iol(const CellInfo *cell) { BelId bel = cell->bel; push_bel(bel); write_enum(cell, "MODE"); write_enum(cell, "IDDRX1_ODDRX1.OUTPUT"); write_enum(cell, "IDDRX1_ODDRX1.TRISTATE"); write_enum(cell, "GSR", "DISABLED"); write_enum(cell, "TSREG.REGSET", "RESET"); write_cell_muxes(cell); pop(); } // Write config for some kind of DSP cell void write_dsp(const CellInfo *cell) { BelId bel = cell->bel; push_bel(bel); 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))); for (auto ¶m : cell->params) { const std::string ¶m_name = param.first.str(ctx); if (is_mux_param(param_name)) continue; if (param.first == id_ROUNDBIT) { // currently unsupported in oxide, but appears rarely used NPNR_ASSERT(param.second.as_string() == "ROUND_TO_BIT0"); continue; } write_enum(cell, param_name); } write_cell_muxes(cell); pop(); } // Which PLL params are 'word' values /* clang-format off */ const dict 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}, }; const dict pll_default_params = { {"BW_CTL_BIAS", "0b0101"}, {"CLKOP_TRIM", "0b0000"}, {"CLKOS_TRIM", "0b0000"}, {"CLKOS2_TRIM", "0b0000"}, {"CLKOS3_TRIM", "0b0000"}, {"CLKOS4_TRIM", "0b0000"}, {"CLKOS5_TRIM", "0b0000"}, {"CRIPPLE", "5P"}, {"CSET", "40P"}, {"DELAY_CTRL", "200PS"}, {"DELA", "0"}, {"DELB", "0"}, {"DELC", "0"}, {"DELD", "0"}, {"DELE", "0"}, {"DELF", "0"}, {"DIRECTION", "DISABLED"}, {"DIVA", "0"}, {"DIVB", "0"}, {"DIVC", "0"}, {"DIVD", "0"}, {"DIVE", "0"}, {"DIVF", "0"}, {"DYN_SEL", "0b000"}, {"DYN_SOURCE", "STATIC"}, {"ENCLK_CLKOP", "DISABLED"}, {"ENCLK_CLKOS", "DISABLED"}, {"ENCLK_CLKOS2", "DISABLED"}, {"ENCLK_CLKOS3", "DISABLED"}, {"ENCLK_CLKOS4", "DISABLED"}, {"ENCLK_CLKOS5", "DISABLED"}, {"ENABLE_SYNC", "DISABLED"}, {"FAST_LOCK_EN", "ENABLED"}, {"V2I_1V_EN", "DISABLED"}, {"FBK_CUR_BLE", "0b00000000"}, {"FBK_EDGE_SEL", "POSITIVE"}, {"FBK_IF_TIMING_CTL", "0b00"}, {"FBK_INTEGER_MODE", "DISABLED"}, {"FBK_MASK", "0b00001000"}, {"FBK_MMD_DIG", "8"}, {"FBK_MMD_PULS_CTL", "0b0000"}, {"FBK_MODE", "0b00"}, {"FBK_PI_BYPASS", "NOT_BYPASSED"}, {"FBK_PI_RC", "0b1100"}, {"FBK_PR_CC", "0b0000"}, {"FBK_PR_IC", "0b1000"}, {"FLOAT_CP", "DISABLED"}, {"FLOCK_CTRL", "2X"}, {"FLOCK_EN", "ENABLED"}, {"FLOCK_SRC_SEL", "REFCLK"}, {"FORCE_FILTER", "DISABLED"}, {"I_CTRL", "10UA"}, {"IPI_CMP", "0b1000"}, {"IPI_CMPN", "0b0011"}, {"IPI_COMP_EN", "DISABLED"}, {"IPP_CTRL", "0b1000"}, {"IPP_SEL", "0b1111"}, {"KP_VCO", "0b11001"}, {"LDT_INT_LOCK_STICKY", "DISABLED"}, {"LDT_LOCK", "1536CYC"}, {"LDT_LOCK_SEL", "U_FREQ"}, {"LEGACY_ATT", "DISABLED"}, {"LOAD_REG", "DISABLED"}, {"OPENLOOP_EN", "DISABLED"}, {"PHIA", "0"}, {"PHIB", "0"}, {"PHIC", "0"}, {"PHID", "0"}, {"PHIE", "0"}, {"PHIF", "0"}, {"PLLPDN_EN", "DISABLED"}, {"PLLPD_N", "UNUSED"}, {"PLLRESET_ENA", "DISABLED"}, {"REF_INTEGER_MODE", "DISABLED"}, {"REF_MASK", "0b00000000"}, {"REF_MMD_DIG", "8"}, {"REF_MMD_IN", "0b00001000"}, {"REF_MMD_PULS_CTL", "0b0000"}, {"REF_TIMING_CTL", "0b00"}, {"REFIN_RESET", "SET"}, {"RESET_LF", "DISABLED"}, {"ROTATE", "DISABLED"}, {"SEL_OUTA", "DISABLED"}, {"SEL_OUTB", "DISABLED"}, {"SEL_OUTC", "DISABLED"}, {"SEL_OUTD", "DISABLED"}, {"SEL_OUTE", "DISABLED"}, {"SEL_OUTF", "DISABLED"}, {"SLEEP", "DISABLED"}, {"SSC_DELTA", "0b000000000000000"}, {"SSC_DELTA_CTL", "0b00"}, {"SSC_DITHER", "DISABLED"}, {"SSC_EN_CENTER_IN", "DOWN_TRIANGLE"}, {"SSC_EN_SDM", "DISABLED"}, {"SSC_EN_SSC", "DISABLED"}, {"SSC_F_CODE", "0b000000000000000"}, {"SSC_N_CODE", "0b000010100"}, {"SSC_ORDER", "SDM_ORDER2"}, {"SSC_PI_BYPASS", "NOT_BYPASSED"}, {"SSC_REG_WEIGHTING_SEL", "0b000"}, {"SSC_SQUARE_MODE", "DISABLED"}, {"SSC_STEP_IN", "0b0000000"}, {"SSC_TBASE", "0b000000000000"}, {"STDBY_ATT", "DISABLED"}, {"TRIMOP_BYPASS_N", "BYPASSED"}, {"TRIMOS_BYPASS_N", "BYPASSED"}, {"TRIMOS2_BYPASS_N", "BYPASSED"}, {"TRIMOS3_BYPASS_N", "BYPASSED"}, {"TRIMOS4_BYPASS_N", "BYPASSED"}, {"TRIMOS5_BYPASS_N", "BYPASSED"}, {"V2I_KVCO_SEL", "85"}, {"V2I_PP_ICTRL", "0b00110"}, {"V2I_PP_RES", "10K"}, {"CLKMUX_FB", "CMUX_CLKOP"}, {"SEL_FBK", "DIVA"}, {"DIV_DEL", "0b0000001"}, }; // Which MIPI params are 'word' values const dict dphy_word_params = { {"CM", 8}, {"CN", 5}, {"CO", 3}, {"RSEL", 2}, {"RXCDRP", 2}, {"RXDATAWIDTHHS", 2}, {"RXLPRP", 3}, {"TEST_ENBL", 6}, {"TEST_PATTERN", 32}, {"TST", 4}, {"TXDATAWIDTHHS", 2}, {"UC_PRG_RXHS_SETTLE", 6}, {"U_PRG_HS_PREPARE", 2}, {"U_PRG_HS_TRAIL", 6}, {"U_PRG_HS_ZERO", 6}, {"U_PRG_RXHS_SETTLE", 6} }; /* clang-format on */ static bool is_number(std::string s) { for (auto c : s) { if (!isdigit(c)) return false; } return true; } // 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(IdString(ctx->bel_data(bel).name)))); for (auto &value : pll_default_params) { IdString n = IdString(ctx, value.first); Property temp; if (is_number(value.second)) temp = Property(std::stoi(value.second), 32); else temp = Property::from_string(value.second); const std::string &name = n.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()) { if (cell->params.count(n)) { write_int_vector(stringf("%s[%d:0]", name.c_str(), fnd_word->second - 1), ctx->parse_lattice_param_from_cell(cell, n, fnd_word->second, 0).as_int64(), fnd_word->second); } else { write_int_vector(stringf("%s[%d:0]", name.c_str(), fnd_word->second - 1), ctx->parse_lattice_param(temp, n, fnd_word->second).as_int64(), fnd_word->second); } } else { if (cell->params.count(n)) { write_bit(stringf("%s.%s", name.c_str(), cell->params.at(n).as_string().c_str())); } else { write_bit(stringf("%s.%s", name.c_str(), temp.as_string().c_str())); } } } pop(); } // Write out config for a DPHY_CORE cell // TODO: duplication with PLL and other hard IP... void write_dphy(const CellInfo *cell) { BelId bel = cell->bel; push(stringf("IP_%s", ctx->nameOf(IdString(ctx->bel_data(bel).name)))); for (auto ¶m : cell->params) { const std::string &name = param.first.str(ctx); if (is_mux_param(name) || name == "GSR") continue; auto fnd_word = dphy_word_params.find(name); if (fnd_word != dphy_word_params.end()) { write_int_vector(stringf("%s[%d:0]", name.c_str(), fnd_word->second - 1), ctx->parse_lattice_param_from_cell(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(); } // Write out config for an LRAM_CORE cell void write_lram(const CellInfo *cell) { BelId bel = cell->bel; push_bel(bel); if (is_lifcl_17) write_bit("MODE.LRAM_CORE"); write_enum(cell, "ASYNC_RST_RELEASE", "SYNC"); write_enum(cell, "EBR_SP_EN", "DISABLE"); write_enum(cell, "ECC_BYTE_SEL", "BYTE_EN"); write_enum(cell, "GSR", "DISABLED"); write_enum(cell, "OUT_REGMODE_A", "NO_REG"); write_enum(cell, "OUT_REGMODE_B", "NO_REG"); write_enum(cell, "RESETMODE", "SYNC"); write_enum(cell, "UNALIGNED_READ", "DISABLE"); write_cell_muxes(cell); pop(); blank(); Loc l = ctx->getBelLocation(bel); if (is_lifcl_17 && l.x == 0) l.x = 1; push(stringf("IP_LRAM_CORE_R%dC%d", l.y, l.x)); for (int i = 0; i < 128; i++) { IdString param = ctx->idf("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("5120'h%s", value.c_str()); } else { // True Verilog bitvector value = stringf("5120'b%s", prop.str.c_str()); } write_bit(stringf("INITVAL_%02X[5119:0] = %s", i, value.c_str())); } pop(); } // 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 dict> 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(); } } } dict bank_vcco; // bank VccO in mV int get_bank_vcco(const std::string &iostd) { if (iostd == "LVCMOS33" || iostd == "LVCMOS33D") return 3300; else if (iostd == "LVCMOS25" || iostd == "LVCMOS25D") return 2500; else if (iostd == "LVCMOS18") return 1800; else if (iostd == "LVCMOS15") return 1500; else if (iostd == "LVCMOS12") return 1200; else return -1; } // Write out placeholder bankref config void write_bankcfg() { for (auto &c : ctx->cells) { const CellInfo *ci = c.second.get(); if (ci->type != id_SEIO33_CORE) continue; if (!ci->attrs.count(id_IO_TYPE)) continue; // VccO only concerns outputs const NetInfo *t = ci->getPort(id_T); auto tmux = ctx->get_cell_pinmux(ci, id_T); if (tmux == PINMUX_1 || (tmux != PINMUX_0 && t == nullptr)) continue; int bank = ctx->get_bel_pad(ci->bel)->bank; std::string iostd = ci->attrs.at(id_IO_TYPE).as_string(); int vcco = get_bank_vcco(iostd); if (vcco == -1) { log_warning("Unexpected IO standard '%s' on port '%s'\n", iostd.c_str(), ctx->nameOf(ci)); continue; } if (bank_vcco.count(bank) && bank_vcco.at(bank) != vcco) { log_warning("Conflicting Vcco %.1fV and %.1fV on bank %d\n", bank_vcco.at(bank) / 1000.0, vcco / 1000.0, bank); continue; } bank_vcco[bank] = vcco; } for (int i = 0; i < 8; i++) { 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 { if (is_lifcl_17 && (i != 0) && (i != 1)) continue; auto vcco = bank_vcco.find(i); if (vcco != bank_vcco.end()) write_bit(stringf("GLOBAL.BANK%d.VCC.%dV%d", i, vcco->second / 1000, (vcco->second / 100) % 10)); else write_bit(stringf("GLOBAL.BANK%d.VCC.3V3", i)); } } blank(); } // Write out FASM for the whole design void operator()() { // Write device config write_attribute("oxide.device", ctx->device); write_attribute("oxide.device_variant", ctx->variant); blank(); // Write routing for (auto &n : ctx->nets) { write_net(n.second.get()); } // Write cell config for (auto &c : ctx->cells) { const CellInfo *ci = c.second.get(); 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); else if (ci->type == id_RAMW) write_ramw(ci); else if (ci->type == id_SEIO33_CORE) write_io33(ci); else if (ci->type == id_SEIO18_CORE) write_io18(ci); else if (ci->type == id_DIFFIO18_CORE) write_diffio18(ci); else if (ci->type == id_OSC_CORE) write_osc(ci); else if (ci->type == id_OXIDE_EBR) write_bram(ci); else if (ci->type.in(id_MULT9_CORE, id_PREADD9_CORE, id_MULT18_CORE, id_MULT18X36_CORE, id_MULT36_CORE, id_REG18_CORE, id_ACC54_CORE)) write_dsp(ci); else if (ci->type == id_PLL_CORE) write_pll(ci); else if (ci->type == id_LRAM_CORE) write_lram(ci); else if (ci->type == id_DPHY_CORE) write_dphy(ci); else if (ci->type.in(id_IOLOGIC, id_SIOLOGIC)) write_iol(ci); else if (ci->type == id_DCC) write_dcc(ci); else if (ci->type == id_DCS) write_dcs(ci); blank(); } // Handle DCC route-throughs write_dcc_thru(); // Write config for unused bels write_unused(); // Write bank config write_bankcfg(); } }; } // namespace void Arch::write_fasm(std::ostream &out) const { NexusFasmWriter(getCtx(), out)(); } NEXTPNR_NAMESPACE_END