diff options
author | David Shah <dave@ds0.me> | 2020-05-01 08:17:52 +0100 |
---|---|---|
committer | David Shah <dave@ds0.me> | 2020-05-01 08:17:52 +0100 |
commit | 0faf07aac851692c523ea7824d6882245898c6e8 (patch) | |
tree | 771ddfa0f36e910c72b92d870ec639d9b0f9820f /ecp5 | |
parent | 5c6b2cbafef7435bd697cedf30436bf16e70dc15 (diff) | |
parent | 84327b634c09b67ef965e3056dd29a7ee4169039 (diff) | |
download | nextpnr-0faf07aac851692c523ea7824d6882245898c6e8.tar.gz nextpnr-0faf07aac851692c523ea7824d6882245898c6e8.tar.bz2 nextpnr-0faf07aac851692c523ea7824d6882245898c6e8.zip |
Merge branch 'rschlaikjer-rschlaikjer-mult18x18-register-timings'
Diffstat (limited to 'ecp5')
-rw-r--r-- | ecp5/arch.cc | 69 | ||||
-rw-r--r-- | ecp5/archdefs.h | 6 | ||||
-rw-r--r-- | ecp5/pack.cc | 56 |
3 files changed, 125 insertions, 6 deletions
diff --git a/ecp5/arch.cc b/ecp5/arch.cc index ab24842e..db043f35 100644 --- a/ecp5/arch.cc +++ b/ecp5/arch.cc @@ -852,10 +852,12 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort } else if (cell->type == id_DP16KD) { return false; } else if (cell->type == id_MULT18X18D) { + if (cell->multInfo.is_clocked) + return false; std::string fn = fromPort.str(this), tn = toPort.str(this); if (fn.size() > 1 && (fn.front() == 'A' || fn.front() == 'B') && std::isdigit(fn.at(1))) { if (tn.size() > 1 && tn.front() == 'P' && std::isdigit(tn.at(1))) - return getDelayFromTimingDatabase(id_MULT18X18D_REGS_NONE, id(std::string("") + fn.front()), id_P, + return getDelayFromTimingDatabase(cell->multInfo.timing_id, id(std::string("") + fn.front()), id_P, delay); } return false; @@ -938,12 +940,33 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in } else if (cell->type == id_MULT18X18D) { if (port == id_CLK0 || port == id_CLK1 || port == id_CLK2 || port == id_CLK3) return TMG_CLOCK_INPUT; + if (port == id_CE0 || port == id_CE1 || port == id_CE2 || port == id_CE3 || port == id_RST0 || + port == id_RST1 || port == id_RST2 || port == id_RST3 || port == id_SIGNEDA || port == id_SIGNEDB) { + if (cell->multInfo.is_clocked) { + clockInfoCount = 1; + return TMG_REGISTER_INPUT; + } else { + return TMG_COMB_INPUT; + } + } std::string pname = port.str(this); if (pname.size() > 1) { - if ((pname.front() == 'A' || pname.front() == 'B') && std::isdigit(pname.at(1))) - return TMG_COMB_INPUT; - if (pname.front() == 'P' && std::isdigit(pname.at(1))) - return TMG_COMB_OUTPUT; + if ((pname.front() == 'A' || pname.front() == 'B') && std::isdigit(pname.at(1))) { + if (cell->multInfo.is_clocked) { + clockInfoCount = 1; + return TMG_REGISTER_INPUT; + } else { + return TMG_COMB_INPUT; + } + } + if ((pname.front() == 'P') && std::isdigit(pname.at(1))) { + if (cell->multInfo.is_clocked) { + clockInfoCount = 1; + return TMG_REGISTER_OUTPUT; + } else { + return TMG_COMB_OUTPUT; + } + } } return TMG_IGNORE; } else if (cell->type == id_ALU54B) { @@ -1117,6 +1140,42 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port } else { NPNR_ASSERT_FALSE("unknown DQSBUFM register port"); } + } else if (cell->type == id_MULT18X18D) { + std::string port_name = port.str(this); + // To keep the timing DB small, like signals (e.g. P[35:0] have been + // grouped. To look up the timing, we therefore need to map this port + // to the enclosing port group. + auto has_prefix = [](std::string base, std::string prefix) { + return base.compare(0, prefix.size(), prefix) == 0; + }; + IdString port_group; + if (has_prefix(port_name, "A")) { + port_group = id_A; + } else if (has_prefix(port_name, "B")) { + port_group = id_B; + } else if (has_prefix(port_name, "P")) { + port_group = id_P; + } else if (has_prefix(port_name, "CE")) { + port_group = id_CE0; + } else if (has_prefix(port_name, "RST")) { + port_group = id_RST0; + } else if (has_prefix(port_name, "SIGNED")) { + // Both SIGNEDA and SIGNEDB exist in the DB, so can directly use these here + port_group = port; + } else { + NPNR_ASSERT_FALSE("Unknown MULT18X18D register port"); + } + + // If this port is clocked at all, it must be clocked from CLK0 + IdString clock_id = id_CLK0; + info.clock_port = clock_id; + info.edge = RISING_EDGE; + if (cell->ports.at(port).type == PORT_OUT) { + bool is_path = getDelayFromTimingDatabase(cell->multInfo.timing_id, clock_id, port_group, info.clockToQ); + NPNR_ASSERT(is_path); + } else { + getSetupHoldFromTimingDatabase(cell->multInfo.timing_id, clock_id, port_group, info.setup, info.hold); + } } return info; } diff --git a/ecp5/archdefs.h b/ecp5/archdefs.h index 0f197345..586c385f 100644 --- a/ecp5/archdefs.h +++ b/ecp5/archdefs.h @@ -187,8 +187,12 @@ struct ArchCellInfo // Which timing information to use for a DP16KD. Depends on registering // configuration. nextpnr_ecp5::IdString regmode_timing_id; - } ramInfo; + struct + { + bool is_clocked; + nextpnr_ecp5::IdString timing_id; + } multInfo; }; NEXTPNR_NAMESPACE_END diff --git a/ecp5/pack.cc b/ecp5/pack.cc index 0872ae58..55b2c791 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -3037,6 +3037,62 @@ void Arch::assignArchInfo() } else if (ci->ramInfo.is_output_a_registered && ci->ramInfo.is_output_b_registered) { ci->ramInfo.regmode_timing_id = id_DP16KD_REGMODE_A_OUTREG_REGMODE_B_OUTREG; } + } else if (ci->type == id_MULT18X18D) { + // For the multiplier block, our timing db is dictated by whether any of the input/output registers are + // enabled. To that end, we need to work out what the parameters are for the INPUTA_CLK, INPUTB_CLK and + // OUTPUT_CLK are. + // The clock check is the same IN_A/B and OUT, so hoist it to a function + auto get_clock_parameter = [&](std::string param_name) { + std::string clk = str_or_default(ci->params, id(param_name), "NONE"); + if (clk != "NONE" && clk != "CLK0" && clk != "CLK1" && clk != "CLK2" && clk != "CLK3") + log_error("MULT18X18D %s has invalid %s configuration '%s'\n", ci->name.c_str(this), + param_name.c_str(), clk.c_str()); + return clk; + }; + + // Get the input clock setting from the cell + std::string reg_inputa_clk = get_clock_parameter("REG_INPUTA_CLK"); + std::string reg_inputb_clk = get_clock_parameter("REG_INPUTB_CLK"); + + // Inputs are registered IFF the REG_INPUT value is not NONE + const bool is_in_a_registered = reg_inputa_clk != "NONE"; + const bool is_in_b_registered = reg_inputb_clk != "NONE"; + + // Similarly, get the output register clock + std::string reg_output_clk = get_clock_parameter("REG_OUTPUT_CLK"); + const bool is_output_registered = reg_output_clk != "NONE"; + + // If only one of the inputs is registered, we are going to treat that as + // neither input registered so that we don't have to deal with mixed timing. + // Emit a warning to that effect. + const bool any_input_registered = is_in_a_registered || is_in_b_registered; + const bool both_inputs_registered = is_in_a_registered && is_in_b_registered; + const bool input_registers_mismatched = any_input_registered && !both_inputs_registered; + if (input_registers_mismatched) { + log_warning("MULT18X18D %s has unsupported mixed input register modes (reg_inputa_clk=%s, " + "reg_inputb_clk=%s)\n", + ci->name.c_str(this), reg_inputa_clk.c_str(), reg_inputb_clk.c_str()); + log_warning("Timings for MULT18X18D %s will be calculated as though neither input were registered\n", + ci->name.c_str(this)); + + // Act as though the inputs are unregistered, so select timing DB based only on the + // output register mode + ci->multInfo.timing_id = is_output_registered ? id_MULT18X18D_REGS_OUTPUT : id_MULT18X18D_REGS_NONE; + } else { + // Based on our register settings, pick the timing data to use for this cell + if (!both_inputs_registered && !is_output_registered) { + ci->multInfo.timing_id = id_MULT18X18D_REGS_NONE; + } else if (both_inputs_registered && !is_output_registered) { + ci->multInfo.timing_id = id_MULT18X18D_REGS_INPUT; + } else if (!both_inputs_registered && is_output_registered) { + ci->multInfo.timing_id = id_MULT18X18D_REGS_OUTPUT; + } else if (both_inputs_registered && is_output_registered) { + ci->multInfo.timing_id = id_MULT18X18D_REGS_ALL; + } + } + // If we aren't a pure combinatorial multiplier, then our timings are + // calculated with respect to CLK0 + ci->multInfo.is_clocked = ci->multInfo.timing_id != id_MULT18X18D_REGS_NONE; } } for (auto net : sorted(nets)) { |