mirror of https://github.com/YosysHQ/yosys.git
573 lines
14 KiB
Plaintext
573 lines
14 KiB
Plaintext
pattern ice40_dsp
|
|
|
|
udata <std::function<SigSpec(const SigSpec&)>> unextend
|
|
state <SigBit> clock
|
|
state <bool> clock_pol cd_signed o_lo
|
|
state <SigSpec> sigA sigB sigCD sigH sigO
|
|
state <Cell*> add mux
|
|
state <IdString> addAB muxAB
|
|
|
|
state <bool> ffAholdpol ffBholdpol ffCDholdpol ffOholdpol
|
|
state <bool> ffArstpol ffBrstpol ffCDrstpol ffOrstpol
|
|
|
|
state <Cell*> ffA ffAholdmux ffArstmux ffB ffBholdmux ffBrstmux ffCD ffCDholdmux
|
|
state <Cell*> ffFJKG ffH ffO ffOholdmux ffOrstmux
|
|
|
|
// subpattern
|
|
state <SigSpec> argQ argD
|
|
state <bool> ffholdpol ffrstpol
|
|
state <int> ffoffset
|
|
udata <SigSpec> dffD dffQ
|
|
udata <SigBit> dffclock
|
|
udata <Cell*> dff dffholdmux dffrstmux
|
|
udata <bool> dffholdpol dffrstpol dffclock_pol
|
|
|
|
match mul
|
|
select mul->type.in($mul, \SB_MAC16)
|
|
select GetSize(mul->getPort(\A)) + GetSize(mul->getPort(\B)) > 10
|
|
endmatch
|
|
|
|
code sigA sigB sigH
|
|
unextend = [](const SigSpec &sig) {
|
|
int i;
|
|
for (i = GetSize(sig)-1; i > 0; i--)
|
|
if (sig[i] != sig[i-1])
|
|
break;
|
|
// Do not remove non-const sign bit
|
|
if (sig[i].wire)
|
|
++i;
|
|
return sig.extract(0, i);
|
|
};
|
|
sigA = unextend(port(mul, \A));
|
|
sigB = unextend(port(mul, \B));
|
|
|
|
SigSpec O;
|
|
if (mul->type == $mul)
|
|
O = mul->getPort(\Y);
|
|
else if (mul->type == \SB_MAC16)
|
|
O = mul->getPort(\O);
|
|
else log_abort();
|
|
if (GetSize(O) <= 10)
|
|
reject;
|
|
|
|
// Only care about those bits that are used
|
|
int i;
|
|
for (i = 0; i < GetSize(O); i++) {
|
|
if (nusers(O[i]) <= 1)
|
|
break;
|
|
sigH.append(O[i]);
|
|
}
|
|
log_assert(nusers(O.extract_end(i)) <= 1);
|
|
endcode
|
|
|
|
code argQ ffA ffAholdmux ffArstmux ffAholdpol ffArstpol sigA clock clock_pol
|
|
if (mul->type != \SB_MAC16 || !param(mul, \A_REG).as_bool()) {
|
|
argQ = sigA;
|
|
subpattern(in_dffe);
|
|
if (dff) {
|
|
ffA = dff;
|
|
clock = dffclock;
|
|
clock_pol = dffclock_pol;
|
|
if (dffrstmux) {
|
|
ffArstmux = dffrstmux;
|
|
ffArstpol = dffrstpol;
|
|
}
|
|
if (dffholdmux) {
|
|
ffAholdmux = dffholdmux;
|
|
ffAholdpol = dffholdpol;
|
|
}
|
|
sigA = dffD;
|
|
}
|
|
}
|
|
endcode
|
|
|
|
code argQ ffB ffBholdmux ffBrstmux ffBholdpol ffBrstpol sigB clock clock_pol
|
|
if (mul->type != \SB_MAC16 || !param(mul, \B_REG).as_bool()) {
|
|
argQ = sigB;
|
|
subpattern(in_dffe);
|
|
if (dff) {
|
|
ffB = dff;
|
|
clock = dffclock;
|
|
clock_pol = dffclock_pol;
|
|
if (dffrstmux) {
|
|
ffBrstmux = dffrstmux;
|
|
ffBrstpol = dffrstpol;
|
|
}
|
|
if (dffholdmux) {
|
|
ffBholdmux = dffholdmux;
|
|
ffBholdpol = dffholdpol;
|
|
}
|
|
sigB = dffD;
|
|
}
|
|
}
|
|
endcode
|
|
|
|
code argD ffFJKG sigH clock clock_pol
|
|
if (nusers(sigH) == 2 &&
|
|
(mul->type != \SB_MAC16 ||
|
|
(!param(mul, \TOP_8x8_MULT_REG).as_bool() && !param(mul, \BOT_8x8_MULT_REG).as_bool() && !param(mul, \PIPELINE_16x16_MULT_REG1).as_bool() && !param(mul, \PIPELINE_16x16_MULT_REG1).as_bool()))) {
|
|
argD = sigH;
|
|
subpattern(out_dffe);
|
|
if (dff) {
|
|
// F/J/K/G do not have a CE-like (hold) input
|
|
if (dffholdmux)
|
|
goto reject_ffFJKG;
|
|
|
|
// Reset signal of F/J (IRSTTOP) and K/G (IRSTBOT)
|
|
// shared with A and B
|
|
if ((ffArstmux != NULL) != (dffrstmux != NULL))
|
|
goto reject_ffFJKG;
|
|
if ((ffBrstmux != NULL) != (dffrstmux != NULL))
|
|
goto reject_ffFJKG;
|
|
if (ffArstmux) {
|
|
if (port(ffArstmux, \S) != port(dffrstmux, \S))
|
|
goto reject_ffFJKG;
|
|
if (ffArstpol != dffrstpol)
|
|
goto reject_ffFJKG;
|
|
}
|
|
if (ffBrstmux) {
|
|
if (port(ffBrstmux, \S) != port(dffrstmux, \S))
|
|
goto reject_ffFJKG;
|
|
if (ffBrstpol != dffrstpol)
|
|
goto reject_ffFJKG;
|
|
}
|
|
|
|
ffFJKG = dff;
|
|
clock = dffclock;
|
|
clock_pol = dffclock_pol;
|
|
sigH = dffQ;
|
|
|
|
reject_ffFJKG: ;
|
|
}
|
|
}
|
|
endcode
|
|
|
|
code argD ffH sigH sigO clock clock_pol
|
|
if (ffFJKG && nusers(sigH) == 2 &&
|
|
(mul->type != \SB_MAC16 || !param(mul, \PIPELINE_16x16_MULT_REG2).as_bool())) {
|
|
argD = sigH;
|
|
subpattern(out_dffe);
|
|
if (dff) {
|
|
// H does not have a CE-like (hold) input
|
|
if (dffholdmux)
|
|
goto reject_ffH;
|
|
|
|
// Reset signal of H (IRSTBOT) shared with B
|
|
if ((ffBrstmux != NULL) != (dffrstmux != NULL))
|
|
goto reject_ffH;
|
|
if (ffBrstmux) {
|
|
if (port(ffBrstmux, \S) != port(dffrstmux, \S))
|
|
goto reject_ffH;
|
|
if (ffBrstpol != dffrstpol)
|
|
goto reject_ffH;
|
|
}
|
|
|
|
ffH = dff;
|
|
clock = dffclock;
|
|
clock_pol = dffclock_pol;
|
|
sigH = dffQ;
|
|
|
|
reject_ffH: ;
|
|
}
|
|
}
|
|
|
|
sigO = sigH;
|
|
endcode
|
|
|
|
match add
|
|
if mul->type != \SB_MAC16 || (param(mul, \TOPOUTPUT_SELECT).as_int() == 3 && param(mul, \BOTOUTPUT_SELECT).as_int() == 3)
|
|
|
|
select add->type.in($add)
|
|
choice <IdString> AB {\A, \B}
|
|
select nusers(port(add, AB)) == 2
|
|
|
|
index <SigBit> port(add, AB)[0] === sigH[0]
|
|
filter GetSize(port(add, AB)) <= GetSize(sigH)
|
|
filter port(add, AB) == sigH.extract(0, GetSize(port(add, AB)))
|
|
filter nusers(sigH.extract_end(GetSize(port(add, AB)))) <= 1
|
|
set addAB AB
|
|
optional
|
|
endmatch
|
|
|
|
code sigCD sigO cd_signed
|
|
if (add) {
|
|
sigCD = port(add, addAB == \A ? \B : \A);
|
|
cd_signed = param(add, addAB == \A ? \B_SIGNED : \A_SIGNED).as_bool();
|
|
|
|
int natural_mul_width = GetSize(sigA) + GetSize(sigB);
|
|
int actual_mul_width = GetSize(sigH);
|
|
int actual_acc_width = GetSize(sigCD);
|
|
|
|
if ((actual_acc_width > actual_mul_width) && (natural_mul_width > actual_mul_width))
|
|
reject;
|
|
// If accumulator, check adder width and signedness
|
|
if (sigCD == sigH && (actual_acc_width != actual_mul_width) && (param(mul, \A_SIGNED).as_bool() != param(add, \A_SIGNED).as_bool()))
|
|
reject;
|
|
|
|
sigO = port(add, \Y);
|
|
}
|
|
endcode
|
|
|
|
match mux
|
|
select mux->type == $mux
|
|
choice <IdString> AB {\A, \B}
|
|
select nusers(port(mux, AB)) == 2
|
|
index <SigSpec> port(mux, AB) === sigO
|
|
set muxAB AB
|
|
optional
|
|
endmatch
|
|
|
|
code sigO
|
|
if (mux)
|
|
sigO = port(mux, \Y);
|
|
endcode
|
|
|
|
code argD ffO ffOholdmux ffOrstmux ffOholdpol ffOrstpol sigO sigCD clock clock_pol cd_signed o_lo
|
|
if (mul->type != \SB_MAC16 ||
|
|
// Ensure that register is not already used
|
|
((param(mul, \TOPOUTPUT_SELECT, 0).as_int() != 1 && param(mul, \BOTOUTPUT_SELECT, 0).as_int() != 1) &&
|
|
// Ensure that OLOADTOP/OLOADBOT is unused or zero
|
|
(port(mul, \OLOADTOP, State::S0).is_fully_zero() && port(mul, \OLOADBOT, State::S0).is_fully_zero()))) {
|
|
|
|
dff = nullptr;
|
|
|
|
// First try entire sigO
|
|
if (nusers(sigO) == 2) {
|
|
argD = sigO;
|
|
subpattern(out_dffe);
|
|
}
|
|
|
|
// Otherwise try just its least significant 16 bits
|
|
if (!dff && GetSize(sigO) > 16) {
|
|
argD = sigO.extract(0, 16);
|
|
if (nusers(argD) == 2) {
|
|
subpattern(out_dffe);
|
|
o_lo = dff;
|
|
}
|
|
}
|
|
|
|
if (dff) {
|
|
ffO = dff;
|
|
clock = dffclock;
|
|
clock_pol = dffclock_pol;
|
|
if (dffrstmux) {
|
|
ffOrstmux = dffrstmux;
|
|
ffOrstpol = dffrstpol;
|
|
}
|
|
if (dffholdmux) {
|
|
ffOholdmux = dffholdmux;
|
|
ffOholdpol = dffholdpol;
|
|
}
|
|
|
|
sigO.replace(sigO.extract(0, GetSize(dffQ)), dffQ);
|
|
}
|
|
|
|
// Loading value into output register is not
|
|
// supported unless using accumulator
|
|
if (mux) {
|
|
if (sigCD != sigO)
|
|
reject;
|
|
sigCD = port(mux, muxAB == \B ? \A : \B);
|
|
|
|
cd_signed = add && param(add, \A_SIGNED).as_bool() && param(add, \B_SIGNED).as_bool();
|
|
}
|
|
}
|
|
endcode
|
|
|
|
code argQ ffCD ffCDholdmux ffCDholdpol ffCDrstpol sigCD clock clock_pol
|
|
if (!sigCD.empty() && sigCD != sigO &&
|
|
(mul->type != \SB_MAC16 || (!param(mul, \C_REG).as_bool() && !param(mul, \D_REG).as_bool()))) {
|
|
argQ = sigCD;
|
|
subpattern(in_dffe);
|
|
if (dff) {
|
|
if (dffholdmux) {
|
|
ffCDholdmux = dffholdmux;
|
|
ffCDholdpol = dffholdpol;
|
|
}
|
|
|
|
// Reset signal of C (IRSTTOP) and D (IRSTBOT)
|
|
// shared with A and B
|
|
if ((ffArstmux != NULL) != (dffrstmux != NULL))
|
|
goto reject_ffCD;
|
|
if ((ffBrstmux != NULL) != (dffrstmux != NULL))
|
|
goto reject_ffCD;
|
|
if (ffArstmux) {
|
|
if (port(ffArstmux, \S) != port(dffrstmux, \S))
|
|
goto reject_ffCD;
|
|
if (ffArstpol != dffrstpol)
|
|
goto reject_ffCD;
|
|
}
|
|
if (ffBrstmux) {
|
|
if (port(ffBrstmux, \S) != port(dffrstmux, \S))
|
|
goto reject_ffCD;
|
|
if (ffBrstpol != dffrstpol)
|
|
goto reject_ffCD;
|
|
}
|
|
|
|
ffCD = dff;
|
|
clock = dffclock;
|
|
clock_pol = dffclock_pol;
|
|
sigCD = dffD;
|
|
|
|
reject_ffCD: ;
|
|
}
|
|
}
|
|
endcode
|
|
|
|
code sigCD
|
|
sigCD.extend_u0(32, cd_signed);
|
|
endcode
|
|
|
|
code
|
|
accept;
|
|
endcode
|
|
|
|
// #######################
|
|
|
|
subpattern in_dffe
|
|
arg argD argQ clock clock_pol
|
|
|
|
code
|
|
dff = nullptr;
|
|
for (auto c : argQ.chunks()) {
|
|
if (!c.wire)
|
|
reject;
|
|
if (c.wire->get_bool_attribute(\keep))
|
|
reject;
|
|
}
|
|
endcode
|
|
|
|
match ff
|
|
select ff->type.in($dff)
|
|
// DSP48E1 does not support clock inversion
|
|
select param(ff, \CLK_POLARITY).as_bool()
|
|
|
|
slice offset GetSize(port(ff, \D))
|
|
index <SigBit> port(ff, \Q)[offset] === argQ[0]
|
|
|
|
// Check that the rest of argQ is present
|
|
filter GetSize(port(ff, \Q)) >= offset + GetSize(argQ)
|
|
filter port(ff, \Q).extract(offset, GetSize(argQ)) == argQ
|
|
|
|
set ffoffset offset
|
|
endmatch
|
|
|
|
code argQ argD
|
|
{
|
|
if (clock != SigBit()) {
|
|
if (port(ff, \CLK) != clock)
|
|
reject;
|
|
if (param(ff, \CLK_POLARITY).as_bool() != clock_pol)
|
|
reject;
|
|
}
|
|
|
|
SigSpec Q = port(ff, \Q);
|
|
dff = ff;
|
|
dffclock = port(ff, \CLK);
|
|
dffclock_pol = param(ff, \CLK_POLARITY).as_bool();
|
|
dffD = argQ;
|
|
argD = port(ff, \D);
|
|
argQ = Q;
|
|
dffD.replace(argQ, argD);
|
|
// Only search for ffrstmux if dffD only
|
|
// has two (ff, ffrstmux) users
|
|
if (nusers(dffD) > 2)
|
|
argD = SigSpec();
|
|
}
|
|
endcode
|
|
|
|
match ffrstmux
|
|
if false /* TODO: ice40 resets are actually async */
|
|
|
|
if !argD.empty()
|
|
select ffrstmux->type.in($mux)
|
|
index <SigSpec> port(ffrstmux, \Y) === argD
|
|
|
|
choice <IdString> BA {\B, \A}
|
|
// DSP48E1 only supports reset to zero
|
|
select port(ffrstmux, BA).is_fully_zero()
|
|
|
|
define <bool> pol (BA == \B)
|
|
set ffrstpol pol
|
|
semioptional
|
|
endmatch
|
|
|
|
code argD
|
|
if (ffrstmux) {
|
|
dffrstmux = ffrstmux;
|
|
dffrstpol = ffrstpol;
|
|
argD = port(ffrstmux, ffrstpol ? \A : \B);
|
|
dffD.replace(port(ffrstmux, \Y), argD);
|
|
|
|
// Only search for ffholdmux if argQ has at
|
|
// least 3 users (ff, <upstream>, ffrstmux) and
|
|
// dffD only has two (ff, ffrstmux)
|
|
if (!(nusers(argQ) >= 3 && nusers(dffD) == 2))
|
|
argD = SigSpec();
|
|
}
|
|
else
|
|
dffrstmux = nullptr;
|
|
endcode
|
|
|
|
match ffholdmux
|
|
if !argD.empty()
|
|
select ffholdmux->type.in($mux)
|
|
index <SigSpec> port(ffholdmux, \Y) === argD
|
|
choice <IdString> BA {\B, \A}
|
|
index <SigSpec> port(ffholdmux, BA) === argQ
|
|
define <bool> pol (BA == \B)
|
|
set ffholdpol pol
|
|
semioptional
|
|
endmatch
|
|
|
|
code argD
|
|
if (ffholdmux) {
|
|
dffholdmux = ffholdmux;
|
|
dffholdpol = ffholdpol;
|
|
argD = port(ffholdmux, ffholdpol ? \A : \B);
|
|
dffD.replace(port(ffholdmux, \Y), argD);
|
|
}
|
|
else
|
|
dffholdmux = nullptr;
|
|
endcode
|
|
|
|
// #######################
|
|
|
|
subpattern out_dffe
|
|
arg argD argQ clock clock_pol
|
|
|
|
code
|
|
dff = nullptr;
|
|
for (auto c : argD.chunks())
|
|
if (c.wire->get_bool_attribute(\keep))
|
|
reject;
|
|
endcode
|
|
|
|
match ffholdmux
|
|
select ffholdmux->type.in($mux)
|
|
// ffholdmux output must have two users: ffholdmux and ff.D
|
|
select nusers(port(ffholdmux, \Y)) == 2
|
|
|
|
choice <IdString> BA {\B, \A}
|
|
// keep-last-value net must have at least three users: ffholdmux, ff, downstream sink(s)
|
|
select nusers(port(ffholdmux, BA)) >= 3
|
|
|
|
slice offset GetSize(port(ffholdmux, \Y))
|
|
define <IdString> AB (BA == \B ? \A : \B)
|
|
index <SigBit> port(ffholdmux, AB)[offset] === argD[0]
|
|
|
|
// Check that the rest of argD is present
|
|
filter GetSize(port(ffholdmux, AB)) >= offset + GetSize(argD)
|
|
filter port(ffholdmux, AB).extract(offset, GetSize(argD)) == argD
|
|
|
|
set ffoffset offset
|
|
define <bool> pol (BA == \B)
|
|
set ffholdpol pol
|
|
|
|
semioptional
|
|
endmatch
|
|
|
|
code argD argQ
|
|
dffholdmux = ffholdmux;
|
|
if (ffholdmux) {
|
|
SigSpec AB = port(ffholdmux, ffholdpol ? \A : \B);
|
|
SigSpec Y = port(ffholdmux, \Y);
|
|
argQ = argD;
|
|
argD.replace(AB, Y);
|
|
argQ.replace(AB, port(ffholdmux, ffholdpol ? \B : \A));
|
|
|
|
dffholdmux = ffholdmux;
|
|
dffholdpol = ffholdpol;
|
|
}
|
|
endcode
|
|
|
|
match ffrstmux
|
|
if false /* TODO: ice40 resets are actually async */
|
|
|
|
select ffrstmux->type.in($mux)
|
|
// ffrstmux output must have two users: ffrstmux and ff.D
|
|
select nusers(port(ffrstmux, \Y)) == 2
|
|
|
|
choice <IdString> BA {\B, \A}
|
|
// DSP48E1 only supports reset to zero
|
|
select port(ffrstmux, BA).is_fully_zero()
|
|
|
|
slice offset GetSize(port(ffrstmux, \Y))
|
|
define <IdString> AB (BA == \B ? \A : \B)
|
|
index <SigBit> port(ffrstmux, AB)[offset] === argD[0]
|
|
|
|
// Check that offset is consistent
|
|
filter !ffholdmux || ffoffset == offset
|
|
// Check that the rest of argD is present
|
|
filter GetSize(port(ffrstmux, AB)) >= offset + GetSize(argD)
|
|
filter port(ffrstmux, AB).extract(offset, GetSize(argD)) == argD
|
|
|
|
set ffoffset offset
|
|
define <bool> pol (AB == \A)
|
|
set ffrstpol pol
|
|
|
|
semioptional
|
|
endmatch
|
|
|
|
code argD argQ
|
|
dffrstmux = ffrstmux;
|
|
if (ffrstmux) {
|
|
SigSpec AB = port(ffrstmux, ffrstpol ? \A : \B);
|
|
SigSpec Y = port(ffrstmux, \Y);
|
|
argD.replace(AB, Y);
|
|
|
|
dffrstmux = ffrstmux;
|
|
dffrstpol = ffrstpol;
|
|
}
|
|
endcode
|
|
|
|
match ff
|
|
select ff->type.in($dff)
|
|
// DSP48E1 does not support clock inversion
|
|
select param(ff, \CLK_POLARITY).as_bool()
|
|
|
|
slice offset GetSize(port(ff, \D))
|
|
index <SigBit> port(ff, \D)[offset] === argD[0]
|
|
|
|
// Check that offset is consistent
|
|
filter (!ffholdmux && !ffrstmux) || ffoffset == offset
|
|
// Check that the rest of argD is present
|
|
filter GetSize(port(ff, \D)) >= offset + GetSize(argD)
|
|
filter port(ff, \D).extract(offset, GetSize(argD)) == argD
|
|
// Check that FF.Q is connected to CE-mux
|
|
filter !ffholdmux || port(ff, \Q).extract(offset, GetSize(argQ)) == argQ
|
|
|
|
set ffoffset offset
|
|
endmatch
|
|
|
|
code argQ
|
|
if (ff) {
|
|
if (clock != SigBit()) {
|
|
if (port(ff, \CLK) != clock)
|
|
reject;
|
|
if (param(ff, \CLK_POLARITY).as_bool() != clock_pol)
|
|
reject;
|
|
}
|
|
SigSpec D = port(ff, \D);
|
|
SigSpec Q = port(ff, \Q);
|
|
if (!ffholdmux) {
|
|
argQ = argD;
|
|
argQ.replace(D, Q);
|
|
}
|
|
|
|
for (auto c : argQ.chunks()) {
|
|
Const init = c.wire->attributes.at(\init, State::Sx);
|
|
if (!init.is_fully_undef() && !init.is_fully_zero())
|
|
reject;
|
|
}
|
|
|
|
dff = ff;
|
|
dffQ = argQ;
|
|
dffclock = port(ff, \CLK);
|
|
dffclock_pol = param(ff, \CLK_POLARITY).as_bool();
|
|
}
|
|
// No enable/reset mux possible without flop
|
|
else if (dffholdmux || dffrstmux)
|
|
reject;
|
|
endcode
|