mirror of https://github.com/YosysHQ/yosys.git
5671 lines
202 KiB
C++
5671 lines
202 KiB
C++
/*
|
|
* yosys -- Yosys Open SYnthesis Suite
|
|
*
|
|
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.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.
|
|
*
|
|
* ---
|
|
*
|
|
* This is the AST frontend library.
|
|
*
|
|
* The AST frontend library is not a frontend on it's own but provides a
|
|
* generic abstract syntax tree (AST) abstraction for HDL code and can be
|
|
* used by HDL frontends. See "ast.h" for an overview of the API and the
|
|
* Verilog frontend for an usage example.
|
|
*
|
|
*/
|
|
|
|
#include "kernel/log.h"
|
|
#include "libs/sha1/sha1.h"
|
|
#include "frontends/verilog/verilog_frontend.h"
|
|
#include "ast.h"
|
|
|
|
#include <sstream>
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
|
|
YOSYS_NAMESPACE_BEGIN
|
|
|
|
using namespace AST;
|
|
using namespace AST_INTERNAL;
|
|
|
|
// Process a format string and arguments for $display, $write, $sprintf, etc
|
|
|
|
std::string AstNode::process_format_str(const std::string &sformat, int next_arg, int stage, int width_hint, bool sign_hint) {
|
|
// Other arguments are placeholders. Process the string as we go through it
|
|
std::string sout;
|
|
for (size_t i = 0; i < sformat.length(); i++)
|
|
{
|
|
// format specifier
|
|
if (sformat[i] == '%')
|
|
{
|
|
// If there's no next character, that's a problem
|
|
if (i+1 >= sformat.length())
|
|
log_file_error(filename, location.first_line, "System task `%s' called with `%%' at end of string.\n", str.c_str());
|
|
|
|
char cformat = sformat[++i];
|
|
|
|
// %% is special, does not need a matching argument
|
|
if (cformat == '%')
|
|
{
|
|
sout += '%';
|
|
continue;
|
|
}
|
|
|
|
bool got_len = false;
|
|
bool got_zlen = false;
|
|
int len_value = 0;
|
|
|
|
while ('0' <= cformat && cformat <= '9')
|
|
{
|
|
if (!got_len && cformat == '0')
|
|
got_zlen = true;
|
|
|
|
got_len = true;
|
|
len_value = 10*len_value + (cformat - '0');
|
|
|
|
cformat = sformat[++i];
|
|
}
|
|
|
|
// Simplify the argument
|
|
AstNode *node_arg = nullptr;
|
|
|
|
// Everything from here on depends on the format specifier
|
|
switch (cformat)
|
|
{
|
|
case 's':
|
|
case 'S':
|
|
case 'd':
|
|
case 'D':
|
|
if (got_len && len_value != 0)
|
|
goto unsupported_format;
|
|
YS_FALLTHROUGH
|
|
case 'x':
|
|
case 'X':
|
|
if (next_arg >= GetSize(children))
|
|
log_file_error(filename, location.first_line, "Missing argument for %%%c format specifier in system task `%s'.\n",
|
|
cformat, str.c_str());
|
|
|
|
node_arg = children[next_arg++];
|
|
while (node_arg->simplify(true, false, false, stage, width_hint, sign_hint, false)) { }
|
|
if (node_arg->type != AST_CONSTANT)
|
|
log_file_error(filename, location.first_line, "Failed to evaluate system task `%s' with non-constant argument.\n", str.c_str());
|
|
break;
|
|
|
|
case 'm':
|
|
case 'M':
|
|
if (got_len)
|
|
goto unsupported_format;
|
|
break;
|
|
|
|
case 'l':
|
|
case 'L':
|
|
if (got_len)
|
|
goto unsupported_format;
|
|
break;
|
|
|
|
default:
|
|
unsupported_format:
|
|
log_file_error(filename, location.first_line, "System task `%s' called with invalid/unsupported format specifier.\n", str.c_str());
|
|
break;
|
|
}
|
|
|
|
switch (cformat)
|
|
{
|
|
case 's':
|
|
case 'S':
|
|
sout += node_arg->bitsAsConst().decode_string();
|
|
break;
|
|
|
|
case 'd':
|
|
case 'D':
|
|
sout += stringf("%d", node_arg->bitsAsConst().as_int());
|
|
break;
|
|
|
|
case 'x':
|
|
case 'X':
|
|
{
|
|
Const val = node_arg->bitsAsConst();
|
|
|
|
while (GetSize(val) % 4 != 0)
|
|
val.bits.push_back(State::S0);
|
|
|
|
int len = GetSize(val) / 4;
|
|
for (int i = len; i < len_value; i++)
|
|
sout += got_zlen ? '0' : ' ';
|
|
|
|
for (int i = len-1; i >= 0; i--) {
|
|
Const digit = val.extract(4*i, 4);
|
|
if (digit.is_fully_def())
|
|
sout += stringf(cformat == 'x' ? "%x" : "%X", digit.as_int());
|
|
else
|
|
sout += cformat == 'x' ? "x" : "X";
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'm':
|
|
case 'M':
|
|
sout += log_id(current_module->name);
|
|
break;
|
|
|
|
case 'l':
|
|
case 'L':
|
|
sout += log_id(current_module->name);
|
|
break;
|
|
|
|
default:
|
|
log_abort();
|
|
}
|
|
}
|
|
|
|
// not a format specifier
|
|
else
|
|
sout += sformat[i];
|
|
}
|
|
return sout;
|
|
}
|
|
|
|
|
|
void AstNode::annotateTypedEnums(AstNode *template_node)
|
|
{
|
|
//check if enum
|
|
if (template_node->attributes.count(ID::enum_type)) {
|
|
//get reference to enum node:
|
|
std::string enum_type = template_node->attributes[ID::enum_type]->str.c_str();
|
|
// log("enum_type=%s (count=%lu)\n", enum_type.c_str(), current_scope.count(enum_type));
|
|
// log("current scope:\n");
|
|
// for (auto &it : current_scope)
|
|
// log(" %s\n", it.first.c_str());
|
|
log_assert(current_scope.count(enum_type) == 1);
|
|
AstNode *enum_node = current_scope.at(enum_type);
|
|
log_assert(enum_node->type == AST_ENUM);
|
|
while (enum_node->simplify(true, false, false, 1, -1, false, true)) { }
|
|
//get width from 1st enum item:
|
|
log_assert(enum_node->children.size() >= 1);
|
|
AstNode *enum_item0 = enum_node->children[0];
|
|
log_assert(enum_item0->type == AST_ENUM_ITEM);
|
|
int width;
|
|
if (!enum_item0->range_valid)
|
|
width = 1;
|
|
else if (enum_item0->range_swapped)
|
|
width = enum_item0->range_right - enum_item0->range_left + 1;
|
|
else
|
|
width = enum_item0->range_left - enum_item0->range_right + 1;
|
|
log_assert(width > 0);
|
|
//add declared enum items:
|
|
for (auto enum_item : enum_node->children){
|
|
log_assert(enum_item->type == AST_ENUM_ITEM);
|
|
//get is_signed
|
|
bool is_signed;
|
|
if (enum_item->children.size() == 1){
|
|
is_signed = false;
|
|
} else if (enum_item->children.size() == 2){
|
|
log_assert(enum_item->children[1]->type == AST_RANGE);
|
|
is_signed = enum_item->children[1]->is_signed;
|
|
} else {
|
|
log_error("enum_item children size==%lu, expected 1 or 2 for %s (%s)\n",
|
|
enum_item->children.size(),
|
|
enum_item->str.c_str(), enum_node->str.c_str()
|
|
);
|
|
}
|
|
//start building attribute string
|
|
std::string enum_item_str = "\\enum_value_";
|
|
//get enum item value
|
|
if(enum_item->children[0]->type != AST_CONSTANT){
|
|
log_error("expected const, got %s for %s (%s)\n",
|
|
type2str(enum_item->children[0]->type).c_str(),
|
|
enum_item->str.c_str(), enum_node->str.c_str()
|
|
);
|
|
}
|
|
RTLIL::Const val = enum_item->children[0]->bitsAsConst(width, is_signed);
|
|
enum_item_str.append(val.as_string());
|
|
//set attribute for available val to enum item name mappings
|
|
attributes[enum_item_str.c_str()] = mkconst_str(enum_item->str);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool name_has_dot(const std::string &name, std::string &struct_name)
|
|
{
|
|
// check if plausible struct member name \sss.mmm
|
|
std::string::size_type pos;
|
|
if (name.substr(0, 1) == "\\" && (pos = name.find('.', 0)) != std::string::npos) {
|
|
struct_name = name.substr(0, pos);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static AstNode *make_range(int left, int right, bool is_signed = false)
|
|
{
|
|
// generate a pre-validated range node for a fixed signal range.
|
|
auto range = new AstNode(AST_RANGE);
|
|
range->range_left = left;
|
|
range->range_right = right;
|
|
range->range_valid = true;
|
|
range->children.push_back(AstNode::mkconst_int(left, true));
|
|
range->children.push_back(AstNode::mkconst_int(right, true));
|
|
range->is_signed = is_signed;
|
|
return range;
|
|
}
|
|
|
|
static int range_width(AstNode *node, AstNode *rnode)
|
|
{
|
|
log_assert(rnode->type==AST_RANGE);
|
|
if (!rnode->range_valid) {
|
|
log_file_error(node->filename, node->location.first_line, "Size must be constant in packed struct/union member %s\n", node->str.c_str());
|
|
|
|
}
|
|
// note: range swapping has already been checked for
|
|
return rnode->range_left - rnode->range_right + 1;
|
|
}
|
|
|
|
[[noreturn]] static void struct_array_packing_error(AstNode *node)
|
|
{
|
|
log_file_error(node->filename, node->location.first_line, "Unpacked array in packed struct/union member %s\n", node->str.c_str());
|
|
}
|
|
|
|
static void save_struct_range_dimensions(AstNode *node, AstNode *rnode)
|
|
{
|
|
node->multirange_dimensions.push_back(rnode->range_right);
|
|
node->multirange_dimensions.push_back(range_width(node, rnode));
|
|
node->multirange_swapped.push_back(rnode->range_swapped);
|
|
}
|
|
|
|
static int get_struct_range_offset(AstNode *node, int dimension)
|
|
{
|
|
return node->multirange_dimensions[2*dimension];
|
|
}
|
|
|
|
static int get_struct_range_width(AstNode *node, int dimension)
|
|
{
|
|
return node->multirange_dimensions[2*dimension + 1];
|
|
}
|
|
|
|
static int size_packed_struct(AstNode *snode, int base_offset)
|
|
{
|
|
// Struct members will be laid out in the structure contiguously from left to right.
|
|
// Union members all have zero offset from the start of the union.
|
|
// Determine total packed size and assign offsets. Store these in the member node.
|
|
bool is_union = (snode->type == AST_UNION);
|
|
int offset = 0;
|
|
int packed_width = -1;
|
|
// examine members from last to first
|
|
for (auto it = snode->children.rbegin(); it != snode->children.rend(); ++it) {
|
|
auto node = *it;
|
|
int width;
|
|
if (node->type == AST_STRUCT || node->type == AST_UNION) {
|
|
// embedded struct or union
|
|
width = size_packed_struct(node, base_offset + offset);
|
|
// set range of struct
|
|
node->range_right = base_offset + offset;
|
|
node->range_left = base_offset + offset + width - 1;
|
|
node->range_valid = true;
|
|
}
|
|
else {
|
|
log_assert(node->type == AST_STRUCT_ITEM);
|
|
if (node->children.size() > 0 && node->children[0]->type == AST_RANGE) {
|
|
// member width e.g. bit [7:0] a
|
|
width = range_width(node, node->children[0]);
|
|
if (node->children.size() == 2) {
|
|
// Unpacked array. Note that this is a Yosys extension; only packed data types
|
|
// and integer data types are allowed in packed structs / unions in SystemVerilog.
|
|
if (node->children[1]->type == AST_RANGE) {
|
|
// Unpacked array, e.g. bit [63:0] a [0:3]
|
|
auto rnode = node->children[1];
|
|
if (rnode->children.size() == 1) {
|
|
// C-style array size, e.g. bit [63:0] a [4]
|
|
node->multirange_dimensions.push_back(0);
|
|
node->multirange_dimensions.push_back(rnode->range_left);
|
|
node->multirange_swapped.push_back(true);
|
|
width *= rnode->range_left;
|
|
} else {
|
|
save_struct_range_dimensions(node, rnode);
|
|
width *= range_width(node, rnode);
|
|
}
|
|
save_struct_range_dimensions(node, node->children[0]);
|
|
}
|
|
else {
|
|
// The Yosys extension for unpacked arrays in packed structs / unions
|
|
// only supports memories, i.e. e.g. logic [7:0] a [256] - see above.
|
|
struct_array_packing_error(node);
|
|
}
|
|
} else {
|
|
// Vector
|
|
save_struct_range_dimensions(node, node->children[0]);
|
|
}
|
|
// range nodes are now redundant
|
|
for (AstNode *child : node->children)
|
|
delete child;
|
|
node->children.clear();
|
|
}
|
|
else if (node->children.size() > 0 && node->children[0]->type == AST_MULTIRANGE) {
|
|
// Packed array, e.g. bit [3:0][63:0] a
|
|
if (node->children.size() != 1) {
|
|
// The Yosys extension for unpacked arrays in packed structs / unions
|
|
// only supports memories, i.e. e.g. logic [7:0] a [256] - see above.
|
|
struct_array_packing_error(node);
|
|
}
|
|
width = 1;
|
|
for (auto rnode : node->children[0]->children) {
|
|
save_struct_range_dimensions(node, rnode);
|
|
width *= range_width(node, rnode);
|
|
}
|
|
// range nodes are now redundant
|
|
for (AstNode *child : node->children)
|
|
delete child;
|
|
node->children.clear();
|
|
}
|
|
else if (node->range_left < 0) {
|
|
// 1 bit signal: bit, logic or reg
|
|
width = 1;
|
|
}
|
|
else {
|
|
// already resolved and compacted
|
|
width = node->range_left - node->range_right + 1;
|
|
}
|
|
if (is_union) {
|
|
node->range_right = base_offset;
|
|
node->range_left = base_offset + width - 1;
|
|
}
|
|
else {
|
|
node->range_right = base_offset + offset;
|
|
node->range_left = base_offset + offset + width - 1;
|
|
}
|
|
node->range_valid = true;
|
|
}
|
|
if (is_union) {
|
|
// check that all members have the same size
|
|
if (packed_width == -1) {
|
|
// first member
|
|
packed_width = width;
|
|
}
|
|
else {
|
|
if (packed_width != width) {
|
|
|
|
log_file_error(node->filename, node->location.first_line, "member %s of a packed union has %d bits, expecting %d\n", node->str.c_str(), width, packed_width);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
offset += width;
|
|
}
|
|
}
|
|
return (is_union ? packed_width : offset);
|
|
}
|
|
|
|
[[noreturn]] static void struct_op_error(AstNode *node)
|
|
{
|
|
log_file_error(node->filename, node->location.first_line, "Unsupported operation for struct/union member %s\n", node->str.c_str()+1);
|
|
}
|
|
|
|
static AstNode *node_int(int ival)
|
|
{
|
|
return AstNode::mkconst_int(ival, true);
|
|
}
|
|
|
|
static AstNode *multiply_by_const(AstNode *expr_node, int stride)
|
|
{
|
|
return new AstNode(AST_MUL, expr_node, node_int(stride));
|
|
}
|
|
|
|
static AstNode *normalize_struct_index(AstNode *expr, AstNode *member_node, int dimension)
|
|
{
|
|
expr = expr->clone();
|
|
|
|
int offset = get_struct_range_offset(member_node, dimension);
|
|
if (offset) {
|
|
expr = new AstNode(AST_SUB, expr, node_int(offset));
|
|
}
|
|
|
|
if (member_node->multirange_swapped[dimension]) {
|
|
// The dimension has swapped range; swap index into the struct accordingly.
|
|
int msb = get_struct_range_width(member_node, dimension) - 1;
|
|
expr = new AstNode(AST_SUB, node_int(msb), expr);
|
|
}
|
|
|
|
return expr;
|
|
}
|
|
|
|
static AstNode *struct_index_lsb_offset(AstNode *lsb_offset, AstNode *rnode, AstNode *member_node, int dimension, int &stride)
|
|
{
|
|
stride /= get_struct_range_width(member_node, dimension);
|
|
auto right = normalize_struct_index(rnode->children.back(), member_node, dimension);
|
|
auto offset = stride > 1 ? multiply_by_const(right, stride) : right;
|
|
return lsb_offset ? new AstNode(AST_ADD, lsb_offset, offset) : offset;
|
|
}
|
|
|
|
static AstNode *struct_index_msb_offset(AstNode *lsb_offset, AstNode *rnode, AstNode *member_node, int dimension, int stride)
|
|
{
|
|
log_assert(rnode->children.size() <= 2);
|
|
|
|
// Offset to add to LSB
|
|
AstNode *offset;
|
|
if (rnode->children.size() == 1) {
|
|
// Index, e.g. s.a[i]
|
|
offset = node_int(stride - 1);
|
|
}
|
|
else {
|
|
// rnode->children.size() == 2
|
|
// Slice, e.g. s.a[i:j]
|
|
auto left = normalize_struct_index(rnode->children[0], member_node, dimension);
|
|
auto right = normalize_struct_index(rnode->children[1], member_node, dimension);
|
|
offset = new AstNode(AST_SUB, left, right);
|
|
if (stride > 1) {
|
|
// offset = (msb - lsb + 1)*stride - 1
|
|
auto slice_width = new AstNode(AST_ADD, offset, node_int(1));
|
|
offset = new AstNode(AST_SUB, multiply_by_const(slice_width, stride), node_int(1));
|
|
}
|
|
}
|
|
|
|
return new AstNode(AST_ADD, lsb_offset, offset);
|
|
}
|
|
|
|
|
|
AstNode *AST::make_struct_member_range(AstNode *node, AstNode *member_node)
|
|
{
|
|
// Work out the range in the packed array that corresponds to a struct member
|
|
// taking into account any range operations applicable to the current node
|
|
// such as array indexing or slicing
|
|
int range_left = member_node->range_left;
|
|
int range_right = member_node->range_right;
|
|
if (node->children.empty()) {
|
|
// no range operations apply, return the whole width
|
|
return make_range(range_left - range_right, 0);
|
|
}
|
|
|
|
if (node->children.size() != 1) {
|
|
struct_op_error(node);
|
|
}
|
|
|
|
// Range operations
|
|
auto rnode = node->children[0];
|
|
AstNode *lsb_offset = NULL;
|
|
int stride = range_left - range_right + 1;
|
|
size_t i = 0;
|
|
|
|
// Calculate LSB offset for the final index / slice
|
|
if (rnode->type == AST_RANGE) {
|
|
lsb_offset = struct_index_lsb_offset(lsb_offset, rnode, member_node, i, stride);
|
|
}
|
|
else if (rnode->type == AST_MULTIRANGE) {
|
|
// Add offset for each dimension
|
|
auto mrnode = rnode;
|
|
for (i = 0; i < mrnode->children.size(); i++) {
|
|
rnode = mrnode->children[i];
|
|
lsb_offset = struct_index_lsb_offset(lsb_offset, rnode, member_node, i, stride);
|
|
}
|
|
i--; // Step back to the final index / slice
|
|
}
|
|
else {
|
|
struct_op_error(node);
|
|
}
|
|
|
|
// Calculate MSB offset for the final index / slice
|
|
auto msb_offset = struct_index_msb_offset(lsb_offset->clone(), rnode, member_node, i, stride);
|
|
|
|
return new AstNode(AST_RANGE, msb_offset, lsb_offset);
|
|
}
|
|
|
|
AstNode *AST::get_struct_member(const AstNode *node)
|
|
{
|
|
AST::AstNode *member_node;
|
|
if (node->attributes.count(ID::wiretype) && (member_node = node->attributes.at(ID::wiretype)) &&
|
|
(member_node->type == AST_STRUCT_ITEM || member_node->type == AST_STRUCT || member_node->type == AST_UNION))
|
|
{
|
|
return member_node;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void add_members_to_scope(AstNode *snode, std::string name)
|
|
{
|
|
// add all the members in a struct or union to local scope
|
|
// in case later referenced in assignments
|
|
log_assert(snode->type==AST_STRUCT || snode->type==AST_UNION);
|
|
for (auto *node : snode->children) {
|
|
auto member_name = name + "." + node->str;
|
|
current_scope[member_name] = node;
|
|
if (node->type != AST_STRUCT_ITEM) {
|
|
// embedded struct or union
|
|
add_members_to_scope(node, name + "." + node->str);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int get_max_offset(AstNode *node)
|
|
{
|
|
// get the width from the MS member in the struct
|
|
// as members are laid out from left to right in the packed wire
|
|
log_assert(node->type==AST_STRUCT || node->type==AST_UNION);
|
|
while (node->type != AST_STRUCT_ITEM) {
|
|
node = node->children[0];
|
|
}
|
|
return node->range_left;
|
|
}
|
|
|
|
static AstNode *make_packed_struct(AstNode *template_node, std::string &name)
|
|
{
|
|
// create a wire for the packed struct
|
|
auto wnode = new AstNode(AST_WIRE);
|
|
wnode->str = name;
|
|
wnode->is_logic = true;
|
|
wnode->range_valid = true;
|
|
wnode->is_signed = template_node->is_signed;
|
|
int offset = get_max_offset(template_node);
|
|
auto range = make_range(offset, 0);
|
|
wnode->children.push_back(range);
|
|
// make sure this node is the one in scope for this name
|
|
current_scope[name] = wnode;
|
|
// add all the struct members to scope under the wire's name
|
|
add_members_to_scope(template_node, name);
|
|
return wnode;
|
|
}
|
|
|
|
// check if a node or its children contains an assignment to the given variable
|
|
static bool node_contains_assignment_to(const AstNode* node, const AstNode* var)
|
|
{
|
|
if (node->type == AST_ASSIGN_EQ || node->type == AST_ASSIGN_LE) {
|
|
// current node is iteslf an assignment
|
|
log_assert(node->children.size() >= 2);
|
|
const AstNode* lhs = node->children[0];
|
|
if (lhs->type == AST_IDENTIFIER && lhs->str == var->str)
|
|
return false;
|
|
}
|
|
for (const AstNode* child : node->children) {
|
|
// if this child shadows the given variable
|
|
if (child != var && child->str == var->str && child->type == AST_WIRE)
|
|
break; // skip the remainder of this block/scope
|
|
// depth-first short circuit
|
|
if (!node_contains_assignment_to(child, var))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static std::string prefix_id(const std::string &prefix, const std::string &str)
|
|
{
|
|
log_assert(!prefix.empty() && (prefix.front() == '$' || prefix.front() == '\\'));
|
|
log_assert(!str.empty() && (str.front() == '$' || str.front() == '\\'));
|
|
log_assert(prefix.back() == '.');
|
|
if (str.front() == '\\')
|
|
return prefix + str.substr(1);
|
|
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;
|
|
}
|
|
|
|
enum class IdentUsage {
|
|
NotReferenced, // target variable is neither read or written in the block
|
|
Assigned, // target variable is always assigned before use
|
|
SyncRequired, // target variable may be used before it has been assigned
|
|
};
|
|
|
|
// determines whether a local variable a block is always assigned before it is
|
|
// used, meaning the nosync attribute can automatically be added to that
|
|
// variable
|
|
static IdentUsage always_asgn_before_use(const AstNode *node, const std::string &target)
|
|
{
|
|
// This variable has been referenced before it has necessarily been assigned
|
|
// a value in this procedure.
|
|
if (node->type == AST_IDENTIFIER && node->str == target)
|
|
return IdentUsage::SyncRequired;
|
|
|
|
// For case statements (which are also used for if/else), we check each
|
|
// possible branch. If the variable is assigned in all branches, then it is
|
|
// assigned, and a sync isn't required. If it used before assignment in any
|
|
// branch, then a sync is required.
|
|
if (node->type == AST_CASE) {
|
|
bool all_defined = true;
|
|
bool any_used = false;
|
|
bool has_default = false;
|
|
for (const AstNode *child : node->children) {
|
|
if (child->type == AST_COND && child->children.at(0)->type == AST_DEFAULT)
|
|
has_default = true;
|
|
IdentUsage nested = always_asgn_before_use(child, target);
|
|
if (nested != IdentUsage::Assigned && child->type == AST_COND)
|
|
all_defined = false;
|
|
if (nested == IdentUsage::SyncRequired)
|
|
any_used = true;
|
|
}
|
|
if (any_used)
|
|
return IdentUsage::SyncRequired;
|
|
else if (all_defined && has_default)
|
|
return IdentUsage::Assigned;
|
|
else
|
|
return IdentUsage::NotReferenced;
|
|
}
|
|
|
|
// Check if this is an assignment to the target variable. For simplicity, we
|
|
// don't analyze sub-ranges of the variable.
|
|
if (node->type == AST_ASSIGN_EQ) {
|
|
const AstNode *ident = node->children.at(0);
|
|
if (ident->type == AST_IDENTIFIER && ident->str == target)
|
|
return IdentUsage::Assigned;
|
|
}
|
|
|
|
for (const AstNode *child : node->children) {
|
|
IdentUsage nested = always_asgn_before_use(child, target);
|
|
if (nested != IdentUsage::NotReferenced)
|
|
return nested;
|
|
}
|
|
return IdentUsage::NotReferenced;
|
|
}
|
|
|
|
static const std::string auto_nosync_prefix = "\\AutoNosync";
|
|
|
|
// mark a local variable in an always_comb block for automatic nosync
|
|
// consideration
|
|
static void mark_auto_nosync(AstNode *block, const AstNode *wire)
|
|
{
|
|
log_assert(block->type == AST_BLOCK);
|
|
log_assert(wire->type == AST_WIRE);
|
|
block->attributes[auto_nosync_prefix + wire->str] = AstNode::mkconst_int(1,
|
|
false);
|
|
}
|
|
|
|
// block names can be prefixed with an explicit scope during elaboration
|
|
static bool is_autonamed_block(const std::string &str) {
|
|
size_t last_dot = str.rfind('.');
|
|
// unprefixed names: autonamed if the first char is a dollar sign
|
|
if (last_dot == std::string::npos)
|
|
return str.at(0) == '$'; // e.g., `$fordecl_block$1`
|
|
// prefixed names: autonamed if the final chunk begins with a dollar sign
|
|
return str.rfind(".$") == last_dot; // e.g., `\foo.bar.$fordecl_block$1`
|
|
}
|
|
|
|
// check a procedural block for auto-nosync markings, remove them, and add
|
|
// nosync to local variables as necessary
|
|
static void check_auto_nosync(AstNode *node)
|
|
{
|
|
std::vector<RTLIL::IdString> attrs_to_drop;
|
|
for (const auto& elem : node->attributes) {
|
|
// skip attributes that don't begin with the prefix
|
|
if (elem.first.compare(0, auto_nosync_prefix.size(),
|
|
auto_nosync_prefix.c_str()))
|
|
continue;
|
|
|
|
// delete and remove the attribute once we're done iterating
|
|
attrs_to_drop.push_back(elem.first);
|
|
|
|
// find the wire based on the attribute
|
|
std::string wire_name = elem.first.substr(auto_nosync_prefix.size());
|
|
auto it = current_scope.find(wire_name);
|
|
if (it == current_scope.end())
|
|
continue;
|
|
|
|
// analyze the usage of the local variable in this block
|
|
IdentUsage ident_usage = always_asgn_before_use(node, wire_name);
|
|
if (ident_usage != IdentUsage::Assigned)
|
|
continue;
|
|
|
|
// mark the wire with `nosync`
|
|
AstNode *wire = it->second;
|
|
log_assert(wire->type == AST_WIRE);
|
|
wire->attributes[ID::nosync] = AstNode::mkconst_int(1, false);
|
|
}
|
|
|
|
// remove the attributes we've "consumed"
|
|
for (const RTLIL::IdString &str : attrs_to_drop) {
|
|
auto it = node->attributes.find(str);
|
|
delete it->second;
|
|
node->attributes.erase(it);
|
|
}
|
|
|
|
// check local variables in any nested blocks
|
|
for (AstNode *child : node->children)
|
|
check_auto_nosync(child);
|
|
}
|
|
|
|
// 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().
|
|
//
|
|
// this function also does all name resolving and sets the id2ast member of all
|
|
// nodes that link to a different node using names and lexical scoping.
|
|
bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage, int width_hint, bool sign_hint, bool in_param)
|
|
{
|
|
static int recursion_counter = 0;
|
|
static bool deep_recursion_warning = false;
|
|
|
|
if (recursion_counter++ == 1000 && deep_recursion_warning) {
|
|
log_warning("Deep recursion in AST simplifier.\nDoes this design contain overly long or deeply nested expressions, or excessive recursion?\n");
|
|
deep_recursion_warning = false;
|
|
}
|
|
|
|
static bool unevaluated_tern_branch = false;
|
|
|
|
AstNode *newNode = NULL;
|
|
bool did_something = false;
|
|
|
|
#if 0
|
|
log("-------------\n");
|
|
log("AST simplify[%d] depth %d at %s:%d on %s %p:\n", stage, recursion_counter, filename.c_str(), location.first_line, type2str(type).c_str(), this);
|
|
log("const_fold=%d, at_zero=%d, in_lvalue=%d, stage=%d, width_hint=%d, sign_hint=%d, in_param=%d\n",
|
|
int(const_fold), int(at_zero), int(in_lvalue), int(stage), int(width_hint), int(sign_hint), int(in_param));
|
|
// dumpAst(NULL, "> ");
|
|
#endif
|
|
|
|
if (stage == 0)
|
|
{
|
|
log_assert(type == AST_MODULE || type == AST_INTERFACE);
|
|
|
|
deep_recursion_warning = true;
|
|
while (simplify(const_fold, at_zero, in_lvalue, 1, width_hint, sign_hint, in_param)) { }
|
|
|
|
if (!flag_nomem2reg && !get_bool_attribute(ID::nomem2reg))
|
|
{
|
|
dict<AstNode*, pool<std::string>> mem2reg_places;
|
|
dict<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);
|
|
|
|
pool<AstNode*> mem2reg_set;
|
|
for (auto &it : mem2reg_candidates)
|
|
{
|
|
AstNode *mem = it.first;
|
|
uint32_t memflags = it.second;
|
|
bool this_nomeminit = flag_nomeminit;
|
|
log_assert((memflags & ~0x00ffff00) == 0);
|
|
|
|
if (mem->get_bool_attribute(ID::nomem2reg))
|
|
continue;
|
|
|
|
if (mem->get_bool_attribute(ID::nomeminit) || get_bool_attribute(ID::nomeminit))
|
|
this_nomeminit = true;
|
|
|
|
if (memflags & AstNode::MEM2REG_FL_FORCED)
|
|
goto silent_activate;
|
|
|
|
if (memflags & AstNode::MEM2REG_FL_EQ2)
|
|
goto verbose_activate;
|
|
|
|
if (memflags & AstNode::MEM2REG_FL_SET_ASYNC)
|
|
goto verbose_activate;
|
|
|
|
if ((memflags & AstNode::MEM2REG_FL_SET_INIT) && (memflags & AstNode::MEM2REG_FL_SET_ELSE) && this_nomeminit)
|
|
goto verbose_activate;
|
|
|
|
if (memflags & AstNode::MEM2REG_FL_CMPLX_LHS)
|
|
goto verbose_activate;
|
|
|
|
if ((memflags & AstNode::MEM2REG_FL_CONST_LHS) && !(memflags & AstNode::MEM2REG_FL_VAR_LHS))
|
|
goto verbose_activate;
|
|
|
|
// log("Note: Not replacing memory %s with list of registers (flags=0x%08lx).\n", mem->str.c_str(), long(memflags));
|
|
continue;
|
|
|
|
verbose_activate:
|
|
if (mem2reg_set.count(mem) == 0) {
|
|
std::string message = stringf("Replacing memory %s with list of registers.", mem->str.c_str());
|
|
bool first_element = true;
|
|
for (auto &place : mem2reg_places[it.first]) {
|
|
message += stringf("%s%s", first_element ? " See " : ", ", place.c_str());
|
|
first_element = false;
|
|
}
|
|
log_warning("%s\n", message.c_str());
|
|
}
|
|
|
|
silent_activate:
|
|
// log("Note: Replacing memory %s with list of registers (flags=0x%08lx).\n", mem->str.c_str(), long(memflags));
|
|
mem2reg_set.insert(mem);
|
|
}
|
|
|
|
for (auto node : mem2reg_set)
|
|
{
|
|
int mem_width, mem_size, addr_bits;
|
|
node->meminfo(mem_width, mem_size, addr_bits);
|
|
|
|
int data_range_left = node->children[0]->range_left;
|
|
int data_range_right = node->children[0]->range_right;
|
|
|
|
if (node->children[0]->range_swapped)
|
|
std::swap(data_range_left, data_range_right);
|
|
|
|
for (int i = 0; i < mem_size; i++) {
|
|
AstNode *reg = new AstNode(AST_WIRE, new AstNode(AST_RANGE,
|
|
mkconst_int(data_range_left, true), mkconst_int(data_range_right, true)));
|
|
reg->str = stringf("%s[%d]", node->str.c_str(), i);
|
|
reg->is_reg = true;
|
|
reg->is_signed = node->is_signed;
|
|
for (auto &it : node->attributes)
|
|
if (it.first != ID::mem2reg)
|
|
reg->attributes.emplace(it.first, it.second->clone());
|
|
reg->filename = node->filename;
|
|
reg->location = node->location;
|
|
children.push_back(reg);
|
|
while (reg->simplify(true, false, false, 1, -1, false, false)) { }
|
|
}
|
|
}
|
|
|
|
AstNode *async_block = NULL;
|
|
while (mem2reg_as_needed_pass2(mem2reg_set, this, NULL, async_block)) { }
|
|
|
|
vector<AstNode*> delnodes;
|
|
mem2reg_remove(mem2reg_set, delnodes);
|
|
|
|
for (auto node : delnodes)
|
|
delete node;
|
|
}
|
|
|
|
while (simplify(const_fold, at_zero, in_lvalue, 2, width_hint, sign_hint, in_param)) { }
|
|
recursion_counter--;
|
|
return false;
|
|
}
|
|
|
|
current_filename = filename;
|
|
|
|
// we do not look inside a task or function
|
|
// (but as soon as a task or function is instantiated we process the generated AST as usual)
|
|
if (type == AST_FUNCTION || type == AST_TASK) {
|
|
recursion_counter--;
|
|
return false;
|
|
}
|
|
|
|
// deactivate all calls to non-synthesis system tasks
|
|
// note that $display, $finish, and $stop are used for synthesis-time DRC so they're not in this list
|
|
if ((type == AST_FCALL || type == AST_TCALL) && (str == "$strobe" || str == "$monitor" || str == "$time" ||
|
|
str == "$dumpfile" || str == "$dumpvars" || str == "$dumpon" || str == "$dumpoff" || str == "$dumpall")) {
|
|
log_file_warning(filename, location.first_line, "Ignoring call to system %s %s.\n", type == AST_FCALL ? "function" : "task", str.c_str());
|
|
delete_children();
|
|
str = std::string();
|
|
}
|
|
|
|
if ((type == AST_TCALL) && (str == "$display" || str == "$write") && (!current_always || current_always->type != AST_INITIAL)) {
|
|
log_file_warning(filename, location.first_line, "System task `%s' outside initial block is unsupported.\n", str.c_str());
|
|
delete_children();
|
|
str = std::string();
|
|
}
|
|
|
|
// print messages if this a call to $display() or $write()
|
|
// This code implements only a small subset of Verilog-2005 $display() format specifiers,
|
|
// but should be good enough for most uses
|
|
if ((type == AST_TCALL) && ((str == "$display") || (str == "$write")))
|
|
{
|
|
int nargs = GetSize(children);
|
|
if (nargs < 1)
|
|
log_file_error(filename, location.first_line, "System task `%s' got %d arguments, expected >= 1.\n",
|
|
str.c_str(), int(children.size()));
|
|
|
|
// First argument is the format string
|
|
AstNode *node_string = children[0];
|
|
while (node_string->simplify(true, false, false, stage, width_hint, sign_hint, false)) { }
|
|
if (node_string->type != AST_CONSTANT)
|
|
log_file_error(filename, location.first_line, "Failed to evaluate system task `%s' with non-constant 1st argument.\n", str.c_str());
|
|
std::string sformat = node_string->bitsAsConst().decode_string();
|
|
std::string sout = process_format_str(sformat, 1, stage, width_hint, sign_hint);
|
|
// Finally, print the message (only include a \n for $display, not for $write)
|
|
log("%s", sout.c_str());
|
|
if (str == "$display")
|
|
log("\n");
|
|
delete_children();
|
|
str = std::string();
|
|
}
|
|
|
|
// activate const folding if this is anything that must be evaluated statically (ranges, parameters, attributes, etc.)
|
|
if (type == AST_WIRE || type == AST_PARAMETER || type == AST_LOCALPARAM || type == AST_ENUM_ITEM || type == AST_DEFPARAM || type == AST_PARASET || type == AST_RANGE || type == AST_PREFIX || type == AST_TYPEDEF)
|
|
const_fold = true;
|
|
if (type == AST_IDENTIFIER && current_scope.count(str) > 0 && (current_scope[str]->type == AST_PARAMETER || current_scope[str]->type == AST_LOCALPARAM || current_scope[str]->type == AST_ENUM_ITEM))
|
|
const_fold = true;
|
|
|
|
// in certain cases a function must be evaluated constant. this is what in_param controls.
|
|
if (type == AST_PARAMETER || type == AST_LOCALPARAM || type == AST_DEFPARAM || type == AST_PARASET || type == AST_PREFIX)
|
|
in_param = true;
|
|
|
|
std::map<std::string, AstNode*> backup_scope;
|
|
|
|
// create name resolution entries for all objects with names
|
|
// also merge multiple declarations for the same wire (e.g. "output foobar; reg foobar;")
|
|
if (type == AST_MODULE || type == AST_INTERFACE) {
|
|
current_scope.clear();
|
|
std::set<std::string> existing;
|
|
int counter = 0;
|
|
label_genblks(existing, counter);
|
|
std::map<std::string, AstNode*> this_wire_scope;
|
|
for (size_t i = 0; i < children.size(); i++) {
|
|
AstNode *node = children[i];
|
|
|
|
if (node->type == AST_WIRE) {
|
|
if (node->children.size() == 1 && node->children[0]->type == AST_RANGE) {
|
|
for (auto c : node->children[0]->children) {
|
|
if (!c->is_simple_const_expr()) {
|
|
if (attributes.count(ID::dynports))
|
|
delete attributes.at(ID::dynports);
|
|
attributes[ID::dynports] = AstNode::mkconst_int(1, true);
|
|
}
|
|
}
|
|
}
|
|
if (this_wire_scope.count(node->str) > 0) {
|
|
AstNode *first_node = this_wire_scope[node->str];
|
|
if (first_node->is_input && node->is_reg)
|
|
goto wires_are_incompatible;
|
|
if (!node->is_input && !node->is_output && node->is_reg && node->children.size() == 0)
|
|
goto wires_are_compatible;
|
|
if (first_node->children.size() == 0 && node->children.size() == 1 && node->children[0]->type == AST_RANGE) {
|
|
AstNode *r = node->children[0];
|
|
if (r->range_valid && r->range_left == 0 && r->range_right == 0) {
|
|
delete r;
|
|
node->children.pop_back();
|
|
}
|
|
}
|
|
if (first_node->children.size() != node->children.size())
|
|
goto wires_are_incompatible;
|
|
for (size_t j = 0; j < node->children.size(); j++) {
|
|
AstNode *n1 = first_node->children[j], *n2 = node->children[j];
|
|
if (n1->type == AST_RANGE && n2->type == AST_RANGE && n1->range_valid && n2->range_valid) {
|
|
if (n1->range_left != n2->range_left)
|
|
goto wires_are_incompatible;
|
|
if (n1->range_right != n2->range_right)
|
|
goto wires_are_incompatible;
|
|
} else if (*n1 != *n2)
|
|
goto wires_are_incompatible;
|
|
}
|
|
if (first_node->range_left != node->range_left)
|
|
goto wires_are_incompatible;
|
|
if (first_node->range_right != node->range_right)
|
|
goto wires_are_incompatible;
|
|
if (first_node->port_id == 0 && (node->is_input || node->is_output))
|
|
goto wires_are_incompatible;
|
|
wires_are_compatible:
|
|
if (node->is_input)
|
|
first_node->is_input = true;
|
|
if (node->is_output)
|
|
first_node->is_output = true;
|
|
if (node->is_reg)
|
|
first_node->is_reg = true;
|
|
if (node->is_logic)
|
|
first_node->is_logic = true;
|
|
if (node->is_signed)
|
|
first_node->is_signed = true;
|
|
for (auto &it : node->attributes) {
|
|
if (first_node->attributes.count(it.first) > 0)
|
|
delete first_node->attributes[it.first];
|
|
first_node->attributes[it.first] = it.second->clone();
|
|
}
|
|
children.erase(children.begin()+(i--));
|
|
did_something = true;
|
|
delete node;
|
|
continue;
|
|
wires_are_incompatible:
|
|
if (stage > 1)
|
|
log_file_error(filename, location.first_line, "Incompatible re-declaration of wire %s.\n", node->str.c_str());
|
|
continue;
|
|
}
|
|
this_wire_scope[node->str] = node;
|
|
}
|
|
// these nodes appear at the top level in a module and can define names
|
|
if (node->type == AST_PARAMETER || node->type == AST_LOCALPARAM || node->type == AST_WIRE || node->type == AST_AUTOWIRE || node->type == AST_GENVAR ||
|
|
node->type == AST_MEMORY || node->type == AST_FUNCTION || node->type == AST_TASK || node->type == AST_DPI_FUNCTION || node->type == AST_CELL ||
|
|
node->type == AST_TYPEDEF) {
|
|
backup_scope[node->str] = current_scope[node->str];
|
|
current_scope[node->str] = node;
|
|
}
|
|
if (node->type == AST_ENUM) {
|
|
current_scope[node->str] = node;
|
|
for (auto enode : node->children) {
|
|
log_assert(enode->type==AST_ENUM_ITEM);
|
|
if (current_scope.count(enode->str) == 0)
|
|
current_scope[enode->str] = enode;
|
|
else
|
|
log_file_error(filename, location.first_line, "enum item %s already exists\n", enode->str.c_str());
|
|
}
|
|
}
|
|
}
|
|
for (size_t i = 0; i < children.size(); i++) {
|
|
AstNode *node = children[i];
|
|
if (node->type == AST_PARAMETER || node->type == AST_LOCALPARAM || node->type == AST_WIRE || node->type == AST_AUTOWIRE || node->type == AST_MEMORY || node->type == AST_TYPEDEF)
|
|
while (node->simplify(true, false, false, 1, -1, false, node->type == AST_PARAMETER || node->type == AST_LOCALPARAM))
|
|
did_something = true;
|
|
if (node->type == AST_ENUM) {
|
|
for (auto enode : node->children){
|
|
log_assert(enode->type==AST_ENUM_ITEM);
|
|
while (node->simplify(true, false, false, 1, -1, false, in_param))
|
|
did_something = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (AstNode *child : children)
|
|
if (child->type == AST_ALWAYS &&
|
|
child->attributes.count(ID::always_comb))
|
|
check_auto_nosync(child);
|
|
}
|
|
|
|
// create name resolution entries for all objects with names
|
|
if (type == AST_PACKAGE) {
|
|
//add names to package scope
|
|
for (size_t i = 0; i < children.size(); i++) {
|
|
AstNode *node = children[i];
|
|
// these nodes appear at the top level in a package and can define names
|
|
if (node->type == AST_PARAMETER || node->type == AST_LOCALPARAM || node->type == AST_TYPEDEF || node->type == AST_FUNCTION || node->type == AST_TASK) {
|
|
current_scope[node->str] = node;
|
|
}
|
|
if (node->type == AST_ENUM) {
|
|
current_scope[node->str] = node;
|
|
for (auto enode : node->children) {
|
|
log_assert(enode->type==AST_ENUM_ITEM);
|
|
if (current_scope.count(enode->str) == 0)
|
|
current_scope[enode->str] = enode;
|
|
else
|
|
log_file_error(filename, location.first_line, "enum item %s already exists in package\n", enode->str.c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
auto backup_current_block = current_block;
|
|
auto backup_current_block_child = current_block_child;
|
|
auto backup_current_top_block = current_top_block;
|
|
auto backup_current_always = current_always;
|
|
auto backup_current_always_clocked = current_always_clocked;
|
|
|
|
if (type == AST_ALWAYS || type == AST_INITIAL)
|
|
{
|
|
if (current_always != nullptr)
|
|
log_file_error(filename, location.first_line, "Invalid nesting of always blocks and/or initializations.\n");
|
|
|
|
current_always = this;
|
|
current_always_clocked = false;
|
|
|
|
if (type == AST_ALWAYS)
|
|
for (auto child : children) {
|
|
if (child->type == AST_POSEDGE || child->type == AST_NEGEDGE)
|
|
current_always_clocked = true;
|
|
if (child->type == AST_EDGE && GetSize(child->children) == 1 &&
|
|
child->children[0]->type == AST_IDENTIFIER && child->children[0]->str == "\\$global_clock")
|
|
current_always_clocked = 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() << "$" << RTLIL::encode_filename(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);
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
int backup_width_hint = width_hint;
|
|
bool backup_sign_hint = sign_hint;
|
|
|
|
bool detect_width_simple = false;
|
|
bool child_0_is_self_determined = false;
|
|
bool child_1_is_self_determined = false;
|
|
bool child_2_is_self_determined = false;
|
|
bool children_are_self_determined = false;
|
|
bool reset_width_after_children = false;
|
|
|
|
switch (type)
|
|
{
|
|
case AST_ASSIGN_EQ:
|
|
case AST_ASSIGN_LE:
|
|
case AST_ASSIGN:
|
|
while (!children[0]->basic_prep && children[0]->simplify(false, false, true, stage, -1, false, in_param) == true)
|
|
did_something = true;
|
|
while (!children[1]->basic_prep && children[1]->simplify(false, false, false, stage, -1, false, in_param) == true)
|
|
did_something = true;
|
|
children[0]->detectSignWidth(backup_width_hint, backup_sign_hint);
|
|
children[1]->detectSignWidth(width_hint, sign_hint);
|
|
width_hint = max(width_hint, backup_width_hint);
|
|
child_0_is_self_determined = true;
|
|
// test only once, before optimizations and memory mappings but after assignment LHS was mapped to an identifier
|
|
if (children[0]->id2ast && !children[0]->was_checked) {
|
|
if ((type == AST_ASSIGN_LE || type == AST_ASSIGN_EQ) && children[0]->id2ast->is_logic)
|
|
children[0]->id2ast->is_reg = true; // if logic type is used in a block asignment
|
|
if ((type == AST_ASSIGN_LE || type == AST_ASSIGN_EQ) && !children[0]->id2ast->is_reg)
|
|
log_warning("wire '%s' is assigned in a block at %s.\n", children[0]->str.c_str(), loc_string().c_str());
|
|
if (type == AST_ASSIGN && children[0]->id2ast->is_reg) {
|
|
bool is_rand_reg = false;
|
|
if (children[1]->type == AST_FCALL) {
|
|
if (children[1]->str == "\\$anyconst")
|
|
is_rand_reg = true;
|
|
if (children[1]->str == "\\$anyseq")
|
|
is_rand_reg = true;
|
|
if (children[1]->str == "\\$allconst")
|
|
is_rand_reg = true;
|
|
if (children[1]->str == "\\$allseq")
|
|
is_rand_reg = true;
|
|
}
|
|
if (!is_rand_reg)
|
|
log_warning("reg '%s' is assigned in a continuous assignment at %s.\n", children[0]->str.c_str(), loc_string().c_str());
|
|
}
|
|
children[0]->was_checked = true;
|
|
}
|
|
break;
|
|
|
|
case AST_STRUCT:
|
|
case AST_UNION:
|
|
if (!basic_prep) {
|
|
for (auto *node : children) {
|
|
// resolve any ranges
|
|
while (!node->basic_prep && node->simplify(true, false, false, stage, -1, false, false)) {
|
|
did_something = true;
|
|
}
|
|
}
|
|
// determine member offsets and widths
|
|
size_packed_struct(this, 0);
|
|
|
|
// instance rather than just a type in a typedef or outer struct?
|
|
if (!str.empty() && str[0] == '\\') {
|
|
// instance so add a wire for the packed structure
|
|
auto wnode = make_packed_struct(this, str);
|
|
log_assert(current_ast_mod);
|
|
current_ast_mod->children.push_back(wnode);
|
|
}
|
|
basic_prep = true;
|
|
}
|
|
break;
|
|
|
|
case AST_STRUCT_ITEM:
|
|
break;
|
|
|
|
case AST_ENUM:
|
|
//log("\nENUM %s: %d child %d\n", str.c_str(), basic_prep, children[0]->basic_prep);
|
|
if (!basic_prep) {
|
|
for (auto item_node : children) {
|
|
while (!item_node->basic_prep && item_node->simplify(false, false, false, stage, -1, false, in_param))
|
|
did_something = true;
|
|
}
|
|
// allocate values (called more than once)
|
|
allocateDefaultEnumValues();
|
|
}
|
|
break;
|
|
|
|
case AST_PARAMETER:
|
|
case AST_LOCALPARAM:
|
|
// if parameter is implicit type which is the typename of a struct or union,
|
|
// save information about struct in wiretype attribute
|
|
if (children[0]->type == AST_IDENTIFIER && current_scope.count(children[0]->str) > 0) {
|
|
auto item_node = current_scope[children[0]->str];
|
|
if (item_node->type == AST_STRUCT || item_node->type == AST_UNION) {
|
|
attributes[ID::wiretype] = item_node->clone();
|
|
size_packed_struct(attributes[ID::wiretype], 0);
|
|
add_members_to_scope(attributes[ID::wiretype], str);
|
|
}
|
|
}
|
|
while (!children[0]->basic_prep && children[0]->simplify(false, false, false, stage, -1, false, true) == true)
|
|
did_something = true;
|
|
children[0]->detectSignWidth(width_hint, sign_hint);
|
|
if (children.size() > 1 && children[1]->type == AST_RANGE) {
|
|
while (!children[1]->basic_prep && children[1]->simplify(false, false, false, stage, -1, false, true) == true)
|
|
did_something = true;
|
|
if (!children[1]->range_valid)
|
|
log_file_error(filename, location.first_line, "Non-constant width range on parameter decl.\n");
|
|
width_hint = max(width_hint, children[1]->range_left - children[1]->range_right + 1);
|
|
}
|
|
break;
|
|
case AST_ENUM_ITEM:
|
|
while (!children[0]->basic_prep && children[0]->simplify(false, false, false, stage, -1, false, in_param))
|
|
did_something = true;
|
|
children[0]->detectSignWidth(width_hint, sign_hint);
|
|
if (children.size() > 1 && children[1]->type == AST_RANGE) {
|
|
while (!children[1]->basic_prep && children[1]->simplify(false, false, false, stage, -1, false, in_param))
|
|
did_something = true;
|
|
if (!children[1]->range_valid)
|
|
log_file_error(filename, location.first_line, "Non-constant width range on enum item decl.\n");
|
|
width_hint = max(width_hint, children[1]->range_left - children[1]->range_right + 1);
|
|
}
|
|
break;
|
|
|
|
case AST_TO_BITS:
|
|
case AST_TO_SIGNED:
|
|
case AST_TO_UNSIGNED:
|
|
case AST_SELFSZ:
|
|
case AST_CAST_SIZE:
|
|
case AST_CONCAT:
|
|
case AST_REPLICATE:
|
|
case AST_REDUCE_AND:
|
|
case AST_REDUCE_OR:
|
|
case AST_REDUCE_XOR:
|
|
case AST_REDUCE_XNOR:
|
|
case AST_REDUCE_BOOL:
|
|
detect_width_simple = true;
|
|
children_are_self_determined = true;
|
|
break;
|
|
|
|
case AST_NEG:
|
|
case AST_BIT_NOT:
|
|
case AST_POS:
|
|
case AST_BIT_AND:
|
|
case AST_BIT_OR:
|
|
case AST_BIT_XOR:
|
|
case AST_BIT_XNOR:
|
|
case AST_ADD:
|
|
case AST_SUB:
|
|
case AST_MUL:
|
|
case AST_DIV:
|
|
case AST_MOD:
|
|
detect_width_simple = true;
|
|
break;
|
|
|
|
case AST_SHIFT_LEFT:
|
|
case AST_SHIFT_RIGHT:
|
|
case AST_SHIFT_SLEFT:
|
|
case AST_SHIFT_SRIGHT:
|
|
case AST_POW:
|
|
detect_width_simple = true;
|
|
child_1_is_self_determined = true;
|
|
break;
|
|
|
|
case AST_LT:
|
|
case AST_LE:
|
|
case AST_EQ:
|
|
case AST_NE:
|
|
case AST_EQX:
|
|
case AST_NEX:
|
|
case AST_GE:
|
|
case AST_GT:
|
|
width_hint = -1;
|
|
sign_hint = true;
|
|
for (auto child : children) {
|
|
while (!child->basic_prep && child->simplify(false, false, in_lvalue, stage, -1, false, in_param) == true)
|
|
did_something = true;
|
|
child->detectSignWidthWorker(width_hint, sign_hint);
|
|
}
|
|
reset_width_after_children = true;
|
|
break;
|
|
|
|
case AST_LOGIC_AND:
|
|
case AST_LOGIC_OR:
|
|
case AST_LOGIC_NOT:
|
|
detect_width_simple = true;
|
|
children_are_self_determined = true;
|
|
break;
|
|
|
|
case AST_TERNARY:
|
|
child_0_is_self_determined = true;
|
|
break;
|
|
|
|
case AST_MEMRD:
|
|
detect_width_simple = true;
|
|
children_are_self_determined = true;
|
|
break;
|
|
|
|
case AST_FCALL:
|
|
case AST_TCALL:
|
|
children_are_self_determined = true;
|
|
break;
|
|
|
|
default:
|
|
width_hint = -1;
|
|
sign_hint = false;
|
|
}
|
|
|
|
if (detect_width_simple && width_hint < 0) {
|
|
if (type == AST_REPLICATE)
|
|
while (children[0]->simplify(true, false, in_lvalue, stage, -1, false, true) == true)
|
|
did_something = true;
|
|
for (auto child : children)
|
|
while (!child->basic_prep && child->simplify(false, false, in_lvalue, stage, -1, false, in_param) == true)
|
|
did_something = true;
|
|
detectSignWidth(width_hint, sign_hint);
|
|
}
|
|
|
|
if (type == AST_FCALL && str == "\\$past")
|
|
detectSignWidth(width_hint, sign_hint);
|
|
|
|
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;
|
|
bool sign_hint_left, sign_hint_right;
|
|
bool found_real_left, found_real_right;
|
|
children[1]->detectSignWidth(width_hint_left, sign_hint_left, &found_real_left);
|
|
children[2]->detectSignWidth(width_hint_right, sign_hint_right, &found_real_right);
|
|
if (found_real_left || found_real_right) {
|
|
child_1_is_self_determined = true;
|
|
child_2_is_self_determined = true;
|
|
}
|
|
}
|
|
|
|
if (type == AST_CONDX && children.size() > 0 && children.at(0)->type == AST_CONSTANT) {
|
|
for (auto &bit : children.at(0)->bits)
|
|
if (bit == State::Sz || bit == State::Sx)
|
|
bit = State::Sa;
|
|
}
|
|
|
|
if (type == AST_CONDZ && children.size() > 0 && children.at(0)->type == AST_CONSTANT) {
|
|
for (auto &bit : children.at(0)->bits)
|
|
if (bit == State::Sz)
|
|
bit = State::Sa;
|
|
}
|
|
|
|
if (const_fold && type == AST_CASE)
|
|
{
|
|
detectSignWidth(width_hint, sign_hint);
|
|
while (children[0]->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) { }
|
|
if (children[0]->type == AST_CONSTANT && children[0]->bits_only_01()) {
|
|
children[0]->is_signed = sign_hint;
|
|
RTLIL::Const case_expr = children[0]->bitsAsConst(width_hint, sign_hint);
|
|
std::vector<AstNode*> new_children;
|
|
new_children.push_back(children[0]);
|
|
for (int i = 1; i < GetSize(children); i++) {
|
|
AstNode *child = children[i];
|
|
log_assert(child->type == AST_COND || child->type == AST_CONDX || child->type == AST_CONDZ);
|
|
for (auto v : child->children) {
|
|
if (v->type == AST_DEFAULT)
|
|
goto keep_const_cond;
|
|
if (v->type == AST_BLOCK)
|
|
continue;
|
|
while (v->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) { }
|
|
if (v->type == AST_CONSTANT && v->bits_only_01()) {
|
|
RTLIL::Const case_item_expr = v->bitsAsConst(width_hint, sign_hint);
|
|
RTLIL::Const match = const_eq(case_expr, case_item_expr, sign_hint, sign_hint, 1);
|
|
log_assert(match.bits.size() == 1);
|
|
if (match.bits.front() == RTLIL::State::S1) {
|
|
while (i+1 < GetSize(children))
|
|
delete children[++i];
|
|
goto keep_const_cond;
|
|
}
|
|
continue;
|
|
}
|
|
goto keep_const_cond;
|
|
}
|
|
if (0)
|
|
keep_const_cond:
|
|
new_children.push_back(child);
|
|
else
|
|
delete child;
|
|
}
|
|
new_children.swap(children);
|
|
}
|
|
}
|
|
|
|
dict<std::string, pool<int>> backup_memwr_visible;
|
|
dict<std::string, pool<int>> final_memwr_visible;
|
|
|
|
if (type == AST_CASE && stage == 2) {
|
|
backup_memwr_visible = current_memwr_visible;
|
|
final_memwr_visible = current_memwr_visible;
|
|
}
|
|
|
|
// simplify all children first
|
|
// (iterate by index as e.g. auto wires can add new children in the process)
|
|
for (size_t i = 0; i < children.size(); i++) {
|
|
bool did_something_here = true;
|
|
bool backup_flag_autowire = flag_autowire;
|
|
bool backup_unevaluated_tern_branch = unevaluated_tern_branch;
|
|
if ((type == AST_GENFOR || type == AST_FOR) && i >= 3)
|
|
break;
|
|
if ((type == AST_GENIF || type == AST_GENCASE) && i >= 1)
|
|
break;
|
|
if (type == AST_GENBLOCK)
|
|
break;
|
|
if (type == AST_CELLARRAY && children[i]->type == AST_CELL)
|
|
continue;
|
|
if (type == AST_BLOCK && !str.empty())
|
|
break;
|
|
if (type == AST_PREFIX && i >= 1)
|
|
break;
|
|
if (type == AST_DEFPARAM && i == 0)
|
|
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()) {
|
|
bool const_fold_here = const_fold, in_lvalue_here = in_lvalue;
|
|
int width_hint_here = width_hint;
|
|
bool sign_hint_here = sign_hint;
|
|
bool in_param_here = in_param;
|
|
if (i == 0 && (type == AST_REPLICATE || type == AST_WIRE))
|
|
const_fold_here = true, in_param_here = true;
|
|
if (i == 0 && (type == AST_GENIF || type == AST_GENCASE))
|
|
in_param_here = true;
|
|
if (i == 1 && (type == AST_FOR || type == AST_GENFOR))
|
|
in_param_here = true;
|
|
if (type == AST_PARAMETER || type == AST_LOCALPARAM)
|
|
const_fold_here = true;
|
|
if (i == 0 && (type == AST_ASSIGN || type == AST_ASSIGN_EQ || type == AST_ASSIGN_LE))
|
|
in_lvalue_here = true;
|
|
if (type == AST_BLOCK) {
|
|
current_block = this;
|
|
current_block_child = children[i];
|
|
}
|
|
if ((type == AST_ALWAYS || type == AST_INITIAL) && children[i]->type == AST_BLOCK)
|
|
current_top_block = children[i];
|
|
if (i == 0 && child_0_is_self_determined)
|
|
width_hint_here = -1, sign_hint_here = false;
|
|
if (i == 1 && child_1_is_self_determined)
|
|
width_hint_here = -1, sign_hint_here = false;
|
|
if (i == 2 && child_2_is_self_determined)
|
|
width_hint_here = -1, sign_hint_here = false;
|
|
if (children_are_self_determined)
|
|
width_hint_here = -1, sign_hint_here = false;
|
|
did_something_here = children[i]->simplify(const_fold_here, at_zero, in_lvalue_here, stage, width_hint_here, sign_hint_here, in_param_here);
|
|
if (did_something_here)
|
|
did_something = true;
|
|
}
|
|
if (stage == 2 && children[i]->type == AST_INITIAL && current_ast_mod != this) {
|
|
current_ast_mod->children.push_back(children[i]);
|
|
children.erase(children.begin() + (i--));
|
|
did_something = true;
|
|
}
|
|
flag_autowire = backup_flag_autowire;
|
|
unevaluated_tern_branch = backup_unevaluated_tern_branch;
|
|
if (stage == 2 && type == AST_CASE) {
|
|
for (auto &x : current_memwr_visible) {
|
|
for (int y : x.second)
|
|
final_memwr_visible[x.first].insert(y);
|
|
}
|
|
current_memwr_visible = backup_memwr_visible;
|
|
}
|
|
}
|
|
for (auto &attr : attributes) {
|
|
while (attr.second->simplify(true, false, false, stage, -1, false, true))
|
|
did_something = true;
|
|
}
|
|
if (type == AST_CASE && stage == 2) {
|
|
current_memwr_visible = final_memwr_visible;
|
|
}
|
|
if (type == AST_ALWAYS && stage == 2) {
|
|
current_memwr_visible.clear();
|
|
current_memwr_count.clear();
|
|
}
|
|
|
|
if (reset_width_after_children) {
|
|
width_hint = backup_width_hint;
|
|
sign_hint = backup_sign_hint;
|
|
if (width_hint < 0)
|
|
detectSignWidth(width_hint, sign_hint);
|
|
}
|
|
|
|
current_block = backup_current_block;
|
|
current_block_child = backup_current_block_child;
|
|
current_top_block = backup_current_top_block;
|
|
current_always = backup_current_always;
|
|
current_always_clocked = backup_current_always_clocked;
|
|
|
|
for (auto it = backup_scope.begin(); it != backup_scope.end(); it++) {
|
|
if (it->second == NULL)
|
|
current_scope.erase(it->first);
|
|
else
|
|
current_scope[it->first] = it->second;
|
|
}
|
|
|
|
current_filename = filename;
|
|
|
|
if (type == AST_MODULE || type == AST_INTERFACE)
|
|
current_scope.clear();
|
|
|
|
// convert defparam nodes to cell parameters
|
|
if (type == AST_DEFPARAM && !children.empty())
|
|
{
|
|
if (children[0]->type != AST_IDENTIFIER)
|
|
log_file_error(filename, location.first_line, "Module name in defparam contains non-constant expressions!\n");
|
|
|
|
string modname, paramname = children[0]->str;
|
|
|
|
size_t pos = paramname.rfind('.');
|
|
|
|
while (pos != 0 && pos != std::string::npos)
|
|
{
|
|
modname = paramname.substr(0, pos);
|
|
|
|
if (current_scope.count(modname))
|
|
break;
|
|
|
|
pos = paramname.rfind('.', pos - 1);
|
|
}
|
|
|
|
if (pos == std::string::npos)
|
|
log_file_error(filename, location.first_line, "Can't find object for defparam `%s`!\n", RTLIL::unescape_id(paramname).c_str());
|
|
|
|
paramname = "\\" + paramname.substr(pos+1);
|
|
|
|
if (current_scope.at(modname)->type != AST_CELL)
|
|
log_file_error(filename, location.first_line, "Defparam argument `%s . %s` does not match a cell!\n",
|
|
RTLIL::unescape_id(modname).c_str(), RTLIL::unescape_id(paramname).c_str());
|
|
|
|
AstNode *paraset = new AstNode(AST_PARASET, children[1]->clone(), GetSize(children) > 2 ? children[2]->clone() : NULL);
|
|
paraset->str = paramname;
|
|
|
|
AstNode *cell = current_scope.at(modname);
|
|
cell->children.insert(cell->children.begin() + 1, paraset);
|
|
delete_children();
|
|
}
|
|
|
|
// resolve typedefs
|
|
if (type == AST_TYPEDEF) {
|
|
log_assert(children.size() == 1);
|
|
auto type_node = children[0];
|
|
log_assert(type_node->type == AST_WIRE || type_node->type == AST_MEMORY || type_node->type == AST_STRUCT || type_node->type == AST_UNION);
|
|
while (type_node->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) {
|
|
did_something = true;
|
|
}
|
|
log_assert(!type_node->is_custom_type);
|
|
}
|
|
|
|
// resolve types of wires
|
|
if (type == AST_WIRE || type == AST_MEMORY) {
|
|
if (is_custom_type) {
|
|
log_assert(children.size() >= 1);
|
|
log_assert(children[0]->type == AST_WIRETYPE);
|
|
auto type_name = children[0]->str;
|
|
if (!current_scope.count(type_name)) {
|
|
log_file_error(filename, location.first_line, "Unknown identifier `%s' used as type name\n", type_name.c_str());
|
|
}
|
|
AstNode *resolved_type_node = current_scope.at(type_name);
|
|
if (resolved_type_node->type != AST_TYPEDEF)
|
|
log_file_error(filename, location.first_line, "`%s' does not name a type\n", type_name.c_str());
|
|
log_assert(resolved_type_node->children.size() == 1);
|
|
AstNode *template_node = resolved_type_node->children[0];
|
|
|
|
// Ensure typedef itself is fully simplified
|
|
while (template_node->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) {};
|
|
|
|
if (template_node->type == AST_STRUCT || template_node->type == AST_UNION) {
|
|
// replace with wire representing the packed structure
|
|
newNode = make_packed_struct(template_node, str);
|
|
newNode->attributes[ID::wiretype] = mkconst_str(resolved_type_node->str);
|
|
// add original input/output attribute to resolved wire
|
|
newNode->is_input = this->is_input;
|
|
newNode->is_output = this->is_output;
|
|
current_scope[str] = this;
|
|
goto apply_newNode;
|
|
}
|
|
|
|
// Remove type reference
|
|
delete children[0];
|
|
children.erase(children.begin());
|
|
|
|
if (type == AST_WIRE)
|
|
type = template_node->type;
|
|
is_reg = template_node->is_reg;
|
|
is_logic = template_node->is_logic;
|
|
is_signed = template_node->is_signed;
|
|
is_string = template_node->is_string;
|
|
is_custom_type = template_node->is_custom_type;
|
|
|
|
range_valid = template_node->range_valid;
|
|
range_swapped = template_node->range_swapped;
|
|
range_left = template_node->range_left;
|
|
range_right = template_node->range_right;
|
|
|
|
attributes[ID::wiretype] = mkconst_str(resolved_type_node->str);
|
|
|
|
// if an enum then add attributes to support simulator tracing
|
|
annotateTypedEnums(template_node);
|
|
|
|
// Insert clones children from template at beginning
|
|
for (int i = 0; i < GetSize(template_node->children); i++)
|
|
children.insert(children.begin() + i, template_node->children[i]->clone());
|
|
|
|
if (type == AST_MEMORY && GetSize(children) == 1) {
|
|
// Single-bit memories must have [0:0] range
|
|
AstNode *rng = make_range(0, 0);
|
|
children.insert(children.begin(), rng);
|
|
}
|
|
did_something = true;
|
|
}
|
|
log_assert(!is_custom_type);
|
|
}
|
|
|
|
// resolve types of parameters
|
|
if (type == AST_LOCALPARAM || type == AST_PARAMETER) {
|
|
if (is_custom_type) {
|
|
log_assert(children.size() == 2);
|
|
log_assert(children[1]->type == AST_WIRETYPE);
|
|
auto type_name = children[1]->str;
|
|
if (!current_scope.count(type_name)) {
|
|
log_file_error(filename, location.first_line, "Unknown identifier `%s' used as type name\n", type_name.c_str());
|
|
}
|
|
AstNode *resolved_type_node = current_scope.at(type_name);
|
|
if (resolved_type_node->type != AST_TYPEDEF)
|
|
log_file_error(filename, location.first_line, "`%s' does not name a type\n", type_name.c_str());
|
|
log_assert(resolved_type_node->children.size() == 1);
|
|
AstNode *template_node = resolved_type_node->children[0];
|
|
|
|
// Ensure typedef itself is fully simplified
|
|
while (template_node->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) {};
|
|
|
|
if (template_node->type == AST_STRUCT || template_node->type == AST_UNION) {
|
|
// replace with wire representing the packed structure
|
|
newNode = make_packed_struct(template_node, str);
|
|
newNode->attributes[ID::wiretype] = mkconst_str(resolved_type_node->str);
|
|
newNode->type = type;
|
|
current_scope[str] = this;
|
|
// copy param value, it needs to be 1st value
|
|
delete children[1];
|
|
children.pop_back();
|
|
newNode->children.insert(newNode->children.begin(), children[0]->clone());
|
|
goto apply_newNode;
|
|
}
|
|
delete children[1];
|
|
children.pop_back();
|
|
|
|
if (template_node->type == AST_MEMORY)
|
|
log_file_error(filename, location.first_line, "unpacked array type `%s' cannot be used for a parameter\n", children[1]->str.c_str());
|
|
is_signed = template_node->is_signed;
|
|
is_string = template_node->is_string;
|
|
is_custom_type = template_node->is_custom_type;
|
|
|
|
range_valid = template_node->range_valid;
|
|
range_swapped = template_node->range_swapped;
|
|
range_left = template_node->range_left;
|
|
range_right = template_node->range_right;
|
|
attributes[ID::wiretype] = mkconst_str(resolved_type_node->str);
|
|
for (auto template_child : template_node->children)
|
|
children.push_back(template_child->clone());
|
|
did_something = true;
|
|
}
|
|
log_assert(!is_custom_type);
|
|
}
|
|
|
|
// resolve constant prefixes
|
|
if (type == AST_PREFIX) {
|
|
if (children[0]->type != AST_CONSTANT) {
|
|
// dumpAst(NULL, "> ");
|
|
log_file_error(filename, location.first_line, "Index in generate block prefix syntax is not constant!\n");
|
|
}
|
|
if (children[1]->type == AST_PREFIX)
|
|
children[1]->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param);
|
|
log_assert(children[1]->type == AST_IDENTIFIER);
|
|
newNode = children[1]->clone();
|
|
const char *second_part = children[1]->str.c_str();
|
|
if (second_part[0] == '\\')
|
|
second_part++;
|
|
newNode->str = stringf("%s[%d].%s", str.c_str(), children[0]->integer, second_part);
|
|
goto apply_newNode;
|
|
}
|
|
|
|
// evaluate TO_BITS nodes
|
|
if (type == AST_TO_BITS) {
|
|
if (children[0]->type != AST_CONSTANT)
|
|
log_file_error(filename, location.first_line, "Left operand of to_bits expression is not constant!\n");
|
|
if (children[1]->type != AST_CONSTANT)
|
|
log_file_error(filename, location.first_line, "Right operand of to_bits expression is not constant!\n");
|
|
RTLIL::Const new_value = children[1]->bitsAsConst(children[0]->bitsAsConst().as_int(), children[1]->is_signed);
|
|
newNode = mkconst_bits(new_value.bits, children[1]->is_signed);
|
|
goto apply_newNode;
|
|
}
|
|
|
|
// annotate constant ranges
|
|
if (type == AST_RANGE) {
|
|
bool old_range_valid = range_valid;
|
|
range_valid = false;
|
|
range_swapped = false;
|
|
range_left = -1;
|
|
range_right = 0;
|
|
log_assert(children.size() >= 1);
|
|
if (children[0]->type == AST_CONSTANT) {
|
|
range_valid = true;
|
|
range_left = children[0]->integer;
|
|
if (children.size() == 1)
|
|
range_right = range_left;
|
|
}
|
|
if (children.size() >= 2) {
|
|
if (children[1]->type == AST_CONSTANT)
|
|
range_right = children[1]->integer;
|
|
else
|
|
range_valid = false;
|
|
}
|
|
if (old_range_valid != range_valid)
|
|
did_something = true;
|
|
if (range_valid && range_right > range_left) {
|
|
int tmp = range_right;
|
|
range_right = range_left;
|
|
range_left = tmp;
|
|
range_swapped = true;
|
|
}
|
|
}
|
|
|
|
// annotate wires with their ranges
|
|
if (type == AST_WIRE) {
|
|
if (children.size() > 0) {
|
|
if (children[0]->range_valid) {
|
|
if (!range_valid)
|
|
did_something = true;
|
|
range_valid = true;
|
|
range_swapped = children[0]->range_swapped;
|
|
range_left = children[0]->range_left;
|
|
range_right = children[0]->range_right;
|
|
bool force_upto = false, force_downto = false;
|
|
if (attributes.count(ID::force_upto)) {
|
|
AstNode *val = attributes[ID::force_upto];
|
|
if (val->type != AST_CONSTANT)
|
|
log_file_error(filename, location.first_line, "Attribute `force_upto' with non-constant value!\n");
|
|
force_upto = val->asAttrConst().as_bool();
|
|
}
|
|
if (attributes.count(ID::force_downto)) {
|
|
AstNode *val = attributes[ID::force_downto];
|
|
if (val->type != AST_CONSTANT)
|
|
log_file_error(filename, location.first_line, "Attribute `force_downto' with non-constant value!\n");
|
|
force_downto = val->asAttrConst().as_bool();
|
|
}
|
|
if (force_upto && force_downto)
|
|
log_file_error(filename, location.first_line, "Attributes `force_downto' and `force_upto' cannot be both set!\n");
|
|
if ((force_upto && !range_swapped) || (force_downto && range_swapped)) {
|
|
std::swap(range_left, range_right);
|
|
range_swapped = force_upto;
|
|
}
|
|
}
|
|
} else {
|
|
if (!range_valid)
|
|
did_something = true;
|
|
range_valid = true;
|
|
range_swapped = false;
|
|
range_left = 0;
|
|
range_right = 0;
|
|
}
|
|
}
|
|
|
|
// resolve multiranges on memory decl
|
|
if (type == AST_MEMORY && children.size() > 1 && children[1]->type == AST_MULTIRANGE)
|
|
{
|
|
int total_size = 1;
|
|
multirange_dimensions.clear();
|
|
multirange_swapped.clear();
|
|
for (auto range : children[1]->children) {
|
|
if (!range->range_valid)
|
|
log_file_error(filename, location.first_line, "Non-constant range on memory decl.\n");
|
|
multirange_dimensions.push_back(min(range->range_left, range->range_right));
|
|
multirange_dimensions.push_back(max(range->range_left, range->range_right) - min(range->range_left, range->range_right) + 1);
|
|
multirange_swapped.push_back(range->range_swapped);
|
|
total_size *= multirange_dimensions.back();
|
|
}
|
|
delete children[1];
|
|
children[1] = new AstNode(AST_RANGE, AstNode::mkconst_int(0, true), AstNode::mkconst_int(total_size-1, true));
|
|
did_something = true;
|
|
}
|
|
|
|
// resolve multiranges on memory access
|
|
if (type == AST_IDENTIFIER && id2ast && id2ast->type == AST_MEMORY && children.size() > 0 && children[0]->type == AST_MULTIRANGE)
|
|
{
|
|
AstNode *index_expr = nullptr;
|
|
|
|
integer = children[0]->children.size(); // save original number of dimensions for $size() etc.
|
|
for (int i = 0; 2*i < GetSize(id2ast->multirange_dimensions); i++)
|
|
{
|
|
if (GetSize(children[0]->children) <= i)
|
|
log_file_error(filename, location.first_line, "Insufficient number of array indices for %s.\n", log_id(str));
|
|
|
|
AstNode *new_index_expr = children[0]->children[i]->children.at(0)->clone();
|
|
|
|
if (id2ast->multirange_dimensions[2*i])
|
|
new_index_expr = new AstNode(AST_SUB, new_index_expr, AstNode::mkconst_int(id2ast->multirange_dimensions[2*i], true));
|
|
|
|
if (i == 0)
|
|
index_expr = new_index_expr;
|
|
else
|
|
index_expr = new AstNode(AST_ADD, new AstNode(AST_MUL, index_expr, AstNode::mkconst_int(id2ast->multirange_dimensions[2*i+1], true)), new_index_expr);
|
|
}
|
|
|
|
for (int i = GetSize(id2ast->multirange_dimensions)/2; i < GetSize(children[0]->children); i++)
|
|
children.push_back(children[0]->children[i]->clone());
|
|
|
|
delete children[0];
|
|
if (index_expr == nullptr)
|
|
children.erase(children.begin());
|
|
else
|
|
children[0] = new AstNode(AST_RANGE, index_expr);
|
|
|
|
did_something = true;
|
|
}
|
|
|
|
// trim/extend parameters
|
|
if (type == AST_PARAMETER || type == AST_LOCALPARAM || type == AST_ENUM_ITEM) {
|
|
if (children.size() > 1 && children[1]->type == AST_RANGE) {
|
|
if (!children[1]->range_valid)
|
|
log_file_error(filename, location.first_line, "Non-constant width range on parameter decl.\n");
|
|
int width = std::abs(children[1]->range_left - children[1]->range_right) + 1;
|
|
if (children[0]->type == AST_REALVALUE) {
|
|
RTLIL::Const constvalue = children[0]->realAsConst(width);
|
|
log_file_warning(filename, location.first_line, "converting real value %e to binary %s.\n",
|
|
children[0]->realvalue, log_signal(constvalue));
|
|
delete children[0];
|
|
children[0] = mkconst_bits(constvalue.bits, sign_hint);
|
|
did_something = true;
|
|
}
|
|
if (children[0]->type == AST_CONSTANT) {
|
|
if (width != int(children[0]->bits.size())) {
|
|
RTLIL::SigSpec sig(children[0]->bits);
|
|
sig.extend_u0(width, children[0]->is_signed);
|
|
AstNode *old_child_0 = children[0];
|
|
children[0] = mkconst_bits(sig.as_const().bits, is_signed);
|
|
delete old_child_0;
|
|
}
|
|
children[0]->is_signed = is_signed;
|
|
}
|
|
range_valid = true;
|
|
range_swapped = children[1]->range_swapped;
|
|
range_left = children[1]->range_left;
|
|
range_right = children[1]->range_right;
|
|
} else
|
|
if (children.size() > 1 && children[1]->type == AST_REALVALUE && children[0]->type == AST_CONSTANT) {
|
|
double as_realvalue = children[0]->asReal(sign_hint);
|
|
delete children[0];
|
|
children[0] = new AstNode(AST_REALVALUE);
|
|
children[0]->realvalue = as_realvalue;
|
|
did_something = true;
|
|
}
|
|
}
|
|
|
|
if (type == AST_IDENTIFIER && !basic_prep) {
|
|
// check if a plausible struct member sss.mmmm
|
|
std::string sname;
|
|
if (name_has_dot(str, sname)) {
|
|
if (current_scope.count(str) > 0) {
|
|
auto item_node = current_scope[str];
|
|
if (item_node->type == AST_STRUCT_ITEM || item_node->type == AST_STRUCT || item_node->type == AST_UNION) {
|
|
// structure member, rewrite this node to reference the packed struct wire
|
|
auto range = make_struct_member_range(this, item_node);
|
|
newNode = new AstNode(AST_IDENTIFIER, range);
|
|
newNode->str = sname;
|
|
// save type and original number of dimensions for $size() etc.
|
|
newNode->attributes[ID::wiretype] = item_node->clone();
|
|
if (!item_node->multirange_dimensions.empty() && children.size() > 0) {
|
|
if (children[0]->type == AST_RANGE)
|
|
newNode->integer = 1;
|
|
else if (children[0]->type == AST_MULTIRANGE)
|
|
newNode->integer = children[0]->children.size();
|
|
}
|
|
newNode->basic_prep = true;
|
|
if (item_node->is_signed)
|
|
newNode = new AstNode(AST_TO_SIGNED, newNode);
|
|
goto apply_newNode;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// annotate identifiers using scope resolution and create auto-wires as needed
|
|
if (type == AST_IDENTIFIER) {
|
|
if (current_scope.count(str) == 0) {
|
|
AstNode *current_scope_ast = (current_ast_mod == nullptr) ? current_ast : current_ast_mod;
|
|
str = try_pop_module_prefix();
|
|
for (auto node : current_scope_ast->children) {
|
|
//log("looking at mod scope child %s\n", type2str(node->type).c_str());
|
|
switch (node->type) {
|
|
case AST_PARAMETER:
|
|
case AST_LOCALPARAM:
|
|
case AST_WIRE:
|
|
case AST_AUTOWIRE:
|
|
case AST_GENVAR:
|
|
case AST_MEMORY:
|
|
case AST_FUNCTION:
|
|
case AST_TASK:
|
|
case AST_DPI_FUNCTION:
|
|
//log("found child %s, %s\n", type2str(node->type).c_str(), node->str.c_str());
|
|
if (str == node->str) {
|
|
//log("add %s, type %s to scope\n", str.c_str(), type2str(node->type).c_str());
|
|
current_scope[node->str] = node;
|
|
}
|
|
break;
|
|
case AST_ENUM:
|
|
current_scope[node->str] = node;
|
|
for (auto enum_node : node->children) {
|
|
log_assert(enum_node->type==AST_ENUM_ITEM);
|
|
if (str == enum_node->str) {
|
|
//log("\nadding enum item %s to scope\n", str.c_str());
|
|
current_scope[str] = enum_node;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (current_scope.count(str) == 0) {
|
|
if (current_ast_mod == nullptr) {
|
|
log_file_error(filename, location.first_line, "Identifier `%s' is implicitly declared outside of a module.\n", str.c_str());
|
|
} else if (flag_autowire || str == "\\$global_clock") {
|
|
AstNode *auto_wire = new AstNode(AST_AUTOWIRE);
|
|
auto_wire->str = str;
|
|
current_ast_mod->children.push_back(auto_wire);
|
|
current_scope[str] = auto_wire;
|
|
did_something = true;
|
|
} else {
|
|
log_file_error(filename, location.first_line, "Identifier `%s' is implicitly declared and `default_nettype is set to none.\n", str.c_str());
|
|
}
|
|
}
|
|
if (id2ast != current_scope[str]) {
|
|
id2ast = current_scope[str];
|
|
did_something = true;
|
|
}
|
|
}
|
|
|
|
// split memory access with bit select to individual statements
|
|
if (type == AST_IDENTIFIER && children.size() == 2 && children[0]->type == AST_RANGE && children[1]->type == AST_RANGE && !in_lvalue && stage == 2)
|
|
{
|
|
if (id2ast == NULL || id2ast->type != AST_MEMORY || children[0]->children.size() != 1)
|
|
log_file_error(filename, location.first_line, "Invalid bit-select on memory access!\n");
|
|
|
|
int mem_width, mem_size, addr_bits;
|
|
id2ast->meminfo(mem_width, mem_size, addr_bits);
|
|
|
|
int data_range_left = id2ast->children[0]->range_left;
|
|
int data_range_right = id2ast->children[0]->range_right;
|
|
|
|
if (id2ast->children[0]->range_swapped)
|
|
std::swap(data_range_left, data_range_right);
|
|
|
|
std::stringstream sstr;
|
|
sstr << "$mem2bits$" << str << "$" << RTLIL::encode_filename(filename) << ":" << location.first_line << "$" << (autoidx++);
|
|
std::string wire_id = sstr.str();
|
|
|
|
AstNode *wire = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(data_range_left, true), mkconst_int(data_range_right, true)));
|
|
wire->str = wire_id;
|
|
if (current_block)
|
|
wire->attributes[ID::nosync] = AstNode::mkconst_int(1, false);
|
|
current_ast_mod->children.push_back(wire);
|
|
while (wire->simplify(true, false, false, 1, -1, false, false)) { }
|
|
|
|
AstNode *data = clone();
|
|
delete data->children[1];
|
|
data->children.pop_back();
|
|
|
|
AstNode *assign = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), data);
|
|
assign->children[0]->str = wire_id;
|
|
assign->children[0]->was_checked = true;
|
|
|
|
if (current_block)
|
|
{
|
|
size_t assign_idx = 0;
|
|
while (assign_idx < current_block->children.size() && current_block->children[assign_idx] != current_block_child)
|
|
assign_idx++;
|
|
log_assert(assign_idx < current_block->children.size());
|
|
current_block->children.insert(current_block->children.begin()+assign_idx, assign);
|
|
wire->is_reg = true;
|
|
}
|
|
else
|
|
{
|
|
AstNode *proc = new AstNode(AST_ALWAYS, new AstNode(AST_BLOCK));
|
|
proc->children[0]->children.push_back(assign);
|
|
current_ast_mod->children.push_back(proc);
|
|
}
|
|
|
|
newNode = new AstNode(AST_IDENTIFIER, children[1]->clone());
|
|
newNode->str = wire_id;
|
|
newNode->integer = integer; // save original number of dimensions for $size() etc.
|
|
newNode->id2ast = wire;
|
|
goto apply_newNode;
|
|
}
|
|
|
|
if (type == AST_WHILE)
|
|
log_file_error(filename, location.first_line, "While loops are only allowed in constant functions!\n");
|
|
|
|
if (type == AST_REPEAT)
|
|
{
|
|
AstNode *count = children[0];
|
|
AstNode *body = children[1];
|
|
|
|
// eval count expression
|
|
while (count->simplify(true, false, false, stage, 32, true, false)) { }
|
|
|
|
if (count->type != AST_CONSTANT)
|
|
log_file_error(filename, location.first_line, "Repeat loops outside must have constant repeat counts!\n");
|
|
|
|
// convert to a block with the body repeated n times
|
|
type = AST_BLOCK;
|
|
children.clear();
|
|
for (int i = 0; i < count->bitsAsConst().as_int(); i++)
|
|
children.insert(children.begin(), body->clone());
|
|
|
|
delete count;
|
|
delete body;
|
|
did_something = true;
|
|
}
|
|
|
|
// unroll for loops and generate-for blocks
|
|
if ((type == AST_GENFOR || type == AST_FOR) && children.size() != 0)
|
|
{
|
|
AstNode *init_ast = children[0];
|
|
AstNode *while_ast = children[1];
|
|
AstNode *next_ast = children[2];
|
|
AstNode *body_ast = children[3];
|
|
|
|
while (body_ast->type == AST_GENBLOCK && body_ast->str.empty() &&
|
|
body_ast->children.size() == 1 && body_ast->children.at(0)->type == AST_GENBLOCK)
|
|
body_ast = body_ast->children.at(0);
|
|
|
|
const char* loop_type_str = "procedural";
|
|
const char* var_type_str = "register";
|
|
AstNodeType var_type = AST_WIRE;
|
|
if (type == AST_GENFOR) {
|
|
loop_type_str = "generate";
|
|
var_type_str = "genvar";
|
|
var_type = AST_GENVAR;
|
|
}
|
|
|
|
if (init_ast->type != AST_ASSIGN_EQ)
|
|
log_file_error(filename, location.first_line, "Unsupported 1st expression of %s for-loop!\n", loop_type_str);
|
|
if (next_ast->type != AST_ASSIGN_EQ)
|
|
log_file_error(filename, location.first_line, "Unsupported 3rd expression of %s for-loop!\n", loop_type_str);
|
|
|
|
if (init_ast->children[0]->id2ast == NULL || init_ast->children[0]->id2ast->type != var_type)
|
|
log_file_error(filename, location.first_line, "Left hand side of 1st expression of %s for-loop is not a %s!\n", loop_type_str, var_type_str);
|
|
if (next_ast->children[0]->id2ast == NULL || next_ast->children[0]->id2ast->type != var_type)
|
|
log_file_error(filename, location.first_line, "Left hand side of 3rd expression of %s for-loop is not a %s!\n", loop_type_str, var_type_str);
|
|
|
|
if (init_ast->children[0]->id2ast != next_ast->children[0]->id2ast)
|
|
log_file_error(filename, location.first_line, "Incompatible left-hand sides in 1st and 3rd expression of %s for-loop!\n", loop_type_str);
|
|
|
|
// eval 1st expression
|
|
AstNode *varbuf = init_ast->children[1]->clone();
|
|
{
|
|
int expr_width_hint = -1;
|
|
bool expr_sign_hint = true;
|
|
varbuf->detectSignWidth(expr_width_hint, expr_sign_hint);
|
|
while (varbuf->simplify(true, false, false, stage, 32, true, false)) { }
|
|
}
|
|
|
|
if (varbuf->type != AST_CONSTANT)
|
|
log_file_error(filename, location.first_line, "Right hand side of 1st expression of %s for-loop is not constant!\n", loop_type_str);
|
|
|
|
auto resolved = current_scope.at(init_ast->children[0]->str);
|
|
if (resolved->range_valid) {
|
|
int const_size = varbuf->range_left - varbuf->range_right;
|
|
int resolved_size = resolved->range_left - resolved->range_right;
|
|
if (const_size < resolved_size) {
|
|
for (int i = const_size; i < resolved_size; i++)
|
|
varbuf->bits.push_back(resolved->is_signed ? varbuf->bits.back() : State::S0);
|
|
varbuf->range_left = resolved->range_left;
|
|
varbuf->range_right = resolved->range_right;
|
|
varbuf->range_swapped = resolved->range_swapped;
|
|
varbuf->range_valid = resolved->range_valid;
|
|
}
|
|
}
|
|
|
|
varbuf = new AstNode(AST_LOCALPARAM, varbuf);
|
|
varbuf->str = init_ast->children[0]->str;
|
|
|
|
AstNode *backup_scope_varbuf = current_scope[varbuf->str];
|
|
current_scope[varbuf->str] = varbuf;
|
|
|
|
size_t current_block_idx = 0;
|
|
if (type == AST_FOR) {
|
|
while (current_block_idx < current_block->children.size() &&
|
|
current_block->children[current_block_idx] != current_block_child)
|
|
current_block_idx++;
|
|
}
|
|
|
|
while (1)
|
|
{
|
|
// eval 2nd expression
|
|
AstNode *buf = while_ast->clone();
|
|
{
|
|
int expr_width_hint = -1;
|
|
bool expr_sign_hint = true;
|
|
buf->detectSignWidth(expr_width_hint, expr_sign_hint);
|
|
while (buf->simplify(true, false, false, stage, expr_width_hint, expr_sign_hint, false)) { }
|
|
}
|
|
|
|
if (buf->type != AST_CONSTANT)
|
|
log_file_error(filename, location.first_line, "2nd expression of %s for-loop is not constant!\n", loop_type_str);
|
|
|
|
if (buf->integer == 0) {
|
|
delete buf;
|
|
break;
|
|
}
|
|
delete buf;
|
|
|
|
// expand body
|
|
int index = varbuf->children[0]->integer;
|
|
log_assert(body_ast->type == AST_GENBLOCK || body_ast->type == AST_BLOCK);
|
|
log_assert(!body_ast->str.empty());
|
|
buf = body_ast->clone();
|
|
|
|
std::stringstream sstr;
|
|
sstr << buf->str << "[" << index << "].";
|
|
std::string prefix = sstr.str();
|
|
|
|
// create a scoped localparam for the current value of the loop variable
|
|
AstNode *local_index = varbuf->clone();
|
|
size_t pos = local_index->str.rfind('.');
|
|
if (pos != std::string::npos) // remove outer prefix
|
|
local_index->str = "\\" + local_index->str.substr(pos + 1);
|
|
local_index->str = prefix_id(prefix, local_index->str);
|
|
current_scope[local_index->str] = local_index;
|
|
current_ast_mod->children.push_back(local_index);
|
|
|
|
buf->expand_genblock(prefix);
|
|
|
|
if (type == AST_GENFOR) {
|
|
for (size_t i = 0; i < buf->children.size(); i++) {
|
|
buf->children[i]->simplify(const_fold, false, false, stage, -1, false, false);
|
|
current_ast_mod->children.push_back(buf->children[i]);
|
|
}
|
|
} else {
|
|
for (size_t i = 0; i < buf->children.size(); i++)
|
|
current_block->children.insert(current_block->children.begin() + current_block_idx++, buf->children[i]);
|
|
}
|
|
buf->children.clear();
|
|
delete buf;
|
|
|
|
// eval 3rd expression
|
|
buf = next_ast->children[1]->clone();
|
|
{
|
|
int expr_width_hint = -1;
|
|
bool expr_sign_hint = true;
|
|
buf->detectSignWidth(expr_width_hint, expr_sign_hint);
|
|
while (buf->simplify(true, false, false, stage, expr_width_hint, expr_sign_hint, true)) { }
|
|
}
|
|
|
|
if (buf->type != AST_CONSTANT)
|
|
log_file_error(filename, location.first_line, "Right hand side of 3rd expression of %s for-loop is not constant (%s)!\n", loop_type_str, type2str(buf->type).c_str());
|
|
|
|
delete varbuf->children[0];
|
|
varbuf->children[0] = buf;
|
|
}
|
|
|
|
if (type == AST_FOR) {
|
|
AstNode *buf = next_ast->clone();
|
|
delete buf->children[1];
|
|
buf->children[1] = varbuf->children[0]->clone();
|
|
current_block->children.insert(current_block->children.begin() + current_block_idx++, buf);
|
|
}
|
|
|
|
current_scope[varbuf->str] = backup_scope_varbuf;
|
|
delete varbuf;
|
|
delete_children();
|
|
did_something = true;
|
|
}
|
|
|
|
// check for local objects in unnamed block
|
|
if (type == AST_BLOCK && str.empty())
|
|
{
|
|
for (size_t i = 0; i < children.size(); i++)
|
|
if (children[i]->type == AST_WIRE || children[i]->type == AST_MEMORY || children[i]->type == AST_PARAMETER || children[i]->type == AST_LOCALPARAM || children[i]->type == AST_TYPEDEF)
|
|
{
|
|
log_assert(!VERILOG_FRONTEND::sv_mode);
|
|
log_file_error(children[i]->filename, children[i]->location.first_line, "Local declaration in unnamed block is only supported in SystemVerilog mode!\n");
|
|
}
|
|
}
|
|
|
|
// transform block with name
|
|
if (type == AST_BLOCK && !str.empty())
|
|
{
|
|
expand_genblock(str + ".");
|
|
|
|
// if this is an autonamed block is in an always_comb
|
|
if (current_always && current_always->attributes.count(ID::always_comb)
|
|
&& is_autonamed_block(str))
|
|
// track local variables in this block so we can consider adding
|
|
// nosync once the block has been fully elaborated
|
|
for (AstNode *child : children)
|
|
if (child->type == AST_WIRE &&
|
|
!child->attributes.count(ID::nosync))
|
|
mark_auto_nosync(this, child);
|
|
|
|
std::vector<AstNode*> new_children;
|
|
for (size_t i = 0; i < children.size(); i++)
|
|
if (children[i]->type == AST_WIRE || children[i]->type == AST_MEMORY || children[i]->type == AST_PARAMETER || children[i]->type == AST_LOCALPARAM || children[i]->type == AST_TYPEDEF) {
|
|
children[i]->simplify(false, false, false, stage, -1, false, false);
|
|
current_ast_mod->children.push_back(children[i]);
|
|
current_scope[children[i]->str] = children[i];
|
|
} else
|
|
new_children.push_back(children[i]);
|
|
|
|
children.swap(new_children);
|
|
did_something = true;
|
|
str.clear();
|
|
}
|
|
|
|
// simplify unconditional generate block
|
|
if (type == AST_GENBLOCK && children.size() != 0)
|
|
{
|
|
if (!str.empty()) {
|
|
expand_genblock(str + ".");
|
|
}
|
|
|
|
for (size_t i = 0; i < children.size(); i++) {
|
|
children[i]->simplify(const_fold, false, false, stage, -1, false, false);
|
|
current_ast_mod->children.push_back(children[i]);
|
|
}
|
|
|
|
children.clear();
|
|
did_something = true;
|
|
}
|
|
|
|
// simplify generate-if blocks
|
|
if (type == AST_GENIF && children.size() != 0)
|
|
{
|
|
AstNode *buf = children[0]->clone();
|
|
while (buf->simplify(true, false, false, stage, width_hint, sign_hint, false)) { }
|
|
if (buf->type != AST_CONSTANT) {
|
|
// for (auto f : log_files)
|
|
// dumpAst(f, "verilog-ast> ");
|
|
log_file_error(filename, location.first_line, "Condition for generate if is not constant!\n");
|
|
}
|
|
if (buf->asBool() != 0) {
|
|
delete buf;
|
|
buf = children[1]->clone();
|
|
} else {
|
|
delete buf;
|
|
buf = children.size() > 2 ? children[2]->clone() : NULL;
|
|
}
|
|
|
|
if (buf)
|
|
{
|
|
if (buf->type != AST_GENBLOCK)
|
|
buf = new AstNode(AST_GENBLOCK, buf);
|
|
|
|
if (!buf->str.empty()) {
|
|
buf->expand_genblock(buf->str + ".");
|
|
}
|
|
|
|
for (size_t i = 0; i < buf->children.size(); i++) {
|
|
buf->children[i]->simplify(const_fold, false, false, stage, -1, false, false);
|
|
current_ast_mod->children.push_back(buf->children[i]);
|
|
}
|
|
|
|
buf->children.clear();
|
|
delete buf;
|
|
}
|
|
|
|
delete_children();
|
|
did_something = true;
|
|
}
|
|
|
|
// simplify generate-case blocks
|
|
if (type == AST_GENCASE && children.size() != 0)
|
|
{
|
|
AstNode *buf = children[0]->clone();
|
|
while (buf->simplify(true, false, false, stage, width_hint, sign_hint, false)) { }
|
|
if (buf->type != AST_CONSTANT) {
|
|
// for (auto f : log_files)
|
|
// dumpAst(f, "verilog-ast> ");
|
|
log_file_error(filename, location.first_line, "Condition for generate case is not constant!\n");
|
|
}
|
|
|
|
bool ref_signed = buf->is_signed;
|
|
RTLIL::Const ref_value = buf->bitsAsConst();
|
|
delete buf;
|
|
|
|
AstNode *selected_case = NULL;
|
|
for (size_t i = 1; i < children.size(); i++)
|
|
{
|
|
log_assert(children.at(i)->type == AST_COND || children.at(i)->type == AST_CONDX || children.at(i)->type == AST_CONDZ);
|
|
|
|
AstNode *this_genblock = NULL;
|
|
for (auto child : children.at(i)->children) {
|
|
log_assert(this_genblock == NULL);
|
|
if (child->type == AST_GENBLOCK)
|
|
this_genblock = child;
|
|
}
|
|
|
|
for (auto child : children.at(i)->children)
|
|
{
|
|
if (child->type == AST_DEFAULT) {
|
|
if (selected_case == NULL)
|
|
selected_case = this_genblock;
|
|
continue;
|
|
}
|
|
if (child->type == AST_GENBLOCK)
|
|
continue;
|
|
|
|
buf = child->clone();
|
|
while (buf->simplify(true, false, false, stage, width_hint, sign_hint, true)) { }
|
|
if (buf->type != AST_CONSTANT) {
|
|
// for (auto f : log_files)
|
|
// dumpAst(f, "verilog-ast> ");
|
|
log_file_error(filename, location.first_line, "Expression in generate case is not constant!\n");
|
|
}
|
|
|
|
bool is_selected = RTLIL::const_eq(ref_value, buf->bitsAsConst(), ref_signed && buf->is_signed, ref_signed && buf->is_signed, 1).as_bool();
|
|
delete buf;
|
|
|
|
if (is_selected) {
|
|
selected_case = this_genblock;
|
|
i = children.size();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (selected_case != NULL)
|
|
{
|
|
log_assert(selected_case->type == AST_GENBLOCK);
|
|
buf = selected_case->clone();
|
|
|
|
if (!buf->str.empty()) {
|
|
buf->expand_genblock(buf->str + ".");
|
|
}
|
|
|
|
for (size_t i = 0; i < buf->children.size(); i++) {
|
|
buf->children[i]->simplify(const_fold, false, false, stage, -1, false, false);
|
|
current_ast_mod->children.push_back(buf->children[i]);
|
|
}
|
|
|
|
buf->children.clear();
|
|
delete buf;
|
|
}
|
|
|
|
delete_children();
|
|
did_something = true;
|
|
}
|
|
|
|
// unroll cell arrays
|
|
if (type == AST_CELLARRAY)
|
|
{
|
|
if (!children.at(0)->range_valid)
|
|
log_file_error(filename, location.first_line, "Non-constant array range on cell array.\n");
|
|
|
|
newNode = new AstNode(AST_GENBLOCK);
|
|
int num = max(children.at(0)->range_left, children.at(0)->range_right) - min(children.at(0)->range_left, children.at(0)->range_right) + 1;
|
|
|
|
for (int i = 0; i < num; i++) {
|
|
int idx = children.at(0)->range_left > children.at(0)->range_right ? children.at(0)->range_right + i : children.at(0)->range_right - i;
|
|
AstNode *new_cell = children.at(1)->clone();
|
|
newNode->children.push_back(new_cell);
|
|
new_cell->str += stringf("[%d]", idx);
|
|
if (new_cell->type == AST_PRIMITIVE) {
|
|
log_file_error(filename, location.first_line, "Cell arrays of primitives are currently not supported.\n");
|
|
} else {
|
|
log_assert(new_cell->children.at(0)->type == AST_CELLTYPE);
|
|
new_cell->children.at(0)->str = stringf("$array:%d:%d:%s", i, num, new_cell->children.at(0)->str.c_str());
|
|
}
|
|
}
|
|
|
|
goto apply_newNode;
|
|
}
|
|
|
|
// replace primitives with assignments
|
|
if (type == AST_PRIMITIVE)
|
|
{
|
|
if (children.size() < 2)
|
|
log_file_error(filename, location.first_line, "Insufficient number of arguments for primitive `%s'!\n", str.c_str());
|
|
|
|
std::vector<AstNode*> children_list;
|
|
for (auto child : children) {
|
|
log_assert(child->type == AST_ARGUMENT);
|
|
log_assert(child->children.size() == 1);
|
|
children_list.push_back(child->children[0]);
|
|
child->children.clear();
|
|
delete child;
|
|
}
|
|
children.clear();
|
|
|
|
if (str == "bufif0" || str == "bufif1" || str == "notif0" || str == "notif1")
|
|
{
|
|
if (children_list.size() != 3)
|
|
log_file_error(filename, location.first_line, "Invalid number of arguments for primitive `%s'!\n", str.c_str());
|
|
|
|
std::vector<RTLIL::State> z_const(1, RTLIL::State::Sz);
|
|
|
|
AstNode *mux_input = children_list.at(1);
|
|
if (str == "notif0" || str == "notif1") {
|
|
mux_input = new AstNode(AST_BIT_NOT, mux_input);
|
|
}
|
|
AstNode *node = new AstNode(AST_TERNARY, children_list.at(2));
|
|
if (str == "bufif0") {
|
|
node->children.push_back(AstNode::mkconst_bits(z_const, false));
|
|
node->children.push_back(mux_input);
|
|
} else {
|
|
node->children.push_back(mux_input);
|
|
node->children.push_back(AstNode::mkconst_bits(z_const, false));
|
|
}
|
|
|
|
str.clear();
|
|
type = AST_ASSIGN;
|
|
children.push_back(children_list.at(0));
|
|
children.back()->was_checked = true;
|
|
children.push_back(node);
|
|
did_something = true;
|
|
}
|
|
else if (str == "buf" || str == "not")
|
|
{
|
|
AstNode *input = children_list.back();
|
|
if (str == "not")
|
|
input = new AstNode(AST_BIT_NOT, input);
|
|
|
|
newNode = new AstNode(AST_GENBLOCK);
|
|
for (auto it = children_list.begin(); it != std::prev(children_list.end()); it++) {
|
|
newNode->children.push_back(new AstNode(AST_ASSIGN, *it, input->clone()));
|
|
newNode->children.back()->was_checked = true;
|
|
}
|
|
delete input;
|
|
|
|
did_something = true;
|
|
}
|
|
else
|
|
{
|
|
AstNodeType op_type = AST_NONE;
|
|
bool invert_results = false;
|
|
|
|
if (str == "and")
|
|
op_type = AST_BIT_AND;
|
|
if (str == "nand")
|
|
op_type = AST_BIT_AND, invert_results = true;
|
|
if (str == "or")
|
|
op_type = AST_BIT_OR;
|
|
if (str == "nor")
|
|
op_type = AST_BIT_OR, invert_results = true;
|
|
if (str == "xor")
|
|
op_type = AST_BIT_XOR;
|
|
if (str == "xnor")
|
|
op_type = AST_BIT_XOR, invert_results = true;
|
|
log_assert(op_type != AST_NONE);
|
|
|
|
AstNode *node = children_list[1];
|
|
if (op_type != AST_POS)
|
|
for (size_t i = 2; i < children_list.size(); i++) {
|
|
node = new AstNode(op_type, node, children_list[i]);
|
|
node->location = location;
|
|
}
|
|
if (invert_results)
|
|
node = new AstNode(AST_BIT_NOT, node);
|
|
|
|
str.clear();
|
|
type = AST_ASSIGN;
|
|
children.push_back(children_list[0]);
|
|
children.back()->was_checked = true;
|
|
children.push_back(node);
|
|
did_something = true;
|
|
}
|
|
}
|
|
|
|
// replace dynamic ranges in left-hand side expressions (e.g. "foo[bar] <= 1'b1;") with
|
|
// either a big case block that selects the correct single-bit assignment, or mask and
|
|
// shift operations.
|
|
if (type == AST_ASSIGN_EQ || type == AST_ASSIGN_LE)
|
|
{
|
|
if (children[0]->type != AST_IDENTIFIER || children[0]->children.size() == 0)
|
|
goto skip_dynamic_range_lvalue_expansion;
|
|
if (children[0]->children[0]->range_valid || did_something)
|
|
goto skip_dynamic_range_lvalue_expansion;
|
|
if (children[0]->id2ast == NULL || children[0]->id2ast->type != AST_WIRE)
|
|
goto skip_dynamic_range_lvalue_expansion;
|
|
if (!children[0]->id2ast->range_valid)
|
|
goto skip_dynamic_range_lvalue_expansion;
|
|
|
|
int source_width = children[0]->id2ast->range_left - children[0]->id2ast->range_right + 1;
|
|
int source_offset = children[0]->id2ast->range_right;
|
|
int result_width = 1;
|
|
AST::AstNode *member_node = get_struct_member(children[0]);
|
|
if (member_node) {
|
|
// Clamp chunk to range of member within struct/union.
|
|
log_assert(!source_offset && !children[0]->id2ast->range_swapped);
|
|
source_width = member_node->range_left - member_node->range_right + 1;
|
|
}
|
|
|
|
AstNode *shift_expr = NULL;
|
|
AstNode *range = children[0]->children[0];
|
|
|
|
if (range->children.size() == 1) {
|
|
shift_expr = range->children[0]->clone();
|
|
} else {
|
|
shift_expr = range->children[1]->clone();
|
|
AstNode *left_at_zero_ast = range->children[0]->clone();
|
|
AstNode *right_at_zero_ast = range->children[1]->clone();
|
|
while (left_at_zero_ast->simplify(true, true, false, stage, -1, false, false)) { }
|
|
while (right_at_zero_ast->simplify(true, true, false, stage, -1, false, false)) { }
|
|
if (left_at_zero_ast->type != AST_CONSTANT || right_at_zero_ast->type != AST_CONSTANT)
|
|
log_file_error(filename, location.first_line, "Unsupported expression on dynamic range select on signal `%s'!\n", str.c_str());
|
|
result_width = abs(int(left_at_zero_ast->integer - right_at_zero_ast->integer)) + 1;
|
|
delete left_at_zero_ast;
|
|
delete right_at_zero_ast;
|
|
}
|
|
|
|
bool use_case_method = false;
|
|
|
|
if (children[0]->id2ast->attributes.count(ID::nowrshmsk)) {
|
|
AstNode *node = children[0]->id2ast->attributes.at(ID::nowrshmsk);
|
|
while (node->simplify(true, false, false, stage, -1, false, false)) { }
|
|
if (node->type != AST_CONSTANT)
|
|
log_file_error(filename, location.first_line, "Non-constant value for `nowrshmsk' attribute on `%s'!\n", children[0]->id2ast->str.c_str());
|
|
if (node->asAttrConst().as_bool())
|
|
use_case_method = true;
|
|
}
|
|
|
|
if (!use_case_method && current_always->detect_latch(children[0]->str))
|
|
use_case_method = true;
|
|
|
|
if (use_case_method)
|
|
{
|
|
// big case block
|
|
|
|
did_something = true;
|
|
newNode = new AstNode(AST_CASE, shift_expr);
|
|
for (int i = 0; i < source_width; i++) {
|
|
int start_bit = source_offset + i;
|
|
int end_bit = std::min(start_bit+result_width,source_width) - 1;
|
|
AstNode *cond = new AstNode(AST_COND, mkconst_int(start_bit, true));
|
|
AstNode *lvalue = children[0]->clone();
|
|
lvalue->delete_children();
|
|
if (member_node)
|
|
lvalue->attributes[ID::wiretype] = member_node->clone();
|
|
lvalue->children.push_back(new AstNode(AST_RANGE,
|
|
mkconst_int(end_bit, true), mkconst_int(start_bit, true)));
|
|
cond->children.push_back(new AstNode(AST_BLOCK, new AstNode(type, lvalue, children[1]->clone())));
|
|
newNode->children.push_back(cond);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// mask and shift operations, disabled for now
|
|
|
|
AstNode *wire_mask = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(source_width-1, true), mkconst_int(0, true)));
|
|
wire_mask->str = stringf("$bitselwrite$mask$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++);
|
|
wire_mask->attributes[ID::nosync] = AstNode::mkconst_int(1, false);
|
|
wire_mask->is_logic = true;
|
|
while (wire_mask->simplify(true, false, false, 1, -1, false, false)) { }
|
|
current_ast_mod->children.push_back(wire_mask);
|
|
|
|
AstNode *wire_data = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(source_width-1, true), mkconst_int(0, true)));
|
|
wire_data->str = stringf("$bitselwrite$data$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++);
|
|
wire_data->attributes[ID::nosync] = AstNode::mkconst_int(1, false);
|
|
wire_data->is_logic = true;
|
|
while (wire_data->simplify(true, false, false, 1, -1, false, false)) { }
|
|
current_ast_mod->children.push_back(wire_data);
|
|
|
|
int shamt_width_hint = -1;
|
|
bool shamt_sign_hint = true;
|
|
shift_expr->detectSignWidth(shamt_width_hint, shamt_sign_hint);
|
|
|
|
AstNode *wire_sel = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(shamt_width_hint-1, true), mkconst_int(0, true)));
|
|
wire_sel->str = stringf("$bitselwrite$sel$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++);
|
|
wire_sel->attributes[ID::nosync] = AstNode::mkconst_int(1, false);
|
|
wire_sel->is_logic = true;
|
|
wire_sel->is_signed = shamt_sign_hint;
|
|
while (wire_sel->simplify(true, false, false, 1, -1, false, false)) { }
|
|
current_ast_mod->children.push_back(wire_sel);
|
|
|
|
did_something = true;
|
|
newNode = new AstNode(AST_BLOCK);
|
|
|
|
AstNode *lvalue = children[0]->clone();
|
|
lvalue->delete_children();
|
|
if (member_node)
|
|
lvalue->attributes[ID::wiretype] = member_node->clone();
|
|
|
|
AstNode *ref_mask = new AstNode(AST_IDENTIFIER);
|
|
ref_mask->str = wire_mask->str;
|
|
ref_mask->id2ast = wire_mask;
|
|
ref_mask->was_checked = true;
|
|
|
|
AstNode *ref_data = new AstNode(AST_IDENTIFIER);
|
|
ref_data->str = wire_data->str;
|
|
ref_data->id2ast = wire_data;
|
|
ref_data->was_checked = true;
|
|
|
|
AstNode *ref_sel = new AstNode(AST_IDENTIFIER);
|
|
ref_sel->str = wire_sel->str;
|
|
ref_sel->id2ast = wire_sel;
|
|
ref_sel->was_checked = true;
|
|
|
|
AstNode *old_data = lvalue->clone();
|
|
if (type == AST_ASSIGN_LE)
|
|
old_data->lookahead = true;
|
|
|
|
AstNode *s = new AstNode(AST_ASSIGN_EQ, ref_sel->clone(), shift_expr);
|
|
newNode->children.push_back(s);
|
|
|
|
AstNode *shamt = ref_sel;
|
|
|
|
// convert to signed while preserving the sign and value
|
|
shamt = new AstNode(AST_CAST_SIZE, mkconst_int(shamt_width_hint + 1, true), shamt);
|
|
shamt = new AstNode(AST_TO_SIGNED, shamt);
|
|
|
|
// offset the shift amount by the lower bound of the dimension
|
|
int start_bit = source_offset;
|
|
shamt = new AstNode(AST_SUB, shamt, mkconst_int(start_bit, true));
|
|
|
|
// reflect the shift amount if the dimension is swapped
|
|
if (children[0]->id2ast->range_swapped)
|
|
shamt = new AstNode(AST_SUB, mkconst_int(source_width - result_width, true), shamt);
|
|
|
|
// AST_SHIFT uses negative amounts for shifting left
|
|
shamt = new AstNode(AST_NEG, shamt);
|
|
|
|
AstNode *t;
|
|
|
|
t = mkconst_bits(std::vector<RTLIL::State>(result_width, State::S1), false);
|
|
t = new AstNode(AST_SHIFT, t, shamt->clone());
|
|
t = new AstNode(AST_ASSIGN_EQ, ref_mask->clone(), t);
|
|
newNode->children.push_back(t);
|
|
|
|
t = new AstNode(AST_BIT_AND, mkconst_bits(std::vector<RTLIL::State>(result_width, State::S1), false), children[1]->clone());
|
|
t = new AstNode(AST_SHIFT, t, shamt);
|
|
t = new AstNode(AST_ASSIGN_EQ, ref_data->clone(), t);
|
|
newNode->children.push_back(t);
|
|
|
|
t = new AstNode(AST_BIT_AND, old_data, new AstNode(AST_BIT_NOT, ref_mask));
|
|
t = new AstNode(AST_BIT_OR, t, ref_data);
|
|
t = new AstNode(type, lvalue, t);
|
|
newNode->children.push_back(t);
|
|
}
|
|
|
|
goto apply_newNode;
|
|
}
|
|
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, false, false, 1, -1, false, 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, false, false, 1, -1, false, 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;
|
|
}
|
|
|
|
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;
|
|
|
|
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));
|
|
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) {
|
|
newNode = new AstNode(AST_MEMRD, children[0]->children[0]->clone());
|
|
newNode->str = str;
|
|
newNode->id2ast = id2ast;
|
|
goto apply_newNode;
|
|
}
|
|
|
|
// assignment with nontrivial member in left-hand concat expression -> split assignment
|
|
if ((type == AST_ASSIGN_EQ || type == AST_ASSIGN_LE) && children[0]->type == AST_CONCAT && width_hint > 0)
|
|
{
|
|
bool found_nontrivial_member = false;
|
|
|
|
for (auto child : children[0]->children) {
|
|
if (child->type == AST_IDENTIFIER && child->id2ast != NULL && child->id2ast->type == AST_MEMORY)
|
|
found_nontrivial_member = true;
|
|
}
|
|
|
|
if (found_nontrivial_member)
|
|
{
|
|
newNode = new AstNode(AST_BLOCK);
|
|
|
|
AstNode *wire_tmp = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(width_hint-1, true), mkconst_int(0, true)));
|
|
wire_tmp->str = stringf("$splitcmplxassign$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++);
|
|
current_ast_mod->children.push_back(wire_tmp);
|
|
current_scope[wire_tmp->str] = wire_tmp;
|
|
wire_tmp->attributes[ID::nosync] = AstNode::mkconst_int(1, false);
|
|
while (wire_tmp->simplify(true, false, false, 1, -1, false, false)) { }
|
|
wire_tmp->is_logic = true;
|
|
|
|
AstNode *wire_tmp_id = new AstNode(AST_IDENTIFIER);
|
|
wire_tmp_id->str = wire_tmp->str;
|
|
|
|
newNode->children.push_back(new AstNode(AST_ASSIGN_EQ, wire_tmp_id, children[1]->clone()));
|
|
newNode->children.back()->was_checked = true;
|
|
|
|
int cursor = 0;
|
|
for (auto child : children[0]->children)
|
|
{
|
|
int child_width_hint = -1;
|
|
bool child_sign_hint = true;
|
|
child->detectSignWidth(child_width_hint, child_sign_hint);
|
|
|
|
AstNode *rhs = wire_tmp_id->clone();
|
|
rhs->children.push_back(new AstNode(AST_RANGE, AstNode::mkconst_int(cursor+child_width_hint-1, true), AstNode::mkconst_int(cursor, true)));
|
|
newNode->children.push_back(new AstNode(type, child->clone(), rhs));
|
|
|
|
cursor += child_width_hint;
|
|
}
|
|
|
|
goto apply_newNode;
|
|
}
|
|
}
|
|
|
|
// assignment with memory in left-hand side expression -> replace with memory write port
|
|
if (stage > 1 && (type == AST_ASSIGN_EQ || type == AST_ASSIGN_LE) && children[0]->type == AST_IDENTIFIER &&
|
|
children[0]->id2ast && children[0]->id2ast->type == AST_MEMORY && children[0]->id2ast->children.size() >= 2 &&
|
|
children[0]->id2ast->children[0]->range_valid && children[0]->id2ast->children[1]->range_valid &&
|
|
(children[0]->children.size() == 1 || children[0]->children.size() == 2) && children[0]->children[0]->type == AST_RANGE)
|
|
{
|
|
std::stringstream sstr;
|
|
sstr << "$memwr$" << children[0]->str << "$" << RTLIL::encode_filename(filename) << ":" << location.first_line << "$" << (autoidx++);
|
|
std::string id_addr = sstr.str() + "_ADDR", id_data = sstr.str() + "_DATA", id_en = sstr.str() + "_EN";
|
|
|
|
int mem_width, mem_size, addr_bits;
|
|
bool mem_signed = children[0]->id2ast->is_signed;
|
|
children[0]->id2ast->meminfo(mem_width, mem_size, addr_bits);
|
|
|
|
newNode = new AstNode(AST_BLOCK);
|
|
AstNode *defNode = new AstNode(AST_BLOCK);
|
|
|
|
int data_range_left = children[0]->id2ast->children[0]->range_left;
|
|
int data_range_right = children[0]->id2ast->children[0]->range_right;
|
|
int mem_data_range_offset = std::min(data_range_left, data_range_right);
|
|
|
|
int addr_width_hint = -1;
|
|
bool addr_sign_hint = true;
|
|
children[0]->children[0]->children[0]->detectSignWidthWorker(addr_width_hint, addr_sign_hint);
|
|
addr_bits = std::max(addr_bits, addr_width_hint);
|
|
|
|
std::vector<RTLIL::State> x_bits_addr, x_bits_data, set_bits_en;
|
|
for (int i = 0; i < addr_bits; i++)
|
|
x_bits_addr.push_back(RTLIL::State::Sx);
|
|
for (int i = 0; i < mem_width; i++)
|
|
x_bits_data.push_back(RTLIL::State::Sx);
|
|
for (int i = 0; i < mem_width; i++)
|
|
set_bits_en.push_back(RTLIL::State::S1);
|
|
|
|
AstNode *node_addr = nullptr;
|
|
if (children[0]->children[0]->children[0]->isConst()) {
|
|
node_addr = children[0]->children[0]->children[0]->clone();
|
|
} else {
|
|
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->was_checked = true;
|
|
current_ast_mod->children.push_back(wire_addr);
|
|
current_scope[wire_addr->str] = wire_addr;
|
|
while (wire_addr->simplify(true, false, false, 1, -1, false, false)) { }
|
|
|
|
AstNode *assign_addr = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), mkconst_bits(x_bits_addr, false));
|
|
assign_addr->children[0]->str = id_addr;
|
|
assign_addr->children[0]->was_checked = true;
|
|
defNode->children.push_back(assign_addr);
|
|
|
|
assign_addr = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), children[0]->children[0]->children[0]->clone());
|
|
assign_addr->children[0]->str = id_addr;
|
|
assign_addr->children[0]->was_checked = true;
|
|
newNode->children.push_back(assign_addr);
|
|
|
|
node_addr = new AstNode(AST_IDENTIFIER);
|
|
node_addr->str = id_addr;
|
|
}
|
|
|
|
AstNode *node_data = nullptr;
|
|
if (children[0]->children.size() == 1 && children[1]->isConst()) {
|
|
node_data = children[1]->clone();
|
|
} else {
|
|
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->was_checked = true;
|
|
wire_data->is_signed = mem_signed;
|
|
current_ast_mod->children.push_back(wire_data);
|
|
current_scope[wire_data->str] = wire_data;
|
|
while (wire_data->simplify(true, false, false, 1, -1, false, false)) { }
|
|
|
|
AstNode *assign_data = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), mkconst_bits(x_bits_data, false));
|
|
assign_data->children[0]->str = id_data;
|
|
assign_data->children[0]->was_checked = true;
|
|
defNode->children.push_back(assign_data);
|
|
|
|
node_data = new AstNode(AST_IDENTIFIER);
|
|
node_data->str = id_data;
|
|
}
|
|
|
|
AstNode *wire_en = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(mem_width-1, true), mkconst_int(0, true)));
|
|
wire_en->str = id_en;
|
|
wire_en->was_checked = true;
|
|
current_ast_mod->children.push_back(wire_en);
|
|
current_scope[wire_en->str] = wire_en;
|
|
while (wire_en->simplify(true, false, false, 1, -1, false, false)) { }
|
|
|
|
AstNode *assign_en_first = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), mkconst_int(0, false, mem_width));
|
|
assign_en_first->children[0]->str = id_en;
|
|
assign_en_first->children[0]->was_checked = true;
|
|
defNode->children.push_back(assign_en_first);
|
|
|
|
AstNode *node_en = new AstNode(AST_IDENTIFIER);
|
|
node_en->str = id_en;
|
|
|
|
if (!defNode->children.empty())
|
|
current_top_block->children.insert(current_top_block->children.begin(), defNode);
|
|
else
|
|
delete defNode;
|
|
|
|
AstNode *assign_data = nullptr;
|
|
AstNode *assign_en = nullptr;
|
|
if (children[0]->children.size() == 2)
|
|
{
|
|
if (children[0]->children[1]->range_valid)
|
|
{
|
|
int offset = children[0]->children[1]->range_right;
|
|
int width = children[0]->children[1]->range_left - offset + 1;
|
|
offset -= mem_data_range_offset;
|
|
|
|
std::vector<RTLIL::State> padding_x(offset, RTLIL::State::Sx);
|
|
|
|
assign_data = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER),
|
|
new AstNode(AST_CONCAT, mkconst_bits(padding_x, false), children[1]->clone()));
|
|
assign_data->children[0]->str = id_data;
|
|
assign_data->children[0]->was_checked = true;
|
|
|
|
for (int i = 0; i < mem_width; i++)
|
|
set_bits_en[i] = offset <= i && i < offset+width ? RTLIL::State::S1 : RTLIL::State::S0;
|
|
assign_en = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), mkconst_bits(set_bits_en, false));
|
|
assign_en->children[0]->str = id_en;
|
|
assign_en->children[0]->was_checked = true;
|
|
}
|
|
else
|
|
{
|
|
AstNode *the_range = children[0]->children[1];
|
|
AstNode *left_at_zero_ast = the_range->children[0]->clone();
|
|
AstNode *right_at_zero_ast = the_range->children.size() >= 2 ? the_range->children[1]->clone() : left_at_zero_ast->clone();
|
|
AstNode *offset_ast = right_at_zero_ast->clone();
|
|
|
|
if (mem_data_range_offset)
|
|
offset_ast = new AstNode(AST_SUB, offset_ast, mkconst_int(mem_data_range_offset, true));
|
|
|
|
while (left_at_zero_ast->simplify(true, true, false, 1, -1, false, false)) { }
|
|
while (right_at_zero_ast->simplify(true, true, false, 1, -1, false, false)) { }
|
|
if (left_at_zero_ast->type != AST_CONSTANT || right_at_zero_ast->type != AST_CONSTANT)
|
|
log_file_error(filename, location.first_line, "Unsupported expression on dynamic range select on signal `%s'!\n", str.c_str());
|
|
int width = abs(int(left_at_zero_ast->integer - right_at_zero_ast->integer)) + 1;
|
|
|
|
assign_data = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER),
|
|
new AstNode(AST_SHIFT_LEFT, children[1]->clone(), offset_ast->clone()));
|
|
assign_data->children[0]->str = id_data;
|
|
assign_data->children[0]->was_checked = true;
|
|
|
|
for (int i = 0; i < mem_width; i++)
|
|
set_bits_en[i] = i < width ? RTLIL::State::S1 : RTLIL::State::S0;
|
|
assign_en = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER),
|
|
new AstNode(AST_SHIFT_LEFT, mkconst_bits(set_bits_en, false), offset_ast->clone()));
|
|
assign_en->children[0]->str = id_en;
|
|
assign_en->children[0]->was_checked = true;
|
|
|
|
delete left_at_zero_ast;
|
|
delete right_at_zero_ast;
|
|
delete offset_ast;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!(children[0]->children.size() == 1 && children[1]->isConst())) {
|
|
assign_data = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), children[1]->clone());
|
|
assign_data->children[0]->str = id_data;
|
|
assign_data->children[0]->was_checked = true;
|
|
}
|
|
|
|
assign_en = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), mkconst_bits(set_bits_en, false));
|
|
assign_en->children[0]->str = id_en;
|
|
assign_en->children[0]->was_checked = true;
|
|
}
|
|
if (assign_data)
|
|
newNode->children.push_back(assign_data);
|
|
if (assign_en)
|
|
newNode->children.push_back(assign_en);
|
|
|
|
AstNode *wrnode;
|
|
if (current_always->type == AST_INITIAL)
|
|
wrnode = new AstNode(AST_MEMINIT, node_addr, node_data, node_en, mkconst_int(1, false));
|
|
else
|
|
wrnode = new AstNode(AST_MEMWR, node_addr, node_data, node_en);
|
|
wrnode->str = children[0]->str;
|
|
wrnode->id2ast = children[0]->id2ast;
|
|
wrnode->location = location;
|
|
if (wrnode->type == AST_MEMWR) {
|
|
int portid = current_memwr_count[wrnode->str]++;
|
|
wrnode->children.push_back(mkconst_int(portid, false));
|
|
std::vector<RTLIL::State> priority_mask;
|
|
for (int i = 0; i < portid; i++) {
|
|
bool has_prio = current_memwr_visible[wrnode->str].count(i);
|
|
priority_mask.push_back(State(has_prio));
|
|
}
|
|
wrnode->children.push_back(mkconst_bits(priority_mask, false));
|
|
current_memwr_visible[wrnode->str].insert(portid);
|
|
current_always->children.push_back(wrnode);
|
|
} else {
|
|
current_ast_mod->children.push_back(wrnode);
|
|
}
|
|
|
|
if (newNode->children.empty()) {
|
|
delete newNode;
|
|
newNode = new AstNode();
|
|
}
|
|
goto apply_newNode;
|
|
}
|
|
|
|
// replace function and task calls with the code from the function or task
|
|
if ((type == AST_FCALL || type == AST_TCALL) && !str.empty())
|
|
{
|
|
if (type == AST_FCALL)
|
|
{
|
|
if (str == "\\$initstate")
|
|
{
|
|
int myidx = autoidx++;
|
|
|
|
AstNode *wire = new AstNode(AST_WIRE);
|
|
wire->str = stringf("$initstate$%d_wire", myidx);
|
|
current_ast_mod->children.push_back(wire);
|
|
while (wire->simplify(true, false, false, 1, -1, false, false)) { }
|
|
|
|
AstNode *cell = new AstNode(AST_CELL, new AstNode(AST_CELLTYPE), new AstNode(AST_ARGUMENT, new AstNode(AST_IDENTIFIER)));
|
|
cell->str = stringf("$initstate$%d", myidx);
|
|
cell->children[0]->str = "$initstate";
|
|
cell->children[1]->str = "\\Y";
|
|
cell->children[1]->children[0]->str = wire->str;
|
|
cell->children[1]->children[0]->id2ast = wire;
|
|
current_ast_mod->children.push_back(cell);
|
|
while (cell->simplify(true, false, false, 1, -1, false, false)) { }
|
|
|
|
newNode = new AstNode(AST_IDENTIFIER);
|
|
newNode->str = wire->str;
|
|
newNode->id2ast = wire;
|
|
goto apply_newNode;
|
|
}
|
|
|
|
if (str == "\\$past")
|
|
{
|
|
if (width_hint < 0)
|
|
goto replace_fcall_later;
|
|
|
|
int num_steps = 1;
|
|
|
|
if (GetSize(children) != 1 && GetSize(children) != 2)
|
|
log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 1 or 2.\n",
|
|
RTLIL::unescape_id(str).c_str(), int(children.size()));
|
|
|
|
if (!current_always_clocked)
|
|
log_file_error(filename, location.first_line, "System function %s is only allowed in clocked blocks.\n",
|
|
RTLIL::unescape_id(str).c_str());
|
|
|
|
if (GetSize(children) == 2)
|
|
{
|
|
AstNode *buf = children[1]->clone();
|
|
while (buf->simplify(true, false, false, stage, -1, false, false)) { }
|
|
if (buf->type != AST_CONSTANT)
|
|
log_file_error(filename, location.first_line, "Failed to evaluate system function `%s' with non-constant value.\n", str.c_str());
|
|
|
|
num_steps = buf->asInt(true);
|
|
delete buf;
|
|
}
|
|
|
|
AstNode *block = nullptr;
|
|
|
|
for (auto child : current_always->children)
|
|
if (child->type == AST_BLOCK)
|
|
block = child;
|
|
|
|
log_assert(block != nullptr);
|
|
|
|
if (num_steps == 0) {
|
|
newNode = children[0]->clone();
|
|
goto apply_newNode;
|
|
}
|
|
|
|
int myidx = autoidx++;
|
|
AstNode *outreg = nullptr;
|
|
|
|
for (int i = 0; i < num_steps; i++)
|
|
{
|
|
AstNode *reg = new AstNode(AST_WIRE, new AstNode(AST_RANGE,
|
|
mkconst_int(width_hint-1, true), mkconst_int(0, true)));
|
|
|
|
reg->str = stringf("$past$%s:%d$%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, myidx, i);
|
|
reg->is_reg = true;
|
|
reg->is_signed = sign_hint;
|
|
|
|
current_ast_mod->children.push_back(reg);
|
|
|
|
while (reg->simplify(true, false, false, 1, -1, false, false)) { }
|
|
|
|
AstNode *regid = new AstNode(AST_IDENTIFIER);
|
|
regid->str = reg->str;
|
|
regid->id2ast = reg;
|
|
regid->was_checked = true;
|
|
|
|
AstNode *rhs = nullptr;
|
|
|
|
if (outreg == nullptr) {
|
|
rhs = children.at(0)->clone();
|
|
} else {
|
|
rhs = new AstNode(AST_IDENTIFIER);
|
|
rhs->str = outreg->str;
|
|
rhs->id2ast = outreg;
|
|
}
|
|
|
|
block->children.push_back(new AstNode(AST_ASSIGN_LE, regid, rhs));
|
|
outreg = reg;
|
|
}
|
|
|
|
newNode = new AstNode(AST_IDENTIFIER);
|
|
newNode->str = outreg->str;
|
|
newNode->id2ast = outreg;
|
|
goto apply_newNode;
|
|
}
|
|
|
|
if (str == "\\$stable" || str == "\\$rose" || str == "\\$fell" || str == "\\$changed")
|
|
{
|
|
if (GetSize(children) != 1)
|
|
log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 1.\n",
|
|
RTLIL::unescape_id(str).c_str(), int(children.size()));
|
|
|
|
if (!current_always_clocked)
|
|
log_file_error(filename, location.first_line, "System function %s is only allowed in clocked blocks.\n",
|
|
RTLIL::unescape_id(str).c_str());
|
|
|
|
AstNode *present = children.at(0)->clone();
|
|
AstNode *past = clone();
|
|
past->str = "\\$past";
|
|
|
|
if (str == "\\$stable")
|
|
newNode = new AstNode(AST_EQ, past, present);
|
|
|
|
else if (str == "\\$changed")
|
|
newNode = new AstNode(AST_NE, past, present);
|
|
|
|
else if (str == "\\$rose")
|
|
newNode = new AstNode(AST_LOGIC_AND,
|
|
new AstNode(AST_LOGIC_NOT, new AstNode(AST_BIT_AND, past, mkconst_int(1,false))),
|
|
new AstNode(AST_BIT_AND, present, mkconst_int(1,false)));
|
|
|
|
else if (str == "\\$fell")
|
|
newNode = new AstNode(AST_LOGIC_AND,
|
|
new AstNode(AST_BIT_AND, past, mkconst_int(1,false)),
|
|
new AstNode(AST_LOGIC_NOT, new AstNode(AST_BIT_AND, present, mkconst_int(1,false))));
|
|
|
|
else
|
|
log_abort();
|
|
|
|
goto apply_newNode;
|
|
}
|
|
|
|
// $anyconst and $anyseq are mapped in AstNode::genRTLIL()
|
|
if (str == "\\$anyconst" || str == "\\$anyseq" || str == "\\$allconst" || str == "\\$allseq") {
|
|
recursion_counter--;
|
|
return false;
|
|
}
|
|
|
|
if (str == "\\$clog2")
|
|
{
|
|
if (children.size() != 1)
|
|
log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 1.\n",
|
|
RTLIL::unescape_id(str).c_str(), int(children.size()));
|
|
|
|
AstNode *buf = children[0]->clone();
|
|
while (buf->simplify(true, false, false, stage, width_hint, sign_hint, false)) { }
|
|
if (buf->type != AST_CONSTANT)
|
|
log_file_error(filename, location.first_line, "Failed to evaluate system function `%s' with non-constant value.\n", str.c_str());
|
|
|
|
RTLIL::Const arg_value = buf->bitsAsConst();
|
|
if (arg_value.as_bool())
|
|
arg_value = const_sub(arg_value, 1, false, false, GetSize(arg_value));
|
|
delete buf;
|
|
|
|
uint32_t result = 0;
|
|
for (size_t i = 0; i < arg_value.bits.size(); i++)
|
|
if (arg_value.bits.at(i) == RTLIL::State::S1)
|
|
result = i + 1;
|
|
|
|
newNode = mkconst_int(result, true);
|
|
goto apply_newNode;
|
|
}
|
|
|
|
if (str == "\\$size" || str == "\\$bits" || str == "\\$high" || str == "\\$low" || str == "\\$left" || str == "\\$right")
|
|
{
|
|
int dim = 1;
|
|
if (str == "\\$bits") {
|
|
if (children.size() != 1)
|
|
log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 1.\n",
|
|
RTLIL::unescape_id(str).c_str(), int(children.size()));
|
|
} else {
|
|
if (children.size() != 1 && children.size() != 2)
|
|
log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 1 or 2.\n",
|
|
RTLIL::unescape_id(str).c_str(), int(children.size()));
|
|
if (children.size() == 2) {
|
|
AstNode *buf = children[1]->clone();
|
|
// Evaluate constant expression
|
|
while (buf->simplify(true, false, false, stage, width_hint, sign_hint, false)) { }
|
|
dim = buf->asInt(false);
|
|
delete buf;
|
|
}
|
|
}
|
|
AstNode *buf = children[0]->clone();
|
|
int mem_depth = 1;
|
|
int result, high = 0, low = 0, left = 0, right = 0, width = 1; // defaults for a simple wire
|
|
AstNode *id_ast = NULL;
|
|
|
|
// Is this needed?
|
|
//while (buf->simplify(true, false, false, stage, width_hint, sign_hint, false)) { }
|
|
buf->detectSignWidth(width_hint, sign_hint);
|
|
|
|
if (buf->type == AST_IDENTIFIER) {
|
|
id_ast = buf->id2ast;
|
|
if (id_ast == NULL && current_scope.count(buf->str))
|
|
id_ast = current_scope.at(buf->str);
|
|
if (!id_ast)
|
|
log_file_error(filename, location.first_line, "Failed to resolve identifier %s for width detection!\n", buf->str.c_str());
|
|
|
|
// Check for item in packed struct / union
|
|
AST::AstNode *item_node = get_struct_member(buf);
|
|
if (id_ast->type == AST_WIRE && item_node) {
|
|
// The dimension of the original array expression is saved in the 'integer' field
|
|
dim += buf->integer;
|
|
if (item_node->multirange_dimensions.empty()) {
|
|
if (dim != 1)
|
|
log_file_error(filename, location.first_line, "Dimension %d out of range in `%s', as it only has one dimension!\n", dim, item_node->str.c_str());
|
|
left = high = item_node->range_left;
|
|
right = low = item_node->range_right;
|
|
} else {
|
|
int dims = GetSize(item_node->multirange_dimensions)/2;
|
|
if (dim < 1 || dim > dims)
|
|
log_file_error(filename, location.first_line, "Dimension %d out of range in `%s', as it only has dimensions 1..%d!\n", dim, item_node->str.c_str(), dims);
|
|
right = low = get_struct_range_offset(item_node, dim - 1);
|
|
left = high = low + get_struct_range_width(item_node, dim - 1) - 1;
|
|
if (item_node->multirange_swapped[dim - 1]) {
|
|
std::swap(left, right);
|
|
}
|
|
for (int i = dim; i < dims; i++) {
|
|
mem_depth *= get_struct_range_width(item_node, i);
|
|
}
|
|
}
|
|
}
|
|
// Otherwise, we have 4 cases:
|
|
// wire x; ==> AST_WIRE, no AST_RANGE children
|
|
// wire [1:0]x; ==> AST_WIRE, AST_RANGE children
|
|
// wire [1:0]x[1:0]; ==> AST_MEMORY, two AST_RANGE children (1st for packed, 2nd for unpacked)
|
|
// wire [1:0]x[1:0][1:0]; ==> AST_MEMORY, one AST_RANGE child (0) for packed, then AST_MULTIRANGE child (1) for unpacked
|
|
// (updated: actually by the time we are here, AST_MULTIRANGE is converted into one big AST_RANGE)
|
|
// case 0 handled by default
|
|
else if ((id_ast->type == AST_WIRE || id_ast->type == AST_MEMORY) && id_ast->children.size() > 0) {
|
|
// handle packed array left/right for case 1, and cases 2/3 when requesting the last dimension (packed side)
|
|
AstNode *wire_range = id_ast->children[0];
|
|
left = wire_range->children[0]->integer;
|
|
right = wire_range->children[1]->integer;
|
|
high = max(left, right);
|
|
low = min(left, right);
|
|
}
|
|
if (id_ast->type == AST_MEMORY) {
|
|
// a slice of our identifier means we advance to the next dimension, e.g. $size(a[3])
|
|
if (buf->children.size() > 0) {
|
|
// something is hanging below this identifier
|
|
if (buf->children[0]->type == AST_RANGE && buf->integer == 0)
|
|
// if integer == 0, this node was originally created as AST_RANGE so it's dimension is 1
|
|
dim++;
|
|
// more than one range, e.g. $size(a[3][2])
|
|
else // created an AST_MULTIRANGE, converted to AST_RANGE, but original dimension saved in 'integer' field
|
|
dim += buf->integer; // increment by multirange size
|
|
}
|
|
|
|
// We got here only if the argument is a memory
|
|
// Otherwise $size() and $bits() return the expression width
|
|
AstNode *mem_range = id_ast->children[1];
|
|
if (str == "\\$bits") {
|
|
if (mem_range->type == AST_RANGE) {
|
|
if (!mem_range->range_valid)
|
|
log_file_error(filename, location.first_line, "Failed to detect width of memory access `%s'!\n", buf->str.c_str());
|
|
mem_depth = mem_range->range_left - mem_range->range_right + 1;
|
|
} else
|
|
log_file_error(filename, location.first_line, "Unknown memory depth AST type in `%s'!\n", buf->str.c_str());
|
|
} else {
|
|
// $size(), $left(), $right(), $high(), $low()
|
|
int dims = 1;
|
|
if (mem_range->type == AST_RANGE) {
|
|
if (id_ast->multirange_dimensions.empty()) {
|
|
if (!mem_range->range_valid)
|
|
log_file_error(filename, location.first_line, "Failed to detect width of memory access `%s'!\n", buf->str.c_str());
|
|
if (dim == 1) {
|
|
left = mem_range->range_right;
|
|
right = mem_range->range_left;
|
|
high = max(left, right);
|
|
low = min(left, right);
|
|
}
|
|
} else {
|
|
dims = GetSize(id_ast->multirange_dimensions)/2;
|
|
if (dim <= dims) {
|
|
width_hint = id_ast->multirange_dimensions[2*dim-1];
|
|
high = id_ast->multirange_dimensions[2*dim-2] + id_ast->multirange_dimensions[2*dim-1] - 1;
|
|
low = id_ast->multirange_dimensions[2*dim-2];
|
|
if (id_ast->multirange_swapped[dim-1]) {
|
|
left = low;
|
|
right = high;
|
|
} else {
|
|
right = low;
|
|
left = high;
|
|
}
|
|
} else if ((dim > dims+1) || (dim < 0))
|
|
log_file_error(filename, location.first_line, "Dimension %d out of range in `%s', as it only has dimensions 1..%d!\n", dim, buf->str.c_str(), dims+1);
|
|
}
|
|
} else {
|
|
log_file_error(filename, location.first_line, "Unknown memory depth AST type in `%s'!\n", buf->str.c_str());
|
|
}
|
|
}
|
|
}
|
|
width = high - low + 1;
|
|
} else {
|
|
width = width_hint;
|
|
}
|
|
delete buf;
|
|
if (str == "\\$high")
|
|
result = high;
|
|
else if (str == "\\$low")
|
|
result = low;
|
|
else if (str == "\\$left")
|
|
result = left;
|
|
else if (str == "\\$right")
|
|
result = right;
|
|
else if (str == "\\$size")
|
|
result = width;
|
|
else { // str == "\\$bits"
|
|
result = width * mem_depth;
|
|
}
|
|
newNode = mkconst_int(result, true);
|
|
goto apply_newNode;
|
|
}
|
|
|
|
if (str == "\\$ln" || str == "\\$log10" || str == "\\$exp" || str == "\\$sqrt" || str == "\\$pow" ||
|
|
str == "\\$floor" || str == "\\$ceil" || str == "\\$sin" || str == "\\$cos" || str == "\\$tan" ||
|
|
str == "\\$asin" || str == "\\$acos" || str == "\\$atan" || str == "\\$atan2" || str == "\\$hypot" ||
|
|
str == "\\$sinh" || str == "\\$cosh" || str == "\\$tanh" || str == "\\$asinh" || str == "\\$acosh" || str == "\\$atanh" ||
|
|
str == "\\$rtoi" || str == "\\$itor")
|
|
{
|
|
bool func_with_two_arguments = str == "\\$pow" || str == "\\$atan2" || str == "\\$hypot";
|
|
double x = 0, y = 0;
|
|
|
|
if (func_with_two_arguments) {
|
|
if (children.size() != 2)
|
|
log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 2.\n",
|
|
RTLIL::unescape_id(str).c_str(), int(children.size()));
|
|
} else {
|
|
if (children.size() != 1)
|
|
log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 1.\n",
|
|
RTLIL::unescape_id(str).c_str(), int(children.size()));
|
|
}
|
|
|
|
if (children.size() >= 1) {
|
|
while (children[0]->simplify(true, false, false, stage, width_hint, sign_hint, false)) { }
|
|
if (!children[0]->isConst())
|
|
log_file_error(filename, location.first_line, "Failed to evaluate system function `%s' with non-constant argument.\n",
|
|
RTLIL::unescape_id(str).c_str());
|
|
int child_width_hint = width_hint;
|
|
bool child_sign_hint = sign_hint;
|
|
children[0]->detectSignWidth(child_width_hint, child_sign_hint);
|
|
x = children[0]->asReal(child_sign_hint);
|
|
}
|
|
|
|
if (children.size() >= 2) {
|
|
while (children[1]->simplify(true, false, false, stage, width_hint, sign_hint, false)) { }
|
|
if (!children[1]->isConst())
|
|
log_file_error(filename, location.first_line, "Failed to evaluate system function `%s' with non-constant argument.\n",
|
|
RTLIL::unescape_id(str).c_str());
|
|
int child_width_hint = width_hint;
|
|
bool child_sign_hint = sign_hint;
|
|
children[1]->detectSignWidth(child_width_hint, child_sign_hint);
|
|
y = children[1]->asReal(child_sign_hint);
|
|
}
|
|
|
|
if (str == "\\$rtoi") {
|
|
newNode = AstNode::mkconst_int(x, true);
|
|
} else {
|
|
newNode = new AstNode(AST_REALVALUE);
|
|
if (str == "\\$ln") newNode->realvalue = ::log(x);
|
|
else if (str == "\\$log10") newNode->realvalue = ::log10(x);
|
|
else if (str == "\\$exp") newNode->realvalue = ::exp(x);
|
|
else if (str == "\\$sqrt") newNode->realvalue = ::sqrt(x);
|
|
else if (str == "\\$pow") newNode->realvalue = ::pow(x, y);
|
|
else if (str == "\\$floor") newNode->realvalue = ::floor(x);
|
|
else if (str == "\\$ceil") newNode->realvalue = ::ceil(x);
|
|
else if (str == "\\$sin") newNode->realvalue = ::sin(x);
|
|
else if (str == "\\$cos") newNode->realvalue = ::cos(x);
|
|
else if (str == "\\$tan") newNode->realvalue = ::tan(x);
|
|
else if (str == "\\$asin") newNode->realvalue = ::asin(x);
|
|
else if (str == "\\$acos") newNode->realvalue = ::acos(x);
|
|
else if (str == "\\$atan") newNode->realvalue = ::atan(x);
|
|
else if (str == "\\$atan2") newNode->realvalue = ::atan2(x, y);
|
|
else if (str == "\\$hypot") newNode->realvalue = ::hypot(x, y);
|
|
else if (str == "\\$sinh") newNode->realvalue = ::sinh(x);
|
|
else if (str == "\\$cosh") newNode->realvalue = ::cosh(x);
|
|
else if (str == "\\$tanh") newNode->realvalue = ::tanh(x);
|
|
else if (str == "\\$asinh") newNode->realvalue = ::asinh(x);
|
|
else if (str == "\\$acosh") newNode->realvalue = ::acosh(x);
|
|
else if (str == "\\$atanh") newNode->realvalue = ::atanh(x);
|
|
else if (str == "\\$itor") newNode->realvalue = x;
|
|
else log_abort();
|
|
}
|
|
goto apply_newNode;
|
|
}
|
|
|
|
if (str == "\\$sformatf") {
|
|
AstNode *node_string = children[0];
|
|
while (node_string->simplify(true, false, false, stage, width_hint, sign_hint, false)) { }
|
|
if (node_string->type != AST_CONSTANT)
|
|
log_file_error(filename, location.first_line, "Failed to evaluate system function `%s' with non-constant 1st argument.\n", str.c_str());
|
|
std::string sformat = node_string->bitsAsConst().decode_string();
|
|
std::string sout = process_format_str(sformat, 1, stage, width_hint, sign_hint);
|
|
newNode = AstNode::mkconst_str(sout);
|
|
goto apply_newNode;
|
|
}
|
|
|
|
if (str == "\\$countbits") {
|
|
if (children.size() < 2)
|
|
log_file_error(filename, location.first_line, "System function %s got %d arguments, expected at least 2.\n",
|
|
RTLIL::unescape_id(str).c_str(), int(children.size()));
|
|
|
|
std::vector<RTLIL::State> control_bits;
|
|
|
|
// Determine which bits to count
|
|
for (size_t i = 1; i < children.size(); i++) {
|
|
AstNode *node = children[i];
|
|
while (node->simplify(true, false, false, stage, -1, false, false)) { }
|
|
if (node->type != AST_CONSTANT)
|
|
log_file_error(filename, location.first_line, "Failed to evaluate system function `%s' with non-constant control bit argument.\n", str.c_str());
|
|
if (node->bits.size() != 1)
|
|
log_file_error(filename, location.first_line, "Failed to evaluate system function `%s' with control bit width != 1.\n", str.c_str());
|
|
control_bits.push_back(node->bits[0]);
|
|
}
|
|
|
|
// Detect width of exp (first argument of $countbits)
|
|
int exp_width = -1;
|
|
bool exp_sign = false;
|
|
AstNode *exp = children[0];
|
|
exp->detectSignWidth(exp_width, exp_sign, NULL);
|
|
|
|
newNode = mkconst_int(0, false);
|
|
|
|
for (int i = 0; i < exp_width; i++) {
|
|
// Generate nodes for: exp << i >> ($size(exp) - 1)
|
|
// ^^ ^^
|
|
AstNode *lsh_node = new AstNode(AST_SHIFT_LEFT, exp->clone(), mkconst_int(i, false));
|
|
AstNode *rsh_node = new AstNode(AST_SHIFT_RIGHT, lsh_node, mkconst_int(exp_width - 1, false));
|
|
|
|
AstNode *or_node = nullptr;
|
|
|
|
for (RTLIL::State control_bit : control_bits) {
|
|
// Generate node for: (exp << i >> ($size(exp) - 1)) === control_bit
|
|
// ^^^
|
|
AstNode *eq_node = new AstNode(AST_EQX, rsh_node->clone(), mkconst_bits({control_bit}, false));
|
|
|
|
// Or the result for each checked bit value
|
|
if (or_node)
|
|
or_node = new AstNode(AST_LOGIC_OR, or_node, eq_node);
|
|
else
|
|
or_node = eq_node;
|
|
}
|
|
|
|
// We should have at least one element in control_bits,
|
|
// because we checked for the number of arguments above
|
|
log_assert(or_node != nullptr);
|
|
|
|
delete rsh_node;
|
|
|
|
// Generate node for adding with result of previous bit
|
|
newNode = new AstNode(AST_ADD, newNode, or_node);
|
|
}
|
|
|
|
goto apply_newNode;
|
|
}
|
|
|
|
if (str == "\\$countones" || str == "\\$isunknown" || str == "\\$onehot" || str == "\\$onehot0") {
|
|
if (children.size() != 1)
|
|
log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 1.\n",
|
|
RTLIL::unescape_id(str).c_str(), int(children.size()));
|
|
|
|
AstNode *countbits = clone();
|
|
countbits->str = "\\$countbits";
|
|
|
|
if (str == "\\$countones") {
|
|
countbits->children.push_back(mkconst_bits({RTLIL::State::S1}, false));
|
|
newNode = countbits;
|
|
} else if (str == "\\$isunknown") {
|
|
countbits->children.push_back(mkconst_bits({RTLIL::Sx}, false));
|
|
countbits->children.push_back(mkconst_bits({RTLIL::Sz}, false));
|
|
newNode = new AstNode(AST_GT, countbits, mkconst_int(0, false));
|
|
} else if (str == "\\$onehot") {
|
|
countbits->children.push_back(mkconst_bits({RTLIL::State::S1}, false));
|
|
newNode = new AstNode(AST_EQ, countbits, mkconst_int(1, false));
|
|
} else if (str == "\\$onehot0") {
|
|
countbits->children.push_back(mkconst_bits({RTLIL::State::S1}, false));
|
|
newNode = new AstNode(AST_LE, countbits, mkconst_int(1, false));
|
|
} else {
|
|
log_abort();
|
|
}
|
|
|
|
goto apply_newNode;
|
|
}
|
|
|
|
if (current_scope.count(str) != 0 && current_scope[str]->type == AST_DPI_FUNCTION)
|
|
{
|
|
AstNode *dpi_decl = current_scope[str];
|
|
|
|
std::string rtype, fname;
|
|
std::vector<std::string> argtypes;
|
|
std::vector<AstNode*> args;
|
|
|
|
rtype = RTLIL::unescape_id(dpi_decl->children.at(0)->str);
|
|
fname = RTLIL::unescape_id(dpi_decl->children.at(1)->str);
|
|
|
|
for (int i = 2; i < GetSize(dpi_decl->children); i++)
|
|
{
|
|
if (i-2 >= GetSize(children))
|
|
log_file_error(filename, location.first_line, "Insufficient number of arguments in DPI function call.\n");
|
|
|
|
argtypes.push_back(RTLIL::unescape_id(dpi_decl->children.at(i)->str));
|
|
args.push_back(children.at(i-2)->clone());
|
|
while (args.back()->simplify(true, false, false, stage, -1, false, true)) { }
|
|
|
|
if (args.back()->type != AST_CONSTANT && args.back()->type != AST_REALVALUE)
|
|
log_file_error(filename, location.first_line, "Failed to evaluate DPI function with non-constant argument.\n");
|
|
}
|
|
|
|
newNode = dpi_call(rtype, fname, argtypes, args);
|
|
|
|
for (auto arg : args)
|
|
delete arg;
|
|
|
|
goto apply_newNode;
|
|
}
|
|
|
|
if (current_scope.count(str) == 0)
|
|
str = try_pop_module_prefix();
|
|
if (current_scope.count(str) == 0 || current_scope[str]->type != AST_FUNCTION)
|
|
log_file_error(filename, location.first_line, "Can't resolve function name `%s'.\n", str.c_str());
|
|
}
|
|
|
|
if (type == AST_TCALL)
|
|
{
|
|
if (str == "$finish" || str == "$stop")
|
|
{
|
|
if (!current_always || current_always->type != AST_INITIAL)
|
|
log_file_error(filename, location.first_line, "System task `%s' outside initial block is unsupported.\n", str.c_str());
|
|
|
|
log_file_error(filename, location.first_line, "System task `%s' executed.\n", str.c_str());
|
|
}
|
|
|
|
if (str == "\\$readmemh" || str == "\\$readmemb")
|
|
{
|
|
if (GetSize(children) < 2 || GetSize(children) > 4)
|
|
log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 2-4.\n",
|
|
RTLIL::unescape_id(str).c_str(), int(children.size()));
|
|
|
|
AstNode *node_filename = children[0]->clone();
|
|
while (node_filename->simplify(true, false, false, stage, width_hint, sign_hint, false)) { }
|
|
if (node_filename->type != AST_CONSTANT)
|
|
log_file_error(filename, location.first_line, "Failed to evaluate system function `%s' with non-constant 1st argument.\n", str.c_str());
|
|
|
|
AstNode *node_memory = children[1]->clone();
|
|
while (node_memory->simplify(true, false, false, stage, width_hint, sign_hint, false)) { }
|
|
if (node_memory->type != AST_IDENTIFIER || node_memory->id2ast == nullptr || node_memory->id2ast->type != AST_MEMORY)
|
|
log_file_error(filename, location.first_line, "Failed to evaluate system function `%s' with non-memory 2nd argument.\n", str.c_str());
|
|
|
|
int start_addr = -1, finish_addr = -1;
|
|
|
|
if (GetSize(children) > 2) {
|
|
AstNode *node_addr = children[2]->clone();
|
|
while (node_addr->simplify(true, false, false, stage, width_hint, sign_hint, false)) { }
|
|
if (node_addr->type != AST_CONSTANT)
|
|
log_file_error(filename, location.first_line, "Failed to evaluate system function `%s' with non-constant 3rd argument.\n", str.c_str());
|
|
start_addr = int(node_addr->asInt(false));
|
|
}
|
|
|
|
if (GetSize(children) > 3) {
|
|
AstNode *node_addr = children[3]->clone();
|
|
while (node_addr->simplify(true, false, false, stage, width_hint, sign_hint, false)) { }
|
|
if (node_addr->type != AST_CONSTANT)
|
|
log_file_error(filename, location.first_line, "Failed to evaluate system function `%s' with non-constant 4th argument.\n", str.c_str());
|
|
finish_addr = int(node_addr->asInt(false));
|
|
}
|
|
|
|
bool unconditional_init = false;
|
|
if (current_always->type == AST_INITIAL) {
|
|
pool<AstNode*> queue;
|
|
log_assert(current_always->children[0]->type == AST_BLOCK);
|
|
queue.insert(current_always->children[0]);
|
|
while (!unconditional_init && !queue.empty()) {
|
|
pool<AstNode*> next_queue;
|
|
for (auto n : queue)
|
|
for (auto c : n->children) {
|
|
if (c == this)
|
|
unconditional_init = true;
|
|
next_queue.insert(c);
|
|
}
|
|
next_queue.swap(queue);
|
|
}
|
|
}
|
|
|
|
newNode = readmem(str == "\\$readmemh", node_filename->bitsAsConst().decode_string(), node_memory->id2ast, start_addr, finish_addr, unconditional_init);
|
|
delete node_filename;
|
|
delete node_memory;
|
|
goto apply_newNode;
|
|
}
|
|
|
|
if (current_scope.count(str) == 0)
|
|
str = try_pop_module_prefix();
|
|
if (current_scope.count(str) == 0 || current_scope[str]->type != AST_TASK)
|
|
log_file_error(filename, location.first_line, "Can't resolve task name `%s'.\n", str.c_str());
|
|
}
|
|
|
|
|
|
std::stringstream sstr;
|
|
sstr << str << "$func$" << RTLIL::encode_filename(filename) << ":" << location.first_line << "$" << (autoidx++) << '.';
|
|
std::string prefix = sstr.str();
|
|
|
|
AstNode *decl = current_scope[str];
|
|
if (unevaluated_tern_branch && decl->is_recursive_function())
|
|
goto replace_fcall_later;
|
|
decl = decl->clone();
|
|
decl->replace_result_wire_name_in_function(str, "$result"); // enables recursion
|
|
decl->expand_genblock(prefix);
|
|
|
|
if (decl->type == AST_FUNCTION && !decl->attributes.count(ID::via_celltype))
|
|
{
|
|
bool require_const_eval = decl->has_const_only_constructs();
|
|
bool all_args_const = true;
|
|
for (auto child : children) {
|
|
while (child->simplify(true, false, false, 1, -1, false, true)) { }
|
|
if (child->type != AST_CONSTANT && child->type != AST_REALVALUE)
|
|
all_args_const = false;
|
|
}
|
|
|
|
if (all_args_const) {
|
|
AstNode *func_workspace = decl->clone();
|
|
func_workspace->str = prefix_id(prefix, "$result");
|
|
newNode = func_workspace->eval_const_function(this, in_param || require_const_eval);
|
|
delete func_workspace;
|
|
if (newNode) {
|
|
delete decl;
|
|
goto apply_newNode;
|
|
}
|
|
}
|
|
|
|
if (in_param)
|
|
log_file_error(filename, location.first_line, "Non-constant function call in constant expression.\n");
|
|
if (require_const_eval)
|
|
log_file_error(filename, location.first_line, "Function %s can only be called with constant arguments.\n", str.c_str());
|
|
}
|
|
|
|
size_t arg_count = 0;
|
|
dict<std::string, AstNode*> wire_cache;
|
|
vector<AstNode*> new_stmts;
|
|
vector<AstNode*> output_assignments;
|
|
|
|
if (current_block == NULL)
|
|
{
|
|
log_assert(type == AST_FCALL);
|
|
|
|
AstNode *wire = NULL;
|
|
std::string res_name = prefix_id(prefix, "$result");
|
|
for (auto child : decl->children)
|
|
if (child->type == AST_WIRE && child->str == res_name)
|
|
wire = child->clone();
|
|
log_assert(wire != NULL);
|
|
|
|
wire->port_id = 0;
|
|
wire->is_input = false;
|
|
wire->is_output = false;
|
|
|
|
current_scope[wire->str] = wire;
|
|
current_ast_mod->children.push_back(wire);
|
|
while (wire->simplify(true, false, false, 1, -1, false, false)) { }
|
|
|
|
AstNode *lvalue = new AstNode(AST_IDENTIFIER);
|
|
lvalue->str = wire->str;
|
|
|
|
AstNode *always = new AstNode(AST_ALWAYS, new AstNode(AST_BLOCK,
|
|
new AstNode(AST_ASSIGN_EQ, lvalue, clone())));
|
|
always->children[0]->children[0]->was_checked = true;
|
|
|
|
current_ast_mod->children.push_back(always);
|
|
|
|
goto replace_fcall_with_id;
|
|
}
|
|
|
|
if (decl->attributes.count(ID::via_celltype))
|
|
{
|
|
std::string celltype = decl->attributes.at(ID::via_celltype)->asAttrConst().decode_string();
|
|
std::string outport = str;
|
|
|
|
if (celltype.find(' ') != std::string::npos) {
|
|
int pos = celltype.find(' ');
|
|
outport = RTLIL::escape_id(celltype.substr(pos+1));
|
|
celltype = RTLIL::escape_id(celltype.substr(0, pos));
|
|
} else
|
|
celltype = RTLIL::escape_id(celltype);
|
|
|
|
AstNode *cell = new AstNode(AST_CELL, new AstNode(AST_CELLTYPE));
|
|
cell->str = prefix.substr(0, GetSize(prefix)-1);
|
|
cell->children[0]->str = celltype;
|
|
|
|
for (auto attr : decl->attributes)
|
|
if (attr.first.str().rfind("\\via_celltype_defparam_", 0) == 0)
|
|
{
|
|
AstNode *cell_arg = new AstNode(AST_PARASET, attr.second->clone());
|
|
cell_arg->str = RTLIL::escape_id(attr.first.substr(strlen("\\via_celltype_defparam_")));
|
|
cell->children.push_back(cell_arg);
|
|
}
|
|
|
|
for (auto child : decl->children)
|
|
if (child->type == AST_WIRE && (child->is_input || child->is_output || (type == AST_FCALL && child->str == str)))
|
|
{
|
|
AstNode *wire = child->clone();
|
|
wire->port_id = 0;
|
|
wire->is_input = false;
|
|
wire->is_output = false;
|
|
current_ast_mod->children.push_back(wire);
|
|
while (wire->simplify(true, false, false, 1, -1, false, false)) { }
|
|
|
|
AstNode *wire_id = new AstNode(AST_IDENTIFIER);
|
|
wire_id->str = wire->str;
|
|
|
|
if ((child->is_input || child->is_output) && arg_count < children.size())
|
|
{
|
|
AstNode *arg = children[arg_count++]->clone();
|
|
AstNode *assign = child->is_input ?
|
|
new AstNode(AST_ASSIGN_EQ, wire_id->clone(), arg) :
|
|
new AstNode(AST_ASSIGN_EQ, arg, wire_id->clone());
|
|
assign->children[0]->was_checked = true;
|
|
|
|
for (auto it = current_block->children.begin(); it != current_block->children.end(); it++) {
|
|
if (*it != current_block_child)
|
|
continue;
|
|
current_block->children.insert(it, assign);
|
|
break;
|
|
}
|
|
}
|
|
|
|
AstNode *cell_arg = new AstNode(AST_ARGUMENT, wire_id);
|
|
cell_arg->str = child->str == str ? outport : child->str;
|
|
cell->children.push_back(cell_arg);
|
|
}
|
|
|
|
current_ast_mod->children.push_back(cell);
|
|
goto replace_fcall_with_id;
|
|
}
|
|
|
|
for (auto child : decl->children)
|
|
if (child->type == AST_WIRE || child->type == AST_MEMORY || child->type == AST_PARAMETER || child->type == AST_LOCALPARAM || child->type == AST_ENUM_ITEM)
|
|
{
|
|
AstNode *wire = nullptr;
|
|
|
|
if (wire_cache.count(child->str))
|
|
{
|
|
wire = wire_cache.at(child->str);
|
|
bool contains_value = wire->type == AST_LOCALPARAM;
|
|
if (wire->children.size() == contains_value) {
|
|
for (auto c : child->children)
|
|
wire->children.push_back(c->clone());
|
|
} else if (!child->children.empty()) {
|
|
while (child->simplify(true, false, false, stage, -1, false, false)) { }
|
|
if (GetSize(child->children) == GetSize(wire->children) - contains_value) {
|
|
for (int i = 0; i < GetSize(child->children); i++)
|
|
if (*child->children.at(i) != *wire->children.at(i + contains_value))
|
|
goto tcall_incompatible_wires;
|
|
} else {
|
|
tcall_incompatible_wires:
|
|
log_file_error(filename, location.first_line, "Incompatible re-declaration of wire %s.\n", child->str.c_str());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
wire = child->clone();
|
|
wire->port_id = 0;
|
|
wire->is_input = false;
|
|
wire->is_output = false;
|
|
wire->is_reg = true;
|
|
wire->attributes[ID::nosync] = AstNode::mkconst_int(1, false);
|
|
if (child->type == AST_ENUM_ITEM)
|
|
wire->attributes[ID::enum_base_type] = child->attributes[ID::enum_base_type];
|
|
|
|
wire_cache[child->str] = wire;
|
|
|
|
current_scope[wire->str] = wire;
|
|
current_ast_mod->children.push_back(wire);
|
|
}
|
|
|
|
while (wire->simplify(true, false, false, 1, -1, false, false)) { }
|
|
|
|
if ((child->is_input || child->is_output) && arg_count < children.size())
|
|
{
|
|
AstNode *arg = children[arg_count++]->clone();
|
|
// convert purely constant arguments into localparams
|
|
if (child->is_input && child->type == AST_WIRE && arg->type == AST_CONSTANT && node_contains_assignment_to(decl, child)) {
|
|
wire->type = AST_LOCALPARAM;
|
|
if (wire->attributes.count(ID::nosync))
|
|
delete wire->attributes.at(ID::nosync);
|
|
wire->attributes.erase(ID::nosync);
|
|
wire->children.insert(wire->children.begin(), arg->clone());
|
|
// args without a range implicitly have width 1
|
|
if (wire->children.back()->type != AST_RANGE) {
|
|
// check if this wire is redeclared with an explicit size
|
|
bool uses_explicit_size = false;
|
|
for (const AstNode *other_child : decl->children)
|
|
if (other_child->type == AST_WIRE && child->str == other_child->str
|
|
&& !other_child->children.empty()
|
|
&& other_child->children.back()->type == AST_RANGE) {
|
|
uses_explicit_size = true;
|
|
break;
|
|
}
|
|
if (!uses_explicit_size) {
|
|
AstNode* range = new AstNode();
|
|
range->type = AST_RANGE;
|
|
wire->children.push_back(range);
|
|
range->children.push_back(mkconst_int(0, true));
|
|
range->children.push_back(mkconst_int(0, true));
|
|
}
|
|
}
|
|
// updates the sizing
|
|
while (wire->simplify(true, false, false, 1, -1, false, false)) { }
|
|
delete arg;
|
|
continue;
|
|
}
|
|
AstNode *wire_id = new AstNode(AST_IDENTIFIER);
|
|
wire_id->str = wire->str;
|
|
AstNode *assign = child->is_input ?
|
|
new AstNode(AST_ASSIGN_EQ, wire_id, arg) :
|
|
new AstNode(AST_ASSIGN_EQ, arg, wire_id);
|
|
assign->children[0]->was_checked = true;
|
|
if (child->is_input)
|
|
new_stmts.push_back(assign);
|
|
else
|
|
output_assignments.push_back(assign);
|
|
}
|
|
}
|
|
|
|
for (auto child : decl->children)
|
|
if (child->type != AST_WIRE && child->type != AST_MEMORY && child->type != AST_PARAMETER && child->type != AST_LOCALPARAM)
|
|
new_stmts.push_back(child->clone());
|
|
|
|
new_stmts.insert(new_stmts.end(), output_assignments.begin(), output_assignments.end());
|
|
|
|
for (auto it = current_block->children.begin(); ; it++) {
|
|
log_assert(it != current_block->children.end());
|
|
if (*it == current_block_child) {
|
|
current_block->children.insert(it, new_stmts.begin(), new_stmts.end());
|
|
break;
|
|
}
|
|
}
|
|
|
|
replace_fcall_with_id:
|
|
delete decl;
|
|
if (type == AST_FCALL) {
|
|
delete_children();
|
|
type = AST_IDENTIFIER;
|
|
str = prefix_id(prefix, "$result");
|
|
}
|
|
if (type == AST_TCALL)
|
|
str = "";
|
|
did_something = true;
|
|
}
|
|
|
|
replace_fcall_later:;
|
|
|
|
// perform const folding when activated
|
|
if (const_fold)
|
|
{
|
|
bool string_op;
|
|
std::vector<RTLIL::State> tmp_bits;
|
|
RTLIL::Const (*const_func)(const RTLIL::Const&, const RTLIL::Const&, bool, bool, int);
|
|
RTLIL::Const dummy_arg;
|
|
|
|
switch (type)
|
|
{
|
|
case AST_IDENTIFIER:
|
|
if (current_scope.count(str) > 0 && (current_scope[str]->type == AST_PARAMETER || current_scope[str]->type == AST_LOCALPARAM || current_scope[str]->type == AST_ENUM_ITEM)) {
|
|
if (current_scope[str]->children[0]->type == AST_CONSTANT) {
|
|
if (children.size() != 0 && children[0]->type == AST_RANGE && children[0]->range_valid) {
|
|
std::vector<RTLIL::State> data;
|
|
bool param_upto = current_scope[str]->range_valid && current_scope[str]->range_swapped;
|
|
int param_offset = current_scope[str]->range_valid ? current_scope[str]->range_right : 0;
|
|
int param_width = current_scope[str]->range_valid ? current_scope[str]->range_left - current_scope[str]->range_right + 1 :
|
|
GetSize(current_scope[str]->children[0]->bits);
|
|
int tmp_range_left = children[0]->range_left, tmp_range_right = children[0]->range_right;
|
|
if (param_upto) {
|
|
tmp_range_left = (param_width + 2*param_offset) - children[0]->range_right - 1;
|
|
tmp_range_right = (param_width + 2*param_offset) - children[0]->range_left - 1;
|
|
}
|
|
AST::AstNode *member_node = get_struct_member(this);
|
|
int chunk_offset = member_node ? member_node->range_right : 0;
|
|
log_assert(!(chunk_offset && param_upto));
|
|
for (int i = tmp_range_right; i <= tmp_range_left; i++) {
|
|
int index = i - param_offset;
|
|
if (0 <= index && index < param_width)
|
|
data.push_back(current_scope[str]->children[0]->bits[chunk_offset + index]);
|
|
else
|
|
data.push_back(RTLIL::State::Sx);
|
|
}
|
|
newNode = mkconst_bits(data, false);
|
|
} else
|
|
if (children.size() == 0)
|
|
newNode = current_scope[str]->children[0]->clone();
|
|
} else
|
|
if (current_scope[str]->children[0]->isConst())
|
|
newNode = current_scope[str]->children[0]->clone();
|
|
}
|
|
else if (at_zero && current_scope.count(str) > 0) {
|
|
AstNode *node = current_scope[str];
|
|
if (node->type == AST_WIRE || node->type == AST_AUTOWIRE || node->type == AST_MEMORY)
|
|
newNode = mkconst_int(0, sign_hint, width_hint);
|
|
}
|
|
break;
|
|
case AST_MEMRD:
|
|
if (at_zero) {
|
|
newNode = mkconst_int(0, sign_hint, width_hint);
|
|
}
|
|
break;
|
|
case AST_BIT_NOT:
|
|
if (children[0]->type == AST_CONSTANT) {
|
|
RTLIL::Const y = RTLIL::const_not(children[0]->bitsAsConst(width_hint, sign_hint), dummy_arg, sign_hint, false, width_hint);
|
|
newNode = mkconst_bits(y.bits, sign_hint);
|
|
}
|
|
break;
|
|
case AST_TO_SIGNED:
|
|
case AST_TO_UNSIGNED:
|
|
if (children[0]->type == AST_CONSTANT) {
|
|
RTLIL::Const y = children[0]->bitsAsConst(width_hint, sign_hint);
|
|
newNode = mkconst_bits(y.bits, type == AST_TO_SIGNED);
|
|
}
|
|
break;
|
|
if (0) { case AST_BIT_AND: const_func = RTLIL::const_and; }
|
|
if (0) { case AST_BIT_OR: const_func = RTLIL::const_or; }
|
|
if (0) { case AST_BIT_XOR: const_func = RTLIL::const_xor; }
|
|
if (0) { case AST_BIT_XNOR: const_func = RTLIL::const_xnor; }
|
|
if (children[0]->type == AST_CONSTANT && children[1]->type == AST_CONSTANT) {
|
|
RTLIL::Const y = const_func(children[0]->bitsAsConst(width_hint, sign_hint),
|
|
children[1]->bitsAsConst(width_hint, sign_hint), sign_hint, sign_hint, width_hint);
|
|
newNode = mkconst_bits(y.bits, sign_hint);
|
|
}
|
|
break;
|
|
if (0) { case AST_REDUCE_AND: const_func = RTLIL::const_reduce_and; }
|
|
if (0) { case AST_REDUCE_OR: const_func = RTLIL::const_reduce_or; }
|
|
if (0) { case AST_REDUCE_XOR: const_func = RTLIL::const_reduce_xor; }
|
|
if (0) { case AST_REDUCE_XNOR: const_func = RTLIL::const_reduce_xnor; }
|
|
if (0) { case AST_REDUCE_BOOL: const_func = RTLIL::const_reduce_bool; }
|
|
if (children[0]->type == AST_CONSTANT) {
|
|
RTLIL::Const y = const_func(RTLIL::Const(children[0]->bits), dummy_arg, false, false, -1);
|
|
newNode = mkconst_bits(y.bits, false);
|
|
}
|
|
break;
|
|
case AST_LOGIC_NOT:
|
|
if (children[0]->type == AST_CONSTANT) {
|
|
RTLIL::Const y = RTLIL::const_logic_not(RTLIL::Const(children[0]->bits), dummy_arg, children[0]->is_signed, false, -1);
|
|
newNode = mkconst_bits(y.bits, false);
|
|
} else
|
|
if (children[0]->isConst()) {
|
|
newNode = mkconst_int(children[0]->asReal(sign_hint) == 0, false, 1);
|
|
}
|
|
break;
|
|
if (0) { case AST_LOGIC_AND: const_func = RTLIL::const_logic_and; }
|
|
if (0) { case AST_LOGIC_OR: const_func = RTLIL::const_logic_or; }
|
|
if (children[0]->type == AST_CONSTANT && children[1]->type == AST_CONSTANT) {
|
|
RTLIL::Const y = const_func(RTLIL::Const(children[0]->bits), RTLIL::Const(children[1]->bits),
|
|
children[0]->is_signed, children[1]->is_signed, -1);
|
|
newNode = mkconst_bits(y.bits, false);
|
|
} else
|
|
if (children[0]->isConst() && children[1]->isConst()) {
|
|
if (type == AST_LOGIC_AND)
|
|
newNode = mkconst_int((children[0]->asReal(sign_hint) != 0) && (children[1]->asReal(sign_hint) != 0), false, 1);
|
|
else
|
|
newNode = mkconst_int((children[0]->asReal(sign_hint) != 0) || (children[1]->asReal(sign_hint) != 0), false, 1);
|
|
}
|
|
break;
|
|
if (0) { case AST_SHIFT_LEFT: const_func = RTLIL::const_shl; }
|
|
if (0) { case AST_SHIFT_RIGHT: const_func = RTLIL::const_shr; }
|
|
if (0) { case AST_SHIFT_SLEFT: const_func = RTLIL::const_sshl; }
|
|
if (0) { case AST_SHIFT_SRIGHT: const_func = RTLIL::const_sshr; }
|
|
if (0) { case AST_POW: const_func = RTLIL::const_pow; }
|
|
if (children[0]->type == AST_CONSTANT && children[1]->type == AST_CONSTANT) {
|
|
RTLIL::Const y = const_func(children[0]->bitsAsConst(width_hint, sign_hint),
|
|
RTLIL::Const(children[1]->bits), sign_hint, type == AST_POW ? children[1]->is_signed : false, width_hint);
|
|
newNode = mkconst_bits(y.bits, sign_hint);
|
|
} else
|
|
if (type == AST_POW && children[0]->isConst() && children[1]->isConst()) {
|
|
newNode = new AstNode(AST_REALVALUE);
|
|
newNode->realvalue = pow(children[0]->asReal(sign_hint), children[1]->asReal(sign_hint));
|
|
}
|
|
break;
|
|
if (0) { case AST_LT: const_func = RTLIL::const_lt; }
|
|
if (0) { case AST_LE: const_func = RTLIL::const_le; }
|
|
if (0) { case AST_EQ: const_func = RTLIL::const_eq; }
|
|
if (0) { case AST_NE: const_func = RTLIL::const_ne; }
|
|
if (0) { case AST_EQX: const_func = RTLIL::const_eqx; }
|
|
if (0) { case AST_NEX: const_func = RTLIL::const_nex; }
|
|
if (0) { case AST_GE: const_func = RTLIL::const_ge; }
|
|
if (0) { case AST_GT: const_func = RTLIL::const_gt; }
|
|
if (children[0]->type == AST_CONSTANT && children[1]->type == AST_CONSTANT) {
|
|
int cmp_width = max(children[0]->bits.size(), children[1]->bits.size());
|
|
bool cmp_signed = children[0]->is_signed && children[1]->is_signed;
|
|
RTLIL::Const y = const_func(children[0]->bitsAsConst(cmp_width, cmp_signed),
|
|
children[1]->bitsAsConst(cmp_width, cmp_signed), cmp_signed, cmp_signed, 1);
|
|
newNode = mkconst_bits(y.bits, false);
|
|
} else
|
|
if (children[0]->isConst() && children[1]->isConst()) {
|
|
bool cmp_signed = (children[0]->type == AST_REALVALUE || children[0]->is_signed) && (children[1]->type == AST_REALVALUE || children[1]->is_signed);
|
|
switch (type) {
|
|
case AST_LT: newNode = mkconst_int(children[0]->asReal(cmp_signed) < children[1]->asReal(cmp_signed), false, 1); break;
|
|
case AST_LE: newNode = mkconst_int(children[0]->asReal(cmp_signed) <= children[1]->asReal(cmp_signed), false, 1); break;
|
|
case AST_EQ: newNode = mkconst_int(children[0]->asReal(cmp_signed) == children[1]->asReal(cmp_signed), false, 1); break;
|
|
case AST_NE: newNode = mkconst_int(children[0]->asReal(cmp_signed) != children[1]->asReal(cmp_signed), false, 1); break;
|
|
case AST_EQX: newNode = mkconst_int(children[0]->asReal(cmp_signed) == children[1]->asReal(cmp_signed), false, 1); break;
|
|
case AST_NEX: newNode = mkconst_int(children[0]->asReal(cmp_signed) != children[1]->asReal(cmp_signed), false, 1); break;
|
|
case AST_GE: newNode = mkconst_int(children[0]->asReal(cmp_signed) >= children[1]->asReal(cmp_signed), false, 1); break;
|
|
case AST_GT: newNode = mkconst_int(children[0]->asReal(cmp_signed) > children[1]->asReal(cmp_signed), false, 1); break;
|
|
default: log_abort();
|
|
}
|
|
}
|
|
break;
|
|
if (0) { case AST_ADD: const_func = RTLIL::const_add; }
|
|
if (0) { case AST_SUB: const_func = RTLIL::const_sub; }
|
|
if (0) { case AST_MUL: const_func = RTLIL::const_mul; }
|
|
if (0) { case AST_DIV: const_func = RTLIL::const_div; }
|
|
if (0) { case AST_MOD: const_func = RTLIL::const_mod; }
|
|
if (children[0]->type == AST_CONSTANT && children[1]->type == AST_CONSTANT) {
|
|
RTLIL::Const y = const_func(children[0]->bitsAsConst(width_hint, sign_hint),
|
|
children[1]->bitsAsConst(width_hint, sign_hint), sign_hint, sign_hint, width_hint);
|
|
newNode = mkconst_bits(y.bits, sign_hint);
|
|
} else
|
|
if (children[0]->isConst() && children[1]->isConst()) {
|
|
newNode = new AstNode(AST_REALVALUE);
|
|
switch (type) {
|
|
case AST_ADD: newNode->realvalue = children[0]->asReal(sign_hint) + children[1]->asReal(sign_hint); break;
|
|
case AST_SUB: newNode->realvalue = children[0]->asReal(sign_hint) - children[1]->asReal(sign_hint); break;
|
|
case AST_MUL: newNode->realvalue = children[0]->asReal(sign_hint) * children[1]->asReal(sign_hint); break;
|
|
case AST_DIV: newNode->realvalue = children[0]->asReal(sign_hint) / children[1]->asReal(sign_hint); break;
|
|
case AST_MOD: newNode->realvalue = fmod(children[0]->asReal(sign_hint), children[1]->asReal(sign_hint)); break;
|
|
default: log_abort();
|
|
}
|
|
}
|
|
break;
|
|
if (0) { case AST_SELFSZ: const_func = RTLIL::const_pos; }
|
|
if (0) { case AST_POS: const_func = RTLIL::const_pos; }
|
|
if (0) { case AST_NEG: const_func = RTLIL::const_neg; }
|
|
if (children[0]->type == AST_CONSTANT) {
|
|
RTLIL::Const y = const_func(children[0]->bitsAsConst(width_hint, sign_hint), dummy_arg, sign_hint, false, width_hint);
|
|
newNode = mkconst_bits(y.bits, sign_hint);
|
|
} else
|
|
if (children[0]->isConst()) {
|
|
newNode = new AstNode(AST_REALVALUE);
|
|
if (type == AST_NEG)
|
|
newNode->realvalue = -children[0]->asReal(sign_hint);
|
|
else
|
|
newNode->realvalue = +children[0]->asReal(sign_hint);
|
|
}
|
|
break;
|
|
case AST_TERNARY:
|
|
if (children[0]->isConst())
|
|
{
|
|
auto pair = get_tern_choice();
|
|
AstNode *choice = pair.first;
|
|
AstNode *not_choice = pair.second;
|
|
|
|
if (choice != NULL) {
|
|
if (choice->type == AST_CONSTANT) {
|
|
int other_width_hint = width_hint;
|
|
bool other_sign_hint = sign_hint, other_real = false;
|
|
not_choice->detectSignWidth(other_width_hint, other_sign_hint, &other_real);
|
|
if (other_real) {
|
|
newNode = new AstNode(AST_REALVALUE);
|
|
choice->detectSignWidth(width_hint, sign_hint);
|
|
newNode->realvalue = choice->asReal(sign_hint);
|
|
} else {
|
|
RTLIL::Const y = choice->bitsAsConst(width_hint, sign_hint);
|
|
if (choice->is_string && y.bits.size() % 8 == 0 && sign_hint == false)
|
|
newNode = mkconst_str(y.bits);
|
|
else
|
|
newNode = mkconst_bits(y.bits, sign_hint);
|
|
}
|
|
} else
|
|
if (choice->isConst()) {
|
|
newNode = choice->clone();
|
|
}
|
|
} else if (children[1]->type == AST_CONSTANT && children[2]->type == AST_CONSTANT) {
|
|
RTLIL::Const a = children[1]->bitsAsConst(width_hint, sign_hint);
|
|
RTLIL::Const b = children[2]->bitsAsConst(width_hint, sign_hint);
|
|
log_assert(a.bits.size() == b.bits.size());
|
|
for (size_t i = 0; i < a.bits.size(); i++)
|
|
if (a.bits[i] != b.bits[i])
|
|
a.bits[i] = RTLIL::State::Sx;
|
|
newNode = mkconst_bits(a.bits, sign_hint);
|
|
} else if (children[1]->isConst() && children[2]->isConst()) {
|
|
newNode = new AstNode(AST_REALVALUE);
|
|
if (children[1]->asReal(sign_hint) == children[2]->asReal(sign_hint))
|
|
newNode->realvalue = children[1]->asReal(sign_hint);
|
|
else
|
|
// IEEE Std 1800-2012 Sec. 11.4.11 states that the entry in Table 7-1 for
|
|
// the data type in question should be returned if the ?: is ambiguous. The
|
|
// value in Table 7-1 for the 'real' type is 0.0.
|
|
newNode->realvalue = 0.0;
|
|
}
|
|
}
|
|
break;
|
|
case AST_CAST_SIZE:
|
|
if (children.at(0)->type == AST_CONSTANT && children.at(1)->type == AST_CONSTANT) {
|
|
int width = children[0]->bitsAsConst().as_int();
|
|
RTLIL::Const val;
|
|
if (children[1]->is_unsized)
|
|
val = children[1]->bitsAsUnsizedConst(width);
|
|
else
|
|
val = children[1]->bitsAsConst(width);
|
|
newNode = mkconst_bits(val.bits, children[1]->is_signed);
|
|
}
|
|
break;
|
|
case AST_CONCAT:
|
|
string_op = !children.empty();
|
|
for (auto it = children.begin(); it != children.end(); it++) {
|
|
if ((*it)->type != AST_CONSTANT)
|
|
goto not_const;
|
|
if (!(*it)->is_string)
|
|
string_op = false;
|
|
tmp_bits.insert(tmp_bits.end(), (*it)->bits.begin(), (*it)->bits.end());
|
|
}
|
|
newNode = string_op ? mkconst_str(tmp_bits) : mkconst_bits(tmp_bits, false);
|
|
break;
|
|
case AST_REPLICATE:
|
|
if (children.at(0)->type != AST_CONSTANT || children.at(1)->type != AST_CONSTANT)
|
|
goto not_const;
|
|
for (int i = 0; i < children[0]->bitsAsConst().as_int(); i++)
|
|
tmp_bits.insert(tmp_bits.end(), children.at(1)->bits.begin(), children.at(1)->bits.end());
|
|
newNode = children.at(1)->is_string ? mkconst_str(tmp_bits) : mkconst_bits(tmp_bits, false);
|
|
break;
|
|
default:
|
|
not_const:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if any of the above set 'newNode' -> use 'newNode' as template to update 'this'
|
|
if (newNode) {
|
|
apply_newNode:
|
|
// fprintf(stderr, "----\n");
|
|
// dumpAst(stderr, "- ");
|
|
// newNode->dumpAst(stderr, "+ ");
|
|
log_assert(newNode != NULL);
|
|
newNode->filename = filename;
|
|
newNode->location = location;
|
|
newNode->cloneInto(this);
|
|
delete newNode;
|
|
did_something = true;
|
|
}
|
|
|
|
if (!did_something)
|
|
basic_prep = true;
|
|
|
|
recursion_counter--;
|
|
return did_something;
|
|
}
|
|
|
|
void AstNode::replace_result_wire_name_in_function(const std::string &from, const std::string &to)
|
|
{
|
|
for (AstNode *child : children)
|
|
child->replace_result_wire_name_in_function(from, to);
|
|
if (str == from && type != AST_FCALL && type != AST_TCALL)
|
|
str = to;
|
|
}
|
|
|
|
// replace a readmem[bh] TCALL ast node with a block of memory assignments
|
|
AstNode *AstNode::readmem(bool is_readmemh, std::string mem_filename, AstNode *memory, int start_addr, int finish_addr, bool unconditional_init)
|
|
{
|
|
int mem_width, mem_size, addr_bits;
|
|
memory->meminfo(mem_width, mem_size, addr_bits);
|
|
|
|
AstNode *block = new AstNode(AST_BLOCK);
|
|
|
|
AstNode *meminit = nullptr;
|
|
int next_meminit_cursor=0;
|
|
vector<State> meminit_bits;
|
|
vector<State> en_bits;
|
|
int meminit_size=0;
|
|
|
|
for (int i = 0; i < mem_width; i++)
|
|
en_bits.push_back(State::S1);
|
|
|
|
std::ifstream f;
|
|
f.open(mem_filename.c_str());
|
|
if (f.fail()) {
|
|
#ifdef _WIN32
|
|
char slash = '\\';
|
|
#else
|
|
char slash = '/';
|
|
#endif
|
|
std::string path = filename.substr(0, filename.find_last_of(slash)+1);
|
|
f.open(path + mem_filename.c_str());
|
|
yosys_input_files.insert(path + mem_filename);
|
|
} else {
|
|
yosys_input_files.insert(mem_filename);
|
|
}
|
|
if (f.fail() || GetSize(mem_filename) == 0)
|
|
log_file_error(filename, location.first_line, "Can not open file `%s` for %s.\n", mem_filename.c_str(), str.c_str());
|
|
|
|
log_assert(GetSize(memory->children) == 2 && memory->children[1]->type == AST_RANGE && memory->children[1]->range_valid);
|
|
int range_left = memory->children[1]->range_left, range_right = memory->children[1]->range_right;
|
|
int range_min = min(range_left, range_right), range_max = max(range_left, range_right);
|
|
|
|
if (start_addr < 0)
|
|
start_addr = range_min;
|
|
|
|
if (finish_addr < 0)
|
|
finish_addr = range_max + 1;
|
|
|
|
bool in_comment = false;
|
|
int increment = start_addr <= finish_addr ? +1 : -1;
|
|
int cursor = start_addr;
|
|
|
|
while (!f.eof())
|
|
{
|
|
std::string line, token;
|
|
std::getline(f, line);
|
|
|
|
for (int i = 0; i < GetSize(line); i++) {
|
|
if (in_comment && line.compare(i, 2, "*/") == 0) {
|
|
line[i] = ' ';
|
|
line[i+1] = ' ';
|
|
in_comment = false;
|
|
continue;
|
|
}
|
|
if (!in_comment && line.compare(i, 2, "/*") == 0)
|
|
in_comment = true;
|
|
if (in_comment)
|
|
line[i] = ' ';
|
|
}
|
|
|
|
while (1)
|
|
{
|
|
token = next_token(line, " \t\r\n");
|
|
if (token.empty() || token.compare(0, 2, "//") == 0)
|
|
break;
|
|
|
|
if (token[0] == '@') {
|
|
token = token.substr(1);
|
|
const char *nptr = token.c_str();
|
|
char *endptr;
|
|
cursor = strtol(nptr, &endptr, 16);
|
|
if (!*nptr || *endptr)
|
|
log_file_error(filename, location.first_line, "Can not parse address `%s` for %s.\n", nptr, str.c_str());
|
|
continue;
|
|
}
|
|
|
|
AstNode *value = VERILOG_FRONTEND::const2ast(stringf("%d'%c", mem_width, is_readmemh ? 'h' : 'b') + token);
|
|
|
|
if (unconditional_init)
|
|
{
|
|
if (meminit == nullptr || cursor != next_meminit_cursor)
|
|
{
|
|
if (meminit != nullptr) {
|
|
meminit->children[1] = AstNode::mkconst_bits(meminit_bits, false);
|
|
meminit->children[3] = AstNode::mkconst_int(meminit_size, false);
|
|
}
|
|
|
|
meminit = new AstNode(AST_MEMINIT);
|
|
meminit->children.push_back(AstNode::mkconst_int(cursor, false));
|
|
meminit->children.push_back(nullptr);
|
|
meminit->children.push_back(AstNode::mkconst_bits(en_bits, false));
|
|
meminit->children.push_back(nullptr);
|
|
meminit->str = memory->str;
|
|
meminit->id2ast = memory;
|
|
meminit_bits.clear();
|
|
meminit_size = 0;
|
|
|
|
current_ast_mod->children.push_back(meminit);
|
|
next_meminit_cursor = cursor;
|
|
}
|
|
|
|
meminit_size++;
|
|
next_meminit_cursor++;
|
|
meminit_bits.insert(meminit_bits.end(), value->bits.begin(), value->bits.end());
|
|
delete value;
|
|
}
|
|
else
|
|
{
|
|
block->children.push_back(new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER, new AstNode(AST_RANGE, AstNode::mkconst_int(cursor, false))), value));
|
|
block->children.back()->children[0]->str = memory->str;
|
|
block->children.back()->children[0]->id2ast = memory;
|
|
block->children.back()->children[0]->was_checked = true;
|
|
}
|
|
|
|
cursor += increment;
|
|
if ((cursor == finish_addr+increment) || (increment > 0 && cursor > range_max) || (increment < 0 && cursor < range_min))
|
|
break;
|
|
}
|
|
|
|
if ((cursor == finish_addr+increment) || (increment > 0 && cursor > range_max) || (increment < 0 && cursor < range_min))
|
|
break;
|
|
}
|
|
|
|
if (meminit != nullptr) {
|
|
meminit->children[1] = AstNode::mkconst_bits(meminit_bits, false);
|
|
meminit->children[3] = AstNode::mkconst_int(meminit_size, false);
|
|
}
|
|
|
|
return block;
|
|
}
|
|
|
|
// annotate the names of all wires and other named objects in a named generate
|
|
// or procedural block; nested blocks are themselves annotated such that the
|
|
// prefix is carried forward, but resolution of their children is deferred
|
|
void AstNode::expand_genblock(const std::string &prefix)
|
|
{
|
|
if (type == AST_IDENTIFIER || type == AST_FCALL || type == AST_TCALL || type == AST_WIRETYPE || type == AST_PREFIX) {
|
|
log_assert(!str.empty());
|
|
|
|
// search starting in the innermost scope and then stepping outward
|
|
for (size_t ppos = prefix.size() - 1; ppos; --ppos) {
|
|
if (prefix.at(ppos) != '.') continue;
|
|
|
|
std::string new_prefix = prefix.substr(0, ppos + 1);
|
|
auto attempt_resolve = [&new_prefix](const std::string &ident) -> std::string {
|
|
std::string new_name = prefix_id(new_prefix, ident);
|
|
if (current_scope.count(new_name))
|
|
return new_name;
|
|
return {};
|
|
};
|
|
|
|
// attempt to resolve the full identifier
|
|
std::string resolved = attempt_resolve(str);
|
|
if (!resolved.empty()) {
|
|
str = resolved;
|
|
break;
|
|
}
|
|
|
|
// attempt to resolve hierarchical prefixes within the identifier,
|
|
// as the prefix could refer to a local scope which exists but
|
|
// hasn't yet been elaborated
|
|
for (size_t spos = str.size() - 1; spos; --spos) {
|
|
if (str.at(spos) != '.') continue;
|
|
resolved = attempt_resolve(str.substr(0, spos));
|
|
if (!resolved.empty()) {
|
|
str = resolved + str.substr(spos);
|
|
ppos = 1; // break outer loop
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
auto prefix_node = [&prefix](AstNode* child) {
|
|
if (child->str.empty()) return;
|
|
std::string new_name = prefix_id(prefix, child->str);
|
|
if (child->type == AST_FUNCTION)
|
|
child->replace_result_wire_name_in_function(child->str, new_name);
|
|
else
|
|
child->str = new_name;
|
|
current_scope[new_name] = child;
|
|
};
|
|
|
|
for (size_t i = 0; i < children.size(); i++) {
|
|
AstNode *child = children[i];
|
|
|
|
switch (child->type) {
|
|
case AST_WIRE:
|
|
case AST_MEMORY:
|
|
case AST_PARAMETER:
|
|
case AST_LOCALPARAM:
|
|
case AST_FUNCTION:
|
|
case AST_TASK:
|
|
case AST_CELL:
|
|
case AST_TYPEDEF:
|
|
case AST_ENUM_ITEM:
|
|
case AST_GENVAR:
|
|
prefix_node(child);
|
|
break;
|
|
|
|
case AST_BLOCK:
|
|
case AST_GENBLOCK:
|
|
if (!child->str.empty())
|
|
prefix_node(child);
|
|
break;
|
|
|
|
case AST_ENUM:
|
|
current_scope[child->str] = child;
|
|
for (auto enode : child->children){
|
|
log_assert(enode->type == AST_ENUM_ITEM);
|
|
prefix_node(enode);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < children.size(); i++) {
|
|
AstNode *child = children[i];
|
|
// AST_PREFIX member names should not be prefixed; we recurse into them
|
|
// as normal to ensure indices and ranges are properly resolved, and
|
|
// then restore the previous string
|
|
if (type == AST_PREFIX && i == 1) {
|
|
std::string backup_scope_name = child->str;
|
|
child->expand_genblock(prefix);
|
|
child->str = backup_scope_name;
|
|
continue;
|
|
}
|
|
// functions/tasks may reference wires, constants, etc. in this scope
|
|
if (child->type == AST_FUNCTION || child->type == AST_TASK)
|
|
continue;
|
|
// named blocks pick up the current prefix and will expanded later
|
|
if ((child->type == AST_GENBLOCK || child->type == AST_BLOCK) && !child->str.empty())
|
|
continue;
|
|
|
|
child->expand_genblock(prefix);
|
|
}
|
|
}
|
|
|
|
// add implicit AST_GENBLOCK names according to IEEE 1364-2005 Section 12.4.3 or
|
|
// IEEE 1800-2017 Section 27.6
|
|
void AstNode::label_genblks(std::set<std::string>& existing, int &counter)
|
|
{
|
|
switch (type) {
|
|
case AST_GENIF:
|
|
case AST_GENFOR:
|
|
case AST_GENCASE:
|
|
// seeing a proper generate control flow construct increments the
|
|
// counter once
|
|
++counter;
|
|
for (AstNode *child : children)
|
|
child->label_genblks(existing, counter);
|
|
break;
|
|
|
|
case AST_GENBLOCK: {
|
|
// if this block is unlabeled, generate its corresponding unique name
|
|
for (int padding = 0; str.empty(); ++padding) {
|
|
std::string candidate = "\\genblk";
|
|
for (int i = 0; i < padding; ++i)
|
|
candidate += '0';
|
|
candidate += std::to_string(counter);
|
|
if (!existing.count(candidate))
|
|
str = candidate;
|
|
}
|
|
// within a genblk, the counter starts fresh
|
|
std::set<std::string> existing_local = existing;
|
|
int counter_local = 0;
|
|
for (AstNode *child : children)
|
|
child->label_genblks(existing_local, counter_local);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
// track names which could conflict with implicit genblk names
|
|
if (str.rfind("\\genblk", 0) == 0)
|
|
existing.insert(str);
|
|
for (AstNode *child : children)
|
|
child->label_genblks(existing, counter);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// helper function for mem2reg_as_needed_pass1
|
|
static void mark_memories_assign_lhs_complex(dict<AstNode*, pool<std::string>> &mem2reg_places,
|
|
dict<AstNode*, uint32_t> &mem2reg_candidates, AstNode *that)
|
|
{
|
|
for (auto &child : that->children)
|
|
mark_memories_assign_lhs_complex(mem2reg_places, mem2reg_candidates, child);
|
|
|
|
if (that->type == AST_IDENTIFIER && that->id2ast && that->id2ast->type == AST_MEMORY) {
|
|
AstNode *mem = that->id2ast;
|
|
if (!(mem2reg_candidates[mem] & AstNode::MEM2REG_FL_CMPLX_LHS))
|
|
mem2reg_places[mem].insert(stringf("%s:%d", RTLIL::encode_filename(that->filename).c_str(), that->location.first_line));
|
|
mem2reg_candidates[mem] |= AstNode::MEM2REG_FL_CMPLX_LHS;
|
|
}
|
|
}
|
|
|
|
// find memories that should be replaced by registers
|
|
void AstNode::mem2reg_as_needed_pass1(dict<AstNode*, pool<std::string>> &mem2reg_places,
|
|
dict<AstNode*, uint32_t> &mem2reg_candidates, dict<AstNode*, uint32_t> &proc_flags, uint32_t &flags)
|
|
{
|
|
uint32_t children_flags = 0;
|
|
int lhs_children_counter = 0;
|
|
|
|
if (type == AST_TYPEDEF)
|
|
return; // don't touch content of typedefs
|
|
|
|
if (type == AST_ASSIGN || type == AST_ASSIGN_LE || type == AST_ASSIGN_EQ)
|
|
{
|
|
// mark all memories that are used in a complex expression on the left side of an assignment
|
|
for (auto &lhs_child : children[0]->children)
|
|
mark_memories_assign_lhs_complex(mem2reg_places, mem2reg_candidates, lhs_child);
|
|
|
|
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", RTLIL::encode_filename(filename).c_str(), location.first_line));
|
|
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", RTLIL::encode_filename(filename).c_str(), location.first_line));
|
|
proc_flags[mem] |= AstNode::MEM2REG_FL_EQ1;
|
|
}
|
|
|
|
// for proper (non-init) writes: remember if this is a constant index or not
|
|
if ((flags & MEM2REG_FL_INIT) == 0) {
|
|
if (children[0]->children.size() && children[0]->children[0]->type == AST_RANGE && children[0]->children[0]->children.size()) {
|
|
if (children[0]->children[0]->children[0]->type == AST_CONSTANT)
|
|
mem2reg_candidates[mem] |= AstNode::MEM2REG_FL_CONST_LHS;
|
|
else
|
|
mem2reg_candidates[mem] |= AstNode::MEM2REG_FL_VAR_LHS;
|
|
}
|
|
}
|
|
|
|
// 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", RTLIL::encode_filename(filename).c_str(), location.first_line));
|
|
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", RTLIL::encode_filename(filename).c_str(), location.first_line));
|
|
mem2reg_candidates[mem] |= AstNode::MEM2REG_FL_SET_ELSE;
|
|
}
|
|
}
|
|
|
|
lhs_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", RTLIL::encode_filename(filename).c_str(), location.first_line));
|
|
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' or 'logic'
|
|
if (type == AST_MEMORY && (get_bool_attribute(ID::mem2reg) || (flags & AstNode::MEM2REG_FL_ALL) || !(is_reg || is_logic)))
|
|
mem2reg_candidates[this] |= AstNode::MEM2REG_FL_FORCED;
|
|
|
|
if ((type == AST_MODULE || type == AST_INTERFACE) && get_bool_attribute(ID::mem2reg))
|
|
children_flags |= AstNode::MEM2REG_FL_ALL;
|
|
|
|
dict<AstNode*, uint32_t> *proc_flags_p = NULL;
|
|
|
|
if (type == AST_ALWAYS) {
|
|
int count_edge_events = 0;
|
|
for (auto child : children)
|
|
if (child->type == AST_POSEDGE || child->type == AST_NEGEDGE)
|
|
count_edge_events++;
|
|
if (count_edge_events != 1)
|
|
children_flags |= AstNode::MEM2REG_FL_ASYNC;
|
|
proc_flags_p = new dict<AstNode*, uint32_t>;
|
|
}
|
|
else if (type == AST_INITIAL) {
|
|
children_flags |= AstNode::MEM2REG_FL_INIT;
|
|
proc_flags_p = new dict<AstNode*, uint32_t>;
|
|
}
|
|
|
|
uint32_t backup_flags = flags;
|
|
flags |= children_flags;
|
|
log_assert((flags & ~0x000000ff) == 0);
|
|
|
|
for (auto child : children)
|
|
{
|
|
if (lhs_children_counter > 0) {
|
|
lhs_children_counter--;
|
|
if (child->children.size() && child->children[0]->type == AST_RANGE && child->children[0]->children.size()) {
|
|
for (auto c : child->children[0]->children) {
|
|
if (proc_flags_p)
|
|
c->mem2reg_as_needed_pass1(mem2reg_places, mem2reg_candidates, *proc_flags_p, flags);
|
|
else
|
|
c->mem2reg_as_needed_pass1(mem2reg_places, mem2reg_candidates, proc_flags, flags);
|
|
}
|
|
}
|
|
} 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) {
|
|
#ifndef NDEBUG
|
|
for (auto it : *proc_flags_p)
|
|
log_assert((it.second & ~0xff000000) == 0);
|
|
#endif
|
|
delete proc_flags_p;
|
|
}
|
|
}
|
|
|
|
bool AstNode::mem2reg_check(pool<AstNode*> &mem2reg_set)
|
|
{
|
|
if (type != AST_IDENTIFIER || !id2ast || !mem2reg_set.count(id2ast))
|
|
return false;
|
|
|
|
if (children.empty() || children[0]->type != AST_RANGE || GetSize(children[0]->children) != 1)
|
|
log_file_error(filename, location.first_line, "Invalid array access.\n");
|
|
|
|
return true;
|
|
}
|
|
|
|
void AstNode::mem2reg_remove(pool<AstNode*> &mem2reg_set, vector<AstNode*> &delnodes)
|
|
{
|
|
log_assert(mem2reg_set.count(this) == 0);
|
|
|
|
if (mem2reg_set.count(id2ast))
|
|
id2ast = nullptr;
|
|
|
|
for (size_t i = 0; i < children.size(); i++) {
|
|
if (mem2reg_set.count(children[i]) > 0) {
|
|
delnodes.push_back(children[i]);
|
|
children.erase(children.begin() + (i--));
|
|
} else {
|
|
children[i]->mem2reg_remove(mem2reg_set, delnodes);
|
|
}
|
|
}
|
|
}
|
|
|
|
// actually replace memories with registers
|
|
bool AstNode::mem2reg_as_needed_pass2(pool<AstNode*> &mem2reg_set, AstNode *mod, AstNode *block, AstNode *&async_block)
|
|
{
|
|
bool did_something = false;
|
|
|
|
if (type == AST_BLOCK)
|
|
block = this;
|
|
|
|
if (type == AST_FUNCTION || type == AST_TASK)
|
|
return false;
|
|
|
|
if (type == AST_TYPEDEF)
|
|
return false;
|
|
|
|
if (type == AST_MEMINIT && id2ast && mem2reg_set.count(id2ast))
|
|
{
|
|
log_assert(children[0]->type == AST_CONSTANT);
|
|
log_assert(children[1]->type == AST_CONSTANT);
|
|
log_assert(children[2]->type == AST_CONSTANT);
|
|
log_assert(children[3]->type == AST_CONSTANT);
|
|
|
|
int cursor = children[0]->asInt(false);
|
|
Const data = children[1]->bitsAsConst();
|
|
Const en = children[2]->bitsAsConst();
|
|
int length = children[3]->asInt(false);
|
|
|
|
if (length != 0)
|
|
{
|
|
AstNode *block = new AstNode(AST_INITIAL, new AstNode(AST_BLOCK));
|
|
mod->children.push_back(block);
|
|
block = block->children[0];
|
|
|
|
int wordsz = GetSize(data) / length;
|
|
|
|
for (int i = 0; i < length; i++) {
|
|
int pos = 0;
|
|
while (pos < wordsz) {
|
|
if (en[pos] != State::S1) {
|
|
pos++;
|
|
} else {
|
|
int epos = pos + 1;
|
|
while (epos < wordsz && en[epos] == State::S1)
|
|
epos++;
|
|
int clen = epos - pos;
|
|
AstNode *range = new AstNode(AST_RANGE, AstNode::mkconst_int(cursor+i, false));
|
|
if (pos != 0 || epos != wordsz) {
|
|
int left;
|
|
int right;
|
|
AstNode *mrange = id2ast->children[0];
|
|
if (mrange->range_left < mrange->range_right) {
|
|
right = mrange->range_right - pos;
|
|
left = mrange->range_right - epos + 1;
|
|
} else {
|
|
right = mrange->range_right + pos;
|
|
left = mrange->range_right + epos - 1;
|
|
}
|
|
range = new AstNode(AST_MULTIRANGE, range, new AstNode(AST_RANGE, AstNode::mkconst_int(left, true), AstNode::mkconst_int(right, true)));
|
|
}
|
|
AstNode *target = new AstNode(AST_IDENTIFIER, range);
|
|
target->str = str;
|
|
target->id2ast = id2ast;
|
|
target->was_checked = true;
|
|
block->children.push_back(new AstNode(AST_ASSIGN_EQ, target, mkconst_bits(data.extract(i*wordsz + pos, clen).bits, false)));
|
|
pos = epos;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
AstNode *newNode = new AstNode(AST_NONE);
|
|
newNode->cloneInto(this);
|
|
delete newNode;
|
|
|
|
did_something = true;
|
|
}
|
|
|
|
if (type == AST_ASSIGN && block == NULL && children[0]->mem2reg_check(mem2reg_set))
|
|
{
|
|
if (async_block == NULL) {
|
|
async_block = new AstNode(AST_ALWAYS, new AstNode(AST_BLOCK));
|
|
mod->children.push_back(async_block);
|
|
}
|
|
|
|
AstNode *newNode = clone();
|
|
newNode->type = AST_ASSIGN_EQ;
|
|
newNode->children[0]->was_checked = true;
|
|
async_block->children[0]->children.push_back(newNode);
|
|
|
|
newNode = new AstNode(AST_NONE);
|
|
newNode->cloneInto(this);
|
|
delete newNode;
|
|
|
|
did_something = true;
|
|
}
|
|
|
|
if ((type == AST_ASSIGN_LE || type == AST_ASSIGN_EQ) && children[0]->mem2reg_check(mem2reg_set) &&
|
|
children[0]->children[0]->children[0]->type != AST_CONSTANT)
|
|
{
|
|
std::stringstream sstr;
|
|
sstr << "$mem2reg_wr$" << children[0]->str << "$" << RTLIL::encode_filename(filename) << ":" << location.first_line << "$" << (autoidx++);
|
|
std::string id_addr = sstr.str() + "_ADDR", id_data = sstr.str() + "_DATA";
|
|
|
|
int mem_width, mem_size, addr_bits;
|
|
bool mem_signed = children[0]->id2ast->is_signed;
|
|
children[0]->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;
|
|
wire_addr->was_checked = true;
|
|
wire_addr->attributes[ID::nosync] = AstNode::mkconst_int(1, false);
|
|
mod->children.push_back(wire_addr);
|
|
while (wire_addr->simplify(true, false, false, 1, -1, false, 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;
|
|
wire_data->was_checked = true;
|
|
wire_data->is_signed = mem_signed;
|
|
wire_data->attributes[ID::nosync] = AstNode::mkconst_int(1, false);
|
|
mod->children.push_back(wire_data);
|
|
while (wire_data->simplify(true, false, false, 1, -1, false, false)) { }
|
|
|
|
log_assert(block != NULL);
|
|
size_t assign_idx = 0;
|
|
while (assign_idx < block->children.size() && block->children[assign_idx] != this)
|
|
assign_idx++;
|
|
log_assert(assign_idx < block->children.size());
|
|
|
|
AstNode *assign_addr = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), children[0]->children[0]->children[0]->clone());
|
|
assign_addr->children[0]->str = id_addr;
|
|
assign_addr->children[0]->was_checked = true;
|
|
block->children.insert(block->children.begin()+assign_idx+1, assign_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]->children[0]->type == AST_CONSTANT && int(children[0]->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(type, new AstNode(AST_IDENTIFIER), new AstNode(AST_IDENTIFIER));
|
|
if (children[0]->children.size() == 2)
|
|
assign_reg->children[0]->children.push_back(children[0]->children[1]->clone());
|
|
assign_reg->children[0]->str = stringf("%s[%d]", children[0]->str.c_str(), i);
|
|
assign_reg->children[1]->str = id_data;
|
|
cond_node->children[1]->children.push_back(assign_reg);
|
|
case_node->children.push_back(cond_node);
|
|
}
|
|
block->children.insert(block->children.begin()+assign_idx+2, case_node);
|
|
|
|
children[0]->delete_children();
|
|
children[0]->range_valid = false;
|
|
children[0]->id2ast = NULL;
|
|
children[0]->str = id_data;
|
|
type = AST_ASSIGN_EQ;
|
|
children[0]->was_checked = true;
|
|
|
|
did_something = true;
|
|
}
|
|
|
|
if (mem2reg_check(mem2reg_set))
|
|
{
|
|
AstNode *bit_part_sel = NULL;
|
|
if (children.size() == 2)
|
|
bit_part_sel = children[1]->clone();
|
|
|
|
if (children[0]->children[0]->type == AST_CONSTANT)
|
|
{
|
|
int id = children[0]->children[0]->integer;
|
|
int left = id2ast->children[1]->children[0]->integer;
|
|
int right = id2ast->children[1]->children[1]->integer;
|
|
bool valid_const_access =
|
|
(left <= id && id <= right) ||
|
|
(right <= id && id <= left);
|
|
if (valid_const_access)
|
|
{
|
|
str = stringf("%s[%d]", str.c_str(), id);
|
|
delete_children();
|
|
range_valid = false;
|
|
id2ast = NULL;
|
|
}
|
|
else
|
|
{
|
|
int width;
|
|
if (bit_part_sel)
|
|
{
|
|
bit_part_sel->dumpAst(nullptr, "? ");
|
|
if (bit_part_sel->children.size() == 1)
|
|
width = 0;
|
|
else
|
|
width = bit_part_sel->children[0]->integer -
|
|
bit_part_sel->children[1]->integer;
|
|
delete bit_part_sel;
|
|
bit_part_sel = nullptr;
|
|
}
|
|
else
|
|
{
|
|
width = id2ast->children[0]->children[0]->integer -
|
|
id2ast->children[0]->children[1]->integer;
|
|
}
|
|
width = abs(width) + 1;
|
|
|
|
delete_children();
|
|
|
|
std::vector<RTLIL::State> x_bits;
|
|
for (int i = 0; i < width; i++)
|
|
x_bits.push_back(RTLIL::State::Sx);
|
|
AstNode *constant = AstNode::mkconst_bits(x_bits, false);
|
|
constant->cloneInto(this);
|
|
delete constant;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::stringstream sstr;
|
|
sstr << "$mem2reg_rd$" << str << "$" << RTLIL::encode_filename(filename) << ":" << location.first_line << "$" << (autoidx++);
|
|
std::string id_addr = sstr.str() + "_ADDR", id_data = sstr.str() + "_DATA";
|
|
|
|
int mem_width, mem_size, addr_bits;
|
|
bool mem_signed = id2ast->is_signed;
|
|
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;
|
|
wire_addr->was_checked = true;
|
|
if (block)
|
|
wire_addr->attributes[ID::nosync] = AstNode::mkconst_int(1, false);
|
|
mod->children.push_back(wire_addr);
|
|
while (wire_addr->simplify(true, false, false, 1, -1, false, 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;
|
|
wire_data->was_checked = true;
|
|
wire_data->is_signed = mem_signed;
|
|
if (block)
|
|
wire_data->attributes[ID::nosync] = AstNode::mkconst_int(1, false);
|
|
mod->children.push_back(wire_data);
|
|
while (wire_data->simplify(true, false, false, 1, -1, false, 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;
|
|
assign_addr->children[0]->was_checked = true;
|
|
|
|
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[0]->was_checked = true;
|
|
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;
|
|
assign_reg->children[0]->was_checked = true;
|
|
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++;
|
|
log_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;
|
|
}
|
|
|
|
if (bit_part_sel)
|
|
children.push_back(bit_part_sel);
|
|
|
|
did_something = true;
|
|
}
|
|
|
|
log_assert(id2ast == NULL || mem2reg_set.count(id2ast) == 0);
|
|
|
|
auto children_list = children;
|
|
for (size_t i = 0; i < children_list.size(); i++)
|
|
if (children_list[i]->mem2reg_as_needed_pass2(mem2reg_set, mod, block, async_block))
|
|
did_something = true;
|
|
|
|
return did_something;
|
|
}
|
|
|
|
// calculate memory dimensions
|
|
void AstNode::meminfo(int &mem_width, int &mem_size, int &addr_bits)
|
|
{
|
|
log_assert(type == AST_MEMORY);
|
|
|
|
mem_width = children[0]->range_left - children[0]->range_right + 1;
|
|
mem_size = children[1]->range_left - children[1]->range_right;
|
|
|
|
if (mem_size < 0)
|
|
mem_size *= -1;
|
|
mem_size += min(children[1]->range_left, children[1]->range_right) + 1;
|
|
|
|
addr_bits = 1;
|
|
while ((1 << addr_bits) < mem_size)
|
|
addr_bits++;
|
|
}
|
|
|
|
bool AstNode::detect_latch(const std::string &var)
|
|
{
|
|
switch (type)
|
|
{
|
|
case AST_ALWAYS:
|
|
for (auto &c : children)
|
|
{
|
|
switch (c->type)
|
|
{
|
|
case AST_POSEDGE:
|
|
case AST_NEGEDGE:
|
|
return false;
|
|
case AST_EDGE:
|
|
break;
|
|
case AST_BLOCK:
|
|
if (!c->detect_latch(var))
|
|
return false;
|
|
break;
|
|
default:
|
|
log_abort();
|
|
}
|
|
}
|
|
return true;
|
|
case AST_BLOCK:
|
|
for (auto &c : children)
|
|
if (!c->detect_latch(var))
|
|
return false;
|
|
return true;
|
|
case AST_CASE:
|
|
{
|
|
bool r = true;
|
|
for (auto &c : children) {
|
|
if (c->type == AST_COND) {
|
|
if (c->children.at(1)->detect_latch(var))
|
|
return true;
|
|
r = false;
|
|
}
|
|
if (c->type == AST_DEFAULT) {
|
|
if (c->children.at(0)->detect_latch(var))
|
|
return true;
|
|
r = false;
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
case AST_ASSIGN_EQ:
|
|
case AST_ASSIGN_LE:
|
|
if (children.at(0)->type == AST_IDENTIFIER &&
|
|
children.at(0)->children.empty() && children.at(0)->str == var)
|
|
return false;
|
|
return true;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool AstNode::has_const_only_constructs()
|
|
{
|
|
if (type == AST_WHILE || type == AST_REPEAT)
|
|
return true;
|
|
for (auto child : children)
|
|
if (child->has_const_only_constructs())
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool AstNode::is_simple_const_expr()
|
|
{
|
|
if (type == AST_IDENTIFIER)
|
|
return false;
|
|
for (auto child : children)
|
|
if (!child->is_simple_const_expr())
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
// helper function for AstNode::eval_const_function()
|
|
bool AstNode::replace_variables(std::map<std::string, AstNode::varinfo_t> &variables, AstNode *fcall, bool must_succeed)
|
|
{
|
|
if (type == AST_IDENTIFIER && variables.count(str)) {
|
|
int offset = variables.at(str).offset, width = variables.at(str).val.bits.size();
|
|
if (!children.empty()) {
|
|
if (children.size() != 1 || children.at(0)->type != AST_RANGE) {
|
|
if (!must_succeed)
|
|
return false;
|
|
log_file_error(filename, location.first_line, "Memory access in constant function is not supported\n%s: ...called from here.\n",
|
|
fcall->loc_string().c_str());
|
|
}
|
|
if (!children.at(0)->replace_variables(variables, fcall, must_succeed))
|
|
return false;
|
|
while (simplify(true, false, false, 1, -1, false, true)) { }
|
|
if (!children.at(0)->range_valid) {
|
|
if (!must_succeed)
|
|
return false;
|
|
log_file_error(filename, location.first_line, "Non-constant range\n%s: ... called from here.\n",
|
|
fcall->loc_string().c_str());
|
|
}
|
|
offset = min(children.at(0)->range_left, children.at(0)->range_right);
|
|
width = min(std::abs(children.at(0)->range_left - children.at(0)->range_right) + 1, width);
|
|
}
|
|
offset -= variables.at(str).offset;
|
|
if (variables.at(str).range_swapped)
|
|
offset = -offset;
|
|
std::vector<RTLIL::State> &var_bits = variables.at(str).val.bits;
|
|
std::vector<RTLIL::State> new_bits(var_bits.begin() + offset, var_bits.begin() + offset + width);
|
|
AstNode *newNode = mkconst_bits(new_bits, variables.at(str).is_signed);
|
|
newNode->cloneInto(this);
|
|
delete newNode;
|
|
return true;
|
|
}
|
|
|
|
for (auto &child : children)
|
|
if (!child->replace_variables(variables, fcall, must_succeed))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
// attempt to statically evaluate a functions with all-const arguments
|
|
AstNode *AstNode::eval_const_function(AstNode *fcall, bool must_succeed)
|
|
{
|
|
std::map<std::string, AstNode*> backup_scope = current_scope;
|
|
std::map<std::string, AstNode::varinfo_t> variables;
|
|
std::vector<AstNode*> to_delete;
|
|
AstNode *block = new AstNode(AST_BLOCK);
|
|
AstNode *result = nullptr;
|
|
|
|
size_t argidx = 0;
|
|
for (auto child : children)
|
|
{
|
|
block->children.push_back(child->clone());
|
|
}
|
|
|
|
while (!block->children.empty())
|
|
{
|
|
AstNode *stmt = block->children.front();
|
|
|
|
#if 0
|
|
log("-----------------------------------\n");
|
|
for (auto &it : variables)
|
|
log("%20s %40s\n", it.first.c_str(), log_signal(it.second.val));
|
|
stmt->dumpAst(NULL, "stmt> ");
|
|
#endif
|
|
|
|
if (stmt->type == AST_WIRE)
|
|
{
|
|
while (stmt->simplify(true, false, false, 1, -1, false, true)) { }
|
|
if (!stmt->range_valid) {
|
|
if (!must_succeed)
|
|
goto finished;
|
|
log_file_error(stmt->filename, stmt->location.first_line, "Can't determine size of variable %s\n%s: ... called from here.\n",
|
|
stmt->str.c_str(), fcall->loc_string().c_str());
|
|
}
|
|
AstNode::varinfo_t &variable = variables[stmt->str];
|
|
int width = abs(stmt->range_left - stmt->range_right) + 1;
|
|
// if this variable has already been declared as an input, check the
|
|
// sizes match if it already had an explicit size
|
|
if (variable.arg && variable.explicitly_sized && variable.val.size() != width) {
|
|
log_file_error(filename, location.first_line, "Incompatible re-declaration of constant function wire %s.\n", stmt->str.c_str());
|
|
}
|
|
variable.val = RTLIL::Const(RTLIL::State::Sx, width);
|
|
variable.offset = stmt->range_swapped ? stmt->range_left : stmt->range_right;
|
|
variable.range_swapped = stmt->range_swapped;
|
|
variable.is_signed = stmt->is_signed;
|
|
variable.explicitly_sized = stmt->children.size() &&
|
|
stmt->children.back()->type == AST_RANGE;
|
|
// identify the argument corresponding to this wire, if applicable
|
|
if (stmt->is_input && argidx < fcall->children.size()) {
|
|
variable.arg = fcall->children.at(argidx++);
|
|
}
|
|
// load the constant arg's value into this variable
|
|
if (variable.arg) {
|
|
if (variable.arg->type == AST_CONSTANT) {
|
|
variable.val = variable.arg->bitsAsConst(width);
|
|
} else {
|
|
log_assert(variable.arg->type == AST_REALVALUE);
|
|
variable.val = variable.arg->realAsConst(width);
|
|
}
|
|
}
|
|
current_scope[stmt->str] = stmt;
|
|
|
|
block->children.erase(block->children.begin());
|
|
to_delete.push_back(stmt);
|
|
continue;
|
|
}
|
|
|
|
log_assert(variables.count(str) != 0);
|
|
|
|
if (stmt->type == AST_LOCALPARAM)
|
|
{
|
|
while (stmt->simplify(true, false, false, 1, -1, false, true)) { }
|
|
|
|
current_scope[stmt->str] = stmt;
|
|
|
|
block->children.erase(block->children.begin());
|
|
to_delete.push_back(stmt);
|
|
continue;
|
|
}
|
|
|
|
if (stmt->type == AST_ASSIGN_EQ)
|
|
{
|
|
if (stmt->children.at(0)->type == AST_IDENTIFIER && stmt->children.at(0)->children.size() != 0 &&
|
|
stmt->children.at(0)->children.at(0)->type == AST_RANGE)
|
|
if (!stmt->children.at(0)->children.at(0)->replace_variables(variables, fcall, must_succeed))
|
|
goto finished;
|
|
if (!stmt->children.at(1)->replace_variables(variables, fcall, must_succeed))
|
|
goto finished;
|
|
while (stmt->simplify(true, false, false, 1, -1, false, true)) { }
|
|
|
|
if (stmt->type != AST_ASSIGN_EQ)
|
|
continue;
|
|
|
|
if (stmt->children.at(1)->type != AST_CONSTANT) {
|
|
if (!must_succeed)
|
|
goto finished;
|
|
log_file_error(stmt->filename, stmt->location.first_line, "Non-constant expression in constant function\n%s: ... called from here. X\n",
|
|
fcall->loc_string().c_str());
|
|
}
|
|
|
|
if (stmt->children.at(0)->type != AST_IDENTIFIER) {
|
|
if (!must_succeed)
|
|
goto finished;
|
|
log_file_error(stmt->filename, stmt->location.first_line, "Unsupported composite left hand side in constant function\n%s: ... called from here.\n",
|
|
fcall->loc_string().c_str());
|
|
}
|
|
|
|
if (!variables.count(stmt->children.at(0)->str)) {
|
|
if (!must_succeed)
|
|
goto finished;
|
|
log_file_error(stmt->filename, stmt->location.first_line, "Assignment to non-local variable in constant function\n%s: ... called from here.\n",
|
|
fcall->loc_string().c_str());
|
|
}
|
|
|
|
if (stmt->children.at(0)->children.empty()) {
|
|
variables[stmt->children.at(0)->str].val = stmt->children.at(1)->bitsAsConst(variables[stmt->children.at(0)->str].val.bits.size());
|
|
} else {
|
|
AstNode *range = stmt->children.at(0)->children.at(0);
|
|
if (!range->range_valid) {
|
|
if (!must_succeed)
|
|
goto finished;
|
|
log_file_error(range->filename, range->location.first_line, "Non-constant range\n%s: ... called from here.\n",
|
|
fcall->loc_string().c_str());
|
|
}
|
|
int offset = min(range->range_left, range->range_right);
|
|
int width = std::abs(range->range_left - range->range_right) + 1;
|
|
varinfo_t &v = variables[stmt->children.at(0)->str];
|
|
RTLIL::Const r = stmt->children.at(1)->bitsAsConst(v.val.bits.size());
|
|
for (int i = 0; i < width; i++) {
|
|
int index = i + offset - v.offset;
|
|
if (v.range_swapped)
|
|
index = -index;
|
|
v.val.bits.at(index) = r.bits.at(i);
|
|
}
|
|
}
|
|
|
|
delete block->children.front();
|
|
block->children.erase(block->children.begin());
|
|
continue;
|
|
}
|
|
|
|
if (stmt->type == AST_FOR)
|
|
{
|
|
block->children.insert(block->children.begin(), stmt->children.at(0));
|
|
stmt->children.at(3)->children.push_back(stmt->children.at(2));
|
|
stmt->children.erase(stmt->children.begin() + 2);
|
|
stmt->children.erase(stmt->children.begin());
|
|
stmt->type = AST_WHILE;
|
|
continue;
|
|
}
|
|
|
|
if (stmt->type == AST_WHILE)
|
|
{
|
|
AstNode *cond = stmt->children.at(0)->clone();
|
|
if (!cond->replace_variables(variables, fcall, must_succeed))
|
|
goto finished;
|
|
while (cond->simplify(true, false, false, 1, -1, false, true)) { }
|
|
|
|
if (cond->type != AST_CONSTANT) {
|
|
if (!must_succeed)
|
|
goto finished;
|
|
log_file_error(stmt->filename, stmt->location.first_line, "Non-constant expression in constant function\n%s: ... called from here.\n",
|
|
fcall->loc_string().c_str());
|
|
}
|
|
|
|
if (cond->asBool()) {
|
|
block->children.insert(block->children.begin(), stmt->children.at(1)->clone());
|
|
} else {
|
|
delete block->children.front();
|
|
block->children.erase(block->children.begin());
|
|
}
|
|
|
|
delete cond;
|
|
continue;
|
|
}
|
|
|
|
if (stmt->type == AST_REPEAT)
|
|
{
|
|
AstNode *num = stmt->children.at(0)->clone();
|
|
if (!num->replace_variables(variables, fcall, must_succeed))
|
|
goto finished;
|
|
while (num->simplify(true, false, false, 1, -1, false, true)) { }
|
|
|
|
if (num->type != AST_CONSTANT) {
|
|
if (!must_succeed)
|
|
goto finished;
|
|
log_file_error(stmt->filename, stmt->location.first_line, "Non-constant expression in constant function\n%s: ... called from here.\n",
|
|
fcall->loc_string().c_str());
|
|
}
|
|
|
|
block->children.erase(block->children.begin());
|
|
for (int i = 0; i < num->bitsAsConst().as_int(); i++)
|
|
block->children.insert(block->children.begin(), stmt->children.at(1)->clone());
|
|
|
|
delete stmt;
|
|
delete num;
|
|
continue;
|
|
}
|
|
|
|
if (stmt->type == AST_CASE)
|
|
{
|
|
AstNode *expr = stmt->children.at(0)->clone();
|
|
if (!expr->replace_variables(variables, fcall, must_succeed))
|
|
goto finished;
|
|
while (expr->simplify(true, false, false, 1, -1, false, true)) { }
|
|
|
|
AstNode *sel_case = NULL;
|
|
for (size_t i = 1; i < stmt->children.size(); i++)
|
|
{
|
|
bool found_match = false;
|
|
log_assert(stmt->children.at(i)->type == AST_COND || stmt->children.at(i)->type == AST_CONDX || stmt->children.at(i)->type == AST_CONDZ);
|
|
|
|
if (stmt->children.at(i)->children.front()->type == AST_DEFAULT) {
|
|
sel_case = stmt->children.at(i)->children.back();
|
|
continue;
|
|
}
|
|
|
|
for (size_t j = 0; j+1 < stmt->children.at(i)->children.size() && !found_match; j++)
|
|
{
|
|
AstNode *cond = stmt->children.at(i)->children.at(j)->clone();
|
|
if (!cond->replace_variables(variables, fcall, must_succeed))
|
|
goto finished;
|
|
|
|
cond = new AstNode(AST_EQ, expr->clone(), cond);
|
|
while (cond->simplify(true, false, false, 1, -1, false, true)) { }
|
|
|
|
if (cond->type != AST_CONSTANT) {
|
|
if (!must_succeed)
|
|
goto finished;
|
|
log_file_error(stmt->filename, stmt->location.first_line, "Non-constant expression in constant function\n%s: ... called from here.\n",
|
|
fcall->loc_string().c_str());
|
|
}
|
|
|
|
found_match = cond->asBool();
|
|
delete cond;
|
|
}
|
|
|
|
if (found_match) {
|
|
sel_case = stmt->children.at(i)->children.back();
|
|
break;
|
|
}
|
|
}
|
|
|
|
block->children.erase(block->children.begin());
|
|
if (sel_case)
|
|
block->children.insert(block->children.begin(), sel_case->clone());
|
|
delete stmt;
|
|
delete expr;
|
|
continue;
|
|
}
|
|
|
|
if (stmt->type == AST_BLOCK)
|
|
{
|
|
if (!stmt->str.empty())
|
|
stmt->expand_genblock(stmt->str + ".");
|
|
|
|
block->children.erase(block->children.begin());
|
|
block->children.insert(block->children.begin(), stmt->children.begin(), stmt->children.end());
|
|
stmt->children.clear();
|
|
delete stmt;
|
|
continue;
|
|
}
|
|
|
|
if (!must_succeed)
|
|
goto finished;
|
|
log_file_error(stmt->filename, stmt->location.first_line, "Unsupported language construct in constant function\n%s: ... called from here.\n",
|
|
fcall->loc_string().c_str());
|
|
log_abort();
|
|
}
|
|
|
|
result = AstNode::mkconst_bits(variables.at(str).val.bits, variables.at(str).is_signed);
|
|
|
|
finished:
|
|
delete block;
|
|
current_scope = backup_scope;
|
|
|
|
for (auto it : to_delete) {
|
|
delete it;
|
|
}
|
|
to_delete.clear();
|
|
|
|
return result;
|
|
}
|
|
|
|
void AstNode::allocateDefaultEnumValues()
|
|
{
|
|
log_assert(type==AST_ENUM);
|
|
log_assert(children.size() > 0);
|
|
if (children.front()->attributes.count(ID::enum_base_type))
|
|
return; // already elaborated
|
|
int last_enum_int = -1;
|
|
for (auto node : children) {
|
|
log_assert(node->type==AST_ENUM_ITEM);
|
|
node->attributes[ID::enum_base_type] = mkconst_str(str);
|
|
for (size_t i = 0; i < node->children.size(); i++) {
|
|
switch (node->children[i]->type) {
|
|
case AST_NONE:
|
|
// replace with auto-incremented constant
|
|
delete node->children[i];
|
|
node->children[i] = AstNode::mkconst_int(++last_enum_int, true);
|
|
break;
|
|
case AST_CONSTANT:
|
|
// explicit constant (or folded expression)
|
|
// TODO: can't extend 'x or 'z item
|
|
last_enum_int = node->children[i]->integer;
|
|
break;
|
|
default:
|
|
// ignore ranges
|
|
break;
|
|
}
|
|
// TODO: range check
|
|
}
|
|
}
|
|
}
|
|
|
|
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};
|
|
}
|
|
|
|
std::string AstNode::try_pop_module_prefix() const
|
|
{
|
|
AstNode *current_scope_ast = (current_ast_mod == nullptr) ? current_ast : current_ast_mod;
|
|
size_t pos = str.find('.', 1);
|
|
if (str[0] == '\\' && pos != std::string::npos) {
|
|
std::string new_str = "\\" + str.substr(pos + 1);
|
|
if (current_scope.count(new_str)) {
|
|
std::string prefix = str.substr(0, pos);
|
|
auto it = current_scope_ast->attributes.find(ID::hdlname);
|
|
if ((it != current_scope_ast->attributes.end() && it->second->str == prefix)
|
|
|| prefix == current_scope_ast->str)
|
|
return new_str;
|
|
}
|
|
}
|
|
return str;
|
|
}
|
|
|
|
YOSYS_NAMESPACE_END
|