verilog: use derived module info to elaborate cell connections

- Attempt to lookup a derived module if it potentially contains a port
  connection with elaboration ambiguities
- Mark the cell if module has not yet been derived
- This can be extended to implement automatic hierarchical port
  connections in a future change
This commit is contained in:
Zachary Snow 2021-10-19 18:46:26 -06:00 committed by Zachary Snow
parent bd16d01c0e
commit e833c6a418
15 changed files with 397 additions and 42 deletions

View File

@ -8,6 +8,14 @@ Yosys 0.10 .. Yosys 0.10-dev
* Various
- Added $aldff and $aldffe (flip-flops with async load) cells
* SystemVerilog
- Fixed an issue which prevented writing directly to a memory word via a
connection to an output port
- Fixed an issue which prevented unbased unsized literals (e.g., `'1`) from
filling the width of a cell input
- Fixed an issue where connecting a slice covering the entirety of a signed
signal to a cell input would cause a failed assertion
Yosys 0.9 .. Yosys 0.10
--------------------------

View File

@ -489,6 +489,11 @@ Verilog Attributes and non-standard features
for use in blackboxes and whiteboxes. Use ``read_verilog -specify`` to
enable this functionality. (By default these blocks are ignored.)
- The ``reprocess_after`` internal attribute is used by the Verilog frontend to
mark cells with bindings which might depend on the specified instantiated
module. Modules with such cells will be reprocessed during the ``hierarchy``
pass once the referenced module definition(s) become available.
Non-standard or SystemVerilog features for formal verification
==============================================================

View File

@ -854,7 +854,7 @@ RTLIL::Const AstNode::bitsAsConst(int width)
return bitsAsConst(width, is_signed);
}
RTLIL::Const AstNode::asAttrConst()
RTLIL::Const AstNode::asAttrConst() const
{
log_assert(type == AST_CONSTANT);
@ -869,8 +869,17 @@ RTLIL::Const AstNode::asAttrConst()
return val;
}
RTLIL::Const AstNode::asParaConst()
RTLIL::Const AstNode::asParaConst() const
{
if (type == AST_REALVALUE)
{
AstNode *strnode = AstNode::mkconst_str(stringf("%f", realvalue));
RTLIL::Const val = strnode->asAttrConst();
val.flags |= RTLIL::CONST_FLAG_REAL;
delete strnode;
return val;
}
RTLIL::Const val = asAttrConst();
if (is_signed)
val.flags |= RTLIL::CONST_FLAG_SIGNED;
@ -1043,8 +1052,11 @@ static RTLIL::Module *process_module(RTLIL::Design *design, AstNode *ast, bool d
}
}
// TODO(zachjs): make design available to simplify() in the future
// simplify this module or interface using the current design as context
// for lookup up ports and wires within cells
set_simplify_design_context(design);
while (ast->simplify(!flag_noopt, false, false, 0, -1, false, false)) { }
set_simplify_design_context(nullptr);
if (flag_dump_ast2) {
log("Dumping AST after simplification:\n");
@ -1171,6 +1183,9 @@ static RTLIL::Module *process_module(RTLIL::Design *design, AstNode *ast, bool d
continue;
module->attributes[attr.first] = attr.second->asAttrConst();
}
for (const AstNode *node : ast->children)
if (node->type == AST_PARAMETER)
current_module->avail_parameters(node->str);
}
if (ast->type == AST_INTERFACE)
@ -1445,6 +1460,26 @@ void AST::explode_interface_port(AstNode *module_ast, RTLIL::Module * intfmodule
}
}
// AstModules may contain cells marked with ID::reprocess_after, which indicates
// that it should be reprocessed once the specified module has been elaborated.
bool AstModule::reprocess_if_necessary(RTLIL::Design *design)
{
for (const RTLIL::Cell *cell : cells())
{
std::string modname = cell->get_string_attribute(ID::reprocess_after);
if (modname.empty())
continue;
if (design->module(modname) || design->module("$abstract" + modname)) {
log("Reprocessing module %s because instantiated module %s has become available.\n",
log_id(name), log_id(modname));
loadconfig();
process_and_replace_module(design, this, ast, NULL);
return true;
}
}
return false;
}
// When an interface instance is found in a module, the whole RTLIL for the module will be rederived again
// from AST. The interface members are copied into the AST module with the prefix of the interface.
void AstModule::expand_interfaces(RTLIL::Design *design, const dict<RTLIL::IdString, RTLIL::Module*> &local_interfaces)
@ -1649,6 +1684,17 @@ static std::string serialize_param_value(const RTLIL::Const &val) {
return res;
}
std::string AST::derived_module_name(std::string stripped_name, const std::vector<std::pair<RTLIL::IdString, RTLIL::Const>> &parameters) {
std::string para_info;
for (const auto &elem : parameters)
para_info += stringf("%s=%s", elem.first.c_str(), serialize_param_value(elem.second).c_str());
if (para_info.size() > 60)
return "$paramod$" + sha1(para_info) + stripped_name;
else
return "$paramod" + stripped_name + para_info;
}
// create a new parametric module (when needed) and return the name of the generated module
std::string AstModule::derive_common(RTLIL::Design *design, const dict<RTLIL::IdString, RTLIL::Const> &parameters, AstNode **new_ast_out, bool quiet)
{
@ -1657,9 +1703,8 @@ std::string AstModule::derive_common(RTLIL::Design *design, const dict<RTLIL::Id
if (stripped_name.compare(0, 9, "$abstract") == 0)
stripped_name = stripped_name.substr(9);
std::string para_info;
int para_counter = 0;
std::vector<std::pair<RTLIL::IdString, RTLIL::Const>> named_parameters;
for (const auto child : ast->children) {
if (child->type != AST_PARAMETER)
continue;
@ -1668,25 +1713,21 @@ std::string AstModule::derive_common(RTLIL::Design *design, const dict<RTLIL::Id
if (it != parameters.end()) {
if (!quiet)
log("Parameter %s = %s\n", child->str.c_str(), log_signal(it->second));
para_info += stringf("%s=%s", child->str.c_str(), serialize_param_value(it->second).c_str());
named_parameters.emplace_back(child->str, it->second);
continue;
}
it = parameters.find(stringf("$%d", para_counter));
if (it != parameters.end()) {
if (!quiet)
log("Parameter %d (%s) = %s\n", para_counter, child->str.c_str(), log_signal(it->second));
para_info += stringf("%s=%s", child->str.c_str(), serialize_param_value(it->second).c_str());
named_parameters.emplace_back(child->str, it->second);
continue;
}
}
std::string modname;
if (parameters.size() == 0)
modname = stripped_name;
else if (para_info.size() > 60)
modname = "$paramod$" + sha1(para_info) + stripped_name;
else
modname = "$paramod" + stripped_name + para_info;
std::string modname = stripped_name;
if (parameters.size()) // not named_parameters to cover hierarchical defparams
modname = derived_module_name(stripped_name, named_parameters);
if (design->has(modname))
return modname;

View File

@ -262,6 +262,7 @@ namespace AST
void mem2reg_remove(pool<AstNode*> &mem2reg_set, vector<AstNode*> &delnodes);
void meminfo(int &mem_width, int &mem_size, int &addr_bits);
bool detect_latch(const std::string &var);
const RTLIL::Module* lookup_cell_module();
// additional functionality for evaluating constant functions
struct varinfo_t {
@ -313,8 +314,8 @@ namespace AST
RTLIL::Const bitsAsConst(int width, bool is_signed);
RTLIL::Const bitsAsConst(int width = -1);
RTLIL::Const bitsAsUnsizedConst(int width);
RTLIL::Const asAttrConst();
RTLIL::Const asParaConst();
RTLIL::Const asAttrConst() const;
RTLIL::Const asParaConst() const;
uint64_t asInt(bool is_signed);
bool bits_only_01() const;
bool asBool() const;
@ -349,6 +350,7 @@ namespace AST
RTLIL::IdString derive(RTLIL::Design *design, const dict<RTLIL::IdString, RTLIL::Const> &parameters, const dict<RTLIL::IdString, RTLIL::Module*> &interfaces, const dict<RTLIL::IdString, RTLIL::IdString> &modports, bool mayfail) override;
std::string derive_common(RTLIL::Design *design, const dict<RTLIL::IdString, RTLIL::Const> &parameters, AstNode **new_ast_out, bool quiet = false);
void expand_interfaces(RTLIL::Design *design, const dict<RTLIL::IdString, RTLIL::Module *> &local_interfaces) override;
bool reprocess_if_necessary(RTLIL::Design *design) override;
RTLIL::Module *clone() const override;
void loadconfig() const;
};
@ -377,6 +379,14 @@ namespace AST
// struct helper exposed from simplify for genrtlil
AstNode *make_struct_member_range(AstNode *node, AstNode *member_node);
// generate standard $paramod... derived module name; parameters should be
// in the order they are declared in the instantiated module
std::string derived_module_name(std::string stripped_name, const std::vector<std::pair<RTLIL::IdString, RTLIL::Const>> &parameters);
// used to provide simplify() access to the current design for looking up
// modules, ports, wires, etc.
void set_simplify_design_context(const RTLIL::Design *design);
}
namespace AST_INTERNAL

View File

@ -1917,21 +1917,15 @@ RTLIL::SigSpec AstNode::genRTLIL(int width_hint, bool sign_hint)
continue;
}
if (child->type == AST_PARASET) {
int extra_const_flags = 0;
IdString paraname = child->str.empty() ? stringf("$%d", ++para_counter) : child->str;
if (child->children[0]->type == AST_REALVALUE) {
const AstNode *value = child->children[0];
if (value->type == AST_REALVALUE)
log_file_warning(filename, location.first_line, "Replacing floating point parameter %s.%s = %f with string.\n",
log_id(cell), log_id(paraname), child->children[0]->realvalue);
extra_const_flags = RTLIL::CONST_FLAG_REAL;
auto strnode = AstNode::mkconst_str(stringf("%f", child->children[0]->realvalue));
strnode->cloneInto(child->children[0]);
delete strnode;
}
if (child->children[0]->type != AST_CONSTANT)
log_id(cell), log_id(paraname), value->realvalue);
else if (value->type != AST_CONSTANT)
log_file_error(filename, location.first_line, "Parameter %s.%s with non-constant value!\n",
log_id(cell), log_id(paraname));
cell->parameters[paraname] = child->children[0]->asParaConst();
cell->parameters[paraname].flags |= extra_const_flags;
cell->parameters[paraname] = value->asParaConst();
continue;
}
if (child->type == AST_ARGUMENT) {
@ -1948,7 +1942,12 @@ RTLIL::SigSpec AstNode::genRTLIL(int width_hint, bool sign_hint)
if (sig.is_wire()) {
// if the resulting SigSpec is a wire, its
// signedness should match that of the AstNode
log_assert(arg->is_signed == sig.as_wire()->is_signed);
if (arg->type == AST_IDENTIFIER && arg->id2ast && arg->id2ast->is_signed && !arg->is_signed)
// fully-sliced signed wire will be resolved
// once the module becomes available
log_assert(attributes.count(ID::reprocess_after));
else
log_assert(arg->is_signed == sig.as_wire()->is_signed);
} else if (arg->is_signed) {
// non-trivial signed nodes are indirected through
// signed wires to enable sign extension

View File

@ -564,6 +564,115 @@ static std::string prefix_id(const std::string &prefix, const std::string &str)
return prefix + str;
}
// direct access to this global should be limited to the following two functions
static const RTLIL::Design *simplify_design_context = nullptr;
void AST::set_simplify_design_context(const RTLIL::Design *design)
{
log_assert(!simplify_design_context || !design);
simplify_design_context = design;
}
// lookup the module with the given name in the current design context
static const RTLIL::Module* lookup_module(const std::string &name)
{
return simplify_design_context->module(name);
}
const RTLIL::Module* AstNode::lookup_cell_module()
{
log_assert(type == AST_CELL);
auto reprocess_after = [this] (const std::string &modname) {
if (!attributes.count(ID::reprocess_after))
attributes[ID::reprocess_after] = AstNode::mkconst_str(modname);
};
const AstNode *celltype = nullptr;
for (const AstNode *child : children)
if (child->type == AST_CELLTYPE) {
celltype = child;
break;
}
log_assert(celltype != nullptr);
const RTLIL::Module *module = lookup_module(celltype->str);
if (!module)
module = lookup_module("$abstract" + celltype->str);
if (!module) {
if (celltype->str.at(0) != '$')
reprocess_after(celltype->str);
return nullptr;
}
// build a mapping from true param name to param value
size_t para_counter = 0;
dict<RTLIL::IdString, RTLIL::Const> cell_params_map;
for (AstNode *child : children) {
if (child->type != AST_PARASET)
continue;
if (child->str.empty() && para_counter >= module->avail_parameters.size())
return nullptr; // let hierarchy handle this error
IdString paraname = child->str.empty() ? module->avail_parameters[para_counter++] : child->str;
const AstNode *value = child->children[0];
if (value->type != AST_REALVALUE && value->type != AST_CONSTANT)
return nullptr; // let genrtlil handle this error
cell_params_map[paraname] = value->asParaConst();
}
// put the parameters in order and generate the derived module name
std::vector<std::pair<RTLIL::IdString, RTLIL::Const>> named_parameters;
for (RTLIL::IdString param : module->avail_parameters) {
auto it = cell_params_map.find(param);
if (it != cell_params_map.end())
named_parameters.emplace_back(it->first, it->second);
}
std::string modname = celltype->str;
if (cell_params_map.size()) // not named_parameters to cover hierarchical defparams
modname = derived_module_name(celltype->str, named_parameters);
// try to find the resolved module
module = lookup_module(modname);
if (!module) {
reprocess_after(modname);
return nullptr;
}
return module;
}
// returns whether an expression contains an unbased unsized literal; does not
// check the literal exists in a self-determined context
static bool contains_unbased_unsized(const AstNode *node)
{
if (node->type == AST_CONSTANT)
return node->is_unsized;
for (const AstNode *child : node->children)
if (contains_unbased_unsized(child))
return true;
return false;
}
// adds a wire to the current module with the given name that matches the
// dimensions of the given wire reference
void add_wire_for_ref(const RTLIL::Wire *ref, const std::string &str)
{
AstNode *left = AstNode::mkconst_int(ref->width - 1 + ref->start_offset, true);
AstNode *right = AstNode::mkconst_int(ref->start_offset, true);
if (ref->upto)
std::swap(left, right);
AstNode *range = new AstNode(AST_RANGE, left, right);
AstNode *wire = new AstNode(AST_WIRE, range);
wire->is_signed = ref->is_signed;
wire->is_logic = true;
wire->str = str;
current_ast_mod->children.push_back(wire);
current_scope[str] = wire;
}
// convert the AST into a simpler AST that has all parameters substituted by their
// values, unrolled for-loops, expanded generate blocks, etc. when this function
// is done with an AST it can be converted into RTLIL using genRTLIL().
@ -920,19 +1029,110 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
}
}
if (type == AST_ARGUMENT)
{
if (children.size() == 1 && children[0]->type == AST_CONSTANT)
{
// HACK: For port bindings using unbased unsized literals, mark them
// signed so they sign-extend. The hierarchy will still incorrectly
// generate a warning complaining about resizing the expression.
// This also doesn't handle the complex of something like a ternary
// expression bound to a port, where the actual size of the port is
// needed to resolve the expression correctly.
AstNode *arg = children[0];
if (arg->is_unsized)
arg->is_signed = true;
if (type == AST_CELL) {
bool lookup_suggested = false;
for (AstNode *child : children) {
// simplify any parameters to constants
if (child->type == AST_PARASET)
while (child->simplify(true, false, false, 1, -1, false, true)) { }
// look for patterns which _may_ indicate ambiguity requiring
// resolution of the underlying module
if (child->type == AST_ARGUMENT) {
if (child->children.size() != 1)
continue;
const AstNode *value = child->children[0];
if (value->type == AST_IDENTIFIER) {
const AstNode *elem = value->id2ast;
if (elem == nullptr) {
if (current_scope.count(value->str))
elem = current_scope.at(value->str);
else
continue;
}
if (elem->type == AST_MEMORY)
// need to determine is the is a read or wire
lookup_suggested = true;
else if (elem->type == AST_WIRE && elem->is_signed && !value->children.empty())
// this may be a fully sliced signed wire which needs
// to be indirected to produce an unsigned connection
lookup_suggested = true;
}
else if (contains_unbased_unsized(value))
// unbased unsized literals extend to width of the context
lookup_suggested = true;
}
}
const RTLIL::Module *module = nullptr;
if (lookup_suggested)
module = lookup_cell_module();
if (module) {
size_t port_counter = 0;
for (AstNode *child : children) {
if (child->type != AST_ARGUMENT)
continue;
// determine the full name of port this argument is connected to
RTLIL::IdString port_name;
if (child->str.size())
port_name = child->str;
else {
if (port_counter >= module->ports.size())
log_file_error(filename, location.first_line,
"Cell instance has more ports than the module!\n");
port_name = module->ports[port_counter++];
}
// find the port's wire in the underlying module
const RTLIL::Wire *ref = module->wire(port_name);
if (ref == nullptr)
log_file_error(filename, location.first_line,
"Cell instance refers to port %s which does not exist in module %s!.\n",
log_id(port_name), log_id(module->name));
// select the argument, if present
log_assert(child->children.size() <= 1);
if (child->children.empty())
continue;
AstNode *arg = child->children[0];
// plain identifiers never need indirection; this also prevents
// adding infinite levels of indirection
if (arg->type == AST_IDENTIFIER && arg->children.empty())
continue;
// only add indirection for standard inputs or outputs
if (ref->port_input == ref->port_output)
continue;
did_something = true;
// create the indirection wire
std::stringstream sstr;
sstr << "$indirect$" << ref->name.c_str() << "$" << filename << ":" << location.first_line << "$" << (autoidx++);
std::string tmp_str = sstr.str();
add_wire_for_ref(ref, tmp_str);
AstNode *asgn = new AstNode(AST_ASSIGN);
current_ast_mod->children.push_back(asgn);
AstNode *ident = new AstNode(AST_IDENTIFIER);
ident->str = tmp_str;
child->children[0] = ident->clone();
if (ref->port_input && !ref->port_output) {
asgn->children.push_back(ident);
asgn->children.push_back(arg);
} else {
log_assert(!ref->port_input && ref->port_output);
asgn->children.push_back(arg);
asgn->children.push_back(ident);
}
}
}
}

View File

@ -163,6 +163,7 @@ X(RD_TRANSPARENCY_MASK)
X(RD_TRANSPARENT)
X(RD_WIDE_CONTINUATION)
X(reg)
X(reprocess_after)
X(S)
X(SET)
X(SET_POLARITY)

View File

@ -941,6 +941,11 @@ void RTLIL::Module::expand_interfaces(RTLIL::Design *, const dict<RTLIL::IdStrin
log_error("Class doesn't support expand_interfaces (module: `%s')!\n", id2cstr(name));
}
bool RTLIL::Module::reprocess_if_necessary(RTLIL::Design *)
{
return false;
}
RTLIL::IdString RTLIL::Module::derive(RTLIL::Design*, const dict<RTLIL::IdString, RTLIL::Const> &, bool mayfail)
{
if (mayfail)

View File

@ -1161,6 +1161,7 @@ public:
virtual RTLIL::IdString derive(RTLIL::Design *design, const dict<RTLIL::IdString, RTLIL::Const> &parameters, const dict<RTLIL::IdString, RTLIL::Module*> &interfaces, const dict<RTLIL::IdString, RTLIL::IdString> &modports, bool mayfail = false);
virtual size_t count_id(RTLIL::IdString id);
virtual void expand_interfaces(RTLIL::Design *design, const dict<RTLIL::IdString, RTLIL::Module *> &local_interfaces);
virtual bool reprocess_if_necessary(RTLIL::Design *design);
virtual void sort();
virtual void check();

View File

@ -558,6 +558,10 @@ bool expand_module(RTLIL::Design *design, RTLIL::Module *module, bool flag_check
return did_something;
}
// Now that modules have been derived, we may want to reprocess this
// module given the additional available context.
if (module->reprocess_if_necessary(design))
return true;
for (auto &it : array_cells)
{

View File

@ -377,10 +377,12 @@ struct TechmapWorker
if (c->attributes.count(ID::src))
c->add_strpool_attribute(ID::src, extra_src_attrs);
if (techmap_replace_cell)
if (techmap_replace_cell) {
for (auto attr : cell->attributes)
if (!c->attributes.count(attr.first))
c->attributes[attr.first] = attr.second;
c->attributes.erase(ID::reprocess_after);
}
}
for (auto &it : tpl->connections()) {

View File

@ -0,0 +1,13 @@
module producer(
output logic [3:0] out
);
assign out = 4'hA;
endmodule
module top(
output logic [3:0] out
);
logic [3:0] v[0:0];
producer p(v[0]);
assign out = v[0];
endmodule

View File

@ -0,0 +1,29 @@
module pass_through_a(
input wire [31:0] inp,
output wire [31:0] out
);
assign out[31:0] = inp[31:0];
endmodule
module top_a(
input wire signed [31:0] inp,
output wire signed [31:0] out
);
pass_through_a pt(inp[31:0], out[31:0]);
endmodule
// tests both module declaration orderings
module top_b(
input wire signed [31:0] inp,
output wire signed [31:0] out
);
pass_through_b pt(inp[31:0], out[31:0]);
endmodule
module pass_through_b(
input wire [31:0] inp,
output wire [31:0] out
);
assign out[31:0] = inp[31:0];
endmodule

View File

@ -0,0 +1,31 @@
module pass_through #(
parameter WIDTH = 1
) (
input logic [WIDTH-1:0] inp,
output logic [WIDTH-1:0] out
);
assign out = inp;
endmodule
module gate (
input logic inp,
output logic [63:0]
out1, out2, out3, out4
);
pass_through #(40) pt1('1, out1);
pass_through #(40) pt2(inp ? '1 : '0, out2);
pass_through #(40) pt3(inp ? '1 : 2'b10, out3);
pass_through #(40) pt4(inp ? '1 : inp, out4);
endmodule
module gold (
input logic inp,
output logic [63:0]
out1, out2, out3, out4
);
localparam ONES = 40'hFF_FFFF_FFFF;
pass_through #(40) pt1(ONES, out1);
pass_through #(40) pt2(inp ? ONES : 0, out2);
pass_through #(40) pt3(inp ? ONES : 2'sb10, out3);
pass_through #(40) pt4(inp ? ONES : inp, out4);
endmodule

View File

@ -0,0 +1,6 @@
read_verilog -sv unbased_unsized_tern.sv
hierarchy
proc
equiv_make gold gate equiv
equiv_simple
equiv_status -assert