summaryrefslogtreecommitdiff
path: root/frontends
diff options
context:
space:
mode:
authorClifford Wolf <clifford@clifford.at>2013-11-21 13:49:00 +0100
committerClifford Wolf <clifford@clifford.at>2013-11-21 13:49:00 +0100
commit09471846c553855c43224ce32d855c46f4df5140 (patch)
tree659fff2b882e2d7c54a5eaf72adeed2328d4ba64 /frontends
parent84ced2bb8ee2e6498b53ae6cdb77930aa98affbb (diff)
Major improvements in mem2reg and added "init" sync rules
Diffstat (limited to 'frontends')
-rw-r--r--frontends/ast/ast.h21
-rw-r--r--frontends/ast/genrtlil.cc33
-rw-r--r--frontends/ast/simplify.cc280
-rw-r--r--frontends/ilang/lexer.l1
-rw-r--r--frontends/ilang/parser.y8
5 files changed, 255 insertions, 88 deletions
diff --git a/frontends/ast/ast.h b/frontends/ast/ast.h
index 37f75454..de32a2bb 100644
--- a/frontends/ast/ast.h
+++ b/frontends/ast/ast.h
@@ -162,12 +162,31 @@ namespace AST
void delete_children();
~AstNode();
+ enum mem2reg_flags
+ {
+ /* status flags */
+ MEM2REG_FL_ALL = 0x00000001,
+ MEM2REG_FL_ASYNC = 0x00000002,
+ MEM2REG_FL_INIT = 0x00000004,
+
+ /* candidate flags */
+ MEM2REG_FL_FORCED = 0x00000100,
+ MEM2REG_FL_SET_INIT = 0x00000200,
+ MEM2REG_FL_SET_ELSE = 0x00000400,
+ MEM2REG_FL_SET_ASYNC = 0x00000800,
+ MEM2REG_FL_EQ2 = 0x00001000,
+
+ /* proc flags */
+ MEM2REG_FL_EQ1 = 0x01000000,
+ };
+
// simplify() creates a simpler AST by unrolling for-loops, expanding generate blocks, etc.
// it also sets the id2ast pointers so that identifier lookups are fast in genRTLIL()
bool simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage, int width_hint, bool sign_hint);
void expand_genblock(std::string index_var, std::string prefix, std::map<std::string, std::string> &name_map);
void replace_ids(std::map<std::string, std::string> &rules);
- void mem2reg_as_needed_pass1(std::set<AstNode*> &mem2reg_set, std::set<AstNode*> &mem2reg_candidates, bool sync_proc, bool async_proc, bool force_mem2reg);
+ void mem2reg_as_needed_pass1(std::map<AstNode*, std::set<std::string>> &mem2reg_places,
+ std::map<AstNode*, uint32_t> &mem2reg_flags, std::map<AstNode*, uint32_t> &proc_flags, uint32_t &status_flags);
void mem2reg_as_needed_pass2(std::set<AstNode*> &mem2reg_set, AstNode *mod, AstNode *block);
void meminfo(int &mem_width, int &mem_size, int &addr_bits);
diff --git a/frontends/ast/genrtlil.cc b/frontends/ast/genrtlil.cc
index 550f7245..e634a27a 100644
--- a/frontends/ast/genrtlil.cc
+++ b/frontends/ast/genrtlil.cc
@@ -234,7 +234,7 @@ struct AST_INTERNAL::ProcessGenerator
{
// input and output structures
AstNode *always;
- RTLIL::SigSpec skipSyncSignals;
+ RTLIL::SigSpec initSyncSignals;
RTLIL::Process *proc;
const RTLIL::SigSpec &outputSignals;
@@ -258,7 +258,10 @@ struct AST_INTERNAL::ProcessGenerator
// map helps generating nice numbered names for all this temporary signals.
std::map<RTLIL::Wire*, int> new_temp_count;
- ProcessGenerator(AstNode *always, RTLIL::SigSpec skipSyncSignalsArg = RTLIL::SigSpec()) : always(always), skipSyncSignals(skipSyncSignalsArg), outputSignals(subst_lvalue_from)
+ // Buffer for generating the init action
+ RTLIL::SigSpec init_lvalue, init_rvalue;
+
+ ProcessGenerator(AstNode *always, RTLIL::SigSpec initSyncSignalsArg = RTLIL::SigSpec()) : always(always), initSyncSignals(initSyncSignalsArg), outputSignals(subst_lvalue_from)
{
// generate process and simple root case
proc = new RTLIL::Process;
@@ -321,6 +324,25 @@ struct AST_INTERNAL::ProcessGenerator
for (auto child : always->children)
if (child->type == AST_BLOCK)
processAst(child);
+
+ if (initSyncSignals.width > 0)
+ {
+ RTLIL::SyncRule *sync = new RTLIL::SyncRule;
+ sync->type = RTLIL::SyncType::STi;
+ proc->syncs.push_back(sync);
+
+ assert(init_lvalue.width == init_rvalue.width);
+ init_lvalue.optimize();
+ init_rvalue.optimize();
+
+ int offset = 0;
+ for (size_t i = 0; i < init_lvalue.chunks.size(); i++) {
+ RTLIL::SigSpec lhs = init_lvalue.chunks[i];
+ RTLIL::SigSpec rhs = init_rvalue.extract(offset, init_lvalue.chunks[i].width);
+ sync->actions.push_back(RTLIL::SigSig(lhs, rhs));
+ offset += lhs.width;
+ }
+ }
}
// create new temporary signals
@@ -406,8 +428,11 @@ struct AST_INTERNAL::ProcessGenerator
// are avoided and the generated $mux cells have a more "natural" size.
void addChunkActions(std::vector<RTLIL::SigSig> &actions, RTLIL::SigSpec lvalue, RTLIL::SigSpec rvalue, bool inSyncRule = false)
{
- if (inSyncRule)
- lvalue.remove2(skipSyncSignals, &rvalue);
+ if (inSyncRule && initSyncSignals.width > 0) {
+ init_lvalue.append(lvalue.extract(initSyncSignals));
+ init_rvalue.append(lvalue.extract(initSyncSignals, &rvalue));
+ lvalue.remove2(initSyncSignals, &rvalue);
+ }
assert(lvalue.width == rvalue.width);
lvalue.optimize();
rvalue.optimize();
diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc
index 636dde48..f1cce397 100644
--- a/frontends/ast/simplify.cc
+++ b/frontends/ast/simplify.cc
@@ -56,8 +56,46 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
if (!flag_nomem2reg && !get_bool_attribute("\\nomem2reg"))
{
- std::set<AstNode*> mem2reg_set, mem2reg_candidates;
- mem2reg_as_needed_pass1(mem2reg_set, mem2reg_candidates, false, false, flag_mem2reg);
+ std::map<AstNode*, std::set<std::string>> mem2reg_places;
+ std::map<AstNode*, uint32_t> mem2reg_candidates, dummy_proc_flags;
+ uint32_t flags = flag_mem2reg ? AstNode::MEM2REG_FL_ALL : 0;
+ mem2reg_as_needed_pass1(mem2reg_places, mem2reg_candidates, dummy_proc_flags, flags);
+
+ std::set<AstNode*> mem2reg_set;
+ for (auto &it : mem2reg_candidates)
+ {
+ AstNode *mem = it.first;
+ uint32_t memflags = it.second;
+ assert((memflags & ~0x00ffff00) == 0);
+
+ if (mem->get_bool_attribute("\\nomem2reg"))
+ continue;
+
+ if (memflags & AstNode::MEM2REG_FL_FORCED)
+ goto silent_activate;
+
+ if (memflags & AstNode::MEM2REG_FL_EQ2)
+ goto verbose_activate;
+
+ if ((memflags & AstNode::MEM2REG_FL_SET_INIT) && (memflags & AstNode::MEM2REG_FL_SET_ELSE))
+ goto verbose_activate;
+
+ continue;
+
+ verbose_activate:
+ if (mem2reg_set.count(mem) == 0) {
+ log("Warning: Replacing memory %s with list of registers.", mem->str.c_str());
+ bool first_element = true;
+ for (auto &place : mem2reg_places[it.first]) {
+ log("%s%s", first_element ? " See " : ", ", place.c_str());
+ first_element = false;
+ }
+ log("\n");
+ }
+
+ silent_activate:
+ mem2reg_set.insert(mem);
+ }
for (auto node : mem2reg_set)
{
@@ -1249,36 +1287,102 @@ void AstNode::replace_ids(std::map<std::string, std::string> &rules)
}
// find memories that should be replaced by registers
-void AstNode::mem2reg_as_needed_pass1(std::set<AstNode*> &mem2reg_set, std::set<AstNode*> &mem2reg_candidates, bool sync_proc, bool async_proc, bool force_mem2reg)
+void AstNode::mem2reg_as_needed_pass1(std::map<AstNode*, std::set<std::string>> &mem2reg_places,
+ std::map<AstNode*, uint32_t> &mem2reg_candidates, std::map<AstNode*, uint32_t> &proc_flags, uint32_t &flags)
{
- if ((type == AST_ASSIGN_LE && async_proc) || (type == AST_ASSIGN_EQ && (sync_proc || async_proc)))
- if (children[0]->type == AST_IDENTIFIER && children[0]->id2ast && children[0]->id2ast->type == AST_MEMORY &&
- !children[0]->id2ast->get_bool_attribute("\\nomem2reg")) {
- if (async_proc || mem2reg_candidates.count(children[0]->id2ast) > 0) {
- if (mem2reg_set.count(children[0]->id2ast) == 0)
- log("Warning: Replacing memory %s with list of registers because of assignment in line %s:%d.\n",
- children[0]->str.c_str(), filename.c_str(), linenum);
- mem2reg_set.insert(children[0]->id2ast);
+ uint32_t children_flags = 0;
+ int ignore_children_counter = 0;
+
+ if (type == AST_ASSIGN || type == AST_ASSIGN_LE || type == AST_ASSIGN_EQ)
+ {
+ if (children[0]->type == AST_IDENTIFIER && children[0]->id2ast && children[0]->id2ast->type == AST_MEMORY)
+ {
+ AstNode *mem = children[0]->id2ast;
+
+ // activate mem2reg if this is assigned in an async proc
+ if (flags & AstNode::MEM2REG_FL_ASYNC) {
+ if (!(mem2reg_candidates[mem] & AstNode::MEM2REG_FL_SET_ASYNC))
+ mem2reg_places[mem].insert(stringf("%s:%d", filename.c_str(), linenum));
+ mem2reg_candidates[mem] |= AstNode::MEM2REG_FL_SET_ASYNC;
+ }
+
+ // remember if this is assigned blocking (=)
+ if (type == AST_ASSIGN_EQ) {
+ if (!(proc_flags[mem] & AstNode::MEM2REG_FL_EQ1))
+ mem2reg_places[mem].insert(stringf("%s:%d", filename.c_str(), linenum));
+ proc_flags[mem] |= AstNode::MEM2REG_FL_EQ1;
+ }
+
+ // remember where this is
+ if (flags & MEM2REG_FL_INIT) {
+ if (!(mem2reg_candidates[mem] & AstNode::MEM2REG_FL_SET_INIT))
+ mem2reg_places[mem].insert(stringf("%s:%d", filename.c_str(), linenum));
+ mem2reg_candidates[mem] |= AstNode::MEM2REG_FL_SET_INIT;
+ } else {
+ if (!(mem2reg_candidates[mem] & AstNode::MEM2REG_FL_SET_ELSE))
+ mem2reg_places[mem].insert(stringf("%s:%d", filename.c_str(), linenum));
+ mem2reg_candidates[mem] |= AstNode::MEM2REG_FL_SET_ELSE;
}
- mem2reg_candidates.insert(children[0]->id2ast);
}
-
- if (type == AST_MEMORY && (get_bool_attribute("\\mem2reg") || force_mem2reg))
- mem2reg_set.insert(this);
+
+ ignore_children_counter = 1;
+ }
+
+ if (type == AST_IDENTIFIER && id2ast && id2ast->type == AST_MEMORY)
+ {
+ AstNode *mem = id2ast;
+
+ // flag if used after blocking assignment (in same proc)
+ if ((proc_flags[mem] & AstNode::MEM2REG_FL_EQ1) && !(mem2reg_candidates[mem] & AstNode::MEM2REG_FL_EQ2)) {
+ mem2reg_places[mem].insert(stringf("%s:%d", filename.c_str(), linenum));
+ mem2reg_candidates[mem] |= AstNode::MEM2REG_FL_EQ2;
+ }
+ }
+
+ // also activate if requested, either by using mem2reg attribute or by declaring array as 'wire' instead of 'reg'
+ if (type == AST_MEMORY && (get_bool_attribute("\\mem2reg") || (flags & AstNode::MEM2REG_FL_ALL) || !is_reg))
+ mem2reg_candidates[this] |= AstNode::MEM2REG_FL_FORCED;
if (type == AST_MODULE && get_bool_attribute("\\mem2reg"))
- force_mem2reg = true;
+ children_flags |= AstNode::MEM2REG_FL_ALL;
+
+ std::map<AstNode*, uint32_t> *proc_flags_p = NULL;
if (type == AST_ALWAYS) {
+ bool sync_proc = false;
for (auto child : children) {
if (child->type == AST_POSEDGE || child->type == AST_NEGEDGE)
sync_proc = true;
}
- async_proc = !sync_proc;
+ if (!sync_proc)
+ children_flags |= AstNode::MEM2REG_FL_ASYNC;
+ proc_flags_p = new std::map<AstNode*, uint32_t>;
+ }
+
+ if (type == AST_INITIAL) {
+ children_flags |= AstNode::MEM2REG_FL_INIT;
+ proc_flags_p = new std::map<AstNode*, uint32_t>;
}
+ uint32_t backup_flags = flags;
+ flags |= children_flags;
+ assert((flags & ~0x000000ff) == 0);
+
for (auto child : children)
- child->mem2reg_as_needed_pass1(mem2reg_set, mem2reg_candidates, sync_proc, async_proc, force_mem2reg);
+ if (ignore_children_counter > 0)
+ ignore_children_counter--;
+ else if (proc_flags_p)
+ child->mem2reg_as_needed_pass1(mem2reg_places, mem2reg_candidates, *proc_flags_p, flags);
+ else
+ child->mem2reg_as_needed_pass1(mem2reg_places, mem2reg_candidates, proc_flags, flags);
+
+ flags &= ~children_flags | backup_flags;
+
+ if (proc_flags_p) {
+ for (auto it : *proc_flags_p)
+ assert((it.second & ~0xff000000) == 0);
+ delete proc_flags_p;
+ }
}
// actually replace memories with registers
@@ -1287,8 +1391,8 @@ void AstNode::mem2reg_as_needed_pass2(std::set<AstNode*> &mem2reg_set, AstNode *
if (type == AST_BLOCK)
block = this;
- if ((type == AST_ASSIGN_LE || type == AST_ASSIGN_EQ) && block != NULL &&
- children[0]->id2ast && mem2reg_set.count(children[0]->id2ast) > 0)
+ if ((type == AST_ASSIGN_LE || type == AST_ASSIGN_EQ) && block != NULL && children[0]->id2ast &&
+ mem2reg_set.count(children[0]->id2ast) > 0 && children[0]->children[0]->children[0]->type != AST_CONSTANT)
{
std::stringstream sstr;
sstr << "$mem2reg_wr$" << children[0]->str << "$" << filename << ":" << linenum << "$" << (RTLIL::autoidx++);
@@ -1344,77 +1448,89 @@ void AstNode::mem2reg_as_needed_pass2(std::set<AstNode*> &mem2reg_set, AstNode *
if (type == AST_IDENTIFIER && id2ast && mem2reg_set.count(id2ast) > 0)
{
- std::stringstream sstr;
- sstr << "$mem2reg_rd$" << children[0]->str << "$" << filename << ":" << linenum << "$" << (RTLIL::autoidx++);
- std::string id_addr = sstr.str() + "_ADDR", id_data = sstr.str() + "_DATA";
-
- int mem_width, mem_size, addr_bits;
- id2ast->meminfo(mem_width, mem_size, addr_bits);
-
- AstNode *wire_addr = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(addr_bits-1, true), mkconst_int(0, true)));
- wire_addr->str = id_addr;
- wire_addr->is_reg = true;
- if (block)
- wire_addr->attributes["\\nosync"] = AstNode::mkconst_int(1, false);
- mod->children.push_back(wire_addr);
- while (wire_addr->simplify(true, false, false, 1, -1, false)) { }
-
- AstNode *wire_data = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(mem_width-1, true), mkconst_int(0, true)));
- wire_data->str = id_data;
- wire_data->is_reg = true;
- if (block)
- wire_data->attributes["\\nosync"] = AstNode::mkconst_int(1, false);
- mod->children.push_back(wire_data);
- while (wire_data->simplify(true, false, false, 1, -1, false)) { }
+ if (children[0]->children[0]->type == AST_CONSTANT)
+ {
+ int id = children[0]->children[0]->integer;
+ str = stringf("%s[%d]", str.c_str(), id);
- AstNode *assign_addr = new AstNode(block ? AST_ASSIGN_EQ : AST_ASSIGN, new AstNode(AST_IDENTIFIER), children[0]->children[0]->clone());
- assign_addr->children[0]->str = id_addr;
+ delete_children();
+ range_valid = false;
+ id2ast = NULL;
+ }
+ else
+ {
+ std::stringstream sstr;
+ sstr << "$mem2reg_rd$" << children[0]->str << "$" << filename << ":" << linenum << "$" << (RTLIL::autoidx++);
+ std::string id_addr = sstr.str() + "_ADDR", id_data = sstr.str() + "_DATA";
+
+ int mem_width, mem_size, addr_bits;
+ id2ast->meminfo(mem_width, mem_size, addr_bits);
+
+ AstNode *wire_addr = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(addr_bits-1, true), mkconst_int(0, true)));
+ wire_addr->str = id_addr;
+ wire_addr->is_reg = true;
+ if (block)
+ wire_addr->attributes["\\nosync"] = AstNode::mkconst_int(1, false);
+ mod->children.push_back(wire_addr);
+ while (wire_addr->simplify(true, false, false, 1, -1, false)) { }
+
+ AstNode *wire_data = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(mem_width-1, true), mkconst_int(0, true)));
+ wire_data->str = id_data;
+ wire_data->is_reg = true;
+ if (block)
+ wire_data->attributes["\\nosync"] = AstNode::mkconst_int(1, false);
+ mod->children.push_back(wire_data);
+ while (wire_data->simplify(true, false, false, 1, -1, false)) { }
+
+ AstNode *assign_addr = new AstNode(block ? AST_ASSIGN_EQ : AST_ASSIGN, new AstNode(AST_IDENTIFIER), children[0]->children[0]->clone());
+ assign_addr->children[0]->str = id_addr;
+
+ AstNode *case_node = new AstNode(AST_CASE, new AstNode(AST_IDENTIFIER));
+ case_node->children[0]->str = id_addr;
+
+ for (int i = 0; i < mem_size; i++) {
+ if (children[0]->children[0]->type == AST_CONSTANT && int(children[0]->children[0]->integer) != i)
+ continue;
+ AstNode *cond_node = new AstNode(AST_COND, AstNode::mkconst_int(i, false, addr_bits), new AstNode(AST_BLOCK));
+ AstNode *assign_reg = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), new AstNode(AST_IDENTIFIER));
+ assign_reg->children[0]->str = id_data;
+ assign_reg->children[1]->str = stringf("%s[%d]", str.c_str(), i);
+ cond_node->children[1]->children.push_back(assign_reg);
+ case_node->children.push_back(cond_node);
+ }
- AstNode *case_node = new AstNode(AST_CASE, new AstNode(AST_IDENTIFIER));
- case_node->children[0]->str = id_addr;
+ std::vector<RTLIL::State> x_bits;
+ for (int i = 0; i < mem_width; i++)
+ x_bits.push_back(RTLIL::State::Sx);
- for (int i = 0; i < mem_size; i++) {
- if (children[0]->children[0]->type == AST_CONSTANT && int(children[0]->children[0]->integer) != i)
- continue;
- AstNode *cond_node = new AstNode(AST_COND, AstNode::mkconst_int(i, false, addr_bits), new AstNode(AST_BLOCK));
- AstNode *assign_reg = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), new AstNode(AST_IDENTIFIER));
+ AstNode *cond_node = new AstNode(AST_COND, new AstNode(AST_DEFAULT), new AstNode(AST_BLOCK));
+ AstNode *assign_reg = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), AstNode::mkconst_bits(x_bits, false));
assign_reg->children[0]->str = id_data;
- assign_reg->children[1]->str = stringf("%s[%d]", str.c_str(), i);
cond_node->children[1]->children.push_back(assign_reg);
case_node->children.push_back(cond_node);
- }
-
- std::vector<RTLIL::State> x_bits;
- for (int i = 0; i < mem_width; i++)
- x_bits.push_back(RTLIL::State::Sx);
- AstNode *cond_node = new AstNode(AST_COND, new AstNode(AST_DEFAULT), new AstNode(AST_BLOCK));
- AstNode *assign_reg = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), AstNode::mkconst_bits(x_bits, false));
- assign_reg->children[0]->str = id_data;
- cond_node->children[1]->children.push_back(assign_reg);
- case_node->children.push_back(cond_node);
+ if (block)
+ {
+ size_t assign_idx = 0;
+ while (assign_idx < block->children.size() && !block->children[assign_idx]->contains(this))
+ assign_idx++;
+ assert(assign_idx < block->children.size());
+ block->children.insert(block->children.begin()+assign_idx, case_node);
+ block->children.insert(block->children.begin()+assign_idx, assign_addr);
+ }
+ else
+ {
+ AstNode *proc = new AstNode(AST_ALWAYS, new AstNode(AST_BLOCK));
+ proc->children[0]->children.push_back(case_node);
+ mod->children.push_back(proc);
+ mod->children.push_back(assign_addr);
+ }
- if (block)
- {
- size_t assign_idx = 0;
- while (assign_idx < block->children.size() && !block->children[assign_idx]->contains(this))
- assign_idx++;
- assert(assign_idx < block->children.size());
- block->children.insert(block->children.begin()+assign_idx, case_node);
- block->children.insert(block->children.begin()+assign_idx, assign_addr);
- }
- else
- {
- AstNode *proc = new AstNode(AST_ALWAYS, new AstNode(AST_BLOCK));
- proc->children[0]->children.push_back(case_node);
- mod->children.push_back(proc);
- mod->children.push_back(assign_addr);
+ delete_children();
+ range_valid = false;
+ id2ast = NULL;
+ str = id_data;
}
-
- delete_children();
- range_valid = false;
- id2ast = NULL;
- str = id_data;
}
assert(id2ast == NULL || mem2reg_set.count(id2ast) == 0);
diff --git a/frontends/ilang/lexer.l b/frontends/ilang/lexer.l
index 2a20f02a..73bc894b 100644
--- a/frontends/ilang/lexer.l
+++ b/frontends/ilang/lexer.l
@@ -60,6 +60,7 @@
"negedge" { return TOK_NEGEDGE; }
"edge" { return TOK_EDGE; }
"always" { return TOK_ALWAYS; }
+"init" { return TOK_INIT; }
"update" { return TOK_UPDATE; }
"process" { return TOK_PROCESS; }
"end" { return TOK_END; }
diff --git a/frontends/ilang/parser.y b/frontends/ilang/parser.y
index b889a998..dc39cf93 100644
--- a/frontends/ilang/parser.y
+++ b/frontends/ilang/parser.y
@@ -52,7 +52,7 @@ using namespace ILANG_FRONTEND;
%token <integer> TOK_INT
%token TOK_MODULE TOK_WIRE TOK_WIDTH TOK_INPUT TOK_OUTPUT TOK_INOUT
%token TOK_CELL TOK_CONNECT TOK_SWITCH TOK_CASE TOK_ASSIGN TOK_SYNC
-%token TOK_LOW TOK_HIGH TOK_POSEDGE TOK_NEGEDGE TOK_EDGE TOK_ALWAYS
+%token TOK_LOW TOK_HIGH TOK_POSEDGE TOK_NEGEDGE TOK_EDGE TOK_ALWAYS TOK_INIT
%token TOK_UPDATE TOK_PROCESS TOK_END TOK_INVALID TOK_EOL TOK_OFFSET
%token TOK_PARAMETER TOK_ATTRIBUTE TOK_AUTO TOK_MEMORY TOK_SIZE
@@ -279,6 +279,12 @@ sync_list:
rule->signal = RTLIL::SigSpec();
current_process->syncs.push_back(rule);
} update_list |
+ sync_list TOK_SYNC TOK_INIT TOK_EOL {
+ RTLIL::SyncRule *rule = new RTLIL::SyncRule;
+ rule->type = RTLIL::SyncType::STi;
+ rule->signal = RTLIL::SigSpec();
+ current_process->syncs.push_back(rule);
+ } update_list |
/* empty */;
sync_type: