Skip to content

Commit 3c0acf0

Browse files
committed
only allow dualedge when flag passed and follow helper based approach for dualedge based on canvas->addDff/addAldff
1 parent de9dd39 commit 3c0acf0

File tree

5 files changed

+169
-85
lines changed

5 files changed

+169
-85
lines changed

src/async_pattern.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ void TimingPatternInterpretor::handle_always(const ast::ProceduralBlockSymbol &s
7979
break;
8080

8181
case ast::EdgeKind::BothEdges:
82+
if (!settings.allow_dual_edge_ff.value_or(false)) {
83+
issuer.add_diag(diag::BothEdgesUnsupported, sigev.sourceRange);
84+
break;
85+
}
8286
triggers.push_back(&sigev);
8387
break;
8488
}

src/diag.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ namespace diag {
123123
engine.setSeverity(SignalSensitivityAmbiguous, DiagnosticSeverity::Warning);
124124
engine.setMessage(EdgeImplicitMixing, "mixing of implicit and edge sensitivity");
125125
engine.setMessage(GenericTimingUnsyn, "unsynthesizable timing control (ignore with '--ignore-timing')");
126-
engine.setMessage(BothEdgesUnsupported, "'edge' sensitivity will not be synthesized");
126+
engine.setMessage(BothEdgesUnsupported, "@(edge) will not be synthesized (override with '--allow-dual-edge-ff')");
127127
engine.setMessage(WaitStatementUnsupported, "wait statement will not be synthesized");
128128
engine.setSeverity(WaitStatementUnsupported, DiagnosticSeverity::Warning);
129129
engine.setMessage(NoteSignalEvent, "signal event specified here");

src/slang_frontend.cc

Lines changed: 110 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -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

7173
namespace 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+
18661961
struct PopulateNetlist : public TimingPatternInterpretor, public ast::ASTVisitor<PopulateNetlist, true, false> {
18671962
public:
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())

src/slang_frontend.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,7 @@ struct SynthesisSettings {
410410
std::optional<bool> empty_blackboxes;
411411
std::optional<bool> ast_compilation_only;
412412
std::optional<bool> no_default_translate_off;
413+
std::optional<bool> allow_dual_edge_ff;
413414
bool disable_instance_caching = false;
414415

415416
enum HierMode {

tests/various/dualedge.ys

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,51 @@
1+
test_slangdiag -expect "@(edge) will not be synthesized (override with '--allow-dual-edge-ff')"
12
read_slang <<EOF
3+
module test_error(input logic clk, input logic d, output logic q);
4+
always_ff @(edge clk)
5+
q <= d;
6+
endmodule
7+
EOF
8+
9+
design -reset
10+
11+
test_slangdiag -expect "@(edge) will not be synthesized (override with '--allow-dual-edge-ff')"
12+
read_slang <<EOF
13+
module multi_edge(
14+
input logic clk1, clk2,
15+
input logic d1, d2,
16+
output logic q1, q2
17+
);
18+
always_ff @(edge clk1)
19+
q1 <= d1;
20+
21+
always_ff @(edge clk2)
22+
q2 <= d2;
23+
endmodule
24+
EOF
25+
26+
design -reset
27+
28+
test_slangdiag -expect "@(edge) will not be synthesized (override with '--allow-dual-edge-ff')"
29+
read_slang <<EOF
30+
module edge_with_reset(
31+
input logic clk, rst_n,
32+
input logic d,
33+
output logic q
34+
);
35+
always_ff @(edge clk or negedge rst_n) begin
36+
if (!rst_n)
37+
q <= 1'b0;
38+
else
39+
q <= d;
40+
end
41+
endmodule
42+
EOF
43+
44+
design -reset
45+
46+
read_slang --allow-dual-edge-ff <<EOF
247
module dualedge(input logic clk, input logic d, output logic q);
3-
always_ff @(edge clk)
48+
always_ff @(edge clk)
449
q <= d;
550
endmodule
651
EOF
@@ -17,15 +62,15 @@ select -assert-count 1 t:$mux %ci:+[S] w:clk %d
1762
select -assert-count 1 t:$mux %co:+[Y] w:q %d
1863

1964
design -reset
20-
read_slang <<EOF
65+
read_slang --allow-dual-edge-ff <<EOF
2166
module mixed_edges(input logic clk, input logic d, output logic q_pos, output logic q_neg, output logic q_dual);
22-
always_ff @(posedge clk)
67+
always_ff @(posedge clk)
2368
q_pos <= d;
2469

25-
always_ff @(negedge clk)
70+
always_ff @(negedge clk)
2671
q_neg <= d;
2772

28-
always_ff @(edge clk)
73+
always_ff @(edge clk)
2974
q_dual <= d;
3075
endmodule
3176
EOF
@@ -37,7 +82,7 @@ select -assert-count 2 t:$dff r:CLK_POLARITY=0 %i
3782

3883

3984
design -reset
40-
read_slang <<EOF
85+
read_slang --allow-dual-edge-ff <<EOF
4186
module dualedge_async_reset(
4287
input logic clk,
4388
input logic rst_n,
@@ -65,13 +110,13 @@ select -assert-count 1 t:$mux %ci:+[S] w:clk %d
65110
select -assert-count 1 t:$mux %co:+[Y] w:q %d
66111

67112
design -reset
68-
read_slang <<EOF
113+
read_slang --allow-dual-edge-ff <<EOF
69114
module dualedge_multibit(
70115
input logic clk,
71116
input logic [7:0] d,
72117
output logic [7:0] q
73118
);
74-
always_ff @(edge clk)
119+
always_ff @(edge clk)
75120
q <= d;
76121
endmodule
77122
EOF

0 commit comments

Comments
 (0)