Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/async_pattern.cc
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,11 @@ void TimingPatternInterpretor::handle_always(const ast::ProceduralBlockSymbol &s
break;

case ast::EdgeKind::BothEdges:
issuer.add_diag(diag::BothEdgesUnsupported, sigev.sourceRange);
if (!settings.allow_dual_edge_ff.value_or(false)) {
issuer.add_diag(diag::BothEdgesUnsupported, sigev.sourceRange);
break;
}
triggers.push_back(&sigev);
break;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/diag.cc
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ namespace diag {
engine.setSeverity(SignalSensitivityAmbiguous, DiagnosticSeverity::Warning);
engine.setMessage(EdgeImplicitMixing, "mixing of implicit and edge sensitivity");
engine.setMessage(GenericTimingUnsyn, "unsynthesizable timing control (ignore with '--ignore-timing')");
engine.setMessage(BothEdgesUnsupported, "'edge' sensitivity will not be synthesized");
engine.setMessage(BothEdgesUnsupported, "@(edge) will not be synthesized (override with '--allow-dual-edge-ff')");
engine.setMessage(WaitStatementUnsupported, "wait statement will not be synthesized");
engine.setSeverity(WaitStatementUnsupported, DiagnosticSeverity::Warning);
engine.setMessage(NoteSignalEvent, "signal event specified here");
Expand Down
142 changes: 131 additions & 11 deletions src/slang_frontend.cc
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ void SynthesisSettings::addOptions(slang::CommandLine &cmdLine) {
"For developers: stop after the AST is fully compiled");
cmdLine.add("--no-default-translate-off-format", no_default_translate_off,
"Do not interpret any comment directives marking disabled input unless specified with '--translate-off-format'");
cmdLine.add("--allow-dual-edge-ff", allow_dual_edge_ff,
"Allow synthesis of dual-edge flip-flops (@(edge))");
}

namespace ast = slang::ast;
Expand Down Expand Up @@ -1863,6 +1865,99 @@ struct HierarchyQueue {
std::vector<NetlistContext *> queue;
};

// Synthesizes two single-edge FFs (one posedge, one negedge) with the same D input,
// then uses a mux controlled by the clock to select the appropriate FF output.
static void add_dual_edge_dff(NetlistContext &netlist, const ast::ProceduralBlockSymbol &symbol,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move this to builder.cc and merge it with the async load version

const NamedChunk &named,
RTLIL::SigSpec clk, RTLIL::SigSpec d, RTLIL::SigSpec q)
{
auto [chunk, name] = named;

log_assert(chunk.variable.get_symbol() != nullptr);
log_assert(netlist.canvas != nullptr);

std::string base_name = Yosys::stringf("$driver$%s%s",
RTLIL::unescape_id(netlist.id(*chunk.variable.get_symbol())).c_str(),
name.c_str());

RTLIL::Wire *pos_q = netlist.canvas->addWire(
netlist.canvas->uniquify(Yosys::stringf("%s$pos$q", base_name.c_str())),
chunk.bitwidth()); // intermediaries
log_assert(pos_q != nullptr);

RTLIL::Wire *neg_q = netlist.canvas->addWire(
netlist.canvas->uniquify(Yosys::stringf("%s$neg$q", base_name.c_str())),
chunk.bitwidth()); // intermediaries
log_assert(neg_q != nullptr);

RTLIL::Cell *pos_ff = netlist.canvas->addDff(
netlist.canvas->uniquify(Yosys::stringf("%s$pos", base_name.c_str())),
clk, d, pos_q, /*edge_polarity=*/true);
log_assert(pos_ff != nullptr);
transfer_attrs(symbol, pos_ff);

// Create negedge FF
RTLIL::Cell *neg_ff = netlist.canvas->addDff(
netlist.canvas->uniquify(Yosys::stringf("%s$neg", base_name.c_str())),
clk, d, neg_q, /*edge_polarity=*/false);
log_assert(neg_ff != nullptr);
transfer_attrs(symbol, neg_ff);

// behaviour: when clk=0: select neg_q (captures on negedge), when clk=1: select pos_q (captures on posedge)
RTLIL::Cell *mux = netlist.canvas->addMux(
netlist.canvas->uniquify(Yosys::stringf("%s$mux", base_name.c_str())),
/*A=*/neg_q, /*B=*/pos_q, /*S=*/clk, /*Y=*/q);
log_assert(mux != nullptr);
transfer_attrs(symbol, mux);
}

// async load/reset version of add_dual_edge_dff()
static void add_dual_edge_aldff(NetlistContext &netlist, const ast::ProceduralBlockSymbol &symbol,
const NamedChunk &named,
RTLIL::SigSpec clk, RTLIL::SigSpec aload, RTLIL::SigSpec d,
RTLIL::SigSpec q, RTLIL::SigSpec ad, bool aload_polarity)
{
auto [chunk, name] = named;

log_assert(chunk.variable.get_symbol() != nullptr);
log_assert(netlist.canvas != nullptr);

std::string base_name = Yosys::stringf("$driver$%s%s",
RTLIL::unescape_id(netlist.id(*chunk.variable.get_symbol())).c_str(),
name.c_str());

RTLIL::Wire *pos_q = netlist.canvas->addWire(
netlist.canvas->uniquify(Yosys::stringf("%s$pos$q", base_name.c_str())),
chunk.bitwidth()); // intermediaries
log_assert(pos_q != nullptr);

RTLIL::Wire *neg_q = netlist.canvas->addWire(
netlist.canvas->uniquify(Yosys::stringf("%s$neg$q", base_name.c_str())),
chunk.bitwidth()); // intermediaries
log_assert(neg_q != nullptr);

RTLIL::Cell *pos_ff = netlist.canvas->addAldff(
netlist.canvas->uniquify(Yosys::stringf("%s$pos", base_name.c_str())),
clk, aload, d, pos_q, ad,
/*clk_polarity=*/true, aload_polarity);
log_assert(pos_ff != nullptr);
transfer_attrs(symbol, pos_ff);

RTLIL::Cell *neg_ff = netlist.canvas->addAldff(
netlist.canvas->uniquify(Yosys::stringf("%s$neg", base_name.c_str())),
clk, aload, d, neg_q, ad,
/*clk_polarity=*/false, aload_polarity);
log_assert(neg_ff != nullptr);
transfer_attrs(symbol, neg_ff);

// behaviour: when clk=0: select neg_q (captures on negedge), when clk=1: select pos_q (captures on posedge)
RTLIL::Cell *mux = netlist.canvas->addMux(
netlist.canvas->uniquify(Yosys::stringf("%s$mux", base_name.c_str())),
/*A=*/neg_q, /*B=*/pos_q, /*S=*/clk, /*Y=*/q);
log_assert(mux != nullptr);
transfer_attrs(symbol, mux);
}

struct PopulateNetlist : public TimingPatternInterpretor, public ast::ASTVisitor<PopulateNetlist, true, false> {
public:
HierarchyQueue &queue;
Expand Down Expand Up @@ -2088,13 +2183,24 @@ struct PopulateNetlist : public TimingPatternInterpretor, public ast::ASTVisitor

RTLIL::Cell *cell;
if (aloads.empty()) {
if (clock.edge == ast::EdgeKind::BothEdges) {
// dual-edge: synthesize via posedge+negedge FFs and mux (ref: https://vlsi-soc.blogspot.com/2013/06/dual-edge-triggered-flip-flop.html)
for (auto named : generate_subfield_names(driven_chunk, type)) {
auto [named_chunk, name] = named;
add_dual_edge_dff(netlist, symbol, named,
timing.triggers[0].signal,
assigned.extract(named_chunk.base - driven_chunk.base, named_chunk.bitwidth()),
netlist.convert_static(named_chunk));
}
} else {
for (auto [named_chunk, name] : generate_subfield_names(driven_chunk, type)) {
cell = netlist.canvas->addDff(netlist.canvas->uniquify("$driver$" + RTLIL::unescape_id(netlist.id(*named_chunk.variable.get_symbol())) + name),
timing.triggers[0].signal,
assigned.extract(named_chunk.base - driven_chunk.base, named_chunk.bitwidth()),
netlist.convert_static(named_chunk),
timing.triggers[0].edge_polarity);
transfer_attrs(symbol, cell);
}
}
} else if (aloads.size() == 1) {
VariableBits aldff_q;
Expand All @@ -2110,17 +2216,31 @@ struct PopulateNetlist : public TimingPatternInterpretor, public ast::ASTVisitor
}

if (!aldff_q.empty()) {
for (auto driven_chunk2 : aldff_q.chunks())
for (auto [named_chunk, name] : generate_subfield_names(driven_chunk2, type)) {
cell = netlist.canvas->addAldff(netlist.canvas->uniquify("$driver$" + RTLIL::unescape_id(netlist.id(*named_chunk.variable.get_symbol())) + name),
timing.triggers[0].signal,
aloads[0].trigger,
assigned.extract(named_chunk.base - driven_chunk.base, named_chunk.bitwidth()),
netlist.convert_static(named_chunk),
aloads[0].values.evaluate(netlist, named_chunk),
timing.triggers[0].edge_polarity,
aloads[0].trigger_polarity);
transfer_attrs(symbol, cell);
if (clock.edge == ast::EdgeKind::BothEdges) {
for (auto driven_chunk2 : aldff_q.chunks())
for (auto named : generate_subfield_names(driven_chunk2, type)) {
auto [named_chunk, name] = named;
add_dual_edge_aldff(netlist, symbol, named,
timing.triggers[0].signal,
aloads[0].trigger,
assigned.extract(named_chunk.base - driven_chunk.base, named_chunk.bitwidth()),
netlist.convert_static(named_chunk),
aloads[0].values.evaluate(netlist, named_chunk),
aloads[0].trigger_polarity);
}
} else {
for (auto driven_chunk2 : aldff_q.chunks())
for (auto [named_chunk, name] : generate_subfield_names(driven_chunk2, type)) {
cell = netlist.canvas->addAldff(netlist.canvas->uniquify("$driver$" + RTLIL::unescape_id(netlist.id(*named_chunk.variable.get_symbol())) + name),
timing.triggers[0].signal,
aloads[0].trigger,
assigned.extract(named_chunk.base - driven_chunk.base, named_chunk.bitwidth()),
netlist.convert_static(named_chunk),
aloads[0].values.evaluate(netlist, named_chunk),
timing.triggers[0].edge_polarity,
aloads[0].trigger_polarity);
transfer_attrs(symbol, cell);
}
}
}

Expand Down
1 change: 1 addition & 0 deletions src/slang_frontend.h
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@ struct SynthesisSettings {
std::optional<bool> empty_blackboxes;
std::optional<bool> ast_compilation_only;
std::optional<bool> no_default_translate_off;
std::optional<bool> allow_dual_edge_ff;
bool disable_instance_caching = false;

enum HierMode {
Expand Down
2 changes: 2 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ endif()

set(ALL_TESTS
unit/async.ys
unit/dualedge.ys
unit/function_call.ys
unit/latch.ys
unit/selftests.tcl
Expand Down Expand Up @@ -57,6 +58,7 @@ set(ALL_TESTS
various/wait_test.ys
various/expect_test.ys
various/program_test.ys
various/dualedge.ys
)

foreach(test_script ${ALL_TESTS})
Expand Down
70 changes: 70 additions & 0 deletions tests/unit/dualedge.ys
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@

read_slang --allow-dual-edge-ff <<EOF
module dualedge01_gate(
input logic clk,
input logic d,
output logic q
);
always_ff @(edge clk) begin
q <= d;
end
endmodule
EOF

read_verilog -icells <<EOF
module dualedge01_gold(
input clk,
input d,
output q
);
wire clk_past, q_past, d_past;
\$ff #(.WIDTH(1)) f1(.D(clk), .Q(clk_past));
\$ff #(.WIDTH(1)) f2(.D(q), .Q(q_past));
\$ff #(.WIDTH(1)) f3(.D(d), .Q(d_past));
assign q = (clk != clk_past) ? d_past : q_past;
endmodule
EOF

clk2fflogic
equiv_make dualedge01_gold dualedge01_gate dualedge01_equiv
equiv_induct dualedge01_equiv
equiv_status -assert

design -reset

read_slang --allow-dual-edge-ff <<EOF
module dualedge02_gate(
input logic clk,
input logic rst_n,
input logic d,
output logic q
);
always_ff @(edge clk or negedge rst_n) begin
if (!rst_n)
q <= 1'b0;
else
q <= d;
end
endmodule
EOF

read_verilog -icells <<EOF
module dualedge02_gold(
input clk,
input rst_n,
input d,
output q
);
wire clk_past, q_past, d_past, rst_n_past;
\$ff #(.WIDTH(1)) f1(.D(clk), .Q(clk_past));
\$ff #(.WIDTH(1)) f2(.D(q), .Q(q_past));
\$ff #(.WIDTH(1)) f3(.D(d), .Q(d_past));
\$ff #(.WIDTH(1)) f4(.D(rst_n), .Q(rst_n_past));
assign q = (!rst_n || !rst_n_past) ? 1'b0 : ((clk != clk_past) ? d_past : q_past);
endmodule
EOF

clk2fflogic
equiv_make dualedge02_gold dualedge02_gate dualedge02_equiv
equiv_induct dualedge02_equiv
equiv_status -assert
Loading