mirror of
https://github.com/YosysHQ/yosys
synced 2025-06-07 06:33:24 +00:00
Merge c68fd85b9c
into d96d1e3630
This commit is contained in:
commit
96ce11288a
21 changed files with 3367 additions and 257 deletions
|
@ -11,9 +11,12 @@ OBJS += techlibs/quicklogic/ql_ioff.o
|
||||||
# --------------------------------------
|
# --------------------------------------
|
||||||
|
|
||||||
OBJS += techlibs/quicklogic/ql_dsp_macc.o
|
OBJS += techlibs/quicklogic/ql_dsp_macc.o
|
||||||
GENFILES += techlibs/quicklogic/ql_dsp_macc_pm.h techlibs/quicklogic/qlf_k6n10f/bram_types_sim.v
|
OBJS += techlibs/quicklogic/ql_dsp.o
|
||||||
|
GENFILES += techlibs/quicklogic/ql_dsp_macc_pm.h techlibs/quicklogic/ql_dsp_pm.h techlibs/quicklogic/qlf_k6n10f/bram_types_sim.v
|
||||||
techlibs/quicklogic/ql_dsp_macc.o: techlibs/quicklogic/ql_dsp_macc_pm.h
|
techlibs/quicklogic/ql_dsp_macc.o: techlibs/quicklogic/ql_dsp_macc_pm.h
|
||||||
|
techlibs/quicklogic/ql_dsp.o: techlibs/quicklogic/ql_dsp_pm.h
|
||||||
$(eval $(call add_extra_objs,techlibs/quicklogic/ql_dsp_macc_pm.h))
|
$(eval $(call add_extra_objs,techlibs/quicklogic/ql_dsp_macc_pm.h))
|
||||||
|
$(eval $(call add_extra_objs,techlibs/quicklogic/ql_dsp_pm.h))
|
||||||
|
|
||||||
# --------------------------------------
|
# --------------------------------------
|
||||||
|
|
||||||
|
@ -36,9 +39,13 @@ $(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf
|
||||||
$(eval $(call add_gen_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/bram_types_sim.v))
|
$(eval $(call add_gen_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/bram_types_sim.v))
|
||||||
$(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/cells_sim.v))
|
$(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/cells_sim.v))
|
||||||
$(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/ffs_map.v))
|
$(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/ffs_map.v))
|
||||||
$(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/dsp_sim.v))
|
$(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/dspv1_sim.v))
|
||||||
$(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/dsp_map.v))
|
$(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/dspv1_sim_extra.v))
|
||||||
$(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/dsp_final_map.v))
|
$(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/dspv1_map.v))
|
||||||
|
$(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/dspv1_final_map.v))
|
||||||
|
$(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/dspv2_sim.v))
|
||||||
|
$(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/dspv2_map.v))
|
||||||
|
$(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/dspv2_final_map.v))
|
||||||
$(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/TDP18K_FIFO.v))
|
$(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/TDP18K_FIFO.v))
|
||||||
$(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/ufifo_ctl.v))
|
$(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/ufifo_ctl.v))
|
||||||
$(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/sram1024x18_mem.v))
|
$(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/sram1024x18_mem.v))
|
||||||
|
|
134
techlibs/quicklogic/ql_dsp.cc
Normal file
134
techlibs/quicklogic/ql_dsp.cc
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
/*
|
||||||
|
* yosys -- Yosys Open SYnthesis Suite
|
||||||
|
*
|
||||||
|
* 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 "kernel/rtlil.h"
|
||||||
|
#include "kernel/register.h"
|
||||||
|
#include "kernel/sigtools.h"
|
||||||
|
|
||||||
|
PRIVATE_NAMESPACE_BEGIN
|
||||||
|
USING_YOSYS_NAMESPACE
|
||||||
|
|
||||||
|
// promote dspv2_16x9x32_cfg_ports to dspv2_32x18x64_cfg_ports if need be
|
||||||
|
bool promote(Module *m, Cell *cell) {
|
||||||
|
if (cell->type == ID(dspv2_32x18x64_cfg_ports)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
log_assert(cell->type == ID(dspv2_16x9x32_cfg_ports));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto widen_output = [&](IdString port_name, int new_width) {
|
||||||
|
if (!cell->hasPort(port_name))
|
||||||
|
return;
|
||||||
|
SigSpec port = cell->getPort(port_name);
|
||||||
|
if (port.size() < new_width) {
|
||||||
|
port = {m->addWire(NEW_ID, new_width - port.size()), port};
|
||||||
|
cell->setPort(port_name, port);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto widen_input = [&](IdString port_name, int new_width) {
|
||||||
|
if (!cell->hasPort(port_name))
|
||||||
|
return;
|
||||||
|
SigSpec port = cell->getPort(port_name);
|
||||||
|
if (port.size() < new_width) {
|
||||||
|
port.extend_u0(new_width, /* is_signed= */ true);
|
||||||
|
cell->setPort(port_name, port);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
widen_output(ID(z_o), 50);
|
||||||
|
widen_output(ID(a_cout_o), 32);
|
||||||
|
widen_output(ID(b_cout_o), 18);
|
||||||
|
widen_output(ID(z_cout_o), 50);
|
||||||
|
|
||||||
|
auto uses_port = [&](IdString port_name) {
|
||||||
|
return cell->hasPort(port_name) && !cell->getPort(port_name).is_fully_undef();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (uses_port(ID(a_cin_i)) || uses_port(ID(b_cin_i)) || uses_port(ID(z_cin_i))) {
|
||||||
|
log_error("Cannot promote %s (type %s) with cascading paths\n", log_id(cell), log_id(cell->type));
|
||||||
|
}
|
||||||
|
|
||||||
|
widen_input(ID(a_i), 32);
|
||||||
|
widen_input(ID(b_i), 18);
|
||||||
|
widen_input(ID(c_i), 18);
|
||||||
|
cell->type = ID(dspv2_32x18x64_cfg_ports);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool did_something;
|
||||||
|
|
||||||
|
#include "techlibs/quicklogic/ql_dsp_pm.h"
|
||||||
|
|
||||||
|
struct QlDspPass : Pass {
|
||||||
|
QlDspPass() : Pass("ql_dsp", "pack into QuickLogic DSPs") {}
|
||||||
|
|
||||||
|
void help() override
|
||||||
|
{
|
||||||
|
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
|
||||||
|
log("\n");
|
||||||
|
log(" ql_dsp [selection]\n");
|
||||||
|
log("\n");
|
||||||
|
log("This pass packs input and output path registers into QuickLogic DSP blocks,\n");
|
||||||
|
log("additionally it supports Z path cascading and post-adder packing.\n");
|
||||||
|
log("\n");
|
||||||
|
log(" -nocascade\n");
|
||||||
|
log(" forbid cascading\n");
|
||||||
|
log("\n");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void execute(std::vector<std::string> args, RTLIL::Design *d) override
|
||||||
|
{
|
||||||
|
log_header(d, "Executing QL_DSP pass. (pack into QuickLogic DSPs)\n");
|
||||||
|
|
||||||
|
bool nocascade = false;
|
||||||
|
size_t argidx;
|
||||||
|
for (argidx = 1; argidx < args.size(); argidx++) {
|
||||||
|
if (args[argidx] == "-nocascade") {
|
||||||
|
nocascade = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
extra_args(args, argidx, d);
|
||||||
|
|
||||||
|
for (auto module : d->selected_modules()) {
|
||||||
|
did_something = true;
|
||||||
|
|
||||||
|
while (did_something)
|
||||||
|
{
|
||||||
|
// TODO: could be optimized by more reuse of the pmgen object
|
||||||
|
did_something = false;
|
||||||
|
{
|
||||||
|
ql_dsp_pm pm(module, module->selected_cells());
|
||||||
|
pm.run_ql_dsp_pack_regs();
|
||||||
|
}
|
||||||
|
if (!nocascade) {
|
||||||
|
ql_dsp_pm pm(module, module->selected_cells());
|
||||||
|
pm.run_ql_dsp_cascade();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ql_dsp_pm pm(module, module->selected_cells());
|
||||||
|
pm.run_ql_dsp_pack_regs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} QlDspPass;
|
||||||
|
|
||||||
|
PRIVATE_NAMESPACE_END
|
272
techlibs/quicklogic/ql_dsp.pmg
Normal file
272
techlibs/quicklogic/ql_dsp.pmg
Normal file
|
@ -0,0 +1,272 @@
|
||||||
|
// derived from passes/pmgen/xilinx_dsp.pmg
|
||||||
|
pattern ql_dsp_pack_regs
|
||||||
|
|
||||||
|
state <SigBit> clock reset
|
||||||
|
state <bool> clock_inferred
|
||||||
|
|
||||||
|
// Variables used for subpatterns
|
||||||
|
state <SigSpec> argQ argD
|
||||||
|
udata <SigSpec> dffD dffQ
|
||||||
|
udata <SigBit> dffclock dffreset
|
||||||
|
udata <Cell*> dff
|
||||||
|
|
||||||
|
match dsp
|
||||||
|
select dsp->type.in(\dspv2_32x18x64_cfg_ports, \dspv2_16x9x32_cfg_ports)
|
||||||
|
endmatch
|
||||||
|
|
||||||
|
code clock_inferred clock reset
|
||||||
|
clock_inferred = false;
|
||||||
|
clock = port(dsp, \clock_i);
|
||||||
|
reset = port(dsp, \reset_i);
|
||||||
|
endcode
|
||||||
|
|
||||||
|
// try packing on Z output
|
||||||
|
code argD clock_inferred clock reset
|
||||||
|
if (port(dsp, \output_select_i)[2] == RTLIL::S0 &&
|
||||||
|
(!dsp->hasPort(\z_cout_o) || nusers(port(dsp, \z_cout_o)) == 1) &&
|
||||||
|
nusers(port(dsp, \z_o)) == 2) {
|
||||||
|
argD = port(dsp, \z_o);
|
||||||
|
subpattern(out_dffe);
|
||||||
|
if (dff) {
|
||||||
|
clock_inferred = true;
|
||||||
|
clock = dffclock;
|
||||||
|
reset = dffreset;
|
||||||
|
log("%s: inferring Z path register from flip-flop %s\n", log_id(dsp), log_id(dff));
|
||||||
|
dsp->connections_[\output_select_i][2] = RTLIL::S1;
|
||||||
|
dsp->setPort(\z_o, dffQ);
|
||||||
|
did_something = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endcode
|
||||||
|
|
||||||
|
// try packing on B input
|
||||||
|
code argQ clock_inferred clock reset
|
||||||
|
if ((!dsp->hasPort(\b_cout_o) || nusers(port(dsp, \b_cout_o)) == 1) &&
|
||||||
|
!param(dsp, \B_REG).as_bool()) {
|
||||||
|
argQ = port(dsp, \b_i);
|
||||||
|
subpattern(in_dffe);
|
||||||
|
if (dff) {
|
||||||
|
clock_inferred = true;
|
||||||
|
clock = dffclock;
|
||||||
|
reset = dffreset;
|
||||||
|
log("%s: inferring B path register from flip-flop %s\n", log_id(dsp), log_id(dff));
|
||||||
|
dsp->parameters[\B_REG] = Const(1, 1);
|
||||||
|
dsp->setPort(\b_i, dffD);
|
||||||
|
did_something = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endcode
|
||||||
|
|
||||||
|
// try packing on A input
|
||||||
|
code argQ clock_inferred clock reset
|
||||||
|
if ((!dsp->hasPort(\a_cout_o) || nusers(port(dsp, \a_cout_o)) == 1) &&
|
||||||
|
!param(dsp, \A_REG).as_bool()) {
|
||||||
|
argQ = port(dsp, \a_i);
|
||||||
|
subpattern(in_dffe);
|
||||||
|
if (dff) {
|
||||||
|
clock_inferred = true;
|
||||||
|
clock = dffclock;
|
||||||
|
reset = dffreset;
|
||||||
|
log("%s: inferring A path register from flip-flop %s\n", log_id(dsp), log_id(dff));
|
||||||
|
dsp->parameters[\A_REG] = Const(1, 1);
|
||||||
|
dsp->setPort(\a_i, dffD);
|
||||||
|
did_something = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endcode
|
||||||
|
|
||||||
|
code
|
||||||
|
if (clock_inferred) {
|
||||||
|
dsp->setPort(\clock_i, clock);
|
||||||
|
dsp->setPort(\reset_i, reset);
|
||||||
|
}
|
||||||
|
endcode
|
||||||
|
|
||||||
|
// #######################
|
||||||
|
// Subpattern for matching against input registers, based on knowledge of the
|
||||||
|
// 'Q' output.
|
||||||
|
subpattern in_dffe
|
||||||
|
arg argQ clock reset
|
||||||
|
|
||||||
|
code
|
||||||
|
dff = nullptr;
|
||||||
|
if (argQ.empty())
|
||||||
|
reject;
|
||||||
|
for (const auto &c : argQ.chunks()) {
|
||||||
|
if (!c.wire) {
|
||||||
|
// Abandon matches when constant Q bits are non-zero
|
||||||
|
// (doesn't match DSPv2 init/reset behavior)
|
||||||
|
if (!SigSpec(c).is_fully_zero())
|
||||||
|
reject;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abandon matches when 'Q' has the keep attribute set
|
||||||
|
if (c.wire->get_bool_attribute(\keep))
|
||||||
|
reject;
|
||||||
|
// Abandon matches when 'Q' has a non-zero init attribute set (not supported by DSPv2)
|
||||||
|
Const init = c.wire->attributes.at(\init, Const());
|
||||||
|
if (!init.empty())
|
||||||
|
for (auto b : init.extract(c.offset, c.width))
|
||||||
|
if (b != State::Sx && b != State::S0)
|
||||||
|
reject;
|
||||||
|
}
|
||||||
|
endcode
|
||||||
|
|
||||||
|
match ff
|
||||||
|
select ff->type.in($dff, $dffe, $adff, $adffe)
|
||||||
|
// DSPv2 does not support polarity inversion
|
||||||
|
select param(ff, \CLK_POLARITY).as_bool()
|
||||||
|
|
||||||
|
// Check that reset value, if present, is fully 0.
|
||||||
|
filter ff->type.in($dff, $dffe) || param(ff, \ARST_VALUE).is_fully_zero()
|
||||||
|
|
||||||
|
// Check reset polarity, if present
|
||||||
|
filter ff->type.in($dff, $dffe) || param(ff, \ARST_POLARITY).as_bool()
|
||||||
|
|
||||||
|
// Check that the LSB argQ bit is present (the rest follow by the nusers(...)=2 condition)
|
||||||
|
slice offset GetSize(port(ff, \D))
|
||||||
|
index <SigBit> port(ff, \Q)[offset] === argQ[0]
|
||||||
|
|
||||||
|
define <SigBit> ff_reset (ff->type.in($dff, $dffe) ? RTLIL::S0 : port(ff, \ARST))
|
||||||
|
filter clock == RTLIL::Sx || port(ff, \CLK)[0] == clock
|
||||||
|
filter clock == RTLIL::Sx || ff_reset == reset
|
||||||
|
endmatch
|
||||||
|
|
||||||
|
code argD
|
||||||
|
dff = ff;
|
||||||
|
dffclock = port(ff, \CLK);
|
||||||
|
dffreset = (ff->type.in($dff, $dffe) ? RTLIL::S0 : port(ff, \ARST));
|
||||||
|
dffD = argQ;
|
||||||
|
dffD.replace(port(ff, \Q), port(ff, \D));
|
||||||
|
endcode
|
||||||
|
|
||||||
|
|
||||||
|
// #######################
|
||||||
|
// Subpattern for matching against output registers, based on knowledge of the
|
||||||
|
// 'D' input.
|
||||||
|
|
||||||
|
subpattern out_dffe
|
||||||
|
arg argD clock reset
|
||||||
|
|
||||||
|
code
|
||||||
|
dff = nullptr;
|
||||||
|
if (argD.empty())
|
||||||
|
reject;
|
||||||
|
for (const auto &c : argD.chunks()) {
|
||||||
|
// Abandon matches when 'D' has the keep attribute set
|
||||||
|
if (!c.wire || c.wire->get_bool_attribute(\keep))
|
||||||
|
reject;
|
||||||
|
}
|
||||||
|
endcode
|
||||||
|
|
||||||
|
match ff
|
||||||
|
select ff->type.in($dff, $dffe, $adff, $adffe)
|
||||||
|
// DSPv2 does not support polarity inversion
|
||||||
|
select param(ff, \CLK_POLARITY).as_bool()
|
||||||
|
|
||||||
|
// Check that reset value, if present, is fully 0.
|
||||||
|
filter ff->type.in($dff, $dffe) || param(ff, \ARST_VALUE).is_fully_zero()
|
||||||
|
|
||||||
|
// Check reset polarity, if present
|
||||||
|
filter ff->type.in($dff, $dffe) || param(ff, \ARST_POLARITY).as_bool()
|
||||||
|
|
||||||
|
slice offset GetSize(port(ff, \D))
|
||||||
|
index <SigBit> port(ff, \D)[offset] === argD[0]
|
||||||
|
|
||||||
|
define <SigBit> ff_reset (ff->type.in($dff, $dffe) ? RTLIL::S0 : port(ff, \ARST))
|
||||||
|
filter clock == RTLIL::Sx || port(ff, \CLK)[0] == clock
|
||||||
|
filter clock == RTLIL::Sx || ff_reset == reset
|
||||||
|
endmatch
|
||||||
|
|
||||||
|
code
|
||||||
|
dff = ff;
|
||||||
|
dffclock = port(ff, \CLK);
|
||||||
|
dffreset = (ff->type.in($dff, $dffe) ? RTLIL::S0 : port(ff, \ARST));
|
||||||
|
dffQ = argD;
|
||||||
|
dffQ.replace(port(ff, \D), port(ff, \Q));
|
||||||
|
|
||||||
|
// Abandon matches when 'Q' has a defined init attribute set
|
||||||
|
// (not supported by DSPv2)
|
||||||
|
for (auto c : dffQ.chunks()) {
|
||||||
|
Const init = c.wire->attributes.at(\init, Const());
|
||||||
|
if (!init.empty())
|
||||||
|
for (auto b : init.extract(c.offset, c.width))
|
||||||
|
if (b != State::Sx)
|
||||||
|
reject;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Rewire retired flip-flop slice
|
||||||
|
SigSpec D = port(ff, \D);
|
||||||
|
SigSpec Q = port(ff, \Q);
|
||||||
|
D.replace(argD, module->addWire(NEW_ID, argD.size()), &Q);
|
||||||
|
D.replace(argD, Const(RTLIL::Sx, argD.size()));
|
||||||
|
ff->setPort(\D, D);
|
||||||
|
ff->setPort(\Q, Q);
|
||||||
|
}
|
||||||
|
endcode
|
||||||
|
|
||||||
|
pattern ql_dsp_cascade
|
||||||
|
|
||||||
|
match dsp1
|
||||||
|
select dsp1->type.in(\dspv2_32x18x64_cfg_ports, \dspv2_16x9x32_cfg_ports)
|
||||||
|
filter !dsp1->hasPort(\z_cout_o) || nusers(port(dsp1, \z_cout_o)) == 1
|
||||||
|
endmatch
|
||||||
|
|
||||||
|
match dsp2
|
||||||
|
select dsp2->type.in(\dspv2_32x18x64_cfg_ports, \dspv2_16x9x32_cfg_ports)
|
||||||
|
filter port(dsp2, \output_select_i).is_fully_const()
|
||||||
|
define <int> output_sel port(dsp2, \output_select_i).as_int()
|
||||||
|
filter output_sel == 0 || (output_sel == 4 && !param(dsp2, \M_REG).as_bool())
|
||||||
|
// expect `dsp2` and `add` for exclusive users
|
||||||
|
filter nusers(port(dsp2, \z_o)) == 2
|
||||||
|
filter !dsp2->hasPort(\z_cout_o) || nusers(port(dsp2, \z_cout_o)) == 1
|
||||||
|
filter dsp1 != dsp2
|
||||||
|
endmatch
|
||||||
|
|
||||||
|
match add
|
||||||
|
select add->type.in($add, $sub)
|
||||||
|
define <int> width param(add, \Y_WIDTH).as_int()
|
||||||
|
|
||||||
|
index <SigBit> port(add, \A)[0] === port(dsp1, \z_o)[0]
|
||||||
|
filter port(add, \A).size() >= width && port(dsp1, \z_o).size() >= width
|
||||||
|
filter port(add, \A).extract(0, width) == port(dsp1, \z_o).extract(0, width)
|
||||||
|
|
||||||
|
index <SigBit> port(add, \B)[0] === port(dsp2, \z_o)[0]
|
||||||
|
filter port(add, \B).size() >= width && port(dsp2, \z_o).size() >= width
|
||||||
|
filter port(add, \B).extract(0, width) == port(dsp2, \z_o).extract(0, width)
|
||||||
|
endmatch
|
||||||
|
|
||||||
|
code
|
||||||
|
const int z_width = 50;
|
||||||
|
|
||||||
|
log("%s: inferring post-adder from %s (type %s)\n", log_id(dsp2), log_id(add), log_id(add->type));
|
||||||
|
if (promote(module, dsp1))
|
||||||
|
log(" - promoting %s to non-fractured DSP block\n", log_id(dsp1));
|
||||||
|
if (promote(module, dsp2))
|
||||||
|
log(" - promoting %s to non-fractured DSP block\n", log_id(dsp2));
|
||||||
|
|
||||||
|
// link up z_cout_o of dsp1 to z_cin_i of dsp2
|
||||||
|
Wire *link = module->addWire(NEW_ID, z_width);
|
||||||
|
dsp1->setPort(\z_cout_o, link);
|
||||||
|
dsp2->setPort(\z_cin_i, link);
|
||||||
|
|
||||||
|
// configure the path inside dsp2
|
||||||
|
if (port(dsp2, \output_select_i).as_int() == 4) {
|
||||||
|
log("%s: inferring M register\n", log_id(dsp2));
|
||||||
|
dsp2->setParam(\M_REG, Const(1, 1));
|
||||||
|
}
|
||||||
|
dsp2->setParam(\SUBTRACT, Const(add->type == $sub, 1));
|
||||||
|
dsp2->setPort(\feedback_i, Const(3, 3));
|
||||||
|
dsp2->setPort(\output_select_i, Const(3, 3));
|
||||||
|
dsp2->setParam(\ROUND, Const(0, 3));
|
||||||
|
dsp2->setParam(\SHIFT_REG, Const(0, 6));
|
||||||
|
dsp2->setParam(\SATURATE, Const(0, 1));
|
||||||
|
dsp2->setParam(\ZCIN_REG, Const(1, 1));
|
||||||
|
dsp2->setPort(\z_o, {port(dsp2, \z_o).extract_end(port(add, \Y).size()), port(add, \Y)});
|
||||||
|
|
||||||
|
did_something = true;
|
||||||
|
autoremove(add);
|
||||||
|
accept;
|
||||||
|
endcode
|
|
@ -44,20 +44,32 @@ struct QlDspIORegs : public Pass {
|
||||||
log("\n");
|
log("\n");
|
||||||
log("This pass looks for QL_DSP2 cells and changes their cell type depending on their\n");
|
log("This pass looks for QL_DSP2 cells and changes their cell type depending on their\n");
|
||||||
log("configuration.\n");
|
log("configuration.\n");
|
||||||
|
log("\n");
|
||||||
|
log(" -dspv2\n");
|
||||||
|
log(" target DSPv2.\n");
|
||||||
|
log("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
void execute(std::vector<std::string> a_Args, RTLIL::Design *a_Design) override
|
void execute(std::vector<std::string> a_Args, RTLIL::Design *a_Design) override
|
||||||
{
|
{
|
||||||
log_header(a_Design, "Executing QL_DSP_IO_REGS pass.\n");
|
log_header(a_Design, "Executing QL_DSP_IO_REGS pass.\n");
|
||||||
|
|
||||||
|
bool target_dspv2 = false;
|
||||||
size_t argidx;
|
size_t argidx;
|
||||||
for (argidx = 1; argidx < a_Args.size(); argidx++) {
|
for (argidx = 1; argidx < a_Args.size(); argidx++) {
|
||||||
|
if (a_Args[argidx] == "-dspv2") {
|
||||||
|
target_dspv2 = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
extra_args(a_Args, argidx, a_Design);
|
extra_args(a_Args, argidx, a_Design);
|
||||||
|
|
||||||
for (auto module : a_Design->selected_modules()) {
|
for (auto module : a_Design->selected_modules()) {
|
||||||
ql_dsp_io_regs_pass(module);
|
if (target_dspv2)
|
||||||
|
ql_dsp_io_regs_pass_v2(module);
|
||||||
|
else
|
||||||
|
ql_dsp_io_regs_pass(module);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,6 +139,7 @@ struct QlDspIORegs : public Pass {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set new type name
|
// Set new type name
|
||||||
|
log_debug("Converted %s to %s\n", log_id(cell->type), new_type.c_str());
|
||||||
cell->type = RTLIL::IdString(new_type);
|
cell->type = RTLIL::IdString(new_type);
|
||||||
|
|
||||||
std::vector<std::string> ports2del;
|
std::vector<std::string> ports2del;
|
||||||
|
@ -152,6 +165,146 @@ struct QlDspIORegs : public Pass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ql_dsp_io_regs_pass_v2(Module *module)
|
||||||
|
{
|
||||||
|
sigmap.set(module);
|
||||||
|
|
||||||
|
for (auto cell : module->cells()) {
|
||||||
|
if (cell->type != ID(QL_DSPV2))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// If the cell does not have the "is_inferred" attribute set
|
||||||
|
// then don't touch it.
|
||||||
|
if (!cell->get_bool_attribute(ID(is_inferred)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!cell->hasPort(ID(output_select)) ||
|
||||||
|
!sigmap(cell->getPort(ID(output_select))).is_fully_def() ||
|
||||||
|
!cell->hasParam(ID(MODE_BITS)) ||
|
||||||
|
cell->getParam(ID(MODE_BITS)).size() != 72) {
|
||||||
|
log_error("Missing configuration tie-offs or parameters on DSP cell %s\n",
|
||||||
|
log_id(cell));
|
||||||
|
}
|
||||||
|
int out_sel_i = sigmap(cell->getPort(ID(output_select))).as_int();
|
||||||
|
Const mode = cell->getParam(ID(MODE_BITS));
|
||||||
|
|
||||||
|
// Get the feedback port
|
||||||
|
if (!cell->hasPort(ID(feedback)))
|
||||||
|
log_error("Missing 'feedback' port on %s", log_id(cell));
|
||||||
|
SigSpec feedback = sigmap(cell->getPort(ID(feedback)));
|
||||||
|
|
||||||
|
bool a_reg = mode[61] != RTLIL::S0;
|
||||||
|
bool b_reg = mode[63] != RTLIL::S0;
|
||||||
|
|
||||||
|
// Build new type name
|
||||||
|
std::string new_type = "\\QL_DSPV2_MULT";
|
||||||
|
|
||||||
|
// Decide if we should be deleting the clock port
|
||||||
|
bool del_clk = true;
|
||||||
|
|
||||||
|
if (a_reg != b_reg) {
|
||||||
|
// no specialized type for mixed scenario
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum {
|
||||||
|
MULT,
|
||||||
|
MULTADD,
|
||||||
|
MULTACC,
|
||||||
|
Unrecognized
|
||||||
|
} base_function = Unrecognized;
|
||||||
|
|
||||||
|
switch (out_sel_i) {
|
||||||
|
case 0:
|
||||||
|
case 4:
|
||||||
|
base_function = MULT;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
case 5:
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
case 6:
|
||||||
|
case 7:
|
||||||
|
if (feedback.is_fully_def() && (feedback.as_int() == 2 || feedback.as_int() == 3)) {
|
||||||
|
del_clk = false;
|
||||||
|
new_type += "ADD";
|
||||||
|
base_function = MULTADD;
|
||||||
|
break;
|
||||||
|
} else if (feedback.extract(1, 2).is_fully_zero()) {
|
||||||
|
del_clk = false;
|
||||||
|
new_type += "ACC";
|
||||||
|
base_function = MULTACC;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
base_function = Unrecognized;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (base_function == Unrecognized) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a_reg && b_reg) {
|
||||||
|
del_clk = false;
|
||||||
|
new_type += "_REGIN";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out_sel_i > 3) {
|
||||||
|
del_clk = false;
|
||||||
|
new_type += "_REGOUT";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set new type name
|
||||||
|
log_debug("Converted %s to %s\n", log_id(cell->type), new_type.c_str());
|
||||||
|
cell->type = RTLIL::IdString(new_type);
|
||||||
|
|
||||||
|
std::vector<std::string> ports2del;
|
||||||
|
|
||||||
|
if (del_clk) {
|
||||||
|
cell->unsetPort(ID(clk));
|
||||||
|
cell->unsetPort(ID(reset));
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (base_function) {
|
||||||
|
case MULTACC: {
|
||||||
|
static const std::vector<IdString> to_del = {
|
||||||
|
ID(c), ID(a_cin), ID(b_cin), ID(z_cin),
|
||||||
|
ID(a_cout), ID(b_cout)
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto port : to_del)
|
||||||
|
cell->unsetPort(port);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MULTADD: {
|
||||||
|
static const std::vector<IdString> to_del = {
|
||||||
|
ID(c), ID(a_cin), ID(b_cin), ID(a_cout), ID(b_cout)
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto port : to_del)
|
||||||
|
cell->unsetPort(port);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MULT: {
|
||||||
|
static const std::vector<IdString> to_del = {
|
||||||
|
ID(c), ID(load_acc), ID(acc_reset),
|
||||||
|
ID(a_cin), ID(b_cin), ID(z_cin), ID(a_cout), ID(b_cout)
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto port : to_del)
|
||||||
|
cell->unsetPort(port);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} QlDspIORegs;
|
} QlDspIORegs;
|
||||||
|
|
||||||
PRIVATE_NAMESPACE_END
|
PRIVATE_NAMESPACE_END
|
||||||
|
|
|
@ -27,158 +27,303 @@ PRIVATE_NAMESPACE_BEGIN
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
static void create_ql_macc_dsp(ql_dsp_macc_pm &pm)
|
static void create_ql_macc_dsp_v1(ql_dsp_macc_pm &pm)
|
||||||
{
|
{
|
||||||
auto &st = pm.st_ql_dsp_macc;
|
auto &st = pm.st_ql_dsp_macc;
|
||||||
|
|
||||||
// Get port widths
|
// Get port widths
|
||||||
size_t a_width = GetSize(st.mul->getPort(ID(A)));
|
size_t a_width = GetSize(st.mul->getPort(ID::A));
|
||||||
size_t b_width = GetSize(st.mul->getPort(ID(B)));
|
size_t b_width = GetSize(st.mul->getPort(ID::B));
|
||||||
size_t z_width = GetSize(st.ff->getPort(ID(Q)));
|
size_t z_width = GetSize(st.ff->getPort(ID::Q));
|
||||||
|
|
||||||
size_t min_width = std::min(a_width, b_width);
|
size_t min_width = std::min(a_width, b_width);
|
||||||
size_t max_width = std::max(a_width, b_width);
|
size_t max_width = std::max(a_width, b_width);
|
||||||
|
|
||||||
// Signed / unsigned
|
// Signed / unsigned
|
||||||
bool ab_signed = st.mul->getParam(ID(A_SIGNED)).as_bool();
|
bool ab_signed = st.mul->getParam(ID::A_SIGNED).as_bool();
|
||||||
log_assert(ab_signed == st.mul->getParam(ID(B_SIGNED)).as_bool());
|
log_assert(ab_signed == st.mul->getParam(ID::B_SIGNED).as_bool());
|
||||||
|
|
||||||
// Determine DSP type or discard if too narrow / wide
|
// Determine DSP type or discard if too narrow / wide
|
||||||
RTLIL::IdString type;
|
RTLIL::IdString type;
|
||||||
size_t tgt_a_width;
|
size_t tgt_a_width;
|
||||||
size_t tgt_b_width;
|
size_t tgt_b_width;
|
||||||
size_t tgt_z_width;
|
size_t tgt_z_width;
|
||||||
|
|
||||||
string cell_base_name = "dsp_t1";
|
string cell_base_name = "dsp_t1";
|
||||||
string cell_size_name = "";
|
string cell_size_name = "";
|
||||||
string cell_cfg_name = "";
|
string cell_cfg_name = "";
|
||||||
string cell_full_name = "";
|
string cell_full_name = "";
|
||||||
|
|
||||||
if (min_width <= 2 && max_width <= 2 && z_width <= 4) {
|
if (min_width <= 2 && max_width <= 2 && z_width <= 4) {
|
||||||
log_debug("\trejected: too narrow (%zd %zd %zd)\n", min_width, max_width, z_width);
|
log_debug("\trejected: too narrow (%zd %zd %zd)\n", min_width, max_width, z_width);
|
||||||
return;
|
return;
|
||||||
} else if (min_width <= 9 && max_width <= 10 && z_width <= 19) {
|
} else if (min_width <= 9 && max_width <= 10 && z_width <= 19) {
|
||||||
cell_size_name = "_10x9x32";
|
cell_size_name = "_10x9x32";
|
||||||
tgt_a_width = 10;
|
tgt_a_width = 10;
|
||||||
tgt_b_width = 9;
|
tgt_b_width = 9;
|
||||||
tgt_z_width = 19;
|
tgt_z_width = 19;
|
||||||
} else if (min_width <= 18 && max_width <= 20 && z_width <= 38) {
|
} else if (min_width <= 18 && max_width <= 20 && z_width <= 38) {
|
||||||
cell_size_name = "_20x18x64";
|
cell_size_name = "_20x18x64";
|
||||||
tgt_a_width = 20;
|
tgt_a_width = 20;
|
||||||
tgt_b_width = 18;
|
tgt_b_width = 18;
|
||||||
tgt_z_width = 38;
|
tgt_z_width = 38;
|
||||||
} else {
|
} else {
|
||||||
log_debug("\trejected: too wide (%zd %zd %zd)\n", min_width, max_width, z_width);
|
log_debug("\trejected: too wide (%zd %zd %zd)\n", min_width, max_width, z_width);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
type = RTLIL::escape_id(cell_base_name + cell_size_name + "_cfg_ports");
|
type = RTLIL::escape_id(cell_base_name + cell_size_name + "_cfg_ports");
|
||||||
log("Inferring MACC %zux%zu->%zu as %s from:\n", a_width, b_width, z_width, log_id(type));
|
log("Inferring MACC %zux%zu->%zu as %s from:\n", a_width, b_width, z_width, log_id(type));
|
||||||
|
|
||||||
for (auto cell : {st.mul, st.add, st.mux, st.ff})
|
for (auto cell : {st.mul, st.add, st.mux, st.ff})
|
||||||
if (cell)
|
if (cell)
|
||||||
log(" %s (%s)\n", log_id(cell), log_id(cell->type));
|
log(" %s (%s)\n", log_id(cell), log_id(cell->type));
|
||||||
|
|
||||||
// Add the DSP cell
|
// Add the DSP cell
|
||||||
RTLIL::Cell *cell = pm.module->addCell(NEW_ID, type);
|
RTLIL::Cell *cell = pm.module->addCell(NEW_ID, type);
|
||||||
|
|
||||||
// Set attributes
|
// Set attributes
|
||||||
cell->set_bool_attribute(ID(is_inferred), true);
|
cell->set_bool_attribute(ID(is_inferred), true);
|
||||||
|
|
||||||
// Get input/output data signals
|
// Get input/output data signals
|
||||||
RTLIL::SigSpec sig_a, sig_b, sig_z;
|
RTLIL::SigSpec sig_a, sig_b, sig_z;
|
||||||
sig_a = st.mul->getPort(ID(A));
|
sig_a = st.mul->getPort(ID::A);
|
||||||
sig_b = st.mul->getPort(ID(B));
|
sig_b = st.mul->getPort(ID::B);
|
||||||
sig_z = st.output_registered ? st.ff->getPort(ID(Q)) : st.ff->getPort(ID(D));
|
sig_z = st.output_registered ? st.ff->getPort(ID::Q) : st.ff->getPort(ID::D);
|
||||||
|
|
||||||
if (a_width < b_width)
|
if (a_width < b_width)
|
||||||
std::swap(sig_a, sig_b);
|
std::swap(sig_a, sig_b);
|
||||||
|
|
||||||
// Connect input data ports, sign extend / pad with zeros
|
// Connect input data ports, sign extend / pad with zeros
|
||||||
sig_a.extend_u0(tgt_a_width, ab_signed);
|
sig_a.extend_u0(tgt_a_width, ab_signed);
|
||||||
sig_b.extend_u0(tgt_b_width, ab_signed);
|
sig_b.extend_u0(tgt_b_width, ab_signed);
|
||||||
cell->setPort(ID(a_i), sig_a);
|
cell->setPort(ID(a_i), sig_a);
|
||||||
cell->setPort(ID(b_i), sig_b);
|
cell->setPort(ID(b_i), sig_b);
|
||||||
|
|
||||||
// Connect output data port, pad if needed
|
// Connect output data port, pad if needed
|
||||||
if ((size_t) GetSize(sig_z) < tgt_z_width) {
|
if ((size_t) GetSize(sig_z) < tgt_z_width) {
|
||||||
auto *wire = pm.module->addWire(NEW_ID, tgt_z_width - GetSize(sig_z));
|
auto *wire = pm.module->addWire(NEW_ID, tgt_z_width - GetSize(sig_z));
|
||||||
sig_z.append(wire);
|
sig_z.append(wire);
|
||||||
}
|
}
|
||||||
cell->setPort(ID(z_o), sig_z);
|
cell->setPort(ID(z_o), sig_z);
|
||||||
|
|
||||||
// Connect clock, reset and enable
|
// Connect clock, reset and enable
|
||||||
cell->setPort(ID(clock_i), st.ff->getPort(ID(CLK)));
|
cell->setPort(ID(clock_i), st.ff->getPort(ID::CLK));
|
||||||
|
|
||||||
RTLIL::SigSpec rst;
|
RTLIL::SigSpec rst;
|
||||||
RTLIL::SigSpec ena;
|
RTLIL::SigSpec ena;
|
||||||
|
|
||||||
if (st.ff->hasPort(ID(ARST))) {
|
if (st.ff->hasPort(ID::ARST)) {
|
||||||
if (st.ff->getParam(ID(ARST_POLARITY)).as_int() != 1) {
|
if (st.ff->getParam(ID::ARST_POLARITY).as_int() != 1) {
|
||||||
rst = pm.module->Not(NEW_ID, st.ff->getPort(ID(ARST)));
|
rst = pm.module->Not(NEW_ID, st.ff->getPort(ID::ARST));
|
||||||
} else {
|
} else {
|
||||||
rst = st.ff->getPort(ID(ARST));
|
rst = st.ff->getPort(ID::ARST);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
rst = RTLIL::SigSpec(RTLIL::S0);
|
rst = RTLIL::SigSpec(RTLIL::S0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (st.ff->hasPort(ID(EN))) {
|
if (st.ff->hasPort(ID::EN)) {
|
||||||
if (st.ff->getParam(ID(EN_POLARITY)).as_int() != 1) {
|
if (st.ff->getParam(ID::EN_POLARITY).as_int() != 1) {
|
||||||
ena = pm.module->Not(NEW_ID, st.ff->getPort(ID(EN)));
|
ena = pm.module->Not(NEW_ID, st.ff->getPort(ID::EN));
|
||||||
} else {
|
} else {
|
||||||
ena = st.ff->getPort(ID(EN));
|
ena = st.ff->getPort(ID::EN);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ena = RTLIL::SigSpec(RTLIL::S1);
|
ena = RTLIL::SigSpec(RTLIL::S1);
|
||||||
}
|
}
|
||||||
|
|
||||||
cell->setPort(ID(reset_i), rst);
|
cell->setPort(ID(reset_i), rst);
|
||||||
cell->setPort(ID(load_acc_i), ena);
|
cell->setPort(ID(load_acc_i), ena);
|
||||||
|
|
||||||
// Insert feedback_i control logic used for clearing / loading the accumulator
|
// Insert feedback_i control logic used for clearing / loading the accumulator
|
||||||
if (st.mux_in_pattern) {
|
if (st.mux_in_pattern) {
|
||||||
RTLIL::SigSpec sig_s = st.mux->getPort(ID(S));
|
RTLIL::SigSpec sig_s = st.mux->getPort(ID::S);
|
||||||
|
|
||||||
// Depending on the mux port ordering insert inverter if needed
|
// Depending on the mux port ordering insert inverter if needed
|
||||||
log_assert(st.mux_ab.in(ID(A), ID(B)));
|
log_assert(st.mux_ab.in(ID::A, ID::B));
|
||||||
if (st.mux_ab == ID(A))
|
if (st.mux_ab == ID::A)
|
||||||
sig_s = pm.module->Not(NEW_ID, sig_s);
|
sig_s = pm.module->Not(NEW_ID, sig_s);
|
||||||
|
|
||||||
// Assemble the full control signal for the feedback_i port
|
// Assemble the full control signal for the feedback_i port
|
||||||
RTLIL::SigSpec sig_f;
|
RTLIL::SigSpec sig_f;
|
||||||
sig_f.append(sig_s);
|
sig_f.append(sig_s);
|
||||||
sig_f.append(RTLIL::S0);
|
sig_f.append(RTLIL::S0);
|
||||||
sig_f.append(RTLIL::S0);
|
sig_f.append(RTLIL::S0);
|
||||||
cell->setPort(ID(feedback_i), sig_f);
|
cell->setPort(ID(feedback_i), sig_f);
|
||||||
}
|
}
|
||||||
// No acc clear/load
|
// No acc clear/load
|
||||||
else {
|
else {
|
||||||
cell->setPort(ID(feedback_i), RTLIL::SigSpec(RTLIL::S0, 3));
|
cell->setPort(ID(feedback_i), RTLIL::SigSpec(RTLIL::S0, 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect control ports
|
// Connect control ports
|
||||||
cell->setPort(ID(unsigned_a_i), RTLIL::SigSpec(ab_signed ? RTLIL::S0 : RTLIL::S1));
|
cell->setPort(ID(unsigned_a_i), RTLIL::SigSpec(ab_signed ? RTLIL::S0 : RTLIL::S1));
|
||||||
cell->setPort(ID(unsigned_b_i), RTLIL::SigSpec(ab_signed ? RTLIL::S0 : RTLIL::S1));
|
cell->setPort(ID(unsigned_b_i), RTLIL::SigSpec(ab_signed ? RTLIL::S0 : RTLIL::S1));
|
||||||
|
|
||||||
// Connect config bits
|
// Connect config bits
|
||||||
cell->setPort(ID(saturate_enable_i), RTLIL::SigSpec(RTLIL::S0));
|
cell->setPort(ID(saturate_enable_i), RTLIL::SigSpec(RTLIL::S0));
|
||||||
cell->setPort(ID(shift_right_i), RTLIL::SigSpec(RTLIL::S0, 6));
|
cell->setPort(ID(shift_right_i), RTLIL::SigSpec(RTLIL::S0, 6));
|
||||||
cell->setPort(ID(round_i), RTLIL::SigSpec(RTLIL::S0));
|
cell->setPort(ID(round_i), RTLIL::SigSpec(RTLIL::S0));
|
||||||
cell->setPort(ID(register_inputs_i), RTLIL::SigSpec(RTLIL::S0));
|
cell->setPort(ID(register_inputs_i), RTLIL::SigSpec(RTLIL::S0));
|
||||||
// 3 - output post acc; 1 - output pre acc
|
// 3 - output post acc; 1 - output pre acc
|
||||||
cell->setPort(ID(output_select_i), RTLIL::Const(st.output_registered ? 1 : 3, 3));
|
cell->setPort(ID(output_select_i), RTLIL::Const(st.output_registered ? 1 : 3, 3));
|
||||||
|
|
||||||
bool subtract = (st.add->type == ID($sub));
|
bool subtract = (st.add->type == ID($sub));
|
||||||
cell->setPort(ID(subtract_i), RTLIL::SigSpec(subtract ? RTLIL::S1 : RTLIL::S0));
|
cell->setPort(ID(subtract_i), RTLIL::SigSpec(subtract ? RTLIL::S1 : RTLIL::S0));
|
||||||
|
|
||||||
// Mark the cells for removal
|
// Mark the cells for removal
|
||||||
pm.autoremove(st.mul);
|
pm.autoremove(st.mul);
|
||||||
pm.autoremove(st.add);
|
pm.autoremove(st.add);
|
||||||
if (st.mux != nullptr) {
|
if (st.mux != nullptr) {
|
||||||
pm.autoremove(st.mux);
|
pm.autoremove(st.mux);
|
||||||
}
|
}
|
||||||
pm.autoremove(st.ff);
|
pm.autoremove(st.ff);
|
||||||
|
}
|
||||||
|
|
||||||
|
void create_ql_macc_dsp_v2(ql_dsp_macc_pm &pm)
|
||||||
|
{
|
||||||
|
auto &st = pm.st_ql_dsp_macc;
|
||||||
|
|
||||||
|
SigSpec sig_a = st.mul->getPort(ID::A);
|
||||||
|
SigSpec sig_b = st.mul->getPort(ID::B);
|
||||||
|
|
||||||
|
if (sig_a.size() < sig_b.size())
|
||||||
|
std::swap(sig_a, sig_b);
|
||||||
|
|
||||||
|
// Signed / unsigned
|
||||||
|
bool ab_signed = st.mul->getParam(ID::A_SIGNED).as_bool();
|
||||||
|
log_assert(ab_signed == st.mul->getParam(ID::B_SIGNED).as_bool());
|
||||||
|
|
||||||
|
int z_width = GetSize(st.ff->getPort(ID::Q));
|
||||||
|
if (!ab_signed) {
|
||||||
|
if (sig_a.msb() != RTLIL::S0 && sig_a.size() < z_width)
|
||||||
|
sig_a.append(RTLIL::S0);
|
||||||
|
if (sig_b.msb() != RTLIL::S0 && sig_b.size() < z_width)
|
||||||
|
sig_b.append(RTLIL::S0);
|
||||||
|
}
|
||||||
|
int a_width = GetSize(sig_a);
|
||||||
|
int b_width = GetSize(sig_b);
|
||||||
|
|
||||||
|
// Determine DSP type or discard if too narrow / wide
|
||||||
|
RTLIL::IdString type;
|
||||||
|
size_t tgt_a_width;
|
||||||
|
size_t tgt_b_width;
|
||||||
|
size_t tgt_z_width;
|
||||||
|
|
||||||
|
string cell_base_name = "dspv2";
|
||||||
|
string cell_size_name = "";
|
||||||
|
string cell_cfg_name = "";
|
||||||
|
string cell_full_name = "";
|
||||||
|
|
||||||
|
if (a_width <= 2 && b_width <= 2 && z_width <= 4) {
|
||||||
|
log_debug("\trejected: too narrow (%d %d %d)\n", a_width, b_width, z_width);
|
||||||
|
return;
|
||||||
|
} else if (a_width <= 16 && b_width <= 9 && z_width <= 25) {
|
||||||
|
cell_size_name = "_16x9x32";
|
||||||
|
tgt_a_width = 16;
|
||||||
|
tgt_b_width = 9;
|
||||||
|
tgt_z_width = 25; // TODO
|
||||||
|
} else if (a_width <= 32 && b_width <= 18 && z_width <= 50) {
|
||||||
|
cell_size_name = "_32x18x64";
|
||||||
|
tgt_a_width = 32;
|
||||||
|
tgt_b_width = 18;
|
||||||
|
tgt_z_width = 50;
|
||||||
|
} else {
|
||||||
|
log_debug("\trejected: too wide (%d %d %d)\n", a_width, b_width, z_width);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
type = RTLIL::escape_id(cell_base_name + cell_size_name + "_cfg_ports");
|
||||||
|
log("Inferring MACC %dx%d->%d as %s from:\n", a_width, b_width, z_width, log_id(type));
|
||||||
|
|
||||||
|
for (auto cell : {st.mul, st.add, st.mux, st.ff})
|
||||||
|
if (cell)
|
||||||
|
log(" %s (%s)\n", log_id(cell), log_id(cell->type));
|
||||||
|
|
||||||
|
// Add the DSP cell
|
||||||
|
RTLIL::Cell *cell = pm.module->addCell(NEW_ID, type);
|
||||||
|
|
||||||
|
// Set attributes
|
||||||
|
cell->set_bool_attribute(ID(is_inferred), true);
|
||||||
|
|
||||||
|
// Get input/output data signals
|
||||||
|
SigSpec sig_z;
|
||||||
|
sig_z = st.output_registered ? st.ff->getPort(ID::Q) : st.ff->getPort(ID::D);
|
||||||
|
|
||||||
|
// Connect input data ports, sign extend / pad with zeros
|
||||||
|
sig_a.extend_u0(tgt_a_width, true);
|
||||||
|
sig_b.extend_u0(tgt_b_width, true);
|
||||||
|
cell->setPort(ID(a_i), sig_a);
|
||||||
|
cell->setPort(ID(b_i), sig_b);
|
||||||
|
cell->setPort(ID(c_i), SigSpec(RTLIL::S0, tgt_b_width));
|
||||||
|
|
||||||
|
// Connect output data port, pad if needed
|
||||||
|
if ((size_t) GetSize(sig_z) < tgt_z_width) {
|
||||||
|
auto *wire = pm.module->addWire(NEW_ID, tgt_z_width - GetSize(sig_z));
|
||||||
|
sig_z.append(wire);
|
||||||
|
}
|
||||||
|
cell->setPort(ID(z_o), sig_z);
|
||||||
|
|
||||||
|
// Connect clock, reset and enable
|
||||||
|
cell->setPort(ID(clock_i), st.ff->getPort(ID::CLK));
|
||||||
|
|
||||||
|
RTLIL::SigSpec rst;
|
||||||
|
RTLIL::SigSpec ena;
|
||||||
|
|
||||||
|
if (st.ff->hasPort(ID::ARST)) {
|
||||||
|
if (st.ff->getParam(ID::ARST_POLARITY).as_int() != 1) {
|
||||||
|
rst = pm.module->Not(NEW_ID, st.ff->getPort(ID::ARST));
|
||||||
|
} else {
|
||||||
|
rst = st.ff->getPort(ID::ARST);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rst = RTLIL::SigSpec(RTLIL::S0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (st.ff->hasPort(ID::EN)) {
|
||||||
|
if (st.ff->getParam(ID::EN_POLARITY).as_int() != 1) {
|
||||||
|
ena = pm.module->Not(NEW_ID, st.ff->getPort(ID::EN));
|
||||||
|
} else {
|
||||||
|
ena = st.ff->getPort(ID::EN);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ena = RTLIL::SigSpec(RTLIL::S1);
|
||||||
|
}
|
||||||
|
|
||||||
|
cell->setPort(ID(reset_i), rst);
|
||||||
|
cell->setPort(ID(load_acc_i), ena);
|
||||||
|
|
||||||
|
// Insert feedback_i control logic used for clearing / loading the accumulator
|
||||||
|
if (st.mux_in_pattern) {
|
||||||
|
RTLIL::SigSpec sig_s = st.mux->getPort(ID::S);
|
||||||
|
|
||||||
|
// Depending on the mux port ordering insert inverter if needed
|
||||||
|
log_assert(st.mux_ab.in(ID::A, ID::B));
|
||||||
|
if (st.mux_ab == ID::A)
|
||||||
|
sig_s = pm.module->Not(NEW_ID, sig_s);
|
||||||
|
|
||||||
|
cell->setPort(ID(feedback_i), {RTLIL::S0, RTLIL::S0, sig_s});
|
||||||
|
}
|
||||||
|
// No acc clear/load
|
||||||
|
else {
|
||||||
|
cell->setPort(ID(feedback_i), RTLIL::SigSpec(RTLIL::S0, 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
cell->setPort(ID(acc_reset_i), RTLIL::SigSpec(RTLIL::S0));
|
||||||
|
// 3 - output post acc; 1 - output pre acc
|
||||||
|
cell->setPort(ID(output_select_i), RTLIL::Const(st.output_registered ? 1 : 3, 3));
|
||||||
|
|
||||||
|
bool subtract = (st.add->type == ID($sub));
|
||||||
|
cell->setParam(ID(SUBTRACT), RTLIL::Const(subtract ? RTLIL::S1 : RTLIL::S0));
|
||||||
|
|
||||||
|
// Mark the cells for removal
|
||||||
|
pm.autoremove(st.mul);
|
||||||
|
pm.autoremove(st.add);
|
||||||
|
if (st.mux != nullptr) {
|
||||||
|
pm.autoremove(st.mux);
|
||||||
|
}
|
||||||
|
pm.autoremove(st.ff);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct QlDspMacc : public Pass {
|
struct QlDspMacc : public Pass {
|
||||||
|
@ -186,27 +331,41 @@ struct QlDspMacc : public Pass {
|
||||||
|
|
||||||
void help() override
|
void help() override
|
||||||
{
|
{
|
||||||
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
|
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
|
||||||
log("\n");
|
log("\n");
|
||||||
log(" ql_dsp_macc [selection]\n");
|
log(" ql_dsp_macc [selection]\n");
|
||||||
log("\n");
|
log("\n");
|
||||||
log("This pass looks for a multiply-accumulate pattern based on which it infers a\n");
|
log("This pass looks for a multiply-accumulate pattern based on which it infers a\n");
|
||||||
log("QuickLogic DSP cell.\n");
|
log("QuickLogic DSP cell.\n");
|
||||||
|
log("\n");
|
||||||
|
log(" -dspv2\n");
|
||||||
|
log(" target DSPv2.\n");
|
||||||
log("\n");
|
log("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
void execute(std::vector<std::string> a_Args, RTLIL::Design *a_Design) override
|
void execute(std::vector<std::string> args, RTLIL::Design *design) override
|
||||||
{
|
{
|
||||||
log_header(a_Design, "Executing QL_DSP_MACC pass.\n");
|
log_header(design, "Executing QL_DSP_MACC pass.\n");
|
||||||
|
|
||||||
|
bool target_dspv2 = false;
|
||||||
size_t argidx;
|
size_t argidx;
|
||||||
for (argidx = 1; argidx < a_Args.size(); argidx++) {
|
for (argidx = 1; argidx < args.size(); argidx++) {
|
||||||
|
if (args[argidx] == "-dspv2") {
|
||||||
|
target_dspv2 = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
extra_args(a_Args, argidx, a_Design);
|
extra_args(args, argidx, design);
|
||||||
|
|
||||||
for (auto module : a_Design->selected_modules())
|
for (auto module : design->selected_modules()) {
|
||||||
ql_dsp_macc_pm(module, module->selected_cells()).run_ql_dsp_macc(create_ql_macc_dsp);
|
ql_dsp_macc_pm pm(module, module->selected_cells());
|
||||||
|
|
||||||
|
if (target_dspv2)
|
||||||
|
pm.run_ql_dsp_macc(create_ql_macc_dsp_v2);
|
||||||
|
else
|
||||||
|
pm.run_ql_dsp_macc(create_ql_macc_dsp_v1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} QlDspMacc;
|
} QlDspMacc;
|
||||||
|
|
|
@ -29,7 +29,7 @@ PRIVATE_NAMESPACE_BEGIN
|
||||||
|
|
||||||
struct QlDspSimdPass : public Pass {
|
struct QlDspSimdPass : public Pass {
|
||||||
|
|
||||||
QlDspSimdPass() : Pass("ql_dsp_simd", "merge QuickLogic K6N10f DSP pairs to operate in SIMD mode") {}
|
QlDspSimdPass() : Pass("ql_dsp_simd", "merge QuickLogic K6N10f DSP pairs to operate in fractured mode") {}
|
||||||
|
|
||||||
void help() override
|
void help() override
|
||||||
{
|
{
|
||||||
|
@ -37,16 +37,17 @@ struct QlDspSimdPass : public Pass {
|
||||||
log("\n");
|
log("\n");
|
||||||
log(" ql_dsp_simd [selection]\n");
|
log(" ql_dsp_simd [selection]\n");
|
||||||
log("\n");
|
log("\n");
|
||||||
log("This pass identifies K6N10f DSP cells with identical configuration and pack pairs\n");
|
log("This pass identifies K6N10f DSP cells with identical configuration and merges\n");
|
||||||
log("of them together into other DSP cells that can perform SIMD operation.\n");
|
log("pairs of them, enabling fractured mode.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ..........................................
|
// ..........................................
|
||||||
|
|
||||||
/// Describes DSP config unique to a whole DSP cell
|
/// Describes DSP config unique to a DSP cell
|
||||||
struct DspConfig {
|
struct DspConfig {
|
||||||
// Port connections
|
// Port connections
|
||||||
dict<RTLIL::IdString, RTLIL::SigSpec> connections;
|
dict<RTLIL::IdString, RTLIL::SigSpec> connections;
|
||||||
|
dict<RTLIL::IdString, RTLIL::Const> parameters;
|
||||||
|
|
||||||
DspConfig() = default;
|
DspConfig() = default;
|
||||||
|
|
||||||
|
@ -55,60 +56,136 @@ struct QlDspSimdPass : public Pass {
|
||||||
|
|
||||||
[[nodiscard]] Hasher hash_into(Hasher h) const { h.eat(connections); return h; }
|
[[nodiscard]] Hasher hash_into(Hasher h) const { h.eat(connections); return h; }
|
||||||
|
|
||||||
bool operator==(const DspConfig &ref) const { return connections == ref.connections; }
|
bool operator==(const DspConfig &ref) const { return connections == ref.connections && parameters == ref.parameters; }
|
||||||
};
|
};
|
||||||
|
|
||||||
// ..........................................
|
// ..........................................
|
||||||
|
|
||||||
const int m_ModeBitsSize = 80;
|
|
||||||
|
|
||||||
// DSP parameters
|
|
||||||
const std::vector<std::string> m_DspParams = {"COEFF_3", "COEFF_2", "COEFF_1", "COEFF_0"};
|
|
||||||
|
|
||||||
/// Temporary SigBit to SigBit helper map.
|
/// Temporary SigBit to SigBit helper map.
|
||||||
SigMap sigmap;
|
SigMap sigmap;
|
||||||
|
|
||||||
|
static bool is_cascade(const Cell* cell)
|
||||||
|
{
|
||||||
|
static const std::vector<IdString> cascade_ports = {
|
||||||
|
ID(a_cout_o),
|
||||||
|
ID(b_cout_o),
|
||||||
|
ID(z_cout_o),
|
||||||
|
ID(a_cin_i),
|
||||||
|
ID(b_cin_i),
|
||||||
|
ID(z_cin_i)
|
||||||
|
};
|
||||||
|
for (auto p : cascade_ports) {
|
||||||
|
if (cell->hasPort(p) && !cell->getPort(p).is_fully_undef())
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// ..........................................
|
// ..........................................
|
||||||
|
|
||||||
void execute(std::vector<std::string> a_Args, RTLIL::Design *a_Design) override
|
void execute(std::vector<std::string> a_Args, RTLIL::Design *a_Design) override
|
||||||
{
|
{
|
||||||
log_header(a_Design, "Executing QL_DSP_SIMD pass.\n");
|
log_header(a_Design, "Executing QL_DSP_SIMD pass.\n");
|
||||||
|
|
||||||
// DSP control and config ports to consider and how to map them to ports
|
// The following lists have to match simulation model interfaces.
|
||||||
// of the target DSP cell
|
|
||||||
static const std::vector<std::pair<IdString, IdString>> m_DspCfgPorts = {
|
// DSP control and config ports that must be equal between
|
||||||
std::make_pair(ID(clock_i), ID(clk)),
|
// merged half-blocks
|
||||||
std::make_pair(ID(reset_i), ID(reset)),
|
// In addition to functional differences,
|
||||||
std::make_pair(ID(feedback_i), ID(feedback)),
|
// v1 and v2 have different balance between shared functionality
|
||||||
std::make_pair(ID(load_acc_i), ID(load_acc)),
|
// in ports vs params.
|
||||||
std::make_pair(ID(unsigned_a_i), ID(unsigned_a)),
|
static const std::vector<IdString> m_Dspv1CfgPorts = {
|
||||||
std::make_pair(ID(unsigned_b_i), ID(unsigned_b)),
|
ID(acc_fir_i),
|
||||||
std::make_pair(ID(subtract_i), ID(subtract)),
|
ID(feedback_i),
|
||||||
std::make_pair(ID(output_select_i), ID(output_select)),
|
ID(load_acc_i),
|
||||||
std::make_pair(ID(saturate_enable_i), ID(saturate_enable)),
|
ID(unsigned_a_i),
|
||||||
std::make_pair(ID(shift_right_i), ID(shift_right)),
|
ID(unsigned_b_i),
|
||||||
std::make_pair(ID(round_i), ID(round)),
|
ID(clock_i),
|
||||||
std::make_pair(ID(register_inputs_i), ID(register_inputs))
|
ID(s_reset),
|
||||||
|
ID(saturate_enable_i),
|
||||||
|
ID(output_select_i),
|
||||||
|
ID(round_i),
|
||||||
|
ID(shift_right_i),
|
||||||
|
ID(subtract_i),
|
||||||
|
ID(register_inputs_i),
|
||||||
|
};
|
||||||
|
static const std::vector<IdString> m_Dspv1CfgParams = {
|
||||||
|
ID(COEFF_0),
|
||||||
|
ID(COEFF_1),
|
||||||
|
ID(COEFF_2),
|
||||||
|
ID(COEFF_3),
|
||||||
|
};
|
||||||
|
static const std::vector<IdString> m_Dspv2CfgPorts = {
|
||||||
|
ID(clock_i),
|
||||||
|
ID(reset_i),
|
||||||
|
ID(acc_reset_i),
|
||||||
|
ID(feedback_i),
|
||||||
|
ID(load_acc_i),
|
||||||
|
ID(output_select_i),
|
||||||
|
};
|
||||||
|
static const std::vector<IdString> m_Dspv2CfgParams = {
|
||||||
|
ID(COEFF_0),
|
||||||
|
ID(ACC_FIR),
|
||||||
|
ID(ROUND),
|
||||||
|
ID(ZC_SHIFT),
|
||||||
|
ID(ZREG_SHIFT),
|
||||||
|
ID(SHIFT_REG),
|
||||||
|
ID(SATURATE),
|
||||||
|
ID(SUBTRACT),
|
||||||
|
ID(PRE_ADD),
|
||||||
|
ID(A_SEL),
|
||||||
|
ID(A_REG),
|
||||||
|
ID(B_SEL),
|
||||||
|
ID(B_REG),
|
||||||
|
ID(C_REG),
|
||||||
|
ID(BC_REG),
|
||||||
|
ID(M_REG),
|
||||||
|
ID(FRAC_MODE),
|
||||||
};
|
};
|
||||||
|
|
||||||
// DSP data ports and how to map them to ports of the target DSP cell
|
|
||||||
static const std::vector<std::pair<IdString, IdString>> m_DspDataPorts = {
|
// Data ports to be concatenated into merged cell
|
||||||
std::make_pair(ID(a_i), ID(a)),
|
static const std::vector<IdString> m_Dspv1DataPorts = {
|
||||||
std::make_pair(ID(b_i), ID(b)),
|
ID(a_i),
|
||||||
std::make_pair(ID(acc_fir_i), ID(acc_fir)),
|
ID(b_i),
|
||||||
std::make_pair(ID(z_o), ID(z)),
|
ID(z_o),
|
||||||
std::make_pair(ID(dly_b_o), ID(dly_b))
|
ID(dly_b_o),
|
||||||
|
};
|
||||||
|
static const std::vector<IdString> m_Dspv2DataPorts = {
|
||||||
|
ID(a_i),
|
||||||
|
ID(b_i),
|
||||||
|
ID(c_i),
|
||||||
|
ID(z_o),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Source DSP cell type (SISD)
|
// Source DSP cell type (half-block)
|
||||||
static const IdString m_SisdDspType = ID(dsp_t1_10x9x32);
|
static const IdString m_Dspv1SisdType = ID(dsp_t1_10x9x32_cfg_ports);
|
||||||
|
static const IdString m_Dspv2SisdType = ID(dspv2_16x9x32_cfg_ports);
|
||||||
|
|
||||||
// Target DSP cell types for the SIMD mode
|
// Target DSP cell types (full-block)
|
||||||
static const IdString m_SimdDspType = ID(QL_DSP2);
|
static const IdString m_Dspv1SimdType = ID(dsp_t1_20x18x64_cfg_ports_fracturable);
|
||||||
|
static const IdString m_Dspv2SimdType = ID(dspv2_32x18x64_cfg_ports);
|
||||||
|
|
||||||
// Parse args
|
// Parse args
|
||||||
extra_args(a_Args, 1, a_Design);
|
int dsp_version = 1;
|
||||||
|
size_t argidx;
|
||||||
|
for (argidx = 1; argidx < a_Args.size(); argidx++) {
|
||||||
|
if (a_Args[argidx] == "-dspv2") {
|
||||||
|
dsp_version = 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
extra_args(a_Args, argidx, a_Design);
|
||||||
|
|
||||||
|
log_assert(dsp_version < 3);
|
||||||
|
log_assert(dsp_version > 0);
|
||||||
|
const auto& cfg_ports = (dsp_version == 1) ? m_Dspv1CfgPorts : m_Dspv2CfgPorts;
|
||||||
|
const auto& cfg_params = (dsp_version == 1) ? m_Dspv1CfgParams : m_Dspv2CfgParams;
|
||||||
|
const auto& data_ports = (dsp_version == 1) ? m_Dspv1DataPorts : m_Dspv2DataPorts;
|
||||||
|
auto half_dsp = (dsp_version == 1) ? m_Dspv1SisdType : m_Dspv2SisdType;
|
||||||
|
auto full_dsp = (dsp_version == 1) ? m_Dspv1SimdType : m_Dspv2SimdType;
|
||||||
|
|
||||||
|
int cellsMerged = 0;
|
||||||
// Process modules
|
// Process modules
|
||||||
for (auto module : a_Design->selected_modules()) {
|
for (auto module : a_Design->selected_modules()) {
|
||||||
// Setup the SigMap
|
// Setup the SigMap
|
||||||
|
@ -118,25 +195,33 @@ struct QlDspSimdPass : public Pass {
|
||||||
dict<DspConfig, std::vector<RTLIL::Cell *>> groups;
|
dict<DspConfig, std::vector<RTLIL::Cell *>> groups;
|
||||||
for (auto cell : module->selected_cells()) {
|
for (auto cell : module->selected_cells()) {
|
||||||
// Check if this is a DSP cell we are looking for (type starts with m_SisdDspType)
|
// Check if this is a DSP cell we are looking for (type starts with m_SisdDspType)
|
||||||
if (cell->type != m_SisdDspType)
|
if (cell->type != half_dsp)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Skip if it has the (* keep *) attribute set
|
// Skip if it has the (* keep *) attribute set
|
||||||
if (cell->has_keep_attr())
|
if (cell->has_keep_attr()) {
|
||||||
|
log_debug("skip %s because it's marked keep\n", log_id(cell));
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if it has cascading
|
||||||
|
if (is_cascade(cell)) {
|
||||||
|
log_debug("skip %s because it's cascading\n", log_id(cell));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Add to a group
|
// Add to a group
|
||||||
const auto key = getDspConfig(cell, m_DspCfgPorts);
|
const auto key = getDspConfig(cell, cfg_ports, cfg_params);
|
||||||
groups[key].push_back(cell);
|
groups[key].push_back(cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log_debug("Checking %zu detected mode-equivalent DSP cell classes\n", groups.size());
|
||||||
std::vector<Cell *> cellsToRemove;
|
std::vector<Cell *> cellsToRemove;
|
||||||
|
|
||||||
// Map cell pairs to the target DSP SIMD cell
|
// Map cell pairs to the target DSP SIMD cell
|
||||||
for (const auto &it : groups) {
|
for (const auto &it : groups) {
|
||||||
const auto &group = it.second;
|
const auto &group = it.second;
|
||||||
const auto &config = it.first;
|
const auto &config = it.first;
|
||||||
|
log_debug("Checking %zu half-blocks\n", group.size());
|
||||||
// Ensure an even number
|
// Ensure an even number
|
||||||
size_t count = group.size();
|
size_t count = group.size();
|
||||||
if (count & 1)
|
if (count & 1)
|
||||||
|
@ -148,7 +233,7 @@ struct QlDspSimdPass : public Pass {
|
||||||
Cell *dsp_b = group[i + 1];
|
Cell *dsp_b = group[i + 1];
|
||||||
|
|
||||||
// Create the new cell
|
// Create the new cell
|
||||||
Cell *simd = module->addCell(NEW_ID, m_SimdDspType);
|
Cell *simd = module->addCell(NEW_ID, full_dsp);
|
||||||
|
|
||||||
log(" SIMD: %s (%s) + %s (%s) => %s (%s)\n", log_id(dsp_a), log_id(dsp_a->type),
|
log(" SIMD: %s (%s) + %s (%s) => %s (%s)\n", log_id(dsp_a), log_id(dsp_a->type),
|
||||||
log_id(dsp_b), log_id(dsp_b->type), log_id(simd), log_id(simd->type));
|
log_id(dsp_b), log_id(dsp_b->type), log_id(simd), log_id(simd->type));
|
||||||
|
@ -156,27 +241,36 @@ struct QlDspSimdPass : public Pass {
|
||||||
// Check if the target cell is known (important to know
|
// Check if the target cell is known (important to know
|
||||||
// its port widths)
|
// its port widths)
|
||||||
if (!simd->known())
|
if (!simd->known())
|
||||||
log_error(" The target cell type '%s' is not known!", log_id(simd));
|
log_error(" The target cell type '%s' is not known!", log_id(simd->type));
|
||||||
|
|
||||||
// Connect common ports
|
// Connect common ports
|
||||||
for (const auto &it : m_DspCfgPorts)
|
|
||||||
simd->setPort(it.first, config.connections.at(it.second));
|
for (auto port : cfg_ports) {
|
||||||
|
if (config.connections.count(port))
|
||||||
|
simd->setPort(port, config.connections.at(port));
|
||||||
|
}
|
||||||
|
for (auto param : cfg_params) {
|
||||||
|
if (config.parameters.count(param))
|
||||||
|
simd->setParam(param, config.parameters.at(param));
|
||||||
|
}
|
||||||
|
|
||||||
// Connect data ports
|
// Connect data ports
|
||||||
for (const auto &it : m_DspDataPorts) {
|
for (auto port : data_ports) {
|
||||||
size_t width;
|
size_t width;
|
||||||
bool isOutput;
|
bool isOutput;
|
||||||
|
|
||||||
std::tie(width, isOutput) = getPortInfo(simd, it.second);
|
std::tie(width, isOutput) = getPortInfo(simd, port);
|
||||||
|
if (!width)
|
||||||
|
log_error("Can't determine portinfo for %s\n", log_id(port));
|
||||||
|
|
||||||
auto getConnection = [&](const RTLIL::Cell *cell) {
|
auto getConnection = [&](const RTLIL::Cell *cell) {
|
||||||
RTLIL::SigSpec sigspec;
|
RTLIL::SigSpec sigspec;
|
||||||
if (cell->hasPort(it.first)) {
|
if (cell->hasPort(port)) {
|
||||||
const auto &sig = cell->getPort(it.first);
|
const auto &sig = cell->getPort(port);
|
||||||
sigspec.append(sig);
|
sigspec.append(sig);
|
||||||
}
|
}
|
||||||
|
|
||||||
int padding = width / 2 - sigspec.size();
|
int padding = width / 2 - sigspec.size();
|
||||||
|
log_assert(padding >= 0);
|
||||||
|
|
||||||
if (padding) {
|
if (padding) {
|
||||||
if (!isOutput)
|
if (!isOutput)
|
||||||
|
@ -190,27 +284,14 @@ struct QlDspSimdPass : public Pass {
|
||||||
RTLIL::SigSpec sigspec;
|
RTLIL::SigSpec sigspec;
|
||||||
sigspec.append(getConnection(dsp_a));
|
sigspec.append(getConnection(dsp_a));
|
||||||
sigspec.append(getConnection(dsp_b));
|
sigspec.append(getConnection(dsp_b));
|
||||||
simd->setPort(it.second, sigspec);
|
simd->setPort(port, sigspec);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Concatenate FIR coefficient parameters into the single
|
// Enable the fractured mode
|
||||||
// MODE_BITS parameter
|
if (dsp_version == 1)
|
||||||
Const mode_bits;
|
simd->setPort(ID(f_mode_i), State::S1);
|
||||||
for (const auto &it : m_DspParams) {
|
else
|
||||||
auto val_a = dsp_a->getParam(it);
|
simd->setParam(ID(FRAC_MODE), State::S1);
|
||||||
auto val_b = dsp_b->getParam(it);
|
|
||||||
|
|
||||||
mode_bits.bits().insert(mode_bits.bits().end(),
|
|
||||||
val_a.begin(), val_a.end());
|
|
||||||
mode_bits.bits().insert(mode_bits.bits().end(),
|
|
||||||
val_b.begin(), val_b.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable the fractured mode by connecting the control
|
|
||||||
// port.
|
|
||||||
simd->setPort(ID(f_mode), State::S1);
|
|
||||||
simd->setParam(ID(MODE_BITS), mode_bits);
|
|
||||||
log_assert(mode_bits.size() == m_ModeBitsSize);
|
|
||||||
|
|
||||||
// Handle the "is_inferred" attribute. If one of the fragments
|
// Handle the "is_inferred" attribute. If one of the fragments
|
||||||
// is not inferred mark the whole DSP as not inferred
|
// is not inferred mark the whole DSP as not inferred
|
||||||
|
@ -223,11 +304,12 @@ struct QlDspSimdPass : public Pass {
|
||||||
cellsToRemove.push_back(dsp_b);
|
cellsToRemove.push_back(dsp_b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
cellsMerged += cellsToRemove.size();
|
||||||
// Remove old cells
|
// Remove old cells
|
||||||
for (auto cell : cellsToRemove)
|
for (auto cell : cellsToRemove)
|
||||||
module->remove(cell);
|
module->remove(cell);
|
||||||
}
|
}
|
||||||
|
log("Merged %d half-block cells\n", cellsMerged);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ..........................................
|
// ..........................................
|
||||||
|
@ -257,19 +339,24 @@ struct QlDspSimdPass : public Pass {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Given a DSP cell populates and returns a DspConfig struct for it.
|
/// Given a DSP cell populates and returns a DspConfig struct for it.
|
||||||
DspConfig getDspConfig(RTLIL::Cell *a_Cell, const std::vector<std::pair<IdString, IdString>> &dspCfgPorts)
|
DspConfig getDspConfig(RTLIL::Cell *a_Cell, const std::vector<IdString> &dspCfgPorts, const std::vector<IdString> &dspCfgParams)
|
||||||
{
|
{
|
||||||
DspConfig config;
|
DspConfig config;
|
||||||
|
|
||||||
for (const auto &it : dspCfgPorts) {
|
for (auto port : dspCfgPorts) {
|
||||||
auto port = it.first;
|
|
||||||
|
|
||||||
// Port unconnected
|
// Port unconnected
|
||||||
if (!a_Cell->hasPort(port))
|
if (!a_Cell->hasPort(port))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
config.connections[port] = sigmap(a_Cell->getPort(port));
|
config.connections[port] = sigmap(a_Cell->getPort(port));
|
||||||
}
|
}
|
||||||
|
for (auto param : dspCfgParams) {
|
||||||
|
// Param unset?
|
||||||
|
if (!a_Cell->hasParam(param))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
config.parameters[param] = a_Cell->getParam(param);
|
||||||
|
}
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,63 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
module dsp_t1_20x18x64_cfg_ports_fracturable (
|
||||||
|
input [19:0] a_i,
|
||||||
|
input [17:0] b_i,
|
||||||
|
input [ 5:0] acc_fir_i,
|
||||||
|
output [37:0] z_o,
|
||||||
|
output [17:0] dly_b_o,
|
||||||
|
|
||||||
|
input clock_i,
|
||||||
|
input reset_i,
|
||||||
|
|
||||||
|
input [2:0] feedback_i,
|
||||||
|
input load_acc_i,
|
||||||
|
input unsigned_a_i,
|
||||||
|
input unsigned_b_i,
|
||||||
|
|
||||||
|
input [2:0] output_select_i,
|
||||||
|
input saturate_enable_i,
|
||||||
|
input [5:0] shift_right_i,
|
||||||
|
input round_i,
|
||||||
|
input subtract_i,
|
||||||
|
input register_inputs_i,
|
||||||
|
input f_mode_i
|
||||||
|
);
|
||||||
|
|
||||||
|
parameter [19:0] COEFF_0 = 20'd0;
|
||||||
|
parameter [19:0] COEFF_1 = 20'd0;
|
||||||
|
parameter [19:0] COEFF_2 = 20'd0;
|
||||||
|
parameter [19:0] COEFF_3 = 20'd0;
|
||||||
|
|
||||||
|
QL_DSP2 # (
|
||||||
|
.MODE_BITS ({COEFF_3, COEFF_2, COEFF_1, COEFF_0})
|
||||||
|
) _TECHMAP_REPLACE_ (
|
||||||
|
.a (a_i),
|
||||||
|
.b (b_i),
|
||||||
|
.acc_fir (acc_fir_i),
|
||||||
|
.z (z_o),
|
||||||
|
.dly_b (dly_b_o),
|
||||||
|
|
||||||
|
.clk (clock_i),
|
||||||
|
.reset (reset_i),
|
||||||
|
|
||||||
|
.feedback (feedback_i),
|
||||||
|
.load_acc (load_acc_i),
|
||||||
|
.unsigned_a (unsigned_a_i),
|
||||||
|
.unsigned_b (unsigned_b_i),
|
||||||
|
|
||||||
|
.f_mode (f_mode_i),
|
||||||
|
.output_select (output_select_i),
|
||||||
|
.saturate_enable (saturate_enable_i),
|
||||||
|
.shift_right (shift_right_i),
|
||||||
|
.round (round_i),
|
||||||
|
.subtract (subtract_i),
|
||||||
|
.register_inputs (register_inputs_i)
|
||||||
|
);
|
||||||
|
|
||||||
|
endmodule
|
||||||
|
|
||||||
module dsp_t1_20x18x64_cfg_ports (
|
module dsp_t1_20x18x64_cfg_ports (
|
||||||
input [19:0] a_i,
|
input [19:0] a_i,
|
||||||
input [17:0] b_i,
|
input [17:0] b_i,
|
||||||
|
@ -42,30 +99,30 @@ module dsp_t1_20x18x64_cfg_ports (
|
||||||
parameter [19:0] COEFF_2 = 20'd0;
|
parameter [19:0] COEFF_2 = 20'd0;
|
||||||
parameter [19:0] COEFF_3 = 20'd0;
|
parameter [19:0] COEFF_3 = 20'd0;
|
||||||
|
|
||||||
QL_DSP2 # (
|
dsp_t1_20x18x64_cfg_ports_fracturable # (
|
||||||
.MODE_BITS ({COEFF_3, COEFF_2, COEFF_1, COEFF_0})
|
.COEFF_0 (COEFF_0),
|
||||||
|
.COEFF_1 (COEFF_1),
|
||||||
|
.COEFF_2 (COEFF_2),
|
||||||
|
.COEFF_3 (COEFF_3)
|
||||||
) _TECHMAP_REPLACE_ (
|
) _TECHMAP_REPLACE_ (
|
||||||
.a (a_i),
|
.a_i (a_i),
|
||||||
.b (b_i),
|
.b_i (b_i),
|
||||||
.acc_fir (acc_fir_i),
|
.acc_fir_i (acc_fir_i),
|
||||||
.z (z_o),
|
.z_o (z_o),
|
||||||
.dly_b (dly_b_o),
|
.dly_b_o (dly_b_o),
|
||||||
|
.clock_i (clock_i),
|
||||||
.clk (clock_i),
|
.reset_i (reset_i),
|
||||||
.reset (reset_i),
|
.feedback_i (feedback_i),
|
||||||
|
.load_acc_i (load_acc_i),
|
||||||
.feedback (feedback_i),
|
.unsigned_a_i (unsigned_a_i),
|
||||||
.load_acc (load_acc_i),
|
.unsigned_b_i (unsigned_b_i),
|
||||||
.unsigned_a (unsigned_a_i),
|
.output_select_i (output_select_i),
|
||||||
.unsigned_b (unsigned_b_i),
|
.saturate_enable_i (saturate_enable_i),
|
||||||
|
.shift_right_i (shift_right_i),
|
||||||
.f_mode (1'b0), // No fracturation
|
.round_i (round_i),
|
||||||
.output_select (output_select_i),
|
.subtract_i (subtract_i),
|
||||||
.saturate_enable (saturate_enable_i),
|
.register_inputs_i (register_inputs_i),
|
||||||
.shift_right (shift_right_i),
|
.f_mode_i (1'b0)
|
||||||
.round (round_i),
|
|
||||||
.subtract (subtract_i),
|
|
||||||
.register_inputs (register_inputs_i)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
endmodule
|
endmodule
|
||||||
|
@ -122,7 +179,7 @@ module dsp_t1_10x9x32_cfg_ports (
|
||||||
.unsigned_a (unsigned_a_i),
|
.unsigned_a (unsigned_a_i),
|
||||||
.unsigned_b (unsigned_b_i),
|
.unsigned_b (unsigned_b_i),
|
||||||
|
|
||||||
.f_mode (1'b1), // Enable fractuation, Use the lower half
|
.f_mode (1'b0),
|
||||||
.output_select (output_select_i),
|
.output_select (output_select_i),
|
||||||
.saturate_enable (saturate_enable_i),
|
.saturate_enable (saturate_enable_i),
|
||||||
.shift_right (shift_right_i),
|
.shift_right (shift_right_i),
|
80
techlibs/quicklogic/qlf_k6n10f/dspv1_sim_extra.v
Normal file
80
techlibs/quicklogic/qlf_k6n10f/dspv1_sim_extra.v
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
// Copyright 2020-2022 F4PGA Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
`timescale 1ps/1ps
|
||||||
|
|
||||||
|
`default_nettype none
|
||||||
|
|
||||||
|
// dsp_t1_20x18x64_cfg_ports but with input wire f_mode_i
|
||||||
|
// This is a yosys-specific extension beyond the vendor-provided model
|
||||||
|
module dsp_t1_20x18x64_cfg_ports_fracturable (
|
||||||
|
input wire [19:0] a_i,
|
||||||
|
input wire [17:0] b_i,
|
||||||
|
input wire [ 5:0] acc_fir_i,
|
||||||
|
output wire [37:0] z_o,
|
||||||
|
output wire [17:0] dly_b_o,
|
||||||
|
|
||||||
|
(* clkbuf_sink *)
|
||||||
|
input wire clock_i,
|
||||||
|
input wire reset_i,
|
||||||
|
|
||||||
|
input wire [ 2:0] feedback_i,
|
||||||
|
input wire load_acc_i,
|
||||||
|
input wire unsigned_a_i,
|
||||||
|
input wire unsigned_b_i,
|
||||||
|
|
||||||
|
input wire [ 2:0] output_select_i,
|
||||||
|
input wire saturate_enable_i,
|
||||||
|
input wire [ 5:0] shift_right_i,
|
||||||
|
input wire round_i,
|
||||||
|
input wire subtract_i,
|
||||||
|
input wire register_inputs_i,
|
||||||
|
input wire f_mode_i
|
||||||
|
);
|
||||||
|
|
||||||
|
parameter [19:0] COEFF_0 = 20'd0;
|
||||||
|
parameter [19:0] COEFF_1 = 20'd0;
|
||||||
|
parameter [19:0] COEFF_2 = 20'd0;
|
||||||
|
parameter [19:0] COEFF_3 = 20'd0;
|
||||||
|
|
||||||
|
QL_DSP2 #(
|
||||||
|
.MODE_BITS({COEFF_3, COEFF_2, COEFF_1, COEFF_0})
|
||||||
|
) dsp (
|
||||||
|
.a(a_i),
|
||||||
|
.b(b_i),
|
||||||
|
.z(z_o),
|
||||||
|
.dly_b(dly_b_o),
|
||||||
|
|
||||||
|
.f_mode(f_mode_i), // 20x18x64 DSP
|
||||||
|
|
||||||
|
.acc_fir(acc_fir_i),
|
||||||
|
.feedback(feedback_i),
|
||||||
|
.load_acc(load_acc_i),
|
||||||
|
|
||||||
|
.unsigned_a(unsigned_a_i),
|
||||||
|
.unsigned_b(unsigned_b_i),
|
||||||
|
|
||||||
|
.clk(clock_i),
|
||||||
|
.reset(reset_i),
|
||||||
|
|
||||||
|
.saturate_enable(saturate_enable_i),
|
||||||
|
.output_select(output_select_i),
|
||||||
|
.round(round_i),
|
||||||
|
.shift_right(shift_right_i),
|
||||||
|
.subtract(subtract_i),
|
||||||
|
.register_inputs(register_inputs_i)
|
||||||
|
);
|
||||||
|
endmodule
|
142
techlibs/quicklogic/qlf_k6n10f/dspv2_final_map.v
Normal file
142
techlibs/quicklogic/qlf_k6n10f/dspv2_final_map.v
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
// Derived from dspv2_sim.v
|
||||||
|
|
||||||
|
module dspv2_32x18x64_cfg_ports (
|
||||||
|
input wire [31:0] a_i,
|
||||||
|
input wire [17:0] b_i,
|
||||||
|
input wire [17:0] c_i,
|
||||||
|
output wire [49:0] z_o,
|
||||||
|
|
||||||
|
input wire clock_i,
|
||||||
|
input wire reset_i,
|
||||||
|
input wire acc_reset_i,
|
||||||
|
|
||||||
|
input wire [ 2:0] feedback_i,
|
||||||
|
input wire load_acc_i,
|
||||||
|
input wire [ 2:0] output_select_i,
|
||||||
|
|
||||||
|
input wire [31:0] a_cin_i,
|
||||||
|
input wire [17:0] b_cin_i,
|
||||||
|
input wire [49:0] z_cin_i,
|
||||||
|
|
||||||
|
output wire [31:0] a_cout_o,
|
||||||
|
output wire [17:0] b_cout_o,
|
||||||
|
output wire [49:0] z_cout_o
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
parameter [31:0] COEFF_0 = 32'h0;
|
||||||
|
parameter [5:0] ACC_FIR = 6'h0;
|
||||||
|
parameter [2:0] ROUND = 3'h0;
|
||||||
|
parameter [4:0] ZC_SHIFT = 5'h0;
|
||||||
|
parameter [4:0] ZREG_SHIFT = 5'h0;
|
||||||
|
parameter [5:0] SHIFT_REG = 6'h0;
|
||||||
|
parameter SATURATE = 1'b0;
|
||||||
|
parameter SUBTRACT = 1'b0;
|
||||||
|
parameter PRE_ADD = 1'b0;
|
||||||
|
parameter A_SEL = 1'b0;
|
||||||
|
parameter A_REG = 1'b0;
|
||||||
|
parameter B_SEL = 1'b0;
|
||||||
|
parameter B_REG = 1'b0;
|
||||||
|
parameter C_REG = 1'b0;
|
||||||
|
parameter BC_REG = 1'b0;
|
||||||
|
parameter M_REG = 1'b0;
|
||||||
|
parameter ZCIN_REG = 1'b0;
|
||||||
|
parameter FRAC_MODE = 1'b0; // 32x18x64 DSP
|
||||||
|
|
||||||
|
(* is_inferred *)
|
||||||
|
QL_DSPV2 #(
|
||||||
|
.MODE_BITS({FRAC_MODE,3'b000,ZCIN_REG,M_REG,BC_REG,C_REG,B_REG,B_SEL,A_REG,A_SEL,PRE_ADD,SUBTRACT,SATURATE,SHIFT_REG,ZREG_SHIFT,ZC_SHIFT,ROUND,ACC_FIR,COEFF_0})
|
||||||
|
) _TECHMAP_REPLACE_ (
|
||||||
|
.a(a_i),
|
||||||
|
.b(b_i),
|
||||||
|
.c(c_i),
|
||||||
|
.load_acc(load_acc_i),
|
||||||
|
.feedback(feedback_i),
|
||||||
|
.output_select(output_select_i),
|
||||||
|
.z(z_o),
|
||||||
|
|
||||||
|
.clk(clock_i),
|
||||||
|
.reset(reset_i),
|
||||||
|
.acc_reset(acc_reset_i),
|
||||||
|
|
||||||
|
.a_cin(a_cin_i),
|
||||||
|
.b_cin(b_cin_i),
|
||||||
|
.z_cin(z_cin_i),
|
||||||
|
|
||||||
|
.z_cout(z_cout_o),
|
||||||
|
.a_cout(a_cout_o),
|
||||||
|
.b_cout(b_cout_o)
|
||||||
|
);
|
||||||
|
|
||||||
|
endmodule
|
||||||
|
|
||||||
|
module dspv2_16x9x32_cfg_ports (
|
||||||
|
input wire [15:0] a_i,
|
||||||
|
input wire [8:0] b_i,
|
||||||
|
input wire [8:0] c_i,
|
||||||
|
output wire [24:0] z_o,
|
||||||
|
|
||||||
|
input wire clock_i,
|
||||||
|
input wire reset_i,
|
||||||
|
input wire acc_reset_i,
|
||||||
|
|
||||||
|
input wire [ 2:0] feedback_i,
|
||||||
|
input wire load_acc_i,
|
||||||
|
input wire [ 2:0] output_select_i,
|
||||||
|
|
||||||
|
input wire [15:0] a_cin_i,
|
||||||
|
input wire [8:0] b_cin_i,
|
||||||
|
input wire [24:0] z_cin_i,
|
||||||
|
|
||||||
|
output wire [15:0] a_cout_o,
|
||||||
|
output wire [8:0] b_cout_o,
|
||||||
|
output wire [24:0] z_cout_o
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
parameter [15:0] COEFF_0 = 16'h0;
|
||||||
|
parameter [5:0] ACC_FIR = 6'h0;
|
||||||
|
parameter [2:0] ROUND = 3'h0;
|
||||||
|
parameter [4:0] ZC_SHIFT = 5'h0;
|
||||||
|
parameter [4:0] ZREG_SHIFT = 5'h0;
|
||||||
|
parameter [5:0] SHIFT_REG = 6'h0;
|
||||||
|
parameter SATURATE = 1'b0;
|
||||||
|
parameter SUBTRACT = 1'b0;
|
||||||
|
parameter PRE_ADD = 1'b0;
|
||||||
|
parameter A_SEL = 1'b0;
|
||||||
|
parameter A_REG = 1'b0;
|
||||||
|
parameter B_SEL = 1'b0;
|
||||||
|
parameter B_REG = 1'b0;
|
||||||
|
parameter C_REG = 1'b0;
|
||||||
|
parameter BC_REG = 1'b0;
|
||||||
|
parameter M_REG = 1'b0;
|
||||||
|
parameter ZCIN_REG = 1'b0;
|
||||||
|
parameter FRAC_MODE = 1'b1; // 16x9x32 DSP
|
||||||
|
|
||||||
|
(* is_inferred *)
|
||||||
|
QL_DSPV2 #(
|
||||||
|
.MODE_BITS({FRAC_MODE,3'b000,ZCIN_REG,M_REG,BC_REG,C_REG,B_REG,B_SEL,A_REG,A_SEL,PRE_ADD,SUBTRACT,SATURATE,SHIFT_REG,ZREG_SHIFT,ZC_SHIFT,ROUND,ACC_FIR,16'h0,COEFF_0})
|
||||||
|
) _TECHMAP_REPLACE_ (
|
||||||
|
.a(a_i),
|
||||||
|
.b(b_i),
|
||||||
|
.c(c_i),
|
||||||
|
.load_acc(load_acc_i),
|
||||||
|
.feedback(feedback_i),
|
||||||
|
.output_select(output_select_i),
|
||||||
|
.z(z_o),
|
||||||
|
|
||||||
|
.clk(clock_i),
|
||||||
|
.reset(reset_i),
|
||||||
|
.acc_reset(acc_reset_i),
|
||||||
|
|
||||||
|
.a_cin(a_cin_i),
|
||||||
|
.b_cin(b_cin_i),
|
||||||
|
.z_cin(z_cin_i),
|
||||||
|
|
||||||
|
.z_cout(z_cout_o),
|
||||||
|
.a_cout(a_cout_o),
|
||||||
|
.b_cout(b_cout_o)
|
||||||
|
);
|
||||||
|
|
||||||
|
endmodule
|
||||||
|
|
59
techlibs/quicklogic/qlf_k6n10f/dspv2_map.v
Normal file
59
techlibs/quicklogic/qlf_k6n10f/dspv2_map.v
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
module \$__MUL32X18 (input [31:0] A, input [17:0] B, output [49:0] Y);
|
||||||
|
parameter A_SIGNED = 0;
|
||||||
|
parameter B_SIGNED = 0;
|
||||||
|
parameter A_WIDTH = 32;
|
||||||
|
parameter B_WIDTH = 18;
|
||||||
|
parameter Y_WIDTH = 50;
|
||||||
|
|
||||||
|
dspv2_32x18x64_cfg_ports _TECHMAP_REPLACE_ (
|
||||||
|
.a_i(A),
|
||||||
|
.b_i(B),
|
||||||
|
.c_i(18'd0),
|
||||||
|
.z_o(Y),
|
||||||
|
|
||||||
|
.clock_i(1'bx),
|
||||||
|
.reset_i(1'bx),
|
||||||
|
.acc_reset_i(1'b0),
|
||||||
|
.feedback_i(3'd0),
|
||||||
|
.load_acc_i(1'b0),
|
||||||
|
.output_select_i(3'd0),
|
||||||
|
.a_cin_i(32'dx),
|
||||||
|
.b_cin_i(18'dx),
|
||||||
|
.z_cin_i(50'dx),
|
||||||
|
/* TODO: connect to dummy wires?
|
||||||
|
.a_cout_o(),
|
||||||
|
.b_cout_o(),
|
||||||
|
.z_cout_o(),
|
||||||
|
*/
|
||||||
|
);
|
||||||
|
endmodule
|
||||||
|
|
||||||
|
module \$__MUL16X9 (input [15:0] A, input [8:0] B, output [24:0] Y);
|
||||||
|
parameter A_SIGNED = 0;
|
||||||
|
parameter B_SIGNED = 0;
|
||||||
|
parameter A_WIDTH = 16;
|
||||||
|
parameter B_WIDTH = 9;
|
||||||
|
parameter Y_WIDTH = 25;
|
||||||
|
|
||||||
|
dspv2_16x9x32_cfg_ports _TECHMAP_REPLACE_ (
|
||||||
|
.a_i(A),
|
||||||
|
.b_i(B),
|
||||||
|
.c_i(9'd0),
|
||||||
|
.z_o(Y),
|
||||||
|
|
||||||
|
.clock_i(1'bx),
|
||||||
|
.reset_i(1'bx),
|
||||||
|
.acc_reset_i(1'b0),
|
||||||
|
.feedback_i(3'd0),
|
||||||
|
.load_acc_i(1'b0),
|
||||||
|
.output_select_i(3'd0),
|
||||||
|
.a_cin_i(32'dx),
|
||||||
|
.b_cin_i(18'dx),
|
||||||
|
.z_cin_i(50'dx),
|
||||||
|
/* TODO: connect to dummy wires?
|
||||||
|
.a_cout_o(),
|
||||||
|
.b_cout_o(),
|
||||||
|
.z_cout_o(),
|
||||||
|
*/
|
||||||
|
);
|
||||||
|
endmodule
|
1437
techlibs/quicklogic/qlf_k6n10f/dspv2_sim.v
Normal file
1437
techlibs/quicklogic/qlf_k6n10f/dspv2_sim.v
Normal file
File diff suppressed because it is too large
Load diff
|
@ -27,6 +27,12 @@ PRIVATE_NAMESPACE_BEGIN
|
||||||
|
|
||||||
struct SynthQuickLogicPass : public ScriptPass {
|
struct SynthQuickLogicPass : public ScriptPass {
|
||||||
|
|
||||||
|
enum DSPKind {
|
||||||
|
None,
|
||||||
|
V1,
|
||||||
|
V2,
|
||||||
|
};
|
||||||
|
|
||||||
SynthQuickLogicPass() : ScriptPass("synth_quicklogic", "Synthesis for QuickLogic FPGAs") {}
|
SynthQuickLogicPass() : ScriptPass("synth_quicklogic", "Synthesis for QuickLogic FPGAs") {}
|
||||||
|
|
||||||
void help() override
|
void help() override
|
||||||
|
@ -50,6 +56,10 @@ struct SynthQuickLogicPass : public ScriptPass {
|
||||||
log(" do not use dsp_t1_* to implement multipliers and associated logic\n");
|
log(" do not use dsp_t1_* to implement multipliers and associated logic\n");
|
||||||
log(" (qlf_k6n10f only).\n");
|
log(" (qlf_k6n10f only).\n");
|
||||||
log("\n");
|
log("\n");
|
||||||
|
log(" -dspv2\n");
|
||||||
|
log(" synthesize for the v2 DSP block model instead of v1\n");
|
||||||
|
log(" (qlf_k6n10f only).\n");
|
||||||
|
log("\n");
|
||||||
log(" -nocarry\n");
|
log(" -nocarry\n");
|
||||||
log(" do not use adder_carry cells in output netlist.\n");
|
log(" do not use adder_carry cells in output netlist.\n");
|
||||||
log("\n");
|
log("\n");
|
||||||
|
@ -78,7 +88,8 @@ struct SynthQuickLogicPass : public ScriptPass {
|
||||||
}
|
}
|
||||||
|
|
||||||
string top_opt, blif_file, edif_file, family, currmodule, verilog_file, lib_path;
|
string top_opt, blif_file, edif_file, family, currmodule, verilog_file, lib_path;
|
||||||
bool abc9, inferAdder, nobram, bramTypes, dsp, ioff;
|
bool abc9, inferAdder, nobram, bramTypes, ioff;
|
||||||
|
DSPKind dsp;
|
||||||
|
|
||||||
void clear_flags() override
|
void clear_flags() override
|
||||||
{
|
{
|
||||||
|
@ -93,7 +104,7 @@ struct SynthQuickLogicPass : public ScriptPass {
|
||||||
nobram = false;
|
nobram = false;
|
||||||
bramTypes = false;
|
bramTypes = false;
|
||||||
lib_path = "+/quicklogic/";
|
lib_path = "+/quicklogic/";
|
||||||
dsp = true;
|
dsp = V1;
|
||||||
ioff = true;
|
ioff = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,7 +167,11 @@ struct SynthQuickLogicPass : public ScriptPass {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (args[argidx] == "-nodsp" || args[argidx] == "-no_dsp") {
|
if (args[argidx] == "-nodsp" || args[argidx] == "-no_dsp") {
|
||||||
dsp = false;
|
dsp = None;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (args[argidx] == "-dspv2") {
|
||||||
|
dsp = V2;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (args[argidx] == "-noioff") {
|
if (args[argidx] == "-noioff") {
|
||||||
|
@ -198,8 +213,11 @@ struct SynthQuickLogicPass : public ScriptPass {
|
||||||
read_simlibs += stringf(" %sqlf_k6n10f/brams_sim.v", lib_path.c_str());
|
read_simlibs += stringf(" %sqlf_k6n10f/brams_sim.v", lib_path.c_str());
|
||||||
if (bramTypes)
|
if (bramTypes)
|
||||||
read_simlibs += stringf(" %sqlf_k6n10f/bram_types_sim.v", lib_path.c_str());
|
read_simlibs += stringf(" %sqlf_k6n10f/bram_types_sim.v", lib_path.c_str());
|
||||||
if (dsp)
|
if (dsp == V1) {
|
||||||
read_simlibs += stringf(" %sqlf_k6n10f/dsp_sim.v", lib_path.c_str());
|
read_simlibs += stringf(" %sqlf_k6n10f/dspv1_sim.v", lib_path.c_str());
|
||||||
|
read_simlibs += stringf(" %sqlf_k6n10f/dspv1_sim_extra.v", lib_path.c_str());
|
||||||
|
} else if (dsp == V2)
|
||||||
|
read_simlibs += stringf(" %sqlf_k6n10f/dspv2_sim.v", lib_path.c_str());
|
||||||
}
|
}
|
||||||
run(read_simlibs);
|
run(read_simlibs);
|
||||||
run(stringf("hierarchy -check %s", help_mode ? "-top <top>" : top_opt.c_str()));
|
run(stringf("hierarchy -check %s", help_mode ? "-top <top>" : top_opt.c_str()));
|
||||||
|
@ -225,18 +243,35 @@ struct SynthQuickLogicPass : public ScriptPass {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (check_label("map_dsp", "(for qlf_k6n10f, skip if -nodsp)")
|
if (check_label("map_dsp", "(for qlf_k6n10f, skip if -nodsp)")
|
||||||
&& ((dsp && family == "qlf_k6n10f") || help_mode)) {
|
&& (((dsp != None) && family == "qlf_k6n10f") || help_mode)) {
|
||||||
run("wreduce t:$mul");
|
run("wreduce t:$mul");
|
||||||
run("ql_dsp_macc");
|
|
||||||
|
|
||||||
run("techmap -map +/mul2dsp.v -D DSP_A_MAXWIDTH=20 -D DSP_B_MAXWIDTH=18 -D DSP_A_MINWIDTH=11 -D DSP_B_MINWIDTH=10 -D DSP_NAME=$__QL_MUL20X18");
|
if (dsp == V1) {
|
||||||
run("techmap -map +/mul2dsp.v -D DSP_A_MAXWIDTH=10 -D DSP_B_MAXWIDTH=9 -D DSP_A_MINWIDTH=4 -D DSP_B_MINWIDTH=4 -D DSP_NAME=$__QL_MUL10X9");
|
run("ql_dsp_macc");
|
||||||
run("chtype -set $mul t:$__soft_mul");
|
|
||||||
|
|
||||||
run("techmap -map " + lib_path + family + "/dsp_map.v -D USE_DSP_CFG_PARAMS=0");
|
run("techmap -map +/mul2dsp.v -D DSP_A_MAXWIDTH=20 -D DSP_B_MAXWIDTH=18 -D DSP_A_MINWIDTH=11 -D DSP_B_MINWIDTH=10 -D DSP_NAME=$__QL_MUL20X18");
|
||||||
run("ql_dsp_simd");
|
run("techmap -map +/mul2dsp.v -D DSP_A_MAXWIDTH=10 -D DSP_B_MAXWIDTH=9 -D DSP_A_MINWIDTH=4 -D DSP_B_MINWIDTH=4 -D DSP_NAME=$__QL_MUL10X9");
|
||||||
run("techmap -map " + lib_path + family + "/dsp_final_map.v");
|
run("chtype -set $mul t:$__soft_mul");
|
||||||
run("ql_dsp_io_regs");
|
|
||||||
|
run("techmap -map " + lib_path + family + "/dspv1_map.v -D USE_DSP_CFG_PARAMS=0");
|
||||||
|
run("ql_dsp_simd");
|
||||||
|
run("techmap -map " + lib_path + family + "/dspv1_final_map.v");
|
||||||
|
run("ql_dsp_io_regs");
|
||||||
|
} else if (dsp == V2) {
|
||||||
|
run("ql_dsp_macc -dspv2");
|
||||||
|
run("techmap -map +/mul2dsp.v -map " + lib_path + family + "/dspv2_map.v -D USE_DSP_CFG_PARAMS=0 -D DSP_SIGNEDONLY "
|
||||||
|
"-D DSP_A_MAXWIDTH=32 -D DSP_B_MAXWIDTH=18 -D DSP_A_MINWIDTH=10 -D DSP_B_MINWIDTH=10 -D DSP_NAME=$__MUL32X18");
|
||||||
|
run("chtype -set $mul t:$__soft_mul");
|
||||||
|
run("techmap -map +/mul2dsp.v -map " + lib_path + family + "/dspv2_map.v -D USE_DSP_CFG_PARAMS=0 -D DSP_SIGNEDONLY "
|
||||||
|
"-D DSP_A_MAXWIDTH=16 -D DSP_B_MAXWIDTH=9 -D DSP_A_MINWIDTH=4 -D DSP_B_MINWIDTH=4 -D DSP_NAME=$__MUL16X9");
|
||||||
|
run("chtype -set $mul t:$__soft_mul");
|
||||||
|
run("ql_dsp");
|
||||||
|
run("ql_dsp_simd -dspv2");
|
||||||
|
run("techmap -map " + lib_path + family + "/dspv2_final_map.v");
|
||||||
|
run("ql_dsp_io_regs -dspv2");
|
||||||
|
} else {
|
||||||
|
log_assert(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (check_label("coarse")) {
|
if (check_label("coarse")) {
|
||||||
|
|
|
@ -21,7 +21,7 @@ EOF
|
||||||
design -save ast
|
design -save ast
|
||||||
proc
|
proc
|
||||||
wreduce
|
wreduce
|
||||||
#equiv_opt -async2sync -map +/quicklogic/qlf_k6n10f/dsp_sim.v synth_quicklogic -family qlf_k6n10f
|
#equiv_opt -async2sync -map +/quicklogic/qlf_k6n10f/dspv1_sim.v synth_quicklogic -family qlf_k6n10f
|
||||||
#design -load postopt
|
#design -load postopt
|
||||||
synth_quicklogic -family qlf_k6n10f
|
synth_quicklogic -family qlf_k6n10f
|
||||||
cd top
|
cd top
|
||||||
|
@ -114,8 +114,8 @@ always @(posedge clk) begin
|
||||||
end
|
end
|
||||||
endmodule
|
endmodule
|
||||||
EOF
|
EOF
|
||||||
read_verilog +/quicklogic/qlf_k6n10f/dsp_sim.v
|
read_verilog +/quicklogic/qlf_k6n10f/dspv1_sim.v
|
||||||
hierarchy -top testbench
|
hierarchy -top testbench
|
||||||
proc
|
proc
|
||||||
async2sync
|
async2sync
|
||||||
sim -assert -q -clock clk -n 20
|
sim -q -clock clk -n 20 -assert
|
65
tests/arch/quicklogic/qlf_k6n10f/dspv1_simd.ys
Normal file
65
tests/arch/quicklogic/qlf_k6n10f/dspv1_simd.ys
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
read_verilog <<EOT
|
||||||
|
module foo(
|
||||||
|
input [7:0] A,
|
||||||
|
input [7:0] B,
|
||||||
|
input [7:0] C,
|
||||||
|
input [7:0] D,
|
||||||
|
output reg [7:0] X,
|
||||||
|
output reg [7:0] Y);
|
||||||
|
assign X = A * B;
|
||||||
|
assign Y = C * D;
|
||||||
|
endmodule
|
||||||
|
EOT
|
||||||
|
|
||||||
|
synth_quicklogic -run :map_dsp
|
||||||
|
|
||||||
|
# this is just taken from map_dsp step
|
||||||
|
wreduce t:$mul
|
||||||
|
ql_dsp_macc
|
||||||
|
techmap -map +/mul2dsp.v -D DSP_A_MAXWIDTH=20 -D DSP_B_MAXWIDTH=18 -D DSP_A_MINWIDTH=11 -D DSP_B_MINWIDTH=10 -D DSP_NAME=$__QL_MUL20X18
|
||||||
|
chtype -set $mul t:$__soft_mul
|
||||||
|
techmap -map +/mul2dsp.v -D DSP_A_MAXWIDTH=10 -D DSP_B_MAXWIDTH=9 -D DSP_A_MINWIDTH=4 -D DSP_B_MINWIDTH=4 -D DSP_NAME=$__QL_MUL10X9
|
||||||
|
techmap -map +/quicklogic/qlf_k6n10f/dspv1_map.v -D USE_DSP_CFG_PARAMS=0
|
||||||
|
read_verilog -lib +/quicklogic/qlf_k6n10f/dspv1_sim.v
|
||||||
|
read_verilog -lib +/quicklogic/qlf_k6n10f/dspv1_sim_extra.v
|
||||||
|
|
||||||
|
select -assert-count 2 t:dsp_t1_10x9x32_cfg_ports
|
||||||
|
select -assert-count 0 t:dsp_t1_20x18x64_cfg_ports_fracturable
|
||||||
|
equiv_opt -assert -async2sync -map +/quicklogic/qlf_k6n10f/dspv1_sim.v -map +/quicklogic/qlf_k6n10f/dspv1_sim_extra.v ql_dsp_simd
|
||||||
|
design -load postopt
|
||||||
|
select -assert-count 0 t:dsp_t1_10x9x32_cfg_ports
|
||||||
|
select -assert-count 1 t:dsp_t1_20x18x64_cfg_ports_fracturable
|
||||||
|
|
||||||
|
design -reset
|
||||||
|
|
||||||
|
read_verilog <<EOT
|
||||||
|
module foo(
|
||||||
|
input [7:0] A,
|
||||||
|
input [7:0] B,
|
||||||
|
input [7:0] C,
|
||||||
|
input [7:0] D,
|
||||||
|
input [7:0] E,
|
||||||
|
output reg [7:0] X,
|
||||||
|
output reg [7:0] Y);
|
||||||
|
assign X = A * B;
|
||||||
|
assign Y = C * (D + E); // <-- look here
|
||||||
|
endmodule
|
||||||
|
EOT
|
||||||
|
|
||||||
|
synth_quicklogic -run :map_dsp
|
||||||
|
|
||||||
|
wreduce t:$mul
|
||||||
|
ql_dsp_macc
|
||||||
|
techmap -map +/mul2dsp.v -D DSP_A_MAXWIDTH=20 -D DSP_B_MAXWIDTH=18 -D DSP_A_MINWIDTH=11 -D DSP_B_MINWIDTH=10 -D DSP_NAME=$__QL_MUL20X18
|
||||||
|
chtype -set $mul t:$__soft_mul
|
||||||
|
techmap -map +/mul2dsp.v -D DSP_A_MAXWIDTH=10 -D DSP_B_MAXWIDTH=9 -D DSP_A_MINWIDTH=4 -D DSP_B_MINWIDTH=4 -D DSP_NAME=$__QL_MUL10X9
|
||||||
|
techmap -map +/quicklogic/qlf_k6n10f/dspv1_map.v -D USE_DSP_CFG_PARAMS=0
|
||||||
|
read_verilog -lib +/quicklogic/qlf_k6n10f/dspv1_sim.v
|
||||||
|
read_verilog -lib +/quicklogic/qlf_k6n10f/dspv1_sim_extra.v
|
||||||
|
|
||||||
|
select -assert-count 2 t:dsp_t1_10x9x32_cfg_ports
|
||||||
|
select -assert-count 0 t:dsp_t1_20x18x64_cfg_ports_fracturable
|
||||||
|
equiv_opt -assert -async2sync -map +/quicklogic/qlf_k6n10f/dspv1_sim.v -map +/quicklogic/qlf_k6n10f/dspv1_sim_extra.v ql_dsp_simd
|
||||||
|
design -load postopt
|
||||||
|
select -assert-count 0 t:dsp_t1_10x9x32_cfg_ports
|
||||||
|
select -assert-count 1 t:dsp_t1_20x18x64_cfg_ports_fracturable
|
174
tests/arch/quicklogic/qlf_k6n10f/dspv2_full_dsp_flow.ys
Normal file
174
tests/arch/quicklogic/qlf_k6n10f/dspv2_full_dsp_flow.ys
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
read_verilog <<EOF
|
||||||
|
module top(input [6:0] a, input [4:0] b, input [4:0] c, input [4:0] d, input [4:0] e, input [4:0] f, output reg [7:0] o);
|
||||||
|
assign o = (a * b) + (c * d) + (e * f);
|
||||||
|
endmodule
|
||||||
|
EOF
|
||||||
|
design -save gold
|
||||||
|
|
||||||
|
synth_quicklogic -family qlf_k6n10f -dspv2 -run :coarse
|
||||||
|
check -assert
|
||||||
|
opt_clean
|
||||||
|
select -assert-count 1 top/t:QL_DSPV2_MULT
|
||||||
|
select -assert-count 2 top/t:QL_DSPV2_MULTADD
|
||||||
|
read_verilog +/quicklogic/qlf_k6n10f/dspv2_sim.v
|
||||||
|
prep -flatten -top top
|
||||||
|
design -save gate
|
||||||
|
|
||||||
|
design -reset
|
||||||
|
design -copy-from gate -as gate top
|
||||||
|
design -copy-from gold -as gold top
|
||||||
|
async2sync
|
||||||
|
equiv_make gold gate equiv
|
||||||
|
opt -fast equiv
|
||||||
|
equiv_induct equiv
|
||||||
|
equiv_status -assert equiv
|
||||||
|
|
||||||
|
design -reset
|
||||||
|
read_verilog <<EOF
|
||||||
|
module top(input signed [3:0] a, input signed [3:0] b, input signed [3:0] c, input signed [3:0] d, output signed [4:0] o);
|
||||||
|
assign o = (a * b) + (c * d);
|
||||||
|
endmodule
|
||||||
|
EOF
|
||||||
|
design -save gold
|
||||||
|
|
||||||
|
synth_quicklogic -family qlf_k6n10f -dspv2 -run :coarse
|
||||||
|
check -assert
|
||||||
|
opt_clean
|
||||||
|
stat
|
||||||
|
select -assert-count 1 top/t:QL_DSPV2_MULT
|
||||||
|
select -assert-count 1 top/t:QL_DSPV2_MULTADD
|
||||||
|
read_verilog +/quicklogic/qlf_k6n10f/dspv2_sim.v
|
||||||
|
prep -flatten -top top
|
||||||
|
design -save gate
|
||||||
|
|
||||||
|
design -reset
|
||||||
|
design -copy-from gate -as gate top
|
||||||
|
design -copy-from gold -as gold top
|
||||||
|
async2sync
|
||||||
|
equiv_make gold gate equiv
|
||||||
|
opt -fast equiv
|
||||||
|
equiv_induct equiv
|
||||||
|
equiv_status -assert equiv
|
||||||
|
|
||||||
|
design -reset
|
||||||
|
read_verilog <<EOF
|
||||||
|
module top(input signed [16:0] ar, input signed [16:0] ai, input signed [16:0] br, input signed [16:0] bi, output reg signed [33:0] qr, output reg signed [33:0] qi, input clk);
|
||||||
|
reg signed [33:0] rr, ri, ir, ii;
|
||||||
|
always @(posedge clk) begin
|
||||||
|
rr <= ar * br;
|
||||||
|
ri <= ar * bi;
|
||||||
|
ir <= ai * br;
|
||||||
|
ii <= ai * bi;
|
||||||
|
qr <= rr - ii;
|
||||||
|
qi <= ir + ri;
|
||||||
|
end
|
||||||
|
endmodule
|
||||||
|
EOF
|
||||||
|
|
||||||
|
synth_quicklogic -family qlf_k6n10f -dspv2 -run :coarse
|
||||||
|
check -assert
|
||||||
|
opt_clean
|
||||||
|
select -assert-count 2 top/t:QL_DSPV2_MULT_REGOUT
|
||||||
|
select -assert-count 2 top/t:QL_DSPV2_MULTADD_REGOUT
|
||||||
|
|
||||||
|
design -reset
|
||||||
|
read_verilog <<EOF
|
||||||
|
module top(input signed [16:0] ar, input signed [16:0] ai, input signed [16:0] br, input signed [16:0] bi, output reg signed [33:0] qr, output reg signed [33:0] qi, input clk);
|
||||||
|
reg signed [16:0] ar_, ai_, br_, bi_;
|
||||||
|
always @(posedge clk) begin
|
||||||
|
ar_ <= ar;
|
||||||
|
ai_ <= ai;
|
||||||
|
br_ <= br;
|
||||||
|
bi_ <= bi;
|
||||||
|
end
|
||||||
|
|
||||||
|
reg signed [33:0] rr, ri, ir, ii;
|
||||||
|
always @(posedge clk) begin
|
||||||
|
rr <= ar_ * br_;
|
||||||
|
ri <= ar_ * bi_;
|
||||||
|
ir <= ai_ * br_;
|
||||||
|
ii <= ai_ * bi_;
|
||||||
|
qr <= rr - ii;
|
||||||
|
qi <= ir + ri;
|
||||||
|
end
|
||||||
|
endmodule
|
||||||
|
EOF
|
||||||
|
|
||||||
|
synth_quicklogic -family qlf_k6n10f -dspv2 -run :coarse
|
||||||
|
check -assert
|
||||||
|
opt_clean -purge
|
||||||
|
select -assert-count 2 top/t:QL_DSPV2_MULT_REGIN_REGOUT
|
||||||
|
select -assert-count 2 top/t:QL_DSPV2_MULTADD_REGIN_REGOUT
|
||||||
|
|
||||||
|
design -reset
|
||||||
|
read_verilog <<EOF
|
||||||
|
module top(input [6:0] a, input [4:0] b, input [4:0] c, input [4:0] d, output [7:0] e, output [7:0] f);
|
||||||
|
assign e = a * b;
|
||||||
|
assign f = c * d;
|
||||||
|
endmodule
|
||||||
|
EOF
|
||||||
|
design -save gold
|
||||||
|
|
||||||
|
synth_quicklogic -family qlf_k6n10f -dspv2 -run :coarse
|
||||||
|
check -assert
|
||||||
|
opt_clean
|
||||||
|
select -assert-count 1 top/t:QL_DSPV2_MULT
|
||||||
|
select -assert-count 1 top/c:*
|
||||||
|
read_verilog +/quicklogic/qlf_k6n10f/dspv2_sim.v
|
||||||
|
prep -flatten -top top
|
||||||
|
design -save gate
|
||||||
|
|
||||||
|
design -reset
|
||||||
|
design -copy-from gate -as gate top
|
||||||
|
design -copy-from gold -as gold top
|
||||||
|
async2sync
|
||||||
|
equiv_make gold gate equiv
|
||||||
|
opt -fast equiv
|
||||||
|
equiv_induct equiv
|
||||||
|
equiv_status -assert equiv
|
||||||
|
|
||||||
|
design -reset
|
||||||
|
read_verilog <<EOF
|
||||||
|
module top(input signed [6:0] a, input signed [4:0] b, input [4:0] c, input [4:0] d, output signed [7:0] e, output [7:0] f);
|
||||||
|
assign e = a * b;
|
||||||
|
assign f = c * d;
|
||||||
|
endmodule
|
||||||
|
EOF
|
||||||
|
design -save gold
|
||||||
|
|
||||||
|
synth_quicklogic -family qlf_k6n10f -dspv2 -run :coarse
|
||||||
|
check -assert
|
||||||
|
opt_clean
|
||||||
|
select -assert-count 1 top/t:QL_DSPV2_MULT
|
||||||
|
select -assert-count 1 top/c:*
|
||||||
|
read_verilog +/quicklogic/qlf_k6n10f/dspv2_sim.v
|
||||||
|
prep -flatten -top top
|
||||||
|
design -save gate
|
||||||
|
|
||||||
|
design -reset
|
||||||
|
design -copy-from gate -as gate top
|
||||||
|
design -copy-from gold -as gold top
|
||||||
|
async2sync
|
||||||
|
equiv_make gold gate equiv
|
||||||
|
opt -fast equiv
|
||||||
|
equiv_induct equiv
|
||||||
|
equiv_status -assert equiv
|
||||||
|
|
||||||
|
design -reset
|
||||||
|
read_verilog <<EOF
|
||||||
|
module top(input [16:0] a, input [16:0] b, output reg [33:0] o, input clk, input [2:0] j);
|
||||||
|
reg [16:0] ar;
|
||||||
|
reg [16:0] br;
|
||||||
|
|
||||||
|
always @(posedge clk) begin
|
||||||
|
ar <= a;
|
||||||
|
br <= b;
|
||||||
|
o <= {ar * br, j};
|
||||||
|
end
|
||||||
|
endmodule
|
||||||
|
EOF
|
||||||
|
|
||||||
|
synth_quicklogic -family qlf_k6n10f -dspv2 -run :coarse
|
||||||
|
check -assert
|
||||||
|
opt_clean
|
||||||
|
select -assert-count 1 top/t:QL_DSPV2_MULT_REGIN_REGOUT
|
45
tests/arch/quicklogic/qlf_k6n10f/dspv2_macc.tcl
Normal file
45
tests/arch/quicklogic/qlf_k6n10f/dspv2_macc.tcl
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
yosys -import
|
||||||
|
|
||||||
|
proc testcase {top} {
|
||||||
|
log -header "Testcase $top"
|
||||||
|
log -push
|
||||||
|
|
||||||
|
design -load ast
|
||||||
|
prep -top $top
|
||||||
|
design -save gold
|
||||||
|
|
||||||
|
design -load ast
|
||||||
|
hierarchy -top $top
|
||||||
|
synth_quicklogic -family qlf_k6n10f -dspv2 -run :coarse
|
||||||
|
opt_clean
|
||||||
|
select -assert-none t:\$mul
|
||||||
|
stat
|
||||||
|
dump $top
|
||||||
|
select -assert-count 1 t:QL_DSPV2_MULTACC
|
||||||
|
read_verilog +/quicklogic/qlf_k6n10f/dspv2_sim.v
|
||||||
|
prep -flatten -top $top
|
||||||
|
design -save gate
|
||||||
|
|
||||||
|
design -clear
|
||||||
|
design -copy-from gate -as gate A:top
|
||||||
|
design -copy-from gold -as gold A:top
|
||||||
|
async2sync
|
||||||
|
equiv_make gold gate equiv
|
||||||
|
opt -fast equiv
|
||||||
|
equiv_induct equiv
|
||||||
|
equiv_status -assert equiv
|
||||||
|
|
||||||
|
log -pop
|
||||||
|
}
|
||||||
|
|
||||||
|
read_verilog dspv2_macc.v
|
||||||
|
design -save ast
|
||||||
|
|
||||||
|
testcase "macc_simple"
|
||||||
|
testcase "macc_simple_clr"
|
||||||
|
testcase "macc_simple_arst"
|
||||||
|
testcase "macc_simple_ena"
|
||||||
|
testcase "macc_simple_arst_clr_ena"
|
||||||
|
testcase "macc_simple_preacc_clr"
|
||||||
|
testcase "macc_simple_signed"
|
||||||
|
testcase "macc_simple_signed_subtract"
|
145
tests/arch/quicklogic/qlf_k6n10f/dspv2_macc.v
Normal file
145
tests/arch/quicklogic/qlf_k6n10f/dspv2_macc.v
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
// Copyright 2020-2022 F4PGA Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
module macc_simple (
|
||||||
|
input wire clk,
|
||||||
|
input wire [5:0] A,
|
||||||
|
input wire [5:0] B,
|
||||||
|
output reg [8:0] Z
|
||||||
|
);
|
||||||
|
|
||||||
|
always @(posedge clk)
|
||||||
|
Z <= Z + (A * B);
|
||||||
|
|
||||||
|
endmodule
|
||||||
|
|
||||||
|
module macc_simple_clr (
|
||||||
|
input wire clk,
|
||||||
|
input wire clr,
|
||||||
|
input wire [5:0] A,
|
||||||
|
input wire [5:0] B,
|
||||||
|
output reg [6:0] Z
|
||||||
|
);
|
||||||
|
|
||||||
|
always @(posedge clk)
|
||||||
|
if (clr) Z <= (A * B);
|
||||||
|
else Z <= Z + (A * B);
|
||||||
|
|
||||||
|
endmodule
|
||||||
|
|
||||||
|
module macc_simple_arst (
|
||||||
|
input wire clk,
|
||||||
|
input wire rst,
|
||||||
|
input wire [5:0] A,
|
||||||
|
input wire [5:0] B,
|
||||||
|
output reg [8:0] Z
|
||||||
|
);
|
||||||
|
|
||||||
|
always @(posedge clk or posedge rst)
|
||||||
|
if (rst) Z <= 0;
|
||||||
|
else Z <= Z + (A * B);
|
||||||
|
|
||||||
|
endmodule
|
||||||
|
|
||||||
|
module macc_simple_ena (
|
||||||
|
input wire clk,
|
||||||
|
input wire ena,
|
||||||
|
input wire [5:0] A,
|
||||||
|
input wire [5:0] B,
|
||||||
|
output reg [8:0] Z
|
||||||
|
);
|
||||||
|
|
||||||
|
always @(posedge clk)
|
||||||
|
if (ena) Z <= Z + (A * B);
|
||||||
|
|
||||||
|
endmodule
|
||||||
|
|
||||||
|
module macc_simple_arst_clr_ena (
|
||||||
|
input wire clk,
|
||||||
|
input wire rst,
|
||||||
|
input wire clr,
|
||||||
|
input wire ena,
|
||||||
|
input wire [5:0] A,
|
||||||
|
input wire [5:0] B,
|
||||||
|
output reg [7:0] Z
|
||||||
|
);
|
||||||
|
|
||||||
|
always @(posedge clk or posedge rst)
|
||||||
|
if (rst) Z <= 0;
|
||||||
|
else if (ena) begin
|
||||||
|
if (clr) Z <= (A * B);
|
||||||
|
else Z <= Z + (A * B);
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
||||||
|
|
||||||
|
module macc_simple_preacc (
|
||||||
|
input wire clk,
|
||||||
|
input wire [4:0] A,
|
||||||
|
input wire [4:0] B,
|
||||||
|
output wire [7:0] Z
|
||||||
|
);
|
||||||
|
|
||||||
|
reg [7:0] acc;
|
||||||
|
|
||||||
|
assign Z = acc + (A * B);
|
||||||
|
|
||||||
|
always @(posedge clk)
|
||||||
|
acc <= Z;
|
||||||
|
|
||||||
|
endmodule
|
||||||
|
|
||||||
|
module macc_simple_preacc_clr (
|
||||||
|
input wire clk,
|
||||||
|
input wire clr,
|
||||||
|
input wire [5:0] A,
|
||||||
|
input wire [5:0] B,
|
||||||
|
output reg [7:0] Z
|
||||||
|
);
|
||||||
|
|
||||||
|
reg [7:0] acc;
|
||||||
|
|
||||||
|
assign Z = (clr) ? (A * B) : (acc + (A * B));
|
||||||
|
|
||||||
|
always @(posedge clk)
|
||||||
|
acc <= Z;
|
||||||
|
|
||||||
|
endmodule
|
||||||
|
|
||||||
|
module macc_simple_signed (
|
||||||
|
input wire clk,
|
||||||
|
input wire signed [4:0] A,
|
||||||
|
input wire signed [4:0] B,
|
||||||
|
output reg signed [7:0] Z
|
||||||
|
);
|
||||||
|
|
||||||
|
always @(posedge clk)
|
||||||
|
Z <= Z + (A * B);
|
||||||
|
|
||||||
|
endmodule
|
||||||
|
|
||||||
|
module macc_simple_signed_subtract (
|
||||||
|
input wire clk,
|
||||||
|
input wire signed [4:0] A,
|
||||||
|
input wire signed [4:0] B,
|
||||||
|
output reg signed [7:0] Z
|
||||||
|
);
|
||||||
|
|
||||||
|
always @(posedge clk)
|
||||||
|
Z <= Z - (A * B);
|
||||||
|
|
||||||
|
endmodule
|
||||||
|
|
59
tests/arch/quicklogic/qlf_k6n10f/dspv2_simd.ys
Normal file
59
tests/arch/quicklogic/qlf_k6n10f/dspv2_simd.ys
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
read_verilog <<EOT
|
||||||
|
module foo(
|
||||||
|
input [7:0] A,
|
||||||
|
input [7:0] B,
|
||||||
|
input [7:0] C,
|
||||||
|
input [7:0] D,
|
||||||
|
output reg [7:0] X,
|
||||||
|
output reg [7:0] Y);
|
||||||
|
assign X = A * B;
|
||||||
|
assign Y = C * D;
|
||||||
|
endmodule
|
||||||
|
EOT
|
||||||
|
|
||||||
|
synth_quicklogic -dspv2 -run :map_dsp
|
||||||
|
|
||||||
|
# this is just taken from map_dsp step
|
||||||
|
wreduce t:$mul
|
||||||
|
ql_dsp_macc -dspv2
|
||||||
|
techmap -map +/mul2dsp.v -map +/quicklogic/qlf_k6n10f/dspv2_map.v -D USE_DSP_CFG_PARAMS=0 -D DSP_SIGNEDONLY -D DSP_A_MAXWIDTH=32 -D DSP_B_MAXWIDTH=18 -D DSP_A_MINWIDTH=10 -D DSP_B_MINWIDTH=10 -D DSP_NAME=$__MUL32X18
|
||||||
|
chtype -set $mul t:$__soft_mul
|
||||||
|
techmap -map +/mul2dsp.v -map +/quicklogic/qlf_k6n10f/dspv2_map.v -D USE_DSP_CFG_PARAMS=0 -D DSP_SIGNEDONLY -D DSP_A_MAXWIDTH=16 -D DSP_B_MAXWIDTH=9 -D DSP_A_MINWIDTH=4 -D DSP_B_MINWIDTH=4 -D DSP_NAME=$__MUL16X9
|
||||||
|
read_verilog -lib +/quicklogic/qlf_k6n10f/dspv2_sim.v
|
||||||
|
select -assert-count 2 t:dspv2_16x9x32_cfg_ports
|
||||||
|
select -assert-count 0 t:dspv2_32x18x64_cfg_ports
|
||||||
|
equiv_opt -assert -async2sync -map +/quicklogic/qlf_k6n10f/dspv2_sim.v ql_dsp_simd -dspv2
|
||||||
|
design -load postopt
|
||||||
|
select -assert-count 0 t:dspv2_16x9x32_cfg_ports
|
||||||
|
select -assert-count 1 t:dspv2_32x18x64_cfg_ports
|
||||||
|
|
||||||
|
design -reset
|
||||||
|
|
||||||
|
read_verilog <<EOT
|
||||||
|
module foo(
|
||||||
|
input [7:0] A,
|
||||||
|
input [7:0] B,
|
||||||
|
input [7:0] C,
|
||||||
|
input [7:0] D,
|
||||||
|
input [7:0] E,
|
||||||
|
output reg [7:0] X,
|
||||||
|
output reg [7:0] Y);
|
||||||
|
assign X = A * B;
|
||||||
|
assign Y = C * (D + E); // <-- look here
|
||||||
|
endmodule
|
||||||
|
EOT
|
||||||
|
|
||||||
|
synth_quicklogic -dspv2 -run :map_dsp
|
||||||
|
|
||||||
|
wreduce t:$mul
|
||||||
|
ql_dsp_macc -dspv2
|
||||||
|
techmap -map +/mul2dsp.v -map +/quicklogic/qlf_k6n10f/dspv2_map.v -D USE_DSP_CFG_PARAMS=0 -D DSP_SIGNEDONLY -D DSP_A_MAXWIDTH=32 -D DSP_B_MAXWIDTH=18 -D DSP_A_MINWIDTH=10 -D DSP_B_MINWIDTH=10 -D DSP_NAME=$__MUL32X18
|
||||||
|
chtype -set $mul t:$__soft_mul
|
||||||
|
techmap -map +/mul2dsp.v -map +/quicklogic/qlf_k6n10f/dspv2_map.v -D USE_DSP_CFG_PARAMS=0 -D DSP_SIGNEDONLY -D DSP_A_MAXWIDTH=16 -D DSP_B_MAXWIDTH=9 -D DSP_A_MINWIDTH=4 -D DSP_B_MINWIDTH=4 -D DSP_NAME=$__MUL16X9
|
||||||
|
read_verilog -lib +/quicklogic/qlf_k6n10f/dspv2_sim.v
|
||||||
|
select -assert-count 2 t:dspv2_16x9x32_cfg_ports
|
||||||
|
select -assert-count 0 t:dspv2_32x18x64_cfg_ports
|
||||||
|
equiv_opt -assert -async2sync -map +/quicklogic/qlf_k6n10f/dspv2_sim.v ql_dsp_simd -dspv2
|
||||||
|
design -load postopt
|
||||||
|
select -assert-count 0 t:dspv2_16x9x32_cfg_ports
|
||||||
|
select -assert-count 1 t:dspv2_32x18x64_cfg_ports
|
|
@ -1,4 +1,4 @@
|
||||||
verific -sv -lib +/quicklogic/qlf_k6n10f/dsp_sim.v
|
verific -sv -lib +/quicklogic/qlf_k6n10f/dspv1_sim.v
|
||||||
|
|
||||||
verific -sv <<EOF
|
verific -sv <<EOF
|
||||||
module top (
|
module top (
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue