Merge pull request #454 from antmicro/wsip/typeparam

Handle type parameters
diff --git a/Makefile_plugin.common b/Makefile_plugin.common
index 45c2640..bfa9809 100644
--- a/Makefile_plugin.common
+++ b/Makefile_plugin.common
@@ -101,14 +101,14 @@
 
 # Shared library
 
-_SO_LIB := $(BUILD_DIR)/$(NAME).so
-_ALL_BUILD_SUBDIRS += $(abspath $(dir $(_SO_LIB)))
+SO_LIB := $(BUILD_DIR)/$(NAME).so
+_ALL_BUILD_SUBDIRS += $(abspath $(dir $(SO_LIB)))
 
-$(_SO_LIB): $(_ALL_OBJECTS) $(_MAKEFILES) | $(abspath $(dir $(_SO_LIB)))
+$(SO_LIB): $(_ALL_OBJECTS) $(_MAKEFILES) | $(abspath $(dir $(SO_LIB)))
 	$(CXX) $(CXXFLAGS) $(LDFLAGS) -shared -o $@ $(_ALL_OBJECTS) $(LDLIBS)
 
 .PHONY: $(NAME).so
-$(NAME).so: $(_SO_LIB)
+$(NAME).so: $(SO_LIB)
 
 # Tests
 
@@ -125,8 +125,8 @@
 
 # Installation
 
-$(YOSYS_PLUGINS_DIR)/$(NAME).so: $(_SO_LIB) | $(YOSYS_PLUGINS_DIR)
-	install -D $(_SO_LIB) $@
+$(YOSYS_PLUGINS_DIR)/$(NAME).so: $(SO_LIB) | $(YOSYS_PLUGINS_DIR)
+	install -D $(SO_LIB) $@
 
 .PHONY: install_plugin
 install_plugin: $(YOSYS_PLUGINS_DIR)/$(NAME).so
@@ -151,4 +151,3 @@
 
 $(PMGEN_PY):
 	@$(MAKE) -C $(TOP_DIR) pmgen.py
-
diff --git a/environment.yml b/environment.yml
index 35b3ed9..a4cf46c 100644
--- a/environment.yml
+++ b/environment.yml
@@ -20,5 +20,5 @@
   - litex-hub
 dependencies:
   - litex-hub::yosys=0.17_7_g990c9b8e1=20220512_085338_py37
-  - litex-hub::surelog=0.0_5580_g493120558=20230116_180937_py37
+  - litex-hub::surelog
   - litex-hub::iverilog
diff --git a/systemverilog-plugin/Makefile b/systemverilog-plugin/Makefile
index af5320d..e809349 100644
--- a/systemverilog-plugin/Makefile
+++ b/systemverilog-plugin/Makefile
@@ -22,36 +22,34 @@
           uhdmcommonfrontend.cc \
           uhdmsurelogastfrontend.cc \
           uhdmastreport.cc \
-          third_party/yosys/const2ast.cc
+          third_party/yosys/const2ast.cc \
+          third_party/yosys/simplify.cc
 
 # Directory to search for Surelog and UHDM libraries
 UHDM_INSTALL_DIR ?= /usr/local
 
+# Tell pkg-config to look in the provided install path first.
+# PKG_CONFIG_PATH and PKG_CONFIG_PATH_FOR_TARGET are search paths it looks in
+# so set the environment variables and prefix with our local install first
+PKG_CONFIG_INVOKE = \
+   PKG_CONFIG_PATH=$(UHDM_INSTALL_DIR)/lib/pkgconfig:${PKG_CONFIG_PATH} \
+   PKG_CONFIG_PATH_FOR_TARGET=$(UHDM_INSTALL_DIR)/lib/pkgconfig:${PKG_CONFIG_PATH_FOR_TARGET} \
+   pkg-config
+
 include ../Makefile_plugin.common
 
+# A litmus-test: make compilation fail if pkg-config fails
+.SECONDARY: $(BUILD_DIR)/.$(NAME)-deps-test
+$(BUILD_DIR)/.$(NAME)-deps-test:
+	$(PKG_CONFIG_INVOKE) --cflags Surelog
+
+${SO_LIB}: | $(BUILD_DIR)/.$(NAME)-deps-test
+
 CXXFLAGS += -std=c++17 -Wall -W -Wextra \
             -Wno-deprecated-declarations \
             -Wno-unused-parameter \
-            -I${UHDM_INSTALL_DIR}/include \
-            -I${UHDM_INSTALL_DIR}/include/Surelog
+            $(shell $(PKG_CONFIG_INVOKE) --cflags Surelog)
 
-LDFLAGS += -L${UHDM_INSTALL_DIR}/lib/uhdm \
-           -L${UHDM_INSTALL_DIR}/lib/surelog \
-           -L${UHDM_INSTALL_DIR}/lib \
-           -L${UHDM_INSTALL_DIR}/lib64/uhdm \
-           -L${UHDM_INSTALL_DIR}/lib64/surelog \
-           -L${UHDM_INSTALL_DIR}/lib64
+LDFLAGS += $(shell $(PKG_CONFIG_INVOKE) --libs-only-L Surelog)
 
-LDLIBS += -Wl,--whole-archive \
-          -luhdm \
-          -Wl,--no-whole-archive \
-          -lsurelog \
-          -lantlr4-runtime \
-          -lflatbuffers \
-          -lcapnp \
-          -lkj \
-          -ldl \
-          -lutil \
-          -lm \
-          -lrt \
-          -lpthread
+LDLIBS += $(shell $(PKG_CONFIG_INVOKE) --libs-only-l --libs-only-other Surelog)
diff --git a/systemverilog-plugin/UhdmAst.cc b/systemverilog-plugin/UhdmAst.cc
index 10c07a4..3a2524e 100644
--- a/systemverilog-plugin/UhdmAst.cc
+++ b/systemverilog-plugin/UhdmAst.cc
@@ -17,6 +17,7 @@
 #include <uhdm/vpi_user.h>
 
 #include "third_party/yosys/const2ast.h"
+#include "third_party/yosys/simplify.h"
 
 YOSYS_NAMESPACE_BEGIN
 namespace VERILOG_FRONTEND
@@ -158,6 +159,8 @@
     node->children.clear();
 }
 
+static void simplify_sv(AST::AstNode *current_node, AST::AstNode *parent_node);
+
 static void sanitize_symbol_name(std::string &name)
 {
     if (!name.empty()) {
@@ -295,18 +298,22 @@
         if (ranges[i]->children.size() == 1) {
             ranges[i]->children.push_back(ranges[i]->children[0]->clone());
         }
-        while (ranges[i]->simplify(true, false, false, 1, -1, false, false)) {
+        simplify_sv(ranges[i], wire_node);
+        while (simplify(ranges[i], true, false, false, 1, -1, false, false)) {
         }
         // this workaround case, where yosys doesn't follow id2ast and simplifies it to resolve constant
         if (ranges[i]->children[0]->id2ast) {
-            while (ranges[i]->children[0]->id2ast->simplify(true, false, false, 1, -1, false, false)) {
+            simplify_sv(ranges[i]->children[0]->id2ast, ranges[i]->children[0]);
+            while (simplify(ranges[i]->children[0]->id2ast, true, false, false, 1, -1, false, false)) {
             }
         }
         if (ranges[i]->children[1]->id2ast) {
-            while (ranges[i]->children[1]->id2ast->simplify(true, false, false, 1, -1, false, false)) {
+            simplify_sv(ranges[i]->children[1]->id2ast, ranges[i]->children[1]);
+            while (simplify(ranges[i]->children[1]->id2ast, true, false, false, 1, -1, false, false)) {
             }
         }
-        while (ranges[i]->simplify(true, false, false, 1, -1, false, false)) {
+        simplify_sv(ranges[i], wire_node);
+        while (simplify(ranges[i], true, false, false, 1, -1, false, false)) {
         }
         log_assert(ranges[i]->children[0]->type == AST::AST_CONSTANT);
         log_assert(ranges[i]->children[1]->type == AST::AST_CONSTANT);
@@ -426,9 +433,11 @@
     wiretype_ast = AST_INTERNAL::current_scope[wiretype_node->str];
     // we need to setup current top ast as this simplify
     // needs to have access to all already defined ids
-    while (wire_node->simplify(true, false, false, 1, -1, false, false)) {
+    while (simplify(wire_node, true, false, false, 1, -1, false, false)) {
     }
-    if (wiretype_ast->children[0]->type == AST::AST_STRUCT && wire_node->type == AST::AST_WIRE) {
+    log_assert(!wiretype_ast->children.empty());
+    if ((wiretype_ast->children[0]->type == AST::AST_STRUCT || wiretype_ast->children[0]->type == AST::AST_UNION) &&
+        wire_node->type == AST::AST_WIRE) {
         auto struct_width = get_max_offset_struct(wiretype_ast->children[0]);
         wire_node->range_left = struct_width;
         wire_node->children[0]->range_left = struct_width;
@@ -691,26 +700,6 @@
     }
     current_struct_elem = *struct_elem_it;
 
-    AST::AstNode *left = nullptr, *right = nullptr;
-    if (current_struct_elem->type == AST::AST_STRUCT_ITEM) {
-        left = AST::AstNode::mkconst_int(current_struct_elem->range_left, true);
-        right = AST::AstNode::mkconst_int(current_struct_elem->range_right, true);
-    } else if (current_struct_elem->type == AST::AST_STRUCT) {
-        // Struct can have multiple range, so to get size of 1 struct,
-        // we get left range for first children, and right range for last children
-        left = AST::AstNode::mkconst_int(current_struct_elem->children.front()->range_left, true);
-        right = AST::AstNode::mkconst_int(current_struct_elem->children.back()->range_right, true);
-    } else if (current_struct_elem->type == AST::AST_UNION) {
-        left = AST::AstNode::mkconst_int(current_struct_elem->range_left, true);
-        right = AST::AstNode::mkconst_int(current_struct_elem->range_right, true);
-    } else {
-        // Structs currently can only have AST_STRUCT, AST_STRUCT_ITEM, or AST_UNION.
-        log_file_error(current_struct_elem->filename, current_struct_elem->location.first_line,
-                       "Accessing struct member of type %s is unsupported.\n", type2str(current_struct_elem->type).c_str());
-    }
-
-    auto elem_size =
-      new AST::AstNode(AST::AST_ADD, new AST::AstNode(AST::AST_SUB, left->clone(), right->clone()), AST::AstNode::mkconst_int(1, true));
     AST::AstNode *sub_dot = nullptr;
     AST::AstNode *struct_range = nullptr;
 
@@ -722,10 +711,46 @@
         }
         if (c->type == AST::AST_RANGE) {
             // Currently supporting only 1 range
-            log_assert(!struct_range);
+            if (struct_range) {
+                log_file_error(struct_range->filename, struct_range->location.first_line,
+                               "Currently support for dot-access is limited to single range\n");
+            }
             struct_range = c;
         }
     }
+    AST::AstNode *left = nullptr, *right = nullptr;
+    switch (current_struct_elem->type) {
+    case AST::AST_STRUCT_ITEM:
+        left = AST::AstNode::mkconst_int(current_struct_elem->range_left, true);
+        right = AST::AstNode::mkconst_int(current_struct_elem->range_right, true);
+        break;
+    case AST::AST_STRUCT:
+    case AST::AST_UNION:
+        // TODO(krak): add proper support for accessing struct/union elements
+        // with multirange
+        // Currently support only special access to 2 dimensional packed element
+        // when selecting single range
+        log_assert(current_struct_elem->multirange_dimensions.size() % 2 == 0);
+        if (struct_range && (current_struct_elem->multirange_dimensions.size() / 2) == 2) {
+            // get element size in number of bits
+            const int single_elem_size = current_struct_elem->children.front()->range_left + 1;
+            left = AST::AstNode::mkconst_int(single_elem_size * current_struct_elem->multirange_dimensions.back(), true);
+            right =
+              AST::AstNode::mkconst_int(current_struct_elem->children.back()->range_right * current_struct_elem->multirange_dimensions.back(), true);
+        } else {
+            left = AST::AstNode::mkconst_int(current_struct_elem->children.front()->range_left, true);
+            right = AST::AstNode::mkconst_int(current_struct_elem->children.back()->range_right, true);
+        }
+        break;
+    default:
+        // Structs currently can only have AST_STRUCT, AST_STRUCT_ITEM, or AST_UNION.
+        log_file_error(current_struct_elem->filename, current_struct_elem->location.first_line,
+                       "Accessing struct member of type %s is unsupported.\n", type2str(current_struct_elem->type).c_str());
+    };
+
+    auto elem_size =
+      new AST::AstNode(AST::AST_ADD, new AST::AstNode(AST::AST_SUB, left->clone(), right->clone()), AST::AstNode::mkconst_int(1, true));
+
     if (sub_dot) {
         // First select correct element in first struct
         std::swap(left, sub_dot->children[0]);
@@ -894,7 +919,7 @@
     int packed_width = -1;
     for (auto s : snode->children) {
         if (s->type == AST::AST_RANGE) {
-            while (s->simplify(true, false, false, 1, -1, false, false)) {
+            while (simplify(s, true, false, false, 1, -1, false, false)) {
             };
         }
     }
@@ -903,10 +928,6 @@
     std::vector<AST::AstNode *> ranges(it, snode->children.end());
     snode->children.erase(it, snode->children.end());
     if (!ranges.empty()) {
-        if (ranges.size() > 1) {
-            log_file_error(ranges[1]->filename, ranges[1]->location.first_line,
-                           "Currently support for custom-type with range is limited to single range\n");
-        }
         for (auto range : ranges) {
             snode->multirange_dimensions.push_back(min(range->range_left, range->range_right));
             snode->multirange_dimensions.push_back(max(range->range_left, range->range_right) - min(range->range_left, range->range_right) + 1);
@@ -922,9 +943,11 @@
             // embedded struct or union
             width = simplify_struct(node, base_offset + offset, parent_node);
             if (!node->multirange_dimensions.empty()) {
-                int number_of_structs = 1;
-                number_of_structs = node->multirange_dimensions.back();
-                width *= number_of_structs;
+                // Multiply widths of all dimensions.
+                // `multirange_dimensions` stores (repeating) pairs of [offset, width].
+                for (size_t i = 1; i < node->multirange_dimensions.size(); i += 2) {
+                    width *= node->multirange_dimensions[i];
+                }
             }
             // set range of struct
             node->range_right = base_offset + offset;
@@ -1055,7 +1078,7 @@
             AST::AstNode *node_arg = current_node->children[next_arg];
             char cformat = sformat[++i];
             if (cformat == 'b' or cformat == 'B') {
-                node_arg->simplify(true, false, false, 1, -1, false, false);
+                simplify(node_arg, true, false, false, 1, -1, false, false);
                 if (node_arg->type != AST::AST_CONSTANT)
                     log_file_error(current_node->filename, current_node->location.first_line,
                                    "Failed to evaluate system task `%s' with non-constant argument.\n", current_node->str.c_str());
@@ -1079,7 +1102,10 @@
     current_node->children[0] = AST::AstNode::mkconst_str(preformatted_string);
 }
 
-static void simplify(AST::AstNode *current_node, AST::AstNode *parent_node)
+// A wrapper for Yosys simplify function.
+// Simplifies AST constructs specific to this plugin to a form understandable by Yosys' simplify and then calls the latter if necessary.
+// Since simplify from Yosys has been forked to this codebase, all new code should be added there instead.
+static void simplify_sv(AST::AstNode *current_node, AST::AstNode *parent_node)
 {
     auto dot_it = std::find_if(current_node->children.begin(), current_node->children.end(),
                                [](auto c) { return c->type == static_cast<int>(AST::Extended::AST_DOT); });
@@ -1099,7 +1125,7 @@
                         log_error("Multirange in AST_DOT is currently unsupported\n");
 
                     dot->type = AST::AST_IDENTIFIER;
-                    simplify(dot, nullptr);
+                    simplify_sv(dot, nullptr);
                     AST::AstNode *range_const = parent_node->children[0]->children[0];
                     prefix_node = new AST::AstNode(AST::AST_PREFIX, range_const->clone(), dot->clone());
                     break;
@@ -1122,7 +1148,7 @@
         } else {
             auto wire_node = AST_INTERNAL::current_scope[current_node->str];
             // make sure wire_node is already simplified
-            simplify(wire_node, nullptr);
+            simplify_sv(wire_node, nullptr);
             expanded = convert_dot(wire_node, current_node, dot);
         }
     }
@@ -1135,7 +1161,7 @@
     }
     // First simplify children
     for (size_t i = 0; i < current_node->children.size(); i++) {
-        simplify(current_node->children[i], current_node);
+        simplify_sv(current_node->children[i], current_node);
     }
     switch (current_node->type) {
     case AST::AST_TYPEDEF:
@@ -1162,7 +1188,7 @@
 
             // if a wire is simplified multiple times, its ranges may be added multiple times and be redundant as a result
             if (!wire_node->attributes.count(UhdmAst::is_simplified_wire())) {
-                simplify(wire_node, nullptr);
+                simplify_sv(wire_node, nullptr);
             }
             const int packed_ranges_size =
               wire_node->attributes.count(UhdmAst::packed_ranges()) ? wire_node->attributes[UhdmAst::packed_ranges()]->children.size() : 0;
@@ -1195,7 +1221,7 @@
     case AST::AST_STRUCT_ITEM:
         AST_INTERNAL::current_scope[current_node->str] = current_node;
         convert_packed_unpacked_range(current_node);
-        while (current_node->simplify(true, false, false, 1, -1, false, false)) {
+        while (simplify(current_node, true, false, false, 1, -1, false, false)) {
         };
         break;
     case AST::AST_TCALL:
@@ -1219,9 +1245,9 @@
             current_node->children[0] = nullptr;
             current_node->children[1] = nullptr;
             delete_children(current_node);
-            while (low_high_bound->children[0]->simplify(true, false, false, 1, -1, false, false)) {
+            while (simplify(low_high_bound->children[0], true, false, false, 1, -1, false, false)) {
             };
-            while (low_high_bound->children[1]->simplify(true, false, false, 1, -1, false, false)) {
+            while (simplify(low_high_bound->children[1], true, false, false, 1, -1, false, false)) {
             };
             log_assert(low_high_bound->children[0]->type == AST::AST_CONSTANT);
             log_assert(low_high_bound->children[1]->type == AST::AST_CONSTANT);
@@ -1876,7 +1902,7 @@
             check_memories(pair.second);
             clear_current_scope();
             setup_current_scope(shared.top_nodes, pair.second);
-            simplify(pair.second, nullptr);
+            simplify_sv(pair.second, nullptr);
             clear_current_scope();
         }
     }
@@ -1891,7 +1917,7 @@
             else {
                 check_memories(pair.second);
                 setup_current_scope(shared.top_nodes, pair.second);
-                simplify(pair.second, nullptr);
+                simplify_sv(pair.second, nullptr);
                 clear_current_scope();
                 current_node->children.push_back(pair.second);
             }
@@ -1923,9 +1949,33 @@
         });
     }
     // first apply custom simplification step if needed
-    simplify(parameter, nullptr);
+    simplify_sv(parameter, module_node);
+    // workaround for yosys sometimes not simplifying parameters children
+    // parameters can have 2 children:
+    // first child should be parameter value
+    // second child should be parameter range (optional)
+    log_assert(!parameter->children.empty());
+    simplify_sv(parameter->children[0], parameter);
+    while (simplify(parameter->children[0], true, false, false, 1, -1, false, false)) {
+    }
+    // follow id2ast as yosys doesn't do it by default
+    if (parameter->children[0]->id2ast) {
+        simplify_sv(parameter->children[0]->id2ast, parameter);
+        while (simplify(parameter->children[0]->id2ast, true, false, false, 1, -1, false, false)) {
+        }
+    }
+    if (parameter->children.size() > 1) {
+        simplify_sv(parameter->children[1], parameter);
+        while (simplify(parameter->children[1], true, false, false, 1, -1, false, false)) {
+        }
+        if (parameter->children[1]->id2ast) {
+            simplify_sv(parameter->children[1]->id2ast, parameter);
+            while (simplify(parameter->children[1]->id2ast, true, false, false, 1, -1, false, false)) {
+            }
+        }
+    }
     // then simplify parameter to AST_CONSTANT or AST_REALVALUE
-    while (parameter->simplify(true, false, false, 1, -1, false, false)) {
+    while (simplify(parameter, true, false, false, 1, -1, false, false)) {
     }
     clear_current_scope();
 }
diff --git a/systemverilog-plugin/third_party/yosys/README b/systemverilog-plugin/third_party/yosys/README
index ceb62a9..df69e69 100644
--- a/systemverilog-plugin/third_party/yosys/README
+++ b/systemverilog-plugin/third_party/yosys/README
@@ -11,7 +11,21 @@
     - Removed Yosys namespace; `const2ast()` has been placed inside
       `systemverilog_plugin` namespace to avoid conflicts with the symbol from
       Yosys when statically linking.
+- simplify.cc: yosys/frontends/ast/simplify.cc (rev. ceef00c)
+  - The file is a part of Yosys AST frontend. It has been placed in the plugin,
+    as in some cases we need to adjust it to support certain functionalities
+    in the plugin. Since it is included now in the plugin, we can skip caling
+    the original Yosys' simplify() during AST preparation. The original Yosys'
+    simplify() is only called in uhdmcommonfrontend.cc when Yosys' process()
+    is called, after having AST done.
+  - Changes:
+    - Removed unneeded code and member functions of AstNode::
+    - Modified usage of AstNode:: members that are called from the Yosys'
+      AstNode:: struct.
+  - The file will be extended in the future instead of simplify_sv()
+    in UhdmAst.cc, and it will be moved to other directory then.
 
 Non-copied files placed here for interfacing purposes:
 
 - const2ast.h
+- simplify.h
diff --git a/systemverilog-plugin/third_party/yosys/simplify.cc b/systemverilog-plugin/third_party/yosys/simplify.cc
new file mode 100644
index 0000000..47d04f7
--- /dev/null
+++ b/systemverilog-plugin/third_party/yosys/simplify.cc
@@ -0,0 +1,4070 @@
+/*
+ *  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 "const2ast.h"
+
+#include <sstream>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <math.h>
+
+YOSYS_NAMESPACE_BEGIN
+namespace VERILOG_FRONTEND
+{
+extern bool sv_mode;
+}
+YOSYS_NAMESPACE_END
+
+namespace systemverilog_plugin
+{
+
+using namespace ::Yosys;
+using namespace ::Yosys::AST_INTERNAL;
+
+bool simplify(Yosys::AST::AstNode *ast_node, bool const_fold, bool at_zero, bool in_lvalue, int stage, int width_hint, bool sign_hint, bool in_param);
+
+void annotateTypedEnums(Yosys::AST::AstNode *ast_node, Yosys::AST::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);
+		Yosys::AST::AstNode *enum_node = current_scope.at(enum_type);
+		log_assert(enum_node->type == Yosys::AST::AST_ENUM);
+		while (simplify(enum_node, true, false, false, 1, -1, false, true)) { }
+		//get width from 1st enum item:
+		log_assert(enum_node->children.size() >= 1);
+		Yosys::AST::AstNode *enum_item0 = enum_node->children[0];
+		log_assert(enum_item0->type == Yosys::AST::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 == Yosys::AST::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 == Yosys::AST::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 != Yosys::AST::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
+			ast_node->attributes[enum_item_str.c_str()] = Yosys::AST::AstNode::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 Yosys::AST::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 Yosys::AST::AstNode(Yosys::AST::AST_RANGE);
+	range->range_left = left;
+	range->range_right = right;
+	range->range_valid = true;
+	range->children.push_back(Yosys::AST::AstNode::mkconst_int(left, true));
+	range->children.push_back(Yosys::AST::AstNode::mkconst_int(right, true));
+	range->is_signed = is_signed;
+	return range;
+}
+
+static int range_width(Yosys::AST::AstNode *node, Yosys::AST::AstNode *rnode)
+{
+	log_assert(rnode->type==Yosys::AST::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(Yosys::AST::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(Yosys::AST::AstNode *node, Yosys::AST::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(Yosys::AST::AstNode *node, int dimension)
+{
+	return node->multirange_dimensions[2*dimension];
+}
+
+static int get_struct_range_width(Yosys::AST::AstNode *node, int dimension)
+{
+	return node->multirange_dimensions[2*dimension + 1];
+}
+
+static int size_packed_struct(Yosys::AST::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 == Yosys::AST::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 == Yosys::AST::AST_STRUCT || node->type == Yosys::AST::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 == Yosys::AST::AST_STRUCT_ITEM);
+			if (node->children.size() > 0 && node->children[0]->type == Yosys::AST::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 == Yosys::AST::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 (Yosys::AST::AstNode *child : node->children)
+					delete child;
+				node->children.clear();
+			}
+			else if (node->children.size() > 0 && node->children[0]->type == Yosys::AST::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 (Yosys::AST::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);
+}
+
+Yosys::AST::AstNode *get_struct_member(const Yosys::AST::AstNode *node)
+{
+	Yosys::AST::AstNode *member_node;
+	if (node->attributes.count(ID::wiretype) && (member_node = node->attributes.at(ID::wiretype)) &&
+	    (member_node->type == Yosys::AST::AST_STRUCT_ITEM || member_node->type == Yosys::AST::AST_STRUCT || member_node->type == Yosys::AST::AST_UNION))
+	{
+		return member_node;
+	}
+	return NULL;
+}
+
+static void add_members_to_scope(Yosys::AST::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==Yosys::AST::AST_STRUCT || snode->type==Yosys::AST::AST_UNION);
+	for (auto *node : snode->children) {
+		auto member_name = name + "." + node->str;
+		current_scope[member_name] = node;
+		if (node->type != Yosys::AST::AST_STRUCT_ITEM) {
+			// embedded struct or union
+			add_members_to_scope(node, name + "." + node->str);
+		}
+	}
+}
+
+static int get_max_offset(Yosys::AST::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==Yosys::AST::AST_STRUCT || node->type==Yosys::AST::AST_UNION);
+	while (node->type != Yosys::AST::AST_STRUCT_ITEM) {
+		node = node->children[0];
+	}
+	return node->range_left;
+}
+
+static Yosys::AST::AstNode *make_packed_struct(Yosys::AST::AstNode *template_node, std::string &name)
+{
+	// create a wire for the packed struct
+	auto wnode = new Yosys::AST::AstNode(Yosys::AST::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 Yosys::AST::AstNode* node, const Yosys::AST::AstNode* var)
+{
+	if (node->type == Yosys::AST::AST_ASSIGN_EQ || node->type == Yosys::AST::AST_ASSIGN_LE) {
+		// current node is iteslf an assignment
+		log_assert(node->children.size() >= 2);
+		const Yosys::AST::AstNode* lhs = node->children[0];
+		if (lhs->type == Yosys::AST::AST_IDENTIFIER && lhs->str == var->str)
+			return false;
+	}
+	for (const Yosys::AST::AstNode* child : node->children) {
+		// if this child shadows the given variable
+		if (child != var && child->str == var->str && child->type == Yosys::AST::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;
+}
+
+// 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 Yosys::AST::AstNode *node)
+{
+	if (node->type == Yosys::AST::AST_CONSTANT)
+		return node->is_unsized;
+	for (const Yosys::AST::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)
+{
+	Yosys::AST::AstNode *left = Yosys::AST::AstNode::mkconst_int(ref->width - 1 + ref->start_offset, true);
+	Yosys::AST::AstNode *right = Yosys::AST::AstNode::mkconst_int(ref->start_offset, true);
+	if (ref->upto)
+		std::swap(left, right);
+	Yosys::AST::AstNode *range = new Yosys::AST::AstNode(Yosys::AST::AST_RANGE, left, right);
+
+	Yosys::AST::AstNode *wire = new Yosys::AST::AstNode(Yosys::AST::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 Yosys::AST::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 == Yosys::AST::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 == Yosys::AST::AST_CASE) {
+		bool all_defined = true;
+		bool any_used = false;
+		bool has_default = false;
+		for (const Yosys::AST::AstNode *child : node->children) {
+			if (child->type == Yosys::AST::AST_COND && child->children.at(0)->type == Yosys::AST::AST_DEFAULT)
+				has_default = true;
+			IdentUsage nested = always_asgn_before_use(child, target);
+			if (nested != IdentUsage::Assigned && child->type == Yosys::AST::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 == Yosys::AST::AST_ASSIGN_EQ) {
+		const Yosys::AST::AstNode *ident = node->children.at(0);
+		if (ident->type == Yosys::AST::AST_IDENTIFIER && ident->str == target)
+			return IdentUsage::Assigned;
+	}
+
+	for (const Yosys::AST::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(Yosys::AST::AstNode *block, const Yosys::AST::AstNode *wire)
+{
+	log_assert(block->type == Yosys::AST::AST_BLOCK);
+	log_assert(wire->type == Yosys::AST::AST_WIRE);
+	block->attributes[auto_nosync_prefix + wire->str] = Yosys::AST::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(Yosys::AST::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`
+		Yosys::AST::AstNode *wire = it->second;
+		log_assert(wire->type == Yosys::AST::AST_WIRE);
+		wire->attributes[ID::nosync] = node->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 (Yosys::AST::AstNode *child : node->children)
+		check_auto_nosync(child);
+}
+
+static inline std::string encode_filename(const std::string &filename)
+{    
+    std::stringstream val; 
+    if (!std::any_of(filename.begin(), filename.end(), [](char c) { 
+        return static_cast<unsigned char>(c) < 33 || static_cast<unsigned char>(c) > 126; 
+    })) return filename;
+    for (unsigned char const c : filename) {
+        if (c < 33 || c > 126) 
+            val << stringf("$%02x", c);
+        else 
+            val << c;
+    }    
+    return val.str();
+}
+
+// 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 simplify(Yosys::AST::AstNode *ast_node, 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;
+
+	Yosys::AST::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(ast_node->type == Yosys::AST::AST_MODULE || ast_node->type == Yosys::AST::AST_INTERFACE);
+
+		deep_recursion_warning = true;
+		while (simplify(ast_node, const_fold, at_zero, in_lvalue, 1, width_hint, sign_hint, in_param)) { }
+
+		if (!flag_nomem2reg && !ast_node->get_bool_attribute(ID::nomem2reg))
+		{
+			dict<Yosys::AST::AstNode*, pool<std::string>> mem2reg_places;
+			dict<Yosys::AST::AstNode*, uint32_t> mem2reg_candidates, dummy_proc_flags;
+			uint32_t flags = flag_mem2reg ? Yosys::AST::AstNode::MEM2REG_FL_ALL : 0;
+			ast_node->mem2reg_as_needed_pass1(mem2reg_places, mem2reg_candidates, dummy_proc_flags, flags); // not sure if correct MAND
+
+			pool<Yosys::AST::AstNode*> mem2reg_set;
+			for (auto &it : mem2reg_candidates)
+			{
+				Yosys::AST::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) || ast_node->get_bool_attribute(ID::nomeminit))
+					this_nomeminit = true;
+
+				if (memflags & Yosys::AST::AstNode::MEM2REG_FL_FORCED)
+					goto silent_activate;
+
+				if (memflags & Yosys::AST::AstNode::MEM2REG_FL_EQ2)
+					goto verbose_activate;
+
+				if (memflags & Yosys::AST::AstNode::MEM2REG_FL_SET_ASYNC)
+					goto verbose_activate;
+
+				if ((memflags & Yosys::AST::AstNode::MEM2REG_FL_SET_INIT) && (memflags & Yosys::AST::AstNode::MEM2REG_FL_SET_ELSE) && this_nomeminit)
+					goto verbose_activate;
+
+				if (memflags & Yosys::AST::AstNode::MEM2REG_FL_CMPLX_LHS)
+					goto verbose_activate;
+
+				if ((memflags & Yosys::AST::AstNode::MEM2REG_FL_CONST_LHS) && !(memflags & Yosys::AST::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++) {
+					Yosys::AST::AstNode *reg = new Yosys::AST::AstNode(Yosys::AST::AST_WIRE, new Yosys::AST::AstNode(Yosys::AST::AST_RANGE,
+							node->mkconst_int(data_range_left, true), node->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;
+					ast_node->children.push_back(reg);
+					while (simplify(reg, true, false, false, 1, -1, false, false)) { } // <- not sure about reg being the first arg here...
+				}
+			}
+
+			Yosys::AST::AstNode *async_block = NULL;
+			while (ast_node->mem2reg_as_needed_pass2(mem2reg_set, ast_node, NULL, async_block)) { }
+
+			vector<Yosys::AST::AstNode*> delnodes;
+			ast_node->mem2reg_remove(mem2reg_set, delnodes);
+
+			for (auto node : delnodes)
+				delete node;
+		}
+
+		while (simplify(ast_node, const_fold, at_zero, in_lvalue, 2, width_hint, sign_hint, in_param)) { }
+		recursion_counter--;
+		return false;
+	}
+
+	Yosys::AST::current_filename = ast_node->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 (ast_node->type == Yosys::AST::AST_FUNCTION || ast_node->type == Yosys::AST::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 ((ast_node->type == Yosys::AST::AST_FCALL || ast_node->type == Yosys::AST::AST_TCALL) && (ast_node->str == "$strobe" || ast_node->str == "$monitor" || ast_node->str == "$time" ||
+			ast_node->str == "$dumpfile" || ast_node->str == "$dumpvars" || ast_node->str == "$dumpon" || ast_node->str == "$dumpoff" || ast_node->str == "$dumpall")) {
+		log_file_warning(ast_node->filename, ast_node->location.first_line, "Ignoring call to system %s %s.\n", ast_node->type == Yosys::AST::AST_FCALL ? "function" : "task", ast_node->str.c_str());
+		ast_node->delete_children();
+		ast_node->str = std::string();
+	}
+
+	if ((ast_node->type == Yosys::AST::AST_TCALL) && (ast_node->str == "$display" || ast_node->str == "$write") && (!current_always || current_always->type != Yosys::AST::AST_INITIAL)) {
+		log_file_warning(ast_node->filename, ast_node->location.first_line, "System task `%s' outside initial block is unsupported.\n", ast_node->str.c_str());
+		ast_node->delete_children();
+		ast_node->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 ((ast_node->type == Yosys::AST::AST_TCALL) && ((ast_node->str == "$display") || (ast_node->str == "$write")))
+	{
+		int nargs = GetSize(ast_node->children);
+		if (nargs < 1)
+			log_file_error(ast_node->filename, ast_node->location.first_line, "System task `%s' got %d arguments, expected >= 1.\n",
+					ast_node->str.c_str(), int(ast_node->children.size()));
+
+		// First argument is the format string
+		Yosys::AST::AstNode *node_string = ast_node->children[0];
+		while (simplify(node_string, true, false, false, stage, width_hint, sign_hint, false)) { }
+		if (node_string->type != Yosys::AST::AST_CONSTANT)
+			log_file_error(ast_node->filename, ast_node->location.first_line, "Failed to evaluate system task `%s' with non-constant 1st argument.\n", ast_node->str.c_str());
+		std::string sformat = node_string->bitsAsConst().decode_string();
+		std::string sout = ast_node->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 (ast_node->str == "$display")
+			log("\n");
+		ast_node->delete_children();
+		ast_node->str = std::string();
+	}
+
+	// activate const folding if this is anything that must be evaluated statically (ranges, parameters, attributes, etc.)
+	if (ast_node->type == Yosys::AST::AST_WIRE || ast_node->type == Yosys::AST::AST_PARAMETER || ast_node->type == Yosys::AST::AST_LOCALPARAM || ast_node->type == Yosys::AST::AST_ENUM_ITEM || ast_node->type == Yosys::AST::AST_DEFPARAM || ast_node->type == Yosys::AST::AST_PARASET || ast_node->type == Yosys::AST::AST_RANGE || ast_node->type == Yosys::AST::AST_PREFIX || ast_node->type == Yosys::AST::AST_TYPEDEF)
+		const_fold = true;
+	if (ast_node->type == Yosys::AST::AST_IDENTIFIER && current_scope.count(ast_node->str) > 0 && (current_scope[ast_node->str]->type == Yosys::AST::AST_PARAMETER || current_scope[ast_node->str]->type == Yosys::AST::AST_LOCALPARAM || current_scope[ast_node->str]->type == Yosys::AST::AST_ENUM_ITEM))
+		const_fold = true;
+
+	// in certain cases a function must be evaluated constant. this is what in_param controls.
+	if (ast_node->type == Yosys::AST::AST_PARAMETER || ast_node->type == Yosys::AST::AST_LOCALPARAM || ast_node->type == Yosys::AST::AST_DEFPARAM || ast_node->type == Yosys::AST::AST_PARASET || ast_node->type == Yosys::AST::AST_PREFIX)
+		in_param = true;
+
+	std::map<std::string, Yosys::AST::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 (ast_node->type == Yosys::AST::AST_MODULE || ast_node->type == Yosys::AST::AST_INTERFACE) {
+		current_scope.clear();
+		std::set<std::string> existing;
+		int counter = 0;
+		ast_node->label_genblks(existing, counter);
+		std::map<std::string, Yosys::AST::AstNode*> this_wire_scope;
+		for (size_t i = 0; i < ast_node->children.size(); i++) {
+			Yosys::AST::AstNode *node = ast_node->children[i];
+
+			if (node->type == Yosys::AST::AST_WIRE) {
+				if (node->children.size() == 1 && node->children[0]->type == Yosys::AST::AST_RANGE) {
+					for (auto c : node->children[0]->children) {
+						if (!c->is_simple_const_expr()) {
+							if (ast_node->attributes.count(ID::dynports))
+								delete ast_node->attributes.at(ID::dynports);
+							node->attributes[ID::dynports] = node->mkconst_int(1, true);
+						}
+					}
+				}
+				if (this_wire_scope.count(node->str) > 0) {
+					Yosys::AST::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 == Yosys::AST::AST_RANGE) {
+						Yosys::AST::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++) {
+						Yosys::AST::AstNode *n1 = first_node->children[j], *n2 = node->children[j];
+						if (n1->type == Yosys::AST::AST_RANGE && n2->type == Yosys::AST::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();
+					}
+					ast_node->children.erase(ast_node->children.begin()+(i--));
+					did_something = true;
+					delete node;
+					continue;
+				wires_are_incompatible:
+					if (stage > 1)
+						log_file_error(ast_node->filename, ast_node->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 == Yosys::AST::AST_PARAMETER || node->type == Yosys::AST::AST_LOCALPARAM || node->type == Yosys::AST::AST_WIRE || node->type == Yosys::AST::AST_AUTOWIRE || node->type == Yosys::AST::AST_GENVAR ||
+					node->type == Yosys::AST::AST_MEMORY || node->type == Yosys::AST::AST_FUNCTION || node->type == Yosys::AST::AST_TASK || node->type == Yosys::AST::AST_DPI_FUNCTION || node->type == Yosys::AST::AST_CELL ||
+					node->type == Yosys::AST::AST_TYPEDEF) {
+				backup_scope[node->str] = current_scope[node->str];
+				current_scope[node->str] = node;
+			}
+			if (node->type == Yosys::AST::AST_ENUM) {
+				current_scope[node->str] = node;
+				for (auto enode : node->children) {
+					log_assert(enode->type==Yosys::AST::AST_ENUM_ITEM);
+					if (current_scope.count(enode->str) == 0)
+						current_scope[enode->str] = enode;
+					else
+						log_file_error(ast_node->filename, ast_node->location.first_line, "enum item %s already exists\n", enode->str.c_str());
+				}
+			}
+		}
+		for (size_t i = 0; i < ast_node->children.size(); i++) {
+			Yosys::AST::AstNode *node = ast_node->children[i];
+			if (node->type == Yosys::AST::AST_PARAMETER || node->type == Yosys::AST::AST_LOCALPARAM || node->type == Yosys::AST::AST_WIRE || node->type == Yosys::AST::AST_AUTOWIRE || node->type == Yosys::AST::AST_MEMORY || node->type == Yosys::AST::AST_TYPEDEF)
+				while (simplify(node, true, false, false, 1, -1, false, node->type == Yosys::AST::AST_PARAMETER || node->type == Yosys::AST::AST_LOCALPARAM))
+					did_something = true;
+			if (node->type == Yosys::AST::AST_ENUM) {
+				for (auto enode : node->children){
+					log_assert(enode->type==Yosys::AST::AST_ENUM_ITEM);
+					while (simplify(node, true, false, false, 1, -1, false, in_param))
+						did_something = true;
+				}
+			}
+		}
+
+		for (Yosys::AST::AstNode *child : ast_node->children)
+			if (child->type == Yosys::AST::AST_ALWAYS &&
+					child->attributes.count(ID::always_comb))
+				check_auto_nosync(child);
+	}
+
+	// create name resolution entries for all objects with names
+	if (ast_node->type == Yosys::AST::AST_PACKAGE) {
+		//add names to package scope
+		for (size_t i = 0; i < ast_node->children.size(); i++) {
+			Yosys::AST::AstNode *node = ast_node->children[i];
+			// these nodes appear at the top level in a package and can define names
+			if (node->type == Yosys::AST::AST_PARAMETER || node->type == Yosys::AST::AST_LOCALPARAM || node->type == Yosys::AST::AST_TYPEDEF || node->type == Yosys::AST::AST_FUNCTION || node->type == Yosys::AST::AST_TASK) {
+				current_scope[node->str] = node;
+			}
+			if (node->type == Yosys::AST::AST_ENUM) {
+				current_scope[node->str] = node;
+				for (auto enode : node->children) {
+					log_assert(enode->type==Yosys::AST::AST_ENUM_ITEM);
+					if (current_scope.count(enode->str) == 0)
+						current_scope[enode->str] = enode;
+					else
+						log_file_error(ast_node->filename, ast_node->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 (ast_node->type == Yosys::AST::AST_ALWAYS || ast_node->type == Yosys::AST::AST_INITIAL)
+	{
+		if (current_always != nullptr)
+			log_file_error(ast_node->filename, ast_node->location.first_line, "Invalid nesting of always blocks and/or initializations.\n");
+
+		current_always = ast_node;
+		current_always_clocked = false;
+
+		if (ast_node->type == Yosys::AST::AST_ALWAYS)
+			for (auto child : ast_node->children) {
+				if (child->type == Yosys::AST::AST_POSEDGE || child->type == Yosys::AST::AST_NEGEDGE)
+					current_always_clocked = true;
+				if (child->type == Yosys::AST::AST_EDGE && GetSize(child->children) == 1 &&
+						child->children[0]->type == Yosys::AST::AST_IDENTIFIER && child->children[0]->str == "\\$global_clock")
+					current_always_clocked = true;
+			}
+	}
+
+	if (ast_node->type == Yosys::AST::AST_CELL) {
+		bool lookup_suggested = false;
+
+		for (Yosys::AST::AstNode *child : ast_node->children) {
+			// simplify any parameters to constants
+			if (child->type == Yosys::AST::AST_PARASET)
+				while (simplify(child, true, false, false, 1, -1, false, true)) { }
+
+			// look for patterns which _may_ indicate ambiguity requiring
+			// resolution of the underlying module
+			if (child->type == Yosys::AST::AST_ARGUMENT) {
+				if (child->children.size() != 1)
+					continue;
+				const Yosys::AST::AstNode *value = child->children[0];
+				if (value->type == Yosys::AST::AST_IDENTIFIER) {
+					const Yosys::AST::AstNode *elem = value->id2ast;
+					if (elem == nullptr) {
+						if (current_scope.count(value->str))
+							elem = current_scope.at(value->str);
+						else
+							continue;
+					}
+					if (elem->type == Yosys::AST::AST_MEMORY)
+						// need to determine is the is a read or wire
+						lookup_suggested = true;
+					else if (elem->type == Yosys::AST::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 = ast_node->lookup_cell_module();
+		if (module) {
+			size_t port_counter = 0;
+			for (Yosys::AST::AstNode *child : ast_node->children) {
+				if (child->type != Yosys::AST::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(ast_node->filename, ast_node->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(ast_node->filename, ast_node->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;
+				Yosys::AST::AstNode *arg = child->children[0];
+
+				// plain identifiers never need indirection; this also prevents
+				// adding infinite levels of indirection
+				if (arg->type == Yosys::AST::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() << "$" << encode_filename(ast_node->filename) << ":" << ast_node->location.first_line << "$" << (autoidx++);
+				std::string tmp_str = sstr.str();
+				add_wire_for_ref(ref, tmp_str);
+
+				Yosys::AST::AstNode *asgn = new Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN);
+				current_ast_mod->children.push_back(asgn);
+
+				Yosys::AST::AstNode *ident = new Yosys::AST::AstNode(Yosys::AST::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 (ast_node->type)
+	{
+	case Yosys::AST::AST_ASSIGN_EQ:
+	case Yosys::AST::AST_ASSIGN_LE:
+	case Yosys::AST::AST_ASSIGN:
+		while (!ast_node->children[0]->basic_prep && simplify(ast_node->children[0], false, false, true, stage, -1, false, in_param) == true)
+			did_something = true;
+		while (!ast_node->children[1]->basic_prep && simplify(ast_node->children[1], false, false, false, stage, -1, false, in_param) == true)
+			did_something = true;
+		ast_node->children[0]->detectSignWidth(backup_width_hint, backup_sign_hint);
+		ast_node->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 (ast_node->children[0]->id2ast && !ast_node->children[0]->was_checked) {
+			if ((ast_node->type == Yosys::AST::AST_ASSIGN_LE || ast_node->type == Yosys::AST::AST_ASSIGN_EQ) && ast_node->children[0]->id2ast->is_logic)
+				ast_node->children[0]->id2ast->is_reg = true; // if logic type is used in a block asignment
+			if ((ast_node->type == Yosys::AST::AST_ASSIGN_LE || ast_node->type == Yosys::AST::AST_ASSIGN_EQ) && !ast_node->children[0]->id2ast->is_reg)
+				log_warning("wire '%s' is assigned in a block at %s.\n", ast_node->children[0]->str.c_str(), ast_node->loc_string().c_str());
+			if (ast_node->type == Yosys::AST::AST_ASSIGN && ast_node->children[0]->id2ast->is_reg) {
+				bool is_rand_reg = false;
+				if (ast_node->children[1]->type == Yosys::AST::AST_FCALL) {
+					if (ast_node->children[1]->str == "\\$anyconst")
+						is_rand_reg = true;
+					if (ast_node->children[1]->str == "\\$anyseq")
+						is_rand_reg = true;
+					if (ast_node->children[1]->str == "\\$allconst")
+						is_rand_reg = true;
+					if (ast_node->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", ast_node->children[0]->str.c_str(), ast_node->loc_string().c_str());
+			}
+			ast_node->children[0]->was_checked = true;
+		}
+		break;
+
+	case Yosys::AST::AST_STRUCT:
+	case Yosys::AST::AST_UNION:
+		if (!ast_node->basic_prep) {
+			for (auto *node : ast_node->children) {
+				// resolve any ranges
+				while (!node->basic_prep && simplify(node, true, false, false, stage, -1, false, false)) {
+					did_something = true;
+				}
+			}
+			// determine member offsets and widths
+			size_packed_struct(ast_node, 0);
+
+			// instance rather than just a type in a typedef or outer struct?
+			if (!ast_node->str.empty() && ast_node->str[0] == '\\') {
+				// instance so add a wire for the packed structure
+				auto wnode = make_packed_struct(ast_node, ast_node->str);
+				log_assert(current_ast_mod);
+				current_ast_mod->children.push_back(wnode);
+			}
+			ast_node->basic_prep = true;
+		}
+		break;
+
+	case Yosys::AST::AST_STRUCT_ITEM:
+		break;
+
+	case Yosys::AST::AST_ENUM:
+		//log("\nENUM %s: %d child %d\n", ast_node->str.c_str(), ast_node->basic_prep, ast_node->children[0]->basic_prep);
+		if (!ast_node->basic_prep) {
+			for (auto item_node : ast_node->children) {
+				while (!item_node->basic_prep && simplify(item_node, false, false, false, stage, -1, false, in_param))
+					did_something = true;
+			}
+			// allocate values (called more than once)
+			ast_node->allocateDefaultEnumValues();
+		}
+		break;
+
+	case Yosys::AST::AST_PARAMETER:
+	case Yosys::AST::AST_LOCALPARAM:
+		// if parameter is implicit type which is the typename of a struct or union,
+		// save information about struct in wiretype attribute
+		if (ast_node->children[0]->type == Yosys::AST::AST_IDENTIFIER && current_scope.count(ast_node->children[0]->str) > 0) {
+			auto item_node = current_scope[ast_node->children[0]->str];
+			if (item_node->type == Yosys::AST::AST_STRUCT || item_node->type == Yosys::AST::AST_UNION) {
+				ast_node->attributes[ID::wiretype] = item_node->clone();
+				size_packed_struct(ast_node->attributes[ID::wiretype], 0);
+				add_members_to_scope(ast_node->attributes[ID::wiretype], ast_node->str);
+			}
+		}
+		while (!ast_node->children[0]->basic_prep && simplify(ast_node->children[0], false, false, false, stage, -1, false, true) == true)
+			did_something = true;
+		ast_node->children[0]->detectSignWidth(width_hint, sign_hint);
+		if (ast_node->children.size() > 1 && ast_node->children[1]->type == Yosys::AST::AST_RANGE) {
+			while (!ast_node->children[1]->basic_prep && simplify(ast_node->children[1], false, false, false, stage, -1, false, true) == true)
+				did_something = true;
+			if (!ast_node->children[1]->range_valid)
+				log_file_error(ast_node->filename, ast_node->location.first_line, "Non-constant width range on parameter decl.\n");
+			width_hint = max(width_hint, ast_node->children[1]->range_left - ast_node->children[1]->range_right + 1);
+		}
+		break;
+	case Yosys::AST::AST_ENUM_ITEM:
+		while (!ast_node->children[0]->basic_prep && simplify(ast_node->children[0], false, false, false, stage, -1, false, in_param))
+			did_something = true;
+		ast_node->children[0]->detectSignWidth(width_hint, sign_hint);
+		if (ast_node->children.size() > 1 && ast_node->children[1]->type == Yosys::AST::AST_RANGE) {
+			while (!ast_node->children[1]->basic_prep && simplify(ast_node->children[1], false, false, false, stage, -1, false, in_param))
+				did_something = true;
+			if (!ast_node->children[1]->range_valid)
+				log_file_error(ast_node->filename, ast_node->location.first_line, "Non-constant width range on enum item decl.\n");
+			width_hint = max(width_hint, ast_node->children[1]->range_left - ast_node->children[1]->range_right + 1);
+		}
+		break;
+
+	case Yosys::AST::AST_TO_BITS:
+	case Yosys::AST::AST_TO_SIGNED:
+	case Yosys::AST::AST_TO_UNSIGNED:
+	case Yosys::AST::AST_SELFSZ:
+	case Yosys::AST::AST_CAST_SIZE:
+	case Yosys::AST::AST_CONCAT:
+	case Yosys::AST::AST_REPLICATE:
+	case Yosys::AST::AST_REDUCE_AND:
+	case Yosys::AST::AST_REDUCE_OR:
+	case Yosys::AST::AST_REDUCE_XOR:
+	case Yosys::AST::AST_REDUCE_XNOR:
+	case Yosys::AST::AST_REDUCE_BOOL:
+		detect_width_simple = true;
+		children_are_self_determined = true;
+		break;
+
+	case Yosys::AST::AST_NEG:
+	case Yosys::AST::AST_BIT_NOT:
+	case Yosys::AST::AST_POS:
+	case Yosys::AST::AST_BIT_AND:
+	case Yosys::AST::AST_BIT_OR:
+	case Yosys::AST::AST_BIT_XOR:
+	case Yosys::AST::AST_BIT_XNOR:
+	case Yosys::AST::AST_ADD:
+	case Yosys::AST::AST_SUB:
+	case Yosys::AST::AST_MUL:
+	case Yosys::AST::AST_DIV:
+	case Yosys::AST::AST_MOD:
+		detect_width_simple = true;
+		break;
+
+	case Yosys::AST::AST_SHIFT_LEFT:
+	case Yosys::AST::AST_SHIFT_RIGHT:
+	case Yosys::AST::AST_SHIFT_SLEFT:
+	case Yosys::AST::AST_SHIFT_SRIGHT:
+	case Yosys::AST::AST_POW:
+		detect_width_simple = true;
+		child_1_is_self_determined = true;
+		break;
+
+	case Yosys::AST::AST_LT:
+	case Yosys::AST::AST_LE:
+	case Yosys::AST::AST_EQ:
+	case Yosys::AST::AST_NE:
+	case Yosys::AST::AST_EQX:
+	case Yosys::AST::AST_NEX:
+	case Yosys::AST::AST_GE:
+	case Yosys::AST::AST_GT:
+		width_hint = -1;
+		sign_hint = true;
+		for (auto child : ast_node->children) {
+			while (!child->basic_prep && simplify(child, 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 Yosys::AST::AST_LOGIC_AND:
+	case Yosys::AST::AST_LOGIC_OR:
+	case Yosys::AST::AST_LOGIC_NOT:
+		detect_width_simple = true;
+		children_are_self_determined = true;
+		break;
+
+	case Yosys::AST::AST_TERNARY:
+		child_0_is_self_determined = true;
+		break;
+
+	case Yosys::AST::AST_MEMRD:
+		detect_width_simple = true;
+		children_are_self_determined = true;
+		break;
+
+	case Yosys::AST::AST_FCALL:
+	case Yosys::AST::AST_TCALL:
+		children_are_self_determined = true;
+		break;
+
+	default:
+		width_hint = -1;
+		sign_hint = false;
+	}
+
+	if (detect_width_simple && width_hint < 0) {
+		if (ast_node->type == Yosys::AST::AST_REPLICATE)
+			while (simplify(ast_node->children[0], true, false, in_lvalue, stage, -1, false, true) == true)
+				did_something = true;
+		for (auto child : ast_node->children)
+			while (!child->basic_prep && simplify(child, false, false, in_lvalue, stage, -1, false, in_param) == true)
+				did_something = true;
+		ast_node->detectSignWidth(width_hint, sign_hint);
+	}
+
+	if (ast_node->type == Yosys::AST::AST_FCALL && ast_node->str == "\\$past")
+		ast_node->detectSignWidth(width_hint, sign_hint);
+
+	if (ast_node->type == Yosys::AST::AST_TERNARY) {
+		if (width_hint < 0) {
+			while (!ast_node->children[0]->basic_prep && simplify(ast_node->children[0], true, false, in_lvalue, stage, -1, false, in_param))
+				did_something = true;
+
+			bool backup_unevaluated_tern_branch = unevaluated_tern_branch;
+			Yosys::AST::AstNode *chosen = ast_node->get_tern_choice().first;
+
+			unevaluated_tern_branch = backup_unevaluated_tern_branch || chosen == ast_node->children[2];
+			while (!ast_node->children[1]->basic_prep && simplify(ast_node->children[1], false, false, in_lvalue, stage, -1, false, in_param))
+				did_something = true;
+
+			unevaluated_tern_branch = backup_unevaluated_tern_branch || chosen == ast_node->children[1];
+			while (!ast_node->children[2]->basic_prep && simplify(ast_node->children[2], false, false, in_lvalue, stage, -1, false, in_param))
+				did_something = true;
+
+			unevaluated_tern_branch = backup_unevaluated_tern_branch;
+			ast_node->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;
+		ast_node->children[1]->detectSignWidth(width_hint_left, sign_hint_left, &found_real_left);
+		ast_node->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 (ast_node->type == Yosys::AST::AST_CONDX && ast_node->children.size() > 0 && ast_node->children.at(0)->type == Yosys::AST::AST_CONSTANT) {
+		for (auto &bit : ast_node->children.at(0)->bits)
+			if (bit == State::Sz || bit == State::Sx)
+				bit = State::Sa;
+	}
+
+	if (ast_node->type == Yosys::AST::AST_CONDZ && ast_node->children.size() > 0 && ast_node->children.at(0)->type == Yosys::AST::AST_CONSTANT) {
+		for (auto &bit : ast_node->children.at(0)->bits)
+			if (bit == State::Sz)
+				bit = State::Sa;
+	}
+
+	if (const_fold && ast_node->type == Yosys::AST::AST_CASE)
+	{
+		ast_node->detectSignWidth(width_hint, sign_hint);
+		while (simplify(ast_node->children[0], const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) { }
+		if (ast_node->children[0]->type == Yosys::AST::AST_CONSTANT && ast_node->children[0]->bits_only_01()) {
+			ast_node->children[0]->is_signed = sign_hint;
+			RTLIL::Const case_expr = ast_node->children[0]->bitsAsConst(width_hint, sign_hint);
+			std::vector<Yosys::AST::AstNode*> new_children;
+			new_children.push_back(ast_node->children[0]);
+			for (int i = 1; i < GetSize(ast_node->children); i++) {
+				Yosys::AST::AstNode *child = ast_node->children[i];
+				log_assert(child->type == Yosys::AST::AST_COND || child->type == Yosys::AST::AST_CONDX || child->type == Yosys::AST::AST_CONDZ);
+				for (auto v : child->children) {
+					if (v->type == Yosys::AST::AST_DEFAULT)
+						goto keep_const_cond;
+					if (v->type == Yosys::AST::AST_BLOCK)
+						continue;
+					while (simplify(v, const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) { }
+					if (v->type == Yosys::AST::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(ast_node->children))
+								delete ast_node->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(ast_node->children);
+		}
+	}
+
+	dict<std::string, pool<int>> backup_memwr_visible;
+	dict<std::string, pool<int>> final_memwr_visible;
+
+	if (ast_node->type == Yosys::AST::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 < ast_node->children.size(); i++) {
+		bool did_something_here = true;
+		bool backup_flag_autowire = flag_autowire;
+		bool backup_unevaluated_tern_branch = unevaluated_tern_branch;
+		if ((ast_node->type == Yosys::AST::AST_GENFOR || ast_node->type == Yosys::AST::AST_FOR) && i >= 3)
+			break;
+		if ((ast_node->type == Yosys::AST::AST_GENIF || ast_node->type == Yosys::AST::AST_GENCASE) && i >= 1)
+			break;
+		if (ast_node->type == Yosys::AST::AST_GENBLOCK)
+			break;
+		if (ast_node->type == Yosys::AST::AST_CELLARRAY && ast_node->children[i]->type == Yosys::AST::AST_CELL)
+			continue;
+		if (ast_node->type == Yosys::AST::AST_BLOCK && !ast_node->str.empty())
+			break;
+		if (ast_node->type == Yosys::AST::AST_PREFIX && i >= 1)
+			break;
+		if (ast_node->type == Yosys::AST::AST_DEFPARAM && i == 0)
+			flag_autowire = true;
+		if (ast_node->type == Yosys::AST::AST_TERNARY && i > 0 && !unevaluated_tern_branch) {
+			Yosys::AST::AstNode *chosen = ast_node->get_tern_choice().first;
+			unevaluated_tern_branch = chosen && chosen != ast_node->children[i];
+		}
+		while (did_something_here && i < ast_node->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 && (ast_node->type == Yosys::AST::AST_REPLICATE || ast_node->type == Yosys::AST::AST_WIRE))
+				const_fold_here = true, in_param_here = true;
+			if (i == 0 && (ast_node->type == Yosys::AST::AST_GENIF || ast_node->type == Yosys::AST::AST_GENCASE))
+				in_param_here = true;
+			if (i == 1 && (ast_node->type == Yosys::AST::AST_FOR || ast_node->type == Yosys::AST::AST_GENFOR))
+				in_param_here = true;
+			if (ast_node->type == Yosys::AST::AST_PARAMETER || ast_node->type == Yosys::AST::AST_LOCALPARAM)
+				const_fold_here = true;
+			if (i == 0 && (ast_node->type == Yosys::AST::AST_ASSIGN || ast_node->type == Yosys::AST::AST_ASSIGN_EQ || ast_node->type == Yosys::AST::AST_ASSIGN_LE))
+				in_lvalue_here = true;
+			if (ast_node->type == Yosys::AST::AST_BLOCK) {
+				current_block = ast_node;
+				current_block_child = ast_node->children[i];
+			}
+			if ((ast_node->type == Yosys::AST::AST_ALWAYS || ast_node->type == Yosys::AST::AST_INITIAL) && ast_node->children[i]->type == Yosys::AST::AST_BLOCK)
+				current_top_block = ast_node->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 = simplify(ast_node->children[i], 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 && ast_node->children[i]->type == Yosys::AST::AST_INITIAL && current_ast_mod != ast_node) {
+			current_ast_mod->children.push_back(ast_node->children[i]);
+			ast_node->children.erase(ast_node->children.begin() + (i--));
+			did_something = true;
+		}
+		flag_autowire = backup_flag_autowire;
+		unevaluated_tern_branch = backup_unevaluated_tern_branch;
+		if (stage == 2 && ast_node->type == Yosys::AST::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 : ast_node->attributes) {
+		while (simplify(attr.second, true, false, false, stage, -1, false, true))
+			did_something = true;
+	}
+	if (ast_node->type == Yosys::AST::AST_CASE && stage == 2) {
+		current_memwr_visible = final_memwr_visible;
+	}
+	if (ast_node->type == Yosys::AST::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)
+			ast_node->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;
+	}
+
+	Yosys::AST::current_filename = ast_node->filename;
+
+	if (ast_node->type == Yosys::AST::AST_MODULE || ast_node->type == Yosys::AST::AST_INTERFACE)
+		current_scope.clear();
+
+	// convert defparam nodes to cell parameters
+	if (ast_node->type == Yosys::AST::AST_DEFPARAM && !ast_node->children.empty())
+	{
+		if (ast_node->children[0]->type != Yosys::AST::AST_IDENTIFIER)
+			log_file_error(ast_node->filename, ast_node->location.first_line, "Module name in defparam contains non-constant expressions!\n");
+
+		string modname, paramname = ast_node->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(ast_node->filename, ast_node->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 != Yosys::AST::AST_CELL)
+			log_file_error(ast_node->filename, ast_node->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());
+
+		Yosys::AST::AstNode *paraset = new Yosys::AST::AstNode(Yosys::AST::AST_PARASET, ast_node->children[1]->clone(), GetSize(ast_node->children) > 2 ? ast_node->children[2]->clone() : NULL);
+		paraset->str = paramname;
+
+		Yosys::AST::AstNode *cell = current_scope.at(modname);
+		cell->children.insert(cell->children.begin() + 1, paraset);
+		ast_node->delete_children();
+	}
+
+	// resolve typedefs
+	if (ast_node->type == Yosys::AST::AST_TYPEDEF) {
+		log_assert(ast_node->children.size() == 1);
+		auto type_node = ast_node->children[0];
+		log_assert(type_node->type == Yosys::AST::AST_WIRE || type_node->type == Yosys::AST::AST_MEMORY || type_node->type == Yosys::AST::AST_STRUCT || type_node->type == Yosys::AST::AST_UNION);
+		while (simplify(type_node, 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 (ast_node->type == Yosys::AST::AST_WIRE || ast_node->type == Yosys::AST::AST_MEMORY) {
+		if (ast_node->is_custom_type) {
+			log_assert(ast_node->children.size() >= 1);
+			log_assert(ast_node->children[0]->type == Yosys::AST::AST_WIRETYPE);
+			auto type_name = ast_node->children[0]->str;
+			if (!current_scope.count(type_name)) {
+				log_file_error(ast_node->filename, ast_node->location.first_line, "Unknown identifier `%s' used as type name\n", type_name.c_str());
+			}
+			Yosys::AST::AstNode *resolved_type_node = current_scope.at(type_name);
+			if (resolved_type_node->type != Yosys::AST::AST_TYPEDEF)
+				log_file_error(ast_node->filename, ast_node->location.first_line, "`%s' does not name a type\n", type_name.c_str());
+			log_assert(resolved_type_node->children.size() == 1);
+			Yosys::AST::AstNode *template_node = resolved_type_node->children[0];
+
+			// Ensure typedef itself is fully simplified
+			while (simplify(template_node, const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) {};
+
+			if (template_node->type == Yosys::AST::AST_STRUCT || template_node->type == Yosys::AST::AST_UNION) {
+				// replace with wire representing the packed structure
+				newNode = make_packed_struct(template_node, ast_node->str);
+				newNode->attributes[ID::wiretype] = ast_node->mkconst_str(resolved_type_node->str);
+				// add original input/output attribute to resolved wire
+				newNode->is_input = ast_node->is_input;
+				newNode->is_output = ast_node->is_output;
+				current_scope[ast_node->str] = ast_node;
+				goto apply_newNode;
+			}
+
+			// Remove type reference
+			delete ast_node->children[0];
+			ast_node->children.erase(ast_node->children.begin());
+
+			if (ast_node->type == Yosys::AST::AST_WIRE)
+				ast_node->type = template_node->type;
+			ast_node->is_reg = template_node->is_reg;
+			ast_node->is_logic = template_node->is_logic;
+			ast_node->is_signed = template_node->is_signed;
+			ast_node->is_string = template_node->is_string;
+			ast_node->is_custom_type = template_node->is_custom_type;
+
+			ast_node->range_valid = template_node->range_valid;
+			ast_node->range_swapped = template_node->range_swapped;
+			ast_node->range_left = template_node->range_left;
+			ast_node->range_right = template_node->range_right;
+
+			ast_node->attributes[ID::wiretype] = ast_node->mkconst_str(resolved_type_node->str);
+
+			// if an enum then add attributes to support simulator tracing
+			annotateTypedEnums(ast_node, template_node);
+
+			// Insert clones children from template at beginning
+			for (int i  = 0; i < GetSize(template_node->children); i++)
+				ast_node->children.insert(ast_node->children.begin() + i, template_node->children[i]->clone());
+
+			if (ast_node->type == Yosys::AST::AST_MEMORY && GetSize(ast_node->children) == 1) {
+				// Single-bit memories must have [0:0] range
+				Yosys::AST::AstNode *rng = make_range(0, 0);
+				ast_node->children.insert(ast_node->children.begin(), rng);
+			}
+			did_something = true;
+		}
+		log_assert(!ast_node->is_custom_type);
+	}
+
+	// resolve types of parameters
+	if (ast_node->type == Yosys::AST::AST_LOCALPARAM || ast_node->type == Yosys::AST::AST_PARAMETER) {
+		if (ast_node->is_custom_type) {
+			log_assert(ast_node->children.size() == 2);
+			log_assert(ast_node->children[1]->type == Yosys::AST::AST_WIRETYPE);
+			auto type_name = ast_node->children[1]->str;
+			if (!current_scope.count(type_name)) {
+				log_file_error(ast_node->filename, ast_node->location.first_line, "Unknown identifier `%s' used as type name\n", type_name.c_str());
+			}
+			Yosys::AST::AstNode *resolved_type_node = current_scope.at(type_name);
+			if (resolved_type_node->type != Yosys::AST::AST_TYPEDEF)
+				log_file_error(ast_node->filename, ast_node->location.first_line, "`%s' does not name a type\n", type_name.c_str());
+			log_assert(resolved_type_node->children.size() == 1);
+			Yosys::AST::AstNode *template_node = resolved_type_node->children[0];
+
+			// Ensure typedef itself is fully simplified
+			while (simplify(template_node, const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) {};
+
+			if (template_node->type == Yosys::AST::AST_STRUCT || template_node->type == Yosys::AST::AST_UNION) {
+				// replace with wire representing the packed structure
+				newNode = make_packed_struct(template_node, ast_node->str);
+				newNode->attributes[ID::wiretype] = ast_node->mkconst_str(resolved_type_node->str);
+				newNode->type = ast_node->type;
+				current_scope[ast_node->str] = ast_node;
+				// copy param value, it needs to be 1st value
+				delete ast_node->children[1];
+				ast_node->children.pop_back();
+				newNode->children.insert(newNode->children.begin(), ast_node->children[0]->clone());
+				goto apply_newNode;
+			}
+			delete ast_node->children[1];
+			ast_node->children.pop_back();
+
+			if (template_node->type == Yosys::AST::AST_MEMORY)
+				log_file_error(ast_node->filename, ast_node->location.first_line, "unpacked array type `%s' cannot be used for a parameter\n", ast_node->children[1]->str.c_str());
+			ast_node->is_signed = template_node->is_signed;
+			ast_node->is_string = template_node->is_string;
+			ast_node->is_custom_type = template_node->is_custom_type;
+
+			ast_node->range_valid = template_node->range_valid;
+			ast_node->range_swapped = template_node->range_swapped;
+			ast_node->range_left = template_node->range_left;
+			ast_node->range_right = template_node->range_right;
+			ast_node->attributes[ID::wiretype] = ast_node->mkconst_str(resolved_type_node->str);
+			for (auto template_child : template_node->children)
+				ast_node->children.push_back(template_child->clone());
+			did_something = true;
+		}
+		log_assert(!ast_node->is_custom_type);
+	}
+
+	// resolve constant prefixes
+	if (ast_node->type == Yosys::AST::AST_PREFIX) {
+		if (ast_node->children[0]->type != Yosys::AST::AST_CONSTANT) {
+			// dumpAst(NULL, ">   ");
+			log_file_error(ast_node->filename, ast_node->location.first_line, "Index in generate block prefix syntax is not constant!\n");
+		}
+		if (ast_node->children[1]->type == Yosys::AST::AST_PREFIX)
+			simplify(ast_node->children[1], const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param);
+		log_assert(ast_node->children[1]->type == Yosys::AST::AST_IDENTIFIER);
+		newNode = ast_node->children[1]->clone();
+		const char *second_part = ast_node->children[1]->str.c_str();
+		if (second_part[0] == '\\')
+			second_part++;
+		newNode->str = stringf("%s[%d].%s", ast_node->str.c_str(), ast_node->children[0]->integer, second_part);
+		goto apply_newNode;
+	}
+
+	// evaluate TO_BITS nodes
+	if (ast_node->type == Yosys::AST::AST_TO_BITS) {
+		if (ast_node->children[0]->type != Yosys::AST::AST_CONSTANT)
+			log_file_error(ast_node->filename, ast_node->location.first_line, "Left operand of to_bits expression is not constant!\n");
+		if (ast_node->children[1]->type != Yosys::AST::AST_CONSTANT)
+			log_file_error(ast_node->filename, ast_node->location.first_line, "Right operand of to_bits expression is not constant!\n");
+		RTLIL::Const new_value = ast_node->children[1]->bitsAsConst(ast_node->children[0]->bitsAsConst().as_int(), ast_node->children[1]->is_signed);
+		newNode = Yosys::AST::AstNode::mkconst_bits(new_value.bits, ast_node->children[1]->is_signed);
+		goto apply_newNode;
+	}
+
+	// annotate constant ranges
+	if (ast_node->type == Yosys::AST::AST_RANGE) {
+		bool old_range_valid = ast_node->range_valid;
+		ast_node->range_valid = false;
+		ast_node->range_swapped = false;
+		ast_node->range_left = -1;
+		ast_node->range_right = 0;
+		log_assert(ast_node->children.size() >= 1);
+		if (ast_node->children[0]->type == Yosys::AST::AST_CONSTANT) {
+			ast_node->range_valid = true;
+			ast_node->range_left = ast_node->children[0]->integer;
+			if (ast_node->children.size() == 1)
+				ast_node->range_right = ast_node->range_left;
+		}
+		if (ast_node->children.size() >= 2) {
+			if (ast_node->children[1]->type == Yosys::AST::AST_CONSTANT)
+				ast_node->range_right = ast_node->children[1]->integer;
+			else
+				ast_node->range_valid = false;
+		}
+		if (old_range_valid != ast_node->range_valid)
+			did_something = true;
+		if (ast_node->range_valid && ast_node->range_right > ast_node->range_left) {
+			int tmp = ast_node->range_right;
+			ast_node->range_right = ast_node->range_left;
+			ast_node->range_left = tmp;
+			ast_node->range_swapped = true;
+		}
+	}
+
+	// annotate wires with their ranges
+	if (ast_node->type == Yosys::AST::AST_WIRE) {
+		if (ast_node->children.size() > 0) {
+			if (ast_node->children[0]->range_valid) {
+				if (!ast_node->range_valid)
+					did_something = true;
+				ast_node->range_valid = true;
+				ast_node->range_swapped = ast_node->children[0]->range_swapped;
+				ast_node->range_left = ast_node->children[0]->range_left;
+				ast_node->range_right = ast_node->children[0]->range_right;
+				bool force_upto = false, force_downto = false;
+				if (ast_node->attributes.count(ID::force_upto)) {
+					Yosys::AST::AstNode *val = ast_node->attributes[ID::force_upto];
+					if (val->type != Yosys::AST::AST_CONSTANT)
+						log_file_error(ast_node->filename, ast_node->location.first_line, "Attribute `force_upto' with non-constant value!\n");
+					force_upto = val->asAttrConst().as_bool();
+				}
+				if (ast_node->attributes.count(ID::force_downto)) {
+					Yosys::AST::AstNode *val = ast_node->attributes[ID::force_downto];
+					if (val->type != Yosys::AST::AST_CONSTANT)
+						log_file_error(ast_node->filename, ast_node->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(ast_node->filename, ast_node->location.first_line, "Attributes `force_downto' and `force_upto' cannot be both set!\n");
+				if ((force_upto && !ast_node->range_swapped) || (force_downto && ast_node->range_swapped)) {
+					std::swap(ast_node->range_left, ast_node->range_right);
+					ast_node->range_swapped = force_upto;
+				}
+			}
+		} else {
+			if (!ast_node->range_valid)
+				did_something = true;
+			ast_node->range_valid = true;
+			ast_node->range_swapped = false;
+			ast_node->range_left = 0;
+			ast_node->range_right = 0;
+		}
+	}
+
+	// resolve multiranges on memory decl
+	if (ast_node->type == Yosys::AST::AST_MEMORY && ast_node->children.size() > 1 && ast_node->children[1]->type == Yosys::AST::AST_MULTIRANGE)
+	{
+		int total_size = 1;
+		ast_node->multirange_dimensions.clear();
+		ast_node->multirange_swapped.clear();
+		for (auto range : ast_node->children[1]->children) {
+			if (!range->range_valid)
+				log_file_error(ast_node->filename, ast_node->location.first_line, "Non-constant range on memory decl.\n");
+			ast_node->multirange_dimensions.push_back(min(range->range_left, range->range_right));
+			ast_node->multirange_dimensions.push_back(max(range->range_left, range->range_right) - min(range->range_left, range->range_right) + 1);
+			ast_node->multirange_swapped.push_back(range->range_swapped);
+			total_size *= ast_node->multirange_dimensions.back();
+		}
+		delete ast_node->children[1];
+		ast_node->children[1] = new Yosys::AST::AstNode(Yosys::AST::AST_RANGE, ast_node->mkconst_int(0, true), ast_node->mkconst_int(total_size-1, true));
+		did_something = true;
+	}
+
+	// resolve multiranges on memory access
+	if (ast_node->type == Yosys::AST::AST_IDENTIFIER && ast_node->id2ast && ast_node->id2ast->type == Yosys::AST::AST_MEMORY && ast_node->children.size() > 0 && ast_node->children[0]->type == Yosys::AST::AST_MULTIRANGE)
+	{
+		Yosys::AST::AstNode *index_expr = nullptr;
+
+		ast_node->integer = ast_node->children[0]->children.size(); // save original number of dimensions for $size() etc.
+		for (int i = 0; 2*i < GetSize(ast_node->id2ast->multirange_dimensions); i++)
+		{
+			if (GetSize(ast_node->children[0]->children) <= i)
+				log_file_error(ast_node->filename, ast_node->location.first_line, "Insufficient number of array indices for %s.\n", log_id(ast_node->str));
+
+			Yosys::AST::AstNode *new_index_expr = ast_node->children[0]->children[i]->children.at(0)->clone();
+
+			if (ast_node->id2ast->multirange_dimensions[2*i])
+				new_index_expr = new Yosys::AST::AstNode(Yosys::AST::AST_SUB, new_index_expr, ast_node->mkconst_int(ast_node->id2ast->multirange_dimensions[2*i], true));
+
+			if (i == 0)
+				index_expr = new_index_expr;
+			else
+				index_expr = new Yosys::AST::AstNode(Yosys::AST::AST_ADD, new Yosys::AST::AstNode(Yosys::AST::AST_MUL, index_expr, ast_node->mkconst_int(ast_node->id2ast->multirange_dimensions[2*i+1], true)), new_index_expr);
+		}
+
+		for (int i = GetSize(ast_node->id2ast->multirange_dimensions)/2; i < GetSize(ast_node->children[0]->children); i++)
+			ast_node->children.push_back(ast_node->children[0]->children[i]->clone());
+
+		delete ast_node->children[0];
+		if (index_expr == nullptr)
+			ast_node->children.erase(ast_node->children.begin());
+		else
+			ast_node->children[0] = new Yosys::AST::AstNode(Yosys::AST::AST_RANGE, index_expr);
+
+		did_something = true;
+	}
+
+	// trim/extend parameters
+	if (ast_node->type == Yosys::AST::AST_PARAMETER || ast_node->type == Yosys::AST::AST_LOCALPARAM || ast_node->type == Yosys::AST::AST_ENUM_ITEM) {
+		if (ast_node->children.size() > 1 && ast_node->children[1]->type == Yosys::AST::AST_RANGE) {
+			if (!ast_node->children[1]->range_valid)
+				log_file_error(ast_node->filename, ast_node->location.first_line, "Non-constant width range on parameter decl.\n");
+			int width = std::abs(ast_node->children[1]->range_left - ast_node->children[1]->range_right) + 1;
+			if (ast_node->children[0]->type == Yosys::AST::AST_REALVALUE) {
+				RTLIL::Const constvalue = ast_node->children[0]->realAsConst(width);
+				log_file_warning(ast_node->filename, ast_node->location.first_line, "converting real value %e to binary %s.\n",
+						ast_node->children[0]->realvalue, log_signal(constvalue));
+				delete ast_node->children[0];
+				ast_node->children[0] = Yosys::AST::AstNode::mkconst_bits(constvalue.bits, sign_hint);
+				did_something = true;
+			}
+			if (ast_node->children[0]->type == Yosys::AST::AST_CONSTANT) {
+				if (width != int(ast_node->children[0]->bits.size())) {
+					RTLIL::SigSpec sig(ast_node->children[0]->bits);
+					sig.extend_u0(width, ast_node->children[0]->is_signed);
+					Yosys::AST::AstNode *old_child_0 = ast_node->children[0];
+					ast_node->children[0] = Yosys::AST::AstNode::mkconst_bits(sig.as_const().bits, ast_node->is_signed);
+					delete old_child_0;
+				}
+				ast_node->children[0]->is_signed = ast_node->is_signed;
+			}
+			ast_node->range_valid = true;
+			ast_node->range_swapped = ast_node->children[1]->range_swapped;
+			ast_node->range_left = ast_node->children[1]->range_left;
+			ast_node->range_right = ast_node->children[1]->range_right;
+		} else
+		if (ast_node->children.size() > 1 && ast_node->children[1]->type == Yosys::AST::AST_REALVALUE && ast_node->children[0]->type == Yosys::AST::AST_CONSTANT) {
+			double as_realvalue = ast_node->children[0]->asReal(sign_hint);
+			delete ast_node->children[0];
+			ast_node->children[0] = new Yosys::AST::AstNode(Yosys::AST::AST_REALVALUE);
+			ast_node->children[0]->realvalue = as_realvalue;
+			did_something = true;
+		}
+	}
+
+	if (ast_node->type == Yosys::AST::AST_IDENTIFIER && !ast_node->basic_prep) {
+		// check if a plausible struct member sss.mmmm
+		std::string sname;
+		if (name_has_dot(ast_node->str, sname)) {
+			if (current_scope.count(ast_node->str) > 0) {
+				auto item_node = current_scope[ast_node->str];
+				if (item_node->type == Yosys::AST::AST_STRUCT_ITEM || item_node->type == Yosys::AST::AST_STRUCT || item_node->type == Yosys::AST::AST_UNION) {
+					// structure member, rewrite ast_node node to reference the packed struct wire
+					auto range = Yosys::AST::make_struct_member_range(ast_node, item_node);
+					newNode = new Yosys::AST::AstNode(Yosys::AST::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() && ast_node->children.size() > 0) {
+						if (ast_node->children[0]->type == Yosys::AST::AST_RANGE)
+							newNode->integer = 1;
+						else if (ast_node->children[0]->type == Yosys::AST::AST_MULTIRANGE)
+							newNode->integer = ast_node->children[0]->children.size();
+					}
+					newNode->basic_prep = true;
+					if (item_node->is_signed)
+						newNode = new Yosys::AST::AstNode(Yosys::AST::AST_TO_SIGNED, newNode);
+					goto apply_newNode;
+				}
+			}
+		}
+	}
+	// annotate identifiers using scope resolution and create auto-wires as needed
+	if (ast_node->type == Yosys::AST::AST_IDENTIFIER) {
+		if (current_scope.count(ast_node->str) == 0) {
+			Yosys::AST::AstNode *current_scope_ast = (current_ast_mod == nullptr) ? current_ast : current_ast_mod;
+			ast_node->str = ast_node->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 Yosys::AST::AST_PARAMETER:
+				case Yosys::AST::AST_LOCALPARAM:
+				case Yosys::AST::AST_WIRE:
+				case Yosys::AST::AST_AUTOWIRE:
+				case Yosys::AST::AST_GENVAR:
+				case Yosys::AST::AST_MEMORY:
+				case Yosys::AST::AST_FUNCTION:
+				case Yosys::AST::AST_TASK:
+				case Yosys::AST::AST_DPI_FUNCTION:
+					//log("found child %s, %s\n", type2str(node->type).c_str(), node->str.c_str());
+					if (ast_node->str == node->str) {
+						//log("add %s, type %s to scope\n", ast_node->str.c_str(), type2str(node->type).c_str());
+						current_scope[node->str] = node;
+					}
+					break;
+				case Yosys::AST::AST_ENUM:
+					current_scope[node->str] = node;
+					for (auto enum_node : node->children) {
+						log_assert(enum_node->type==Yosys::AST::AST_ENUM_ITEM);
+						if (ast_node->str == enum_node->str) {
+							//log("\nadding enum item %s to scope\n", ast_node->str.c_str());
+							current_scope[ast_node->str] = enum_node;
+						}
+					}
+					break;
+				default:
+					break;
+				}
+			}
+		}
+		if (current_scope.count(ast_node->str) == 0) {
+			if (current_ast_mod == nullptr) {
+				log_file_error(ast_node->filename, ast_node->location.first_line, "Identifier `%s' is implicitly declared outside of a module.\n", ast_node->str.c_str());
+			} else if (flag_autowire || ast_node->str == "\\$global_clock") {
+				Yosys::AST::AstNode *auto_wire = new Yosys::AST::AstNode(Yosys::AST::AST_AUTOWIRE);
+				auto_wire->str = ast_node->str;
+				current_ast_mod->children.push_back(auto_wire);
+				current_scope[ast_node->str] = auto_wire;
+				did_something = true;
+			} else {
+				log_file_error(ast_node->filename, ast_node->location.first_line, "Identifier `%s' is implicitly declared and `default_nettype is set to none.\n", ast_node->str.c_str());
+			}
+		}
+		if (ast_node->id2ast != current_scope[ast_node->str]) {
+			ast_node->id2ast = current_scope[ast_node->str];
+			did_something = true;
+		}
+	}
+
+	// split memory access with bit select to individual statements
+	if (ast_node->type == Yosys::AST::AST_IDENTIFIER && ast_node->children.size() == 2 && ast_node->children[0]->type == Yosys::AST::AST_RANGE && ast_node->children[1]->type == Yosys::AST::AST_RANGE && !in_lvalue && stage == 2)
+	{
+		if (ast_node->id2ast == NULL || ast_node->id2ast->type != Yosys::AST::AST_MEMORY || ast_node->children[0]->children.size() != 1)
+			log_file_error(ast_node->filename, ast_node->location.first_line, "Invalid bit-select on memory access!\n");
+
+		int mem_width, mem_size, addr_bits;
+		ast_node->id2ast->meminfo(mem_width, mem_size, addr_bits);
+
+		int data_range_left = ast_node->id2ast->children[0]->range_left;
+		int data_range_right = ast_node->id2ast->children[0]->range_right;
+
+		if (ast_node->id2ast->children[0]->range_swapped)
+			std::swap(data_range_left, data_range_right);
+
+		std::stringstream sstr;
+		sstr << "$mem2bits$" << ast_node->str << "$" << encode_filename(ast_node->filename) << ":" << ast_node->location.first_line << "$" << (autoidx++);
+		std::string wire_id = sstr.str();
+
+		Yosys::AST::AstNode *wire = new Yosys::AST::AstNode(Yosys::AST::AST_WIRE, new Yosys::AST::AstNode(Yosys::AST::AST_RANGE, ast_node->mkconst_int(data_range_left, true), ast_node->mkconst_int(data_range_right, true)));
+		wire->str = wire_id;
+		if (current_block)
+			wire->attributes[ID::nosync] = ast_node->mkconst_int(1, false);
+		current_ast_mod->children.push_back(wire);
+		while (simplify(wire, true, false, false, 1, -1, false, false)) { }
+
+		Yosys::AST::AstNode *data = ast_node->clone();
+		delete data->children[1];
+		data->children.pop_back();
+
+		Yosys::AST::AstNode *assign = new Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN_EQ, new Yosys::AST::AstNode(Yosys::AST::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
+		{
+			Yosys::AST::AstNode *proc = new Yosys::AST::AstNode(Yosys::AST::AST_ALWAYS, new Yosys::AST::AstNode(Yosys::AST::AST_BLOCK));
+			proc->children[0]->children.push_back(assign);
+			current_ast_mod->children.push_back(proc);
+		}
+
+		newNode = new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER, ast_node->children[1]->clone());
+		newNode->str = wire_id;
+		newNode->integer = ast_node->integer; // save original number of dimensions for $size() etc.
+		newNode->id2ast = wire;
+		goto apply_newNode;
+	}
+
+	if (ast_node->type == Yosys::AST::AST_WHILE)
+		log_file_error(ast_node->filename, ast_node->location.first_line, "While loops are only allowed in constant functions!\n");
+
+	if (ast_node->type == Yosys::AST::AST_REPEAT)
+	{
+		Yosys::AST::AstNode *count = ast_node->children[0];
+		Yosys::AST::AstNode *body = ast_node->children[1];
+
+		// eval count expression
+		while (simplify(count, true, false, false, stage, 32, true, false)) { }
+
+		if (count->type != Yosys::AST::AST_CONSTANT)
+			log_file_error(ast_node->filename, ast_node->location.first_line, "Repeat loops outside must have constant repeat counts!\n");
+
+		// convert to a block with the body repeated n times
+		ast_node->type = Yosys::AST::AST_BLOCK;
+		ast_node->children.clear();
+		for (int i = 0; i < count->bitsAsConst().as_int(); i++)
+			ast_node->children.insert(ast_node->children.begin(), body->clone());
+
+		delete count;
+		delete body;
+		did_something = true;
+	}
+
+	// unroll for loops and generate-for blocks
+	if ((ast_node->type == Yosys::AST::AST_GENFOR || ast_node->type == Yosys::AST::AST_FOR) && ast_node->children.size() != 0)
+	{
+		Yosys::AST::AstNode *init_ast = ast_node->children[0];
+		Yosys::AST::AstNode *while_ast = ast_node->children[1];
+		Yosys::AST::AstNode *next_ast = ast_node->children[2];
+		Yosys::AST::AstNode *body_ast = ast_node->children[3];
+
+		while (body_ast->type == Yosys::AST::AST_GENBLOCK && body_ast->str.empty() &&
+				body_ast->children.size() == 1 && body_ast->children.at(0)->type == Yosys::AST::AST_GENBLOCK)
+			body_ast = body_ast->children.at(0);
+
+		const char* loop_type_str = "procedural";
+		const char* var_type_str = "register";
+		Yosys::AST::AstNodeType var_type = Yosys::AST::AST_WIRE;
+		if (ast_node->type == Yosys::AST::AST_GENFOR) {
+			loop_type_str = "generate";
+			var_type_str = "genvar";
+			var_type = Yosys::AST::AST_GENVAR;
+		}
+
+		if (init_ast->type != Yosys::AST::AST_ASSIGN_EQ)
+			log_file_error(ast_node->filename, ast_node->location.first_line, "Unsupported 1st expression of %s for-loop!\n", loop_type_str);
+		if (next_ast->type != Yosys::AST::AST_ASSIGN_EQ)
+			log_file_error(ast_node->filename, ast_node->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(ast_node->filename, ast_node->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(ast_node->filename, ast_node->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(ast_node->filename, ast_node->location.first_line, "Incompatible left-hand sides in 1st and 3rd expression of %s for-loop!\n", loop_type_str);
+
+		// eval 1st expression
+		Yosys::AST::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 (simplify(varbuf, true, false, false, stage, 32, true, false)) { }
+		}
+
+		if (varbuf->type != Yosys::AST::AST_CONSTANT)
+			log_file_error(ast_node->filename, ast_node->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 Yosys::AST::AstNode(Yosys::AST::AST_LOCALPARAM, varbuf);
+		varbuf->str = init_ast->children[0]->str;
+
+		Yosys::AST::AstNode *backup_scope_varbuf = current_scope[varbuf->str];
+		current_scope[varbuf->str] = varbuf;
+
+		size_t current_block_idx = 0;
+		if (ast_node->type == Yosys::AST::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
+			Yosys::AST::AstNode *buf = while_ast->clone();
+			{
+				int expr_width_hint = -1;
+				bool expr_sign_hint = true;
+				buf->detectSignWidth(expr_width_hint, expr_sign_hint);
+				while (simplify(buf, true, false, false, stage, expr_width_hint, expr_sign_hint, false)) { }
+			}
+
+			if (buf->type != Yosys::AST::AST_CONSTANT)
+				log_file_error(ast_node->filename, ast_node->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 == Yosys::AST::AST_GENBLOCK || body_ast->type == Yosys::AST::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
+			Yosys::AST::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 (ast_node->type == Yosys::AST::AST_GENFOR) {
+				for (size_t i = 0; i < buf->children.size(); i++) {
+					simplify(buf->children[i], 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 (simplify(buf, true, false, false, stage, expr_width_hint, expr_sign_hint, true)) { }
+			}
+
+			if (buf->type != Yosys::AST::AST_CONSTANT)
+				log_file_error(ast_node->filename, ast_node->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 (ast_node->type == Yosys::AST::AST_FOR) {
+			Yosys::AST::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;
+		ast_node->delete_children();
+		did_something = true;
+	}
+
+	// check for local objects in unnamed block
+	if (ast_node->type == Yosys::AST::AST_BLOCK && ast_node->str.empty())
+	{
+		for (size_t i = 0; i < ast_node->children.size(); i++)
+			if (ast_node->children[i]->type == Yosys::AST::AST_WIRE || ast_node->children[i]->type == Yosys::AST::AST_MEMORY || ast_node->children[i]->type == Yosys::AST::AST_PARAMETER || ast_node->children[i]->type == Yosys::AST::AST_LOCALPARAM || ast_node->children[i]->type == Yosys::AST::AST_TYPEDEF)
+			{
+				log_assert(!VERILOG_FRONTEND::sv_mode);
+				log_file_error(ast_node->children[i]->filename, ast_node->children[i]->location.first_line, "Local declaration in unnamed block is only supported in SystemVerilog mode!\n");
+			}
+	}
+
+	// transform block with name
+	if (ast_node->type == Yosys::AST::AST_BLOCK && !ast_node->str.empty())
+	{
+		ast_node->expand_genblock(ast_node->str + ".");
+
+		// if ast_node is an autonamed block is in an always_comb
+		if (current_always && current_always->attributes.count(ID::always_comb)
+				&& is_autonamed_block(ast_node->str))
+			// track local variables in ast_node block so we can consider adding
+			// nosync once the block has been fully elaborated
+			for (Yosys::AST::AstNode *child : ast_node->children)
+				if (child->type == Yosys::AST::AST_WIRE &&
+						!child->attributes.count(ID::nosync))
+					mark_auto_nosync(ast_node, child);
+
+		std::vector<Yosys::AST::AstNode*> new_children;
+		for (size_t i = 0; i < ast_node->children.size(); i++)
+			if (ast_node->children[i]->type == Yosys::AST::AST_WIRE || ast_node->children[i]->type == Yosys::AST::AST_MEMORY || ast_node->children[i]->type == Yosys::AST::AST_PARAMETER || ast_node->children[i]->type == Yosys::AST::AST_LOCALPARAM || ast_node->children[i]->type == Yosys::AST::AST_TYPEDEF) {
+				simplify(ast_node->children[i], false, false, false, stage, -1, false, false);
+				current_ast_mod->children.push_back(ast_node->children[i]);
+				current_scope[ast_node->children[i]->str] = ast_node->children[i];
+			} else
+				new_children.push_back(ast_node->children[i]);
+
+		ast_node->children.swap(new_children);
+		did_something = true;
+		ast_node->str.clear();
+	}
+
+	// simplify unconditional generate block
+	if (ast_node->type == Yosys::AST::AST_GENBLOCK && ast_node->children.size() != 0)
+	{
+		if (!ast_node->str.empty()) {
+			ast_node->expand_genblock(ast_node->str + ".");
+		}
+
+		for (size_t i = 0; i < ast_node->children.size(); i++) {
+			simplify(ast_node->children[i], const_fold, false, false, stage, -1, false, false);
+			current_ast_mod->children.push_back(ast_node->children[i]);
+		}
+
+		ast_node->children.clear();
+		did_something = true;
+	}
+
+	// simplify generate-if blocks
+	if (ast_node->type == Yosys::AST::AST_GENIF && ast_node->children.size() != 0)
+	{
+		Yosys::AST::AstNode *buf = ast_node->children[0]->clone();
+		while (simplify(buf, true, false, false, stage, width_hint, sign_hint, false)) { }
+		if (buf->type != Yosys::AST::AST_CONSTANT) {
+			// for (auto f : log_files)
+			// 	dumpAst(f, "verilog-ast> ");
+			log_file_error(ast_node->filename, ast_node->location.first_line, "Condition for generate if is not constant!\n");
+		}
+		if (buf->asBool() != 0) {
+			delete buf;
+			buf = ast_node->children[1]->clone();
+		} else {
+			delete buf;
+			buf = ast_node->children.size() > 2 ? ast_node->children[2]->clone() : NULL;
+		}
+
+		if (buf)
+		{
+			if (buf->type != Yosys::AST::AST_GENBLOCK)
+				buf = new Yosys::AST::AstNode(Yosys::AST::AST_GENBLOCK, buf);
+
+			if (!buf->str.empty()) {
+				buf->expand_genblock(buf->str + ".");
+			}
+
+			for (size_t i = 0; i < buf->children.size(); i++) {
+				simplify(buf->children[i], const_fold, false, false, stage, -1, false, false);
+				current_ast_mod->children.push_back(buf->children[i]);
+			}
+
+			buf->children.clear();
+			delete buf;
+		}
+
+		ast_node->delete_children();
+		did_something = true;
+	}
+
+	// simplify generate-case blocks
+	if (ast_node->type == Yosys::AST::AST_GENCASE && ast_node->children.size() != 0)
+	{
+		Yosys::AST::AstNode *buf = ast_node->children[0]->clone();
+		while (simplify(buf, true, false, false, stage, width_hint, sign_hint, false)) { }
+		if (buf->type != Yosys::AST::AST_CONSTANT) {
+			// for (auto f : log_files)
+			// 	dumpAst(f, "verilog-ast> ");
+			log_file_error(ast_node->filename, ast_node->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;
+
+		Yosys::AST::AstNode *selected_case = NULL;
+		for (size_t i = 1; i < ast_node->children.size(); i++)
+		{
+			log_assert(ast_node->children.at(i)->type == Yosys::AST::AST_COND || ast_node->children.at(i)->type == Yosys::AST::AST_CONDX || ast_node->children.at(i)->type == Yosys::AST::AST_CONDZ);
+
+			Yosys::AST::AstNode *this_genblock = NULL;
+			for (auto child : ast_node->children.at(i)->children) {
+				log_assert(this_genblock == NULL);
+				if (child->type == Yosys::AST::AST_GENBLOCK)
+					this_genblock = child;
+			}
+
+			for (auto child : ast_node->children.at(i)->children)
+			{
+				if (child->type == Yosys::AST::AST_DEFAULT) {
+					if (selected_case == NULL)
+						selected_case = this_genblock;
+					continue;
+				}
+				if (child->type == Yosys::AST::AST_GENBLOCK)
+					continue;
+
+				buf = child->clone();
+				while (simplify(buf, true, false, false, stage, width_hint, sign_hint, true)) { }
+				if (buf->type != Yosys::AST::AST_CONSTANT) {
+					// for (auto f : log_files)
+					// 	dumpAst(f, "verilog-ast> ");
+					log_file_error(ast_node->filename, ast_node->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 = ast_node->children.size();
+					break;
+				}
+			}
+		}
+
+		if (selected_case != NULL)
+		{
+			log_assert(selected_case->type == Yosys::AST::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++) {
+				simplify(buf->children[i], const_fold, false, false, stage, -1, false, false);
+				current_ast_mod->children.push_back(buf->children[i]);
+			}
+
+			buf->children.clear();
+			delete buf;
+		}
+
+		ast_node->delete_children();
+		did_something = true;
+	}
+
+	// unroll cell arrays
+	if (ast_node->type == Yosys::AST::AST_CELLARRAY)
+	{
+		if (!ast_node->children.at(0)->range_valid)
+			log_file_error(ast_node->filename, ast_node->location.first_line, "Non-constant array range on cell array.\n");
+
+		newNode = new Yosys::AST::AstNode(Yosys::AST::AST_GENBLOCK);
+		int num = max(ast_node->children.at(0)->range_left, ast_node->children.at(0)->range_right) - min(ast_node->children.at(0)->range_left, ast_node->children.at(0)->range_right) + 1;
+
+		for (int i = 0; i < num; i++) {
+			int idx = ast_node->children.at(0)->range_left > ast_node->children.at(0)->range_right ? ast_node->children.at(0)->range_right + i : ast_node->children.at(0)->range_right - i;
+			Yosys::AST::AstNode *new_cell = ast_node->children.at(1)->clone();
+			newNode->children.push_back(new_cell);
+			new_cell->str += stringf("[%d]", idx);
+			if (new_cell->type == Yosys::AST::AST_PRIMITIVE) {
+				log_file_error(ast_node->filename, ast_node->location.first_line, "Cell arrays of primitives are currently not supported.\n");
+			} else {
+				log_assert(new_cell->children.at(0)->type == Yosys::AST::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 (ast_node->type == Yosys::AST::AST_PRIMITIVE)
+	{
+		if (ast_node->children.size() < 2)
+			log_file_error(ast_node->filename, ast_node->location.first_line, "Insufficient number of arguments for primitive `%s'!\n", ast_node->str.c_str());
+
+		std::vector<Yosys::AST::AstNode*> children_list;
+		for (auto child : ast_node->children) {
+			log_assert(child->type == Yosys::AST::AST_ARGUMENT);
+			log_assert(child->children.size() == 1);
+			children_list.push_back(child->children[0]);
+			child->children.clear();
+			delete child;
+		}
+		ast_node->children.clear();
+
+		if (ast_node->str == "bufif0" || ast_node->str == "bufif1" || ast_node->str == "notif0" || ast_node->str == "notif1")
+		{
+			if (children_list.size() != 3)
+				log_file_error(ast_node->filename, ast_node->location.first_line, "Invalid number of arguments for primitive `%s'!\n", ast_node->str.c_str());
+
+			std::vector<RTLIL::State> z_const(1, RTLIL::State::Sz);
+
+			Yosys::AST::AstNode *mux_input = children_list.at(1);
+			if (ast_node->str == "notif0" || ast_node->str == "notif1") {
+				mux_input = new Yosys::AST::AstNode(Yosys::AST::AST_BIT_NOT, mux_input);
+			}
+			Yosys::AST::AstNode *node = new Yosys::AST::AstNode(Yosys::AST::AST_TERNARY, children_list.at(2));
+			if (ast_node->str == "bufif0") {
+				node->children.push_back(Yosys::AST::AstNode::mkconst_bits(z_const, false));
+				node->children.push_back(mux_input);
+			} else {
+				node->children.push_back(mux_input);
+				node->children.push_back(Yosys::AST::AstNode::mkconst_bits(z_const, false));
+			}
+
+			ast_node->str.clear();
+			ast_node->type = Yosys::AST::AST_ASSIGN;
+			ast_node->children.push_back(children_list.at(0));
+			ast_node->children.back()->was_checked = true;
+			ast_node->children.push_back(node);
+			did_something = true;
+		}
+		else if (ast_node->str == "buf" || ast_node->str == "not")
+		{
+			Yosys::AST::AstNode *input = children_list.back();
+			if (ast_node->str == "not")
+				input = new Yosys::AST::AstNode(Yosys::AST::AST_BIT_NOT, input);
+
+			newNode = new Yosys::AST::AstNode(Yosys::AST::AST_GENBLOCK);
+			for (auto it = children_list.begin(); it != std::prev(children_list.end()); it++) {
+				newNode->children.push_back(new Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN, *it, input->clone()));
+				newNode->children.back()->was_checked = true;
+			}
+			delete input;
+
+			did_something = true;
+		}
+		else
+		{
+			Yosys::AST::AstNodeType op_type = Yosys::AST::AST_NONE;
+			bool invert_results = false;
+
+			if (ast_node->str == "and")
+				op_type = Yosys::AST::AST_BIT_AND;
+			if (ast_node->str == "nand")
+				op_type = Yosys::AST::AST_BIT_AND, invert_results = true;
+			if (ast_node->str == "or")
+				op_type = Yosys::AST::AST_BIT_OR;
+			if (ast_node->str == "nor")
+				op_type = Yosys::AST::AST_BIT_OR, invert_results = true;
+			if (ast_node->str == "xor")
+				op_type = Yosys::AST::AST_BIT_XOR;
+			if (ast_node->str == "xnor")
+				op_type = Yosys::AST::AST_BIT_XOR, invert_results = true;
+			log_assert(op_type != Yosys::AST::AST_NONE);
+
+			Yosys::AST::AstNode *node = children_list[1];
+			if (op_type != Yosys::AST::AST_POS)
+				for (size_t i = 2; i < children_list.size(); i++) {
+					node = new Yosys::AST::AstNode(op_type, node, children_list[i]);
+					node->location = ast_node->location;
+				}
+			if (invert_results)
+				node = new Yosys::AST::AstNode(Yosys::AST::AST_BIT_NOT, node);
+
+			ast_node->str.clear();
+			ast_node->type = Yosys::AST::AST_ASSIGN;
+			ast_node->children.push_back(children_list[0]);
+			ast_node->children.back()->was_checked = true;
+			ast_node->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 (ast_node->type == Yosys::AST::AST_ASSIGN_EQ || ast_node->type == Yosys::AST::AST_ASSIGN_LE)
+	{
+		if (ast_node->children[0]->type != Yosys::AST::AST_IDENTIFIER || ast_node->children[0]->children.size() == 0)
+			goto skip_dynamic_range_lvalue_expansion;
+		if (ast_node->children[0]->children[0]->range_valid || did_something)
+			goto skip_dynamic_range_lvalue_expansion;
+		if (ast_node->children[0]->id2ast == NULL || ast_node->children[0]->id2ast->type != Yosys::AST::AST_WIRE)
+			goto skip_dynamic_range_lvalue_expansion;
+		if (!ast_node->children[0]->id2ast->range_valid)
+			goto skip_dynamic_range_lvalue_expansion;
+
+		int source_width = ast_node->children[0]->id2ast->range_left - ast_node->children[0]->id2ast->range_right + 1;
+		int source_offset = ast_node->children[0]->id2ast->range_right;
+		int result_width = 1;
+		Yosys::AST::AstNode *member_node = systemverilog_plugin::get_struct_member(ast_node->children[0]);
+		if (member_node) {
+			// Clamp chunk to range of member within struct/union.
+			log_assert(!source_offset && !ast_node->children[0]->id2ast->range_swapped);
+			source_width = member_node->range_left - member_node->range_right + 1;
+		}
+
+		Yosys::AST::AstNode *shift_expr = NULL;
+		Yosys::AST::AstNode *range = ast_node->children[0]->children[0];
+
+		if (range->children.size() == 1) {
+			shift_expr = range->children[0]->clone();
+		} else {
+			shift_expr = range->children[1]->clone();
+			Yosys::AST::AstNode *left_at_zero_ast = range->children[0]->clone();
+			Yosys::AST::AstNode *right_at_zero_ast = range->children[1]->clone();
+			while (simplify(left_at_zero_ast, true, true, false, stage, -1, false, false)) { }
+			while (simplify(right_at_zero_ast, true, true, false, stage, -1, false, false)) { }
+			if (left_at_zero_ast->type != Yosys::AST::AST_CONSTANT || right_at_zero_ast->type != Yosys::AST::AST_CONSTANT)
+				log_file_error(ast_node->filename, ast_node->location.first_line, "Unsupported expression on dynamic range select on signal `%s'!\n", ast_node->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 (ast_node->children[0]->id2ast->attributes.count(ID::nowrshmsk)) {
+			Yosys::AST::AstNode *node = ast_node->children[0]->id2ast->attributes.at(ID::nowrshmsk);
+			while (simplify(node, true, false, false, stage, -1, false, false)) { }
+			if (node->type != Yosys::AST::AST_CONSTANT)
+				log_file_error(ast_node->filename, ast_node->location.first_line, "Non-constant value for `nowrshmsk' attribute on `%s'!\n", ast_node->children[0]->id2ast->str.c_str());
+			if (node->asAttrConst().as_bool())
+				use_case_method = true;
+		}
+
+		if (!use_case_method && current_always->detect_latch(ast_node->children[0]->str))
+			use_case_method = true;
+
+		if (use_case_method)
+		{
+			// big case block
+
+			did_something = true;
+			newNode = new Yosys::AST::AstNode(Yosys::AST::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;
+				Yosys::AST::AstNode *cond = new Yosys::AST::AstNode(Yosys::AST::AST_COND, ast_node->mkconst_int(start_bit, true));
+				Yosys::AST::AstNode *lvalue = ast_node->children[0]->clone();
+				lvalue->delete_children();
+				if (member_node)
+					lvalue->attributes[ID::wiretype] = member_node->clone();
+				lvalue->children.push_back(new Yosys::AST::AstNode(Yosys::AST::AST_RANGE,
+						ast_node->mkconst_int(end_bit, true), ast_node->mkconst_int(start_bit, true)));
+				cond->children.push_back(new Yosys::AST::AstNode(Yosys::AST::AST_BLOCK, new Yosys::AST::AstNode(ast_node->type, lvalue, ast_node->children[1]->clone())));
+				newNode->children.push_back(cond);
+			}
+		}
+		else
+		{
+			// mask and shift operations, disabled for now
+
+			Yosys::AST::AstNode *wire_mask = new Yosys::AST::AstNode(Yosys::AST::AST_WIRE, new Yosys::AST::AstNode(Yosys::AST::AST_RANGE, ast_node->mkconst_int(source_width-1, true), ast_node->mkconst_int(0, true)));
+			wire_mask->str = stringf("$bitselwrite$mask$%s:%d$%d", encode_filename(ast_node->filename).c_str(), ast_node->location.first_line, autoidx++);
+			wire_mask->attributes[ID::nosync] = ast_node->mkconst_int(1, false);
+			wire_mask->is_logic = true;
+			while (simplify(wire_mask, true, false, false, 1, -1, false, false)) { }
+			current_ast_mod->children.push_back(wire_mask);
+
+			Yosys::AST::AstNode *wire_data = new Yosys::AST::AstNode(Yosys::AST::AST_WIRE, new Yosys::AST::AstNode(Yosys::AST::AST_RANGE, ast_node->mkconst_int(source_width-1, true), ast_node->mkconst_int(0, true)));
+			wire_data->str = stringf("$bitselwrite$data$%s:%d$%d", encode_filename(ast_node->filename).c_str(), ast_node->location.first_line, autoidx++);
+			wire_data->attributes[ID::nosync] = ast_node->mkconst_int(1, false);
+			wire_data->is_logic = true;
+			while (simplify(wire_data, 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);
+
+			Yosys::AST::AstNode *wire_sel = new Yosys::AST::AstNode(Yosys::AST::AST_WIRE, new Yosys::AST::AstNode(Yosys::AST::AST_RANGE, ast_node->mkconst_int(shamt_width_hint-1, true), ast_node->mkconst_int(0, true)));
+			wire_sel->str = stringf("$bitselwrite$sel$%s:%d$%d", encode_filename(ast_node->filename).c_str(), ast_node->location.first_line, autoidx++);
+			wire_sel->attributes[ID::nosync] = ast_node->mkconst_int(1, false);
+			wire_sel->is_logic = true;
+			wire_sel->is_signed = shamt_sign_hint;
+			while (simplify(wire_sel, true, false, false, 1, -1, false, false)) { }
+			current_ast_mod->children.push_back(wire_sel);
+
+			did_something = true;
+			newNode = new Yosys::AST::AstNode(Yosys::AST::AST_BLOCK);
+
+			Yosys::AST::AstNode *lvalue = ast_node->children[0]->clone();
+			lvalue->delete_children();
+			if (member_node)
+				lvalue->attributes[ID::wiretype] = member_node->clone();
+
+			Yosys::AST::AstNode *ref_mask = new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER);
+			ref_mask->str = wire_mask->str;
+			ref_mask->id2ast = wire_mask;
+			ref_mask->was_checked = true;
+
+			Yosys::AST::AstNode *ref_data = new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER);
+			ref_data->str = wire_data->str;
+			ref_data->id2ast = wire_data;
+			ref_data->was_checked = true;
+
+			Yosys::AST::AstNode *ref_sel = new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER);
+			ref_sel->str = wire_sel->str;
+			ref_sel->id2ast = wire_sel;
+			ref_sel->was_checked = true;
+
+			Yosys::AST::AstNode *old_data = lvalue->clone();
+			if (ast_node->type == Yosys::AST::AST_ASSIGN_LE)
+				old_data->lookahead = true;
+
+			Yosys::AST::AstNode *s = new Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN_EQ, ref_sel->clone(), shift_expr);
+			newNode->children.push_back(s);
+
+			Yosys::AST::AstNode *shamt = ref_sel;
+
+			// convert to signed while preserving the sign and value
+			shamt = new Yosys::AST::AstNode(Yosys::AST::AST_CAST_SIZE, ast_node->mkconst_int(shamt_width_hint + 1, true), shamt);
+			shamt = new Yosys::AST::AstNode(Yosys::AST::AST_TO_SIGNED, shamt);
+
+			// offset the shift amount by the lower bound of the dimension
+			int start_bit = source_offset;
+			shamt = new Yosys::AST::AstNode(Yosys::AST::AST_SUB, shamt, ast_node->mkconst_int(start_bit, true));
+
+			// reflect the shift amount if the dimension is swapped
+			if (ast_node->children[0]->id2ast->range_swapped)
+				shamt = new Yosys::AST::AstNode(Yosys::AST::AST_SUB, ast_node->mkconst_int(source_width - result_width, true), shamt);
+
+			// AST_SHIFT uses negative amounts for shifting left
+			shamt = new Yosys::AST::AstNode(Yosys::AST::AST_NEG, shamt);
+
+			Yosys::AST::AstNode *t;
+
+			t = Yosys::AST::AstNode::mkconst_bits(std::vector<RTLIL::State>(result_width, State::S1), false);
+			t = new Yosys::AST::AstNode(Yosys::AST::AST_SHIFT, t, shamt->clone());
+			t = new Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN_EQ, ref_mask->clone(), t);
+			newNode->children.push_back(t);
+
+			t = new Yosys::AST::AstNode(Yosys::AST::AST_BIT_AND, Yosys::AST::AstNode::mkconst_bits(std::vector<RTLIL::State>(result_width, State::S1), false), ast_node->children[1]->clone());
+			t = new Yosys::AST::AstNode(Yosys::AST::AST_SHIFT, t, shamt);
+			t = new Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN_EQ, ref_data->clone(), t);
+			newNode->children.push_back(t);
+
+			t = new Yosys::AST::AstNode(Yosys::AST::AST_BIT_AND, old_data, new Yosys::AST::AstNode(Yosys::AST::AST_BIT_NOT, ref_mask));
+			t = new Yosys::AST::AstNode(Yosys::AST::AST_BIT_OR, t, ref_data);
+			t = new Yosys::AST::AstNode(ast_node->type, lvalue, t);
+			newNode->children.push_back(t);
+		}
+
+		goto apply_newNode;
+	}
+skip_dynamic_range_lvalue_expansion:;
+
+	if (stage > 1 && (ast_node->type == Yosys::AST::AST_ASSERT || ast_node->type == Yosys::AST::AST_ASSUME || ast_node->type == Yosys::AST::AST_LIVE || ast_node->type == Yosys::AST::AST_FAIR || ast_node->type == Yosys::AST::AST_COVER) && current_block != NULL)
+	{
+		std::stringstream sstr;
+		sstr << "$formal$" << encode_filename(ast_node->filename) << ":" << ast_node->location.first_line << "$" << (autoidx++);
+		std::string id_check = sstr.str() + "_CHECK", id_en = sstr.str() + "_EN";
+
+		Yosys::AST::AstNode *wire_check = new Yosys::AST::AstNode(Yosys::AST::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 (simplify(wire_check, true, false, false, 1, -1, false, false)) { }
+
+		Yosys::AST::AstNode *wire_en = new Yosys::AST::AstNode(Yosys::AST::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 Yosys::AST::AstNode(Yosys::AST::AST_INITIAL, new Yosys::AST::AstNode(Yosys::AST::AST_BLOCK, new Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN_LE, new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER), ast_node->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 (simplify(wire_en, true, false, false, 1, -1, false, false)) { }
+
+		Yosys::AST::AstNode *check_defval;
+		if (ast_node->type == Yosys::AST::AST_LIVE || ast_node->type == Yosys::AST::AST_FAIR) {
+			check_defval = new Yosys::AST::AstNode(Yosys::AST::AST_REDUCE_BOOL, ast_node->children[0]->clone());
+		} else {
+			std::vector<RTLIL::State> x_bit;
+			x_bit.push_back(RTLIL::State::Sx);
+			check_defval = Yosys::AST::AstNode::mkconst_bits(x_bit, false);
+		}
+
+		Yosys::AST::AstNode *assign_check = new Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN_LE, new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER), check_defval);
+		assign_check->children[0]->str = id_check;
+		assign_check->children[0]->was_checked = true;
+
+		Yosys::AST::AstNode *assign_en = new Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN_LE, new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER), ast_node->mkconst_int(0, false, 1));
+		assign_en->children[0]->str = id_en;
+		assign_en->children[0]->was_checked = true;
+
+		Yosys::AST::AstNode *default_signals = new Yosys::AST::AstNode(Yosys::AST::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 (ast_node->type == Yosys::AST::AST_LIVE || ast_node->type == Yosys::AST::AST_FAIR) {
+			assign_check = nullptr;
+		} else {
+			assign_check = new Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN_LE, new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER), new Yosys::AST::AstNode(Yosys::AST::AST_REDUCE_BOOL, ast_node->children[0]->clone()));
+			assign_check->children[0]->str = id_check;
+			assign_check->children[0]->was_checked = true;
+		}
+
+		if (current_always == nullptr || current_always->type != Yosys::AST::AST_INITIAL) {
+			assign_en = new Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN_LE, new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER), ast_node->mkconst_int(1, false, 1));
+		} else {
+			assign_en = new Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN_LE, new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER), new Yosys::AST::AstNode(Yosys::AST::AST_FCALL));
+			assign_en->children[1]->str = "\\$initstate";
+		}
+		assign_en->children[0]->str = id_en;
+		assign_en->children[0]->was_checked = true;
+
+		newNode = new Yosys::AST::AstNode(Yosys::AST::AST_BLOCK);
+		if (assign_check != nullptr)
+			newNode->children.push_back(assign_check);
+		newNode->children.push_back(assign_en);
+
+		Yosys::AST::AstNode *assertnode = new Yosys::AST::AstNode(ast_node->type);
+		assertnode->location = ast_node->location;
+		assertnode->str = ast_node->str;
+		assertnode->children.push_back(new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER));
+		assertnode->children.push_back(new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER));
+		assertnode->children[0]->str = id_check;
+		assertnode->children[1]->str = id_en;
+		assertnode->attributes.swap(ast_node->attributes);
+		current_ast_mod->children.push_back(assertnode);
+
+		goto apply_newNode;
+	}
+
+	if (stage > 1 && (ast_node->type == Yosys::AST::AST_ASSERT || ast_node->type == Yosys::AST::AST_ASSUME || ast_node->type == Yosys::AST::AST_LIVE || ast_node->type == Yosys::AST::AST_FAIR || ast_node->type == Yosys::AST::AST_COVER) && ast_node->children.size() == 1)
+	{
+		ast_node->children.push_back(ast_node->mkconst_int(1, false, 1));
+		did_something = true;
+	}
+
+	// found right-hand side identifier for memory -> replace with memory read port
+	if (stage > 1 && ast_node->type == Yosys::AST::AST_IDENTIFIER && ast_node->id2ast != NULL && ast_node->id2ast->type == Yosys::AST::AST_MEMORY && !in_lvalue &&
+			ast_node->children.size() == 1 && ast_node->children[0]->type == Yosys::AST::AST_RANGE && ast_node->children[0]->children.size() == 1) {
+		newNode = new Yosys::AST::AstNode(Yosys::AST::AST_MEMRD, ast_node->children[0]->children[0]->clone());
+		newNode->str = ast_node->str;
+		newNode->id2ast = ast_node->id2ast;
+		goto apply_newNode;
+	}
+
+	// assignment with nontrivial member in left-hand concat expression -> split assignment
+	if ((ast_node->type == Yosys::AST::AST_ASSIGN_EQ || ast_node->type == Yosys::AST::AST_ASSIGN_LE) && ast_node->children[0]->type == Yosys::AST::AST_CONCAT && width_hint > 0)
+	{
+		bool found_nontrivial_member = false;
+
+		for (auto child : ast_node->children[0]->children) {
+			if (child->type == Yosys::AST::AST_IDENTIFIER && child->id2ast != NULL && child->id2ast->type == Yosys::AST::AST_MEMORY)
+				found_nontrivial_member = true;
+		}
+
+		if (found_nontrivial_member)
+		{
+			newNode = new Yosys::AST::AstNode(Yosys::AST::AST_BLOCK);
+
+			Yosys::AST::AstNode *wire_tmp = new Yosys::AST::AstNode(Yosys::AST::AST_WIRE, new Yosys::AST::AstNode(Yosys::AST::AST_RANGE, ast_node->mkconst_int(width_hint-1, true), ast_node->mkconst_int(0, true)));
+			wire_tmp->str = stringf("$splitcmplxassign$%s:%d$%d", encode_filename(ast_node->filename).c_str(), ast_node->location.first_line, autoidx++);
+			current_ast_mod->children.push_back(wire_tmp);
+			current_scope[wire_tmp->str] = wire_tmp;
+			wire_tmp->attributes[ID::nosync] = ast_node->mkconst_int(1, false);
+			while (simplify(wire_tmp, true, false, false, 1, -1, false, false)) { }
+			wire_tmp->is_logic = true;
+
+			Yosys::AST::AstNode *wire_tmp_id = new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER);
+			wire_tmp_id->str = wire_tmp->str;
+
+			newNode->children.push_back(new Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN_EQ, wire_tmp_id, ast_node->children[1]->clone()));
+			newNode->children.back()->was_checked = true;
+
+			int cursor = 0;
+			for (auto child : ast_node->children[0]->children)
+			{
+				int child_width_hint = -1;
+				bool child_sign_hint = true;
+				child->detectSignWidth(child_width_hint, child_sign_hint);
+
+				Yosys::AST::AstNode *rhs = wire_tmp_id->clone();
+				rhs->children.push_back(new Yosys::AST::AstNode(Yosys::AST::AST_RANGE, ast_node->mkconst_int(cursor+child_width_hint-1, true), ast_node->mkconst_int(cursor, true)));
+				newNode->children.push_back(new Yosys::AST::AstNode(ast_node->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 && (ast_node->type == Yosys::AST::AST_ASSIGN_EQ || ast_node->type == Yosys::AST::AST_ASSIGN_LE) && ast_node->children[0]->type == Yosys::AST::AST_IDENTIFIER &&
+			ast_node->children[0]->id2ast && ast_node->children[0]->id2ast->type == Yosys::AST::AST_MEMORY && ast_node->children[0]->id2ast->children.size() >= 2 &&
+			ast_node->children[0]->id2ast->children[0]->range_valid && ast_node->children[0]->id2ast->children[1]->range_valid &&
+			(ast_node->children[0]->children.size() == 1 || ast_node->children[0]->children.size() == 2) && ast_node->children[0]->children[0]->type == Yosys::AST::AST_RANGE)
+	{
+		std::stringstream sstr;
+		sstr << "$memwr$" << ast_node->children[0]->str << "$" << encode_filename(ast_node->filename) << ":" << ast_node->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 = ast_node->children[0]->id2ast->is_signed;
+		ast_node->children[0]->id2ast->meminfo(mem_width, mem_size, addr_bits);
+
+		newNode = new Yosys::AST::AstNode(Yosys::AST::AST_BLOCK);
+		Yosys::AST::AstNode *defNode = new Yosys::AST::AstNode(Yosys::AST::AST_BLOCK);
+
+		int data_range_left = ast_node->children[0]->id2ast->children[0]->range_left;
+		int data_range_right = ast_node->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;
+		ast_node->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);
+
+		Yosys::AST::AstNode *node_addr = nullptr;
+		if (ast_node->children[0]->children[0]->children[0]->isConst()) {
+			node_addr = ast_node->children[0]->children[0]->children[0]->clone();
+		} else {
+			Yosys::AST::AstNode *wire_addr = new Yosys::AST::AstNode(Yosys::AST::AST_WIRE, new Yosys::AST::AstNode(Yosys::AST::AST_RANGE, ast_node->mkconst_int(addr_bits-1, true), ast_node->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 (simplify(wire_addr, true, false, false, 1, -1, false, false)) { }
+
+			Yosys::AST::AstNode *assign_addr = new Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN_EQ, new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER), Yosys::AST::AstNode::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 Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN_EQ, new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER), ast_node->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 Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER);
+			node_addr->str = id_addr;
+		}
+
+		Yosys::AST::AstNode *node_data = nullptr;
+		if (ast_node->children[0]->children.size() == 1 && ast_node->children[1]->isConst()) {
+			node_data = ast_node->children[1]->clone();
+		} else {
+			Yosys::AST::AstNode *wire_data = new Yosys::AST::AstNode(Yosys::AST::AST_WIRE, new Yosys::AST::AstNode(Yosys::AST::AST_RANGE, ast_node->mkconst_int(mem_width-1, true), ast_node->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 (simplify(wire_data, true, false, false, 1, -1, false, false)) { }
+
+			Yosys::AST::AstNode *assign_data = new Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN_EQ, new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER), Yosys::AST::AstNode::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 Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER);
+			node_data->str = id_data;
+		}
+
+		Yosys::AST::AstNode *wire_en = new Yosys::AST::AstNode(Yosys::AST::AST_WIRE, new Yosys::AST::AstNode(Yosys::AST::AST_RANGE, ast_node->mkconst_int(mem_width-1, true), ast_node->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 (simplify(wire_en, true, false, false, 1, -1, false, false)) { }
+
+		Yosys::AST::AstNode *assign_en_first = new Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN_EQ, new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER), ast_node->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);
+
+		Yosys::AST::AstNode *node_en = new Yosys::AST::AstNode(Yosys::AST::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;
+
+		Yosys::AST::AstNode *assign_data = nullptr;
+		Yosys::AST::AstNode *assign_en = nullptr;
+		if (ast_node->children[0]->children.size() == 2)
+		{
+			if (ast_node->children[0]->children[1]->range_valid)
+			{
+				int offset = ast_node->children[0]->children[1]->range_right;
+				int width = ast_node->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 Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN_EQ, new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER),
+						new Yosys::AST::AstNode(Yosys::AST::AST_CONCAT, Yosys::AST::AstNode::mkconst_bits(padding_x, false), ast_node->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 Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN_EQ, new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER), Yosys::AST::AstNode::mkconst_bits(set_bits_en, false));
+				assign_en->children[0]->str = id_en;
+				assign_en->children[0]->was_checked = true;
+			}
+			else
+			{
+				Yosys::AST::AstNode *the_range = ast_node->children[0]->children[1];
+				Yosys::AST::AstNode *left_at_zero_ast = the_range->children[0]->clone();
+				Yosys::AST::AstNode *right_at_zero_ast = the_range->children.size() >= 2 ? the_range->children[1]->clone() : left_at_zero_ast->clone();
+				Yosys::AST::AstNode *offset_ast = right_at_zero_ast->clone();
+
+				if (mem_data_range_offset)
+					offset_ast = new Yosys::AST::AstNode(Yosys::AST::AST_SUB, offset_ast, ast_node->mkconst_int(mem_data_range_offset, true));
+
+				while (simplify(left_at_zero_ast, true, true, false, 1, -1, false, false)) { }
+				while (simplify(right_at_zero_ast, true, true, false, 1, -1, false, false)) { }
+				if (left_at_zero_ast->type != Yosys::AST::AST_CONSTANT || right_at_zero_ast->type != Yosys::AST::AST_CONSTANT)
+					log_file_error(ast_node->filename, ast_node->location.first_line, "Unsupported expression on dynamic range select on signal `%s'!\n", ast_node->str.c_str());
+				int width = abs(int(left_at_zero_ast->integer - right_at_zero_ast->integer)) + 1;
+
+				assign_data = new Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN_EQ, new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER),
+						new Yosys::AST::AstNode(Yosys::AST::AST_SHIFT_LEFT, ast_node->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 Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN_EQ, new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER),
+						new Yosys::AST::AstNode(Yosys::AST::AST_SHIFT_LEFT, Yosys::AST::AstNode::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 (!(ast_node->children[0]->children.size() == 1 && ast_node->children[1]->isConst())) {
+				assign_data = new Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN_EQ, new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER), ast_node->children[1]->clone());
+				assign_data->children[0]->str = id_data;
+				assign_data->children[0]->was_checked = true;
+			}
+
+			assign_en = new Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN_EQ, new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER), Yosys::AST::AstNode::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);
+
+		Yosys::AST::AstNode *wrnode;
+		if (current_always->type == Yosys::AST::AST_INITIAL)
+			wrnode = new Yosys::AST::AstNode(Yosys::AST::AST_MEMINIT, node_addr, node_data, node_en, ast_node->mkconst_int(1, false));
+		else
+			wrnode = new Yosys::AST::AstNode(Yosys::AST::AST_MEMWR, node_addr, node_data, node_en);
+		wrnode->str = ast_node->children[0]->str;
+		wrnode->id2ast = ast_node->children[0]->id2ast;
+		wrnode->location = ast_node->location;
+		if (wrnode->type == Yosys::AST::AST_MEMWR) {
+			int portid = current_memwr_count[wrnode->str]++;
+			wrnode->children.push_back(ast_node->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(Yosys::AST::AstNode::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 Yosys::AST::AstNode();
+		}
+		goto apply_newNode;
+	}
+
+	// replace function and task calls with the code from the function or task
+	if ((ast_node->type == Yosys::AST::AST_FCALL || ast_node->type == Yosys::AST::AST_TCALL) && !ast_node->str.empty())
+	{
+		if (ast_node->type == Yosys::AST::AST_FCALL)
+		{
+			if (ast_node->str == "\\$initstate")
+			{
+				int myidx = autoidx++;
+
+				Yosys::AST::AstNode *wire = new Yosys::AST::AstNode(Yosys::AST::AST_WIRE);
+				wire->str = stringf("$initstate$%d_wire", myidx);
+				current_ast_mod->children.push_back(wire);
+				while (simplify(wire, true, false, false, 1, -1, false, false)) { }
+
+				Yosys::AST::AstNode *cell = new Yosys::AST::AstNode(Yosys::AST::AST_CELL, new Yosys::AST::AstNode(Yosys::AST::AST_CELLTYPE), new Yosys::AST::AstNode(Yosys::AST::AST_ARGUMENT, new Yosys::AST::AstNode(Yosys::AST::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 (simplify(cell, true, false, false, 1, -1, false, false)) { }
+
+				newNode = new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER);
+				newNode->str = wire->str;
+				newNode->id2ast = wire;
+				goto apply_newNode;
+			}
+
+			if (ast_node->str == "\\$past")
+			{
+				if (width_hint < 0)
+					goto replace_fcall_later;
+
+				int num_steps = 1;
+
+				if (GetSize(ast_node->children) != 1 && GetSize(ast_node->children) != 2)
+					log_file_error(ast_node->filename, ast_node->location.first_line, "System function %s got %d arguments, expected 1 or 2.\n",
+							RTLIL::unescape_id(ast_node->str).c_str(), int(ast_node->children.size()));
+
+				if (!current_always_clocked)
+					log_file_error(ast_node->filename, ast_node->location.first_line, "System function %s is only allowed in clocked blocks.\n",
+							RTLIL::unescape_id(ast_node->str).c_str());
+
+				if (GetSize(ast_node->children) == 2)
+				{
+					Yosys::AST::AstNode *buf = ast_node->children[1]->clone();
+					while (simplify(buf, true, false, false, stage, -1, false, false)) { }
+					if (buf->type != Yosys::AST::AST_CONSTANT)
+						log_file_error(ast_node->filename, ast_node->location.first_line, "Failed to evaluate system function `%s' with non-constant value.\n", ast_node->str.c_str());
+
+					num_steps = buf->asInt(true);
+					delete buf;
+				}
+
+				Yosys::AST::AstNode *block = nullptr;
+
+				for (auto child : current_always->children)
+					if (child->type == Yosys::AST::AST_BLOCK)
+						block = child;
+
+				log_assert(block != nullptr);
+
+				if (num_steps == 0) {
+					newNode = ast_node->children[0]->clone();
+					goto apply_newNode;
+				}
+
+				int myidx = autoidx++;
+				Yosys::AST::AstNode *outreg = nullptr;
+
+				for (int i = 0; i < num_steps; i++)
+				{
+					Yosys::AST::AstNode *reg = new Yosys::AST::AstNode(Yosys::AST::AST_WIRE, new Yosys::AST::AstNode(Yosys::AST::AST_RANGE,
+							ast_node->mkconst_int(width_hint-1, true), ast_node->mkconst_int(0, true)));
+
+					reg->str = stringf("$past$%s:%d$%d$%d", encode_filename(ast_node->filename).c_str(), ast_node->location.first_line, myidx, i);
+					reg->is_reg = true;
+					reg->is_signed = sign_hint;
+
+					current_ast_mod->children.push_back(reg);
+
+					while (simplify(reg, true, false, false, 1, -1, false, false)) { }
+
+					Yosys::AST::AstNode *regid = new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER);
+					regid->str = reg->str;
+					regid->id2ast = reg;
+					regid->was_checked = true;
+
+					Yosys::AST::AstNode *rhs = nullptr;
+
+					if (outreg == nullptr) {
+						rhs = ast_node->children.at(0)->clone();
+					} else {
+						rhs = new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER);
+						rhs->str = outreg->str;
+						rhs->id2ast = outreg;
+					}
+
+					block->children.push_back(new Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN_LE, regid, rhs));
+					outreg = reg;
+				}
+
+				newNode = new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER);
+				newNode->str = outreg->str;
+				newNode->id2ast = outreg;
+				goto apply_newNode;
+			}
+
+			if (ast_node->str == "\\$stable" || ast_node->str == "\\$rose" || ast_node->str == "\\$fell" || ast_node->str == "\\$changed")
+			{
+				if (GetSize(ast_node->children) != 1)
+					log_file_error(ast_node->filename, ast_node->location.first_line, "System function %s got %d arguments, expected 1.\n",
+							RTLIL::unescape_id(ast_node->str).c_str(), int(ast_node->children.size()));
+
+				if (!current_always_clocked)
+					log_file_error(ast_node->filename, ast_node->location.first_line, "System function %s is only allowed in clocked blocks.\n",
+							RTLIL::unescape_id(ast_node->str).c_str());
+
+				Yosys::AST::AstNode *present = ast_node->children.at(0)->clone();
+				Yosys::AST::AstNode *past = ast_node->clone();
+				past->str = "\\$past";
+
+				if (ast_node->str == "\\$stable")
+					newNode = new Yosys::AST::AstNode(Yosys::AST::AST_EQ, past, present);
+
+				else if (ast_node->str == "\\$changed")
+					newNode = new Yosys::AST::AstNode(Yosys::AST::AST_NE, past, present);
+
+				else if (ast_node->str == "\\$rose")
+					newNode = new Yosys::AST::AstNode(Yosys::AST::AST_LOGIC_AND,
+							new Yosys::AST::AstNode(Yosys::AST::AST_LOGIC_NOT, new Yosys::AST::AstNode(Yosys::AST::AST_BIT_AND, past, ast_node->mkconst_int(1,false))),
+							new Yosys::AST::AstNode(Yosys::AST::AST_BIT_AND, present, ast_node->mkconst_int(1,false)));
+
+				else if (ast_node->str == "\\$fell")
+					newNode = new Yosys::AST::AstNode(Yosys::AST::AST_LOGIC_AND,
+							new Yosys::AST::AstNode(Yosys::AST::AST_BIT_AND, past, ast_node->mkconst_int(1,false)),
+							new Yosys::AST::AstNode(Yosys::AST::AST_LOGIC_NOT, new Yosys::AST::AstNode(Yosys::AST::AST_BIT_AND, present, ast_node->mkconst_int(1,false))));
+
+				else
+					log_abort();
+
+				goto apply_newNode;
+			}
+
+			// $anyconst and $anyseq are mapped in genRTLIL()
+			if (ast_node->str == "\\$anyconst" || ast_node->str == "\\$anyseq" || ast_node->str == "\\$allconst" || ast_node->str == "\\$allseq") {
+				recursion_counter--;
+				return false;
+			}
+
+			if (ast_node->str == "\\$clog2")
+			{
+				if (ast_node->children.size() != 1)
+					log_file_error(ast_node->filename, ast_node->location.first_line, "System function %s got %d arguments, expected 1.\n",
+							RTLIL::unescape_id(ast_node->str).c_str(), int(ast_node->children.size()));
+
+				Yosys::AST::AstNode *buf = ast_node->children[0]->clone();
+				while (simplify(buf, true, false, false, stage, width_hint, sign_hint, false)) { }
+				if (buf->type != Yosys::AST::AST_CONSTANT)
+					log_file_error(ast_node->filename, ast_node->location.first_line, "Failed to evaluate system function `%s' with non-constant value.\n", ast_node->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 = ast_node->mkconst_int(result, true);
+				goto apply_newNode;
+			}
+
+			if (ast_node->str == "\\$size" || ast_node->str == "\\$bits" || ast_node->str == "\\$high" || ast_node->str == "\\$low" || ast_node->str == "\\$left" || ast_node->str == "\\$right")
+			{
+				int dim = 1;
+				if (ast_node->str == "\\$bits") {
+					if (ast_node->children.size() != 1)
+						log_file_error(ast_node->filename, ast_node->location.first_line, "System function %s got %d arguments, expected 1.\n",
+								RTLIL::unescape_id(ast_node->str).c_str(), int(ast_node->children.size()));
+				} else {
+					if (ast_node->children.size() != 1 && ast_node->children.size() != 2)
+						log_file_error(ast_node->filename, ast_node->location.first_line, "System function %s got %d arguments, expected 1 or 2.\n",
+							RTLIL::unescape_id(ast_node->str).c_str(), int(ast_node->children.size()));
+					if (ast_node->children.size() == 2) {
+						Yosys::AST::AstNode *buf = ast_node->children[1]->clone();
+						// Evaluate constant expression
+						while (simplify(buf, true, false, false, stage, width_hint, sign_hint, false)) { }
+						dim = buf->asInt(false);
+						delete buf;
+					}
+				}
+				Yosys::AST::AstNode *buf = ast_node->children[0]->clone();
+				int mem_depth = 1;
+				int result, high = 0, low = 0, left = 0, right = 0, width = 1; // defaults for a simple wire
+				Yosys::AST::AstNode *id_ast = NULL;
+
+				// Is this needed?
+				//while (simplify(buf, true, false, false, stage, width_hint, sign_hint, false)) { }
+				buf->detectSignWidth(width_hint, sign_hint);
+
+				if (buf->type == Yosys::AST::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(ast_node->filename, ast_node->location.first_line, "Failed to resolve identifier %s for width detection!\n", buf->str.c_str());
+
+					// Check for item in packed struct / union
+					Yosys::AST::AstNode *item_node = systemverilog_plugin::get_struct_member(buf);
+					if (id_ast->type == Yosys::AST::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(ast_node->filename, ast_node->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(ast_node->filename, ast_node->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 == Yosys::AST::AST_WIRE || id_ast->type == Yosys::AST::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)
+						Yosys::AST::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 == Yosys::AST::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 == Yosys::AST::AST_RANGE && buf->integer == 0)
+								// if integer == 0, ast_node node was originally created as Yosys::AST::AST_RANGE so it's dimension is 1
+								dim++;
+							// more than one range, e.g. $size(a[3][2])
+							else // created an Yosys::AST::AST_MULTIRANGE, converted to Yosys::AST::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
+						Yosys::AST::AstNode *mem_range = id_ast->children[1];
+						if (ast_node->str == "\\$bits") {
+							if (mem_range->type == Yosys::AST::AST_RANGE) {
+								if (!mem_range->range_valid)
+									log_file_error(ast_node->filename, ast_node->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(ast_node->filename, ast_node->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 == Yosys::AST::AST_RANGE) {
+								if (id_ast->multirange_dimensions.empty()) {
+									if (!mem_range->range_valid)
+										log_file_error(ast_node->filename, ast_node->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(ast_node->filename, ast_node->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(ast_node->filename, ast_node->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 (ast_node->str == "\\$high")
+					result = high;
+				else if (ast_node->str == "\\$low")
+					result = low;
+				else if (ast_node->str == "\\$left")
+					result = left;
+				else if (ast_node->str == "\\$right")
+					result = right;
+				else if (ast_node->str == "\\$size")
+					result = width;
+				else { // ast_node->str == "\\$bits"
+					result = width * mem_depth;
+				}
+				newNode = ast_node->mkconst_int(result, true);
+				goto apply_newNode;
+			}
+
+			if (ast_node->str == "\\$ln" || ast_node->str == "\\$log10" || ast_node->str == "\\$exp" || ast_node->str == "\\$sqrt" || ast_node->str == "\\$pow" ||
+					ast_node->str == "\\$floor" || ast_node->str == "\\$ceil" || ast_node->str == "\\$sin" || ast_node->str == "\\$cos" || ast_node->str == "\\$tan" ||
+					ast_node->str == "\\$asin" || ast_node->str == "\\$acos" || ast_node->str == "\\$atan" || ast_node->str == "\\$atan2" || ast_node->str == "\\$hypot" ||
+					ast_node->str == "\\$sinh" || ast_node->str == "\\$cosh" || ast_node->str == "\\$tanh" || ast_node->str == "\\$asinh" || ast_node->str == "\\$acosh" || ast_node->str == "\\$atanh" ||
+					ast_node->str == "\\$rtoi" || ast_node->str == "\\$itor")
+			{
+				bool func_with_two_arguments = ast_node->str == "\\$pow" || ast_node->str == "\\$atan2" || ast_node->str == "\\$hypot";
+				double x = 0, y = 0;
+
+				if (func_with_two_arguments) {
+					if (ast_node->children.size() != 2)
+						log_file_error(ast_node->filename, ast_node->location.first_line, "System function %s got %d arguments, expected 2.\n",
+								RTLIL::unescape_id(ast_node->str).c_str(), int(ast_node->children.size()));
+				} else {
+					if (ast_node->children.size() != 1)
+						log_file_error(ast_node->filename, ast_node->location.first_line, "System function %s got %d arguments, expected 1.\n",
+								RTLIL::unescape_id(ast_node->str).c_str(), int(ast_node->children.size()));
+				}
+
+				if (ast_node->children.size() >= 1) {
+					while (simplify(ast_node->children[0], true, false, false, stage, width_hint, sign_hint, false)) { }
+					if (!ast_node->children[0]->isConst())
+						log_file_error(ast_node->filename, ast_node->location.first_line, "Failed to evaluate system function `%s' with non-constant argument.\n",
+								RTLIL::unescape_id(ast_node->str).c_str());
+					int child_width_hint = width_hint;
+					bool child_sign_hint = sign_hint;
+					ast_node->children[0]->detectSignWidth(child_width_hint, child_sign_hint);
+					x = ast_node->children[0]->asReal(child_sign_hint);
+				}
+
+				if (ast_node->children.size() >= 2) {
+					while (simplify(ast_node->children[1], true, false, false, stage, width_hint, sign_hint, false)) { }
+					if (!ast_node->children[1]->isConst())
+						log_file_error(ast_node->filename, ast_node->location.first_line, "Failed to evaluate system function `%s' with non-constant argument.\n",
+								RTLIL::unescape_id(ast_node->str).c_str());
+					int child_width_hint = width_hint;
+					bool child_sign_hint = sign_hint;
+					ast_node->children[1]->detectSignWidth(child_width_hint, child_sign_hint);
+					y = ast_node->children[1]->asReal(child_sign_hint);
+				}
+
+				if (ast_node->str == "\\$rtoi") {
+					newNode = ast_node->mkconst_int(x, true);
+				} else {
+					newNode = new Yosys::AST::AstNode(Yosys::AST::AST_REALVALUE);
+					if (ast_node->str == "\\$ln")         newNode->realvalue = ::log(x);
+					else if (ast_node->str == "\\$log10") newNode->realvalue = ::log10(x);
+					else if (ast_node->str == "\\$exp")   newNode->realvalue = ::exp(x);
+					else if (ast_node->str == "\\$sqrt")  newNode->realvalue = ::sqrt(x);
+					else if (ast_node->str == "\\$pow")   newNode->realvalue = ::pow(x, y);
+					else if (ast_node->str == "\\$floor") newNode->realvalue = ::floor(x);
+					else if (ast_node->str == "\\$ceil")  newNode->realvalue = ::ceil(x);
+					else if (ast_node->str == "\\$sin")   newNode->realvalue = ::sin(x);
+					else if (ast_node->str == "\\$cos")   newNode->realvalue = ::cos(x);
+					else if (ast_node->str == "\\$tan")   newNode->realvalue = ::tan(x);
+					else if (ast_node->str == "\\$asin")  newNode->realvalue = ::asin(x);
+					else if (ast_node->str == "\\$acos")  newNode->realvalue = ::acos(x);
+					else if (ast_node->str == "\\$atan")  newNode->realvalue = ::atan(x);
+					else if (ast_node->str == "\\$atan2") newNode->realvalue = ::atan2(x, y);
+					else if (ast_node->str == "\\$hypot") newNode->realvalue = ::hypot(x, y);
+					else if (ast_node->str == "\\$sinh")  newNode->realvalue = ::sinh(x);
+					else if (ast_node->str == "\\$cosh")  newNode->realvalue = ::cosh(x);
+					else if (ast_node->str == "\\$tanh")  newNode->realvalue = ::tanh(x);
+					else if (ast_node->str == "\\$asinh") newNode->realvalue = ::asinh(x);
+					else if (ast_node->str == "\\$acosh") newNode->realvalue = ::acosh(x);
+					else if (ast_node->str == "\\$atanh") newNode->realvalue = ::atanh(x);
+					else if (ast_node->str == "\\$itor")  newNode->realvalue = x;
+					else log_abort();
+				}
+				goto apply_newNode;
+			}
+
+			if (ast_node->str == "\\$sformatf") {
+				Yosys::AST::AstNode *node_string = ast_node->children[0];
+				while (simplify(node_string, true, false, false, stage, width_hint, sign_hint, false)) { }
+				if (node_string->type != Yosys::AST::AST_CONSTANT)
+					log_file_error(ast_node->filename, ast_node->location.first_line, "Failed to evaluate system function `%s' with non-constant 1st argument.\n", ast_node->str.c_str());
+				std::string sformat = node_string->bitsAsConst().decode_string();
+				std::string sout = ast_node->process_format_str(sformat, 1, stage, width_hint, sign_hint);
+				newNode = ast_node->mkconst_str(sout);
+				goto apply_newNode;
+			}
+
+			if (ast_node->str == "\\$countbits") {
+				if (ast_node->children.size() < 2)
+					log_file_error(ast_node->filename, ast_node->location.first_line, "System function %s got %d arguments, expected at least 2.\n",
+							RTLIL::unescape_id(ast_node->str).c_str(), int(ast_node->children.size()));
+
+				std::vector<RTLIL::State> control_bits;
+
+				// Determine which bits to count
+				for (size_t i = 1; i < ast_node->children.size(); i++) {
+					Yosys::AST::AstNode *node = ast_node->children[i];
+					while (simplify(node, true, false, false, stage, -1, false, false)) { }
+					if (node->type != Yosys::AST::AST_CONSTANT)
+						log_file_error(ast_node->filename, ast_node->location.first_line, "Failed to evaluate system function `%s' with non-constant control bit argument.\n", ast_node->str.c_str());
+					if (node->bits.size() != 1)
+						log_file_error(ast_node->filename, ast_node->location.first_line, "Failed to evaluate system function `%s' with control bit width != 1.\n", ast_node->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;
+				Yosys::AST::AstNode *exp = ast_node->children[0];
+				exp->detectSignWidth(exp_width, exp_sign, NULL);
+
+				newNode = ast_node->mkconst_int(0, false);
+
+				for (int i = 0; i < exp_width; i++) {
+					// Generate nodes for:  exp << i >> ($size(exp) - 1)
+					//                          ^^   ^^
+					Yosys::AST::AstNode *lsh_node = new Yosys::AST::AstNode(Yosys::AST::AST_SHIFT_LEFT, exp->clone(), Yosys::AST::AstNode::mkconst_int(i, false));
+					Yosys::AST::AstNode *rsh_node = new Yosys::AST::AstNode(Yosys::AST::AST_SHIFT_RIGHT, lsh_node, Yosys::AST::AstNode::mkconst_int(exp_width - 1, false));
+
+					Yosys::AST::AstNode *or_node = nullptr;
+
+					for (RTLIL::State control_bit : control_bits) {
+						// Generate node for:  (exp << i >> ($size(exp) - 1)) === control_bit
+						//                                                    ^^^
+						Yosys::AST::AstNode *eq_node = new Yosys::AST::AstNode(Yosys::AST::AST_EQX, rsh_node->clone(), Yosys::AST::AstNode::mkconst_bits({control_bit}, false));
+
+						// Or the result for each checked bit value
+						if (or_node)
+							or_node = new Yosys::AST::AstNode(Yosys::AST::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 Yosys::AST::AstNode(Yosys::AST::AST_ADD, newNode, or_node);
+				}
+
+				goto apply_newNode;
+			}
+
+			if (ast_node->str == "\\$countones" || ast_node->str == "\\$isunknown" || ast_node->str == "\\$onehot" || ast_node->str == "\\$onehot0") {
+				if (ast_node->children.size() != 1)
+					log_file_error(ast_node->filename, ast_node->location.first_line, "System function %s got %d arguments, expected 1.\n",
+							RTLIL::unescape_id(ast_node->str).c_str(), int(ast_node->children.size()));
+
+				Yosys::AST::AstNode *countbits = ast_node->clone();
+				countbits->str = "\\$countbits";
+
+				if (ast_node->str == "\\$countones") {
+					countbits->children.push_back(Yosys::AST::AstNode::mkconst_bits({RTLIL::State::S1}, false));
+					newNode = countbits;
+				} else if (ast_node->str == "\\$isunknown") {
+					countbits->children.push_back(Yosys::AST::AstNode::mkconst_bits({RTLIL::Sx}, false));
+					countbits->children.push_back(Yosys::AST::AstNode::mkconst_bits({RTLIL::Sz}, false));
+					newNode = new Yosys::AST::AstNode(Yosys::AST::AST_GT, countbits, Yosys::AST::AstNode::mkconst_int(0, false));
+				} else if (ast_node->str == "\\$onehot") {
+					countbits->children.push_back(Yosys::AST::AstNode::mkconst_bits({RTLIL::State::S1}, false));
+					newNode = new Yosys::AST::AstNode(Yosys::AST::AST_EQ, countbits, Yosys::AST::AstNode::mkconst_int(1, false));
+				} else if (ast_node->str == "\\$onehot0") {
+					countbits->children.push_back(Yosys::AST::AstNode::mkconst_bits({RTLIL::State::S1}, false));
+					newNode = new Yosys::AST::AstNode(Yosys::AST::AST_LE, countbits, Yosys::AST::AstNode::mkconst_int(1, false));
+				} else {
+					log_abort();
+				}
+
+				goto apply_newNode;
+			}
+
+			if (current_scope.count(ast_node->str) != 0 && current_scope[ast_node->str]->type == Yosys::AST::AST_DPI_FUNCTION)
+			{
+				Yosys::AST::AstNode *dpi_decl = current_scope[ast_node->str];
+
+				std::string rtype, fname;
+				std::vector<std::string> argtypes;
+				std::vector<Yosys::AST::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(ast_node->children))
+						log_file_error(ast_node->filename, ast_node->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(ast_node->children.at(i-2)->clone());
+					while (simplify(args.back(), true, false, false, stage, -1, false, true)) { }
+
+					if (args.back()->type != Yosys::AST::AST_CONSTANT && args.back()->type != Yosys::AST::AST_REALVALUE)
+						log_file_error(ast_node->filename, ast_node->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(ast_node->str) == 0)
+				ast_node->str = ast_node->try_pop_module_prefix();
+			if (current_scope.count(ast_node->str) == 0 || current_scope[ast_node->str]->type != Yosys::AST::AST_FUNCTION)
+				log_file_error(ast_node->filename, ast_node->location.first_line, "Can't resolve function name `%s'.\n", ast_node->str.c_str());
+		}
+
+		if (ast_node->type == Yosys::AST::AST_TCALL)
+		{
+			if (ast_node->str == "$finish" || ast_node->str == "$stop")
+			{
+				if (!current_always || current_always->type != Yosys::AST::AST_INITIAL)
+					log_file_error(ast_node->filename, ast_node->location.first_line, "System task `%s' outside initial block is unsupported.\n", ast_node->str.c_str());
+
+				log_file_error(ast_node->filename, ast_node->location.first_line, "System task `%s' executed.\n", ast_node->str.c_str());
+			}
+
+			if (ast_node->str == "\\$readmemh" || ast_node->str == "\\$readmemb")
+			{
+				if (GetSize(ast_node->children) < 2 || GetSize(ast_node->children) > 4)
+					log_file_error(ast_node->filename, ast_node->location.first_line, "System function %s got %d arguments, expected 2-4.\n",
+							RTLIL::unescape_id(ast_node->str).c_str(), int(ast_node->children.size()));
+
+				Yosys::AST::AstNode *node_filename = ast_node->children[0]->clone();
+				while (simplify(node_filename, true, false, false, stage, width_hint, sign_hint, false)) { }
+				if (node_filename->type != Yosys::AST::AST_CONSTANT)
+					log_file_error(ast_node->filename, ast_node->location.first_line, "Failed to evaluate system function `%s' with non-constant 1st argument.\n", ast_node->str.c_str());
+
+				Yosys::AST::AstNode *node_memory = ast_node->children[1]->clone();
+				while (simplify(node_memory, true, false, false, stage, width_hint, sign_hint, false)) { }
+				if (node_memory->type != Yosys::AST::AST_IDENTIFIER || node_memory->id2ast == nullptr || node_memory->id2ast->type != Yosys::AST::AST_MEMORY)
+					log_file_error(ast_node->filename, ast_node->location.first_line, "Failed to evaluate system function `%s' with non-memory 2nd argument.\n", ast_node->str.c_str());
+
+				int start_addr = -1, finish_addr = -1;
+
+				if (GetSize(ast_node->children) > 2) {
+					Yosys::AST::AstNode *node_addr = ast_node->children[2]->clone();
+					while (simplify(node_addr, true, false, false, stage, width_hint, sign_hint, false)) { }
+					if (node_addr->type != Yosys::AST::AST_CONSTANT)
+						log_file_error(ast_node->filename, ast_node->location.first_line, "Failed to evaluate system function `%s' with non-constant 3rd argument.\n", ast_node->str.c_str());
+					start_addr = int(node_addr->asInt(false));
+				}
+
+				if (GetSize(ast_node->children) > 3) {
+					Yosys::AST::AstNode *node_addr = ast_node->children[3]->clone();
+					while (simplify(node_addr, true, false, false, stage, width_hint, sign_hint, false)) { }
+					if (node_addr->type != Yosys::AST::AST_CONSTANT)
+						log_file_error(ast_node->filename, ast_node->location.first_line, "Failed to evaluate system function `%s' with non-constant 4th argument.\n", ast_node->str.c_str());
+					finish_addr = int(node_addr->asInt(false));
+				}
+
+				bool unconditional_init = false;
+				if (current_always->type == Yosys::AST::AST_INITIAL) {
+					pool<Yosys::AST::AstNode*> queue;
+					log_assert(current_always->children[0]->type == Yosys::AST::AST_BLOCK);
+					queue.insert(current_always->children[0]);
+					while (!unconditional_init && !queue.empty()) {
+						pool<Yosys::AST::AstNode*> next_queue;
+						for (auto n : queue)
+						for (auto c : n->children) {
+							if (c == ast_node)
+								unconditional_init = true;
+							next_queue.insert(c);
+						}
+						next_queue.swap(queue);
+					}
+				}
+
+				newNode = ast_node->readmem(ast_node->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(ast_node->str) == 0)
+				ast_node->str = ast_node->try_pop_module_prefix();
+			if (current_scope.count(ast_node->str) == 0 || current_scope[ast_node->str]->type != Yosys::AST::AST_TASK)
+				log_file_error(ast_node->filename, ast_node->location.first_line, "Can't resolve task name `%s'.\n", ast_node->str.c_str());
+		}
+
+
+		std::stringstream sstr;
+		sstr << ast_node->str << "$func$" << encode_filename(ast_node->filename) << ":" << ast_node->location.first_line << "$" << (autoidx++) << '.';
+		std::string prefix = sstr.str();
+
+		Yosys::AST::AstNode *decl = current_scope[ast_node->str];
+		if (unevaluated_tern_branch && decl->is_recursive_function())
+			goto replace_fcall_later;
+		decl = decl->clone();
+		decl->replace_result_wire_name_in_function(ast_node->str, "$result"); // enables recursion
+		decl->expand_genblock(prefix);
+
+		if (decl->type == Yosys::AST::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 : ast_node->children) {
+				while (simplify(child, true, false, false, 1, -1, false, true)) { }
+				if (child->type != Yosys::AST::AST_CONSTANT && child->type != Yosys::AST::AST_REALVALUE)
+					all_args_const = false;
+			}
+
+			if (all_args_const) {
+				Yosys::AST::AstNode *func_workspace = decl->clone();
+				func_workspace->str = prefix_id(prefix, "$result");
+				newNode = func_workspace->eval_const_function(ast_node, in_param || require_const_eval);
+				delete func_workspace;
+				if (newNode) {
+					delete decl;
+					goto apply_newNode;
+				}
+			}
+
+			if (in_param)
+				log_file_error(ast_node->filename, ast_node->location.first_line, "Non-constant function call in constant expression.\n");
+			if (require_const_eval)
+				log_file_error(ast_node->filename, ast_node->location.first_line, "Function %s can only be called with constant arguments.\n", ast_node->str.c_str());
+		}
+
+		size_t arg_count = 0;
+		dict<std::string, Yosys::AST::AstNode*> wire_cache;
+		vector<Yosys::AST::AstNode*> new_stmts;
+		vector<Yosys::AST::AstNode*> output_assignments;
+
+		if (current_block == NULL)
+		{
+			log_assert(ast_node->type == Yosys::AST::AST_FCALL);
+
+			Yosys::AST::AstNode *wire = NULL;
+			std::string res_name = prefix_id(prefix, "$result");
+			for (auto child : decl->children)
+				if (child->type == Yosys::AST::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 (simplify(wire, true, false, false, 1, -1, false, false)) { }
+
+			Yosys::AST::AstNode *lvalue = new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER);
+			lvalue->str = wire->str;
+
+			Yosys::AST::AstNode *always = new Yosys::AST::AstNode(Yosys::AST::AST_ALWAYS, new Yosys::AST::AstNode(Yosys::AST::AST_BLOCK,
+					new Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN_EQ, lvalue, ast_node->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 = ast_node->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);
+
+			Yosys::AST::AstNode *cell = new Yosys::AST::AstNode(Yosys::AST::AST_CELL, new Yosys::AST::AstNode(Yosys::AST::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)
+				{
+					Yosys::AST::AstNode *cell_arg = new Yosys::AST::AstNode(Yosys::AST::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 == Yosys::AST::AST_WIRE && (child->is_input || child->is_output || (ast_node->type == Yosys::AST::AST_FCALL && child->str == ast_node->str)))
+				{
+					Yosys::AST::AstNode *wire = child->clone();
+					wire->port_id = 0;
+					wire->is_input = false;
+					wire->is_output = false;
+					current_ast_mod->children.push_back(wire);
+					while (simplify(wire, true, false, false, 1, -1, false, false)) { }
+
+					Yosys::AST::AstNode *wire_id = new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER);
+					wire_id->str = wire->str;
+
+					if ((child->is_input || child->is_output) && arg_count < ast_node->children.size())
+					{
+						Yosys::AST::AstNode *arg = ast_node->children[arg_count++]->clone();
+						Yosys::AST::AstNode *assign = child->is_input ?
+								new Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN_EQ, wire_id->clone(), arg) :
+								new Yosys::AST::AstNode(Yosys::AST::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;
+						}
+					}
+
+					Yosys::AST::AstNode *cell_arg = new Yosys::AST::AstNode(Yosys::AST::AST_ARGUMENT, wire_id);
+					cell_arg->str = child->str == ast_node->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 == Yosys::AST::AST_WIRE || child->type == Yosys::AST::AST_MEMORY || child->type == Yosys::AST::AST_PARAMETER || child->type == Yosys::AST::AST_LOCALPARAM || child->type == Yosys::AST::AST_ENUM_ITEM)
+			{
+				Yosys::AST::AstNode *wire = nullptr;
+
+				if (wire_cache.count(child->str))
+				{
+					wire = wire_cache.at(child->str);
+					bool contains_value = wire->type == Yosys::AST::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 (simplify(child, 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(ast_node->filename, ast_node->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] = Yosys::AST::AstNode::mkconst_int(1, false);
+					if (child->type == Yosys::AST::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 (simplify(wire, true, false, false, 1, -1, false, false)) { }
+
+				if ((child->is_input || child->is_output) && arg_count < ast_node->children.size())
+				{
+					Yosys::AST::AstNode *arg = ast_node->children[arg_count++]->clone();
+					// convert purely constant arguments into localparams
+					if (child->is_input && child->type == Yosys::AST::AST_WIRE && arg->type == Yosys::AST::AST_CONSTANT && node_contains_assignment_to(decl, child)) {
+						wire->type = Yosys::AST::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 != Yosys::AST::AST_RANGE) {
+							// check if this wire is redeclared with an explicit size
+							bool uses_explicit_size = false;
+							for (const Yosys::AST::AstNode *other_child : decl->children)
+								if (other_child->type == Yosys::AST::AST_WIRE && child->str == other_child->str
+										&& !other_child->children.empty()
+										&& other_child->children.back()->type == Yosys::AST::AST_RANGE) {
+									uses_explicit_size = true;
+									break;
+								}
+							if (!uses_explicit_size) {
+								Yosys::AST::AstNode* range = new Yosys::AST::AstNode();
+								range->type = Yosys::AST::AST_RANGE;
+								wire->children.push_back(range);
+								range->children.push_back(Yosys::AST::AstNode::mkconst_int(0, true));
+								range->children.push_back(Yosys::AST::AstNode::mkconst_int(0, true));
+							}
+						}
+						// updates the sizing
+						while (simplify(wire, true, false, false, 1, -1, false, false)) { }
+						delete arg;
+						continue;
+					}
+					Yosys::AST::AstNode *wire_id = new Yosys::AST::AstNode(Yosys::AST::AST_IDENTIFIER);
+					wire_id->str = wire->str;
+					Yosys::AST::AstNode *assign = child->is_input ?
+							new Yosys::AST::AstNode(Yosys::AST::AST_ASSIGN_EQ, wire_id, arg) :
+							new Yosys::AST::AstNode(Yosys::AST::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 != Yosys::AST::AST_WIRE && child->type != Yosys::AST::AST_MEMORY && child->type != Yosys::AST::AST_PARAMETER && child->type != Yosys::AST::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 (ast_node->type == Yosys::AST::AST_FCALL) {
+			ast_node->delete_children();
+			ast_node->type = Yosys::AST::AST_IDENTIFIER;
+			ast_node->str = prefix_id(prefix, "$result");
+		}
+		if (ast_node->type == Yosys::AST::AST_TCALL)
+			ast_node->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 (ast_node->type)
+		{
+		case Yosys::AST::AST_IDENTIFIER:
+			if (current_scope.count(ast_node->str) > 0 && (current_scope[ast_node->str]->type == Yosys::AST::AST_PARAMETER || current_scope[ast_node->str]->type == Yosys::AST::AST_LOCALPARAM || current_scope[ast_node->str]->type == Yosys::AST::AST_ENUM_ITEM)) {
+				if (current_scope[ast_node->str]->children[0]->type == Yosys::AST::AST_CONSTANT) {
+					if (ast_node->children.size() != 0 && ast_node->children[0]->type == Yosys::AST::AST_RANGE && ast_node->children[0]->range_valid) {
+						std::vector<RTLIL::State> data;
+						bool param_upto = current_scope[ast_node->str]->range_valid && current_scope[ast_node->str]->range_swapped;
+						int param_offset = current_scope[ast_node->str]->range_valid ? current_scope[ast_node->str]->range_right : 0;
+						int param_width = current_scope[ast_node->str]->range_valid ? current_scope[ast_node->str]->range_left - current_scope[ast_node->str]->range_right + 1 :
+								GetSize(current_scope[ast_node->str]->children[0]->bits);
+						int tmp_range_left = ast_node->children[0]->range_left, tmp_range_right = ast_node->children[0]->range_right;
+						if (param_upto) {
+							tmp_range_left = (param_width + 2*param_offset) - ast_node->children[0]->range_right - 1;
+							tmp_range_right = (param_width + 2*param_offset) - ast_node->children[0]->range_left - 1;
+						}
+						Yosys::AST::AstNode *member_node = systemverilog_plugin::get_struct_member(ast_node);
+						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[ast_node->str]->children[0]->bits[chunk_offset + index]);
+							else
+								data.push_back(RTLIL::State::Sx);
+						}
+						newNode = Yosys::AST::AstNode::mkconst_bits(data, false);
+					} else
+					if (ast_node->children.size() == 0)
+						newNode = current_scope[ast_node->str]->children[0]->clone();
+				} else
+				if (current_scope[ast_node->str]->children[0]->isConst())
+					newNode = current_scope[ast_node->str]->children[0]->clone();
+			}
+			else if (at_zero && current_scope.count(ast_node->str) > 0) {
+				Yosys::AST::AstNode *node = current_scope[ast_node->str];
+				if (node->type == Yosys::AST::AST_WIRE || node->type == Yosys::AST::AST_AUTOWIRE || node->type == Yosys::AST::AST_MEMORY)
+					newNode = node->mkconst_int(0, sign_hint, width_hint);
+			}
+			break;
+		case Yosys::AST::AST_MEMRD:
+			if (at_zero) {
+				newNode = Yosys::AST::AstNode::mkconst_int(0, sign_hint, width_hint);
+			}
+			break;
+		case Yosys::AST::AST_BIT_NOT:
+			if (ast_node->children[0]->type == Yosys::AST::AST_CONSTANT) {
+				RTLIL::Const y = RTLIL::const_not(ast_node->children[0]->bitsAsConst(width_hint, sign_hint), dummy_arg, sign_hint, false, width_hint);
+				newNode = Yosys::AST::AstNode::mkconst_bits(y.bits, sign_hint);
+			}
+			break;
+		case Yosys::AST::AST_TO_SIGNED:
+		case Yosys::AST::AST_TO_UNSIGNED:
+			if (ast_node->children[0]->type == Yosys::AST::AST_CONSTANT) {
+				RTLIL::Const y = ast_node->children[0]->bitsAsConst(width_hint, sign_hint);
+				newNode = Yosys::AST::AstNode::mkconst_bits(y.bits, ast_node->type == Yosys::AST::AST_TO_SIGNED);
+			}
+			break;
+		if (0) { case Yosys::AST::AST_BIT_AND:  const_func = RTLIL::const_and;  }
+		if (0) { case Yosys::AST::AST_BIT_OR:   const_func = RTLIL::const_or;   }
+		if (0) { case Yosys::AST::AST_BIT_XOR:  const_func = RTLIL::const_xor;  }
+		if (0) { case Yosys::AST::AST_BIT_XNOR: const_func = RTLIL::const_xnor; }
+			if (ast_node->children[0]->type == Yosys::AST::AST_CONSTANT && ast_node->children[1]->type == Yosys::AST::AST_CONSTANT) {
+				RTLIL::Const y = const_func(ast_node->children[0]->bitsAsConst(width_hint, sign_hint),
+						ast_node->children[1]->bitsAsConst(width_hint, sign_hint), sign_hint, sign_hint, width_hint);
+				newNode = Yosys::AST::AstNode::mkconst_bits(y.bits, sign_hint);
+			}
+			break;
+		if (0) { case Yosys::AST::AST_REDUCE_AND:  const_func = RTLIL::const_reduce_and;  }
+		if (0) { case Yosys::AST::AST_REDUCE_OR:   const_func = RTLIL::const_reduce_or;   }
+		if (0) { case Yosys::AST::AST_REDUCE_XOR:  const_func = RTLIL::const_reduce_xor;  }
+		if (0) { case Yosys::AST::AST_REDUCE_XNOR: const_func = RTLIL::const_reduce_xnor; }
+		if (0) { case Yosys::AST::AST_REDUCE_BOOL: const_func = RTLIL::const_reduce_bool; }
+			if (ast_node->children[0]->type == Yosys::AST::AST_CONSTANT) {
+				RTLIL::Const y = const_func(RTLIL::Const(ast_node->children[0]->bits), dummy_arg, false, false, -1);
+				newNode = Yosys::AST::AstNode::mkconst_bits(y.bits, false);
+			}
+			break;
+		case Yosys::AST::AST_LOGIC_NOT:
+			if (ast_node->children[0]->type == Yosys::AST::AST_CONSTANT) {
+				RTLIL::Const y = RTLIL::const_logic_not(RTLIL::Const(ast_node->children[0]->bits), dummy_arg, ast_node->children[0]->is_signed, false, -1);
+				newNode = Yosys::AST::AstNode::mkconst_bits(y.bits, false);
+			} else
+			if (ast_node->children[0]->isConst()) {
+				newNode = Yosys::AST::AstNode::mkconst_int(ast_node->children[0]->asReal(sign_hint) == 0, false, 1);
+			}
+			break;
+		if (0) { case Yosys::AST::AST_LOGIC_AND: const_func = RTLIL::const_logic_and; }
+		if (0) { case Yosys::AST::AST_LOGIC_OR:  const_func = RTLIL::const_logic_or;  }
+			if (ast_node->children[0]->type == Yosys::AST::AST_CONSTANT && ast_node->children[1]->type == Yosys::AST::AST_CONSTANT) {
+				RTLIL::Const y = const_func(RTLIL::Const(ast_node->children[0]->bits), RTLIL::Const(ast_node->children[1]->bits),
+						ast_node->children[0]->is_signed, ast_node->children[1]->is_signed, -1);
+				newNode = Yosys::AST::AstNode::mkconst_bits(y.bits, false);
+			} else
+			if (ast_node->children[0]->isConst() && ast_node->children[1]->isConst()) {
+				if (ast_node->type == Yosys::AST::AST_LOGIC_AND)
+					newNode = Yosys::AST::AstNode::mkconst_int((ast_node->children[0]->asReal(sign_hint) != 0) && (ast_node->children[1]->asReal(sign_hint) != 0), false, 1);
+				else
+					newNode = Yosys::AST::AstNode::mkconst_int((ast_node->children[0]->asReal(sign_hint) != 0) || (ast_node->children[1]->asReal(sign_hint) != 0), false, 1);
+			}
+			break;
+		if (0) { case Yosys::AST::AST_SHIFT_LEFT:   const_func = RTLIL::const_shl;  }
+		if (0) { case Yosys::AST::AST_SHIFT_RIGHT:  const_func = RTLIL::const_shr;  }
+		if (0) { case Yosys::AST::AST_SHIFT_SLEFT:  const_func = RTLIL::const_sshl; }
+		if (0) { case Yosys::AST::AST_SHIFT_SRIGHT: const_func = RTLIL::const_sshr; }
+		if (0) { case Yosys::AST::AST_POW:          const_func = RTLIL::const_pow; }
+			if (ast_node->children[0]->type == Yosys::AST::AST_CONSTANT && ast_node->children[1]->type == Yosys::AST::AST_CONSTANT) {
+				RTLIL::Const y = const_func(ast_node->children[0]->bitsAsConst(width_hint, sign_hint),
+						RTLIL::Const(ast_node->children[1]->bits), sign_hint, ast_node->type == Yosys::AST::AST_POW ? ast_node->children[1]->is_signed : false, width_hint);
+				newNode = Yosys::AST::AstNode::mkconst_bits(y.bits, sign_hint);
+			} else
+			if (ast_node->type == Yosys::AST::AST_POW && ast_node->children[0]->isConst() && ast_node->children[1]->isConst()) {
+				newNode = new Yosys::AST::AstNode(Yosys::AST::AST_REALVALUE);
+				newNode->realvalue = pow(ast_node->children[0]->asReal(sign_hint), ast_node->children[1]->asReal(sign_hint));
+			}
+			break;
+		if (0) { case Yosys::AST::AST_LT:  const_func = RTLIL::const_lt; }
+		if (0) { case Yosys::AST::AST_LE:  const_func = RTLIL::const_le; }
+		if (0) { case Yosys::AST::AST_EQ:  const_func = RTLIL::const_eq; }
+		if (0) { case Yosys::AST::AST_NE:  const_func = RTLIL::const_ne; }
+		if (0) { case Yosys::AST::AST_EQX: const_func = RTLIL::const_eqx; }
+		if (0) { case Yosys::AST::AST_NEX: const_func = RTLIL::const_nex; }
+		if (0) { case Yosys::AST::AST_GE:  const_func = RTLIL::const_ge; }
+		if (0) { case Yosys::AST::AST_GT:  const_func = RTLIL::const_gt; }
+			if (ast_node->children[0]->type == Yosys::AST::AST_CONSTANT && ast_node->children[1]->type == Yosys::AST::AST_CONSTANT) {
+				int cmp_width = max(ast_node->children[0]->bits.size(), ast_node->children[1]->bits.size());
+				bool cmp_signed = ast_node->children[0]->is_signed && ast_node->children[1]->is_signed;
+				RTLIL::Const y = const_func(ast_node->children[0]->bitsAsConst(cmp_width, cmp_signed),
+						ast_node->children[1]->bitsAsConst(cmp_width, cmp_signed), cmp_signed, cmp_signed, 1);
+				newNode = Yosys::AST::AstNode::mkconst_bits(y.bits, false);
+			} else
+			if (ast_node->children[0]->isConst() && ast_node->children[1]->isConst()) {
+				bool cmp_signed = (ast_node->children[0]->type == Yosys::AST::AST_REALVALUE || ast_node->children[0]->is_signed) && (ast_node->children[1]->type == Yosys::AST::AST_REALVALUE || ast_node->children[1]->is_signed);
+				switch (ast_node->type) {
+				case Yosys::AST::AST_LT:  newNode = Yosys::AST::AstNode::mkconst_int(ast_node->children[0]->asReal(cmp_signed) <  ast_node->children[1]->asReal(cmp_signed), false, 1); break;
+				case Yosys::AST::AST_LE:  newNode = Yosys::AST::AstNode::mkconst_int(ast_node->children[0]->asReal(cmp_signed) <= ast_node->children[1]->asReal(cmp_signed), false, 1); break;
+				case Yosys::AST::AST_EQ:  newNode = Yosys::AST::AstNode::mkconst_int(ast_node->children[0]->asReal(cmp_signed) == ast_node->children[1]->asReal(cmp_signed), false, 1); break;
+				case Yosys::AST::AST_NE:  newNode = Yosys::AST::AstNode::mkconst_int(ast_node->children[0]->asReal(cmp_signed) != ast_node->children[1]->asReal(cmp_signed), false, 1); break;
+				case Yosys::AST::AST_EQX: newNode = Yosys::AST::AstNode::mkconst_int(ast_node->children[0]->asReal(cmp_signed) == ast_node->children[1]->asReal(cmp_signed), false, 1); break;
+				case Yosys::AST::AST_NEX: newNode = Yosys::AST::AstNode::mkconst_int(ast_node->children[0]->asReal(cmp_signed) != ast_node->children[1]->asReal(cmp_signed), false, 1); break;
+				case Yosys::AST::AST_GE:  newNode = Yosys::AST::AstNode::mkconst_int(ast_node->children[0]->asReal(cmp_signed) >= ast_node->children[1]->asReal(cmp_signed), false, 1); break;
+				case Yosys::AST::AST_GT:  newNode = Yosys::AST::AstNode::mkconst_int(ast_node->children[0]->asReal(cmp_signed) >  ast_node->children[1]->asReal(cmp_signed), false, 1); break;
+				default: log_abort();
+				}
+			}
+			break;
+		if (0) { case Yosys::AST::AST_ADD: const_func = RTLIL::const_add; }
+		if (0) { case Yosys::AST::AST_SUB: const_func = RTLIL::const_sub; }
+		if (0) { case Yosys::AST::AST_MUL: const_func = RTLIL::const_mul; }
+		if (0) { case Yosys::AST::AST_DIV: const_func = RTLIL::const_div; }
+		if (0) { case Yosys::AST::AST_MOD: const_func = RTLIL::const_mod; }
+			if (ast_node->children[0]->type == Yosys::AST::AST_CONSTANT && ast_node->children[1]->type == Yosys::AST::AST_CONSTANT) {
+				RTLIL::Const y = const_func(ast_node->children[0]->bitsAsConst(width_hint, sign_hint),
+						ast_node->children[1]->bitsAsConst(width_hint, sign_hint), sign_hint, sign_hint, width_hint);
+				newNode = Yosys::AST::AstNode::mkconst_bits(y.bits, sign_hint);
+			} else
+			if (ast_node->children[0]->isConst() && ast_node->children[1]->isConst()) {
+				newNode = new Yosys::AST::AstNode(Yosys::AST::AST_REALVALUE);
+				switch (ast_node->type) {
+				case Yosys::AST::AST_ADD: newNode->realvalue = ast_node->children[0]->asReal(sign_hint) + ast_node->children[1]->asReal(sign_hint); break;
+				case Yosys::AST::AST_SUB: newNode->realvalue = ast_node->children[0]->asReal(sign_hint) - ast_node->children[1]->asReal(sign_hint); break;
+				case Yosys::AST::AST_MUL: newNode->realvalue = ast_node->children[0]->asReal(sign_hint) * ast_node->children[1]->asReal(sign_hint); break;
+				case Yosys::AST::AST_DIV: newNode->realvalue = ast_node->children[0]->asReal(sign_hint) / ast_node->children[1]->asReal(sign_hint); break;
+				case Yosys::AST::AST_MOD: newNode->realvalue = fmod(ast_node->children[0]->asReal(sign_hint), ast_node->children[1]->asReal(sign_hint)); break;
+				default: log_abort();
+				}
+			}
+			break;
+		if (0) { case Yosys::AST::AST_SELFSZ: const_func = RTLIL::const_pos; }
+		if (0) { case Yosys::AST::AST_POS: const_func = RTLIL::const_pos; }
+		if (0) { case Yosys::AST::AST_NEG: const_func = RTLIL::const_neg; }
+			if (ast_node->children[0]->type == Yosys::AST::AST_CONSTANT) {
+				RTLIL::Const y = const_func(ast_node->children[0]->bitsAsConst(width_hint, sign_hint), dummy_arg, sign_hint, false, width_hint);
+				newNode = Yosys::AST::AstNode::mkconst_bits(y.bits, sign_hint);
+			} else
+			if (ast_node->children[0]->isConst()) {
+				newNode = new Yosys::AST::AstNode(Yosys::AST::AST_REALVALUE);
+				if (ast_node->type == Yosys::AST::AST_NEG)
+					newNode->realvalue = -ast_node->children[0]->asReal(sign_hint);
+				else
+					newNode->realvalue = +ast_node->children[0]->asReal(sign_hint);
+			}
+			break;
+		case Yosys::AST::AST_TERNARY:
+			if (ast_node->children[0]->isConst())
+			{
+				auto pair = ast_node->get_tern_choice();
+				Yosys::AST::AstNode *choice = pair.first;
+				Yosys::AST::AstNode *not_choice = pair.second;
+
+				if (choice != NULL) {
+					if (choice->type == Yosys::AST::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 Yosys::AST::AstNode(Yosys::AST::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 = Yosys::AST::AstNode::mkconst_str(y.bits);
+							else
+								newNode = Yosys::AST::AstNode::mkconst_bits(y.bits, sign_hint);
+						}
+					} else
+					if (choice->isConst()) {
+						newNode = choice->clone();
+					}
+				} else if (ast_node->children[1]->type == Yosys::AST::AST_CONSTANT && ast_node->children[2]->type == Yosys::AST::AST_CONSTANT) {
+					RTLIL::Const a = ast_node->children[1]->bitsAsConst(width_hint, sign_hint);
+					RTLIL::Const b = ast_node->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 = Yosys::AST::AstNode::mkconst_bits(a.bits, sign_hint);
+				} else if (ast_node->children[1]->isConst() && ast_node->children[2]->isConst()) {
+					newNode = new Yosys::AST::AstNode(Yosys::AST::AST_REALVALUE);
+					if (ast_node->children[1]->asReal(sign_hint) == ast_node->children[2]->asReal(sign_hint))
+						newNode->realvalue = ast_node->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 Yosys::AST::AST_CAST_SIZE:
+			if (ast_node->children.at(0)->type == Yosys::AST::AST_CONSTANT && ast_node->children.at(1)->type == Yosys::AST::AST_CONSTANT) {
+				int width = ast_node->children[0]->bitsAsConst().as_int();
+				RTLIL::Const val;
+				if (ast_node->children[1]->is_unsized)
+					val = ast_node->children[1]->bitsAsUnsizedConst(width);
+				else
+					val = ast_node->children[1]->bitsAsConst(width);
+				newNode = Yosys::AST::AstNode::mkconst_bits(val.bits, ast_node->children[1]->is_signed);
+			}
+			break;
+		case Yosys::AST::AST_CONCAT:
+			string_op = !ast_node->children.empty();
+			for (auto it = ast_node->children.begin(); it != ast_node->children.end(); it++) {
+				if ((*it)->type != Yosys::AST::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 ? Yosys::AST::AstNode::mkconst_str(tmp_bits) : Yosys::AST::AstNode::mkconst_bits(tmp_bits, false);
+			break;
+		case Yosys::AST::AST_REPLICATE:
+			if (ast_node->children.at(0)->type != Yosys::AST::AST_CONSTANT || ast_node->children.at(1)->type != Yosys::AST::AST_CONSTANT)
+				goto not_const;
+			for (int i = 0; i < ast_node->children[0]->bitsAsConst().as_int(); i++)
+				tmp_bits.insert(tmp_bits.end(), ast_node->children.at(1)->bits.begin(), ast_node->children.at(1)->bits.end());
+			newNode = ast_node->children.at(1)->is_string ? Yosys::AST::AstNode::mkconst_str(tmp_bits) : Yosys::AST::AstNode::mkconst_bits(tmp_bits, false);
+			break;
+		default:
+		not_const:
+			break;
+		}
+	}
+
+	// if any of the above set 'newNode' -> use 'newNode' as template to update 'ast_node'
+	if (newNode) {
+apply_newNode:
+		// fprintf(stderr, "----\n");
+		// dumpAst(stderr, "- ");
+		// newNode->dumpAst(stderr, "+ ");
+		log_assert(newNode != NULL);
+		newNode->filename = ast_node->filename;
+		newNode->location = ast_node->location;
+		newNode->cloneInto(ast_node);
+		delete newNode;
+		did_something = true;
+	}
+
+	if (!did_something)
+		ast_node->basic_prep = true;
+
+	recursion_counter--;
+	return did_something;
+}
+
+} // namespace systemverilog_plugin
diff --git a/systemverilog-plugin/third_party/yosys/simplify.h b/systemverilog-plugin/third_party/yosys/simplify.h
new file mode 100644
index 0000000..5620ab9
--- /dev/null
+++ b/systemverilog-plugin/third_party/yosys/simplify.h
@@ -0,0 +1,6 @@
+#include "frontends/ast/ast.h"
+
+namespace systemverilog_plugin
+{
+    bool simplify(Yosys::AST::AstNode *ast_node, bool const_fold, bool at_zero, bool in_lvalue, int stage, int width_hint, bool sign_hint, bool in_param);
+}
diff --git a/systemverilog-plugin/uhdmastshared.h b/systemverilog-plugin/uhdmastshared.h
index 50c79b0..4bbc447 100644
--- a/systemverilog-plugin/uhdmastshared.h
+++ b/systemverilog-plugin/uhdmastshared.h
@@ -1,6 +1,8 @@
 #ifndef _UHDM_AST_SHARED_H_
 #define _UHDM_AST_SHARED_H_ 1
 
+#include "frontends/ast/ast.h"
+
 #include "uhdmastreport.h"
 #include <string>
 #include <unordered_map>
diff --git a/systemverilog-plugin/uhdmsurelogastfrontend.cc b/systemverilog-plugin/uhdmsurelogastfrontend.cc
index a0e71a6..1361184 100644
--- a/systemverilog-plugin/uhdmsurelogastfrontend.cc
+++ b/systemverilog-plugin/uhdmsurelogastfrontend.cc
@@ -33,8 +33,10 @@
 
 #include <list>
 
-#include "Surelog/ErrorReporting/Report.h"
-#include "Surelog/surelog.h"
+#include "Surelog/API/Surelog.h"
+#include "Surelog/CommandLine/CommandLineParser.h"
+#include "Surelog/ErrorReporting/ErrorContainer.h"
+#include "Surelog/SourceCompile/SymbolTable.h"
 
 namespace UHDM
 {