Merge branch 'rschlaikjer-rschlaikjer-mult18x18-register-timings'
This commit is contained in:
commit
0faf07aac8
69
ecp5/arch.cc
69
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;
|
||||
}
|
||||
|
@ -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
|
||||
|
56
ecp5/pack.cc
56
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)) {
|
||||
|
Loading…
Reference in New Issue
Block a user