From f5f7ef68649e873e6e5dc912544ed8abb5eb8fc3 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sun, 3 Oct 2021 19:33:07 +0100 Subject: [PATCH] mistral: Adding support for MLABs as memory Signed-off-by: gatecat --- mistral/arch.cc | 24 ++++++--- mistral/arch.h | 6 +++ mistral/archdefs.h | 5 ++ mistral/bitstream.cc | 50 +++++++++++++++++-- mistral/constids.inc | 10 ++++ mistral/lab.cc | 115 +++++++++++++++++++++++++++++++++++++++---- mistral/pack.cc | 40 +++++++++++++++ mistral/pins.cc | 6 ++- 8 files changed, 235 insertions(+), 21 deletions(-) diff --git a/mistral/arch.cc b/mistral/arch.cc index 045625a3..a6146fcb 100644 --- a/mistral/arch.cc +++ b/mistral/arch.cc @@ -152,11 +152,12 @@ IdStringList Arch::getBelName(BelId bel) const bool Arch::isBelLocationValid(BelId bel) const { auto &data = bel_data(bel); - if (data.type == id_MISTRAL_COMB) { - return is_alm_legal(data.lab_data.lab, data.lab_data.alm) && check_lab_input_count(data.lab_data.lab); + if (data.type.in(id_MISTRAL_COMB, id_MISTRAL_MCOMB)) { + return is_alm_legal(data.lab_data.lab, data.lab_data.alm) && check_lab_input_count(data.lab_data.lab) && + check_mlab_groups(data.lab_data.lab); } else if (data.type == id_MISTRAL_FF) { return is_alm_legal(data.lab_data.lab, data.lab_data.alm) && check_lab_input_count(data.lab_data.lab) && - is_lab_ctrlset_legal(data.lab_data.lab); + is_lab_ctrlset_legal(data.lab_data.lab) && check_mlab_groups(data.lab_data.lab); } return true; } @@ -164,7 +165,7 @@ bool Arch::isBelLocationValid(BelId bel) const void Arch::update_bel(BelId bel) { auto &data = bel_data(bel); - if (data.type == id_MISTRAL_COMB || data.type == id_MISTRAL_FF) { + if (data.type.in(id_MISTRAL_COMB, id_MISTRAL_MCOMB, id_MISTRAL_FF)) { update_alm_input_count(data.lab_data.lab, data.lab_data.alm); } } @@ -249,6 +250,8 @@ bool Arch::isValidBelForCellType(IdString cell_type, BelId bel) const IdString bel_type = getBelType(bel); if (bel_type == id_MISTRAL_COMB) return is_comb_cell(cell_type); + else if (bel_type == id_MISTRAL_MCOMB) + return is_comb_cell(cell_type) || (cell_type == id_MISTRAL_MLAB); else if (bel_type == id_MISTRAL_IO) return is_io_cell(cell_type); else if (bel_type == id_MISTRAL_CLKENA) @@ -259,7 +262,7 @@ bool Arch::isValidBelForCellType(IdString cell_type, BelId bel) const BelBucketId Arch::getBelBucketForCellType(IdString cell_type) const { - if (is_comb_cell(cell_type)) + if (is_comb_cell(cell_type) || cell_type == id_MISTRAL_MLAB) return id_MISTRAL_COMB; else if (is_io_cell(cell_type)) return id_MISTRAL_IO; @@ -269,6 +272,15 @@ BelBucketId Arch::getBelBucketForCellType(IdString cell_type) const return cell_type; } +BelBucketId Arch::getBelBucketForBel(BelId bel) const +{ + IdString bel_type = getBelType(bel); + if (bel_type == id_MISTRAL_MCOMB) + return id_MISTRAL_COMB; + else + return bel_type; +} + BelId Arch::bel_by_block_idx(int x, int y, IdString type, int block_index) const { auto &bels = bels_by_tile.at(pos2idx(x, y)); @@ -380,7 +392,7 @@ void Arch::assignArchInfo() { for (auto &cell : cells) { CellInfo *ci = cell.second.get(); - if (is_comb_cell(ci->type)) + if (is_comb_cell(ci->type) || ci->type == id_MISTRAL_MLAB) assign_comb_info(ci); else if (ci->type == id_MISTRAL_FF) assign_ff_info(ci); diff --git a/mistral/arch.h b/mistral/arch.h index a808f69d..77a35caf 100644 --- a/mistral/arch.h +++ b/mistral/arch.h @@ -437,6 +437,7 @@ struct Arch : BaseArch bool isValidBelForCellType(IdString cell_type, BelId bel) const override; BelBucketId getBelBucketForCellType(IdString cell_type) const override; + BelBucketId getBelBucketForBel(BelId bel) const override; // ------------------------------------------------- @@ -469,6 +470,7 @@ struct Arch : BaseArch bool is_alm_legal(uint32_t lab, uint8_t alm) const; // lab.cc bool is_lab_ctrlset_legal(uint32_t lab) const; // lab.cc bool check_lab_input_count(uint32_t lab) const; // lab.cc + bool check_mlab_groups(uint32_t lab) const; // lab.cc void assign_comb_info(CellInfo *cell) const; // lab.cc void assign_ff_info(CellInfo *cell) const; // lab.cc @@ -480,6 +482,10 @@ struct Arch : BaseArch uint64_t compute_lut_mask(uint32_t lab, uint8_t alm); // lab.cc + // Keeping track of unique MLAB write ports to assign them indices + dict get_mlab_key(const CellInfo *cell, bool include_raddr = false) const; // lab.cc + mutable idict> mlab_groups; + // ------------------------------------------------- bool is_io_cell(IdString cell_type) const; // io.cc diff --git a/mistral/archdefs.h b/mistral/archdefs.h index 8b4256ab..f4040398 100644 --- a/mistral/archdefs.h +++ b/mistral/archdefs.h @@ -195,6 +195,11 @@ struct ArchCellInfo : BaseClusterInfo bool is_carry, is_shared, is_extended; bool carry_start, carry_end; + + // MLABs with compatible write ports have this set to the same non-negative integer. -1 means this isn't a + // MLAB + int mlab_group; + ControlSig wclk, we; } combInfo; struct { diff --git a/mistral/bitstream.cc b/mistral/bitstream.cc index f0e9c003..e8c4dba7 100644 --- a/mistral/bitstream.cc +++ b/mistral/bitstream.cc @@ -226,11 +226,33 @@ struct MistralBitgen std::all_of(ffs.begin(), ffs.end(), [](CellInfo *c) { return !c; })) return false; + bool is_lutram = + (luts[0] && luts[0]->combInfo.mlab_group != -1) || (luts[1] && luts[1]->combInfo.mlab_group != -1); + auto pos = alm_data.lut_bels[0].pos; - // Combinational mode - TODO: flop feedback - cv->bmux_m_set(block_type, pos, CycloneV::MODE, alm, alm_data.l6_mode ? CycloneV::L6 : CycloneV::L5); - // LUT function - cv->bmux_r_set(block_type, pos, CycloneV::LUT_MASK, alm, ctx->compute_lut_mask(lab, alm)); + if (is_lutram) { + for (int i = 0; i < 10; i++) { + // Many MLAB settings apply to the whole LAB, not just the ALM + cv->bmux_m_set(block_type, pos, CycloneV::MODE, i, CycloneV::RAM); + cv->bmux_n_set(block_type, pos, CycloneV::T_FEEDBACK_SEL, i, 1); + } + cv->bmux_r_set(block_type, pos, CycloneV::LUT_MASK, alm, 0xFFFFFFFFFFFFFFFFULL); // TODO: LUTRAM init + cv->bmux_b_set(block_type, pos, CycloneV::BPKREG1, alm, true); + cv->bmux_b_set(block_type, pos, CycloneV::TPKREG0, alm, true); + cv->bmux_m_set(block_type, pos, CycloneV::MCRG_VOLTAGE, 0, CycloneV::VCCL); + cv->bmux_b_set(block_type, pos, CycloneV::RAM_DIS, 0, false); + cv->bmux_b_set(block_type, pos, CycloneV::WRITE_EN, 0, true); + cv->bmux_n_set(block_type, pos, CycloneV::WRITE_PULSE_LENGTH, 0, 650); // picoseconds, presumably + // TODO: understand how these enables really work + cv->bmux_b_set(block_type, pos, CycloneV::EN2_EN, 0, false); + cv->bmux_b_set(block_type, pos, CycloneV::EN_SCLK_LOAD_WHAT, 0, true); + cv->bmux_m_set(block_type, pos, CycloneV::SCLR_MUX, 0, CycloneV::GIN2); + } else { + // Combinational mode - TODO: flop feedback + cv->bmux_m_set(block_type, pos, CycloneV::MODE, alm, alm_data.l6_mode ? CycloneV::L6 : CycloneV::L5); + // LUT function + cv->bmux_r_set(block_type, pos, CycloneV::LUT_MASK, alm, ctx->compute_lut_mask(lab, alm)); + } // DFF/LUT output selection const std::array mux_settings{CycloneV::TDFF0, CycloneV::TDFF1, CycloneV::TDFF1L, CycloneV::BDFF0, CycloneV::BDFF1, CycloneV::BDFF1L}; @@ -310,6 +332,26 @@ struct MistralBitgen cv->bmux_b_set(block_type, pos, CycloneV::SLOAD_INV, 0, ff->ffInfo.ctrlset.sload.inverted); } } + if (is_lutram) { + for (int i = 0; i < 2; i++) { + CellInfo *lut = luts[i]; + if (!lut || lut->combInfo.mlab_group == -1) + continue; + int ce_idx = alm_data.clk_ena_idx[1]; + cv->bmux_m_set(block_type, pos, clk_sel[1], alm, clk_choice[ce_idx]); + if (lut->combInfo.wclk.inverted) + cv->bmux_b_set(block_type, pos, clk_inv[ce_idx], 0, true); + if (get_net_or_empty(lut, id_A1EN) != nullptr) { + cv->bmux_b_set(block_type, pos, en_en[ce_idx], 0, true); + cv->bmux_b_set(block_type, pos, en_ninv[ce_idx], 0, lut->combInfo.we.inverted); + } else { + cv->bmux_b_set(block_type, pos, en_en[ce_idx], 0, false); + } + // TODO: understand what these are doing + cv->bmux_b_set(block_type, pos, sclr_dis[0], alm, true); + cv->bmux_b_set(block_type, pos, sclr_dis[1], alm, true); + } + } return true; } diff --git a/mistral/constids.inc b/mistral/constids.inc index 393989cd..693f2572 100644 --- a/mistral/constids.inc +++ b/mistral/constids.inc @@ -1,4 +1,6 @@ X(MISTRAL_COMB) +X(MISTRAL_MCOMB) + X(MISTRAL_FF) X(LAB) X(MLAB) @@ -84,3 +86,11 @@ X(WA3) X(WA4) X(WCLK) X(WE) + +X(MISTRAL_MLAB) +X(CLK1) +X(A1EN) +X(A1DATA) +X(B1DATA) +X(WCLK_INV) +X(WE_INV) diff --git a/mistral/lab.cc b/mistral/lab.cc index 2874aac3..e78a815c 100644 --- a/mistral/lab.cc +++ b/mistral/lab.cc @@ -84,7 +84,8 @@ static void create_alm(Arch *arch, int x, int y, int z, uint32_t lab_idx) share_out = arch->add_wire(x, y, arch->id(stringf("SHARE[%d]", z * 2 + i))); } - BelId bel = arch->add_bel(x, y, arch->id(stringf("ALM%d_COMB%d", z, i)), id_MISTRAL_COMB); + BelId bel = arch->add_bel(x, y, arch->id(stringf("ALM%d_COMB%d", z, i)), + lab.is_mlab ? id_MISTRAL_MCOMB : id_MISTRAL_COMB); // LUT/MUX inputs arch->add_bel_pin(bel, id_A, PORT_IN, arch->get_port(block_type, x, y, z, CycloneV::A)); arch->add_bel_pin(bel, id_B, PORT_IN, arch->get_port(block_type, x, y, z, CycloneV::B)); @@ -244,6 +245,23 @@ bool Arch::is_comb_cell(IdString cell_type) const } } +dict Arch::get_mlab_key(const CellInfo *cell, bool include_raddr) const +{ + dict key; + for (auto &port : cell->ports) { + if (port.first.in(id_A1DATA, id_B1DATA)) + continue; + if (!include_raddr && port.first.str(this).find("B1ADDR") == 0) + continue; + key[port.first] = port.second.net ? port.second.net->name : IdString(); + } + if (cell->pin_data.count(id_CLK1) && cell->pin_data.at(id_CLK1).state == PIN_INV) + key[id_WCLK_INV] = id_Y; + if (cell->pin_data.count(id_A1EN) && cell->pin_data.at(id_A1EN).state == PIN_INV) + key[id_WE_INV] = id_Y; + return key; +} + void Arch::assign_comb_info(CellInfo *cell) const { cell->combInfo.is_carry = false; @@ -252,8 +270,19 @@ void Arch::assign_comb_info(CellInfo *cell) const cell->combInfo.carry_start = false; cell->combInfo.carry_end = false; cell->combInfo.chain_shared_input_count = 0; + cell->combInfo.mlab_group = -1; - if (cell->type == id_MISTRAL_ALUT_ARITH) { + if (cell->type == id_MISTRAL_MLAB) { + cell->combInfo.wclk = get_ctrlsig(getCtx(), cell, id_CLK1); + cell->combInfo.we = get_ctrlsig(getCtx(), cell, id_A1EN, true); + cell->combInfo.lut_input_count = 5; + cell->combInfo.lut_bits_count = 32; + for (int i = 0; i < 5; i++) + cell->combInfo.lut_in[i] = get_net_or_empty(cell, id(stringf("B1ADDR[%d]", i))); + auto key = get_mlab_key(cell); + cell->combInfo.mlab_group = mlab_groups(key); + cell->combInfo.comb_out = get_net_or_empty(cell, id_B1DATA); + } else if (cell->type == id_MISTRAL_ALUT_ARITH) { cell->combInfo.is_carry = true; cell->combInfo.lut_input_count = 5; cell->combInfo.lut_bits_count = 32; @@ -477,8 +506,9 @@ void Arch::update_alm_input_count(uint32_t lab, uint8_t alm) break; } } - if (shared_lut_inputs >= 2) { - // only 2 inputs have guaranteed sharing, without routeability based LUT permutation at least + if (shared_lut_inputs >= 2 && luts[0]->combInfo.mlab_group == -1) { + // only 2 inputs have guaranteed sharing in non-MLAB mode, without routeability based LUT permutation at + // least break; } } @@ -513,6 +543,38 @@ bool Arch::check_lab_input_count(uint32_t lab) const return (count <= 42); } +bool Arch::check_mlab_groups(uint32_t lab) const +{ + auto &lab_data = labs.at(lab); + if (!lab_data.is_mlab) + return true; + int found_group = -2; + for (const auto &alm_data : lab_data.alms) { + std::array luts{getBoundBelCell(alm_data.lut_bels[0]), + getBoundBelCell(alm_data.lut_bels[1])}; + for (const CellInfo *lut : luts) { + if (!lut) + continue; + if (found_group == -2) + found_group = lut->combInfo.mlab_group; + else if (found_group != lut->combInfo.mlab_group) + return false; + } + } + if (found_group >= 0) { + for (const auto &alm_data : lab_data.alms) { + std::array ffs{ + getBoundBelCell(alm_data.ff_bels[0]), getBoundBelCell(alm_data.ff_bels[1]), + getBoundBelCell(alm_data.ff_bels[2]), getBoundBelCell(alm_data.ff_bels[3])}; + for (const CellInfo *ff : ffs) { + if (ff) + return false; // be conservative and don't allow LUTRAMs and FFs together + } + } + } + return true; +} + namespace { bool check_assign_sig(ControlSig &sig_set, const ControlSig &sig) { @@ -564,7 +626,6 @@ struct LabCtrlSetWorker const CellInfo *ff = arch->getBoundBelCell(arch->labs.at(lab).alms.at(alm).ff_bels.at(i)); if (ff == nullptr) continue; - if (!check_assign_sig(clk, ff->ffInfo.ctrlset.clk)) return false; if (!check_assign_sig(sload, ff->ffInfo.ctrlset.sload)) @@ -647,6 +708,19 @@ void Arch::assign_control_sets(uint32_t lab) for (uint8_t alm = 0; alm < 10; alm++) { auto &alm_data = lab_data.alms.at(alm); + if (lab_data.is_mlab) { + for (uint8_t i = 0; i < 2; i++) { + BelId lut_bel = alm_data.lut_bels.at(i); + const CellInfo *lut = getBoundBelCell(lut_bel); + if (!lut || lut->combInfo.mlab_group == -1) + continue; + WireId wclk_wire = getBelPinWire(lut_bel, id_WCLK); + WireId we_wire = getBelPinWire(lut_bel, id_WE); + // Force use of CLK0/ENA0 for LUTRAMs. Might have to revisit if we ever support packing LUTRAMs and FFs + reserve_route(lab_data.clk_wires[0], wclk_wire); + reserve_route(lab_data.ena_wires[0], we_wire); + } + } for (uint8_t i = 0; i < 4; i++) { BelId ff_bel = alm_data.ff_bels.at(i); const CellInfo *ff = getBoundBelCell(ff_bel); @@ -658,7 +732,7 @@ void Arch::assign_control_sets(uint32_t lab) for (int j = 0; j < 3; j++) { if (ena_sig == worker.datain[ena_datain[j]]) { if (getCtx()->debug) { - log_info("Assigned CLK/ENA set %d to FF %s (%s)\n", i, nameOf(ff), getCtx()->nameOfBel(ff_bel)); + log_info("Assigned CLK/ENA set %d to FF %s (%s)\n", j, nameOf(ff), getCtx()->nameOfBel(ff_bel)); } // TODO: lock clock according to ENA choice, too, when we support two clocks per ALM reserve_route(lab_data.clk_wires[0], clk_wire); @@ -667,7 +741,6 @@ void Arch::assign_control_sets(uint32_t lab) break; } } - ControlSig aclr_sig = ff->ffInfo.ctrlset.aclr; WireId aclr_wire = getBelPinWire(ff_bel, id_ACLR); for (int j = 0; j < 2; j++) { @@ -707,6 +780,22 @@ static void assign_lut6_inputs(CellInfo *cell, int lut) cell->pin_data[log].bel_pins.push_back(phys_pins.at(phys_idx++)); } } + +static void assign_mlab_inputs(Context *ctx, CellInfo *cell, int lut) +{ + cell->pin_data[id_CLK1].bel_pins = {id_WCLK}; + cell->pin_data[id_A1EN].bel_pins = {id_WE}; + cell->pin_data[id_A1DATA].bel_pins = {(lut == 1) ? id_E1 : id_E0}; + cell->pin_data[id_B1DATA].bel_pins = {id_COMBOUT}; + cell->pin_data[id_A1EN].bel_pins = {id_WE}; + + std::array raddr_pins{id_A, id_B, id_C, id_D, id_F0}; + for (int i = 0; i < 5; i++) { + cell->pin_data[ctx->id(stringf("A1ADDR[%d]", i))].bel_pins = {ctx->id(stringf("WA%d", i))}; + cell->pin_data[ctx->id(stringf("B1ADDR[%d]", i))].bel_pins = {raddr_pins.at(i)}; + } +} + } // namespace void Arch::reassign_alm_inputs(uint32_t lab, uint8_t alm) @@ -720,16 +809,22 @@ void Arch::reassign_alm_inputs(uint32_t lab, uint8_t alm) std::array ffs{getBoundBelCell(alm_data.ff_bels[0]), getBoundBelCell(alm_data.ff_bels[1]), getBoundBelCell(alm_data.ff_bels[2]), getBoundBelCell(alm_data.ff_bels[3])}; + bool found_mlab = false; for (int i = 0; i < 2; i++) { - // Currently we treat LUT6s as a special case, as they never share inputs - if (luts[i] != nullptr && luts[i]->type == id_MISTRAL_ALUT6) { + // Currently we treat LUT6s and MLABs as a special case, as they never share inputs or have fixed mappings + if (!luts[i]) + continue; + if (luts[i]->type == id_MISTRAL_ALUT6) { alm_data.l6_mode = true; NPNR_ASSERT(luts[1 - i] == nullptr); // only allow one LUT6 per ALM and no other LUTs assign_lut6_inputs(luts[i], i); + } else if (luts[i]->type == id_MISTRAL_MLAB) { + found_mlab = true; + assign_mlab_inputs(getCtx(), luts[i], i); } } - if (!alm_data.l6_mode) { + if (!alm_data.l6_mode && !found_mlab) { // In L5 mode; which is what we use in this case // - A and B are shared // - C, E0, and F0 are exclusive to the top LUT5 secion diff --git a/mistral/pack.cc b/mistral/pack.cc index 98ab22bf..ef3b3887 100644 --- a/mistral/pack.cc +++ b/mistral/pack.cc @@ -342,12 +342,52 @@ struct MistralPacker } } + void constrain_lutram() + { + // We form clusters based on both read and write address; as both being the same makes it more likely these + // cells should be packed together, too. + // This makes things easier for the placement legaliser to deal with RAM in LAB-compatible blocks without + // over-constraining things + idict> mlab_keys; + std::vector> mlab_groups; + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->type != id_MISTRAL_MLAB) + continue; + auto key = ctx->get_mlab_key(ci, true); + int key_idx = mlab_keys(key); + if (key_idx >= int(mlab_groups.size())) + mlab_groups.resize(key_idx + 1); + mlab_groups.at(key_idx).push_back(ci); + } + // Combine into clusters + size_t cluster_size = 20; + for (auto &group : mlab_groups) { + for (size_t i = 0; i < group.size(); i++) { + CellInfo *ci = group.at(i); + CellInfo *base = group.at((i / cluster_size) * cluster_size); + int cell_index = int(i) % cluster_size; + int alm = i / 2; + int alm_cell = i % 2; + ci->constr_abs_z = true; + ci->constr_z = alm * 6 + alm_cell; + if (cell_index != 0) { + // Not the root of a cluster + base->constr_children.push_back(ci); + ci->constr_x = 0; + ci->constr_y = 0; + } + } + } + } + void run() { init_constant_nets(); pack_constants(); pack_io(); constrain_carries(); + constrain_lutram(); } }; }; // namespace diff --git a/mistral/pins.cc b/mistral/pins.cc index 51cb83c4..120ccbb2 100644 --- a/mistral/pins.cc +++ b/mistral/pins.cc @@ -44,7 +44,11 @@ const dict Arch::cell_pins_db = { {id_SDATA, PINSTYLE_DEDI}, {id_DATAIN, PINSTYLE_INP}, }}, -}; + {id_MISTRAL_MLAB, + { + {id_CLK1, PINSTYLE_CLK}, + {id_A1EN, PINSTYLE_CE}, + }}}; CellPinStyle Arch::get_cell_pin_style(const CellInfo *cell, IdString port) const {