aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG3
-rw-r--r--README.md5
-rw-r--r--backends/aiger/xaiger.cc7
-rw-r--r--frontends/verific/verific.cc17
-rw-r--r--frontends/verific/verific.h2
-rw-r--r--frontends/verific/verificsva.cc42
-rw-r--r--frontends/verilog/verilog_lexer.l6
-rw-r--r--frontends/verilog/verilog_parser.y39
-rw-r--r--passes/hierarchy/Makefile.inc1
-rw-r--r--passes/hierarchy/clkpart.cc269
-rw-r--r--passes/hierarchy/submod.cc74
-rw-r--r--passes/memory/memory.cc2
-rw-r--r--passes/opt/Makefile.inc1
-rw-r--r--passes/opt/opt_mem.cc143
-rw-r--r--passes/proc/proc_dlatch.cc20
-rw-r--r--passes/techmap/abc9.cc2
-rw-r--r--techlibs/gowin/.gitignore2
-rw-r--r--tests/arch/gowin/mux.ys1
-rw-r--r--tests/simple_abc9/abc9.v12
-rw-r--r--tests/various/submod.ys50
-rwxr-xr-xtests/various/svalways.sh63
21 files changed, 702 insertions, 59 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 1fc139d49..d9d261fbc 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -51,6 +51,9 @@ Yosys 0.9 .. Yosys 0.9-dev
- "synth_ice40 -dsp" to infer DSP blocks
- Added latch support to synth_xilinx
- Added "check -mapped"
+ - Added checking of SystemVerilog always block types (always_comb,
+ always_latch and always_ff)
+ - Added "clkpart" pass
Yosys 0.8 .. Yosys 0.9
----------------------
diff --git a/README.md b/README.md
index db7810cb4..e46971526 100644
--- a/README.md
+++ b/README.md
@@ -371,6 +371,11 @@ Verilog Attributes and non-standard features
for example, to specify the clk-to-Q delay of a flip-flop for consideration
during techmapping.
+- The frontend sets attributes ``always_comb``, ``always_latch`` and
+ ``always_ff`` on processes derived from SystemVerilog style always blocks
+ according to the type of the always. These are checked for correctness in
+ ``proc_dlatch``.
+
- In addition to the ``(* ... *)`` attribute syntax, Yosys supports
the non-standard ``{* ... *}`` attribute syntax to set default attributes
for everything that comes after the ``{* ... *}`` statement. (Reset
diff --git a/backends/aiger/xaiger.cc b/backends/aiger/xaiger.cc
index 5d125b653..de2f7dd73 100644
--- a/backends/aiger/xaiger.cc
+++ b/backends/aiger/xaiger.cc
@@ -545,15 +545,12 @@ struct XAigerWriter
undriven_bits.erase(bit);
if (!undriven_bits.empty() && !holes_mode) {
- bool whole_module = module->design->selected_whole_module(module->name);
undriven_bits.sort();
for (auto bit : undriven_bits) {
- if (whole_module)
- log_warning("Treating undriven bit %s.%s like $anyseq.\n", log_id(module), log_signal(bit));
+ log_warning("Treating undriven bit %s.%s like $anyseq.\n", log_id(module), log_signal(bit));
input_bits.insert(bit);
}
- if (whole_module)
- log_warning("Treating a total of %d undriven bits in %s like $anyseq.\n", GetSize(undriven_bits), log_id(module));
+ log_warning("Treating a total of %d undriven bits in %s like $anyseq.\n", GetSize(undriven_bits), log_id(module));
}
if (holes_mode) {
diff --git a/frontends/verific/verific.cc b/frontends/verific/verific.cc
index a5c4aa26a..843e7b9b4 100644
--- a/frontends/verific/verific.cc
+++ b/frontends/verific/verific.cc
@@ -130,7 +130,7 @@ RTLIL::SigBit VerificImporter::net_map_at(Net *net)
bool is_blackbox(Netlist *nl)
{
- if (nl->IsBlackBox())
+ if (nl->IsBlackBox() || nl->IsEmptyBox())
return true;
const char *attr = nl->GetAttValue("blackbox");
@@ -784,15 +784,15 @@ void VerificImporter::merge_past_ffs(pool<RTLIL::Cell*> &candidates)
merge_past_ffs_clock(it.second, it.first.first, it.first.second);
}
-void VerificImporter::import_netlist(RTLIL::Design *design, Netlist *nl, std::set<Netlist*> &nl_todo)
+void VerificImporter::import_netlist(RTLIL::Design *design, Netlist *nl, std::set<Netlist*> &nl_todo, bool norename)
{
std::string netlist_name = nl->GetAtt(" \\top") ? nl->CellBaseName() : nl->Owner()->Name();
std::string module_name = netlist_name;
- if (nl->IsOperator()) {
+ if (nl->IsOperator() || nl->IsPrimitive()) {
module_name = "$verific$" + module_name;
} else {
- if (*nl->Name()) {
+ if (!norename && *nl->Name()) {
module_name += "(";
module_name += nl->Name();
module_name += ")";
@@ -1409,7 +1409,7 @@ void VerificImporter::import_netlist(RTLIL::Design *design, Netlist *nl, std::se
std::string inst_type = inst->View()->Owner()->Name();
- if (inst->View()->IsOperator()) {
+ if (inst->View()->IsOperator() || inst->View()->IsPrimitive()) {
inst_type = "$verific$" + inst_type;
} else {
if (*inst->View()->Name()) {
@@ -1899,7 +1899,7 @@ void verific_import(Design *design, const std::map<std::string,std::string> &par
Netlist *nl = *nl_todo.begin();
if (nl_done.count(nl) == 0) {
VerificImporter importer(false, false, false, false, false, false, false);
- importer.import_netlist(design, nl, nl_todo);
+ importer.import_netlist(design, nl, nl_todo, nl->Owner()->Name() == top);
}
nl_todo.erase(nl);
nl_done.insert(nl);
@@ -2373,6 +2373,8 @@ struct VerificPass : public Pass {
if (argidx > GetSize(args) && args[argidx].compare(0, 1, "-") == 0)
cmd_error(args, argidx, "unknown option");
+ std::set<std::string> top_mod_names;
+
if (mode_all)
{
log("Running hier_tree::ElaborateAll().\n");
@@ -2401,6 +2403,7 @@ struct VerificPass : public Pass {
for (; argidx < GetSize(args); argidx++)
{
const char *name = args[argidx].c_str();
+ top_mod_names.insert(name);
VeriLibrary* veri_lib = veri_file::GetLibrary(work.c_str(), 1);
if (veri_lib) {
@@ -2466,7 +2469,7 @@ struct VerificPass : public Pass {
if (nl_done.count(nl) == 0) {
VerificImporter importer(mode_gates, mode_keep, mode_nosva,
mode_names, mode_verific, mode_autocover, mode_fullinit);
- importer.import_netlist(design, nl, nl_todo);
+ importer.import_netlist(design, nl, nl_todo, top_mod_names.count(nl->Owner()->Name()));
}
nl_todo.erase(nl);
nl_done.insert(nl);
diff --git a/frontends/verific/verific.h b/frontends/verific/verific.h
index 5cbd78f7b..2ccfcd42c 100644
--- a/frontends/verific/verific.h
+++ b/frontends/verific/verific.h
@@ -93,7 +93,7 @@ struct VerificImporter
void merge_past_ffs_clock(pool<RTLIL::Cell*> &candidates, SigBit clock, bool clock_pol);
void merge_past_ffs(pool<RTLIL::Cell*> &candidates);
- void import_netlist(RTLIL::Design *design, Verific::Netlist *nl, std::set<Verific::Netlist*> &nl_todo);
+ void import_netlist(RTLIL::Design *design, Verific::Netlist *nl, std::set<Verific::Netlist*> &nl_todo, bool norename = false);
};
void verific_import_sva_assert(VerificImporter *importer, Verific::Instance *inst);
diff --git a/frontends/verific/verificsva.cc b/frontends/verific/verificsva.cc
index 909e9b4f1..49c0c40ac 100644
--- a/frontends/verific/verificsva.cc
+++ b/frontends/verific/verificsva.cc
@@ -36,6 +36,8 @@
// basic_property:
// sequence
// not basic_property
+// nexttime basic_property
+// nexttime[N] basic_property
// sequence #-# basic_property
// sequence #=# basic_property
// basic_property or basic_property (cover only)
@@ -1264,6 +1266,26 @@ struct VerificSvaImporter
return node;
}
+ if (inst->Type() == PRIM_SVA_NEXTTIME || inst->Type() == PRIM_SVA_S_NEXTTIME)
+ {
+ const char *sva_low_s = inst->GetAttValue("sva:low");
+ const char *sva_high_s = inst->GetAttValue("sva:high");
+
+ int sva_low = atoi(sva_low_s);
+ int sva_high = atoi(sva_high_s);
+ log_assert(sva_low == sva_high);
+
+ int node = start_node;
+
+ for (int i = 0; i < sva_low; i++) {
+ int next_node = fsm.createNode();
+ fsm.createEdge(node, next_node);
+ node = next_node;
+ }
+
+ return parse_sequence(fsm, node, inst->GetInput());
+ }
+
if (inst->Type() == PRIM_SVA_SEQ_CONCAT)
{
const char *sva_low_s = inst->GetAttValue("sva:low");
@@ -1590,15 +1612,25 @@ struct VerificSvaImporter
Instance *consequent_inst = net_to_ast_driver(consequent_net);
if (consequent_inst && (consequent_inst->Type() == PRIM_SVA_UNTIL || consequent_inst->Type() == PRIM_SVA_S_UNTIL ||
- consequent_inst->Type() == PRIM_SVA_UNTIL_WITH || consequent_inst->Type() == PRIM_SVA_S_UNTIL_WITH))
+ consequent_inst->Type() == PRIM_SVA_UNTIL_WITH || consequent_inst->Type() == PRIM_SVA_S_UNTIL_WITH ||
+ consequent_inst->Type() == PRIM_SVA_ALWAYS || consequent_inst->Type() == PRIM_SVA_S_ALWAYS))
{
bool until_with = consequent_inst->Type() == PRIM_SVA_UNTIL_WITH || consequent_inst->Type() == PRIM_SVA_S_UNTIL_WITH;
- Net *until_net = consequent_inst->GetInput2();
- consequent_net = consequent_inst->GetInput1();
- consequent_inst = net_to_ast_driver(consequent_net);
+ Net *until_net = nullptr;
+ if (consequent_inst->Type() == PRIM_SVA_ALWAYS || consequent_inst->Type() == PRIM_SVA_S_ALWAYS)
+ {
+ consequent_net = consequent_inst->GetInput();
+ consequent_inst = net_to_ast_driver(consequent_net);
+ }
+ else
+ {
+ until_net = consequent_inst->GetInput2();
+ consequent_net = consequent_inst->GetInput1();
+ consequent_inst = net_to_ast_driver(consequent_net);
+ }
- SigBit until_sig = parse_expression(until_net);
+ SigBit until_sig = until_net ? parse_expression(until_net) : RTLIL::S0;
SigBit not_until_sig = module->Not(NEW_ID, until_sig);
antecedent_fsm.createEdge(node, node, not_until_sig);
diff --git a/frontends/verilog/verilog_lexer.l b/frontends/verilog/verilog_lexer.l
index 4acfb414d..c8984c2c4 100644
--- a/frontends/verilog/verilog_lexer.l
+++ b/frontends/verilog/verilog_lexer.l
@@ -188,9 +188,9 @@ YOSYS_NAMESPACE_END
"unique0" { SV_KEYWORD(TOK_UNIQUE); }
"priority" { SV_KEYWORD(TOK_PRIORITY); }
-"always_comb" { SV_KEYWORD(TOK_ALWAYS); }
-"always_ff" { SV_KEYWORD(TOK_ALWAYS); }
-"always_latch" { SV_KEYWORD(TOK_ALWAYS); }
+"always_comb" { SV_KEYWORD(TOK_ALWAYS_COMB); }
+"always_ff" { SV_KEYWORD(TOK_ALWAYS_FF); }
+"always_latch" { SV_KEYWORD(TOK_ALWAYS_LATCH); }
/* use special token for labels on assert, assume, cover, and restrict because it's insanley complex
to fix parsing of cells otherwise. (the current cell parser forces a reduce very early to update some
diff --git a/frontends/verilog/verilog_parser.y b/frontends/verilog/verilog_parser.y
index 77f6d2051..daea3b43a 100644
--- a/frontends/verilog/verilog_parser.y
+++ b/frontends/verilog/verilog_parser.y
@@ -141,6 +141,7 @@ struct specify_rise_fall {
%token TOK_INTERFACE TOK_ENDINTERFACE TOK_MODPORT TOK_VAR
%token TOK_INPUT TOK_OUTPUT TOK_INOUT TOK_WIRE TOK_WAND TOK_WOR TOK_REG TOK_LOGIC
%token TOK_INTEGER TOK_SIGNED TOK_ASSIGN TOK_ALWAYS TOK_INITIAL
+%token TOK_ALWAYS_FF TOK_ALWAYS_COMB TOK_ALWAYS_LATCH
%token TOK_BEGIN TOK_END TOK_IF TOK_ELSE TOK_FOR TOK_WHILE TOK_REPEAT
%token TOK_DPI_FUNCTION TOK_POSEDGE TOK_NEGEDGE TOK_OR TOK_AUTOMATIC
%token TOK_CASE TOK_CASEX TOK_CASEZ TOK_ENDCASE TOK_DEFAULT
@@ -156,7 +157,7 @@ struct specify_rise_fall {
%type <ast> range range_or_multirange non_opt_range non_opt_multirange range_or_signed_int
%type <ast> wire_type expr basic_expr concat_list rvalue lvalue lvalue_concat_list
%type <string> opt_label opt_sva_label tok_prim_wrapper hierarchical_id hierarchical_type_id
-%type <boolean> opt_signed opt_property unique_case_attr
+%type <boolean> opt_signed opt_property unique_case_attr always_comb_or_latch always_or_always_ff
%type <al> attr case_attr
%type <specify_target_ptr> specify_target
@@ -1581,10 +1582,28 @@ cell_port:
free_attr($1);
};
+always_comb_or_latch:
+ TOK_ALWAYS_COMB {
+ $$ = false;
+ } |
+ TOK_ALWAYS_LATCH {
+ $$ = true;
+ };
+
+always_or_always_ff:
+ TOK_ALWAYS {
+ $$ = false;
+ } |
+ TOK_ALWAYS_FF {
+ $$ = true;
+ };
+
always_stmt:
- attr TOK_ALWAYS {
+ attr always_or_always_ff {
AstNode *node = new AstNode(AST_ALWAYS);
append_attr(node, $1);
+ if ($2)
+ node->attributes[ID(always_ff)] = AstNode::mkconst_int(1, false);
ast_stack.back()->children.push_back(node);
ast_stack.push_back(node);
} always_cond {
@@ -1595,6 +1614,22 @@ always_stmt:
ast_stack.pop_back();
ast_stack.pop_back();
} |
+ attr always_comb_or_latch {
+ AstNode *node = new AstNode(AST_ALWAYS);
+ append_attr(node, $1);
+ if ($2)
+ node->attributes[ID(always_latch)] = AstNode::mkconst_int(1, false);
+ else
+ node->attributes[ID(always_comb)] = AstNode::mkconst_int(1, false);
+ ast_stack.back()->children.push_back(node);
+ ast_stack.push_back(node);
+ AstNode *block = new AstNode(AST_BLOCK);
+ ast_stack.back()->children.push_back(block);
+ ast_stack.push_back(block);
+ } behavioral_stmt {
+ ast_stack.pop_back();
+ ast_stack.pop_back();
+ } |
attr TOK_INITIAL {
AstNode *node = new AstNode(AST_INITIAL);
append_attr(node, $1);
diff --git a/passes/hierarchy/Makefile.inc b/passes/hierarchy/Makefile.inc
index b3f139b72..ea809ec08 100644
--- a/passes/hierarchy/Makefile.inc
+++ b/passes/hierarchy/Makefile.inc
@@ -2,4 +2,5 @@
OBJS += passes/hierarchy/hierarchy.o
OBJS += passes/hierarchy/uniquify.o
OBJS += passes/hierarchy/submod.o
+OBJS += passes/hierarchy/clkpart.o
diff --git a/passes/hierarchy/clkpart.cc b/passes/hierarchy/clkpart.cc
new file mode 100644
index 000000000..8f671c175
--- /dev/null
+++ b/passes/hierarchy/clkpart.cc
@@ -0,0 +1,269 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * 2019 Eddie Hung <eddie@fpgeh.com>
+ *
+ * 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/register.h"
+#include "kernel/sigtools.h"
+#include "kernel/celltypes.h"
+#include "kernel/rtlil.h"
+#include "kernel/log.h"
+
+USING_YOSYS_NAMESPACE
+PRIVATE_NAMESPACE_BEGIN
+
+struct ClkPartPass : public Pass {
+ ClkPartPass() : Pass("clkpart", "partition design according to clock domain") { }
+ void help() YS_OVERRIDE
+ {
+ // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+ log("\n");
+ log(" clkpart [options] [selection]\n");
+ log("\n");
+ log("Partition the contents of selected modules according to the clock (and optionally\n");
+ log("the enable) domains of its $_DFF* cells by extracting them into sub-modules,\n");
+ log("using the `submod` command.\n");
+ log("Sub-modules created by this command are marked with a 'clkpart' attribute.\n");
+ log("\n");
+ log(" -unpart\n");
+ log(" undo this operation within the selected modules, by flattening those with\n");
+ log(" a 'clkpart' attribute into those modules without this attribute.\n");
+ log("\n");
+ log(" -enable\n");
+ log(" also consider enable domains.\n");
+ log("\n");
+ }
+
+ bool unpart_mode, enable_mode;
+
+ void clear_flags() YS_OVERRIDE
+ {
+ unpart_mode = false;
+ enable_mode = false;
+ }
+ void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
+ {
+ log_header(design, "Executing CLKPART pass (partition design according to clock domain).\n");
+ log_push();
+
+ clear_flags();
+
+ size_t argidx;
+ for (argidx = 1; argidx < args.size(); argidx++)
+ {
+ if (args[argidx] == "-unpart") {
+ unpart_mode = true;
+ continue;
+ }
+ if (args[argidx] == "-enable") {
+ enable_mode = true;
+ continue;
+ }
+ break;
+ }
+ extra_args(args, argidx, design);
+
+ if (unpart_mode)
+ unpart(design);
+ else
+ part(design);
+
+ log_pop();
+ }
+
+ void part(RTLIL::Design *design)
+ {
+ CellTypes ct(design);
+ SigMap assign_map;
+
+ for (auto mod : design->selected_modules())
+ {
+ if (mod->processes.size() > 0) {
+ log("Skipping module %s as it contains processes.\n", log_id(mod));
+ continue;
+ }
+
+ assign_map.set(mod);
+
+ std::vector<RTLIL::Cell*> all_cells = mod->selected_cells();
+ std::set<RTLIL::Cell*> unassigned_cells(all_cells.begin(), all_cells.end());
+
+ std::set<RTLIL::Cell*> expand_queue, next_expand_queue;
+ std::set<RTLIL::Cell*> expand_queue_up, next_expand_queue_up;
+ std::set<RTLIL::Cell*> expand_queue_down, next_expand_queue_down;
+
+ typedef tuple<bool, RTLIL::SigSpec, bool, RTLIL::SigSpec> clkdomain_t;
+ std::map<clkdomain_t, vector<RTLIL::IdString>> assigned_cells;
+ std::map<RTLIL::Cell*, clkdomain_t> assigned_cells_reverse;
+
+ std::map<RTLIL::Cell*, std::set<RTLIL::SigBit>> cell_to_bit, cell_to_bit_up, cell_to_bit_down;
+ std::map<RTLIL::SigBit, std::set<RTLIL::Cell*>> bit_to_cell, bit_to_cell_up, bit_to_cell_down;
+
+ for (auto cell : all_cells)
+ {
+ clkdomain_t key;
+
+ for (auto &conn : cell->connections())
+ for (auto bit : conn.second) {
+ bit = assign_map(bit);
+ if (bit.wire != nullptr) {
+ cell_to_bit[cell].insert(bit);
+ bit_to_cell[bit].insert(cell);
+ if (ct.cell_input(cell->type, conn.first)) {
+ cell_to_bit_up[cell].insert(bit);
+ bit_to_cell_down[bit].insert(cell);
+ }
+ if (ct.cell_output(cell->type, conn.first)) {
+ cell_to_bit_down[cell].insert(bit);
+ bit_to_cell_up[bit].insert(cell);
+ }
+ }
+ }
+
+ if (cell->type.in(ID($_DFF_N_), ID($_DFF_P_)))
+ {
+ key = clkdomain_t(cell->type == ID($_DFF_P_), assign_map(cell->getPort(ID(C))), true, RTLIL::SigSpec());
+ }
+ else
+ if (cell->type.in(ID($_DFFE_NN_), ID($_DFFE_NP_), ID($_DFFE_PN_), ID($_DFFE_PP_)))
+ {
+ bool this_clk_pol = cell->type.in(ID($_DFFE_PN_), ID($_DFFE_PP_));
+ bool this_en_pol = !enable_mode || cell->type.in(ID($_DFFE_NP_), ID($_DFFE_PP_));
+ key = clkdomain_t(this_clk_pol, assign_map(cell->getPort(ID(C))), this_en_pol, enable_mode ? assign_map(cell->getPort(ID(E))) : RTLIL::SigSpec());
+ }
+ else
+ continue;
+
+ unassigned_cells.erase(cell);
+ expand_queue.insert(cell);
+ expand_queue_up.insert(cell);
+ expand_queue_down.insert(cell);
+
+ assigned_cells[key].push_back(cell->name);
+ assigned_cells_reverse[cell] = key;
+ }
+
+ while (!expand_queue_up.empty() || !expand_queue_down.empty())
+ {
+ if (!expand_queue_up.empty())
+ {
+ RTLIL::Cell *cell = *expand_queue_up.begin();
+ clkdomain_t key = assigned_cells_reverse.at(cell);
+ expand_queue_up.erase(cell);
+
+ for (auto bit : cell_to_bit_up[cell])
+ for (auto c : bit_to_cell_up[bit])
+ if (unassigned_cells.count(c)) {
+ unassigned_cells.erase(c);
+ next_expand_queue_up.insert(c);
+ assigned_cells[key].push_back(c->name);
+ assigned_cells_reverse[c] = key;
+ expand_queue.insert(c);
+ }
+ }
+
+ if (!expand_queue_down.empty())
+ {
+ RTLIL::Cell *cell = *expand_queue_down.begin();
+ clkdomain_t key = assigned_cells_reverse.at(cell);
+ expand_queue_down.erase(cell);
+
+ for (auto bit : cell_to_bit_down[cell])
+ for (auto c : bit_to_cell_down[bit])
+ if (unassigned_cells.count(c)) {
+ unassigned_cells.erase(c);
+ next_expand_queue_up.insert(c);
+ assigned_cells[key].push_back(c->name);
+ assigned_cells_reverse[c] = key;
+ expand_queue.insert(c);
+ }
+ }
+
+ if (expand_queue_up.empty() && expand_queue_down.empty()) {
+ expand_queue_up.swap(next_expand_queue_up);
+ expand_queue_down.swap(next_expand_queue_down);
+ }
+ }
+
+ while (!expand_queue.empty())
+ {
+ RTLIL::Cell *cell = *expand_queue.begin();
+ clkdomain_t key = assigned_cells_reverse.at(cell);
+ expand_queue.erase(cell);
+
+ for (auto bit : cell_to_bit.at(cell)) {
+ for (auto c : bit_to_cell[bit])
+ if (unassigned_cells.count(c)) {
+ unassigned_cells.erase(c);
+ next_expand_queue.insert(c);
+ assigned_cells[key].push_back(c->name);
+ assigned_cells_reverse[c] = key;
+ }
+ bit_to_cell[bit].clear();
+ }
+
+ if (expand_queue.empty())
+ expand_queue.swap(next_expand_queue);
+ }
+
+ clkdomain_t key(true, RTLIL::SigSpec(), true, RTLIL::SigSpec());
+ for (auto cell : unassigned_cells) {
+ assigned_cells[key].push_back(cell->name);
+ assigned_cells_reverse[cell] = key;
+ }
+
+ log_header(design, "Summary of detected clock domains:\n");
+ for (auto &it : assigned_cells)
+ log(" %d cells in clk=%s%s, en=%s%s\n", GetSize(it.second),
+ std::get<0>(it.first) ? "" : "!", log_signal(std::get<1>(it.first)),
+ std::get<2>(it.first) ? "" : "!", log_signal(std::get<3>(it.first)));
+
+ if (assigned_cells.size() > 1)
+ for (auto &it : assigned_cells) {
+ RTLIL::Selection sel(false);
+ sel.selected_members[mod->name] = pool<IdString>(it.second.begin(), it.second.end());
+
+ RTLIL::IdString submod = stringf("%s.%s", mod->name.c_str(), NEW_ID.c_str());
+ Pass::call_on_selection(design, sel, stringf("submod -name %s", submod.c_str()));
+
+ design->module(submod)->set_bool_attribute(ID(clkpart));
+ }
+ }
+ }
+
+ void unpart(RTLIL::Design *design)
+ {
+ vector<Module*> keeped;
+ for (auto mod : design->selected_modules()) {
+ if (mod->get_bool_attribute(ID(clkpart)))
+ continue;
+ if (mod->get_bool_attribute(ID(keep_hierarchy)))
+ continue;
+ keeped.push_back(mod);
+ mod->set_bool_attribute(ID(keep_hierarchy));
+ }
+
+ Pass::call(design, "flatten");
+
+ for (auto mod : keeped)
+ mod->set_bool_attribute(ID(keep_hierarchy), false);
+
+ }
+} ClkPartPass;
+
+PRIVATE_NAMESPACE_END
diff --git a/passes/hierarchy/submod.cc b/passes/hierarchy/submod.cc
index ec242aa1f..212932e46 100644
--- a/passes/hierarchy/submod.cc
+++ b/passes/hierarchy/submod.cc
@@ -20,6 +20,7 @@
#include "kernel/register.h"
#include "kernel/celltypes.h"
#include "kernel/log.h"
+#include "kernel/sigtools.h"
#include <stdlib.h>
#include <stdio.h>
#include <set>
@@ -32,6 +33,8 @@ struct SubmodWorker
CellTypes ct;
RTLIL::Design *design;
RTLIL::Module *module;
+ SigMap sigmap;
+ pool<SigBit> outputs;
bool copy_mode;
std::string opt_name;
@@ -44,44 +47,44 @@ struct SubmodWorker
std::map<std::string, SubModule> submodules;
- struct wire_flags_t {
+ struct bit_flags_t {
RTLIL::Wire *new_wire;
bool is_int_driven, is_int_used, is_ext_driven, is_ext_used;
- wire_flags_t() : new_wire(NULL), is_int_driven(false), is_int_used(false), is_ext_driven(false), is_ext_used(false) { }
+ bit_flags_t() : new_wire(NULL), is_int_driven(false), is_int_used(false), is_ext_driven(false), is_ext_used(false) { }
};
- std::map<RTLIL::Wire*, wire_flags_t> wire_flags;
+ std::map<SigBit, bit_flags_t> bit_flags;
bool flag_found_something;
- void flag_wire(RTLIL::Wire *wire, bool create, bool set_int_driven, bool set_int_used, bool set_ext_driven, bool set_ext_used)
+ void flag_bit(RTLIL::SigBit bit, bool create, bool set_int_driven, bool set_int_used, bool set_ext_driven, bool set_ext_used)
{
- if (wire_flags.count(wire) == 0) {
+ if (bit_flags.count(bit) == 0) {
if (!create)
return;
- wire_flags[wire] = wire_flags_t();
+ bit_flags[bit] = bit_flags_t();
}
if (set_int_driven)
- wire_flags[wire].is_int_driven = true;
+ bit_flags[bit].is_int_driven = true;
if (set_int_used)
- wire_flags[wire].is_int_used = true;
+ bit_flags[bit].is_int_used = true;
if (set_ext_driven)
- wire_flags[wire].is_ext_driven = true;
+ bit_flags[bit].is_ext_driven = true;
if (set_ext_used)
- wire_flags[wire].is_ext_used = true;
+ bit_flags[bit].is_ext_used = true;
flag_found_something = true;
}
void flag_signal(const RTLIL::SigSpec &sig, bool create, bool set_int_driven, bool set_int_used, bool set_ext_driven, bool set_ext_used)
{
- for (auto &c : sig.chunks())
- if (c.wire != NULL)
- flag_wire(c.wire, create, set_int_driven, set_int_used, set_ext_driven, set_ext_used);
+ for (auto &b : sig)
+ if (b.wire != NULL)
+ flag_bit(b, create, set_int_driven, set_int_used, set_ext_driven, set_ext_used);
}
void handle_submodule(SubModule &submod)
{
log("Creating submodule %s (%s) of module %s.\n", submod.name.c_str(), submod.full_name.c_str(), module->name.c_str());
- wire_flags.clear();
+ bit_flags.clear();
for (RTLIL::Cell *cell : submod.cells) {
if (ct.cell_known(cell->type)) {
for (auto &conn : cell->connections())
@@ -114,18 +117,19 @@ struct SubmodWorker
int auto_name_counter = 1;
std::set<RTLIL::IdString> all_wire_names;
- for (auto &it : wire_flags) {
- all_wire_names.insert(it.first->name);
+ for (auto &it : bit_flags) {
+ all_wire_names.insert(it.first.wire->name);
}
- for (auto &it : wire_flags)
+ for (auto &it : bit_flags)
{
- RTLIL::Wire *wire = it.first;
- wire_flags_t &flags = it.second;
+ const RTLIL::SigBit &bit = it.first;
+ RTLIL::Wire *wire = bit.wire;
+ bit_flags_t &flags = it.second;
if (wire->port_input)
flags.is_ext_driven = true;
- if (wire->port_output)
+ if (outputs.count(bit))
flags.is_ext_used = true;
bool new_wire_port_input = false;
@@ -139,7 +143,11 @@ struct SubmodWorker
if (flags.is_int_driven && flags.is_ext_driven)
new_wire_port_input = true, new_wire_port_output = true;
- std::string new_wire_name = wire->name.str();
+ RTLIL::IdString new_wire_name;
+ if (GetSize(wire) == 1)
+ new_wire_name = wire->name;
+ else
+ new_wire_name = stringf("%s[%d]", wire->name.c_str(), bit.offset);
if (new_wire_port_input || new_wire_port_output) {
while (new_wire_name[0] == '$') {
std::string next_wire_name = stringf("\\n%d", auto_name_counter++);
@@ -150,10 +158,9 @@ struct SubmodWorker
}
}
- RTLIL::Wire *new_wire = new_mod->addWire(new_wire_name, wire->width);
+ RTLIL::Wire *new_wire = new_mod->addWire(new_wire_name);
new_wire->port_input = new_wire_port_input;
new_wire->port_output = new_wire_port_output;
- new_wire->start_offset = wire->start_offset;
new_wire->attributes = wire->attributes;
if (new_wire->port_input && new_wire->port_output)
@@ -176,8 +183,8 @@ struct SubmodWorker
for (auto &conn : new_cell->connections_)
for (auto &bit : conn.second)
if (bit.wire != NULL) {
- log_assert(wire_flags.count(bit.wire) > 0);
- bit.wire = wire_flags[bit.wire].new_wire;
+ log_assert(bit_flags.count(bit) > 0);
+ bit = bit_flags[bit].new_wire;
}
log(" cell %s (%s)\n", new_cell->name.c_str(), new_cell->type.c_str());
if (!copy_mode)
@@ -187,18 +194,18 @@ struct SubmodWorker
if (!copy_mode) {
RTLIL::Cell *new_cell = module->addCell(submod.full_name, submod.full_name);
- for (auto &it : wire_flags)
+ for (auto &it : bit_flags)
{
- RTLIL::Wire *old_wire = it.first;
+ RTLIL::SigBit old_bit = it.first;
RTLIL::Wire *new_wire = it.second.new_wire;
if (new_wire->port_id > 0)
- new_cell->setPort(new_wire->name, RTLIL::SigSpec(old_wire));
+ new_cell->setPort(new_wire->name, old_bit);
}
}
}
SubmodWorker(RTLIL::Design *design, RTLIL::Module *module, bool copy_mode = false, std::string opt_name = std::string()) :
- design(design), module(module), copy_mode(copy_mode), opt_name(opt_name)
+ design(design), module(module), sigmap(module), copy_mode(copy_mode), opt_name(opt_name)
{
if (!design->selected_whole_module(module->name) && opt_name.empty())
return;
@@ -219,6 +226,15 @@ struct SubmodWorker
ct.setup_stdcells_mem();
ct.setup_design(design);
+ for (auto port : module->ports) {
+ auto wire = module->wire(port);
+ if (!wire->port_output)
+ continue;
+ for (auto b : sigmap(wire))
+ if (b.wire)
+ outputs.insert(b);
+ }
+
if (opt_name.empty())
{
for (auto &it : module->wires_)
diff --git a/passes/memory/memory.cc b/passes/memory/memory.cc
index 712bc2537..cee63bdd8 100644
--- a/passes/memory/memory.cc
+++ b/passes/memory/memory.cc
@@ -35,6 +35,7 @@ struct MemoryPass : public Pass {
log("\n");
log("This pass calls all the other memory_* passes in a useful order:\n");
log("\n");
+ log(" opt_mem\n");
log(" memory_dff [-nordff] (-memx implies -nordff)\n");
log(" opt_clean\n");
log(" memory_share\n");
@@ -81,6 +82,7 @@ struct MemoryPass : public Pass {
}
extra_args(args, argidx, design);
+ Pass::call(design, "opt_mem");
Pass::call(design, flag_nordff ? "memory_dff -nordff" : "memory_dff");
Pass::call(design, "opt_clean");
Pass::call(design, "memory_share");
diff --git a/passes/opt/Makefile.inc b/passes/opt/Makefile.inc
index eb07e9452..002c1a6a1 100644
--- a/passes/opt/Makefile.inc
+++ b/passes/opt/Makefile.inc
@@ -1,6 +1,7 @@
OBJS += passes/opt/opt.o
OBJS += passes/opt/opt_merge.o
+OBJS += passes/opt/opt_mem.o
OBJS += passes/opt/opt_muxtree.o
OBJS += passes/opt/opt_reduce.o
OBJS += passes/opt/opt_rmdff.o
diff --git a/passes/opt/opt_mem.cc b/passes/opt/opt_mem.cc
new file mode 100644
index 000000000..98d3551eb
--- /dev/null
+++ b/passes/opt/opt_mem.cc
@@ -0,0 +1,143 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ *
+ * 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/yosys.h"
+#include "kernel/sigtools.h"
+
+USING_YOSYS_NAMESPACE
+PRIVATE_NAMESPACE_BEGIN
+
+struct OptMemWorker
+{
+ RTLIL::Design *design;
+ RTLIL::Module *module;
+ SigMap sigmap;
+ bool restart;
+
+ dict<IdString, vector<IdString>> memrd, memwr, meminit;
+ pool<IdString> remove_mem, remove_cells;
+
+ OptMemWorker(RTLIL::Module *module) : design(module->design), module(module), sigmap(module), restart(false)
+ {
+ for (auto &it : module->memories)
+ {
+ memrd[it.first];
+ memwr[it.first];
+ meminit[it.first];
+ }
+
+ for (auto cell : module->cells())
+ {
+ if (cell->type == ID($memrd)) {
+ IdString id = cell->getParam(ID(MEMID)).decode_string();
+ memrd.at(id).push_back(cell->name);
+ }
+
+ if (cell->type == ID($memwr)) {
+ IdString id = cell->getParam(ID(MEMID)).decode_string();
+ memwr.at(id).push_back(cell->name);
+ }
+
+ if (cell->type == ID($meminit)) {
+ IdString id = cell->getParam(ID(MEMID)).decode_string();
+ meminit.at(id).push_back(cell->name);
+ }
+ }
+ }
+
+ ~OptMemWorker()
+ {
+ for (auto it : remove_mem)
+ {
+ for (auto cell_name : memrd[it])
+ module->remove(module->cell(cell_name));
+ for (auto cell_name : memwr[it])
+ module->remove(module->cell(cell_name));
+ for (auto cell_name : meminit[it])
+ module->remove(module->cell(cell_name));
+
+ delete module->memories.at(it);
+ module->memories.erase(it);
+ }
+
+ for (auto cell_name : remove_cells)
+ module->remove(module->cell(cell_name));
+ }
+
+ int run(RTLIL::Memory *mem)
+ {
+ if (restart || remove_mem.count(mem->name))
+ return 0;
+
+ if (memwr.at(mem->name).empty() && meminit.at(mem->name).empty()) {
+ log("Removing memory %s.%s with no write ports or init data.\n", log_id(module), log_id(mem));
+ remove_mem.insert(mem->name);
+ return 1;
+ }
+
+ return 0;
+ }
+};
+
+struct OptMemPass : public Pass {
+ OptMemPass() : Pass("opt_mem", "optimize memories") { }
+ void help() YS_OVERRIDE
+ {
+ // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+ log("\n");
+ log(" opt_mem [options] [selection]\n");
+ log("\n");
+ log("This pass performs various optimizations on memories in the design.\n");
+ log("\n");
+ }
+ void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
+ {
+ log_header(design, "Executing OPT_MEM pass (optimize memories).\n");
+
+ size_t argidx;
+ for (argidx = 1; argidx < args.size(); argidx++) {
+ // if (args[argidx] == "-nomux") {
+ // mode_nomux = true;
+ // continue;
+ // }
+ break;
+ }
+ extra_args(args, argidx, design);
+
+ int total_count = 0;
+ for (auto module : design->selected_modules()) {
+ while (1) {
+ int cnt = 0;
+ OptMemWorker worker(module);
+ for (auto &it : module->memories)
+ if (module->selected(it.second))
+ cnt += worker.run(it.second);
+ if (!cnt && !worker.restart)
+ break;
+ total_count += cnt;
+ }
+ }
+
+ if (total_count)
+ design->scratchpad_set_bool("opt.did_something", true);
+ log("Performed a total of %d transformations.\n", total_count);
+ }
+} OptMemPass;
+
+PRIVATE_NAMESPACE_END
diff --git a/passes/proc/proc_dlatch.cc b/passes/proc/proc_dlatch.cc
index d9d5dfbed..a0c8351b6 100644
--- a/passes/proc/proc_dlatch.cc
+++ b/passes/proc/proc_dlatch.cc
@@ -349,6 +349,10 @@ void proc_dlatch(proc_dlatch_db_t &db, RTLIL::Process *proc)
continue;
}
+ if (proc->get_bool_attribute(ID(always_ff)))
+ log_error("Found non edge/level sensitive event in always_ff process `%s.%s'.\n",
+ db.module->name.c_str(), proc->name.c_str());
+
for (auto ss : sr->actions)
{
db.sigmap.apply(ss.first);
@@ -383,8 +387,12 @@ void proc_dlatch(proc_dlatch_db_t &db, RTLIL::Process *proc)
int offset = 0;
for (auto chunk : nolatches_bits.first.chunks()) {
SigSpec lhs = chunk, rhs = nolatches_bits.second.extract(offset, chunk.width);
- log("No latch inferred for signal `%s.%s' from process `%s.%s'.\n",
- db.module->name.c_str(), log_signal(lhs), db.module->name.c_str(), proc->name.c_str());
+ if (proc->get_bool_attribute(ID(always_latch)))
+ log_error("No latch inferred for signal `%s.%s' from always_latch process `%s.%s'.\n",
+ db.module->name.c_str(), log_signal(lhs), db.module->name.c_str(), proc->name.c_str());
+ else
+ log("No latch inferred for signal `%s.%s' from process `%s.%s'.\n",
+ db.module->name.c_str(), log_signal(lhs), db.module->name.c_str(), proc->name.c_str());
db.module->connect(lhs, rhs);
offset += chunk.width;
}
@@ -410,8 +418,12 @@ void proc_dlatch(proc_dlatch_db_t &db, RTLIL::Process *proc)
cell->set_src_attribute(src);
db.generated_dlatches.insert(cell);
- log("Latch inferred for signal `%s.%s' from process `%s.%s': %s\n",
- db.module->name.c_str(), log_signal(lhs), db.module->name.c_str(), proc->name.c_str(), log_id(cell));
+ if (proc->get_bool_attribute(ID(always_comb)))
+ log_error("Latch inferred for signal `%s.%s' from always_comb process `%s.%s'.\n",
+ db.module->name.c_str(), log_signal(lhs), db.module->name.c_str(), proc->name.c_str());
+ else
+ log("Latch inferred for signal `%s.%s' from process `%s.%s': %s\n",
+ db.module->name.c_str(), log_signal(lhs), db.module->name.c_str(), proc->name.c_str(), log_id(cell));
}
offset += width;
diff --git a/passes/techmap/abc9.cc b/passes/techmap/abc9.cc
index 8d4ff4025..4b6ec6e11 100644
--- a/passes/techmap/abc9.cc
+++ b/passes/techmap/abc9.cc
@@ -1165,7 +1165,7 @@ struct Abc9Pass : public Pass {
for (auto bit : cell_to_bit_up[cell])
for (auto c : bit_to_cell_up[bit])
- if (unassigned_cells.count(c)) {
+ if (unassigned_cells.count(c) && !c->type.in("$__ABC9_FF_", "$__ABC9_ASYNC_")) {
unassigned_cells.erase(c);
next_expand_queue_up.insert(c);
assigned_cells[key].insert(c->name);
diff --git a/techlibs/gowin/.gitignore b/techlibs/gowin/.gitignore
new file mode 100644
index 000000000..d6c48e90d
--- /dev/null
+++ b/techlibs/gowin/.gitignore
@@ -0,0 +1,2 @@
+brams_init.mk
+bram_init_*.vh
diff --git a/tests/arch/gowin/mux.ys b/tests/arch/gowin/mux.ys
index 4990be421..afad29a89 100644
--- a/tests/arch/gowin/mux.ys
+++ b/tests/arch/gowin/mux.ys
@@ -45,6 +45,5 @@ design -load postopt # load the post-opt design (otherwise equiv_opt loads the p
cd mux16 # Constrain all select calls below inside the top module
select -assert-count 20 t:IBUF
select -assert-count 1 t:OBUF
-show
select -assert-none t:LUT4 t:MUX2_LUT6 t:MUX2_LUT5 t:MUX2_LUT6 t:MUX2_LUT7 t:MUX2_LUT8 t:IBUF t:OBUF %% t:* %D
diff --git a/tests/simple_abc9/abc9.v b/tests/simple_abc9/abc9.v
index 65eb01338..99075d319 100644
--- a/tests/simple_abc9/abc9.v
+++ b/tests/simple_abc9/abc9.v
@@ -268,7 +268,7 @@ assign o = { 1'b1, 1'bx };
assign p = { 1'b1, 1'bx, 1'b0 };
endmodule
-module abc9_test029(input clk1, clk2, input d, output reg q1, q2);
+module abc9_test029(input clk1, clk2, d, output reg q1, q2);
always @(posedge clk1) q1 <= d;
always @(negedge clk2) q2 <= q1;
endmodule
@@ -284,3 +284,13 @@ always @(negedge clk or posedge r)
if (r) q <= 1'b1;
else q <= d;
endmodule
+
+module abc9_test033(input clk, d, output reg q1, q2);
+always @(posedge clk) q1 <= d;
+always @(posedge clk) q2 <= q1;
+endmodule
+
+module abc9_test034(input clk, d, output reg [1:0] q);
+always @(posedge clk) q[0] <= d;
+always @(negedge clk) q[1] <= q[0];
+endmodule
diff --git a/tests/various/submod.ys b/tests/various/submod.ys
new file mode 100644
index 000000000..7c6f555ac
--- /dev/null
+++ b/tests/various/submod.ys
@@ -0,0 +1,50 @@
+read_verilog <<EOT
+module top(input a, output [1:0] b);
+wire c;
+(* submod="bar" *) sub s1(a, c);
+assign b[0] = c;
+endmodule
+
+module sub(input a, output c);
+assign c = a;
+endmodule
+EOT
+
+hierarchy -top top
+proc
+design -save gold
+
+submod
+design -stash gate
+
+design -import gold -as gold
+design -import gate -as gate
+
+miter -equiv -flatten -make_assert -make_outputs gold gate miter
+sat -verify -prove-asserts -show-ports miter
+
+
+design -reset
+read_verilog <<EOT
+module top(input a, output [1:0] b);
+(* submod="bar" *) sub s1(a, b[1]);
+assign b[0] = 1'b0;
+endmodule
+
+module sub(input a, output c);
+assign c = a;
+endmodule
+EOT
+
+hierarchy -top top
+proc
+design -save gold
+
+submod
+design -stash gate
+
+design -import gold -as gold
+design -import gate -as gate
+
+miter -equiv -flatten -make_assert -make_outputs gold gate miter
+sat -verify -prove-asserts -show-ports miter
diff --git a/tests/various/svalways.sh b/tests/various/svalways.sh
new file mode 100755
index 000000000..2cc09f801
--- /dev/null
+++ b/tests/various/svalways.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+
+trap 'echo "ERROR in svalways.sh" >&2; exit 1' ERR
+
+# Good case
+../../yosys -f "verilog -sv" -qp proc - <<EOT
+module top(input clk, en, d, output reg p, q, r);
+
+always_ff @(posedge clk)
+ p <= d;
+
+always_comb
+ q = ~d;
+
+always_latch
+ if (en) r = d;
+
+endmodule
+EOT
+
+# Incorrect always_comb syntax
+((../../yosys -f "verilog -sv" -qp proc -|| true) <<EOT
+module top(input d, output reg q);
+
+always_comb @(d)
+ q = ~d;
+
+endmodule
+EOT
+) 2>&1 | grep -F "<stdin>:3: ERROR: syntax error, unexpected '@'" > /dev/null
+
+# Incorrect use of always_comb
+((../../yosys -f "verilog -sv" -qp proc -|| true) <<EOT
+module top(input en, d, output reg q);
+
+always_comb
+ if (en) q = d;
+
+endmodule
+EOT
+) 2>&1 | grep -F "ERROR: Latch inferred for signal \`\\top.\\q' from always_comb process" > /dev/null
+
+# Incorrect use of always_latch
+((../../yosys -f "verilog -sv" -qp proc -|| true) <<EOT
+module top(input en, d, output reg q);
+
+always_latch
+ q = !d;
+
+endmodule
+EOT
+) 2>&1 | grep -F "ERROR: No latch inferred for signal \`\\top.\\q' from always_latch process" > /dev/null
+
+# Incorrect use of always_ff
+((../../yosys -f "verilog -sv" -qp proc -|| true) <<EOT
+module top(input en, d, output reg q);
+
+always_ff @(*)
+ q = !d;
+
+endmodule
+EOT
+) 2>&1 | grep -F "ERROR: Found non edge/level sensitive event in always_ff process" > /dev/null