mirror of
https://github.com/YosysHQ/yosys
synced 2025-04-25 18:15:34 +00:00
Add new $check
cell to represent assertions with a message.
This commit is contained in:
parent
e1a59ba80b
commit
c7bf0e3b8f
12 changed files with 516 additions and 306 deletions
|
@ -163,6 +163,28 @@ static RTLIL::SigSpec mux2rtlil(AstNode *that, const RTLIL::SigSpec &cond, const
|
|||
return wire;
|
||||
}
|
||||
|
||||
static void check_unique_id(RTLIL::Module *module, RTLIL::IdString id,
|
||||
const AstNode *node, const char *to_add_kind)
|
||||
{
|
||||
auto already_exists = [&](const RTLIL::AttrObject *existing, const char *existing_kind) {
|
||||
std::string src = existing->get_string_attribute(ID::src);
|
||||
std::string location_str = "earlier";
|
||||
if (!src.empty())
|
||||
location_str = "at " + src;
|
||||
node->input_error("Cannot add %s `%s' because a %s with the same name was already created %s!\n",
|
||||
to_add_kind, id.c_str(), existing_kind, location_str.c_str());
|
||||
};
|
||||
|
||||
if (const RTLIL::Wire *wire = module->wire(id))
|
||||
already_exists(wire, "signal");
|
||||
if (const RTLIL::Cell *cell = module->cell(id))
|
||||
already_exists(cell, "cell");
|
||||
if (module->processes.count(id))
|
||||
already_exists(module->processes.at(id), "process");
|
||||
if (module->memories.count(id))
|
||||
already_exists(module->memories.at(id), "memory");
|
||||
}
|
||||
|
||||
// helper class for rewriting simple lookahead references in AST always blocks
|
||||
struct AST_INTERNAL::LookaheadRewriter
|
||||
{
|
||||
|
@ -316,10 +338,10 @@ struct AST_INTERNAL::ProcessGenerator
|
|||
// Buffer for generating the init action
|
||||
RTLIL::SigSpec init_lvalue, init_rvalue;
|
||||
|
||||
// The most recently assigned $print cell \PRIORITY.
|
||||
int last_print_priority;
|
||||
// The most recently assigned $print or $check cell \PRIORITY.
|
||||
int last_effect_priority;
|
||||
|
||||
ProcessGenerator(AstNode *always, RTLIL::SigSpec initSyncSignalsArg = RTLIL::SigSpec()) : always(always), initSyncSignals(initSyncSignalsArg), last_print_priority(0)
|
||||
ProcessGenerator(AstNode *always, RTLIL::SigSpec initSyncSignalsArg = RTLIL::SigSpec()) : always(always), initSyncSignals(initSyncSignalsArg), last_effect_priority(0)
|
||||
{
|
||||
// rewrite lookahead references
|
||||
LookaheadRewriter la_rewriter(always);
|
||||
|
@ -703,8 +725,10 @@ struct AST_INTERNAL::ProcessGenerator
|
|||
std::stringstream sstr;
|
||||
sstr << ast->str << "$" << ast->filename << ":" << ast->location.first_line << "$" << (autoidx++);
|
||||
|
||||
RTLIL::Cell *cell = current_module->addCell(sstr.str(), ID($print));
|
||||
set_src_attr(cell, ast);
|
||||
Wire *en = current_module->addWire(sstr.str() + "_EN", 1);
|
||||
set_src_attr(en, ast);
|
||||
proc->root_case.actions.push_back(SigSig(en, false));
|
||||
current_case->actions.push_back(SigSig(en, true));
|
||||
|
||||
RTLIL::SigSpec triggers;
|
||||
RTLIL::Const polarity;
|
||||
|
@ -717,18 +741,15 @@ struct AST_INTERNAL::ProcessGenerator
|
|||
polarity.bits.push_back(RTLIL::S0);
|
||||
}
|
||||
}
|
||||
cell->parameters[ID::TRG_WIDTH] = triggers.size();
|
||||
cell->parameters[ID::TRG_ENABLE] = (always->type == AST_INITIAL) || !triggers.empty();
|
||||
cell->parameters[ID::TRG_POLARITY] = polarity;
|
||||
cell->parameters[ID::PRIORITY] = --last_print_priority;
|
||||
|
||||
RTLIL::Cell *cell = current_module->addCell(sstr.str(), ID($print));
|
||||
set_src_attr(cell, ast);
|
||||
cell->setParam(ID::TRG_WIDTH, triggers.size());
|
||||
cell->setParam(ID::TRG_ENABLE, (always->type == AST_INITIAL) || !triggers.empty());
|
||||
cell->setParam(ID::TRG_POLARITY, polarity);
|
||||
cell->setParam(ID::PRIORITY, --last_effect_priority);
|
||||
cell->setPort(ID::TRG, triggers);
|
||||
|
||||
Wire *wire = current_module->addWire(sstr.str() + "_EN", 1);
|
||||
set_src_attr(wire, ast);
|
||||
cell->setPort(ID::EN, wire);
|
||||
|
||||
proc->root_case.actions.push_back(SigSig(wire, false));
|
||||
current_case->actions.push_back(SigSig(wire, true));
|
||||
cell->setPort(ID::EN, en);
|
||||
|
||||
int default_base = 10;
|
||||
if (ast->str.back() == 'b')
|
||||
|
@ -766,7 +787,7 @@ struct AST_INTERNAL::ProcessGenerator
|
|||
args.push_back(arg);
|
||||
}
|
||||
|
||||
Fmt fmt = {};
|
||||
Fmt fmt;
|
||||
fmt.parse_verilog(args, /*sformat_like=*/false, default_base, /*task_name=*/ast->str, current_module->name);
|
||||
if (ast->str.substr(0, 8) == "$display")
|
||||
fmt.append_string("\n");
|
||||
|
@ -776,6 +797,70 @@ struct AST_INTERNAL::ProcessGenerator
|
|||
}
|
||||
break;
|
||||
|
||||
// generate $check cells
|
||||
case AST_ASSERT:
|
||||
case AST_ASSUME:
|
||||
case AST_LIVE:
|
||||
case AST_FAIR:
|
||||
case AST_COVER:
|
||||
{
|
||||
std::string flavor, desc;
|
||||
if (ast->type == AST_ASSERT) { flavor = "assert"; desc = "assert ()"; }
|
||||
if (ast->type == AST_ASSUME) { flavor = "assume"; desc = "assume ()"; }
|
||||
if (ast->type == AST_LIVE) { flavor = "live"; desc = "assert (eventually)"; }
|
||||
if (ast->type == AST_FAIR) { flavor = "fair"; desc = "assume (eventually)"; }
|
||||
if (ast->type == AST_COVER) { flavor = "cover"; desc = "cover ()"; }
|
||||
|
||||
IdString cellname;
|
||||
if (ast->str.empty())
|
||||
cellname = stringf("$%s$%s:%d$%d", flavor.c_str(), RTLIL::encode_filename(ast->filename).c_str(), ast->location.first_line, autoidx++);
|
||||
else
|
||||
cellname = ast->str;
|
||||
check_unique_id(current_module, cellname, ast, "procedural assertion");
|
||||
|
||||
RTLIL::SigSpec check = ast->children[0]->genWidthRTLIL(-1, false, &subst_rvalue_map.stdmap());
|
||||
if (GetSize(check) != 1)
|
||||
check = current_module->ReduceBool(NEW_ID, check);
|
||||
|
||||
Wire *en = current_module->addWire(cellname.str() + "_EN", 1);
|
||||
set_src_attr(en, ast);
|
||||
proc->root_case.actions.push_back(SigSig(en, false));
|
||||
current_case->actions.push_back(SigSig(en, true));
|
||||
|
||||
RTLIL::SigSpec triggers;
|
||||
RTLIL::Const polarity;
|
||||
for (auto sync : proc->syncs) {
|
||||
if (sync->type == RTLIL::STp) {
|
||||
triggers.append(sync->signal);
|
||||
polarity.bits.push_back(RTLIL::S1);
|
||||
} else if (sync->type == RTLIL::STn) {
|
||||
triggers.append(sync->signal);
|
||||
polarity.bits.push_back(RTLIL::S0);
|
||||
}
|
||||
}
|
||||
|
||||
RTLIL::Cell *cell = current_module->addCell(cellname, ID($check));
|
||||
set_src_attr(cell, ast);
|
||||
for (auto &attr : ast->attributes) {
|
||||
if (attr.second->type != AST_CONSTANT)
|
||||
log_file_error(ast->filename, ast->location.first_line, "Attribute `%s' with non-constant value!\n", attr.first.c_str());
|
||||
cell->attributes[attr.first] = attr.second->asAttrConst();
|
||||
}
|
||||
cell->setParam(ID::FLAVOR, flavor);
|
||||
cell->setParam(ID::TRG_WIDTH, triggers.size());
|
||||
cell->setParam(ID::TRG_ENABLE, (always->type == AST_INITIAL) || !triggers.empty());
|
||||
cell->setParam(ID::TRG_POLARITY, polarity);
|
||||
cell->setParam(ID::PRIORITY, --last_effect_priority);
|
||||
cell->setPort(ID::TRG, triggers);
|
||||
cell->setPort(ID::EN, en);
|
||||
cell->setPort(ID::A, check);
|
||||
|
||||
// No message is emitted to ensure Verilog code roundtrips correctly.
|
||||
Fmt fmt;
|
||||
fmt.emit_rtlil(cell);
|
||||
break;
|
||||
}
|
||||
|
||||
case AST_NONE:
|
||||
case AST_FOR:
|
||||
break;
|
||||
|
@ -1242,28 +1327,6 @@ void AstNode::detectSignWidth(int &width_hint, bool &sign_hint, bool *found_real
|
|||
width_hint, kWidthLimit);
|
||||
}
|
||||
|
||||
static void check_unique_id(RTLIL::Module *module, RTLIL::IdString id,
|
||||
const AstNode *node, const char *to_add_kind)
|
||||
{
|
||||
auto already_exists = [&](const RTLIL::AttrObject *existing, const char *existing_kind) {
|
||||
std::string src = existing->get_string_attribute(ID::src);
|
||||
std::string location_str = "earlier";
|
||||
if (!src.empty())
|
||||
location_str = "at " + src;
|
||||
node->input_error("Cannot add %s `%s' because a %s with the same name was already created %s!\n",
|
||||
to_add_kind, id.c_str(), existing_kind, location_str.c_str());
|
||||
};
|
||||
|
||||
if (const RTLIL::Wire *wire = module->wire(id))
|
||||
already_exists(wire, "signal");
|
||||
if (const RTLIL::Cell *cell = module->cell(id))
|
||||
already_exists(cell, "cell");
|
||||
if (module->processes.count(id))
|
||||
already_exists(module->processes.at(id), "process");
|
||||
if (module->memories.count(id))
|
||||
already_exists(module->memories.at(id), "memory");
|
||||
}
|
||||
|
||||
// create RTLIL from an AST node
|
||||
// all generated cells, wires and processes are added to the module pointed to by 'current_module'
|
||||
// when the AST node is an expression (AST_ADD, AST_BIT_XOR, etc.), the result signal is returned.
|
||||
|
@ -1945,48 +2008,50 @@ RTLIL::SigSpec AstNode::genRTLIL(int width_hint, bool sign_hint)
|
|||
}
|
||||
break;
|
||||
|
||||
// generate $assert cells
|
||||
// generate $check cells
|
||||
case AST_ASSERT:
|
||||
case AST_ASSUME:
|
||||
case AST_LIVE:
|
||||
case AST_FAIR:
|
||||
case AST_COVER:
|
||||
{
|
||||
IdString celltype;
|
||||
if (type == AST_ASSERT) celltype = ID($assert);
|
||||
if (type == AST_ASSUME) celltype = ID($assume);
|
||||
if (type == AST_LIVE) celltype = ID($live);
|
||||
if (type == AST_FAIR) celltype = ID($fair);
|
||||
if (type == AST_COVER) celltype = ID($cover);
|
||||
std::string flavor, desc;
|
||||
if (type == AST_ASSERT) { flavor = "assert"; desc = "assert property ()"; }
|
||||
if (type == AST_ASSUME) { flavor = "assume"; desc = "assume property ()"; }
|
||||
if (type == AST_LIVE) { flavor = "live"; desc = "assert property (eventually)"; }
|
||||
if (type == AST_FAIR) { flavor = "fair"; desc = "assume property (eventually)"; }
|
||||
if (type == AST_COVER) { flavor = "cover"; desc = "cover property ()"; }
|
||||
|
||||
log_assert(children.size() == 2);
|
||||
IdString cellname;
|
||||
if (str.empty())
|
||||
cellname = stringf("$%s$%s:%d$%d", flavor.c_str(), RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++);
|
||||
else
|
||||
cellname = str;
|
||||
check_unique_id(current_module, cellname, this, "procedural assertion");
|
||||
|
||||
RTLIL::SigSpec check = children[0]->genRTLIL();
|
||||
if (GetSize(check) != 1)
|
||||
check = current_module->ReduceBool(NEW_ID, check);
|
||||
|
||||
RTLIL::SigSpec en = children[1]->genRTLIL();
|
||||
if (GetSize(en) != 1)
|
||||
en = current_module->ReduceBool(NEW_ID, en);
|
||||
|
||||
IdString cellname;
|
||||
if (str.empty())
|
||||
cellname = stringf("%s$%s:%d$%d", celltype.c_str(), RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++);
|
||||
else
|
||||
cellname = str;
|
||||
|
||||
check_unique_id(current_module, cellname, this, "procedural assertion");
|
||||
RTLIL::Cell *cell = current_module->addCell(cellname, celltype);
|
||||
RTLIL::Cell *cell = current_module->addCell(cellname, ID($check));
|
||||
set_src_attr(cell, this);
|
||||
|
||||
for (auto &attr : attributes) {
|
||||
if (attr.second->type != AST_CONSTANT)
|
||||
input_error("Attribute `%s' with non-constant value!\n", attr.first.c_str());
|
||||
cell->attributes[attr.first] = attr.second->asAttrConst();
|
||||
}
|
||||
|
||||
cell->setParam(ID(FLAVOR), flavor);
|
||||
cell->parameters[ID::TRG_WIDTH] = 0;
|
||||
cell->parameters[ID::TRG_ENABLE] = 0;
|
||||
cell->parameters[ID::TRG_POLARITY] = 0;
|
||||
cell->parameters[ID::PRIORITY] = 0;
|
||||
cell->setPort(ID::TRG, RTLIL::SigSpec());
|
||||
cell->setPort(ID::EN, RTLIL::S1);
|
||||
cell->setPort(ID::A, check);
|
||||
cell->setPort(ID::EN, en);
|
||||
|
||||
// No message is emitted to ensure Verilog code roundtrips correctly.
|
||||
Fmt fmt;
|
||||
fmt.emit_rtlil(cell);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
@ -178,7 +178,7 @@ Fmt AstNode::processFormat(int stage, bool sformat_like, int default_base, size_
|
|||
args.push_back(arg);
|
||||
}
|
||||
|
||||
Fmt fmt = {};
|
||||
Fmt fmt;
|
||||
fmt.parse_verilog(args, sformat_like, default_base, /*task_name=*/str, current_module->name);
|
||||
return fmt;
|
||||
}
|
||||
|
@ -784,7 +784,7 @@ AstNode *AstNode::clone_at_zero()
|
|||
pointee->type != AST_MEMORY)
|
||||
break;
|
||||
|
||||
YS_FALLTHROUGH;
|
||||
YS_FALLTHROUGH
|
||||
case AST_MEMRD:
|
||||
detectSignWidth(width_hint, sign_hint);
|
||||
return mkconst_int(0, sign_hint, width_hint);
|
||||
|
@ -3039,97 +3039,6 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin
|
|||
}
|
||||
skip_dynamic_range_lvalue_expansion:;
|
||||
|
||||
if (stage > 1 && (type == AST_ASSERT || type == AST_ASSUME || type == AST_LIVE || type == AST_FAIR || type == AST_COVER) && current_block != NULL)
|
||||
{
|
||||
std::stringstream sstr;
|
||||
sstr << "$formal$" << RTLIL::encode_filename(filename) << ":" << location.first_line << "$" << (autoidx++);
|
||||
std::string id_check = sstr.str() + "_CHECK", id_en = sstr.str() + "_EN";
|
||||
|
||||
AstNode *wire_check = new AstNode(AST_WIRE);
|
||||
wire_check->str = id_check;
|
||||
wire_check->was_checked = true;
|
||||
current_ast_mod->children.push_back(wire_check);
|
||||
current_scope[wire_check->str] = wire_check;
|
||||
while (wire_check->simplify(true, 1, -1, false)) { }
|
||||
|
||||
AstNode *wire_en = new AstNode(AST_WIRE);
|
||||
wire_en->str = id_en;
|
||||
wire_en->was_checked = true;
|
||||
current_ast_mod->children.push_back(wire_en);
|
||||
if (current_always_clocked) {
|
||||
current_ast_mod->children.push_back(new AstNode(AST_INITIAL, new AstNode(AST_BLOCK, new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), AstNode::mkconst_int(0, false, 1)))));
|
||||
current_ast_mod->children.back()->children[0]->children[0]->children[0]->str = id_en;
|
||||
current_ast_mod->children.back()->children[0]->children[0]->children[0]->was_checked = true;
|
||||
}
|
||||
current_scope[wire_en->str] = wire_en;
|
||||
while (wire_en->simplify(true, 1, -1, false)) { }
|
||||
|
||||
AstNode *check_defval;
|
||||
if (type == AST_LIVE || type == AST_FAIR) {
|
||||
check_defval = new AstNode(AST_REDUCE_BOOL, children[0]->clone());
|
||||
} else {
|
||||
std::vector<RTLIL::State> x_bit;
|
||||
x_bit.push_back(RTLIL::State::Sx);
|
||||
check_defval = mkconst_bits(x_bit, false);
|
||||
}
|
||||
|
||||
AstNode *assign_check = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), check_defval);
|
||||
assign_check->children[0]->str = id_check;
|
||||
assign_check->children[0]->was_checked = true;
|
||||
|
||||
AstNode *assign_en = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), mkconst_int(0, false, 1));
|
||||
assign_en->children[0]->str = id_en;
|
||||
assign_en->children[0]->was_checked = true;
|
||||
|
||||
AstNode *default_signals = new AstNode(AST_BLOCK);
|
||||
default_signals->children.push_back(assign_check);
|
||||
default_signals->children.push_back(assign_en);
|
||||
current_top_block->children.insert(current_top_block->children.begin(), default_signals);
|
||||
|
||||
if (type == AST_LIVE || type == AST_FAIR) {
|
||||
assign_check = nullptr;
|
||||
} else {
|
||||
assign_check = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), new AstNode(AST_REDUCE_BOOL, children[0]->clone()));
|
||||
assign_check->children[0]->str = id_check;
|
||||
assign_check->children[0]->was_checked = true;
|
||||
assign_check->fixup_hierarchy_flags();
|
||||
}
|
||||
|
||||
if (current_always == nullptr || current_always->type != AST_INITIAL) {
|
||||
assign_en = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), mkconst_int(1, false, 1));
|
||||
} else {
|
||||
assign_en = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), new AstNode(AST_FCALL));
|
||||
assign_en->children[1]->str = "\\$initstate";
|
||||
}
|
||||
assign_en->children[0]->str = id_en;
|
||||
assign_en->children[0]->was_checked = true;
|
||||
assign_en->fixup_hierarchy_flags();
|
||||
|
||||
newNode = new AstNode(AST_BLOCK);
|
||||
if (assign_check != nullptr)
|
||||
newNode->children.push_back(assign_check);
|
||||
newNode->children.push_back(assign_en);
|
||||
|
||||
AstNode *assertnode = new AstNode(type);
|
||||
assertnode->location = location;
|
||||
assertnode->str = str;
|
||||
assertnode->children.push_back(new AstNode(AST_IDENTIFIER));
|
||||
assertnode->children.push_back(new AstNode(AST_IDENTIFIER));
|
||||
assertnode->children[0]->str = id_check;
|
||||
assertnode->children[1]->str = id_en;
|
||||
assertnode->attributes.swap(attributes);
|
||||
current_ast_mod->children.push_back(assertnode);
|
||||
|
||||
goto apply_newNode;
|
||||
}
|
||||
|
||||
if (stage > 1 && (type == AST_ASSERT || type == AST_ASSUME || type == AST_LIVE || type == AST_FAIR || type == AST_COVER) && children.size() == 1)
|
||||
{
|
||||
children.push_back(mkconst_int(1, false, 1));
|
||||
fixup_hierarchy_flags();
|
||||
did_something = true;
|
||||
}
|
||||
|
||||
// found right-hand side identifier for memory -> replace with memory read port
|
||||
if (stage > 1 && type == AST_IDENTIFIER && id2ast != NULL && id2ast->type == AST_MEMORY && !in_lvalue &&
|
||||
children.size() == 1 && children[0]->type == AST_RANGE && children[0]->children.size() == 1) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue