diff --git a/machxo2/bitstream.cc b/machxo2/bitstream.cc index 92e5e732..3b44785d 100644 --- a/machxo2/bitstream.cc +++ b/machxo2/bitstream.cc @@ -154,6 +154,53 @@ struct MachXO2Bitgen return bv; } + inline int chtohex(char c) + { + static const std::string hex = "0123456789ABCDEF"; + return hex.find(std::toupper(c)); + } + + std::vector parse_init_str(const Property &p, int length, const char *cellname) + { + // Parse a string that may be binary or hex + std::vector result; + result.resize(length, false); + if (p.is_string) { + std::string str = p.as_string(); + NPNR_ASSERT(str.substr(0, 2) == "0x"); + // Lattice style hex string + if (int(str.length()) > (2 + ((length + 3) / 4))) + log_error("hex string value too long, expected up to %d chars and found %d.\n", + (2 + ((length + 3) / 4)), int(str.length())); + for (int i = 0; i < int(str.length()) - 2; i++) { + char c = str.at((str.size() - i) - 1); + int nibble = chtohex(c); + if (nibble == (int)std::string::npos) + log_error("hex string has invalid char '%c' at position %d.\n", c, i); + result.at(i * 4) = nibble & 0x1; + if (i * 4 + 1 < length) + result.at(i * 4 + 1) = nibble & 0x2; + if (i * 4 + 2 < length) + result.at(i * 4 + 2) = nibble & 0x4; + if (i * 4 + 3 < length) + result.at(i * 4 + 3) = nibble & 0x8; + } + } else { + result = p.as_bits(); + result.resize(length, false); + } + return result; + } + + inline uint16_t bit_reverse(uint16_t x, int size) + { + uint16_t y = 0; + for (int i = 0; i < size; i++) + if (x & (1 << i)) + y |= (1 << ((size - 1) - i)); + return y; + } + // Get the PIC tile corresponding to a PIO bel std::string get_pic_tile(BelId bel) { @@ -184,6 +231,21 @@ struct MachXO2Bitgen } } + // Get the list of tiles corresponding to a blockram + std::vector get_bram_tiles(BelId bel) + { + std::vector tiles; + Loc loc = ctx->getBelLocation(bel); + + static const std::set ebr0 = {"EBR0", "EBR0_END", "EBR0_10K", "EBR0_END_10K"}; + static const std::set ebr1 = {"EBR1", "EBR1_10K"}; + static const std::set ebr2 = {"EBR2", "EBR2_END", "EBR2_10K", "EBR2_END_10K"}; + tiles.push_back(ctx->get_tile_by_type_loc(loc.y, loc.x, ebr0)); + tiles.push_back(ctx->get_tile_by_type_loc(loc.y, loc.x+1, ebr1)); + tiles.push_back(ctx->get_tile_by_type_loc(loc.y, loc.x+2, ebr2)); + return tiles; + } + // Get the list of tiles corresponding to a PLL std::vector get_pll_tiles(BelId bel) { @@ -363,6 +425,78 @@ struct MachXO2Bitgen } } + void write_bram(CellInfo *ci) + { + TileGroup tg; + tg.tiles = get_bram_tiles(ci->bel); + std::string ebr = "EBR"; + + if (ci->ramInfo.is_pdp) { + tg.config.add_enum(ebr + ".MODE", "PDPW8KC"); + tg.config.add_enum(ebr + ".PDPW8KC.DATA_WIDTH_R", intstr_or_default(ci->params, id_DATA_WIDTH_B, "18")); + tg.config.add_enum(ebr + ".FIFO8KB.DATA_WIDTH_W", "18"); // default for PDPW8KC + } else { + tg.config.add_enum(ebr + ".MODE", "DP8KC"); + tg.config.add_enum(ebr + ".DP8KC.DATA_WIDTH_A", intstr_or_default(ci->params, id_DATA_WIDTH_A, "18")); + tg.config.add_enum(ebr + ".DP8KC.DATA_WIDTH_B", intstr_or_default(ci->params, id_DATA_WIDTH_B, "18")); + tg.config.add_enum(ebr + ".DP8KC.WRITEMODE_A", str_or_default(ci->params, id_WRITEMODE_A, "NORMAL")); + tg.config.add_enum(ebr + ".DP8KC.WRITEMODE_B", str_or_default(ci->params, id_WRITEMODE_B, "NORMAL")); + } + + auto csd_a = str_to_bitvector(str_or_default(ci->params, id_CSDECODE_A, "0b000"), 3), + csd_b = str_to_bitvector(str_or_default(ci->params, id_CSDECODE_B, "0b000"), 3); + + tg.config.add_enum(ebr + ".REGMODE_A", str_or_default(ci->params, id_REGMODE_A, "NOREG")); + tg.config.add_enum(ebr + ".REGMODE_B", str_or_default(ci->params, id_REGMODE_B, "NOREG")); + + tg.config.add_enum(ebr + ".RESETMODE", str_or_default(ci->params, id_RESETMODE, "SYNC")); + tg.config.add_enum(ebr + ".ASYNC_RESET_RELEASE", str_or_default(ci->params, id_ASYNC_RESET_RELEASE, "SYNC")); + tg.config.add_enum(ebr + ".GSR", str_or_default(ci->params, id_GSR, "DISABLED")); + + tg.config.add_word(ebr + ".WID", int_to_bitvector(int_or_default(ci->attrs, id_WID, 0), 9)); + + // Invert CSDECODE bits to emulate inversion muxes on CSA/CSB signals + for (auto &port : {std::make_pair("CSA", std::ref(csd_a)), std::make_pair("CSB", std::ref(csd_b))}) { + for (int bit = 0; bit < 3; bit++) { + std::string sig = port.first + std::to_string(bit); + if (str_or_default(ci->params, ctx->id(sig + "MUX"), sig) == "INV") + port.second.at(bit) = !port.second.at(bit); + } + } + tg.config.add_enum(ebr + ".RSTAMUX", str_or_default(ci->params, id_RSTAMUX, "RSTA")); + tg.config.add_enum(ebr + ".RSTBMUX", str_or_default(ci->params, id_RSTBMUX, "RSTB")); + if (!ci->ramInfo.is_pdp) { + tg.config.add_enum(ebr + ".WEAMUX", str_or_default(ci->params, id_WEAMUX, "WEA")); + tg.config.add_enum(ebr + ".WEBMUX", str_or_default(ci->params, id_WEBMUX, "WEB")); + + } + std::reverse(csd_a.begin(), csd_a.end()); + std::reverse(csd_b.begin(), csd_b.end()); + + tg.config.add_word(ebr + ".CSDECODE_A", csd_a); + tg.config.add_word(ebr + ".CSDECODE_B", csd_b); + + std::vector init_data; + init_data.resize(1024, 0x0); + // INIT_00 .. INIT_1F + for (int i = 0; i <= 0x1F; i++) { + IdString param = ctx->idf("INITVAL_%02X", i); + auto value = parse_init_str(get_or_default(ci->params, param, Property(0)), 320, ci->name.c_str(ctx)); + for (int j = 0; j < 16; j++) { + // INIT parameter consists of 16 18-bit words with 2-bit padding + int ofs = 20 * j; + for (int k = 0; k < 18; k++) { + if (value.at(ofs + k)) + init_data.at(i * 32 + j * 2 + (k / 9)) |= (1 << (k % 9)); + } + } + } + int wid = int_or_default(ci->attrs, id_WID, 0); + NPNR_ASSERT(!cc.bram_data.count(wid)); + cc.bram_data[wid] = init_data; + cc.tilegroups.push_back(tg); + } + void write_pll(CellInfo *ci) { TileGroup tg; @@ -543,6 +677,8 @@ struct MachXO2Bitgen cc.tiles[ctx->get_tile_by_type("CFG1")].add_enum("OSCH.NOM_FREQ", freq); } else if (ci->type == id_DCCA) { write_dcc(ci); + } else if (ci->type == id_DP8KC) { + write_bram(ci); } else if (ci->type == id_EHXPLLJ) { write_pll(ci); } diff --git a/machxo2/pack.cc b/machxo2/pack.cc index 9f87ba0f..6728e41d 100644 --- a/machxo2/pack.cc +++ b/machxo2/pack.cc @@ -1012,6 +1012,107 @@ class MachXO2Packer } } + // Pack EBR + void pack_ebr() + { + // Autoincrement WID (starting from 3 seems to match vendor behaviour?) + int wid = 3; + auto rename_bus = [&](CellInfo *c, const std::string &oldname, const std::string &newname, int width, + int oldoffset, int newoffset) { + for (int i = 0; i < width; i++) + c->renamePort(ctx->id(oldname + std::to_string(i + oldoffset)), + ctx->id(newname + std::to_string(i + newoffset))); + }; + auto rename_param = [&](CellInfo *c, const std::string &oldname, const std::string &newname) { + IdString o = ctx->id(oldname), n = ctx->id(newname); + if (!c->params.count(o)) + return; + c->params[n] = c->params[o]; + c->params.erase(o); + }; + /*for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + // Convert 36-bit PDP RAMs to regular 18-bit DP ones that match the Bel + if (ci->type == id_PDPW8KC) { + ci->params[id_DATA_WIDTH_A] = 18; // force PDP mode + ci->params.erase(id_DATA_WIDTH_W); + rename_bus(ci, "BE", "ADA", 4, 0, 0); + rename_bus(ci, "ADW", "ADA", 9, 0, 5); + rename_bus(ci, "ADR", "ADB", 14, 0, 0); + rename_bus(ci, "CSW", "CSA", 3, 0, 0); + rename_bus(ci, "CSR", "CSB", 3, 0, 0); + rename_bus(ci, "DI", "DIA", 18, 0, 0); + rename_bus(ci, "DI", "DIB", 18, 18, 0); + rename_bus(ci, "DO", "DOA", 18, 18, 0); + rename_bus(ci, "DO", "DOB", 18, 0, 0); + ci->renamePort(id_CLKW, id_CLKA); + ci->renamePort(id_CLKR, id_CLKB); + ci->renamePort(id_CEW, id_CEA); + ci->renamePort(id_CER, id_CEB); + ci->renamePort(id_OCER, id_OCEB); + rename_param(ci, "CLKWMUX", "CLKAMUX"); + if (str_or_default(ci->params, id_CLKAMUX) == "CLKW") + ci->params[id_CLKAMUX] = std::string("CLKA"); + rename_param(ci, "CLKRMUX", "CLKBMUX"); + if (str_or_default(ci->params, id_CLKBMUX) == "CLKR") + ci->params[id_CLKBMUX] = std::string("CLKB"); + rename_param(ci, "CSDECODE_W", "CSDECODE_A"); + rename_param(ci, "CSDECODE_R", "CSDECODE_B"); + std::string outreg = str_or_default(ci->params, id_REGMODE, "NOREG"); + ci->params[id_REGMODE_A] = outreg; + ci->params[id_REGMODE_B] = outreg; + ci->params.erase(id_REGMODE); + rename_param(ci, "DATA_WIDTH_R", "DATA_WIDTH_B"); + if (ci->ports.count(id_RST)) { + autocreate_empty_port(ci, id_RSTA); + autocreate_empty_port(ci, id_RSTB); + NetInfo *rst = ci->ports.at(id_RST).net; + ci->connectPort(id_RSTA, rst); + ci->connectPort(id_RSTB, rst); + ci->disconnectPort(id_RST); + ci->ports.erase(id_RST); + } + ci->type = id_DP8KC; + } + }*/ + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->type == id_DP8KC) { + // Add ports, even if disconnected, to ensure correct tie-offs + for (int i = 0; i < 13; i++) { + autocreate_empty_port(ci, ctx->id("ADA" + std::to_string(i))); + autocreate_empty_port(ci, ctx->id("ADB" + std::to_string(i))); + } + for (int i = 0; i < 9; i++) { + autocreate_empty_port(ci, ctx->id("DIA" + std::to_string(i))); + autocreate_empty_port(ci, ctx->id("DIB" + std::to_string(i))); + } + for (int i = 0; i < 3; i++) { + autocreate_empty_port(ci, ctx->id("CSA" + std::to_string(i))); + autocreate_empty_port(ci, ctx->id("CSB" + std::to_string(i))); + } + for (int i = 0; i < 3; i++) { + autocreate_empty_port(ci, ctx->id("CSA" + std::to_string(i))); + autocreate_empty_port(ci, ctx->id("CSB" + std::to_string(i))); + } + + autocreate_empty_port(ci, id_CLKA); + autocreate_empty_port(ci, id_CEA); + autocreate_empty_port(ci, id_OCEA); + autocreate_empty_port(ci, id_WEA); + autocreate_empty_port(ci, id_RSTA); + + autocreate_empty_port(ci, id_CLKB); + autocreate_empty_port(ci, id_CEB); + autocreate_empty_port(ci, id_OCEB); + autocreate_empty_port(ci, id_WEB); + autocreate_empty_port(ci, id_RSTB); + + ci->attrs[id_WID] = wid++; + } + } + } + // Preplace PLL void preplace_plls() { @@ -1408,6 +1509,7 @@ class MachXO2Packer print_logic_usage(); pack_io(); preplace_plls(); + pack_ebr(); pack_constants(); pack_dram(); pack_carries(); @@ -1510,6 +1612,18 @@ void Arch::assign_arch_info_for_cell(CellInfo *ci) ci->ffInfo.clk_sig = get_port_net(ci, id_CLK); ci->ffInfo.ce_sig = get_port_net(ci, id_CE); ci->ffInfo.lsr_sig = get_port_net(ci, id_LSR); + } else if (ci->type == id_DP8KC) { + ci->ramInfo.is_pdp = (int_or_default(ci->params, id_DATA_WIDTH_A, 0) == 18); + + // Output register mode (REGMODE_{A,B}). Valid options are 'NOREG' and 'OUTREG'. + std::string regmode_a = str_or_default(ci->params, id_REGMODE_A, "NOREG"); + if (regmode_a != "NOREG" && regmode_a != "OUTREG") + log_error("DP8KC %s has invalid REGMODE_A configuration '%s'\n", ci->name.c_str(this), regmode_a.c_str()); + std::string regmode_b = str_or_default(ci->params, id_REGMODE_B, "NOREG"); + if (regmode_b != "NOREG" && regmode_b != "OUTREG") + log_error("DP8KC %s has invalid REGMODE_B configuration '%s'\n", ci->name.c_str(this), regmode_b.c_str()); + ci->ramInfo.is_output_a_registered = regmode_a == "OUTREG"; + ci->ramInfo.is_output_b_registered = regmode_b == "OUTREG"; } }