mirror of https://github.com/YosysHQ/yosys.git
Merge pull request #2586 from zachjs/tern-recurse
verilog: support recursive functions using ternary expressions
This commit is contained in:
commit
01ccb80b70
|
@ -270,6 +270,9 @@ namespace AST
|
||||||
bool is_simple_const_expr();
|
bool is_simple_const_expr();
|
||||||
std::string process_format_str(const std::string &sformat, int next_arg, int stage, int width_hint, bool sign_hint);
|
std::string process_format_str(const std::string &sformat, int next_arg, int stage, int width_hint, bool sign_hint);
|
||||||
|
|
||||||
|
bool is_recursive_function() const;
|
||||||
|
std::pair<AstNode*, AstNode*> get_tern_choice();
|
||||||
|
|
||||||
// create a human-readable text representation of the AST (for debugging)
|
// create a human-readable text representation of the AST (for debugging)
|
||||||
void dumpAst(FILE *f, std::string indent) const;
|
void dumpAst(FILE *f, std::string indent) const;
|
||||||
void dumpVlog(FILE *f, std::string indent) const;
|
void dumpVlog(FILE *f, std::string indent) const;
|
||||||
|
|
|
@ -944,6 +944,41 @@ void AstNode::detectSignWidthWorker(int &width_hint, bool &sign_hint, bool *foun
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (current_scope.count(str))
|
||||||
|
{
|
||||||
|
// This width detection is needed for function calls which are
|
||||||
|
// unelaborated, which currently only applies to calls to recursive
|
||||||
|
// functions reached by unevaluated ternary branches.
|
||||||
|
const AstNode *func = current_scope.at(str);
|
||||||
|
if (func->type != AST_FUNCTION)
|
||||||
|
log_file_error(filename, location.first_line, "Function call to %s resolved to something that isn't a function!\n", RTLIL::unescape_id(str).c_str());
|
||||||
|
const AstNode *wire = nullptr;
|
||||||
|
for (const AstNode *child : func->children)
|
||||||
|
if (child->str == func->str) {
|
||||||
|
wire = child;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
log_assert(wire && wire->type == AST_WIRE);
|
||||||
|
sign_hint = wire->is_signed;
|
||||||
|
width_hint = 1;
|
||||||
|
if (!wire->children.empty())
|
||||||
|
{
|
||||||
|
log_assert(wire->children.size() == 1);
|
||||||
|
const AstNode *range = wire->children.at(0);
|
||||||
|
log_assert(range->type == AST_RANGE && range->children.size() == 2);
|
||||||
|
AstNode *left = range->children.at(0)->clone();
|
||||||
|
AstNode *right = range->children.at(1)->clone();
|
||||||
|
while (left->simplify(true, false, false, 1, -1, false, true)) { }
|
||||||
|
while (right->simplify(true, false, false, 1, -1, false, true)) { }
|
||||||
|
if (left->type != AST_CONSTANT || right->type != AST_CONSTANT)
|
||||||
|
log_file_error(filename, location.first_line, "Function %s has non-constant width!",
|
||||||
|
RTLIL::unescape_id(str).c_str());
|
||||||
|
width_hint = abs(int(left->asInt(true) - right->asInt(true)));
|
||||||
|
delete left;
|
||||||
|
delete right;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
YS_FALLTHROUGH
|
YS_FALLTHROUGH
|
||||||
|
|
||||||
// everything should have been handled above -> print error if not.
|
// everything should have been handled above -> print error if not.
|
||||||
|
|
|
@ -575,6 +575,8 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
|
||||||
deep_recursion_warning = false;
|
deep_recursion_warning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool unevaluated_tern_branch = false;
|
||||||
|
|
||||||
AstNode *newNode = NULL;
|
AstNode *newNode = NULL;
|
||||||
bool did_something = false;
|
bool did_something = false;
|
||||||
|
|
||||||
|
@ -1091,7 +1093,6 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AST_TERNARY:
|
case AST_TERNARY:
|
||||||
detect_width_simple = true;
|
|
||||||
child_0_is_self_determined = true;
|
child_0_is_self_determined = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -1124,6 +1125,24 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
|
||||||
detectSignWidth(width_hint, sign_hint);
|
detectSignWidth(width_hint, sign_hint);
|
||||||
|
|
||||||
if (type == AST_TERNARY) {
|
if (type == AST_TERNARY) {
|
||||||
|
if (width_hint < 0) {
|
||||||
|
while (!children[0]->basic_prep && children[0]->simplify(true, false, in_lvalue, stage, -1, false, in_param))
|
||||||
|
did_something = true;
|
||||||
|
|
||||||
|
bool backup_unevaluated_tern_branch = unevaluated_tern_branch;
|
||||||
|
AstNode *chosen = get_tern_choice().first;
|
||||||
|
|
||||||
|
unevaluated_tern_branch = backup_unevaluated_tern_branch || chosen == children[2];
|
||||||
|
while (!children[1]->basic_prep && children[1]->simplify(false, false, in_lvalue, stage, -1, false, in_param))
|
||||||
|
did_something = true;
|
||||||
|
|
||||||
|
unevaluated_tern_branch = backup_unevaluated_tern_branch || chosen == children[1];
|
||||||
|
while (!children[2]->basic_prep && children[2]->simplify(false, false, in_lvalue, stage, -1, false, in_param))
|
||||||
|
did_something = true;
|
||||||
|
|
||||||
|
unevaluated_tern_branch = backup_unevaluated_tern_branch;
|
||||||
|
detectSignWidth(width_hint, sign_hint);
|
||||||
|
}
|
||||||
int width_hint_left, width_hint_right;
|
int width_hint_left, width_hint_right;
|
||||||
bool sign_hint_left, sign_hint_right;
|
bool sign_hint_left, sign_hint_right;
|
||||||
bool found_real_left, found_real_right;
|
bool found_real_left, found_real_right;
|
||||||
|
@ -1187,6 +1206,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
|
||||||
for (size_t i = 0; i < children.size(); i++) {
|
for (size_t i = 0; i < children.size(); i++) {
|
||||||
bool did_something_here = true;
|
bool did_something_here = true;
|
||||||
bool backup_flag_autowire = flag_autowire;
|
bool backup_flag_autowire = flag_autowire;
|
||||||
|
bool backup_unevaluated_tern_branch = unevaluated_tern_branch;
|
||||||
if ((type == AST_GENFOR || type == AST_FOR) && i >= 3)
|
if ((type == AST_GENFOR || type == AST_FOR) && i >= 3)
|
||||||
break;
|
break;
|
||||||
if ((type == AST_GENIF || type == AST_GENCASE) && i >= 1)
|
if ((type == AST_GENIF || type == AST_GENCASE) && i >= 1)
|
||||||
|
@ -1199,6 +1219,10 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
|
||||||
break;
|
break;
|
||||||
if (type == AST_DEFPARAM && i == 0)
|
if (type == AST_DEFPARAM && i == 0)
|
||||||
flag_autowire = true;
|
flag_autowire = true;
|
||||||
|
if (type == AST_TERNARY && i > 0 && !unevaluated_tern_branch) {
|
||||||
|
AstNode *chosen = get_tern_choice().first;
|
||||||
|
unevaluated_tern_branch = chosen && chosen != children[i];
|
||||||
|
}
|
||||||
while (did_something_here && i < children.size()) {
|
while (did_something_here && i < children.size()) {
|
||||||
bool const_fold_here = const_fold, in_lvalue_here = in_lvalue;
|
bool const_fold_here = const_fold, in_lvalue_here = in_lvalue;
|
||||||
int width_hint_here = width_hint;
|
int width_hint_here = width_hint;
|
||||||
|
@ -1238,6 +1262,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
|
||||||
did_something = true;
|
did_something = true;
|
||||||
}
|
}
|
||||||
flag_autowire = backup_flag_autowire;
|
flag_autowire = backup_flag_autowire;
|
||||||
|
unevaluated_tern_branch = backup_unevaluated_tern_branch;
|
||||||
}
|
}
|
||||||
for (auto &attr : attributes) {
|
for (auto &attr : attributes) {
|
||||||
while (attr.second->simplify(true, false, false, stage, -1, false, true))
|
while (attr.second->simplify(true, false, false, stage, -1, false, true))
|
||||||
|
@ -3177,6 +3202,8 @@ skip_dynamic_range_lvalue_expansion:;
|
||||||
std::string prefix = sstr.str();
|
std::string prefix = sstr.str();
|
||||||
|
|
||||||
AstNode *decl = current_scope[str];
|
AstNode *decl = current_scope[str];
|
||||||
|
if (unevaluated_tern_branch && decl->is_recursive_function())
|
||||||
|
goto replace_fcall_later;
|
||||||
decl = decl->clone();
|
decl = decl->clone();
|
||||||
decl->replace_result_wire_name_in_function(str, "$result"); // enables recursion
|
decl->replace_result_wire_name_in_function(str, "$result"); // enables recursion
|
||||||
decl->expand_genblock(prefix);
|
decl->expand_genblock(prefix);
|
||||||
|
@ -3610,24 +3637,9 @@ replace_fcall_later:;
|
||||||
case AST_TERNARY:
|
case AST_TERNARY:
|
||||||
if (children[0]->isConst())
|
if (children[0]->isConst())
|
||||||
{
|
{
|
||||||
bool found_sure_true = false;
|
auto pair = get_tern_choice();
|
||||||
bool found_maybe_true = false;
|
AstNode *choice = pair.first;
|
||||||
|
AstNode *not_choice = pair.second;
|
||||||
if (children[0]->type == AST_CONSTANT)
|
|
||||||
for (auto &bit : children[0]->bits) {
|
|
||||||
if (bit == RTLIL::State::S1)
|
|
||||||
found_sure_true = true;
|
|
||||||
if (bit > RTLIL::State::S1)
|
|
||||||
found_maybe_true = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
found_sure_true = children[0]->asReal(sign_hint) != 0;
|
|
||||||
|
|
||||||
AstNode *choice = NULL, *not_choice = NULL;
|
|
||||||
if (found_sure_true)
|
|
||||||
choice = children[1], not_choice = children[2];
|
|
||||||
else if (!found_maybe_true)
|
|
||||||
choice = children[2], not_choice = children[1];
|
|
||||||
|
|
||||||
if (choice != NULL) {
|
if (choice != NULL) {
|
||||||
if (choice->type == AST_CONSTANT) {
|
if (choice->type == AST_CONSTANT) {
|
||||||
|
@ -4845,4 +4857,54 @@ void AstNode::allocateDefaultEnumValues()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AstNode::is_recursive_function() const
|
||||||
|
{
|
||||||
|
std::set<const AstNode *> visited;
|
||||||
|
std::function<bool(const AstNode *node)> visit = [&](const AstNode *node) {
|
||||||
|
if (visited.count(node))
|
||||||
|
return node == this;
|
||||||
|
visited.insert(node);
|
||||||
|
if (node->type == AST_FCALL) {
|
||||||
|
auto it = current_scope.find(node->str);
|
||||||
|
if (it != current_scope.end() && visit(it->second))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (const AstNode *child : node->children) {
|
||||||
|
if (visit(child))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
log_assert(type == AST_FUNCTION);
|
||||||
|
return visit(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<AstNode*, AstNode*> AstNode::get_tern_choice()
|
||||||
|
{
|
||||||
|
if (!children[0]->isConst())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
bool found_sure_true = false;
|
||||||
|
bool found_maybe_true = false;
|
||||||
|
|
||||||
|
if (children[0]->type == AST_CONSTANT)
|
||||||
|
for (auto &bit : children[0]->bits) {
|
||||||
|
if (bit == RTLIL::State::S1)
|
||||||
|
found_sure_true = true;
|
||||||
|
if (bit > RTLIL::State::S1)
|
||||||
|
found_maybe_true = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
found_sure_true = children[0]->asReal(true) != 0;
|
||||||
|
|
||||||
|
AstNode *choice = nullptr, *not_choice = nullptr;
|
||||||
|
if (found_sure_true)
|
||||||
|
choice = children[1], not_choice = children[2];
|
||||||
|
else if (!found_maybe_true)
|
||||||
|
choice = children[2], not_choice = children[1];
|
||||||
|
|
||||||
|
return {choice, not_choice};
|
||||||
|
}
|
||||||
|
|
||||||
YOSYS_NAMESPACE_END
|
YOSYS_NAMESPACE_END
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
module gate(
|
||||||
|
off, fib0, fib1, fib2, fib3, fib4, fib5, fib6, fib7, fib8, fib9
|
||||||
|
);
|
||||||
|
input wire signed [31:0] off;
|
||||||
|
|
||||||
|
function automatic blah(
|
||||||
|
input x
|
||||||
|
);
|
||||||
|
blah = x;
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function automatic integer fib(
|
||||||
|
input integer k
|
||||||
|
);
|
||||||
|
fib = k == 0
|
||||||
|
? 0
|
||||||
|
: k == 1
|
||||||
|
? 1
|
||||||
|
: fib(k - 1) + fib(k - 2);
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function automatic integer fib_wrap(
|
||||||
|
input integer k,
|
||||||
|
output integer o
|
||||||
|
);
|
||||||
|
o = off + fib(k);
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
output integer fib0;
|
||||||
|
output integer fib1;
|
||||||
|
output integer fib2;
|
||||||
|
output integer fib3;
|
||||||
|
output integer fib4;
|
||||||
|
output integer fib5;
|
||||||
|
output integer fib6;
|
||||||
|
output integer fib7;
|
||||||
|
output integer fib8;
|
||||||
|
output integer fib9;
|
||||||
|
|
||||||
|
initial begin : blk
|
||||||
|
integer unused;
|
||||||
|
unused = fib_wrap(0, fib0);
|
||||||
|
unused = fib_wrap(1, fib1);
|
||||||
|
unused = fib_wrap(2, fib2);
|
||||||
|
unused = fib_wrap(3, fib3);
|
||||||
|
unused = fib_wrap(4, fib4);
|
||||||
|
unused = fib_wrap(5, fib5);
|
||||||
|
unused = fib_wrap(6, fib6);
|
||||||
|
unused = fib_wrap(7, fib7);
|
||||||
|
unused = fib_wrap(8, fib8);
|
||||||
|
unused = fib_wrap(9, fib9);
|
||||||
|
end
|
||||||
|
endmodule
|
||||||
|
|
||||||
|
module gold(
|
||||||
|
off, fib0, fib1, fib2, fib3, fib4, fib5, fib6, fib7, fib8, fib9
|
||||||
|
);
|
||||||
|
input wire signed [31:0] off;
|
||||||
|
|
||||||
|
output integer fib0 = off + 0;
|
||||||
|
output integer fib1 = off + 1;
|
||||||
|
output integer fib2 = off + 1;
|
||||||
|
output integer fib3 = off + 2;
|
||||||
|
output integer fib4 = off + 3;
|
||||||
|
output integer fib5 = off + 5;
|
||||||
|
output integer fib6 = off + 8;
|
||||||
|
output integer fib7 = off + 13;
|
||||||
|
output integer fib8 = off + 21;
|
||||||
|
output integer fib9 = off + 34;
|
||||||
|
endmodule
|
|
@ -0,0 +1,6 @@
|
||||||
|
read_verilog fib_tern.v
|
||||||
|
hierarchy
|
||||||
|
proc
|
||||||
|
equiv_make gold gate equiv
|
||||||
|
equiv_simple
|
||||||
|
equiv_status -assert
|
Loading…
Reference in New Issue