/* * nextpnr -- Next Generation Place and Route * * Copyright (C) 2018 Claire Xenia Wolf * Copyright (C) 2018 gatecat * Copyright (C) 2018 Serge Bazanski * * 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 "bitstream.h" #include #include #include "cells.h" #include "log.h" #include "util.h" NEXTPNR_NAMESPACE_BEGIN inline TileType tile_at(const Context *ctx, int x, int y) { return ctx->chip_info->tile_grid[y * ctx->chip_info->width + x]; } const ConfigEntryPOD &find_config(const TileInfoPOD &tile, const std::string &name) { for (auto &entry : tile.entries) { if (std::string(entry.name.get()) == name) { return entry; } } NPNR_ASSERT_FALSE_STR("unable to find config bit " + name); } std::tuple get_ieren(const BitstreamInfoPOD &bi, int8_t x, int8_t y, int8_t z) { for (auto &ie : bi.ierens) { if (ie.iox == x && ie.ioy == y && ie.ioz == z) { return std::make_tuple(ie.ierx, ie.iery, ie.ierz); } } // No pin at this location return std::make_tuple(-1, -1, -1); }; bool get_config(const TileInfoPOD &ti, std::vector> &tile_cfg, const std::string &name, int index = -1) { const ConfigEntryPOD &cfg = find_config(ti, name); if (index == -1) { for (auto &bit : cfg.bits) { return tile_cfg.at(bit.row).at(bit.col); } } else { return tile_cfg.at(cfg.bits[index].row).at(cfg.bits[index].col); } return false; } void set_config(const TileInfoPOD &ti, std::vector> &tile_cfg, const std::string &name, bool value, int index = -1) { const ConfigEntryPOD &cfg = find_config(ti, name); if (index == -1) { for (auto &bit : cfg.bits) { int8_t &cbit = tile_cfg.at(bit.row).at(bit.col); if (cbit && !value) log_error("clearing already set config bit %s\n", name.c_str()); cbit = value; } } else { int8_t &cbit = tile_cfg.at(cfg.bits[index].row).at(cfg.bits[index].col); cbit = value; if (cbit && !value) log_error("clearing already set config bit %s[%d]\n", name.c_str(), index); } } // Set an IE_{EN,REN} logical bit in a tile config. Logical means enabled. // On {HX,LP}1K devices these bits are active low, so we need to invert them. void set_ie_bit_logical(const Context *ctx, const TileInfoPOD &ti, std::vector> &tile_cfg, const std::string &name, bool value) { if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) { set_config(ti, tile_cfg, name, !value); } else { set_config(ti, tile_cfg, name, value); } } int get_param_or_def(const Context *ctx, const CellInfo *cell, const IdString param, int defval = 0) { auto found = cell->params.find(param); if (found != cell->params.end()) { if (found->second.is_string) log_error("expected numeric value for parameter '%s' on cell '%s'\n", param.c_str(ctx), cell->name.c_str(ctx)); return found->second.as_int64(); } else return defval; } std::string get_param_str_or_def(const CellInfo *cell, const IdString param, std::string defval = "") { auto found = cell->params.find(param); if (found != cell->params.end()) return found->second.as_string(); else return defval; } char get_hexdigit(int i) { return std::string("0123456789ABCDEF").at(i); } static const BelConfigPOD &get_ec_config(const ChipInfoPOD *chip, BelId bel) { for (auto &cfg : chip->bel_config) { if (cfg.bel_index == bel.index) return cfg; } NPNR_ASSERT_FALSE("failed to find bel config"); } typedef std::vector>>> chipconfig_t; static bool has_ec_cbit(const BelConfigPOD &cell_cbits, std::string name) { for (auto &cbit : cell_cbits.entries) { if (cbit.entry_name.get() == name) return true; } return false; } static void set_ec_cbit(chipconfig_t &config, const Context *ctx, const BelConfigPOD &cell_cbits, std::string name, bool value, std::string prefix) { const ChipInfoPOD *chip = ctx->chip_info; for (auto &cbit : cell_cbits.entries) { if (cbit.entry_name.get() == name) { const auto &ti = chip->bits_info->tiles_nonrouting[tile_at(ctx, cbit.x, cbit.y)]; set_config(ti, config.at(cbit.y).at(cbit.x), prefix + cbit.cbit_name.get(), value); return; } } if (value) NPNR_ASSERT_FALSE_STR("failed to config extra cell config bit " + name); else log_warning("failed to config extra cell config bit %s to zero (ignored, maybe update icestorm ?)\n", name.c_str()); } void configure_extra_cell(chipconfig_t &config, const Context *ctx, CellInfo *cell, const std::vector> ¶ms, bool string_style, std::string prefix) { const ChipInfoPOD *chip = ctx->chip_info; const auto &bc = get_ec_config(chip, cell->bel); for (auto p : params) { std::vector value; if (string_style) { // Lattice's weird string style params, not sure if // prefixes other than 0b should be supported, only 0b features in docs if (cell->params.count(ctx->id(p.first)) && !cell->params.at(ctx->id(p.first)).is_string) log_error("expected configuration string starting with '0b' for parameter '%s' on cell '%s'\n", p.first.c_str(), cell->name.c_str(ctx)); std::string raw = get_param_str_or_def(cell, ctx->id(p.first), "0b0"); if (raw.substr(0, 2) != "0b") log_error("expected configuration string starting with '0b' for parameter '%s' on cell '%s'\n", p.first.c_str(), cell->name.c_str(ctx)); raw = raw.substr(2); value.resize(raw.length()); for (int i = 0; i < (int)raw.length(); i++) { if (raw[i] == '1') { value[(raw.length() - 1) - i] = 1; } else { assert(raw[i] == '0'); value[(raw.length() - 1) - i] = 0; } } } else { int ival; try { ival = get_param_or_def(ctx, cell, ctx->id(p.first), 0); } catch (std::exception &e) { log_error("expected numeric value for parameter '%s' on cell '%s'\n", p.first.c_str(), cell->name.c_str(ctx)); } for (int i = 0; i < p.second; i++) value.push_back((ival >> i) & 0x1); } value.resize(p.second); if ((p.second == 1) || !has_ec_cbit(bc, p.first + "_0")) { set_ec_cbit(config, ctx, bc, p.first, value.at(0), prefix); } else { for (int i = 0; i < p.second; i++) { set_ec_cbit(config, ctx, bc, p.first + "_" + std::to_string(i), value.at(i), prefix); } } } } std::string tagTileType(TileType &tile) { if (tile == TILE_NONE) return ""; switch (tile) { case TILE_LOGIC: return ".logic_tile"; break; case TILE_IO: return ".io_tile"; break; case TILE_RAMB: return ".ramb_tile"; break; case TILE_RAMT: return ".ramt_tile"; break; case TILE_DSP0: return ".dsp0_tile"; break; case TILE_DSP1: return ".dsp1_tile"; break; case TILE_DSP2: return ".dsp2_tile"; break; case TILE_DSP3: return ".dsp3_tile"; break; case TILE_IPCON: return ".ipcon_tile"; break; default: NPNR_ASSERT(false); } } static BelPin get_one_bel_pin(const Context *ctx, WireId wire) { auto pins = ctx->getWireBelPins(wire); NPNR_ASSERT(pins.begin() != pins.end()); return *pins.begin(); } // Permute LUT init value given map (LUT input -> ext input) unsigned permute_lut(unsigned orig_init, const dict &input_permute) { unsigned new_init = 0; for (int i = 0; i < 16; i++) { int permute_address = 0; for (int j = 0; j < 4; j++) { if ((i >> j) & 0x1) permute_address |= (1 << input_permute.at(j)); } if ((orig_init >> i) & 0x1) { new_init |= (1 << permute_address); } } return new_init; } void write_asc(const Context *ctx, std::ostream &out) { static const std::vector lut_perm = { 4, 14, 15, 5, 6, 16, 17, 7, 3, 13, 12, 2, 1, 11, 10, 0, }; // [y][x][row][col] const ChipInfoPOD &ci = *ctx->chip_info; const BitstreamInfoPOD &bi = *ci.bits_info; chipconfig_t config; config.resize(ci.height); for (int y = 0; y < ci.height; y++) { config.at(y).resize(ci.width); for (int x = 0; x < ci.width; x++) { TileType tile = tile_at(ctx, x, y); int rows = bi.tiles_nonrouting[tile].rows; int cols = bi.tiles_nonrouting[tile].cols; config.at(y).at(x).resize(rows, std::vector(cols)); } } std::vector> extra_bits; out << ".comment from next-pnr" << std::endl; switch (ctx->args.type) { case ArchArgs::LP384: out << ".device 384" << std::endl; break; case ArchArgs::HX1K: case ArchArgs::LP1K: out << ".device 1k" << std::endl; break; case ArchArgs::HX4K: case ArchArgs::LP4K: case ArchArgs::HX8K: case ArchArgs::LP8K: out << ".device 8k" << std::endl; break; case ArchArgs::UP3K: case ArchArgs::UP5K: out << ".device 5k" << std::endl; break; case ArchArgs::U1K: case ArchArgs::U2K: case ArchArgs::U4K: out << ".device u4k" << std::endl; break; default: NPNR_ASSERT_FALSE("unsupported device type\n"); } // Set pips for (auto pip : ctx->getPips()) { if (ctx->pip_to_net[pip.index] != nullptr) { const PipInfoPOD &pi = ci.pip_data[pip.index]; const SwitchInfoPOD &swi = bi.switches[pi.switch_index]; int sw_bel_idx = swi.bel; if (sw_bel_idx >= 0) { const BelInfoPOD &beli = ci.bel_data[sw_bel_idx]; const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_LOGIC]; BelId sw_bel; sw_bel.index = sw_bel_idx; NPNR_ASSERT(ctx->getBelType(sw_bel) == id_ICESTORM_LC); if (ci.wire_data[ctx->getPipDstWire(pip).index].type == WireInfoPOD::WIRE_TYPE_LUTFF_IN_LUT) continue; // Permutation pips BelPin output = get_one_bel_pin(ctx, ctx->getPipDstWire(pip)); NPNR_ASSERT(output.bel == sw_bel && output.pin == id_O); unsigned lut_init; WireId permWire; for (auto permPip : ctx->getPipsUphill(ctx->getPipSrcWire(pip))) { if (ctx->getBoundPipNet(permPip) != nullptr) { permWire = ctx->getPipSrcWire(permPip); } } NPNR_ASSERT(permWire != WireId()); std::string dName = ci.wire_data[permWire.index].name.get(); switch (dName.back()) { case '0': lut_init = 2; break; case '1': lut_init = 4; break; case '2': lut_init = 16; break; case '3': lut_init = 256; break; default: NPNR_ASSERT_FALSE("bad feedthru LUT input"); } std::vector lc(20, false); for (int i = 0; i < 16; i++) { if ((lut_init >> i) & 0x1) lc.at(lut_perm.at(i)) = true; } for (int i = 0; i < 20; i++) set_config(ti, config.at(beli.y).at(beli.x), "LC_" + std::to_string(beli.z), lc.at(i), i); } else { for (int i = 0; i < swi.num_bits; i++) { bool val = (pi.switch_mask & (1 << ((swi.num_bits - 1) - i))) != 0; int8_t &cbit = config.at(swi.y).at(swi.x).at(swi.cbits[i].row).at(swi.cbits[i].col); if (bool(cbit) != 0) NPNR_ASSERT(false); cbit = val; } } } } // Scan for PLL and collects the affected SB_IOs pool sb_io_used_by_pll_out; pool sb_io_used_by_pll_pad; for (auto &cell : ctx->cells) { if (cell.second->type != id_ICESTORM_PLL) continue; // Collect all locations matching an PLL output port // note: It doesn't matter if the port is connected or not, or if fabric/global // is used. As long as it's a PLL type for which the port exists, the SB_IO // is not available and must be configured for PLL mode const std::vector ports = {id_PLLOUT_A, id_PLLOUT_B}; for (auto &port : ports) { // If the output is not enabled in this mode, ignore it if (port == id_PLLOUT_B && !is_sb_pll40_dual(ctx, cell.second.get())) continue; // Get IO Bel that this PLL port goes through by finding sibling // Bel driving the same wire via PIN_D_IN_0. auto wire = ctx->getBelPinWire(cell.second->bel, port); BelId io_bel; for (auto pin : ctx->getWireBelPins(wire)) { if (pin.pin == id_D_IN_0) { io_bel = pin.bel; break; } } NPNR_ASSERT(io_bel.index != -1); auto io_bel_loc = ctx->getBelLocation(io_bel); // Mark this SB_IO as being used by a PLL output path sb_io_used_by_pll_out.insert(io_bel_loc); // If this is a PAD PLL, and this is the 'PLLOUT_A' port, then the same SB_IO is also PAD if (port == id_PLLOUT_A && is_sb_pll40_pad(ctx, cell.second.get())) sb_io_used_by_pll_pad.insert(io_bel_loc); // Configure the SB_IO that the clock outputs are going through. // note: PINTYPE[1:0] must be set property to passes the PLL through to the fabric. // "01" if ICEGATE is disabed for that port and "11" if it's enabled const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO]; bool icegate_ena = get_param_or_def( ctx, cell.second.get(), (port == id_PLLOUT_A) ? id_ENABLE_ICEGATE_PORTA : id_ENABLE_ICEGATE_PORTB); set_config(ti, config.at(io_bel_loc.y).at(io_bel_loc.x), "IOB_" + std::to_string(io_bel_loc.z) + ".PINTYPE_1", icegate_ena); set_config(ti, config.at(io_bel_loc.y).at(io_bel_loc.x), "IOB_" + std::to_string(io_bel_loc.z) + ".PINTYPE_0", true); } } // Set logic cell config for (auto &cell : ctx->cells) { BelId bel = cell.second.get()->bel; if (bel == BelId()) { std::cout << "Found unplaced cell " << cell.first.str(ctx) << " while generating bitstream!" << std::endl; continue; } if (cell.second->type == id_ICESTORM_LC) { const BelInfoPOD &beli = ci.bel_data[bel.index]; int x = beli.x, y = beli.y, z = beli.z; const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_LOGIC]; unsigned lut_init = get_param_or_def(ctx, cell.second.get(), id_LUT_INIT); bool neg_clk = get_param_or_def(ctx, cell.second.get(), id_NEG_CLK); bool dff_enable = get_param_or_def(ctx, cell.second.get(), id_DFF_ENABLE); bool async_sr = get_param_or_def(ctx, cell.second.get(), id_ASYNC_SR); bool set_noreset = get_param_or_def(ctx, cell.second.get(), id_SET_NORESET); bool carry_enable = get_param_or_def(ctx, cell.second.get(), id_CARRY_ENABLE); std::vector lc(20, false); // Discover permutation dict input_perm; std::set unused; for (int i = 0; i < 4; i++) unused.insert(i); for (int i = 0; i < 4; i++) { WireId lut_wire = ctx->getBelPinWire(bel, IdString(ID_I0 + i)); for (auto pip : ctx->getPipsUphill(lut_wire)) { if (ctx->getBoundPipNet(pip) != nullptr) { std::string name = ci.wire_data[ctx->getPipSrcWire(pip).index].name.get(); switch (name.back()) { case '0': input_perm[i] = 0; unused.erase(0); break; case '1': input_perm[i] = 1; unused.erase(1); break; case '2': input_perm[i] = 2; unused.erase(2); break; case '3': input_perm[i] = 3; unused.erase(3); break; default: NPNR_ASSERT_FALSE("failed to determine LUT permutation"); } break; } } } for (int i = 0; i < 4; i++) { if (!input_perm.count(i)) { NPNR_ASSERT(!unused.empty()); input_perm[i] = *(unused.begin()); unused.erase(input_perm[i]); } } lut_init = permute_lut(lut_init, input_perm); for (int i = 0; i < 16; i++) { if ((lut_init >> i) & 0x1) lc.at(lut_perm.at(i)) = true; } lc.at(8) = carry_enable; lc.at(9) = dff_enable; lc.at(18) = set_noreset; lc.at(19) = async_sr; for (int i = 0; i < 20; i++) set_config(ti, config.at(y).at(x), "LC_" + std::to_string(z), lc.at(i), i); if (dff_enable) set_config(ti, config.at(y).at(x), "NegClk", neg_clk); bool carry_const = get_param_or_def(ctx, cell.second.get(), id_CIN_CONST); bool carry_set = get_param_or_def(ctx, cell.second.get(), id_CIN_SET); if (carry_const) { if (!ctx->force) NPNR_ASSERT(z == 0); set_config(ti, config.at(y).at(x), "CarryInSet", carry_set); } } else if (cell.second->type == id_SB_IO) { const BelInfoPOD &beli = ci.bel_data[bel.index]; int x = beli.x, y = beli.y, z = beli.z; const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO]; unsigned pin_type = get_param_or_def(ctx, cell.second.get(), id_PIN_TYPE); bool neg_trigger = get_param_or_def(ctx, cell.second.get(), id_NEG_TRIGGER); bool pullup = get_param_or_def(ctx, cell.second.get(), id_PULLUP); bool lvds = cell.second->ioInfo.lvds; bool used_by_pll_out = sb_io_used_by_pll_out.count(Loc(x, y, z)) > 0; bool used_by_pll_pad = sb_io_used_by_pll_pad.count(Loc(x, y, z)) > 0; for (int i = used_by_pll_out ? 2 : 0; i < 6; i++) { bool val = (pin_type >> i) & 0x01; set_config(ti, config.at(y).at(x), "IOB_" + std::to_string(z) + ".PINTYPE_" + std::to_string(i), val); } set_config(ti, config.at(y).at(x), "NegClk", neg_trigger); bool input_en = false; if ((ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_0).index] != nullptr) || (ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_1).index] != nullptr)) { input_en = true; } input_en = (input_en & !used_by_pll_out) | used_by_pll_pad; input_en |= cell.second->ioInfo.global; if (!lvds) { auto ieren = get_ieren(bi, x, y, z); int iex, iey, iez; std::tie(iex, iey, iez) = ieren; NPNR_ASSERT(iez != -1); if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) { set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), !input_en); set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup); } else { set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), input_en); set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup); } if (ctx->args.type == ArchArgs::UP5K || ctx->args.type == ArchArgs::UP3K) { std::string pullup_resistor = "100K"; if (cell.second->attrs.count(id_PULLUP_RESISTOR)) pullup_resistor = cell.second->attrs.at(id_PULLUP_RESISTOR).as_string(); NPNR_ASSERT(pullup_resistor == "100K" || pullup_resistor == "10K" || pullup_resistor == "6P8K" || pullup_resistor == "3P3K"); if (iez == 0) { set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_39", (!pullup) || (pullup_resistor != "100K")); set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_36", pullup && pullup_resistor == "3P3K"); set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_37", pullup && pullup_resistor == "6P8K"); set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_38", pullup && pullup_resistor == "10K"); } else if (iez == 1) { set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_35", (!pullup) || (pullup_resistor != "100K")); set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_32", pullup && pullup_resistor == "3P3K"); set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_33", pullup && pullup_resistor == "6P8K"); set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_34", pullup && pullup_resistor == "10K"); } } } else { NPNR_ASSERT(z == 0); // Only enable the actual LVDS buffer if input is used for something set_config(ti, config.at(y).at(x), "IoCtrl.LVDS", input_en); // Set both IO config for (int cz = 0; cz < 2; cz++) { auto ieren = get_ieren(bi, x, y, cz); int iex, iey, iez; std::tie(iex, iey, iez) = ieren; NPNR_ASSERT(iez != -1); pullup &= !input_en; /* If input is used, force disable pullups */ if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) { set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), true); set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup); } else { set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), false); set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup); } if (ctx->args.type == ArchArgs::UP5K || ctx->args.type == ArchArgs::UP3K) { if (iez == 0) { set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_39", !pullup); } else if (iez == 1) { set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_35", !pullup); } } } } } else if (cell.second->type == id_SB_GB) { if (cell.second->gbInfo.forPadIn) { Loc gb_loc = ctx->getBelLocation(bel); for (auto &glb : ci.global_network_info) { if ((gb_loc.x == glb.gb_x) && (gb_loc.y == glb.gb_y)) { extra_bits.push_back(std::make_tuple(glb.pi_eb_bank, glb.pi_eb_x, glb.pi_eb_y)); } } } } else if (cell.second->type == id_ICESTORM_RAM) { const BelInfoPOD &beli = ci.bel_data[bel.index]; int x = beli.x, y = beli.y; const TileInfoPOD &ti_ramt = bi.tiles_nonrouting[TILE_RAMT]; const TileInfoPOD &ti_ramb = bi.tiles_nonrouting[TILE_RAMB]; if (!(ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K)) { set_config(ti_ramb, config.at(y).at(x), "RamConfig.PowerUp", true); } bool negclk_r = get_param_or_def(ctx, cell.second.get(), id_NEG_CLK_R); bool negclk_w = get_param_or_def(ctx, cell.second.get(), id_NEG_CLK_W); int write_mode = get_param_or_def(ctx, cell.second.get(), id_WRITE_MODE); int read_mode = get_param_or_def(ctx, cell.second.get(), id_READ_MODE); if (ctx->args.type == ArchArgs::UP5K || ctx->args.type == ArchArgs::UP3K) { set_config(ti_ramb, config.at(y).at(x), "NegClk", negclk_r); set_config(ti_ramt, config.at(y + 1).at(x), "NegClk", negclk_w); } else { set_config(ti_ramb, config.at(y).at(x), "NegClk", negclk_w); set_config(ti_ramt, config.at(y + 1).at(x), "NegClk", negclk_r); } set_config(ti_ramt, config.at(y + 1).at(x), "RamConfig.CBIT_0", write_mode & 0x1); set_config(ti_ramt, config.at(y + 1).at(x), "RamConfig.CBIT_1", write_mode & 0x2); set_config(ti_ramt, config.at(y + 1).at(x), "RamConfig.CBIT_2", read_mode & 0x1); set_config(ti_ramt, config.at(y + 1).at(x), "RamConfig.CBIT_3", read_mode & 0x2); } else if (cell.second->type == id_SB_LED_DRV_CUR) { set_ec_cbit(config, ctx, get_ec_config(ctx->chip_info, cell.second->bel), "LED_DRV_CUR_EN", true, "IpConfig."); } else if (cell.second->type == id_SB_RGB_DRV) { const std::vector> rgb_params = { {"RGB0_CURRENT", 6}, {"RGB1_CURRENT", 6}, {"RGB2_CURRENT", 6}}; configure_extra_cell(config, ctx, cell.second.get(), rgb_params, true, std::string("IpConfig.")); set_ec_cbit(config, ctx, get_ec_config(ctx->chip_info, cell.second->bel), "RGB_DRV_EN", true, "IpConfig."); } else if (cell.second->type == id_SB_RGBA_DRV) { const std::vector> rgba_params = { {"CURRENT_MODE", 1}, {"RGB0_CURRENT", 6}, {"RGB1_CURRENT", 6}, {"RGB2_CURRENT", 6}}; configure_extra_cell(config, ctx, cell.second.get(), rgba_params, true, std::string("IpConfig.")); set_ec_cbit(config, ctx, get_ec_config(ctx->chip_info, cell.second->bel), "RGBA_DRV_EN", true, "IpConfig."); } else if (cell.second->type.in(id_SB_WARMBOOT, id_ICESTORM_LFOSC, id_SB_LEDDA_IP)) { // No config needed } else if (cell.second->type == id_SB_I2C) { bool sda_in_dly = !cell.second->attrs.count(id_SDA_INPUT_DELAYED) || cell.second->attrs[id_SDA_INPUT_DELAYED].as_bool(); bool sda_out_dly = !cell.second->attrs.count(id_SDA_OUTPUT_DELAYED) || cell.second->attrs[id_SDA_OUTPUT_DELAYED].as_bool(); set_ec_cbit(config, ctx, get_ec_config(ctx->chip_info, cell.second->bel), "SDA_INPUT_DELAYED", sda_in_dly, "IpConfig."); set_ec_cbit(config, ctx, get_ec_config(ctx->chip_info, cell.second->bel), "SDA_OUTPUT_DELAYED", sda_out_dly, "IpConfig."); set_ec_cbit(config, ctx, get_ec_config(ctx->chip_info, cell.second->bel), "I2C_ENABLE_0", true, "IpConfig."); set_ec_cbit(config, ctx, get_ec_config(ctx->chip_info, cell.second->bel), "I2C_ENABLE_1", true, "IpConfig."); } else if (cell.second->type == id_SB_SPI) { set_ec_cbit(config, ctx, get_ec_config(ctx->chip_info, cell.second->bel), "SPI_ENABLE_0", true, "IpConfig."); set_ec_cbit(config, ctx, get_ec_config(ctx->chip_info, cell.second->bel), "SPI_ENABLE_1", true, "IpConfig."); set_ec_cbit(config, ctx, get_ec_config(ctx->chip_info, cell.second->bel), "SPI_ENABLE_2", true, "IpConfig."); set_ec_cbit(config, ctx, get_ec_config(ctx->chip_info, cell.second->bel), "SPI_ENABLE_3", true, "IpConfig."); } else if (cell.second->type == id_ICESTORM_SPRAM) { const BelInfoPOD &beli = ci.bel_data[bel.index]; int x = beli.x, y = beli.y, z = beli.z; NPNR_ASSERT(ctx->args.type == ArchArgs::UP5K || ctx->args.type == ArchArgs::UP3K); if (x == 0 && y == 0) { const TileInfoPOD &ti_ipcon = bi.tiles_nonrouting[TILE_IPCON]; if (z == 1) { set_config(ti_ipcon, config.at(1).at(0), "IpConfig.CBIT_0", true); } else if (z == 2) { set_config(ti_ipcon, config.at(1).at(0), "IpConfig.CBIT_1", true); } else { NPNR_ASSERT(false); } } else if (x == 25 && y == 0) { const TileInfoPOD &ti_ipcon = bi.tiles_nonrouting[TILE_IPCON]; if (z == 3) { set_config(ti_ipcon, config.at(1).at(25), "IpConfig.CBIT_0", true); } else if (z == 4) { set_config(ti_ipcon, config.at(1).at(25), "IpConfig.CBIT_1", true); } else { NPNR_ASSERT(false); } } } else if (cell.second->type == id_ICESTORM_DSP) { const std::vector> mac16_params = {{"C_REG", 1}, {"A_REG", 1}, {"B_REG", 1}, {"D_REG", 1}, {"TOP_8x8_MULT_REG", 1}, {"BOT_8x8_MULT_REG", 1}, {"PIPELINE_16x16_MULT_REG1", 1}, {"PIPELINE_16x16_MULT_REG2", 1}, {"TOPOUTPUT_SELECT", 2}, {"TOPADDSUB_LOWERINPUT", 2}, {"TOPADDSUB_UPPERINPUT", 1}, {"TOPADDSUB_CARRYSELECT", 2}, {"BOTOUTPUT_SELECT", 2}, {"BOTADDSUB_LOWERINPUT", 2}, {"BOTADDSUB_UPPERINPUT", 1}, {"BOTADDSUB_CARRYSELECT", 2}, {"MODE_8x8", 1}, {"A_SIGNED", 1}, {"B_SIGNED", 1}}; configure_extra_cell(config, ctx, cell.second.get(), mac16_params, false, std::string("IpConfig.")); } else if (cell.second->type == id_ICESTORM_HFOSC) { std::vector> hfosc_params = {{"CLKHF_DIV", 2}}; if (ctx->args.type != ArchArgs::U4K && ctx->args.type != ArchArgs::U1K && ctx->args.type != ArchArgs::U2K) hfosc_params.push_back(std::pair("TRIM_EN", 1)); configure_extra_cell(config, ctx, cell.second.get(), hfosc_params, true, std::string("IpConfig.")); } else if (cell.second->type == id_ICESTORM_PLL) { const std::vector> pll_params = {{"DELAY_ADJMODE_FB", 1}, {"DELAY_ADJMODE_REL", 1}, {"DIVF", 7}, {"DIVQ", 3}, {"DIVR", 4}, {"ENABLE_ICEGATE_PORTA", 1}, {"ENABLE_ICEGATE_PORTB", 1}, {"FDA_FEEDBACK", 4}, {"FDA_RELATIVE", 4}, {"FEEDBACK_PATH", 3}, {"FILTER_RANGE", 3}, {"PLLOUT_SELECT_A", 2}, {"PLLOUT_SELECT_B", 2}, {"PLLTYPE", 3}, {"SHIFTREG_DIV_MODE", 2}, {"TEST_MODE", 1}}; configure_extra_cell(config, ctx, cell.second.get(), pll_params, false, std::string("PLL.")); } else { NPNR_ASSERT(false); } } // Set config bits in unused IO and RAM for (auto bel : ctx->getBels()) { if (ctx->bel_to_cell[bel.index] == nullptr && ctx->getBelType(bel) == id_SB_IO) { const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO]; const BelInfoPOD &beli = ci.bel_data[bel.index]; int x = beli.x, y = beli.y, z = beli.z; if (sb_io_used_by_pll_out.count(Loc(x, y, z))) { continue; } auto ieren = get_ieren(bi, x, y, z); int iex, iey, iez; std::tie(iex, iey, iez) = ieren; if (iez != -1) { // If IO is in LVDS pair, it will be configured by the other pair if (z == 1) { BelId lvds0 = ctx->getBelByLocation(Loc{x, y, 0}); const CellInfo *lvds0cell = ctx->getBoundBelCell(lvds0); if (lvds0cell != nullptr && lvds0cell->ioInfo.lvds) continue; } if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) { set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), true); set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), false); } else { set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), false); set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), false); } } } else if (ctx->bel_to_cell[bel.index] == nullptr && ctx->getBelType(bel) == id_ICESTORM_RAM) { const BelInfoPOD &beli = ci.bel_data[bel.index]; int x = beli.x, y = beli.y; const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_RAMB]; if ((ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K)) { set_config(ti, config.at(y).at(x), "RamConfig.PowerUp", true); } } } // Set other config bits for (int y = 0; y < ci.height; y++) { for (int x = 0; x < ci.width; x++) { TileType tile = tile_at(ctx, x, y); const TileInfoPOD &ti = bi.tiles_nonrouting[tile]; // set all ColBufCtrl bits (FIXME) bool setColBufCtrl = true; if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) { if (tile == TILE_RAMB || tile == TILE_RAMT) { setColBufCtrl = (y == 3 || y == 5 || y == 11 || y == 13); } else { setColBufCtrl = (y == 4 || y == 5 || y == 12 || y == 13); } } else if (ctx->args.type == ArchArgs::LP8K || ctx->args.type == ArchArgs::HX8K || ctx->args.type == ArchArgs::LP4K || ctx->args.type == ArchArgs::HX4K) { setColBufCtrl = (y == 8 || y == 9 || y == 24 || y == 25); } else if (ctx->args.type == ArchArgs::UP5K || ctx->args.type == ArchArgs::UP3K) { setColBufCtrl = (y == 4 || y == 5 || y == 14 || y == 15 || y == 26 || y == 27); } else if (ctx->args.type == ArchArgs::U4K || ctx->args.type == ArchArgs::U1K || ctx->args.type == ArchArgs::U2K) { setColBufCtrl = (y == 4 || y == 5 || y == 16 || y == 17); } else if (ctx->args.type == ArchArgs::LP384) { setColBufCtrl = false; } if (setColBufCtrl) { set_config(ti, config.at(y).at(x), "ColBufCtrl.glb_netwk_0", true); set_config(ti, config.at(y).at(x), "ColBufCtrl.glb_netwk_1", true); set_config(ti, config.at(y).at(x), "ColBufCtrl.glb_netwk_2", true); set_config(ti, config.at(y).at(x), "ColBufCtrl.glb_netwk_3", true); set_config(ti, config.at(y).at(x), "ColBufCtrl.glb_netwk_4", true); set_config(ti, config.at(y).at(x), "ColBufCtrl.glb_netwk_5", true); set_config(ti, config.at(y).at(x), "ColBufCtrl.glb_netwk_6", true); set_config(ti, config.at(y).at(x), "ColBufCtrl.glb_netwk_7", true); } // Weird UltraPlus bits if (tile == TILE_DSP0 || tile == TILE_DSP1 || tile == TILE_DSP2 || tile == TILE_DSP3 || tile == TILE_IPCON) { auto set_ipcon = [&](int lc_idx) { static const std::vector ip_dsp_lut_perm = { 4, 14, 15, 5, 6, 16, 17, 7, 3, 13, 12, 2, 1, 11, 10, 0, }; for (int i = 0; i < 16; i++) set_config(ti, config.at(y).at(x), "LC_" + std::to_string(lc_idx), ((i % 8) >= 4), ip_dsp_lut_perm.at(i)); if (tile == TILE_IPCON) set_config(ti, config.at(y).at(x), "Cascade.IPCON_LC0" + std::to_string(lc_idx) + "_inmux02_5", true); else set_config(ti, config.at(y).at(x), "Cascade.MULT" + std::to_string(int(tile - TILE_DSP0)) + "_LC0" + std::to_string(lc_idx) + "_inmux02_5", true); }; if ((ctx->args.type == ArchArgs::UP5K || ctx->args.type == ArchArgs::UP3K) && x == 25 && y == 14) { // Mystery bits not set in this one tile, other than to route through the DSP carry out if used if (ctx->getBoundBelCell(ctx->getBelByLocation(Loc(25, 10, 0)))) { set_ipcon(0); } } else { for (int lc_idx = 0; lc_idx < 8; lc_idx++) { set_ipcon(lc_idx); } } } } } // Write config out for (int y = 0; y < ci.height; y++) { for (int x = 0; x < ci.width; x++) { TileType tile = tile_at(ctx, x, y); if (tile == TILE_NONE) continue; out << tagTileType(tile); out << " " << x << " " << y << std::endl; for (auto row : config.at(y).at(x)) { for (auto col : row) { if (col == 1) out << "1"; else out << "0"; } out << std::endl; } out << std::endl; } } // Write RAM init data for (auto &cell : ctx->cells) { if (cell.second->bel != BelId()) { if (cell.second->type == id_ICESTORM_RAM) { const BelInfoPOD &beli = ci.bel_data[cell.second->bel.index]; int x = beli.x, y = beli.y; out << ".ram_data " << x << " " << y << std::endl; for (int w = 0; w < 16; w++) { std::vector bits(256); Property init = get_or_default(cell.second->params, ctx->id(std::string("INIT_") + get_hexdigit(w)), Property(0, 256)); for (size_t i = 0; i < init.str.size(); i++) { bool val = (init.str.at(i) == Property::State::S1); bits.at(i) = val; } for (int i = bits.size() - 4; i >= 0; i -= 4) { int c = bits.at(i) + (bits.at(i + 1) << 1) + (bits.at(i + 2) << 2) + (bits.at(i + 3) << 3); out << char(std::tolower(get_hexdigit(c))); } out << std::endl; } out << std::endl; } } } // Write extra-bits for (auto eb : extra_bits) out << ".extra_bit " << std::get<0>(eb) << " " << std::get<1>(eb) << " " << std::get<2>(eb) << std::endl; // Write symbols // const bool write_symbols = 1; for (auto wire : ctx->getWires()) { NetInfo *net = ctx->getBoundWireNet(wire); if (net != nullptr) out << ".sym " << wire.index << " " << net->name.str(ctx) << std::endl; } } void read_config(Context *ctx, std::istream &in, chipconfig_t &config) { constexpr size_t line_buf_size = 65536; char buffer[line_buf_size]; int tile_x = -1, tile_y = -1, line_nr = -1; while (1) { in.getline(buffer, line_buf_size); if (buffer[0] == '.') { line_nr = -1; const char *tok = strtok(buffer, " \t\r\n"); if (!strcmp(tok, ".device")) { std::string config_device = strtok(nullptr, " \t\r\n"); std::string expected; switch (ctx->args.type) { case ArchArgs::LP384: expected = "384"; break; case ArchArgs::HX1K: case ArchArgs::LP1K: expected = "1k"; break; case ArchArgs::HX4K: case ArchArgs::LP4K: case ArchArgs::HX8K: case ArchArgs::LP8K: expected = "8k"; break; case ArchArgs::UP3K: case ArchArgs::UP5K: expected = "5k"; break; case ArchArgs::U1K: case ArchArgs::U2K: case ArchArgs::U4K: expected = "u4k"; break; default: log_error("unsupported device type\n"); } if (expected != config_device) log_error("device type does not match\n"); } else if (!strcmp(tok, ".io_tile") || !strcmp(tok, ".logic_tile") || !strcmp(tok, ".ramb_tile") || !strcmp(tok, ".ramt_tile") || !strcmp(tok, ".ipcon_tile") || !strcmp(tok, ".dsp0_tile") || !strcmp(tok, ".dsp1_tile") || !strcmp(tok, ".dsp2_tile") || !strcmp(tok, ".dsp3_tile")) { line_nr = 0; tile_x = atoi(strtok(nullptr, " \t\r\n")); tile_y = atoi(strtok(nullptr, " \t\r\n")); TileType tile = tile_at(ctx, tile_x, tile_y); if (tok != tagTileType(tile)) log_error("Wrong tile type for specified position\n"); } else if (!strcmp(tok, ".extra_bit")) { /* int b = atoi(strtok(nullptr, " \t\r\n")); int x = atoi(strtok(nullptr, " \t\r\n")); int y = atoi(strtok(nullptr, " \t\r\n")); std::tuple key(b, x, y); extra_bits.insert(key); */ } else if (!strcmp(tok, ".sym")) { int wireIndex = atoi(strtok(nullptr, " \t\r\n")); const char *name = strtok(nullptr, " \t\r\n"); IdString netName = ctx->id(name); if (ctx->nets.find(netName) == ctx->nets.end()) { ctx->createNet(netName); } WireId wire; wire.index = wireIndex; ctx->bindWire(wire, ctx->nets.at(netName).get(), STRENGTH_WEAK); } } else if (line_nr >= 0 && strlen(buffer) > 0) { if (line_nr > int(config.at(tile_y).at(tile_x).size() - 1)) log_error("Invalid data in input asc file"); for (int i = 0; buffer[i] == '0' || buffer[i] == '1'; i++) config.at(tile_y).at(tile_x).at(line_nr).at(i) = (buffer[i] == '1') ? 1 : 0; line_nr++; } if (in.eof()) break; } } bool read_asc(Context *ctx, std::istream &in) { try { // [y][x][row][col] const ChipInfoPOD &ci = *ctx->chip_info; const BitstreamInfoPOD &bi = *ci.bits_info; chipconfig_t config; config.resize(ci.height); for (int y = 0; y < ci.height; y++) { config.at(y).resize(ci.width); for (int x = 0; x < ci.width; x++) { TileType tile = tile_at(ctx, x, y); int rows = bi.tiles_nonrouting[tile].rows; int cols = bi.tiles_nonrouting[tile].cols; config.at(y).at(x).resize(rows, std::vector(cols)); } } read_config(ctx, in, config); // Set pips for (auto pip : ctx->getPips()) { const PipInfoPOD &pi = ci.pip_data[pip.index]; const SwitchInfoPOD &swi = bi.switches[pi.switch_index]; bool isUsed = true; for (int i = 0; i < swi.num_bits; i++) { bool val = (pi.switch_mask & (1 << ((swi.num_bits - 1) - i))) != 0; int8_t cbit = config.at(swi.y).at(swi.x).at(swi.cbits[i].row).at(swi.cbits[i].col); isUsed &= !(bool(cbit) ^ val); } if (isUsed) { NetInfo *net = ctx->wire_to_net[pi.dst]; if (net != nullptr) { WireId wire; wire.index = pi.dst; ctx->unbindWire(wire); ctx->bindPip(pip, net, STRENGTH_WEAK); } } } for (auto bel : ctx->getBels()) { if (ctx->getBelType(bel) == id_ICESTORM_LC) { const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_LOGIC]; const BelInfoPOD &beli = ci.bel_data[bel.index]; int x = beli.x, y = beli.y, z = beli.z; std::vector lc(20, false); bool isUsed = false; for (int i = 0; i < 20; i++) { lc.at(i) = get_config(ti, config.at(y).at(x), "LC_" + std::to_string(z), i); isUsed |= lc.at(i); } bool neg_clk = get_config(ti, config.at(y).at(x), "NegClk"); isUsed |= neg_clk; bool carry_set = get_config(ti, config.at(y).at(x), "CarryInSet"); isUsed |= carry_set; if (isUsed) { std::unique_ptr created = create_ice_cell(ctx, id_ICESTORM_LC); IdString name = created->name; ctx->cells[name] = std::move(created); ctx->bindBel(bel, ctx->cells[name].get(), STRENGTH_WEAK); // TODO: Add port mapping to nets and assign values of properties } } if (ctx->getBelType(bel) == id_SB_IO) { const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO]; const BelInfoPOD &beli = ci.bel_data[bel.index]; int x = beli.x, y = beli.y, z = beli.z; bool isUsed = false; for (int i = 0; i < 6; i++) { isUsed |= get_config(ti, config.at(y).at(x), "IOB_" + std::to_string(z) + ".PINTYPE_" + std::to_string(i)); } bool neg_trigger = get_config(ti, config.at(y).at(x), "NegClk"); isUsed |= neg_trigger; if (isUsed) { std::unique_ptr created = create_ice_cell(ctx, id_SB_IO); IdString name = created->name; ctx->cells[name] = std::move(created); ctx->bindBel(bel, ctx->cells[name].get(), STRENGTH_WEAK); // TODO: Add port mapping to nets and assign values of properties } } } // Add cells that are without change in initial state of configuration for (auto &net : ctx->nets) { for (auto w : net.second->wires) { if (w.second.pip == PipId()) { WireId wire = w.first; for (auto belpin : ctx->getWireBelPins(wire)) { if (ctx->checkBelAvail(belpin.bel)) { if (ctx->getBelType(belpin.bel) == id_ICESTORM_LC) { std::unique_ptr created = create_ice_cell(ctx, id_ICESTORM_LC); IdString name = created->name; ctx->cells[name] = std::move(created); ctx->bindBel(belpin.bel, ctx->cells[name].get(), STRENGTH_WEAK); // TODO: Add port mapping to nets } if (ctx->getBelType(belpin.bel) == id_SB_IO) { std::unique_ptr created = create_ice_cell(ctx, id_SB_IO); IdString name = created->name; ctx->cells[name] = std::move(created); ctx->bindBel(belpin.bel, ctx->cells[name].get(), STRENGTH_WEAK); // TODO: Add port mapping to nets } if (ctx->getBelType(belpin.bel) == id_SB_GB) { std::unique_ptr created = create_ice_cell(ctx, id_SB_GB); IdString name = created->name; ctx->cells[name] = std::move(created); ctx->bindBel(belpin.bel, ctx->cells[name].get(), STRENGTH_WEAK); // TODO: Add port mapping to nets } if (ctx->getBelType(belpin.bel) == id_SB_WARMBOOT) { std::unique_ptr created = create_ice_cell(ctx, id_SB_WARMBOOT); IdString name = created->name; ctx->cells[name] = std::move(created); ctx->bindBel(belpin.bel, ctx->cells[name].get(), STRENGTH_WEAK); // TODO: Add port mapping to nets } if (ctx->getBelType(belpin.bel) == id_ICESTORM_LFOSC) { std::unique_ptr created = create_ice_cell(ctx, id_ICESTORM_LFOSC); IdString name = created->name; ctx->cells[name] = std::move(created); ctx->bindBel(belpin.bel, ctx->cells[name].get(), STRENGTH_WEAK); // TODO: Add port mapping to nets } } } } } } for (auto &cell : ctx->cells) { if (cell.second->bel != BelId()) { for (auto &port : cell.second->ports) { IdString pin = port.first; WireId wire = ctx->getBelPinWire(cell.second->bel, pin); if (wire != WireId()) { NetInfo *net = ctx->getBoundWireNet(wire); if (net != nullptr) { port.second.net = net; PortRef ref; ref.cell = cell.second.get(); ref.port = port.second.name; if (port.second.type == PORT_OUT) net->driver = ref; else port.second.user_idx = net->users.add(ref); } } } } } return true; } catch (log_execution_error_exception) { return false; } } NEXTPNR_NAMESPACE_END