@@ -66,6 +66,8 @@ void SynthesisSettings::addOptions(slang::CommandLine &cmdLine) {
6666 " For developers: stop after the AST is fully compiled" );
6767 cmdLine.add (" --no-default-translate-off-format" , no_default_translate_off,
6868 " Do not interpret any comment directives marking disabled input unless specified with '--translate-off-format'" );
69+ cmdLine.add (" --allow-dual-edge-ff" , allow_dual_edge_ff,
70+ " Allow synthesis of dual-edge flip-flops (@(edge))" );
6971}
7072
7173namespace ast = slang::ast;
@@ -1863,6 +1865,99 @@ struct HierarchyQueue {
18631865 std::vector<NetlistContext *> queue;
18641866};
18651867
1868+ // Synthesizes two single-edge FFs (one posedge, one negedge) with the same D input,
1869+ // then uses a mux controlled by the clock to select the appropriate FF output.
1870+ static void add_dual_edge_dff (NetlistContext &netlist, const ast::ProceduralBlockSymbol &symbol,
1871+ const NamedChunk &named,
1872+ RTLIL::SigSpec clk, RTLIL::SigSpec d, RTLIL::SigSpec q)
1873+ {
1874+ auto [chunk, name] = named;
1875+
1876+ log_assert (chunk.variable .get_symbol () != nullptr );
1877+ log_assert (netlist.canvas != nullptr );
1878+
1879+ std::string base_name = Yosys::stringf (" $driver$%s%s" ,
1880+ RTLIL::unescape_id (netlist.id (*chunk.variable .get_symbol ())).c_str (),
1881+ name.c_str ());
1882+
1883+ RTLIL::Wire *pos_q = netlist.canvas ->addWire (
1884+ netlist.canvas ->uniquify (Yosys::stringf (" %s$pos$q" , base_name.c_str ())),
1885+ chunk.bitwidth ()); // intermediaries
1886+ log_assert (pos_q != nullptr );
1887+
1888+ RTLIL::Wire *neg_q = netlist.canvas ->addWire (
1889+ netlist.canvas ->uniquify (Yosys::stringf (" %s$neg$q" , base_name.c_str ())),
1890+ chunk.bitwidth ()); // intermediaries
1891+ log_assert (neg_q != nullptr );
1892+
1893+ RTLIL::Cell *pos_ff = netlist.canvas ->addDff (
1894+ netlist.canvas ->uniquify (Yosys::stringf (" %s$pos" , base_name.c_str ())),
1895+ clk, d, pos_q, /* edge_polarity=*/ true );
1896+ log_assert (pos_ff != nullptr );
1897+ transfer_attrs (symbol, pos_ff);
1898+
1899+ // Create negedge FF
1900+ RTLIL::Cell *neg_ff = netlist.canvas ->addDff (
1901+ netlist.canvas ->uniquify (Yosys::stringf (" %s$neg" , base_name.c_str ())),
1902+ clk, d, neg_q, /* edge_polarity=*/ false );
1903+ log_assert (neg_ff != nullptr );
1904+ transfer_attrs (symbol, neg_ff);
1905+
1906+ // behaviour: when clk=0: select neg_q (captures on negedge), when clk=1: select pos_q (captures on posedge)
1907+ RTLIL::Cell *mux = netlist.canvas ->addMux (
1908+ netlist.canvas ->uniquify (Yosys::stringf (" %s$mux" , base_name.c_str ())),
1909+ /* A=*/ neg_q, /* B=*/ pos_q, /* S=*/ clk, /* Y=*/ q);
1910+ log_assert (mux != nullptr );
1911+ transfer_attrs (symbol, mux);
1912+ }
1913+
1914+ // async load/reset version of add_dual_edge_dff()
1915+ static void add_dual_edge_aldff (NetlistContext &netlist, const ast::ProceduralBlockSymbol &symbol,
1916+ const NamedChunk &named,
1917+ RTLIL::SigSpec clk, RTLIL::SigSpec aload, RTLIL::SigSpec d,
1918+ RTLIL::SigSpec q, RTLIL::SigSpec ad, bool aload_polarity)
1919+ {
1920+ auto [chunk, name] = named;
1921+
1922+ log_assert (chunk.variable .get_symbol () != nullptr );
1923+ log_assert (netlist.canvas != nullptr );
1924+
1925+ std::string base_name = Yosys::stringf (" $driver$%s%s" ,
1926+ RTLIL::unescape_id (netlist.id (*chunk.variable .get_symbol ())).c_str (),
1927+ name.c_str ());
1928+
1929+ RTLIL::Wire *pos_q = netlist.canvas ->addWire (
1930+ netlist.canvas ->uniquify (Yosys::stringf (" %s$pos$q" , base_name.c_str ())),
1931+ chunk.bitwidth ()); // intermediaries
1932+ log_assert (pos_q != nullptr );
1933+
1934+ RTLIL::Wire *neg_q = netlist.canvas ->addWire (
1935+ netlist.canvas ->uniquify (Yosys::stringf (" %s$neg$q" , base_name.c_str ())),
1936+ chunk.bitwidth ()); // intermediaries
1937+ log_assert (neg_q != nullptr );
1938+
1939+ RTLIL::Cell *pos_ff = netlist.canvas ->addAldff (
1940+ netlist.canvas ->uniquify (Yosys::stringf (" %s$pos" , base_name.c_str ())),
1941+ clk, aload, d, pos_q, ad,
1942+ /* clk_polarity=*/ true , aload_polarity);
1943+ log_assert (pos_ff != nullptr );
1944+ transfer_attrs (symbol, pos_ff);
1945+
1946+ RTLIL::Cell *neg_ff = netlist.canvas ->addAldff (
1947+ netlist.canvas ->uniquify (Yosys::stringf (" %s$neg" , base_name.c_str ())),
1948+ clk, aload, d, neg_q, ad,
1949+ /* clk_polarity=*/ false , aload_polarity);
1950+ log_assert (neg_ff != nullptr );
1951+ transfer_attrs (symbol, neg_ff);
1952+
1953+ // behaviour: when clk=0: select neg_q (captures on negedge), when clk=1: select pos_q (captures on posedge)
1954+ RTLIL::Cell *mux = netlist.canvas ->addMux (
1955+ netlist.canvas ->uniquify (Yosys::stringf (" %s$mux" , base_name.c_str ())),
1956+ /* A=*/ neg_q, /* B=*/ pos_q, /* S=*/ clk, /* Y=*/ q);
1957+ log_assert (mux != nullptr );
1958+ transfer_attrs (symbol, mux);
1959+ }
1960+
18661961struct PopulateNetlist : public TimingPatternInterpretor , public ast ::ASTVisitor<PopulateNetlist, true , false > {
18671962public:
18681963 HierarchyQueue &queue;
@@ -2090,40 +2185,12 @@ struct PopulateNetlist : public TimingPatternInterpretor, public ast::ASTVisitor
20902185 if (aloads.empty ()) {
20912186 if (clock.edge == ast::EdgeKind::BothEdges) {
20922187 // dual-edge: synthesize via posedge+negedge FFs and mux (ref: https://vlsi-soc.blogspot.com/2013/06/dual-edge-triggered-flip-flop.html)
2093- for (auto [named_chunk, name] : generate_subfield_names (driven_chunk, type)) {
2094- std::string var_name = RTLIL::unescape_id (netlist.id (*named_chunk.variable .get_symbol ()));
2095-
2096- // intermediaries that dont connect directly to output
2097- std::string pos_wire_name = " $driver$pos$q$" + var_name + name;
2098- RTLIL::Wire *pos_q = netlist.canvas ->addWire (netlist.canvas ->uniquify (pos_wire_name), named_chunk.bitwidth ());
2099-
2100- std::string neg_wire_name = " $driver$neg$q$" + var_name + name;
2101- RTLIL::Wire *neg_q = netlist.canvas ->addWire (netlist.canvas ->uniquify (neg_wire_name), named_chunk.bitwidth ());
2102-
2103- std::string pos_name = " $driver$pos$" + var_name + name;
2104- RTLIL::Cell *pos_ff = netlist.canvas ->addDff (netlist.canvas ->uniquify (pos_name),
2105- timing.triggers [0 ].signal ,
2106- assigned.extract (named_chunk.base - driven_chunk.base , named_chunk.bitwidth ()),
2107- pos_q,
2108- /* edge_polarity=*/ true );
2109- transfer_attrs (symbol, pos_ff);
2110-
2111- std::string neg_name = " $driver$neg$" + var_name + name;
2112- RTLIL::Cell *neg_ff = netlist.canvas ->addDff (netlist.canvas ->uniquify (neg_name),
2113- timing.triggers [0 ].signal ,
2114- assigned.extract (named_chunk.base - driven_chunk.base , named_chunk.bitwidth ()),
2115- neg_q,
2116- /* edge_polarity=*/ false );
2117- transfer_attrs (symbol, neg_ff);
2118-
2119- // mux behavior: A=neg_q (output when clk=0), B=pos_q (output when clk=1), S=clk
2120- std::string mux_name = " $driver$mux$" + var_name + name;
2121- RTLIL::Cell *mux = netlist.canvas ->addMux (netlist.canvas ->uniquify (mux_name),
2122- /* A=*/ neg_q,
2123- /* B=*/ pos_q,
2124- /* S=*/ timing.triggers [0 ].signal ,
2125- /* Y=*/ netlist.convert_static (named_chunk));
2126- transfer_attrs (symbol, mux);
2188+ for (auto named : generate_subfield_names (driven_chunk, type)) {
2189+ auto [named_chunk, name] = named;
2190+ add_dual_edge_dff (netlist, symbol, named,
2191+ timing.triggers [0 ].signal ,
2192+ assigned.extract (named_chunk.base - driven_chunk.base , named_chunk.bitwidth ()),
2193+ netlist.convert_static (named_chunk));
21272194 }
21282195 } else {
21292196 for (auto [named_chunk, name] : generate_subfield_names (driven_chunk, type)) {
@@ -2151,48 +2218,15 @@ struct PopulateNetlist : public TimingPatternInterpretor, public ast::ASTVisitor
21512218 if (!aldff_q.empty ()) {
21522219 if (clock.edge == ast::EdgeKind::BothEdges) {
21532220 for (auto driven_chunk2 : aldff_q.chunks ())
2154- for (auto [named_chunk, name] : generate_subfield_names (driven_chunk2, type)) {
2155- std::string var_name = RTLIL::unescape_id (netlist.id (*named_chunk.variable .get_symbol ()));
2156-
2157- // intermediaries
2158- std::string pos_wire_name = " $driver$pos$q$" + var_name + name;
2159- RTLIL::Wire *pos_q = netlist.canvas ->addWire (netlist.canvas ->uniquify (pos_wire_name), named_chunk.bitwidth ());
2160-
2161- std::string neg_wire_name = " $driver$neg$q$" + var_name + name;
2162- RTLIL::Wire *neg_q = netlist.canvas ->addWire (netlist.canvas ->uniquify (neg_wire_name), named_chunk.bitwidth ());
2163-
2164- // posedge aldff
2165- std::string pos_name = " $driver$pos$" + var_name + name;
2166- RTLIL::Cell *pos_ff = netlist.canvas ->addAldff (netlist.canvas ->uniquify (pos_name),
2167- timing.triggers [0 ].signal ,
2168- aloads[0 ].trigger ,
2169- assigned.extract (named_chunk.base - driven_chunk.base , named_chunk.bitwidth ()),
2170- pos_q,
2171- aloads[0 ].values .evaluate (netlist, named_chunk),
2172- /* clk_polarity=*/ true ,
2173- aloads[0 ].trigger_polarity );
2174- transfer_attrs (symbol, pos_ff);
2175-
2176- // negedge aldff
2177- std::string neg_name = " $driver$neg$" + var_name + name;
2178- RTLIL::Cell *neg_ff = netlist.canvas ->addAldff (netlist.canvas ->uniquify (neg_name),
2179- timing.triggers [0 ].signal ,
2180- aloads[0 ].trigger ,
2181- assigned.extract (named_chunk.base - driven_chunk.base , named_chunk.bitwidth ()),
2182- neg_q,
2183- aloads[0 ].values .evaluate (netlist, named_chunk),
2184- /* clk_polarity=*/ false ,
2185- aloads[0 ].trigger_polarity );
2186- transfer_attrs (symbol, neg_ff);
2187-
2188- // mux behavior: A=neg_q (output when clk=0), B=pos_q (output when clk=1), S=clk
2189- std::string mux_name = " $driver$mux$" + var_name + name;
2190- RTLIL::Cell *mux = netlist.canvas ->addMux (netlist.canvas ->uniquify (mux_name),
2191- /* A=*/ neg_q,
2192- /* B=*/ pos_q,
2193- /* S=*/ timing.triggers [0 ].signal ,
2194- /* Y=*/ netlist.convert_static (named_chunk));
2195- transfer_attrs (symbol, mux);
2221+ for (auto named : generate_subfield_names (driven_chunk2, type)) {
2222+ auto [named_chunk, name] = named;
2223+ add_dual_edge_aldff (netlist, symbol, named,
2224+ timing.triggers [0 ].signal ,
2225+ aloads[0 ].trigger ,
2226+ assigned.extract (named_chunk.base - driven_chunk.base , named_chunk.bitwidth ()),
2227+ netlist.convert_static (named_chunk),
2228+ aloads[0 ].values .evaluate (netlist, named_chunk),
2229+ aloads[0 ].trigger_polarity );
21962230 }
21972231 } else {
21982232 for (auto driven_chunk2 : aldff_q.chunks ())
0 commit comments