diff --git a/.github/workflows/test-linux.yml b/.github/workflows/test-linux.yml index eee556794af..a16481726d3 100644 --- a/.github/workflows/test-linux.yml +++ b/.github/workflows/test-linux.yml @@ -43,6 +43,7 @@ jobs: sudo apt-get install $CC $CXX echo "CC=$CC" >> $GITHUB_ENV echo "CXX=$CXX" >> $GITHUB_ENV + echo "CXXFLAGS=-Wp,-D_GLIBCXX_ASSERTIONS" >> $GITHUB_ENV env: CC: ${{ matrix.compiler }} diff --git a/.gitignore b/.gitignore index e20c6981c68..a71cd2de74c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ *.gch *.gcda *.gcno +*~ __pycache__ /.cproject /.project diff --git a/CHANGELOG b/CHANGELOG index 027187b6d9f..9bf1d9e2b1d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,8 +2,60 @@ List of major changes and improvements between releases ======================================================= -Yosys 0.32 .. Yosys 0.33-dev +Yosys 0.35 .. Yosys 0.36-dev -------------------------- +* New commands and options + - Added option "--" to pass arguments down to tcl when using -c option. + - Added ability on MacOS and Windows to pass options after arguments on cli. + + +Yosys 0.34 .. Yosys 0.35 +-------------------------- + * Various + - Improvements on "peepopt" shiftmul matcher. + - Improvements on "ram_style" attributes handling. + + * Verific support + - Improved static elaboration for VHDL and mixed HDL designs. + - Expose "hdlname" attribute with original module name. + - Expose "architecture" attribute with VHDL architecture name. + +Yosys 0.33 .. Yosys 0.34 +-------------------------- + * New commands and options + - Added option "-assert" to "sim" pass. + - Added option "-noinitstate" to "sim" pass. + - Added option "-dont_use" to "abc" pass. + - Added "dft_tag" pass to create tagging logic for data flow tracking. + - Added "future" pass to resolve future sampled value functions. + - Added "booth" pass to map $mul cells to Booth multipliers. + - Added option "-booth" to "synth" pass. + + * SystemVerilog + - Added support for assignments within expressions, e.g., `x[y++] = z;` or + `x = (y *= 2) - 1;`. + + * Verific support + - "src" attribute contain full location info. + - module parameters are kept after import. + - accurate access order semantics in memory inference. + - better "bind" support for mixed language projects. + + * Various + - "show" command displays dot instead of box for wire aliases. + +Yosys 0.32 .. Yosys 0.33 +-------------------------- + * Various + - Added "$print" cell, produced by "$display" and "$write" + Verilog tasks. + - Added "$print" cell handling in CXXRTL. + + * Lattice FPGA support + - Added generic "synth_lattice" pass (for now MachXO2/XO3/XO3D) + - Removed "synth_machxo2" pass + - Pass "ecp5_gsr" renamed to "lattice_gsr" + - "synth_machxo2" equivalent is "synth_lattice -family xo2" Yosys 0.31 .. Yosys 0.32 -------------------------- diff --git a/Makefile b/Makefile index ac12edd9758..90e2038171e 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LDLIBS += -lrt endif endif -YOSYS_VER := 0.32+74 +YOSYS_VER := 0.35+29 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo @@ -157,7 +157,7 @@ endif OBJS = kernel/version_$(GIT_REV).o bumpversion: - sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline fbab08a.. | wc -l`/;" Makefile + sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline cc31c6e.. | wc -l`/;" Makefile # set 'ABCREV = default' to use abc/ as it is # @@ -165,7 +165,7 @@ bumpversion: # is just a symlink to your actual ABC working directory, as 'make mrproper' # will remove the 'abc' directory and you do not want to accidentally # delete your work on ABC.. -ABCREV = bb64142 +ABCREV = 896e5e7 ABCPULL = 1 ABCURL ?= https://github.com/YosysHQ/abc ABCMKARGS = CC="$(CXX)" CXX="$(CXX)" ABC_USE_LIBSTDCXX=1 ABC_USE_NAMESPACE=abc VERBOSE=$(Q) @@ -321,11 +321,11 @@ AR = $(WASI_SDK)/bin/ar RANLIB = $(WASI_SDK)/bin/ranlib WASIFLAGS := --sysroot $(WASI_SDK)/share/wasi-sysroot $(WASIFLAGS) endif -CXXFLAGS := $(WASIFLAGS) -std=$(CXXSTD) -Os $(filter-out -fPIC,$(CXXFLAGS)) +CXXFLAGS := $(WASIFLAGS) -std=$(CXXSTD) -Os -D_WASI_EMULATED_PROCESS_CLOCKS $(filter-out -fPIC,$(CXXFLAGS)) LDFLAGS := $(WASIFLAGS) -Wl,-z,stack-size=1048576 $(filter-out -rdynamic,$(LDFLAGS)) -LDLIBS := $(filter-out -lrt,$(LDLIBS)) +LDLIBS := -lwasi-emulated-process-clocks $(filter-out -lrt,$(LDLIBS)) ABCMKARGS += AR="$(AR)" RANLIB="$(RANLIB)" -ABCMKARGS += ARCHFLAGS="$(WASIFLAGS) -DABC_USE_STDINT_H -DABC_NO_DYNAMIC_LINKING -DABC_NO_RLIMIT -Wno-c++11-narrowing" +ABCMKARGS += ARCHFLAGS="$(WASIFLAGS) -D_WASI_EMULATED_PROCESS_CLOCKS -DABC_USE_STDINT_H -DABC_NO_DYNAMIC_LINKING -DABC_NO_RLIMIT -Wno-c++11-narrowing" ABCMKARGS += OPTFLAGS="-Os" EXE = .wasm @@ -357,7 +357,7 @@ CXXFLAGS := $(filter-out -fPIC,$(CXXFLAGS)) LDFLAGS := $(filter-out -rdynamic,$(LDFLAGS)) -s LDLIBS := $(filter-out -lrt,$(LDLIBS)) ABCMKARGS += ARCHFLAGS="-DABC_USE_STDINT_H -DWIN32_NO_DLL -DHAVE_STRUCT_TIMESPEC -fpermissive -w" -ABCMKARGS += LIBS="-lpthread -s" ABC_USE_NO_READLINE=0 CC="i686-w64-mingw32-gcc" CXX="$(CXX)" +ABCMKARGS += LIBS="-lpthread -lshlwapi -s" ABC_USE_NO_READLINE=0 CC="i686-w64-mingw32-gcc" CXX="$(CXX)" EXE = .exe else ifeq ($(CONFIG),msys2-64) @@ -368,7 +368,7 @@ CXXFLAGS := $(filter-out -fPIC,$(CXXFLAGS)) LDFLAGS := $(filter-out -rdynamic,$(LDFLAGS)) -s LDLIBS := $(filter-out -lrt,$(LDLIBS)) ABCMKARGS += ARCHFLAGS="-DABC_USE_STDINT_H -DWIN32_NO_DLL -DHAVE_STRUCT_TIMESPEC -fpermissive -w" -ABCMKARGS += LIBS="-lpthread -s" ABC_USE_NO_READLINE=0 CC="x86_64-w64-mingw32-gcc" CXX="$(CXX)" +ABCMKARGS += LIBS="-lpthread -lshlwapi -s" ABC_USE_NO_READLINE=0 CC="x86_64-w64-mingw32-gcc" CXX="$(CXX)" EXE = .exe else ifneq ($(CONFIG),none) @@ -606,31 +606,35 @@ Q = S = endif -$(eval $(call add_include_file,kernel/yosys.h)) -$(eval $(call add_include_file,kernel/hashlib.h)) -$(eval $(call add_include_file,kernel/log.h)) -$(eval $(call add_include_file,kernel/rtlil.h)) $(eval $(call add_include_file,kernel/binding.h)) -$(eval $(call add_include_file,kernel/register.h)) $(eval $(call add_include_file,kernel/cellaigs.h)) -$(eval $(call add_include_file,kernel/celltypes.h)) $(eval $(call add_include_file,kernel/celledges.h)) +$(eval $(call add_include_file,kernel/celltypes.h)) $(eval $(call add_include_file,kernel/consteval.h)) $(eval $(call add_include_file,kernel/constids.inc)) -$(eval $(call add_include_file,kernel/sigtools.h)) -$(eval $(call add_include_file,kernel/modtools.h)) -$(eval $(call add_include_file,kernel/macc.h)) -$(eval $(call add_include_file,kernel/utils.h)) -$(eval $(call add_include_file,kernel/satgen.h)) -$(eval $(call add_include_file,kernel/qcsat.h)) +$(eval $(call add_include_file,kernel/cost.h)) $(eval $(call add_include_file,kernel/ff.h)) $(eval $(call add_include_file,kernel/ffinit.h)) +$(eval $(call add_include_file,kernel/ffmerge.h)) +$(eval $(call add_include_file,kernel/fmt.h)) ifeq ($(ENABLE_ZLIB),1) $(eval $(call add_include_file,kernel/fstdata.h)) endif +$(eval $(call add_include_file,kernel/hashlib.h)) +$(eval $(call add_include_file,kernel/json.h)) +$(eval $(call add_include_file,kernel/log.h)) +$(eval $(call add_include_file,kernel/macc.h)) +$(eval $(call add_include_file,kernel/modtools.h)) $(eval $(call add_include_file,kernel/mem.h)) +$(eval $(call add_include_file,kernel/qcsat.h)) +$(eval $(call add_include_file,kernel/register.h)) +$(eval $(call add_include_file,kernel/rtlil.h)) +$(eval $(call add_include_file,kernel/satgen.h)) +$(eval $(call add_include_file,kernel/sigtools.h)) +$(eval $(call add_include_file,kernel/timinginfo.h)) +$(eval $(call add_include_file,kernel/utils.h)) +$(eval $(call add_include_file,kernel/yosys.h)) $(eval $(call add_include_file,kernel/yw.h)) -$(eval $(call add_include_file,kernel/json.h)) $(eval $(call add_include_file,libs/ezsat/ezsat.h)) $(eval $(call add_include_file,libs/ezsat/ezminisat.h)) ifeq ($(ENABLE_ZLIB),1) diff --git a/README.md b/README.md index 5e5a8ec3e12..69a227b7fb5 100644 --- a/README.md +++ b/README.md @@ -592,6 +592,8 @@ from SystemVerilog: - SystemVerilog interfaces (SVIs) are supported. Modports for specifying whether ports are inputs or outputs are supported. +- Assignments within expressions are supported. + Building the documentation ========================== diff --git a/backends/cxxrtl/cxxrtl.h b/backends/cxxrtl/cxxrtl.h index 8f5e4035a34..b2871ee19df 100644 --- a/backends/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/cxxrtl.h @@ -1019,14 +1019,14 @@ struct metadata { // In debug mode, using the wrong .as_*() function will assert. // In release mode, using the wrong .as_*() function will safely return a default value. - const unsigned uint_value = 0; - const signed sint_value = 0; + const uint64_t uint_value = 0; + const int64_t sint_value = 0; const std::string string_value = ""; const double double_value = 0.0; metadata() : value_type(MISSING) {} - metadata(unsigned value) : value_type(UINT), uint_value(value) {} - metadata(signed value) : value_type(SINT), sint_value(value) {} + metadata(uint64_t value) : value_type(UINT), uint_value(value) {} + metadata(int64_t value) : value_type(SINT), sint_value(value) {} metadata(const std::string &value) : value_type(STRING), string_value(value) {} metadata(const char *value) : value_type(STRING), string_value(value) {} metadata(double value) : value_type(DOUBLE), double_value(value) {} @@ -1034,12 +1034,12 @@ struct metadata { metadata(const metadata &) = default; metadata &operator=(const metadata &) = delete; - unsigned as_uint() const { + uint64_t as_uint() const { assert(value_type == UINT); return uint_value; } - signed as_sint() const { + int64_t as_sint() const { assert(value_type == SINT); return sint_value; } @@ -1068,6 +1068,9 @@ using debug_outline = ::_cxxrtl_outline; // // To avoid violating strict aliasing rules, this structure has to be a subclass of the one used // in the C API, or it would not be possible to cast between the pointers to these. +// +// The `attrs` member cannot be owned by this structure because a `cxxrtl_object` can be created +// from external C code. struct debug_item : ::cxxrtl_object { // Object types. enum : uint32_t { @@ -1103,6 +1106,7 @@ struct debug_item : ::cxxrtl_object { curr = item.data; next = item.data; outline = nullptr; + attrs = nullptr; } template @@ -1118,6 +1122,7 @@ struct debug_item : ::cxxrtl_object { curr = const_cast(item.data); next = nullptr; outline = nullptr; + attrs = nullptr; } template @@ -1134,6 +1139,7 @@ struct debug_item : ::cxxrtl_object { curr = item.curr.data; next = item.next.data; outline = nullptr; + attrs = nullptr; } template @@ -1149,6 +1155,7 @@ struct debug_item : ::cxxrtl_object { curr = item.data ? item.data[0].data : nullptr; next = nullptr; outline = nullptr; + attrs = nullptr; } template @@ -1164,6 +1171,7 @@ struct debug_item : ::cxxrtl_object { curr = const_cast(item.data); next = nullptr; outline = nullptr; + attrs = nullptr; } template @@ -1180,6 +1188,7 @@ struct debug_item : ::cxxrtl_object { curr = const_cast(item.curr.data); next = nullptr; outline = nullptr; + attrs = nullptr; } template @@ -1195,6 +1204,7 @@ struct debug_item : ::cxxrtl_object { curr = const_cast(item.data); next = nullptr; outline = &group; + attrs = nullptr; } template @@ -1215,10 +1225,28 @@ struct debug_item : ::cxxrtl_object { }; static_assert(std::is_standard_layout::value, "debug_item is not compatible with C layout"); +} // namespace cxxrtl + +typedef struct _cxxrtl_attr_set { + cxxrtl::metadata_map map; +} *cxxrtl_attr_set; + +namespace cxxrtl { + +// Representation of an attribute set in the C++ interface. +using debug_attrs = ::_cxxrtl_attr_set; + struct debug_items { std::map> table; - - void add(const std::string &name, debug_item &&item) { + std::map> attrs_table; + + void add(const std::string &name, debug_item &&item, metadata_map &&item_attrs = {}) { + std::unique_ptr &attrs = attrs_table[name]; + if (attrs.get() == nullptr) + attrs = std::unique_ptr(new debug_attrs); + for (auto attr : item_attrs) + attrs->map.insert(attr); + item.attrs = attrs.get(); std::vector &parts = table[name]; parts.emplace_back(item); std::sort(parts.begin(), parts.end(), @@ -1246,6 +1274,10 @@ struct debug_items { const debug_item &operator [](const std::string &name) const { return at(name); } + + const metadata_map &attrs(const std::string &name) const { + return attrs_table.at(name)->map; + } }; // Tag class to disambiguate the default constructor used by the toplevel module that calls reset(), diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index 7696ae5747c..3fd4857bd51 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -2120,6 +2120,46 @@ struct CxxrtlWorker { dec_indent(); } + void dump_metadata_map(const dict &metadata_map) + { + if (metadata_map.empty()) { + f << "metadata_map()"; + return; + } + f << "metadata_map({\n"; + inc_indent(); + for (auto metadata_item : metadata_map) { + if (!metadata_item.first.isPublic()) + continue; + if (metadata_item.second.size() > 64 && (metadata_item.second.flags & RTLIL::CONST_FLAG_STRING) == 0) { + f << indent << "/* attribute " << metadata_item.first.str().substr(1) << " is over 64 bits wide */"; + continue; + } + f << indent << "{ " << escape_cxx_string(metadata_item.first.str().substr(1)) << ", "; + // In Yosys, a real is a type of string. + if (metadata_item.second.flags & RTLIL::CONST_FLAG_REAL) { + f << std::showpoint << std::stod(metadata_item.second.decode_string()) << std::noshowpoint; + } else if (metadata_item.second.flags & RTLIL::CONST_FLAG_STRING) { + f << escape_cxx_string(metadata_item.second.decode_string()); + } else if (metadata_item.second.flags & RTLIL::CONST_FLAG_SIGNED) { + f << "INT64_C(" << metadata_item.second.as_int(/*is_signed=*/true) << ")"; + } else { + f << "UINT64_C(" << metadata_item.second.as_int(/*is_signed=*/false) << ")"; + } + f << " },\n"; + } + dec_indent(); + f << indent << "})"; + } + + void dump_debug_attrs(const RTLIL::AttrObject *object) + { + dict attributes = object->attributes; + // Inherently necessary to get access to the object, so a waste of space to emit. + attributes.erase(ID::hdlname); + dump_metadata_map(attributes); + } + void dump_debug_info_method(RTLIL::Module *module) { size_t count_public_wires = 0; @@ -2205,7 +2245,9 @@ struct CxxrtlWorker { } f << "debug_item::" << flag; } - f << "));\n"; + f << "), "; + dump_debug_attrs(wire); + f << ");\n"; count_member_wires++; break; } @@ -2220,7 +2262,9 @@ struct CxxrtlWorker { f << "debug_eval_outline"; else f << "debug_alias()"; - f << ", " << mangle(aliasee) << ", " << wire->start_offset << "));\n"; + f << ", " << mangle(aliasee) << ", " << wire->start_offset << "), "; + dump_debug_attrs(aliasee); + f << ");\n"; count_alias_wires++; break; } @@ -2230,14 +2274,18 @@ struct CxxrtlWorker { dump_const(debug_wire_type.sig_subst.as_const()); f << ";\n"; f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire)); - f << ", debug_item(const_" << mangle(wire) << ", " << wire->start_offset << "));\n"; + f << ", debug_item(const_" << mangle(wire) << ", " << wire->start_offset << "), "; + dump_debug_attrs(wire); + f << ");\n"; count_const_wires++; break; } case WireType::OUTLINE: { // Localized or inlined, but rematerializable wire f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire)); - f << ", debug_item(debug_eval_outline, " << mangle(wire) << ", " << wire->start_offset << "));\n"; + f << ", debug_item(debug_eval_outline, " << mangle(wire) << ", " << wire->start_offset << "), "; + dump_debug_attrs(wire); + f << ");\n"; count_inline_wires++; break; } @@ -2254,7 +2302,13 @@ struct CxxrtlWorker { continue; f << indent << "items.add(path + " << escape_cxx_string(mem.packed ? get_hdl_name(mem.cell) : get_hdl_name(mem.mem)); f << ", debug_item(" << mangle(&mem) << ", "; - f << mem.start_offset << "));\n"; + f << mem.start_offset << "), "; + if (mem.packed) { + dump_debug_attrs(mem.cell); + } else { + dump_debug_attrs(mem.mem); + } + f << ");\n"; } for (auto cell : module->cells()) { if (is_internal_cell(cell->type)) @@ -2282,33 +2336,6 @@ struct CxxrtlWorker { } } - void dump_metadata_map(const dict &metadata_map) - { - if (metadata_map.empty()) { - f << "metadata_map()"; - return; - } - f << "metadata_map({\n"; - inc_indent(); - for (auto metadata_item : metadata_map) { - if (!metadata_item.first.begins_with("\\")) - continue; - f << indent << "{ " << escape_cxx_string(metadata_item.first.str().substr(1)) << ", "; - if (metadata_item.second.flags & RTLIL::CONST_FLAG_REAL) { - f << std::showpoint << std::stod(metadata_item.second.decode_string()) << std::noshowpoint; - } else if (metadata_item.second.flags & RTLIL::CONST_FLAG_STRING) { - f << escape_cxx_string(metadata_item.second.decode_string()); - } else { - f << metadata_item.second.as_int(/*is_signed=*/metadata_item.second.flags & RTLIL::CONST_FLAG_SIGNED); - if (!(metadata_item.second.flags & RTLIL::CONST_FLAG_SIGNED)) - f << "u"; - } - f << " },\n"; - } - dec_indent(); - f << indent << "})"; - } - void dump_module_intf(RTLIL::Module *module) { dump_attrs(module); diff --git a/backends/cxxrtl/cxxrtl_capi.cc b/backends/cxxrtl/cxxrtl_capi.cc index 227173ba87f..d50ddcd6941 100644 --- a/backends/cxxrtl/cxxrtl_capi.cc +++ b/backends/cxxrtl/cxxrtl_capi.cc @@ -90,3 +90,46 @@ void cxxrtl_enum(cxxrtl_handle handle, void *data, void cxxrtl_outline_eval(cxxrtl_outline outline) { outline->eval(); } + +int cxxrtl_attr_type(cxxrtl_attr_set attrs_, const char *name) { + auto attrs = (cxxrtl::metadata_map*)attrs_; + if (!attrs->count(name)) + return CXXRTL_ATTR_NONE; + switch (attrs->at(name).value_type) { + case cxxrtl::metadata::UINT: + return CXXRTL_ATTR_UNSIGNED_INT; + case cxxrtl::metadata::SINT: + return CXXRTL_ATTR_SIGNED_INT; + case cxxrtl::metadata::STRING: + return CXXRTL_ATTR_STRING; + case cxxrtl::metadata::DOUBLE: + return CXXRTL_ATTR_DOUBLE; + default: + // Present unsupported attribute type the same way as no attribute at all. + return CXXRTL_ATTR_NONE; + } +} + +uint64_t cxxrtl_attr_get_unsigned_int(cxxrtl_attr_set attrs_, const char *name) { + auto &attrs = *(cxxrtl::metadata_map*)attrs_; + assert(attrs.count(name) && attrs.at(name).value_type == cxxrtl::metadata::UINT); + return attrs[name].as_uint(); +} + +int64_t cxxrtl_attr_get_signed_int(cxxrtl_attr_set attrs_, const char *name) { + auto &attrs = *(cxxrtl::metadata_map*)attrs_; + assert(attrs.count(name) && attrs.at(name).value_type == cxxrtl::metadata::SINT); + return attrs[name].as_sint(); +} + +const char *cxxrtl_attr_get_string(cxxrtl_attr_set attrs_, const char *name) { + auto &attrs = *(cxxrtl::metadata_map*)attrs_; + assert(attrs.count(name) && attrs.at(name).value_type == cxxrtl::metadata::STRING); + return attrs[name].as_string().c_str(); +} + +double cxxrtl_attr_get_double(cxxrtl_attr_set attrs_, const char *name) { + auto &attrs = *(cxxrtl::metadata_map*)attrs_; + assert(attrs.count(name) && attrs.at(name).value_type == cxxrtl::metadata::DOUBLE); + return attrs[name].as_double(); +} diff --git a/backends/cxxrtl/cxxrtl_capi.h b/backends/cxxrtl/cxxrtl_capi.h index 2df2b7287f4..e5c84bf656a 100644 --- a/backends/cxxrtl/cxxrtl_capi.h +++ b/backends/cxxrtl/cxxrtl_capi.h @@ -249,6 +249,15 @@ struct cxxrtl_object { // this field to NULL. struct _cxxrtl_outline *outline; + // Opaque reference to an attribute set. + // + // See the documentation of `cxxrtl_attr_set` for details. When creating a `cxxrtl_object`, set + // this field to NULL. + // + // The lifetime of the pointers returned by `cxxrtl_attr_*` family of functions is the same as + // the lifetime of this structure. + struct _cxxrtl_attr_set *attrs; + // More description fields may be added in the future, but the existing ones will never change. }; @@ -304,6 +313,62 @@ typedef struct _cxxrtl_outline *cxxrtl_outline; // re-evaluated, otherwise the bits read from that object are meaningless. void cxxrtl_outline_eval(cxxrtl_outline outline); +// Opaque reference to an attribute set. +// +// An attribute set is a map between attribute names (always strings) and values (which may have +// several different types). To find out the type of an attribute, use `cxxrtl_attr_type`, and +// to retrieve the value of an attribute, use `cxxrtl_attr_as_string`. +typedef struct _cxxrtl_attr_set *cxxrtl_attr_set; + +// Type of an attribute. +enum cxxrtl_attr_type { + // Attribute is not present. + CXXRTL_ATTR_NONE = 0, + + // Attribute has an unsigned integer value. + CXXRTL_ATTR_UNSIGNED_INT = 1, + + // Attribute has an unsigned integer value. + CXXRTL_ATTR_SIGNED_INT = 2, + + // Attribute has a string value. + CXXRTL_ATTR_STRING = 3, + + // Attribute has a double precision floating point value. + CXXRTL_ATTR_DOUBLE = 4, + + // More attribute types may be defined in the future, but the existing values will never change. +}; + +// Determine the presence and type of an attribute in an attribute set. +// +// This function returns one of the possible `cxxrtl_attr_type` values. +int cxxrtl_attr_type(cxxrtl_attr_set attrs, const char *name); + +// Retrieve an unsigned integer valued attribute from an attribute set. +// +// This function asserts that `cxxrtl_attr_type(attrs, name) == CXXRTL_ATTR_UNSIGNED_INT`. +// If assertions are disabled, returns 0 if the attribute is missing or has an incorrect type. +uint64_t cxxrtl_attr_get_unsigned_int(cxxrtl_attr_set attrs, const char *name); + +// Retrieve a signed integer valued attribute from an attribute set. +// +// This function asserts that `cxxrtl_attr_type(attrs, name) == CXXRTL_ATTR_SIGNED_INT`. +// If assertions are disabled, returns 0 if the attribute is missing or has an incorrect type. +int64_t cxxrtl_attr_get_signed_int(cxxrtl_attr_set attrs, const char *name); + +// Retrieve a string valued attribute from an attribute set. The returned string is zero-terminated. +// +// This function asserts that `cxxrtl_attr_type(attrs, name) == CXXRTL_ATTR_STRING`. If assertions +// are disabled, returns NULL if the attribute is missing or has an incorrect type. +const char *cxxrtl_attr_get_string(cxxrtl_attr_set attrs, const char *name); + +// Retrieve a double precision floating point valued attribute from an attribute set. +// +// This function asserts that `cxxrtl_attr_type(attrs, name) == CXXRTL_ATTR_DOUBLE`. If assertions +// are disabled, returns NULL if the attribute is missing or has an incorrect type. +double cxxrtl_attr_get_double(cxxrtl_attr_set attrs, const char *name); + #ifdef __cplusplus } #endif diff --git a/backends/verilog/verilog_backend.cc b/backends/verilog/verilog_backend.cc index 7099c18c349..735672a43b4 100644 --- a/backends/verilog/verilog_backend.cc +++ b/backends/verilog/verilog_backend.cc @@ -2008,6 +2008,11 @@ void dump_proc_switch(std::ostream &f, std::string indent, RTLIL::SwitchRule *sw dump_case_body(f, indent + " ", *it); } + if (sw->cases.empty()) { + // Verilog does not allow empty cases. + f << stringf("%s default: ;\n", indent.c_str()); + } + f << stringf("%s" "endcase\n", indent.c_str()); } diff --git a/frontends/ast/ast.cc b/frontends/ast/ast.cc index a027295e979..5335a3992d6 100644 --- a/frontends/ast/ast.cc +++ b/frontends/ast/ast.cc @@ -229,6 +229,10 @@ AstNode::AstNode(AstNodeType type, AstNode *child1, AstNode *child2, AstNode *ch id2ast = NULL; basic_prep = false; lookahead = false; + in_lvalue_from_above = false; + in_param_from_above = false; + in_lvalue = false; + in_param = false; if (child1) children.push_back(child1); @@ -238,6 +242,8 @@ AstNode::AstNode(AstNodeType type, AstNode *child1, AstNode *child2, AstNode *ch children.push_back(child3); if (child4) children.push_back(child4); + + fixup_hierarchy_flags(); } // create a (deep recursive) copy of a node @@ -249,6 +255,10 @@ AstNode *AstNode::clone() const it = it->clone(); for (auto &it : that->attributes) it.second = it.second->clone(); + + that->set_in_lvalue_flag(false); + that->set_in_param_flag(false); + that->fixup_hierarchy_flags(); // fixup to set flags on cloned children return that; } @@ -256,10 +266,13 @@ AstNode *AstNode::clone() const void AstNode::cloneInto(AstNode *other) const { AstNode *tmp = clone(); + tmp->in_lvalue_from_above = other->in_lvalue_from_above; + tmp->in_param_from_above = other->in_param_from_above; other->delete_children(); *other = *tmp; tmp->children.clear(); tmp->attributes.clear(); + other->fixup_hierarchy_flags(); delete tmp; } @@ -351,6 +364,10 @@ void AstNode::dumpAst(FILE *f, std::string indent) const if (is_enum) { fprintf(f, " type=enum"); } + if (in_lvalue) + fprintf(f, " in_lvalue"); + if (in_param) + fprintf(f, " in_param"); fprintf(f, "\n"); for (auto &it : attributes) { @@ -1061,7 +1078,7 @@ static RTLIL::Module *process_module(RTLIL::Design *design, AstNode *ast, bool d // simplify this module or interface using the current design as context // for lookup up ports and wires within cells set_simplify_design_context(design); - while (ast->simplify(!flag_noopt, false, 0, -1, false, false)) { } + while (ast->simplify(!flag_noopt, 0, -1, false)) { } set_simplify_design_context(nullptr); if (flag_dump_ast2) { @@ -1091,7 +1108,7 @@ static RTLIL::Module *process_module(RTLIL::Design *design, AstNode *ast, bool d ast->attributes.erase(ID::whitebox); } AstNode *n = ast->attributes.at(ID::lib_whitebox); - ast->attributes[ID::whitebox] = n; + ast->set_attribute(ID::whitebox, n); ast->attributes.erase(ID::lib_whitebox); } } @@ -1150,7 +1167,7 @@ static RTLIL::Module *process_module(RTLIL::Design *design, AstNode *ast, bool d ast->children.swap(new_children); if (ast->attributes.count(ID::blackbox) == 0) { - ast->attributes[ID::blackbox] = AstNode::mkconst_int(1, false); + ast->set_attribute(ID::blackbox, AstNode::mkconst_int(1, false)); } } @@ -1298,6 +1315,8 @@ void AST::process(RTLIL::Design *design, AstNode *ast, bool dump_ast1, bool dump flag_pwires = pwires; flag_autowire = autowire; + ast->fixup_hierarchy_flags(true); + log_assert(current_ast->type == AST_DESIGN); for (AstNode *child : current_ast->children) { @@ -1361,7 +1380,7 @@ void AST::process(RTLIL::Design *design, AstNode *ast, bool dump_ast1, bool dump } else if (child->type == AST_PACKAGE) { // process enum/other declarations - child->simplify(true, false, 1, -1, false, false); + child->simplify(true, 1, -1, false); rename_in_package_stmts(child); design->verilog_packages.push_back(child->clone()); current_scope.clear(); @@ -1748,7 +1767,7 @@ std::string AstModule::derive_common(RTLIL::Design *design, const dictclone(); if (!new_ast->attributes.count(ID::hdlname)) - new_ast->attributes[ID::hdlname] = AstNode::mkconst_str(stripped_name); + new_ast->set_attribute(ID::hdlname, AstNode::mkconst_str(stripped_name)); para_counter = 0; for (auto child : new_ast->children) { @@ -1795,6 +1814,7 @@ std::string AstModule::derive_common(RTLIL::Design *design, const dictchildren.push_back(defparam); } + new_ast->fixup_hierarchy_flags(true); (*new_ast_out) = new_ast; return modname; } diff --git a/frontends/ast/ast.h b/frontends/ast/ast.h index e357579add2..f789e930b3e 100644 --- a/frontends/ast/ast.h +++ b/frontends/ast/ast.h @@ -221,6 +221,13 @@ namespace AST std::string filename; AstSrcLocType location; + // are we embedded in an lvalue, param? + // (see fixup_hierarchy_flags) + bool in_lvalue; + bool in_param; + bool in_lvalue_from_above; + bool in_param_from_above; + // creating and deleting nodes AstNode(AstNodeType type = AST_NONE, AstNode *child1 = nullptr, AstNode *child2 = nullptr, AstNode *child3 = nullptr, AstNode *child4 = nullptr); AstNode *clone() const; @@ -251,7 +258,7 @@ namespace AST // simplify() creates a simpler AST by unrolling for-loops, expanding generate blocks, etc. // it also sets the id2ast pointers so that identifier lookups are fast in genRTLIL() - bool simplify(bool const_fold, bool in_lvalue, int stage, int width_hint, bool sign_hint, bool in_param); + bool simplify(bool const_fold, int stage, int width_hint, bool sign_hint); void replace_result_wire_name_in_function(const std::string &from, const std::string &to); AstNode *readmem(bool is_readmemh, std::string mem_filename, AstNode *memory, int start_addr, int finish_addr, bool unconditional_init); void expand_genblock(const std::string &prefix); @@ -343,6 +350,24 @@ namespace AST // to evaluate widths of dynamic ranges) AstNode *clone_at_zero(); + void set_attribute(RTLIL::IdString key, AstNode *node) + { + attributes[key] = node; + node->set_in_param_flag(true); + } + + // helper to set in_lvalue/in_param flags from the hierarchy context (the actual flag + // can be overridden based on the intrinsic properties of this node, i.e. based on its type) + void set_in_lvalue_flag(bool flag, bool no_descend = false); + void set_in_param_flag(bool flag, bool no_descend = false); + + // fix up the hierarchy flags (in_lvalue/in_param) of this node and its children + // + // to keep the flags in sync, fixup_hierarchy_flags(true) needs to be called once after + // parsing the AST to walk the full tree, then plain fixup_hierarchy_flags() performs + // localized fixups after modifying children/attributes of a particular node + void fixup_hierarchy_flags(bool force_descend = false); + // helper to print errors from simplify/genrtlil code [[noreturn]] void input_error(const char *format, ...) const YS_ATTRIBUTE(format(printf, 2, 3)); }; diff --git a/frontends/ast/genrtlil.cc b/frontends/ast/genrtlil.cc index 81fb3189d94..6e750863fbe 100644 --- a/frontends/ast/genrtlil.cc +++ b/frontends/ast/genrtlil.cc @@ -176,10 +176,11 @@ struct AST_INTERNAL::LookaheadRewriter AstNode *wire = new AstNode(AST_WIRE); for (auto c : node->id2ast->children) wire->children.push_back(c->clone()); + wire->fixup_hierarchy_flags(); wire->str = stringf("$lookahead%s$%d", node->str.c_str(), autoidx++); - wire->attributes[ID::nosync] = AstNode::mkconst_int(1, false); + wire->set_attribute(ID::nosync, AstNode::mkconst_int(1, false)); wire->is_logic = true; - while (wire->simplify(true, false, 1, -1, false, false)) { } + while (wire->simplify(true, 1, -1, false)) { } current_ast_mod->children.push_back(wire); lookaheadids[node->str] = make_pair(node->id2ast, wire); wire->genRTLIL(); @@ -759,7 +760,7 @@ struct AST_INTERNAL::ProcessGenerator arg.realtime = true; } else { arg.type = VerilogFmtArg::INTEGER; - arg.sig = node->genRTLIL(); + arg.sig = node->genWidthRTLIL(-1, false, &subst_rvalue_map.stdmap()); arg.signed_ = is_signed; } args.push_back(arg); @@ -926,7 +927,7 @@ void AstNode::detectSignWidthWorker(int &width_hint, bool &sign_hint, bool *foun this_width = id_ast->children[1]->range_left - id_ast->children[1]->range_right + 1; } else { if (id_ast->children[0]->type != AST_CONSTANT) - while (id_ast->simplify(true, false, 1, -1, false, true)) { } + while (id_ast->simplify(true, 1, -1, false)) { } if (id_ast->children[0]->type == AST_CONSTANT) this_width = id_ast->children[0]->bits.size(); else @@ -970,8 +971,8 @@ void AstNode::detectSignWidthWorker(int &width_hint, bool &sign_hint, bool *foun else if (!range->range_valid) { AstNode *left_at_zero_ast = children[0]->children[0]->clone_at_zero(); AstNode *right_at_zero_ast = children[0]->children.size() >= 2 ? children[0]->children[1]->clone_at_zero() : left_at_zero_ast->clone(); - while (left_at_zero_ast->simplify(true, false, 1, -1, false, false)) { } - while (right_at_zero_ast->simplify(true, false, 1, -1, false, false)) { } + while (left_at_zero_ast->simplify(true, 1, -1, false)) { } + while (right_at_zero_ast->simplify(true, 1, -1, false)) { } if (left_at_zero_ast->type != AST_CONSTANT || right_at_zero_ast->type != AST_CONSTANT) input_error("Unsupported expression on dynamic range select on signal `%s'!\n", str.c_str()); this_width = abs(int(left_at_zero_ast->integer - right_at_zero_ast->integer)) + 1; @@ -987,7 +988,7 @@ void AstNode::detectSignWidthWorker(int &width_hint, bool &sign_hint, bool *foun break; case AST_TO_BITS: - while (children[0]->simplify(true, false, 1, -1, false, false) == true) { } + while (children[0]->simplify(true, 1, -1, false) == true) { } if (children[0]->type != AST_CONSTANT) input_error("Left operand of tobits expression is not constant!\n"); children[1]->detectSignWidthWorker(sub_width_hint, sign_hint); @@ -1009,7 +1010,7 @@ void AstNode::detectSignWidthWorker(int &width_hint, bool &sign_hint, bool *foun break; case AST_CAST_SIZE: - while (children.at(0)->simplify(true, false, 1, -1, false, false)) { } + while (children.at(0)->simplify(true, 1, -1, false)) { } if (children.at(0)->type != AST_CONSTANT) input_error("Static cast with non constant expression!\n"); children.at(1)->detectSignWidthWorker(width_hint, sign_hint); @@ -1031,7 +1032,7 @@ void AstNode::detectSignWidthWorker(int &width_hint, bool &sign_hint, bool *foun break; case AST_REPLICATE: - while (children[0]->simplify(true, false, 1, -1, false, true) == true) { } + while (children[0]->simplify(true, 1, -1, false) == true) { } if (children[0]->type != AST_CONSTANT) input_error("Left operand of replicate expression is not constant!\n"); children[1]->detectSignWidthWorker(sub_width_hint, sub_sign_hint); @@ -1143,7 +1144,7 @@ void AstNode::detectSignWidthWorker(int &width_hint, bool &sign_hint, bool *foun case AST_PREFIX: // Prefix nodes always resolve to identifiers in generate loops, so we // can simply perform the resolution to determine the sign and width. - simplify(true, false, 1, -1, false, false); + simplify(true, 1, -1, false); log_assert(type == AST_IDENTIFIER); detectSignWidthWorker(width_hint, sign_hint, found_real); break; @@ -1151,7 +1152,7 @@ void AstNode::detectSignWidthWorker(int &width_hint, bool &sign_hint, bool *foun case AST_FCALL: if (str == "\\$anyconst" || str == "\\$anyseq" || str == "\\$allconst" || str == "\\$allseq") { if (GetSize(children) == 1) { - while (children[0]->simplify(true, false, 1, -1, false, true) == true) { } + while (children[0]->simplify(true, 1, -1, false) == true) { } if (children[0]->type != AST_CONSTANT) input_error("System function %s called with non-const argument!\n", RTLIL::unescape_id(str).c_str()); @@ -1198,8 +1199,10 @@ void AstNode::detectSignWidthWorker(int &width_hint, bool &sign_hint, bool *foun log_assert(range->type == AST_RANGE && range->children.size() == 2); AstNode *left = range->children.at(0)->clone(); AstNode *right = range->children.at(1)->clone(); - while (left->simplify(true, false, 1, -1, false, true)) { } - while (right->simplify(true, false, 1, -1, false, true)) { } + left->set_in_param_flag(true); + right->set_in_param_flag(true); + while (left->simplify(true, 1, -1, false)) { } + while (right->simplify(true, 1, -1, false)) { } if (left->type != AST_CONSTANT || right->type != AST_CONSTANT) input_error("Function %s has non-constant width!", RTLIL::unescape_id(str).c_str()); @@ -1543,8 +1546,8 @@ RTLIL::SigSpec AstNode::genRTLIL(int width_hint, bool sign_hint) if (!children[0]->range_valid) { AstNode *left_at_zero_ast = children[0]->children[0]->clone_at_zero(); AstNode *right_at_zero_ast = children[0]->children.size() >= 2 ? children[0]->children[1]->clone_at_zero() : left_at_zero_ast->clone(); - while (left_at_zero_ast->simplify(true, false, 1, -1, false, false)) { } - while (right_at_zero_ast->simplify(true, false, 1, -1, false, false)) { } + while (left_at_zero_ast->simplify(true, 1, -1, false)) { } + while (right_at_zero_ast->simplify(true, 1, -1, false)) { } if (left_at_zero_ast->type != AST_CONSTANT || right_at_zero_ast->type != AST_CONSTANT) input_error("Unsupported expression on dynamic range select on signal `%s'!\n", str.c_str()); int width = abs(int(left_at_zero_ast->integer - right_at_zero_ast->integer)) + 1; @@ -1552,7 +1555,7 @@ RTLIL::SigSpec AstNode::genRTLIL(int width_hint, bool sign_hint) children[0]->children[1]->clone() : children[0]->children[0]->clone()); fake_ast->children[0]->delete_children(); if (member_node) - fake_ast->children[0]->attributes[ID::wiretype] = member_node->clone(); + fake_ast->children[0]->set_attribute(ID::wiretype, member_node->clone()); int fake_ast_width = 0; bool fake_ast_sign = true; diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc index ee1be3781c8..2a500b56bd3 100644 --- a/frontends/ast/simplify.cc +++ b/frontends/ast/simplify.cc @@ -41,13 +41,102 @@ YOSYS_NAMESPACE_BEGIN using namespace AST; using namespace AST_INTERNAL; +void AstNode::set_in_lvalue_flag(bool flag, bool no_descend) +{ + if (flag != in_lvalue_from_above) { + in_lvalue_from_above = flag; + if (!no_descend) + fixup_hierarchy_flags(); + } +} + +void AstNode::set_in_param_flag(bool flag, bool no_descend) +{ + if (flag != in_param_from_above) { + in_param_from_above = flag; + if (!no_descend) + fixup_hierarchy_flags(); + } +} + +void AstNode::fixup_hierarchy_flags(bool force_descend) +{ + // With forced descend, we disable the implicit + // descend from within the set_* functions, instead + // we do an explicit descend at the end of this function + + in_param = in_param_from_above; + + switch (type) { + case AST_PARAMETER: + case AST_LOCALPARAM: + case AST_DEFPARAM: + case AST_PARASET: + case AST_PREFIX: + in_param = true; + for (auto child : children) + child->set_in_param_flag(true, force_descend); + break; + + case AST_REPLICATE: + case AST_WIRE: + case AST_GENIF: + case AST_GENCASE: + for (auto child : children) + child->set_in_param_flag(in_param, force_descend); + if (children.size() >= 1) + children[0]->set_in_param_flag(true, force_descend); + break; + + case AST_GENFOR: + case AST_FOR: + for (auto child : children) + child->set_in_param_flag(in_param, force_descend); + if (children.size() >= 2) + children[1]->set_in_param_flag(true, force_descend); + break; + + default: + in_param = in_param_from_above; + for (auto child : children) + child->set_in_param_flag(in_param, force_descend); + } + + for (auto attr : attributes) + attr.second->set_in_param_flag(true, force_descend); + + in_lvalue = in_lvalue_from_above; + + switch (type) { + case AST_ASSIGN: + case AST_ASSIGN_EQ: + case AST_ASSIGN_LE: + if (children.size() >= 1) + children[0]->set_in_lvalue_flag(true, force_descend); + if (children.size() >= 2) + children[1]->set_in_lvalue_flag(in_lvalue, force_descend); + break; + + default: + for (auto child : children) + child->set_in_lvalue_flag(in_lvalue, force_descend); + } + + if (force_descend) { + for (auto child : children) + child->fixup_hierarchy_flags(true); + for (auto attr : attributes) + attr.second->fixup_hierarchy_flags(true); + } +} + // Process a format string and arguments for $display, $write, $sprintf, etc Fmt AstNode::processFormat(int stage, bool sformat_like, int default_base, size_t first_arg_at) { std::vector args; for (size_t index = first_arg_at; index < children.size(); index++) { AstNode *node_arg = children[index]; - while (node_arg->simplify(true, false, stage, -1, false, false)) { } + while (node_arg->simplify(true, stage, -1, false)) { } VerilogFmtArg arg = {}; arg.filename = filename; @@ -91,7 +180,7 @@ void AstNode::annotateTypedEnums(AstNode *template_node) log_assert(current_scope.count(enum_type) == 1); AstNode *enum_node = current_scope.at(enum_type); log_assert(enum_node->type == AST_ENUM); - while (enum_node->simplify(true, false, 1, -1, false, true)) { } + while (enum_node->simplify(true, 1, -1, false)) { } //get width from 1st enum item: log_assert(enum_node->children.size() >= 1); AstNode *enum_item0 = enum_node->children[0]; @@ -132,7 +221,7 @@ void AstNode::annotateTypedEnums(AstNode *template_node) RTLIL::Const val = enum_item->children[0]->bitsAsConst(width, is_signed); enum_item_str.append(val.as_string()); //set attribute for available val to enum item name mappings - attributes[enum_item_str.c_str()] = mkconst_str(enum_item->str); + set_attribute(enum_item_str.c_str(), mkconst_str(enum_item->str)); } } } @@ -457,17 +546,15 @@ static int get_max_offset(AstNode *node) static AstNode *make_packed_struct(AstNode *template_node, std::string &name, decltype(AstNode::attributes) &attributes) { // create a wire for the packed struct - auto wnode = new AstNode(AST_WIRE); + int offset = get_max_offset(template_node); + auto wnode = new AstNode(AST_WIRE, make_range(offset, 0)); wnode->str = name; wnode->is_logic = true; wnode->range_valid = true; wnode->is_signed = template_node->is_signed; for (auto &pair : attributes) { - wnode->attributes[pair.first] = pair.second->clone(); + wnode->set_attribute(pair.first, pair.second->clone()); } - 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 @@ -527,7 +614,7 @@ const RTLIL::Module* AstNode::lookup_cell_module() auto reprocess_after = [this] (const std::string &modname) { if (!attributes.count(ID::reprocess_after)) - attributes[ID::reprocess_after] = AstNode::mkconst_str(modname); + set_attribute(ID::reprocess_after, AstNode::mkconst_str(modname)); }; const AstNode *celltype = nullptr; @@ -707,6 +794,11 @@ AstNode *AstNode::clone_at_zero() it = it->clone_at_zero(); for (auto &it : that->attributes) it.second = it.second->clone(); + + that->set_in_lvalue_flag(false); + that->set_in_param_flag(false); + that->fixup_hierarchy_flags(); + return that; } @@ -722,8 +814,8 @@ static bool try_determine_range_width(AstNode *range, int &result_width) AstNode *left_at_zero_ast = range->children[0]->clone_at_zero(); AstNode *right_at_zero_ast = range->children[1]->clone_at_zero(); - while (left_at_zero_ast->simplify(true, false, 1, -1, false, false)) {} - while (right_at_zero_ast->simplify(true, false, 1, -1, false, false)) {} + while (left_at_zero_ast->simplify(true, 1, -1, false)) {} + while (right_at_zero_ast->simplify(true, 1, -1, false)) {} bool ok = false; if (left_at_zero_ast->type == AST_CONSTANT @@ -745,8 +837,7 @@ static void mark_auto_nosync(AstNode *block, const AstNode *wire) { log_assert(block->type == AST_BLOCK); log_assert(wire->type == AST_WIRE); - block->attributes[auto_nosync_prefix + wire->str] = AstNode::mkconst_int(1, - false); + block->set_attribute(auto_nosync_prefix + wire->str, AstNode::mkconst_int(1, false)); } // block names can be prefixed with an explicit scope during elaboration @@ -787,7 +878,7 @@ static void check_auto_nosync(AstNode *node) // mark the wire with `nosync` AstNode *wire = it->second; log_assert(wire->type == AST_WIRE); - wire->attributes[ID::nosync] = AstNode::mkconst_int(1, false); + wire->set_attribute(ID::nosync, AstNode::mkconst_int(1, false)); } // remove the attributes we've "consumed" @@ -808,7 +899,7 @@ static void check_auto_nosync(AstNode *node) // // this function also does all name resolving and sets the id2ast member of all // nodes that link to a different node using names and lexical scoping. -bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hint, bool sign_hint, bool in_param) +bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hint) { static int recursion_counter = 0; static bool deep_recursion_warning = false; @@ -826,8 +917,8 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin #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, in_lvalue=%d, stage=%d, width_hint=%d, sign_hint=%d, in_param=%d\n", - int(const_fold), int(in_lvalue), int(stage), int(width_hint), int(sign_hint), int(in_param)); + log("const_fold=%d, stage=%d, width_hint=%d, sign_hint=%d\n", + int(const_fold), int(stage), int(width_hint), int(sign_hint)); // dumpAst(NULL, "> "); #endif @@ -836,7 +927,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin log_assert(type == AST_MODULE || type == AST_INTERFACE); deep_recursion_warning = true; - while (simplify(const_fold, in_lvalue, 1, width_hint, sign_hint, in_param)) { } + while (simplify(const_fold, 1, width_hint, sign_hint)) { } if (!flag_nomem2reg && !get_bool_attribute(ID::nomem2reg)) { @@ -915,11 +1006,11 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin 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->set_attribute(it.first, it.second->clone()); reg->filename = node->filename; reg->location = node->location; children.push_back(reg); - while (reg->simplify(true, false, 1, -1, false, false)) { } + while (reg->simplify(true, 1, -1, false)) { } } } @@ -933,7 +1024,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin delete node; } - while (simplify(const_fold, in_lvalue, 2, width_hint, sign_hint, in_param)) { } + while (simplify(const_fold, 2, width_hint, sign_hint)) { } recursion_counter--; return false; } @@ -980,7 +1071,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin // when $display()/$write() functions are used in an always block, simplify the expressions and // convert them to a special cell later in genrtlil for (auto node : children) - while (node->simplify(true, false, stage, -1, false, false)) {} + while (node->simplify(true, stage, -1, false)) {} return false; } @@ -994,10 +1085,6 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin if (type == AST_IDENTIFIER && current_scope.count(str) > 0 && (current_scope[str]->type == AST_PARAMETER || current_scope[str]->type == AST_LOCALPARAM || current_scope[str]->type == AST_ENUM_ITEM)) const_fold = true; - // in certain cases a function must be evaluated constant. this is what in_param controls. - if (type == AST_PARAMETER || type == AST_LOCALPARAM || type == AST_DEFPARAM || type == AST_PARASET || type == AST_PREFIX) - in_param = true; - std::map backup_scope; // create name resolution entries for all objects with names @@ -1017,7 +1104,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin if (!c->is_simple_const_expr()) { if (attributes.count(ID::dynports)) delete attributes.at(ID::dynports); - attributes[ID::dynports] = AstNode::mkconst_int(1, true); + set_attribute(ID::dynports, AstNode::mkconst_int(1, true)); } } } @@ -1066,7 +1153,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin 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(); + first_node->set_attribute(it.first, it.second->clone()); } children.erase(children.begin()+(i--)); did_something = true; @@ -1100,12 +1187,12 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin for (size_t i = 0; i < children.size(); i++) { AstNode *node = children[i]; if (node->type == AST_PARAMETER || node->type == AST_LOCALPARAM || node->type == AST_WIRE || node->type == AST_AUTOWIRE || node->type == AST_MEMORY || node->type == AST_TYPEDEF) - while (node->simplify(true, false, 1, -1, false, node->type == AST_PARAMETER || node->type == AST_LOCALPARAM)) + while (node->simplify(true, 1, -1, false)) did_something = true; if (node->type == AST_ENUM) { for (auto enode : node->children){ log_assert(enode->type==AST_ENUM_ITEM); - while (node->simplify(true, false, 1, -1, false, in_param)) + while (node->simplify(true, 1, -1, false)) did_something = true; } } @@ -1170,7 +1257,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin for (AstNode *child : children) { // simplify any parameters to constants if (child->type == AST_PARASET) - while (child->simplify(true, false, 1, -1, false, true)) { } + while (child->simplify(true, 1, -1, false)) { } // look for patterns which _may_ indicate ambiguity requiring // resolution of the underlying module @@ -1263,6 +1350,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin asgn->children.push_back(arg); asgn->children.push_back(ident); } + asgn->fixup_hierarchy_flags(); } @@ -1284,9 +1372,9 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin case AST_ASSIGN_EQ: case AST_ASSIGN_LE: case AST_ASSIGN: - while (!children[0]->basic_prep && children[0]->simplify(false, true, stage, -1, false, in_param) == true) + while (!children[0]->basic_prep && children[0]->simplify(false, stage, -1, false) == true) did_something = true; - while (!children[1]->basic_prep && children[1]->simplify(false, false, stage, -1, false, in_param) == true) + while (!children[1]->basic_prep && children[1]->simplify(false, stage, -1, false) == true) did_something = true; children[0]->detectSignWidth(backup_width_hint, backup_sign_hint); children[1]->detectSignWidth(width_hint, sign_hint); @@ -1322,7 +1410,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin if (!basic_prep) { for (auto *node : children) { // resolve any ranges - while (!node->basic_prep && node->simplify(true, false, stage, -1, false, false)) { + while (!node->basic_prep && node->simplify(true, stage, -1, false)) { did_something = true; } } @@ -1355,7 +1443,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin AstNode *template_node = resolved_type_node->children[0]; // Ensure typedef itself is fully simplified - while (template_node->simplify(const_fold, in_lvalue, stage, width_hint, sign_hint, in_param)) {}; + while (template_node->simplify(const_fold, stage, width_hint, sign_hint)) {}; // Remove type reference delete children[0]; @@ -1384,7 +1472,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin range_left = template_node->range_left; range_right = template_node->range_right; - attributes[ID::wiretype] = mkconst_str(resolved_type_node->str); + set_attribute(ID::wiretype, mkconst_str(resolved_type_node->str)); // Copy clones of children from template for (auto template_child : template_node->children) { @@ -1401,7 +1489,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin //log("\nENUM %s: %d child %d\n", str.c_str(), basic_prep, children[0]->basic_prep); if (!basic_prep) { for (auto item_node : children) { - while (!item_node->basic_prep && item_node->simplify(false, false, stage, -1, false, in_param)) + while (!item_node->basic_prep && item_node->simplify(false, stage, -1, false)) did_something = true; } // allocate values (called more than once) @@ -1416,16 +1504,16 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin if (children[0]->type == AST_IDENTIFIER && current_scope.count(children[0]->str) > 0) { auto item_node = current_scope[children[0]->str]; if (item_node->type == AST_STRUCT || item_node->type == AST_UNION) { - attributes[ID::wiretype] = item_node->clone(); + set_attribute(ID::wiretype, item_node->clone()); size_packed_struct(attributes[ID::wiretype], 0); add_members_to_scope(attributes[ID::wiretype], str); } } - while (!children[0]->basic_prep && children[0]->simplify(false, false, stage, -1, false, true) == true) + while (!children[0]->basic_prep && children[0]->simplify(false, stage, -1, false) == true) did_something = true; children[0]->detectSignWidth(width_hint, sign_hint); if (children.size() > 1 && children[1]->type == AST_RANGE) { - while (!children[1]->basic_prep && children[1]->simplify(false, false, stage, -1, false, true) == true) + while (!children[1]->basic_prep && children[1]->simplify(false, stage, -1, false) == true) did_something = true; if (!children[1]->range_valid) input_error("Non-constant width range on parameter decl.\n"); @@ -1433,11 +1521,11 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin } break; case AST_ENUM_ITEM: - while (!children[0]->basic_prep && children[0]->simplify(false, false, stage, -1, false, in_param)) + while (!children[0]->basic_prep && children[0]->simplify(false, stage, -1, false)) did_something = true; children[0]->detectSignWidth(width_hint, sign_hint); if (children.size() > 1 && children[1]->type == AST_RANGE) { - while (!children[1]->basic_prep && children[1]->simplify(false, false, stage, -1, false, in_param)) + while (!children[1]->basic_prep && children[1]->simplify(false, stage, -1, false)) did_something = true; if (!children[1]->range_valid) input_error("Non-constant width range on enum item decl.\n"); @@ -1496,7 +1584,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin width_hint = -1; sign_hint = true; for (auto child : children) { - while (!child->basic_prep && child->simplify(false, in_lvalue, stage, -1, false, in_param) == true) + while (!child->basic_prep && child->simplify(false, stage, -1, false) == true) did_something = true; child->detectSignWidthWorker(width_hint, sign_hint); } @@ -1531,10 +1619,10 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin if (detect_width_simple && width_hint < 0) { if (type == AST_REPLICATE) - while (children[0]->simplify(true, in_lvalue, stage, -1, false, true) == true) + while (children[0]->simplify(true, stage, -1, false) == true) did_something = true; for (auto child : children) - while (!child->basic_prep && child->simplify(false, in_lvalue, stage, -1, false, in_param) == true) + while (!child->basic_prep && child->simplify(false, stage, -1, false) == true) did_something = true; detectSignWidth(width_hint, sign_hint); } @@ -1544,18 +1632,18 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin if (type == AST_TERNARY) { if (width_hint < 0) { - while (!children[0]->basic_prep && children[0]->simplify(true, in_lvalue, stage, -1, false, in_param)) + while (!children[0]->basic_prep && children[0]->simplify(true, stage, -1, false)) did_something = true; bool backup_unevaluated_tern_branch = unevaluated_tern_branch; AstNode *chosen = get_tern_choice().first; unevaluated_tern_branch = backup_unevaluated_tern_branch || chosen == children[2]; - while (!children[1]->basic_prep && children[1]->simplify(false, in_lvalue, stage, -1, false, in_param)) + while (!children[1]->basic_prep && children[1]->simplify(false, stage, -1, false)) did_something = true; unevaluated_tern_branch = backup_unevaluated_tern_branch || chosen == children[1]; - while (!children[2]->basic_prep && children[2]->simplify(false, in_lvalue, stage, -1, false, in_param)) + while (!children[2]->basic_prep && children[2]->simplify(false, stage, -1, false)) did_something = true; unevaluated_tern_branch = backup_unevaluated_tern_branch; @@ -1587,7 +1675,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin if (const_fold && type == AST_CASE) { detectSignWidth(width_hint, sign_hint); - while (children[0]->simplify(const_fold, in_lvalue, stage, width_hint, sign_hint, in_param)) { } + while (children[0]->simplify(const_fold, stage, width_hint, sign_hint)) { } if (children[0]->type == AST_CONSTANT && children[0]->bits_only_01()) { children[0]->is_signed = sign_hint; RTLIL::Const case_expr = children[0]->bitsAsConst(width_hint, sign_hint); @@ -1601,7 +1689,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin goto keep_const_cond; if (v->type == AST_BLOCK) continue; - while (v->simplify(const_fold, in_lvalue, stage, width_hint, sign_hint, in_param)) { } + while (v->simplify(const_fold, stage, width_hint, sign_hint)) { } if (v->type == AST_CONSTANT && v->bits_only_01()) { RTLIL::Const case_item_expr = v->bitsAsConst(width_hint, sign_hint); RTLIL::Const match = const_eq(case_expr, case_item_expr, sign_hint, sign_hint, 1); @@ -1658,20 +1746,13 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin unevaluated_tern_branch = chosen && chosen != children[i]; } while (did_something_here && i < children.size()) { - bool const_fold_here = const_fold, in_lvalue_here = in_lvalue; + bool const_fold_here = const_fold; int width_hint_here = width_hint; bool sign_hint_here = sign_hint; - bool in_param_here = in_param; if (i == 0 && (type == AST_REPLICATE || type == AST_WIRE)) - const_fold_here = true, in_param_here = true; - if (i == 0 && (type == AST_GENIF || type == AST_GENCASE)) - in_param_here = true; - if (i == 1 && (type == AST_FOR || type == AST_GENFOR)) - in_param_here = true; + const_fold_here = true; if (type == AST_PARAMETER || type == AST_LOCALPARAM) const_fold_here = true; - if (i == 0 && (type == AST_ASSIGN || type == AST_ASSIGN_EQ || type == AST_ASSIGN_LE)) - in_lvalue_here = true; if (type == AST_BLOCK) { current_block = this; current_block_child = children[i]; @@ -1686,7 +1767,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin width_hint_here = -1, sign_hint_here = false; if (children_are_self_determined) width_hint_here = -1, sign_hint_here = false; - did_something_here = children[i]->simplify(const_fold_here, in_lvalue_here, stage, width_hint_here, sign_hint_here, in_param_here); + did_something_here = children[i]->simplify(const_fold_here, stage, width_hint_here, sign_hint_here); if (did_something_here) did_something = true; } @@ -1706,7 +1787,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin } } for (auto &attr : attributes) { - while (attr.second->simplify(true, false, stage, -1, false, true)) + while (attr.second->simplify(true, stage, -1, false)) did_something = true; } if (type == AST_CASE && stage == 2) { @@ -1784,7 +1865,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin log_assert(children.size() == 1); auto type_node = children[0]; log_assert(type_node->type == AST_WIRE || type_node->type == AST_MEMORY || type_node->type == AST_STRUCT || type_node->type == AST_UNION); - while (type_node->simplify(const_fold, in_lvalue, stage, width_hint, sign_hint, in_param)) { + while (type_node->simplify(const_fold, stage, width_hint, sign_hint)) { did_something = true; } log_assert(!type_node->is_custom_type); @@ -1806,12 +1887,12 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin AstNode *template_node = resolved_type_node->children[0]; // Ensure typedef itself is fully simplified - while (template_node->simplify(const_fold, in_lvalue, stage, width_hint, sign_hint, in_param)) {}; + while (template_node->simplify(const_fold, stage, width_hint, sign_hint)) {}; if (!str.empty() && str[0] == '\\' && (template_node->type == AST_STRUCT || template_node->type == AST_UNION)) { // replace instance with wire representing the packed structure newNode = make_packed_struct(template_node, str, attributes); - newNode->attributes[ID::wiretype] = mkconst_str(resolved_type_node->str); + newNode->set_attribute(ID::wiretype, mkconst_str(resolved_type_node->str)); // add original input/output attribute to resolved wire newNode->is_input = this->is_input; newNode->is_output = this->is_output; @@ -1836,7 +1917,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin range_left = template_node->range_left; range_right = template_node->range_right; - attributes[ID::wiretype] = mkconst_str(resolved_type_node->str); + set_attribute(ID::wiretype, mkconst_str(resolved_type_node->str)); // if an enum then add attributes to support simulator tracing annotateTypedEnums(template_node); @@ -1850,6 +1931,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin AstNode *rng = make_range(0, 0); children.insert(children.begin(), rng); } + fixup_hierarchy_flags(); did_something = true; } log_assert(!is_custom_type); @@ -1871,12 +1953,12 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin AstNode *template_node = resolved_type_node->children[0]; // Ensure typedef itself is fully simplified - while (template_node->simplify(const_fold, in_lvalue, stage, width_hint, sign_hint, in_param)) {}; + while (template_node->simplify(const_fold, stage, width_hint, sign_hint)) {}; if (template_node->type == AST_STRUCT || template_node->type == AST_UNION) { // replace with wire representing the packed structure newNode = make_packed_struct(template_node, str, attributes); - newNode->attributes[ID::wiretype] = mkconst_str(resolved_type_node->str); + newNode->set_attribute(ID::wiretype, mkconst_str(resolved_type_node->str)); newNode->type = type; current_scope[str] = this; // copy param value, it needs to be 1st value @@ -1898,9 +1980,10 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin range_swapped = template_node->range_swapped; range_left = template_node->range_left; range_right = template_node->range_right; - attributes[ID::wiretype] = mkconst_str(resolved_type_node->str); + set_attribute(ID::wiretype, mkconst_str(resolved_type_node->str)); for (auto template_child : template_node->children) children.push_back(template_child->clone()); + fixup_hierarchy_flags(); did_something = true; } log_assert(!is_custom_type); @@ -1913,7 +1996,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin input_error("Index in generate block prefix syntax is not constant!\n"); } if (children[1]->type == AST_PREFIX) - children[1]->simplify(const_fold, in_lvalue, stage, width_hint, sign_hint, in_param); + children[1]->simplify(const_fold, stage, width_hint, sign_hint); log_assert(children[1]->type == AST_IDENTIFIER); newNode = children[1]->clone(); const char *second_part = children[1]->str.c_str(); @@ -2020,6 +2103,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin } delete children[1]; children[1] = new AstNode(AST_RANGE, AstNode::mkconst_int(0, true), AstNode::mkconst_int(total_size-1, true)); + fixup_hierarchy_flags(); did_something = true; } @@ -2054,6 +2138,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin else children[0] = new AstNode(AST_RANGE, index_expr); + fixup_hierarchy_flags(); did_something = true; } @@ -2069,6 +2154,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin children[0]->realvalue, log_signal(constvalue)); delete children[0]; children[0] = mkconst_bits(constvalue.bits, sign_hint); + fixup_hierarchy_flags(); did_something = true; } if (children[0]->type == AST_CONSTANT) { @@ -2078,6 +2164,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin AstNode *old_child_0 = children[0]; children[0] = mkconst_bits(sig.as_const().bits, is_signed); delete old_child_0; + fixup_hierarchy_flags(); } children[0]->is_signed = is_signed; } @@ -2091,6 +2178,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin delete children[0]; children[0] = new AstNode(AST_REALVALUE); children[0]->realvalue = as_realvalue; + fixup_hierarchy_flags(); did_something = true; } } @@ -2107,7 +2195,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin newNode = new AstNode(AST_IDENTIFIER, range); newNode->str = sname; // save type and original number of dimensions for $size() etc. - newNode->attributes[ID::wiretype] = item_node->clone(); + newNode->set_attribute(ID::wiretype, item_node->clone()); if (!item_node->multirange_dimensions.empty() && children.size() > 0) { if (children[0]->type == AST_RANGE) newNode->integer = 1; @@ -2201,9 +2289,9 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin AstNode *wire = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(data_range_left, true), mkconst_int(data_range_right, true))); wire->str = wire_id; if (current_block) - wire->attributes[ID::nosync] = AstNode::mkconst_int(1, false); + wire->set_attribute(ID::nosync, AstNode::mkconst_int(1, false)); current_ast_mod->children.push_back(wire); - while (wire->simplify(true, false, 1, -1, false, false)) { } + while (wire->simplify(true, 1, -1, false)) { } AstNode *data = clone(); delete data->children[1]; @@ -2245,7 +2333,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin AstNode *body = children[1]; // eval count expression - while (count->simplify(true, false, stage, 32, true, false)) { } + while (count->simplify(true, stage, 32, true)) { } if (count->type != AST_CONSTANT) input_error("Repeat loops outside must have constant repeat counts!\n"); @@ -2301,7 +2389,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin int expr_width_hint = -1; bool expr_sign_hint = true; varbuf->detectSignWidth(expr_width_hint, expr_sign_hint); - while (varbuf->simplify(true, false, stage, 32, true, false)) { } + while (varbuf->simplify(true, stage, 32, true)) { } } if (varbuf->type != AST_CONSTANT) @@ -2342,7 +2430,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin int expr_width_hint = -1; bool expr_sign_hint = true; buf->detectSignWidth(expr_width_hint, expr_sign_hint); - while (buf->simplify(true, false, stage, expr_width_hint, expr_sign_hint, false)) { } + while (buf->simplify(true, stage, expr_width_hint, expr_sign_hint)) { } } if (buf->type != AST_CONSTANT) @@ -2377,7 +2465,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin if (type == AST_GENFOR) { for (size_t i = 0; i < buf->children.size(); i++) { - buf->children[i]->simplify(const_fold, false, stage, -1, false, false); + buf->children[i]->simplify(const_fold, stage, -1, false); current_ast_mod->children.push_back(buf->children[i]); } } else { @@ -2389,11 +2477,12 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin // eval 3rd expression buf = next_ast->children[1]->clone(); + buf->set_in_param_flag(true); { int expr_width_hint = -1; bool expr_sign_hint = true; buf->detectSignWidth(expr_width_hint, expr_sign_hint); - while (buf->simplify(true, false, stage, expr_width_hint, expr_sign_hint, true)) { } + while (buf->simplify(true, stage, expr_width_hint, expr_sign_hint)) { } } if (buf->type != AST_CONSTANT) @@ -2445,7 +2534,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin std::vector new_children; for (size_t i = 0; i < children.size(); i++) if (children[i]->type == AST_WIRE || children[i]->type == AST_MEMORY || children[i]->type == AST_PARAMETER || children[i]->type == AST_LOCALPARAM || children[i]->type == AST_TYPEDEF) { - children[i]->simplify(false, false, stage, -1, false, false); + children[i]->simplify(false, stage, -1, false); current_ast_mod->children.push_back(children[i]); current_scope[children[i]->str] = children[i]; } else @@ -2464,7 +2553,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin } for (size_t i = 0; i < children.size(); i++) { - children[i]->simplify(const_fold, false, stage, -1, false, false); + children[i]->simplify(const_fold, stage, -1, false); current_ast_mod->children.push_back(children[i]); } @@ -2476,7 +2565,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin if (type == AST_GENIF && children.size() != 0) { AstNode *buf = children[0]->clone(); - while (buf->simplify(true, false, stage, width_hint, sign_hint, false)) { } + while (buf->simplify(true, stage, width_hint, sign_hint)) { } if (buf->type != AST_CONSTANT) { // for (auto f : log_files) // dumpAst(f, "verilog-ast> "); @@ -2500,7 +2589,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin } for (size_t i = 0; i < buf->children.size(); i++) { - buf->children[i]->simplify(const_fold, false, stage, -1, false, false); + buf->children[i]->simplify(const_fold, stage, -1, false); current_ast_mod->children.push_back(buf->children[i]); } @@ -2516,7 +2605,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin if (type == AST_GENCASE && children.size() != 0) { AstNode *buf = children[0]->clone(); - while (buf->simplify(true, false, stage, width_hint, sign_hint, false)) { } + while (buf->simplify(true, stage, width_hint, sign_hint)) { } if (buf->type != AST_CONSTANT) { // for (auto f : log_files) // dumpAst(f, "verilog-ast> "); @@ -2550,7 +2639,8 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin continue; buf = child->clone(); - while (buf->simplify(true, false, stage, width_hint, sign_hint, true)) { } + buf->set_in_param_flag(true); + while (buf->simplify(true, stage, width_hint, sign_hint)) { } if (buf->type != AST_CONSTANT) { // for (auto f : log_files) // dumpAst(f, "verilog-ast> "); @@ -2578,7 +2668,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin } for (size_t i = 0; i < buf->children.size(); i++) { - buf->children[i]->simplify(const_fold, false, stage, -1, false, false); + buf->children[i]->simplify(const_fold, stage, -1, false); current_ast_mod->children.push_back(buf->children[i]); } @@ -2656,6 +2746,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin children.push_back(children_list.at(0)); children.back()->was_checked = true; children.push_back(node); + fixup_hierarchy_flags(); did_something = true; } else if (str == "buf" || str == "not") @@ -2706,6 +2797,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin children.push_back(children_list[0]); children.back()->was_checked = true; children.push_back(node); + fixup_hierarchy_flags(); did_something = true; } } @@ -2761,7 +2853,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin if (children[0]->id2ast->attributes.count(ID::nowrshmsk)) { AstNode *node = children[0]->id2ast->attributes.at(ID::nowrshmsk); - while (node->simplify(true, false, stage, -1, false, false)) { } + while (node->simplify(true, stage, -1, false)) { } if (node->type != AST_CONSTANT) input_error("Non-constant value for `nowrshmsk' attribute on `%s'!\n", children[0]->id2ast->str.c_str()); if (node->asAttrConst().as_bool()) @@ -2784,7 +2876,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin AstNode *lvalue = children[0]->clone(); lvalue->delete_children(); if (member_node) - lvalue->attributes[ID::wiretype] = member_node->clone(); + lvalue->set_attribute(ID::wiretype, member_node->clone()); lvalue->children.push_back(new AstNode(AST_RANGE, mkconst_int(end_bit, true), mkconst_int(start_bit, true))); cond->children.push_back(new AstNode(AST_BLOCK, new AstNode(type, lvalue, children[1]->clone()))); @@ -2797,16 +2889,16 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin AstNode *wire_mask = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(source_width-1, true), mkconst_int(0, true))); wire_mask->str = stringf("$bitselwrite$mask$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++); - wire_mask->attributes[ID::nosync] = AstNode::mkconst_int(1, false); + wire_mask->set_attribute(ID::nosync, AstNode::mkconst_int(1, false)); wire_mask->is_logic = true; - while (wire_mask->simplify(true, false, 1, -1, false, false)) { } + while (wire_mask->simplify(true, 1, -1, false)) { } current_ast_mod->children.push_back(wire_mask); AstNode *wire_data = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(source_width-1, true), mkconst_int(0, true))); wire_data->str = stringf("$bitselwrite$data$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++); - wire_data->attributes[ID::nosync] = AstNode::mkconst_int(1, false); + wire_data->set_attribute(ID::nosync, AstNode::mkconst_int(1, false)); wire_data->is_logic = true; - while (wire_data->simplify(true, false, 1, -1, false, false)) { } + while (wire_data->simplify(true, 1, -1, false)) { } current_ast_mod->children.push_back(wire_data); int shamt_width_hint = -1; @@ -2815,10 +2907,10 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin AstNode *wire_sel = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(shamt_width_hint-1, true), mkconst_int(0, true))); wire_sel->str = stringf("$bitselwrite$sel$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++); - wire_sel->attributes[ID::nosync] = AstNode::mkconst_int(1, false); + wire_sel->set_attribute(ID::nosync, AstNode::mkconst_int(1, false)); wire_sel->is_logic = true; wire_sel->is_signed = shamt_sign_hint; - while (wire_sel->simplify(true, false, 1, -1, false, false)) { } + while (wire_sel->simplify(true, 1, -1, false)) { } current_ast_mod->children.push_back(wire_sel); did_something = true; @@ -2827,7 +2919,7 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin AstNode *lvalue = children[0]->clone(); lvalue->delete_children(); if (member_node) - lvalue->attributes[ID::wiretype] = member_node->clone(); + lvalue->set_attribute(ID::wiretype, member_node->clone()); AstNode *ref_mask = new AstNode(AST_IDENTIFIER); ref_mask->str = wire_mask->str; @@ -2884,6 +2976,8 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin t = new AstNode(AST_BIT_OR, t, ref_data); t = new AstNode(type, lvalue, t); newNode->children.push_back(t); + + newNode->fixup_hierarchy_flags(true); } goto apply_newNode; @@ -2901,7 +2995,7 @@ skip_dynamic_range_lvalue_expansion:; wire_check->was_checked = true; current_ast_mod->children.push_back(wire_check); current_scope[wire_check->str] = wire_check; - while (wire_check->simplify(true, false, 1, -1, false, false)) { } + while (wire_check->simplify(true, 1, -1, false)) { } AstNode *wire_en = new AstNode(AST_WIRE); wire_en->str = id_en; @@ -2913,7 +3007,7 @@ skip_dynamic_range_lvalue_expansion:; current_ast_mod->children.back()->children[0]->children[0]->children[0]->was_checked = true; } current_scope[wire_en->str] = wire_en; - while (wire_en->simplify(true, false, 1, -1, false, false)) { } + while (wire_en->simplify(true, 1, -1, false)) { } AstNode *check_defval; if (type == AST_LIVE || type == AST_FAIR) { @@ -2943,6 +3037,7 @@ skip_dynamic_range_lvalue_expansion:; assign_check = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), new AstNode(AST_REDUCE_BOOL, children[0]->clone())); assign_check->children[0]->str = id_check; assign_check->children[0]->was_checked = true; + assign_check->fixup_hierarchy_flags(); } if (current_always == nullptr || current_always->type != AST_INITIAL) { @@ -2953,6 +3048,7 @@ skip_dynamic_range_lvalue_expansion:; } assign_en->children[0]->str = id_en; assign_en->children[0]->was_checked = true; + assign_en->fixup_hierarchy_flags(); newNode = new AstNode(AST_BLOCK); if (assign_check != nullptr) @@ -2975,6 +3071,7 @@ skip_dynamic_range_lvalue_expansion:; if (stage > 1 && (type == AST_ASSERT || type == AST_ASSUME || type == AST_LIVE || type == AST_FAIR || type == AST_COVER) && children.size() == 1) { children.push_back(mkconst_int(1, false, 1)); + fixup_hierarchy_flags(); did_something = true; } @@ -3005,8 +3102,8 @@ skip_dynamic_range_lvalue_expansion:; wire_tmp->str = stringf("$splitcmplxassign$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++); current_ast_mod->children.push_back(wire_tmp); current_scope[wire_tmp->str] = wire_tmp; - wire_tmp->attributes[ID::nosync] = AstNode::mkconst_int(1, false); - while (wire_tmp->simplify(true, false, 1, -1, false, false)) { } + wire_tmp->set_attribute(ID::nosync, AstNode::mkconst_int(1, false)); + while (wire_tmp->simplify(true, 1, -1, false)) { } wire_tmp->is_logic = true; AstNode *wire_tmp_id = new AstNode(AST_IDENTIFIER); @@ -3076,7 +3173,7 @@ skip_dynamic_range_lvalue_expansion:; wire_addr->was_checked = true; current_ast_mod->children.push_back(wire_addr); current_scope[wire_addr->str] = wire_addr; - while (wire_addr->simplify(true, false, 1, -1, false, false)) { } + while (wire_addr->simplify(true, 1, -1, false)) { } AstNode *assign_addr = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), mkconst_bits(x_bits_addr, false)); assign_addr->children[0]->str = id_addr; @@ -3102,7 +3199,7 @@ skip_dynamic_range_lvalue_expansion:; wire_data->is_signed = mem_signed; current_ast_mod->children.push_back(wire_data); current_scope[wire_data->str] = wire_data; - while (wire_data->simplify(true, false, 1, -1, false, false)) { } + while (wire_data->simplify(true, 1, -1, false)) { } AstNode *assign_data = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), mkconst_bits(x_bits_data, false)); assign_data->children[0]->str = id_data; @@ -3118,7 +3215,7 @@ skip_dynamic_range_lvalue_expansion:; wire_en->was_checked = true; current_ast_mod->children.push_back(wire_en); current_scope[wire_en->str] = wire_en; - while (wire_en->simplify(true, false, 1, -1, false, false)) { } + while (wire_en->simplify(true, 1, -1, false)) { } AstNode *assign_en_first = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), mkconst_int(0, false, mem_width)); assign_en_first->children[0]->str = id_en; @@ -3246,7 +3343,7 @@ skip_dynamic_range_lvalue_expansion:; AstNode *wire = new AstNode(AST_WIRE); wire->str = stringf("$initstate$%d_wire", myidx); current_ast_mod->children.push_back(wire); - while (wire->simplify(true, false, 1, -1, false, false)) { } + while (wire->simplify(true, 1, -1, false)) { } AstNode *cell = new AstNode(AST_CELL, new AstNode(AST_CELLTYPE), new AstNode(AST_ARGUMENT, new AstNode(AST_IDENTIFIER))); cell->str = stringf("$initstate$%d", myidx); @@ -3255,7 +3352,7 @@ skip_dynamic_range_lvalue_expansion:; cell->children[1]->children[0]->str = wire->str; cell->children[1]->children[0]->id2ast = wire; current_ast_mod->children.push_back(cell); - while (cell->simplify(true, false, 1, -1, false, false)) { } + while (cell->simplify(true, 1, -1, false)) { } newNode = new AstNode(AST_IDENTIFIER); newNode->str = wire->str; @@ -3281,7 +3378,7 @@ skip_dynamic_range_lvalue_expansion:; if (GetSize(children) == 2) { AstNode *buf = children[1]->clone(); - while (buf->simplify(true, false, stage, -1, false, false)) { } + while (buf->simplify(true, stage, -1, false)) { } if (buf->type != AST_CONSTANT) input_error("Failed to evaluate system function `%s' with non-constant value.\n", str.c_str()); @@ -3316,7 +3413,7 @@ skip_dynamic_range_lvalue_expansion:; current_ast_mod->children.push_back(reg); - while (reg->simplify(true, false, 1, -1, false, false)) { } + while (reg->simplify(true, 1, -1, false)) { } AstNode *regid = new AstNode(AST_IDENTIFIER); regid->str = reg->str; @@ -3392,7 +3489,7 @@ skip_dynamic_range_lvalue_expansion:; RTLIL::unescape_id(str).c_str(), int(children.size())); AstNode *buf = children[0]->clone(); - while (buf->simplify(true, false, stage, width_hint, sign_hint, false)) { } + while (buf->simplify(true, stage, width_hint, sign_hint)) { } if (buf->type != AST_CONSTANT) input_error("Failed to evaluate system function `%s' with non-constant value.\n", str.c_str()); @@ -3424,7 +3521,7 @@ skip_dynamic_range_lvalue_expansion:; if (children.size() == 2) { AstNode *buf = children[1]->clone(); // Evaluate constant expression - while (buf->simplify(true, false, stage, width_hint, sign_hint, false)) { } + while (buf->simplify(true, stage, width_hint, sign_hint)) { } dim = buf->asInt(false); delete buf; } @@ -3582,7 +3679,7 @@ skip_dynamic_range_lvalue_expansion:; } if (children.size() >= 1) { - while (children[0]->simplify(true, false, stage, width_hint, sign_hint, false)) { } + while (children[0]->simplify(true, stage, width_hint, sign_hint)) { } if (!children[0]->isConst()) input_error("Failed to evaluate system function `%s' with non-constant argument.\n", RTLIL::unescape_id(str).c_str()); @@ -3593,7 +3690,7 @@ skip_dynamic_range_lvalue_expansion:; } if (children.size() >= 2) { - while (children[1]->simplify(true, false, stage, width_hint, sign_hint, false)) { } + while (children[1]->simplify(true, stage, width_hint, sign_hint)) { } if (!children[1]->isConst()) input_error("Failed to evaluate system function `%s' with non-constant argument.\n", RTLIL::unescape_id(str).c_str()); @@ -3650,7 +3747,7 @@ skip_dynamic_range_lvalue_expansion:; // Determine which bits to count for (size_t i = 1; i < children.size(); i++) { AstNode *node = children[i]; - while (node->simplify(true, false, stage, -1, false, false)) { } + while (node->simplify(true, stage, -1, false)) { } if (node->type != AST_CONSTANT) input_error("Failed to evaluate system function `%s' with non-constant control bit argument.\n", str.c_str()); if (node->bits.size() != 1) @@ -3745,7 +3842,7 @@ skip_dynamic_range_lvalue_expansion:; argtypes.push_back(RTLIL::unescape_id(dpi_decl->children.at(i)->str)); args.push_back(children.at(i-2)->clone()); - while (args.back()->simplify(true, false, stage, -1, false, true)) { } + while (args.back()->simplify(true, stage, -1, false)) { } if (args.back()->type != AST_CONSTANT && args.back()->type != AST_REALVALUE) input_error("Failed to evaluate DPI function with non-constant argument.\n"); @@ -3782,12 +3879,12 @@ skip_dynamic_range_lvalue_expansion:; RTLIL::unescape_id(str).c_str(), int(children.size())); AstNode *node_filename = children[0]->clone(); - while (node_filename->simplify(true, false, stage, width_hint, sign_hint, false)) { } + while (node_filename->simplify(true, stage, width_hint, sign_hint)) { } if (node_filename->type != AST_CONSTANT) input_error("Failed to evaluate system function `%s' with non-constant 1st argument.\n", str.c_str()); AstNode *node_memory = children[1]->clone(); - while (node_memory->simplify(true, false, stage, width_hint, sign_hint, false)) { } + while (node_memory->simplify(true, stage, width_hint, sign_hint)) { } if (node_memory->type != AST_IDENTIFIER || node_memory->id2ast == nullptr || node_memory->id2ast->type != AST_MEMORY) input_error("Failed to evaluate system function `%s' with non-memory 2nd argument.\n", str.c_str()); @@ -3795,7 +3892,7 @@ skip_dynamic_range_lvalue_expansion:; if (GetSize(children) > 2) { AstNode *node_addr = children[2]->clone(); - while (node_addr->simplify(true, false, stage, width_hint, sign_hint, false)) { } + while (node_addr->simplify(true, stage, width_hint, sign_hint)) { } if (node_addr->type != AST_CONSTANT) input_error("Failed to evaluate system function `%s' with non-constant 3rd argument.\n", str.c_str()); start_addr = int(node_addr->asInt(false)); @@ -3803,7 +3900,7 @@ skip_dynamic_range_lvalue_expansion:; if (GetSize(children) > 3) { AstNode *node_addr = children[3]->clone(); - while (node_addr->simplify(true, false, stage, width_hint, sign_hint, false)) { } + while (node_addr->simplify(true, stage, width_hint, sign_hint)) { } if (node_addr->type != AST_CONSTANT) input_error("Failed to evaluate system function `%s' with non-constant 4th argument.\n", str.c_str()); finish_addr = int(node_addr->asInt(false)); @@ -3855,13 +3952,14 @@ skip_dynamic_range_lvalue_expansion:; bool require_const_eval = decl->has_const_only_constructs(); bool all_args_const = true; for (auto child : children) { - while (child->simplify(true, false, 1, -1, false, true)) { } + while (child->simplify(true, 1, -1, false)) { } if (child->type != AST_CONSTANT && child->type != AST_REALVALUE) all_args_const = false; } if (all_args_const) { AstNode *func_workspace = decl->clone(); + func_workspace->set_in_param_flag(true); func_workspace->str = prefix_id(prefix, "$result"); newNode = func_workspace->eval_const_function(this, in_param || require_const_eval); delete func_workspace; @@ -3899,7 +3997,7 @@ skip_dynamic_range_lvalue_expansion:; current_scope[wire->str] = wire; current_ast_mod->children.push_back(wire); - while (wire->simplify(true, false, 1, -1, false, false)) { } + while (wire->simplify(true, 1, -1, false)) { } AstNode *lvalue = new AstNode(AST_IDENTIFIER); lvalue->str = wire->str; @@ -3945,7 +4043,7 @@ skip_dynamic_range_lvalue_expansion:; wire->is_input = false; wire->is_output = false; current_ast_mod->children.push_back(wire); - while (wire->simplify(true, false, 1, -1, false, false)) { } + while (wire->simplify(true, 1, -1, false)) { } AstNode *wire_id = new AstNode(AST_IDENTIFIER); wire_id->str = wire->str; @@ -3988,7 +4086,7 @@ skip_dynamic_range_lvalue_expansion:; for (auto c : child->children) wire->children.push_back(c->clone()); } else if (!child->children.empty()) { - while (child->simplify(true, false, stage, -1, false, false)) { } + while (child->simplify(true, stage, -1, 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)) @@ -4006,9 +4104,9 @@ skip_dynamic_range_lvalue_expansion:; wire->is_input = false; wire->is_output = false; wire->is_reg = true; - wire->attributes[ID::nosync] = AstNode::mkconst_int(1, false); + wire->set_attribute(ID::nosync, AstNode::mkconst_int(1, false)); if (child->type == AST_ENUM_ITEM) - wire->attributes[ID::enum_base_type] = child->attributes[ID::enum_base_type]; + wire->set_attribute(ID::enum_base_type, child->attributes[ID::enum_base_type]); wire_cache[child->str] = wire; @@ -4016,7 +4114,7 @@ skip_dynamic_range_lvalue_expansion:; current_ast_mod->children.push_back(wire); } - while (wire->simplify(true, false, 1, -1, false, false)) { } + while (wire->simplify(true, 1, -1, false)) { } if ((child->is_input || child->is_output) && arg_count < children.size()) { @@ -4047,8 +4145,9 @@ skip_dynamic_range_lvalue_expansion:; range->children.push_back(mkconst_int(0, true)); } } + wire->fixup_hierarchy_flags(); // updates the sizing - while (wire->simplify(true, false, 1, -1, false, false)) { } + while (wire->simplify(true, 1, -1, false)) { } delete arg; continue; } @@ -4366,6 +4465,7 @@ replace_fcall_later:; newNode->filename = filename; newNode->location = location; newNode->cloneInto(this); + fixup_hierarchy_flags(); delete newNode; did_something = true; } @@ -4956,18 +5056,18 @@ bool AstNode::mem2reg_as_needed_pass2(pool &mem2reg_set, AstNode *mod, wire_addr->str = id_addr; wire_addr->is_reg = true; wire_addr->was_checked = true; - wire_addr->attributes[ID::nosync] = AstNode::mkconst_int(1, false); + wire_addr->set_attribute(ID::nosync, AstNode::mkconst_int(1, false)); mod->children.push_back(wire_addr); - while (wire_addr->simplify(true, false, 1, -1, false, false)) { } + while (wire_addr->simplify(true, 1, -1, false)) { } AstNode *wire_data = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(mem_width-1, true), mkconst_int(0, true))); wire_data->str = id_data; wire_data->is_reg = true; wire_data->was_checked = true; wire_data->is_signed = mem_signed; - wire_data->attributes[ID::nosync] = AstNode::mkconst_int(1, false); + wire_data->set_attribute(ID::nosync, AstNode::mkconst_int(1, false)); mod->children.push_back(wire_data); - while (wire_data->simplify(true, false, 1, -1, false, false)) { } + while (wire_data->simplify(true, 1, -1, false)) { } log_assert(block != NULL); size_t assign_idx = 0; @@ -4994,6 +5094,10 @@ bool AstNode::mem2reg_as_needed_pass2(pool &mem2reg_set, AstNode *mod, cond_node->children[1]->children.push_back(assign_reg); case_node->children.push_back(cond_node); } + + // fixup on the full hierarchy below case_node + case_node->fixup_hierarchy_flags(true); + block->children.insert(block->children.begin()+assign_idx+2, case_node); children[0]->delete_children(); @@ -5003,6 +5107,7 @@ bool AstNode::mem2reg_as_needed_pass2(pool &mem2reg_set, AstNode *mod, type = AST_ASSIGN_EQ; children[0]->was_checked = true; + fixup_hierarchy_flags(); did_something = true; } @@ -5073,9 +5178,9 @@ bool AstNode::mem2reg_as_needed_pass2(pool &mem2reg_set, AstNode *mod, wire_addr->is_reg = true; wire_addr->was_checked = true; if (block) - wire_addr->attributes[ID::nosync] = AstNode::mkconst_int(1, false); + wire_addr->set_attribute(ID::nosync, AstNode::mkconst_int(1, false)); mod->children.push_back(wire_addr); - while (wire_addr->simplify(true, false, 1, -1, false, false)) { } + while (wire_addr->simplify(true, 1, -1, false)) { } AstNode *wire_data = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(mem_width-1, true), mkconst_int(0, true))); wire_data->str = id_data; @@ -5083,9 +5188,9 @@ bool AstNode::mem2reg_as_needed_pass2(pool &mem2reg_set, AstNode *mod, wire_data->was_checked = true; wire_data->is_signed = mem_signed; if (block) - wire_data->attributes[ID::nosync] = AstNode::mkconst_int(1, false); + wire_data->set_attribute(ID::nosync, AstNode::mkconst_int(1, false)); mod->children.push_back(wire_data); - while (wire_data->simplify(true, false, 1, -1, false, false)) { } + while (wire_data->simplify(true, 1, -1, false)) { } AstNode *assign_addr = new AstNode(block ? AST_ASSIGN_EQ : AST_ASSIGN, new AstNode(AST_IDENTIFIER), children[0]->children[0]->clone()); assign_addr->children[0]->str = id_addr; @@ -5117,6 +5222,9 @@ bool AstNode::mem2reg_as_needed_pass2(pool &mem2reg_set, AstNode *mod, cond_node->children[1]->children.push_back(assign_reg); case_node->children.push_back(cond_node); + // fixup on the full hierarchy below case_node + case_node->fixup_hierarchy_flags(true); + if (block) { size_t assign_idx = 0; @@ -5128,10 +5236,10 @@ bool AstNode::mem2reg_as_needed_pass2(pool &mem2reg_set, AstNode *mod, } else { - AstNode *proc = new AstNode(AST_ALWAYS, new AstNode(AST_BLOCK)); - proc->children[0]->children.push_back(case_node); + AstNode *proc = new AstNode(AST_ALWAYS, new AstNode(AST_BLOCK, case_node)); mod->children.push_back(proc); mod->children.push_back(assign_addr); + mod->fixup_hierarchy_flags(); } delete_children(); @@ -5140,8 +5248,10 @@ bool AstNode::mem2reg_as_needed_pass2(pool &mem2reg_set, AstNode *mod, str = id_data; } - if (bit_part_sel) + if (bit_part_sel) { children.push_back(bit_part_sel); + fixup_hierarchy_flags(); + } did_something = true; } @@ -5263,7 +5373,7 @@ bool AstNode::replace_variables(std::map &varia } if (!children.at(0)->replace_variables(variables, fcall, must_succeed)) return false; - while (simplify(true, false, 1, -1, false, true)) { } + while (simplify(true, 1, -1, false)) { } if (!children.at(0)->range_valid) { if (!must_succeed) return false; @@ -5304,6 +5414,7 @@ AstNode *AstNode::eval_const_function(AstNode *fcall, bool must_succeed) { block->children.push_back(child->clone()); } + block->set_in_param_flag(true); while (!block->children.empty()) { @@ -5318,7 +5429,7 @@ AstNode *AstNode::eval_const_function(AstNode *fcall, bool must_succeed) if (stmt->type == AST_WIRE) { - while (stmt->simplify(true, false, 1, -1, false, true)) { } + while (stmt->simplify(true, 1, -1, false)) { } if (!stmt->range_valid) { if (!must_succeed) goto finished; @@ -5362,7 +5473,7 @@ AstNode *AstNode::eval_const_function(AstNode *fcall, bool must_succeed) if (stmt->type == AST_LOCALPARAM) { - while (stmt->simplify(true, false, 1, -1, false, true)) { } + while (stmt->simplify(true, 1, -1, false)) { } current_scope[stmt->str] = stmt; @@ -5379,7 +5490,7 @@ AstNode *AstNode::eval_const_function(AstNode *fcall, bool must_succeed) goto finished; if (!stmt->children.at(1)->replace_variables(variables, fcall, must_succeed)) goto finished; - while (stmt->simplify(true, false, 1, -1, false, true)) { } + while (stmt->simplify(true, 1, -1, false)) { } if (stmt->type != AST_ASSIGN_EQ) continue; @@ -5446,7 +5557,8 @@ AstNode *AstNode::eval_const_function(AstNode *fcall, bool must_succeed) AstNode *cond = stmt->children.at(0)->clone(); if (!cond->replace_variables(variables, fcall, must_succeed)) goto finished; - while (cond->simplify(true, false, 1, -1, false, true)) { } + cond->set_in_param_flag(true); + while (cond->simplify(true, 1, -1, false)) { } if (cond->type != AST_CONSTANT) { if (!must_succeed) @@ -5471,7 +5583,8 @@ AstNode *AstNode::eval_const_function(AstNode *fcall, bool must_succeed) AstNode *num = stmt->children.at(0)->clone(); if (!num->replace_variables(variables, fcall, must_succeed)) goto finished; - while (num->simplify(true, false, 1, -1, false, true)) { } + num->set_in_param_flag(true); + while (num->simplify(true, 1, -1, false)) { } if (num->type != AST_CONSTANT) { if (!must_succeed) @@ -5494,7 +5607,8 @@ AstNode *AstNode::eval_const_function(AstNode *fcall, bool must_succeed) AstNode *expr = stmt->children.at(0)->clone(); if (!expr->replace_variables(variables, fcall, must_succeed)) goto finished; - while (expr->simplify(true, false, 1, -1, false, true)) { } + expr->set_in_param_flag(true); + while (expr->simplify(true, 1, -1, false)) { } AstNode *sel_case = NULL; for (size_t i = 1; i < stmt->children.size(); i++) @@ -5514,7 +5628,8 @@ AstNode *AstNode::eval_const_function(AstNode *fcall, bool must_succeed) goto finished; cond = new AstNode(AST_EQ, expr->clone(), cond); - while (cond->simplify(true, false, 1, -1, false, true)) { } + cond->set_in_param_flag(true); + while (cond->simplify(true, 1, -1, false)) { } if (cond->type != AST_CONSTANT) { if (!must_succeed) @@ -5549,6 +5664,7 @@ AstNode *AstNode::eval_const_function(AstNode *fcall, bool must_succeed) block->children.erase(block->children.begin()); block->children.insert(block->children.begin(), stmt->children.begin(), stmt->children.end()); stmt->children.clear(); + block->fixup_hierarchy_flags(); delete stmt; continue; } @@ -5583,7 +5699,7 @@ void AstNode::allocateDefaultEnumValues() int last_enum_int = -1; for (auto node : children) { log_assert(node->type==AST_ENUM_ITEM); - node->attributes[ID::enum_base_type] = mkconst_str(str); + node->set_attribute(ID::enum_base_type, mkconst_str(str)); for (size_t i = 0; i < node->children.size(); i++) { switch (node->children[i]->type) { case AST_NONE: diff --git a/frontends/verific/verific.cc b/frontends/verific/verific.cc index b0d789d8fa3..5a6cd4044a0 100644 --- a/frontends/verific/verific.cc +++ b/frontends/verific/verific.cc @@ -52,6 +52,7 @@ USING_YOSYS_NAMESPACE #ifdef VERIFIC_VHDL_SUPPORT #include "vhdl_file.h" #include "VhdlUnits.h" +#include "NameSpace.h" #endif #ifdef VERIFIC_EDIF_SUPPORT @@ -74,7 +75,7 @@ USING_YOSYS_NAMESPACE # error "Only YosysHQ flavored Verific is supported. Please contact office@yosyshq.com for commercial support for Yosys+Verific." #endif -#if YOSYSHQ_VERIFIC_API_VERSION < 20210801 +#if YOSYSHQ_VERIFIC_API_VERSION < 20230901 # error "Please update your version of YosysHQ flavored Verific." #endif @@ -114,7 +115,9 @@ void msg_func(msg_type_t msg_type, const char *message_id, linefile_type linefil if (log_verific_callback) { string full_message = stringf("%s%s\n", message_prefix.c_str(), message.c_str()); - log_verific_callback(int(msg_type), message_id, LineFile::GetFileName(linefile), LineFile::GetLineNo(linefile), full_message.c_str()); + log_verific_callback(int(msg_type), message_id, LineFile::GetFileName(linefile), + linefile ? linefile->GetLeftLine() : 0, linefile ? linefile->GetLeftCol() : 0, + linefile ? linefile->GetRightLine() : 0, linefile ? linefile->GetRightCol() : 0, full_message.c_str()); } else { if (msg_type == VERIFIC_ERROR || msg_type == VERIFIC_WARNING || msg_type == VERIFIC_PROGRAM_ERROR) log_warning_noprefix("%s%s\n", message_prefix.c_str(), message.c_str()); @@ -125,7 +128,7 @@ void msg_func(msg_type_t msg_type, const char *message_id, linefile_type linefil verific_error_msg = message; } -void set_verific_logging(void (*cb)(int msg_type, const char *message_id, const char* file_path, unsigned int line_no, const char *msg)) +void set_verific_logging(void (*cb)(int msg_type, const char *message_id, const char* file_path, unsigned int left_line, unsigned int left_col, unsigned int right_line, unsigned int right_col, const char *msg)) { Message::SetConsoleOutput(0); Message::RegisterCallBackMsg(msg_func); @@ -251,13 +254,21 @@ static const RTLIL::Const verific_const(const char *value, bool allow_string = t return c; } +static const std::string verific_unescape(const char *value) +{ + std::string val = std::string(value); + if (val.size()>1 && val[0]=='\"' && val.back()=='\"') + return val.substr(1,val.size()-2); + return value; +} + void VerificImporter::import_attributes(dict &attributes, DesignObj *obj, Netlist *nl) { MapIter mi; Att *attr; if (obj->Linefile()) - attributes[ID::src] = stringf("%s:%d", LineFile::GetFileName(obj->Linefile()), LineFile::GetLineNo(obj->Linefile())); + attributes[ID::src] = stringf("%s:%d.%d-%d.%d", LineFile::GetFileName(obj->Linefile()), obj->Linefile()->GetLeftLine(), obj->Linefile()->GetLeftCol(), obj->Linefile()->GetRightLine(), obj->Linefile()->GetRightCol()); FOREACH_ATTRIBUTE(obj, mi, attr) { if (attr->Key()[0] == ' ' || attr->Value() == nullptr) @@ -1103,6 +1114,43 @@ bool VerificImporter::import_netlist_instance_cells(Instance *inst, RTLIL::IdStr return true; } + if (inst->Type() == OPER_YOSYSHQ_SET_TAG) + { + RTLIL::SigSpec sig_expr = operatorInport(inst, "expr"); + RTLIL::SigSpec sig_set_mask = operatorInport(inst, "set_mask"); + RTLIL::SigSpec sig_clr_mask = operatorInport(inst, "clr_mask"); + RTLIL::SigSpec sig_o = operatorOutput(inst); + std::string tag = inst->GetAtt("tag") ? verific_unescape(inst->GetAttValue("tag")) : ""; + module->connect(sig_o, module->SetTag(new_verific_id(inst), tag, sig_expr, sig_set_mask, sig_clr_mask)); + return true; + } + if (inst->Type() == OPER_YOSYSHQ_GET_TAG) + { + std::string tag = inst->GetAtt("tag") ? verific_unescape(inst->GetAttValue("tag")) : ""; + module->connect(operatorOutput(inst),module->GetTag(new_verific_id(inst), tag, operatorInput(inst))); + return true; + } + if (inst->Type() == OPER_YOSYSHQ_OVERWRITE_TAG) + { + RTLIL::SigSpec sig_signal = operatorInport(inst, "signal"); + RTLIL::SigSpec sig_set_mask = operatorInport(inst, "set_mask"); + RTLIL::SigSpec sig_clr_mask = operatorInport(inst, "clr_mask"); + std::string tag = inst->GetAtt("tag") ? verific_unescape(inst->GetAttValue("tag")) : ""; + module->addOverwriteTag(new_verific_id(inst), tag, sig_signal, sig_set_mask, sig_clr_mask); + return true; + } + if (inst->Type() == OPER_YOSYSHQ_ORIGINAL_TAG) + { + std::string tag = inst->GetAtt("tag") ? verific_unescape(inst->GetAttValue("tag")) : ""; + module->connect(operatorOutput(inst),module->OriginalTag(new_verific_id(inst), tag, operatorInput(inst))); + return true; + } + if (inst->Type() == OPER_YOSYSHQ_FUTURE_FF) + { + module->connect(operatorOutput(inst),module->FutureFF(new_verific_id(inst), operatorInput(inst))); + return true; + } + #undef IN #undef IN1 #undef IN2 @@ -1230,9 +1278,24 @@ void VerificImporter::import_netlist(RTLIL::Design *design, Netlist *nl, std::ma log("Importing module %s.\n", RTLIL::id2cstr(module->name)); } import_attributes(module->attributes, nl, nl); + module->set_string_attribute(ID::hdlname, nl->CellBaseName()); +#ifdef VERIFIC_VHDL_SUPPORT + if (nl->IsFromVhdl()) { + NameSpace name_space(0); + char *architecture_name = name_space.ReName(nl->Name()) ; + module->set_string_attribute(ID(architecture), (architecture_name) ? architecture_name : nl->Name()); + } +#endif + const char *param_name ; + const char *param_value ; + MapIter mi; + FOREACH_PARAMETER_OF_NETLIST(nl, mi, param_name, param_value) { + module->avail_parameters(RTLIL::escape_id(param_name)); + module->parameter_default_values[RTLIL::escape_id(param_name)] = verific_const(param_value); + } SetIter si; - MapIter mi, mi2; + MapIter mi2; Port *port; PortBus *portbus; Net *net; @@ -1284,7 +1347,12 @@ void VerificImporter::import_netlist(RTLIL::Design *design, Netlist *nl, std::ma wire->start_offset = min(portbus->LeftIndex(), portbus->RightIndex()); wire->upto = portbus->IsUp(); import_attributes(wire->attributes, portbus, nl); - + SetIter si ; + Port *port ; + FOREACH_PORT_OF_PORTBUS(portbus, si, port) { + import_attributes(wire->attributes, port->GetNet(), nl); + break; + } bool portbus_input = portbus->GetDir() == DIR_INOUT || portbus->GetDir() == DIR_IN; if (portbus_input) wire->port_input = true; @@ -2443,51 +2511,71 @@ std::string verific_import(Design *design, const std::mapGetModule(top.c_str(), 1); - if (veri_module) { - veri_modules.InsertLast(veri_module); - if (veri_module->IsConfiguration()) { - VeriConfiguration *cfg = (VeriConfiguration*)veri_module; - VeriName *module_name = (VeriName*)cfg->GetTopModuleNames()->GetLast(); - VeriLibrary *lib = veri_module->GetLibrary() ; - if (module_name && module_name->IsHierName()) { - VeriName *prefix = module_name->GetPrefix() ; - const char *lib_name = (prefix) ? prefix->GetName() : 0 ; - if (!Strings::compare("work", lib_name)) lib = veri_file::GetLibrary(lib_name, 1) ; + +#ifdef YOSYSHQ_VERIFIC_EXTENSIONS + for (int static_elaborate = 1; static_elaborate >= 0; static_elaborate--) +#endif + { + Array veri_modules, vhdl_units; + + if (veri_lib) { + VeriModule *veri_module = veri_lib->GetModule(top.c_str(), 1); + if (veri_module) { + veri_modules.InsertLast(veri_module); + if (veri_module->IsConfiguration()) { + VeriConfiguration *cfg = (VeriConfiguration*)veri_module; + VeriName *module_name = (VeriName*)cfg->GetTopModuleNames()->GetLast(); + VeriLibrary *lib = veri_module->GetLibrary() ; + if (module_name && module_name->IsHierName()) { + VeriName *prefix = module_name->GetPrefix() ; + const char *lib_name = (prefix) ? prefix->GetName() : 0 ; + if (!Strings::compare("work", lib_name)) lib = veri_file::GetLibrary(lib_name, 1) ; + } + if (lib && module_name) + top = lib->GetModule(module_name->GetName(), 1)->GetName(); } - if (lib && module_name) - top = lib->GetModule(module_name->GetName(), 1)->GetName(); } - } - // Also elaborate all root modules since they may contain bind statements - MapIter mi; - FOREACH_VERILOG_MODULE_IN_LIBRARY(veri_lib, mi, veri_module) { - if (!veri_module->IsRootModule()) continue; - veri_modules.InsertLast(veri_module); +#ifdef YOSYSHQ_VERIFIC_EXTENSIONS + if (!static_elaborate) +#endif + { + // Also elaborate all root modules since they may contain bind statements + MapIter mi; + FOREACH_VERILOG_MODULE_IN_LIBRARY(veri_lib, mi, veri_module) { + if (!veri_module->IsRootModule()) continue; + veri_modules.InsertLast(veri_module); + } + } } - } #ifdef VERIFIC_VHDL_SUPPORT - if (vhdl_lib) { - VhdlDesignUnit *vhdl_unit = vhdl_lib->GetPrimUnit(top.c_str()); - if (vhdl_unit) - vhdl_units.InsertLast(vhdl_unit); - } + if (vhdl_lib) { + VhdlDesignUnit *vhdl_unit = vhdl_lib->GetPrimUnit(top.c_str()); + if (vhdl_unit) + vhdl_units.InsertLast(vhdl_unit); + } +#endif + +#ifdef YOSYSHQ_VERIFIC_EXTENSIONS + if (static_elaborate) { + VerificExtensions::ElaborateAndRewrite("work", &veri_modules, &vhdl_units, &verific_params); + verific_error_msg.clear(); + continue; + } #endif - netlists = hier_tree::Elaborate(&veri_modules, &vhdl_units, &verific_params); + + netlists = hier_tree::Elaborate(&veri_modules, &vhdl_units, &verific_params); + } } Netlist *nl; @@ -2500,7 +2588,7 @@ std::string verific_import(Design *design, const std::mapAddAtt(new Att(" \\top", NULL)); nl_todo.emplace(nl->CellBaseName(), nl); - cell_name = nl->Owner()->Name(); + cell_name = nl->CellBaseName(); } if (top.empty()) cell_name = top; @@ -2522,7 +2610,7 @@ std::string verific_import(Design *design, const std::mapfirst) == 0) { VerificImporter importer(false, false, false, false, false, false, false); nl_done[it->first] = it->second; - importer.import_netlist(design, nl, nl_todo, nl->Owner()->Name() == cell_name); + importer.import_netlist(design, nl, nl_todo, nl->CellBaseName() == cell_name); } nl_todo.erase(it); } @@ -2951,6 +3039,9 @@ struct VerificPass : public Pass { RuntimeFlags::SetVar("db_infer_wide_operators", 1); RuntimeFlags::SetVar("db_infer_set_reset_registers", 0); + // Properly respect order of read and write for rams + RuntimeFlags::SetVar("db_change_inplace_ram_blocking_write_before_read", 1); + RuntimeFlags::SetVar("veri_extract_dualport_rams", 0); RuntimeFlags::SetVar("veri_extract_multiport_rams", 1); RuntimeFlags::SetVar("veri_allow_any_ram_in_loop", 1); @@ -3008,6 +3099,7 @@ struct VerificPass : public Pass { int argidx = 1; std::string work = "work"; bool is_work_set = false; + (void)is_work_set; veri_file::RegisterCallBackVerificStream(&verific_read_cb); if (GetSize(args) > argidx && (args[argidx] == "-set-error" || args[argidx] == "-set-warning" || @@ -3085,7 +3177,20 @@ struct VerificPass : public Pass { } veri_file::RemoveAllLOptions(); - veri_file::AddLOption("work"); + for (int i = argidx; i < GetSize(args); i++) + { + if (args[i] == "-work" && i+1 < GetSize(args)) { + work = args[++i]; + is_work_set = true; + continue; + } + if (args[i] == "-L" && i+1 < GetSize(args)) { + ++i; + continue; + } + break; + } + veri_file::AddLOption(work.c_str()); for (int i = argidx; i < GetSize(args); i++) { if (args[i] == "-work" && i+1 < GetSize(args)) { @@ -3093,7 +3198,7 @@ struct VerificPass : public Pass { continue; } if (args[i] == "-L" && i+1 < GetSize(args)) { - if (args[++i] == "work") + if (args[++i] == work) veri_file::RemoveAllLOptions(); continue; } @@ -3526,15 +3631,16 @@ struct VerificPass : public Pass { std::set top_mod_names; + if (mode_all) + { + #ifdef YOSYSHQ_VERIFIC_EXTENSIONS - VerificExtensions::ElaborateAndRewrite(work, ¶meters); - verific_error_msg.clear(); + VerificExtensions::ElaborateAndRewrite(work, ¶meters); + verific_error_msg.clear(); #endif - if (!ppfile.empty()) - veri_file::PrettyPrint(ppfile.c_str(), nullptr, work.c_str()); + if (!ppfile.empty()) + veri_file::PrettyPrint(ppfile.c_str(), nullptr, work.c_str()); - if (mode_all) - { log("Running hier_tree::ElaborateAll().\n"); VeriLibrary *veri_lib = veri_file::GetLibrary(work.c_str(), 1); @@ -3559,67 +3665,93 @@ struct VerificPass : public Pass { if (argidx == GetSize(args)) cmd_error(args, argidx, "No top module specified.\n"); - VeriLibrary* veri_lib = veri_file::GetLibrary(work.c_str(), 1); + Array *netlists = nullptr; + +#ifdef YOSYSHQ_VERIFIC_EXTENSIONS + for (int static_elaborate = 1; static_elaborate >= 0; static_elaborate--) +#endif + { + + VeriLibrary* veri_lib = veri_file::GetLibrary(work.c_str(), 1); #ifdef VERIFIC_VHDL_SUPPORT - VhdlLibrary *vhdl_lib = vhdl_file::GetLibrary(work.c_str(), 1); + VhdlLibrary *vhdl_lib = vhdl_file::GetLibrary(work.c_str(), 1); #endif - Array veri_modules, vhdl_units; - for (; argidx < GetSize(args); argidx++) - { - const char *name = args[argidx].c_str(); - top_mod_names.insert(name); - - VeriModule *veri_module = veri_lib ? veri_lib->GetModule(name, 1) : nullptr; - if (veri_module) { - if (veri_module->IsConfiguration()) { - log("Adding Verilog configuration '%s' to elaboration queue.\n", name); - veri_modules.InsertLast(veri_module); - - top_mod_names.erase(name); - - VeriConfiguration *cfg = (VeriConfiguration*)veri_module; - VeriName *module_name; - int i; - FOREACH_ARRAY_ITEM(cfg->GetTopModuleNames(), i, module_name) { - VeriLibrary *lib = veri_module->GetLibrary() ; - if (module_name && module_name->IsHierName()) { - VeriName *prefix = module_name->GetPrefix() ; - const char *lib_name = (prefix) ? prefix->GetName() : 0 ; - if (!Strings::compare("work", lib_name)) lib = veri_file::GetLibrary(lib_name, 1) ; + Array veri_modules, vhdl_units; + for (int i = argidx; i < GetSize(args); i++) + { + const char *name = args[i].c_str(); + top_mod_names.insert(name); + + VeriModule *veri_module = veri_lib ? veri_lib->GetModule(name, 1) : nullptr; + if (veri_module) { + if (veri_module->IsConfiguration()) { + log("Adding Verilog configuration '%s' to elaboration queue.\n", name); + veri_modules.InsertLast(veri_module); + + top_mod_names.erase(name); + + VeriConfiguration *cfg = (VeriConfiguration*)veri_module; + VeriName *module_name; + int i; + FOREACH_ARRAY_ITEM(cfg->GetTopModuleNames(), i, module_name) { + VeriLibrary *lib = veri_module->GetLibrary() ; + if (module_name && module_name->IsHierName()) { + VeriName *prefix = module_name->GetPrefix() ; + const char *lib_name = (prefix) ? prefix->GetName() : 0 ; + if (work != lib_name) lib = veri_file::GetLibrary(lib_name, 1) ; + } + if (lib && module_name) + top_mod_names.insert(lib->GetModule(module_name->GetName(), 1)->GetName()); } - if (lib && module_name) - top_mod_names.insert(lib->GetModule(module_name->GetName(), 1)->GetName()); + } else { + log("Adding Verilog module '%s' to elaboration queue.\n", name); + veri_modules.InsertLast(veri_module); } - } else { - log("Adding Verilog module '%s' to elaboration queue.\n", name); - veri_modules.InsertLast(veri_module); + continue; } - continue; - } #ifdef VERIFIC_VHDL_SUPPORT - VhdlDesignUnit *vhdl_unit = vhdl_lib ? vhdl_lib->GetPrimUnit(name) : nullptr; - if (vhdl_unit) { - log("Adding VHDL unit '%s' to elaboration queue.\n", name); - vhdl_units.InsertLast(vhdl_unit); - continue; + VhdlDesignUnit *vhdl_unit = vhdl_lib ? vhdl_lib->GetPrimUnit(name) : nullptr; + if (vhdl_unit) { + log("Adding VHDL unit '%s' to elaboration queue.\n", name); + vhdl_units.InsertLast(vhdl_unit); + continue; + } +#endif + log_error("Can't find module/unit '%s'.\n", name); } + +#ifdef YOSYSHQ_VERIFIC_EXTENSIONS + if (static_elaborate) { + VerificExtensions::ElaborateAndRewrite(work, &veri_modules, &vhdl_units, ¶meters); + verific_error_msg.clear(); #endif - log_error("Can't find module/unit '%s'.\n", name); - } + if (!ppfile.empty()) + veri_file::PrettyPrint(ppfile.c_str(), nullptr, work.c_str()); - if (veri_lib) { - // Also elaborate all root modules since they may contain bind statements - MapIter mi; - VeriModule *veri_module; - FOREACH_VERILOG_MODULE_IN_LIBRARY(veri_lib, mi, veri_module) { - if (!veri_module->IsRootModule()) continue; - veri_modules.InsertLast(veri_module); +#ifdef YOSYSHQ_VERIFIC_EXTENSIONS + continue; + } +#endif + const char *lib_name = nullptr; + SetIter si; + FOREACH_SET_ITEM(veri_file::GetAllLOptions(), si, &lib_name) { + VeriLibrary* veri_lib = veri_file::GetLibrary(lib_name, 0); + if (veri_lib) { + // Also elaborate all root modules since they may contain bind statements + MapIter mi; + VeriModule *veri_module; + FOREACH_VERILOG_MODULE_IN_LIBRARY(veri_lib, mi, veri_module) { + if (!veri_module->IsRootModule()) continue; + veri_modules.InsertLast(veri_module); + } + } } + + log("Running hier_tree::Elaborate().\n"); + netlists = hier_tree::Elaborate(&veri_modules, &vhdl_units, ¶meters); } - log("Running hier_tree::Elaborate().\n"); - Array *netlists = hier_tree::Elaborate(&veri_modules, &vhdl_units, ¶meters); Netlist *nl; int i; @@ -3684,7 +3816,7 @@ struct VerificPass : public Pass { VerificImporter importer(mode_gates, mode_keep, mode_nosva, mode_names, mode_verific, mode_autocover, mode_fullinit); nl_done[it->first] = it->second; - importer.import_netlist(design, nl, nl_todo, top_mod_names.count(nl->Owner()->Name())); + importer.import_netlist(design, nl, nl_todo, top_mod_names.count(nl->CellBaseName())); } nl_todo.erase(it); } diff --git a/frontends/verilog/verilog_parser.y b/frontends/verilog/verilog_parser.y index 1e82940bbbd..cb8c453c087 100644 --- a/frontends/verilog/verilog_parser.y +++ b/frontends/verilog/verilog_parser.y @@ -292,6 +292,65 @@ static void rewriteGenForDeclInit(AstNode *loop) substitute(incr); } +static void ensureAsgnExprAllowed() +{ + if (!sv_mode) + frontend_verilog_yyerror("Assignments within expressions are only supported in SystemVerilog mode."); + if (ast_stack.back()->type != AST_BLOCK) + frontend_verilog_yyerror("Assignments within expressions are only permitted within procedures."); +} + +// add a pre/post-increment/decrement statement +static const AstNode *addIncOrDecStmt(dict *stmt_attr, AstNode *lhs, + dict *op_attr, AST::AstNodeType op, + YYLTYPE begin, YYLTYPE end) +{ + AstNode *one = AstNode::mkconst_int(1, true); + AstNode *rhs = new AstNode(op, lhs->clone(), one); + if (op_attr != nullptr) + append_attr(rhs, op_attr); + AstNode *stmt = new AstNode(AST_ASSIGN_EQ, lhs, rhs); + SET_AST_NODE_LOC(stmt, begin, end); + if (stmt_attr != nullptr) + append_attr(stmt, stmt_attr); + ast_stack.back()->children.push_back(stmt); + return stmt; +} + +// create a pre/post-increment/decrement expression, and add the corresponding statement +static AstNode *addIncOrDecExpr(AstNode *lhs, dict *attr, AST::AstNodeType op, YYLTYPE begin, YYLTYPE end, bool undo) +{ + ensureAsgnExprAllowed(); + const AstNode *stmt = addIncOrDecStmt(nullptr, lhs, attr, op, begin, end); + log_assert(stmt->type == AST_ASSIGN_EQ); + AstNode *expr = stmt->children[0]->clone(); + if (undo) { + AstNode *minus_one = AstNode::mkconst_int(-1, true, 1); + expr = new AstNode(op, expr, minus_one); + } + SET_AST_NODE_LOC(expr, begin, end); + return expr; +} + +// add a binary operator assignment statement, e.g., a += b +static const AstNode *addAsgnBinopStmt(dict *attr, AstNode *lhs, AST::AstNodeType op, AstNode *rhs, YYLTYPE begin, YYLTYPE end) +{ + SET_AST_NODE_LOC(rhs, end, end); + if (op == AST_SHIFT_LEFT || op == AST_SHIFT_RIGHT || + op == AST_SHIFT_SLEFT || op == AST_SHIFT_SRIGHT) { + rhs = new AstNode(AST_TO_UNSIGNED, rhs); + SET_AST_NODE_LOC(rhs, end, end); + } + rhs = new AstNode(op, lhs->clone(), rhs); + AstNode *stmt = new AstNode(AST_ASSIGN_EQ, lhs, rhs); + SET_AST_NODE_LOC(rhs, begin, end); + SET_AST_NODE_LOC(stmt, begin, end); + ast_stack.back()->children.push_back(stmt); + if (attr != nullptr) + append_attr(stmt, attr); + return lhs; +} + %} %define api.prefix {frontend_verilog_yy} @@ -358,7 +417,7 @@ static void rewriteGenForDeclInit(AstNode *loop) %type integer_atom_type integer_vector_type %type attr case_attr %type struct_union -%type asgn_binop +%type asgn_binop inc_or_dec_op %type genvar_identifier %type specify_target @@ -2610,17 +2669,11 @@ simple_behavioral_stmt: SET_AST_NODE_LOC(node, @2, @5); append_attr(node, $1); } | - attr lvalue TOK_INCREMENT { - AstNode *node = new AstNode(AST_ASSIGN_EQ, $2, new AstNode(AST_ADD, $2->clone(), AstNode::mkconst_int(1, true))); - ast_stack.back()->children.push_back(node); - SET_AST_NODE_LOC(node, @2, @3); - append_attr(node, $1); + attr lvalue attr inc_or_dec_op { + addIncOrDecStmt($1, $2, $3, $4, @1, @4); } | - attr lvalue TOK_DECREMENT { - AstNode *node = new AstNode(AST_ASSIGN_EQ, $2, new AstNode(AST_SUB, $2->clone(), AstNode::mkconst_int(1, true))); - ast_stack.back()->children.push_back(node); - SET_AST_NODE_LOC(node, @2, @3); - append_attr(node, $1); + attr inc_or_dec_op attr lvalue { + addIncOrDecStmt($1, $4, $3, $2, @1, @4); } | attr lvalue OP_LE delay expr { AstNode *node = new AstNode(AST_ASSIGN_LE, $2, $5); @@ -2629,18 +2682,7 @@ simple_behavioral_stmt: append_attr(node, $1); } | attr lvalue asgn_binop delay expr { - AstNode *expr_node = $5; - if ($3 == AST_SHIFT_LEFT || $3 == AST_SHIFT_RIGHT || - $3 == AST_SHIFT_SLEFT || $3 == AST_SHIFT_SRIGHT) { - expr_node = new AstNode(AST_TO_UNSIGNED, expr_node); - SET_AST_NODE_LOC(expr_node, @5, @5); - } - AstNode *op_node = new AstNode($3, $2->clone(), expr_node); - AstNode *node = new AstNode(AST_ASSIGN_EQ, $2, op_node); - SET_AST_NODE_LOC(op_node, @2, @5); - SET_AST_NODE_LOC(node, @2, @5); - ast_stack.back()->children.push_back(node); - append_attr(node, $1); + addAsgnBinopStmt($1, $2, $3, $5, @2, @5); }; asgn_binop: @@ -2657,6 +2699,12 @@ asgn_binop: TOK_SSHL_ASSIGN { $$ = AST_SHIFT_SLEFT; } | TOK_SSHR_ASSIGN { $$ = AST_SHIFT_SRIGHT; } ; +inc_or_dec_op: + // NOTE: These should only be permitted in SV mode, but Yosys has + // allowed them in all modes since support for them was added in 2017. + TOK_INCREMENT { $$ = AST_ADD; } | + TOK_DECREMENT { $$ = AST_SUB; } ; + for_initialization: TOK_ID '=' expr { AstNode *ident = new AstNode(AST_IDENTIFIER); @@ -3149,6 +3197,14 @@ expr: $$->children.push_back($6); SET_AST_NODE_LOC($$, @1, @$); append_attr($$, $3); + } | + inc_or_dec_op attr rvalue { + $$ = addIncOrDecExpr($3, $2, $1, @1, @3, false); + } | + // TODO: Attributes are allowed in the middle here, but they create some + // non-trivial conflicts that don't seem worth solving for now. + rvalue inc_or_dec_op { + $$ = addIncOrDecExpr($1, nullptr, $2, @1, @2, true); }; basic_expr: @@ -3436,6 +3492,17 @@ basic_expr: frontend_verilog_yyerror("Static cast is only supported in SystemVerilog mode."); $$ = new AstNode(AST_CAST_SIZE, $1, $4); SET_AST_NODE_LOC($$, @1, @4); + } | + '(' expr '=' expr ')' { + ensureAsgnExprAllowed(); + AstNode *node = new AstNode(AST_ASSIGN_EQ, $2, $4); + ast_stack.back()->children.push_back(node); + SET_AST_NODE_LOC(node, @2, @4); + $$ = $2->clone(); + } | + '(' expr asgn_binop expr ')' { + ensureAsgnExprAllowed(); + $$ = addAsgnBinopStmt(nullptr, $2, $3, $4, @2, @4)-> clone(); }; concat_list: diff --git a/kernel/celltypes.h b/kernel/celltypes.h index 4a0621a7386..cad505d9afd 100644 --- a/kernel/celltypes.h +++ b/kernel/celltypes.h @@ -102,6 +102,11 @@ struct CellTypes setup_type(ID($specify3), {ID::EN, ID::SRC, ID::DST, ID::DAT}, pool(), true); setup_type(ID($specrule), {ID::EN_SRC, ID::EN_DST, ID::SRC, ID::DST}, pool(), true); setup_type(ID($print), {ID::EN, ID::ARGS, ID::TRG}, pool()); + setup_type(ID($set_tag), {ID::A, ID::SET, ID::CLR}, {ID::Y}); + setup_type(ID($get_tag), {ID::A}, {ID::Y}); + setup_type(ID($overwrite_tag), {ID::A, ID::SET, ID::CLR}, pool()); + setup_type(ID($original_tag), {ID::A}, {ID::Y}); + setup_type(ID($future_ff), {ID::A}, {ID::Y}); } void setup_internals_eval() diff --git a/kernel/constids.inc b/kernel/constids.inc index 08b0ecdc2b4..480e2afc6fa 100644 --- a/kernel/constids.inc +++ b/kernel/constids.inc @@ -140,6 +140,7 @@ X(nomem2reg) X(nomeminit) X(nosync) X(nowrshmsk) +X(no_ram) X(no_rw_check) X(O) X(OFFSET) @@ -208,6 +209,7 @@ X(syn_romstyle) X(S_WIDTH) X(T) X(TABLE) +X(TAG) X(techmap_autopurge) X(_TECHMAP_BITS_CONNMAP_) X(_TECHMAP_CELLNAME_) diff --git a/kernel/driver.cc b/kernel/driver.cc index ef8e7792473..c779611e097 100644 --- a/kernel/driver.cc +++ b/kernel/driver.cc @@ -51,40 +51,51 @@ #if !defined(_WIN32) || defined(__MINGW32__) # include -#else +#endif + +USING_YOSYS_NAMESPACE + char *optarg; -int optind = 1, optcur = 1; +int optind = 1, optcur = 1, optopt = 0; int getopt(int argc, char **argv, const char *optstring) { - if (optind >= argc || argv[optind][0] != '-') + if (optind >= argc) return -1; + if (argv[optind][0] != '-' || argv[optind][1] == 0) { + optopt = 1; + optarg = argv[optind++]; + return optopt; + } + bool takes_arg = false; - int opt = argv[optind][optcur]; + optopt = argv[optind][optcur]; + + if (optopt == '-') { + ++optind; + return -1; + } + for (int i = 0; optstring[i]; i++) - if (opt == optstring[i] && optstring[i + 1] == ':') + if (optopt == optstring[i] && optstring[i + 1] == ':') takes_arg = true; if (!takes_arg) { if (argv[optind][++optcur] == 0) optind++, optcur = 1; - return opt; + return optopt; } if (argv[optind][++optcur]) { optarg = argv[optind++] + optcur; optcur = 1; - return opt; + return optopt; } optarg = argv[++optind]; optind++, optcur = 1; - return opt; + return optopt; } -#endif - - -USING_YOSYS_NAMESPACE #ifdef EMSCRIPTEN # include @@ -215,6 +226,7 @@ int main(int argc, char **argv) std::string backend_command = "auto"; std::vector vlog_defines; std::vector passes_commands; + std::vector frontend_files; std::vector plugin_filenames; std::string output_filename = ""; std::string scriptfile = ""; @@ -509,6 +521,9 @@ int main(int argc, char **argv) case 'C': run_tcl_shell = true; break; + case '\001': + frontend_files.push_back(optarg); + break; default: fprintf(stderr, "Run '%s -h' for help.\n", argv[0]); exit(1); @@ -561,17 +576,33 @@ int main(int argc, char **argv) run_pass(vdef_cmd); } - while (optind < argc) - if (run_frontend(argv[optind++], frontend_command)) + if (scriptfile.empty() || !scriptfile_tcl) { + // Without a TCL script, arguments following '--' are also treated as frontend files + for (int i = optind; i < argc; ++i) + frontend_files.push_back(argv[i]); + } + + for (auto it = frontend_files.begin(); it != frontend_files.end(); ++it) { + if (run_frontend((*it).c_str(), frontend_command)) run_shell = false; + } if (!topmodule.empty()) run_pass("hierarchy -top " + topmodule); - if (!scriptfile.empty()) { if (scriptfile_tcl) { #ifdef YOSYS_ENABLE_TCL - if (Tcl_EvalFile(yosys_get_tcl_interp(), scriptfile.c_str()) != TCL_OK) + int tcl_argc = argc - optind; + std::vector script_args; + Tcl_Interp *interp = yosys_get_tcl_interp(); + for (int i = optind; i < argc; ++i) + script_args.push_back(Tcl_NewStringObj(argv[i], strlen(argv[i]))); + + Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argc", 4), NULL, Tcl_NewIntObj(tcl_argc), 0); + Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argv", 4), NULL, Tcl_NewListObj(tcl_argc, script_args.data()), 0); + Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argv0", 5), NULL, Tcl_NewStringObj(scriptfile.c_str(), scriptfile.length()), 0); + + if (Tcl_EvalFile(interp, scriptfile.c_str()) != TCL_OK) log_error("TCL interpreter returned an error: %s\n", Tcl_GetStringResult(yosys_get_tcl_interp())); #else log_error("Can't exectue TCL script: this version of yosys is not built with TCL support enabled.\n"); diff --git a/kernel/fmt.cc b/kernel/fmt.cc index 69bdbb013b0..965e58ebce4 100644 --- a/kernel/fmt.cc +++ b/kernel/fmt.cc @@ -740,7 +740,7 @@ std::string Fmt::render() const log_assert(part.width == 0 || part.padding != '\0'); if (part.justify == FmtPart::RIGHT && buf.size() < part.width) { size_t pad_width = part.width - buf.size(); - if (part.padding == '0' && (buf.front() == '+' || buf.front() == '-')) { + if (part.padding == '0' && (!buf.empty() && (buf.front() == '+' || buf.front() == '-'))) { str += buf.front(); buf.erase(0, 1); } diff --git a/kernel/hashlib.h b/kernel/hashlib.h index b3f99bf730f..9cf43da6ca5 100644 --- a/kernel/hashlib.h +++ b/kernel/hashlib.h @@ -90,6 +90,12 @@ template<> struct hash_ops : hash_int_ops return a; } }; +template<> struct hash_ops : hash_int_ops +{ + static inline unsigned int hash(uint64_t a) { + return mkhash((unsigned int)(a), (unsigned int)(a >> 32)); + } +}; template<> struct hash_ops { static inline bool cmp(const std::string &a, const std::string &b) { @@ -988,7 +994,7 @@ class pool return !operator==(other); } - bool hash() const { + unsigned int hash() const { unsigned int hashval = mkhash_init; for (auto &it : entries) hashval ^= ops.hash(it.udata); diff --git a/kernel/log.cc b/kernel/log.cc index 73e7f16eca5..9a61e8f08b3 100644 --- a/kernel/log.cc +++ b/kernel/log.cc @@ -59,7 +59,7 @@ bool log_quiet_warnings = false; int log_verbose_level; string log_last_error; void (*log_error_atexit)() = NULL; -void (*log_verific_callback)(int msg_type, const char *message_id, const char* file_path, unsigned int line_no, const char *msg) = NULL; +void (*log_verific_callback)(int msg_type, const char *message_id, const char* file_path, unsigned int left_line, unsigned int left_col, unsigned int right_line, unsigned int right_col, const char *msg) = NULL; int log_make_debug = 0; int log_force_debug = 0; diff --git a/kernel/log.h b/kernel/log.h index 78a2e434c7a..e4f06c69d19 100644 --- a/kernel/log.h +++ b/kernel/log.h @@ -131,8 +131,8 @@ void log_header(RTLIL::Design *design, const char *format, ...) YS_ATTRIBUTE(for void log_warning(const char *format, ...) YS_ATTRIBUTE(format(printf, 1, 2)); void log_experimental(const char *format, ...) YS_ATTRIBUTE(format(printf, 1, 2)); -void set_verific_logging(void (*cb)(int msg_type, const char *message_id, const char* file_path, unsigned int line_no, const char *msg)); -extern void (*log_verific_callback)(int msg_type, const char *message_id, const char* file_path, unsigned int line_no, const char *msg); +void set_verific_logging(void (*cb)(int msg_type, const char *message_id, const char* file_path, unsigned int left_line, unsigned int left_col, unsigned int right_line, unsigned int right_col, const char *msg)); +extern void (*log_verific_callback)(int msg_type, const char *message_id, const char* file_path, unsigned int left_line, unsigned int left_col, unsigned int right_line, unsigned int right_col, const char *msg); // Log with filename to report a problem in a source file. void log_file_warning(const std::string &filename, int lineno, const char *format, ...) YS_ATTRIBUTE(format(printf, 3, 4)); diff --git a/kernel/mem.cc b/kernel/mem.cc index 628f6210499..01c866770f5 100644 --- a/kernel/mem.cc +++ b/kernel/mem.cc @@ -148,6 +148,9 @@ void Mem::emit() { for (int j = 0; j < (1 << wr_ports[i].wide_log2); j++) wr_port_xlat.push_back(i); for (auto &port : rd_ports) { + for (auto attr: port.attributes) + if (!cell->has_attribute(attr.first)) + cell->attributes.insert(attr); if (port.cell) { module->remove(port.cell); port.cell = nullptr; @@ -210,6 +213,9 @@ void Mem::emit() { cell->setPort(ID::RD_ADDR, rd_addr); cell->setPort(ID::RD_DATA, rd_data); for (auto &port : wr_ports) { + for (auto attr: port.attributes) + if (!cell->has_attribute(attr.first)) + cell->attributes.insert(attr); if (port.cell) { module->remove(port.cell); port.cell = nullptr; @@ -246,6 +252,9 @@ void Mem::emit() { cell->setPort(ID::WR_ADDR, wr_addr); cell->setPort(ID::WR_DATA, wr_data); for (auto &init : inits) { + for (auto attr: init.attributes) + if (!cell->has_attribute(attr.first)) + cell->attributes.insert(attr); if (init.cell) { module->remove(init.cell); init.cell = nullptr; @@ -1252,12 +1261,12 @@ void Mem::prepare_wr_merge(int idx1, int idx2, FfInitVals *initvals) { // If transparent with only one, emulate it, and remove the collision-X // flag that emulate_transparency will set (to align with the other port). if (rport.transparency_mask[idx1]) { - emulate_transparency(i, idx1, initvals); + emulate_transparency(idx1, i, initvals); rport.collision_x_mask[idx1] = false; continue; } if (rport.transparency_mask[idx2]) { - emulate_transparency(i, idx2, initvals); + emulate_transparency(idx2, i, initvals); rport.collision_x_mask[idx2] = false; continue; } diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 7a59c526275..ffbd4b3ff0c 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -313,18 +313,33 @@ RTLIL::Const RTLIL::Const::from_string(const std::string &str) std::string RTLIL::Const::decode_string() const { - std::string string; - string.reserve(GetSize(bits)/8); - for (int i = 0; i < GetSize(bits); i += 8) { + const int n = GetSize(bits); + const int n_over_8 = n / 8; + std::string s; + s.reserve(n_over_8); + int i = n_over_8 * 8; + if (i < n) { char ch = 0; - for (int j = 0; j < 8 && i + j < int (bits.size()); j++) - if (bits[i + j] == RTLIL::State::S1) + for (int j = 0; j < (n - i); j++) { + if (bits[i + j] == RTLIL::State::S1) { ch |= 1 << j; + } + } + if (ch != 0) + s.append({ch}); + } + i -= 8; + for (; i >= 0; i -= 8) { + char ch = 0; + for (int j = 0; j < 8; j++) { + if (bits[i + j] == RTLIL::State::S1) { + ch |= 1 << j; + } + } if (ch != 0) - string.append({ch}); + s.append({ch}); } - std::reverse(string.begin(), string.end()); - return string; + return s; } bool RTLIL::Const::is_fully_zero() const @@ -1828,6 +1843,40 @@ namespace { ID($_DLATCHSR_PNN_), ID($_DLATCHSR_PNP_), ID($_DLATCHSR_PPN_), ID($_DLATCHSR_PPP_))) { port(ID::E,1); port(ID::S,1); port(ID::R,1); port(ID::D,1); port(ID::Q,1); check_expected(); return; } + if (cell->type.in(ID($set_tag))) { + param(ID::WIDTH); + param(ID::TAG); + port(ID::A, param(ID::WIDTH)); + port(ID::SET, param(ID::WIDTH)); + port(ID::CLR, param(ID::WIDTH)); + port(ID::Y, param(ID::WIDTH)); + check_expected(); + return; + } + if (cell->type.in(ID($get_tag),ID($original_tag))) { + param(ID::WIDTH); + param(ID::TAG); + port(ID::A, param(ID::WIDTH)); + port(ID::Y, param(ID::WIDTH)); + check_expected(); + return; + } + if (cell->type.in(ID($overwrite_tag))) { + param(ID::WIDTH); + param(ID::TAG); + port(ID::A, param(ID::WIDTH)); + port(ID::SET, param(ID::WIDTH)); + port(ID::CLR, param(ID::WIDTH)); + check_expected(); + return; + } + if (cell->type.in(ID($future_ff))) { + param(ID::WIDTH); + port(ID::A, param(ID::WIDTH)); + port(ID::Y, param(ID::WIDTH)); + check_expected(); + return; + } error(__LINE__); } }; @@ -2643,6 +2692,19 @@ RTLIL::Cell* RTLIL::Module::addPow(RTLIL::IdString name, const RTLIL::SigSpec &s return cell; } +RTLIL::Cell* RTLIL::Module::addFa(RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, const RTLIL::SigSpec &sig_c, const RTLIL::SigSpec &sig_x, const RTLIL::SigSpec &sig_y, const std::string &src) +{ + RTLIL::Cell *cell = addCell(name, ID($fa)); + cell->parameters[ID::WIDTH] = sig_a.size(); + cell->setPort(ID::A, sig_a); + cell->setPort(ID::B, sig_b); + cell->setPort(ID::C, sig_c); + cell->setPort(ID::X, sig_x); + cell->setPort(ID::Y, sig_y); + cell->set_src_attribute(src); + return cell; +} + RTLIL::Cell* RTLIL::Module::addSlice(RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_y, RTLIL::Const offset, const std::string &src) { RTLIL::Cell *cell = addCell(name, ID($slice)); @@ -3246,6 +3308,80 @@ RTLIL::SigSpec RTLIL::Module::Initstate(RTLIL::IdString name, const std::string return sig; } +RTLIL::SigSpec RTLIL::Module::SetTag(RTLIL::IdString name, const std::string &tag, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_s, const RTLIL::SigSpec &sig_c, const std::string &src) +{ + RTLIL::SigSpec sig = addWire(NEW_ID, sig_a.size()); + Cell *cell = addCell(name, ID($set_tag)); + cell->parameters[ID::WIDTH] = sig_a.size(); + cell->parameters[ID::TAG] = tag; + cell->setPort(ID::A, sig_a); + cell->setPort(ID::SET, sig_s); + cell->setPort(ID::CLR, sig_c); + cell->setPort(ID::Y, sig); + cell->set_src_attribute(src); + return sig; +} + +RTLIL::Cell* RTLIL::Module::addSetTag(RTLIL::IdString name, const std::string &tag, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_s, const RTLIL::SigSpec &sig_c, const RTLIL::SigSpec &sig_y, const std::string &src) +{ + Cell *cell = addCell(name, ID($set_tag)); + cell->parameters[ID::WIDTH] = sig_a.size(); + cell->parameters[ID::TAG] = tag; + cell->setPort(ID::A, sig_a); + cell->setPort(ID::SET, sig_s); + cell->setPort(ID::CLR, sig_c); + cell->setPort(ID::Y, sig_y); + cell->set_src_attribute(src); + return cell; +} + +RTLIL::SigSpec RTLIL::Module::GetTag(RTLIL::IdString name, const std::string &tag, const RTLIL::SigSpec &sig_a, const std::string &src) +{ + RTLIL::SigSpec sig = addWire(NEW_ID, sig_a.size()); + Cell *cell = addCell(name, ID($get_tag)); + cell->parameters[ID::WIDTH] = sig_a.size(); + cell->parameters[ID::TAG] = tag; + cell->setPort(ID::A, sig_a); + cell->setPort(ID::Y, sig); + cell->set_src_attribute(src); + return sig; +} + +RTLIL::Cell* RTLIL::Module::addOverwriteTag(RTLIL::IdString name, const std::string &tag, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_s, const RTLIL::SigSpec &sig_c, const std::string &src) +{ + RTLIL::Cell *cell = addCell(name, ID($overwrite_tag)); + cell->parameters[ID::WIDTH] = sig_a.size(); + cell->parameters[ID::TAG] = tag; + cell->setPort(ID::A, sig_a); + cell->setPort(ID::SET, sig_s); + cell->setPort(ID::CLR, sig_c); + cell->set_src_attribute(src); + return cell; +} + +RTLIL::SigSpec RTLIL::Module::OriginalTag(RTLIL::IdString name, const std::string &tag, const RTLIL::SigSpec &sig_a, const std::string &src) +{ + RTLIL::SigSpec sig = addWire(NEW_ID, sig_a.size()); + Cell *cell = addCell(name, ID($original_tag)); + cell->parameters[ID::WIDTH] = sig_a.size(); + cell->parameters[ID::TAG] = tag; + cell->setPort(ID::A, sig_a); + cell->setPort(ID::Y, sig); + cell->set_src_attribute(src); + return sig; +} + +RTLIL::SigSpec RTLIL::Module::FutureFF(RTLIL::IdString name, const RTLIL::SigSpec &sig_e, const std::string &src) +{ + RTLIL::SigSpec sig = addWire(NEW_ID, sig_e.size()); + Cell *cell = addCell(name, ID($future_ff)); + cell->parameters[ID::WIDTH] = sig_e.size(); + cell->setPort(ID::A, sig_e); + cell->setPort(ID::Y, sig); + cell->set_src_attribute(src); + return sig; +} + RTLIL::Wire::Wire() { static unsigned int hashidx_count = 123456789; @@ -3923,13 +4059,17 @@ void RTLIL::SigSpec::replace(const RTLIL::SigSpec &pattern, const RTLIL::SigSpec unpack(); other->unpack(); + dict pattern_to_with; for (int i = 0; i < GetSize(pattern.bits_); i++) { if (pattern.bits_[i].wire != NULL) { - for (int j = 0; j < GetSize(bits_); j++) { - if (bits_[j] == pattern.bits_[i]) { - other->bits_[j] = with.bits_[i]; - } - } + pattern_to_with.emplace(pattern.bits_[i], i); + } + } + + for (int j = 0; j < GetSize(bits_); j++) { + auto it = pattern_to_with.find(bits_[j]); + if (it != pattern_to_with.end()) { + other->bits_[j] = with.bits_[it->second]; } } diff --git a/kernel/rtlil.h b/kernel/rtlil.h index a69ce480baf..9c318eac93c 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -308,10 +308,14 @@ namespace RTLIL bool operator!=(const char *rhs) const { return strcmp(c_str(), rhs) != 0; } char operator[](size_t i) const { - const char *p = c_str(); + const char *p = c_str(); +#ifndef NDEBUG for (; i != 0; i--, p++) log_assert(*p != 0); return *p; +#else + return *(p + i); +#endif } std::string substr(size_t pos = 0, size_t len = std::string::npos) const { @@ -1298,6 +1302,8 @@ struct RTLIL::Module : public RTLIL::AttrObject RTLIL::Cell* addModFloor (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); RTLIL::Cell* addPow (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, const RTLIL::SigSpec &sig_y, bool a_signed = false, bool b_signed = false, const std::string &src = ""); + RTLIL::Cell* addFa (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, const RTLIL::SigSpec &sig_c, const RTLIL::SigSpec &sig_x, const RTLIL::SigSpec &sig_y, const std::string &src = ""); + RTLIL::Cell* addLogicNot (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); RTLIL::Cell* addLogicAnd (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); RTLIL::Cell* addLogicOr (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); @@ -1465,6 +1471,13 @@ struct RTLIL::Module : public RTLIL::AttrObject RTLIL::SigSpec Allseq (RTLIL::IdString name, int width = 1, const std::string &src = ""); RTLIL::SigSpec Initstate (RTLIL::IdString name, const std::string &src = ""); + RTLIL::SigSpec SetTag (RTLIL::IdString name, const std::string &tag, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_s, const RTLIL::SigSpec &sig_c, const std::string &src = ""); + RTLIL::Cell* addSetTag (RTLIL::IdString name, const std::string &tag, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_s, const RTLIL::SigSpec &sig_c, const RTLIL::SigSpec &sig_y, const std::string &src = ""); + RTLIL::SigSpec GetTag (RTLIL::IdString name, const std::string &tag, const RTLIL::SigSpec &sig_a, const std::string &src = ""); + RTLIL::Cell* addOverwriteTag (RTLIL::IdString name, const std::string &tag, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_s, const RTLIL::SigSpec &sig_c, const std::string &src = ""); + RTLIL::SigSpec OriginalTag (RTLIL::IdString name, const std::string &tag, const RTLIL::SigSpec &sig_a, const std::string &src = ""); + RTLIL::SigSpec FutureFF (RTLIL::IdString name, const RTLIL::SigSpec &sig_e, const std::string &src = ""); + #ifdef WITH_PYTHON static std::map *get_all_modules(void); #endif diff --git a/kernel/utils.h b/kernel/utils.h index d37f045ff7c..8fa223824da 100644 --- a/kernel/utils.h +++ b/kernel/utils.h @@ -128,41 +128,103 @@ struct stackmap // A simple class for topological sorting // ------------------------------------------------ -template> -struct TopoSort +template , typename OPS = hash_ops> class TopoSort { - bool analyze_loops, found_loops; - std::map, C> database; - std::set> loops; + public: + // We use this ordering of the edges in the adjacency matrix for + // exact compatibility with an older implementation. + struct IndirectCmp { + IndirectCmp(const std::vector &nodes) : node_cmp_(), nodes_(nodes) {} + bool operator()(int a, int b) const + { + log_assert(static_cast(a) < nodes_.size()); + log_assert(static_cast(b) < nodes_.size()); + return node_cmp_(nodes_[a], nodes_[b]); + } + const C node_cmp_; + const std::vector &nodes_; + }; + + bool analyze_loops; + std::map node_to_index; + std::vector> edges; std::vector sorted; + std::set> loops; - TopoSort() + TopoSort() : indirect_cmp(nodes) { analyze_loops = true; found_loops = false; } - void node(T n) + int node(T n) { - if (database.count(n) == 0) - database[n] = std::set(); + auto rv = node_to_index.emplace(n, static_cast(nodes.size())); + if (rv.second) { + nodes.push_back(n); + edges.push_back(std::set(indirect_cmp)); + } + return rv.first->second; } - void edge(T left, T right) + void edge(int l_index, int r_index) { edges[r_index].insert(l_index); } + + void edge(T left, T right) { edge(node(left), node(right)); } + + bool has_node(const T &node) { return node_to_index.find(node) != node_to_index.end(); } + + bool sort() { - node(left); - database[right].insert(left); + log_assert(GetSize(node_to_index) == GetSize(edges)); + log_assert(GetSize(nodes) == GetSize(edges)); + + loops.clear(); + sorted.clear(); + found_loops = false; + + std::vector marked_cells(edges.size(), false); + std::vector active_cells(edges.size(), false); + std::vector active_stack; + sorted.reserve(edges.size()); + + for (const auto &it : node_to_index) + sort_worker(it.second, marked_cells, active_cells, active_stack); + + log_assert(GetSize(sorted) == GetSize(nodes)); + + return !found_loops; } - void sort_worker(const T &n, std::set &marked_cells, std::set &active_cells, std::vector &active_stack) + // Build the more expensive representation of edges for + // a few passes that use it directly. + std::map, C> get_database() { - if (active_cells.count(n)) { + std::map, C> database; + for (size_t i = 0; i < nodes.size(); ++i) { + std::set converted_edge_set; + for (int other_node : edges[i]) { + converted_edge_set.insert(nodes[other_node]); + } + database.emplace(nodes[i], converted_edge_set); + } + return database; + } + + private: + bool found_loops; + std::vector nodes; + const IndirectCmp indirect_cmp; + + void sort_worker(const int root_index, std::vector &marked_cells, std::vector &active_cells, std::vector &active_stack) + { + if (active_cells[root_index]) { found_loops = true; if (analyze_loops) { std::set loop; - for (int i = GetSize(active_stack)-1; i >= 0; i--) { - loop.insert(active_stack[i]); - if (active_stack[i] == n) + for (int i = GetSize(active_stack) - 1; i >= 0; i--) { + const int index = active_stack[i]; + loop.insert(nodes[index]); + if (index == root_index) break; } loops.insert(loop); @@ -170,42 +232,24 @@ struct TopoSort return; } - if (marked_cells.count(n)) + if (marked_cells[root_index]) return; - if (!database.at(n).empty()) - { + if (!edges[root_index].empty()) { if (analyze_loops) - active_stack.push_back(n); - active_cells.insert(n); + active_stack.push_back(root_index); + active_cells[root_index] = true; - for (auto &left_n : database.at(n)) + for (int left_n : edges[root_index]) sort_worker(left_n, marked_cells, active_cells, active_stack); if (analyze_loops) active_stack.pop_back(); - active_cells.erase(n); + active_cells[root_index] = false; } - marked_cells.insert(n); - sorted.push_back(n); - } - - bool sort() - { - loops.clear(); - sorted.clear(); - found_loops = false; - - std::set marked_cells; - std::set active_cells; - std::vector active_stack; - - for (auto &it : database) - sort_worker(it.first, marked_cells, active_cells, active_stack); - - log_assert(GetSize(sorted) == GetSize(database)); - return !found_loops; + marked_cells[root_index] = true; + sorted.push_back(nodes[root_index]); } }; diff --git a/kernel/yosys.cc b/kernel/yosys.cc index bd8dded4b5f..4409dc91ddd 100644 --- a/kernel/yosys.cc +++ b/kernel/yosys.cc @@ -175,48 +175,6 @@ int ceil_log2(int x) #endif } -std::string stringf(const char *fmt, ...) -{ - std::string string; - va_list ap; - - va_start(ap, fmt); - string = vstringf(fmt, ap); - va_end(ap); - - return string; -} - -std::string vstringf(const char *fmt, va_list ap) -{ - std::string string; - char *str = NULL; - -#if defined(_WIN32 )|| defined(__CYGWIN__) - int sz = 64, rc; - while (1) { - va_list apc; - va_copy(apc, ap); - str = (char*)realloc(str, sz); - rc = vsnprintf(str, sz, fmt, apc); - va_end(apc); - if (rc >= 0 && rc < sz) - break; - sz *= 2; - } -#else - if (vasprintf(&str, fmt, ap) < 0) - str = NULL; -#endif - - if (str != NULL) { - string = str; - free(str); - } - - return string; -} - int readsome(std::istream &f, char *s, int n) { int rc = int(f.readsome(s, n)); @@ -1395,8 +1353,12 @@ void shell(RTLIL::Design *design) if ((command = fgets(command_buffer, 4096, stdin)) == NULL) break; #endif - if (command[strspn(command, " \t\r\n")] == 0) + if (command[strspn(command, " \t\r\n")] == 0) { +#if defined(YOSYS_ENABLE_READLINE) || defined(YOSYS_ENABLE_EDITLINE) + free(command); +#endif continue; + } #if defined(YOSYS_ENABLE_READLINE) || defined(YOSYS_ENABLE_EDITLINE) add_history(command); #endif @@ -1418,10 +1380,17 @@ void shell(RTLIL::Design *design) log_reset_stack(); } design->check(); +#if defined(YOSYS_ENABLE_READLINE) || defined(YOSYS_ENABLE_EDITLINE) + if (command) + free(command); +#endif } if (command == NULL) printf("exit\n"); - +#if defined(YOSYS_ENABLE_READLINE) || defined(YOSYS_ENABLE_EDITLINE) + else + free(command); +#endif recursion_counter--; log_cmd_error_throw = false; } diff --git a/kernel/yosys.h b/kernel/yosys.h index 29415ff842e..97a79861e15 100644 --- a/kernel/yosys.h +++ b/kernel/yosys.h @@ -272,8 +272,64 @@ inline void memhasher() { if (memhasher_active) memhasher_do(); } void yosys_banner(); int ceil_log2(int x) YS_ATTRIBUTE(const); + +inline std::string vstringf(const char *fmt, va_list ap) +{ + // For the common case of strings shorter than 128, save a heap + // allocation by using a stack allocated buffer. + const int kBufSize = 128; + char buf[kBufSize]; + buf[0] = '\0'; + va_list apc; + va_copy(apc, ap); + int n = vsnprintf(buf, kBufSize, fmt, apc); + va_end(apc); + if (n < kBufSize) + return std::string(buf); + + std::string string; + char *str = NULL; +#if defined(_WIN32 )|| defined(__CYGWIN__) + int sz = 2 * kBufSize, rc; + while (1) { + va_copy(apc, ap); + str = (char*)realloc(str, sz); + rc = vsnprintf(str, sz, fmt, apc); + va_end(apc); + if (rc >= 0 && rc < sz) + break; + sz *= 2; + } + if (str != NULL) { + string = str; + free(str); + } + return string; +#else + if (vasprintf(&str, fmt, ap) < 0) + str = NULL; + if (str != NULL) { + string = str; + free(str); + } + return string; +#endif +} + std::string stringf(const char *fmt, ...) YS_ATTRIBUTE(format(printf, 1, 2)); -std::string vstringf(const char *fmt, va_list ap); + +inline std::string stringf(const char *fmt, ...) +{ + std::string string; + va_list ap; + + va_start(ap, fmt); + string = vstringf(fmt, ap); + va_end(ap); + + return string; +} + int readsome(std::istream &f, char *s, int n); std::string next_token(std::string &text, const char *sep = " \t\r\n", bool long_strings = false); std::vector split_tokens(const std::string &text, const char *sep = " \t\r\n"); diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc index 29b3a1132ad..d7e572462b0 100644 --- a/passes/cmds/Makefile.inc +++ b/passes/cmds/Makefile.inc @@ -46,3 +46,5 @@ OBJS += passes/cmds/printattrs.o OBJS += passes/cmds/sta.o OBJS += passes/cmds/clean_zerowidth.o OBJS += passes/cmds/xprop.o +OBJS += passes/cmds/dft_tag.o +OBJS += passes/cmds/future.o diff --git a/passes/cmds/autoname.cc b/passes/cmds/autoname.cc index 6019c61534f..737bd3e58be 100644 --- a/passes/cmds/autoname.cc +++ b/passes/cmds/autoname.cc @@ -24,8 +24,8 @@ PRIVATE_NAMESPACE_BEGIN int autoname_worker(Module *module, const dict& wire_score) { - dict> proposed_cell_names; - dict> proposed_wire_names; + dict> proposed_cell_names; + dict> proposed_wire_names; int best_score = -1; for (auto cell : module->selected_cells()) { @@ -36,7 +36,7 @@ int autoname_worker(Module *module, const dict& wire_score) if (bit.wire != nullptr && bit.wire->name[0] != '$') { if (suffix.empty()) suffix = stringf("_%s_%s", log_id(cell->type), log_id(conn.first)); - IdString new_name(bit.wire->name.str() + suffix); + string new_name(bit.wire->name.str() + suffix); int score = wire_score.at(bit.wire); if (cell->output(conn.first)) score = 0; score = 10000*score + new_name.size(); @@ -54,7 +54,7 @@ int autoname_worker(Module *module, const dict& wire_score) if (bit.wire != nullptr && bit.wire->name[0] == '$' && !bit.wire->port_id) { if (suffix.empty()) suffix = stringf("_%s", log_id(conn.first)); - IdString new_name(cell->name.str() + suffix); + string new_name(cell->name.str() + suffix); int score = wire_score.at(bit.wire); if (cell->output(conn.first)) score = 0; score = 10000*score + new_name.size(); @@ -71,7 +71,7 @@ int autoname_worker(Module *module, const dict& wire_score) for (auto &it : proposed_cell_names) { if (best_score*2 < it.second.first) continue; - IdString n = module->uniquify(it.second.second); + IdString n = module->uniquify(IdString(it.second.second)); log_debug("Rename cell %s in %s to %s.\n", log_id(it.first), log_id(module), log_id(n)); module->rename(it.first, n); } @@ -79,7 +79,7 @@ int autoname_worker(Module *module, const dict& wire_score) for (auto &it : proposed_wire_names) { if (best_score*2 < it.second.first) continue; - IdString n = module->uniquify(it.second.second); + IdString n = module->uniquify(IdString(it.second.second)); log_debug("Rename wire %s in %s to %s.\n", log_id(it.first), log_id(module), log_id(n)); module->rename(it.first, n); } diff --git a/passes/cmds/dft_tag.cc b/passes/cmds/dft_tag.cc new file mode 100644 index 00000000000..2b2340dab53 --- /dev/null +++ b/passes/cmds/dft_tag.cc @@ -0,0 +1,1015 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2022 Jannis Harder + * + * 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. + * + */ + +#include "kernel/celltypes.h" +#include "kernel/ff.h" +#include "kernel/modtools.h" +#include "kernel/sigtools.h" +#include "kernel/yosys.h" +#include + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct DftTagOptions { + bool tag_public = false; + bool overwrite_only = false; +}; + +struct DftTagWorker { + Module *module; + DftTagOptions options; + ModWalker modwalker; + SigMap &sigmap; + FfInitVals initvals; + + struct tag_set { + int index = 0; + + tag_set(int index = 0) : index(index) {} + + bool operator<(const tag_set &other) const { return index < other.index; } + bool operator==(const tag_set &other) const { return index == other.index; } + + unsigned int hash() const { return hash_ops::hash(index); } + + bool empty() const { return index == 0; } + }; + + idict> tag_sets; + + pool tmp_tag_set; + dict, tag_set> tag_set_union_cache; + + dict tagged_signals; + + dict> tag_groups; + dict group_of_tag; + pool all_tags; + + pool pending_cells; + std::deque pending_cell_queue; + + dict, SigBit> tag_signals; + + // Uses SigSpec instead of SigBit so we can use coarse grained cells to combine the individual tags + dict, SigSpec> tag_group_signals; + + pool warned_cells; + + DftTagWorker(Module *module, DftTagOptions options) : + module(module), options(options), modwalker(module->design), sigmap(modwalker.sigmap) + { + modwalker.setup(module); + initvals.set(&modwalker.sigmap, module); + tag_sets(tmp_tag_set); + } + + void resolve_overwrites() + { + std::vector overwrite_cells; + std::vector original_cells; + + bool design_changed = false; + + for (auto cell : module->cells()) { + if (cell->type == ID($overwrite_tag)) + overwrite_cells.push_back(cell); + + if (cell->type == ID($original_tag)) + original_cells.push_back(cell); + } + + for (auto cell : overwrite_cells) { + log_debug("Applying $overwrite_tag %s for signal %s\n", log_id(cell->name), log_signal(cell->getPort(ID::A))); + SigSpec orig_signal = cell->getPort(ID::A); + SigSpec interposed_signal = divert_users(orig_signal); + auto *set_tag_cell = module->addSetTag(NEW_ID, cell->getParam(ID::TAG).decode_string(), orig_signal, cell->getPort(ID::SET), cell->getPort(ID::CLR), interposed_signal); + modwalker.add_cell(set_tag_cell); // Make sure the next $overwrite_tag sees the new connections + design_changed = true; + } + + for (auto cell : overwrite_cells) { + module->remove(cell); + } + for (auto cell : original_cells) { + cell->type = ID($get_tag); + } + + if (design_changed) + modwalker.setup(module); + } + + SigSpec divert_users(SigSpec signal) + { + SigSpec signal_mapped = sigmap(signal); + signal_mapped.sort_and_unify(); + if (GetSize(signal_mapped) < GetSize(signal)) + log_warning("Detected $overwrite_tag on signal %s which contains repeated bits, this can result in unexpected behavior.\n", log_signal(signal)); + SigSpec new_wire = module->addWire(NEW_ID, GetSize(signal)); + for (int i = 0; i < GetSize(new_wire); ++i) + divert_users(signal[i], new_wire[i]); + return new_wire; + } + + void divert_users(SigBit driver_bit, SigBit interposed_bit) + { + dict, SigSpec> updated_ports; + // TODO also check module outputs + auto found = modwalker.signal_consumers.find(driver_bit); + if (found == modwalker.signal_consumers.end()) + return; + for (auto &consumer : found->second) { + if (consumer.cell->type.in(ID($original_tag))) + continue; + if (sigmap(consumer.cell->getPort(consumer.port)[consumer.offset]) != driver_bit) + continue; + std::pair key = {consumer.cell, consumer.port}; + auto found_port = updated_ports.find(key); + if (found_port == updated_ports.end()) { + updated_ports.emplace(key, consumer.cell->getPort(consumer.port)); + } + updated_ports[key][consumer.offset] = interposed_bit; + } + for (auto &update : updated_ports) { + update.first.first->setPort(update.first.second, update.second); + modwalker.add_cell(update.first.first); // Make sure the next $overwrite_tag sees the new connections + } + } + + const pool &tag_pool(tag_set set) { return tag_sets[set.index]; } + + tag_set singleton(IdString tag) + { + tmp_tag_set.clear(); + tmp_tag_set.emplace(tag); + return tag_sets(tmp_tag_set); + } + + tag_set merge(tag_set a, tag_set b) + { + if (b < a) + std::swap(a, b); + if (a.empty() || a == b) + return b; + auto found = tag_set_union_cache.find(std::make_pair(a, b)); + if (found == tag_set_union_cache.end()) { + tmp_tag_set.clear(); + auto &a_tags = tag_pool(a); + auto &b_tags = tag_pool(b); + tmp_tag_set.insert(a_tags.begin(), a_tags.end()); + tmp_tag_set.insert(b_tags.begin(), b_tags.end()); + tag_set result = tag_sets(tmp_tag_set); + tag_set_union_cache.emplace(std::make_pair(a, b), result); + return result; + } + return found->second; + } + + tag_set tags(SigBit bit) + { + sigmap.apply(bit); + auto found = tagged_signals.find(bit); + if (found != tagged_signals.end()) + return found->second; + return tag_set(); + } + + tag_set tags(SigSpec sig) + { + tag_set result; + for (auto bit : sig) + result = merge(result, tags(bit)); + return result; + } + + tag_set tags(Cell *cell) + { + tag_set result; + for (auto &conn : cell->connections()) { + if (cell->input(conn.first)) + result = merge(result, tags(conn.second)); + } + return result; + } + + void add_tags(SigBit bit, tag_set new_tags) + { + sigmap.apply(bit); + auto &tags = tagged_signals[bit]; + tag_set merged_tags = merge(tags, new_tags); + if (merged_tags == tags) + return; + tags = merged_tags; + auto it = modwalker.signal_consumers.find(bit); + if (it == modwalker.signal_consumers.end()) + return; + for (auto &consumer : it->second) + if (pending_cells.insert(consumer.cell).second) + pending_cell_queue.push_back(consumer.cell); + } + + void add_tags(SigSpec sig, tag_set new_tags) + { + for (auto bit : sigmap(sig)) + add_tags(bit, new_tags); + } + + void add_tags(Cell *cell, tag_set new_tags) + { + for (auto &conn : cell->connections()) + if (cell->output(conn.first)) + add_tags(conn.second, new_tags); + } + + void forward_tags(SigSpec dst, SigSpec src) + { + log_assert(GetSize(dst) == GetSize(src)); + for (int i = 0; i < GetSize(dst); i++) + add_tags(dst[i], tags(src[i])); + } + + void propagate_tags() + { + for (auto cell : module->cells()) { + if (cell->type == ID($set_tag)) { + pending_cells.insert(cell); + pending_cell_queue.push_back(cell); + } + } + + while (!pending_cell_queue.empty()) { + Cell *cell = pending_cell_queue.front(); + pending_cell_queue.pop_front(); + pending_cells.erase(cell); + + propagate_tags(cell); + } + } + + SigBit tag_signal(IdString tag, SigBit bit) + { + sigmap.apply(bit); + if (!bit.is_wire()) + return State::S0; // Constant value - no tags + + auto found = tag_signals.find(std::make_pair(tag, bit)); + if (found != tag_signals.end()) + return found->second; + + if (!tag_pool(tags(bit)).count(tag)) + return State::S0; // Statically known to not have this tag + + // TODO handle module inputs + auto drivers = modwalker.signal_drivers.find(bit); + if (drivers == modwalker.signal_drivers.end() || drivers->second.empty()) + return State::S0; // No driver - no tags + + log_assert(drivers->second.size() == 1); + auto driver = *drivers->second.begin(); + + emit_tag_signals(tag, driver.cell); + + found = tag_signals.find(std::make_pair(tag, bit)); + log_assert(found != tag_signals.end()); + return found->second; + } + + SigSpec tag_signal(IdString tag, SigSpec sig) + { + SigSpec result; + for (auto bit : sig) + result.append(tag_signal(tag, bit)); + return result; + } + + SigSpec tag_group_signal(IdString tag_group, SigSpec sig) + { + sigmap.apply(sig); + if (sig.is_fully_const() || tag_groups.count(tag_group) == 0) + return Const(0, GetSize(sig)); + + auto found = tag_group_signals.find(std::make_pair(tag_group, sig)); + if (found != tag_group_signals.end()) + return found->second; + + SigSpec combined; + + for (auto &tag : tag_groups[tag_group]) { + auto tag_sig = tag_signal(tag, sig); + + if (!GetSize(combined)) + combined = tag_sig; + else + combined = autoOr(NEW_ID, combined, tag_sig); + } + + if (!GetSize(combined)) + combined = Const(0, GetSize(sig)); + + tag_group_signals.emplace(std::make_pair(tag_group, sig), combined); + return combined; + } + + void emit_tag_signal(IdString tag, SigBit bit, SigBit tag_bit) + { + sigmap.apply(bit); + sigmap.apply(tag_bit); + + if (!tag_pool(tags(bit)).count(tag)) + return; + + auto key = std::make_pair(tag, bit); + auto found = tag_signals.find(key); + if (found != tag_signals.end()) { + module->connect(found->second, tag_bit); + return; + } + tag_signals.emplace(key, tag_bit); + } + + void emit_tag_signal(IdString tag, SigSpec sig, SigSpec tag_sig) + { + log_assert(GetSize(sig) == GetSize(tag_sig)); + for (int i = 0; i < GetSize(sig); i++) + emit_tag_signal(tag, sig[i], tag_sig[i]); + } + + void emit_tag_signals(IdString tag, Cell *cell) + { + if (!pending_cells.insert(cell).second) { + // We have a cycle, emit placeholder wires which will be connected + // when the outer call for this tag/cell returns + for (auto &conn : cell->connections()) + if (cell->output(conn.first)) + emit_tag_signal(tag, conn.second, module->addWire(NEW_ID, GetSize(conn.second))); + + return; + } + + process_cell(tag, cell); + + pending_cells.erase(cell); + } + + void propagate_tags(Cell *cell) + { + if (cell->type == ID($set_tag)) { + IdString tag = stringf("\\%s", cell->getParam(ID::TAG).decode_string().c_str()); + if (all_tags.insert(tag).second) { + auto group_sep = tag.str().find(':'); + IdString tag_group = group_sep != std::string::npos ? tag.str().substr(0, group_sep) : tag; + tag_groups[tag_group].insert(tag); + group_of_tag[tag] = tag_group; + } + + auto &sig_y = cell->getPort(ID::Y); + auto &sig_a = cell->getPort(ID::A); + // TODO handle constant set/clr masks + add_tags(sig_y, singleton(tag)); + forward_tags(sig_y, sig_a); + return; + } + + if (cell->type == ID($get_tag)) { + return; + } + + if (cell->type.in(ID($not), ID($pos))) { + auto &sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); + if (cell->type.in(ID($not), ID($or))) { + sig_a.extend_u0(GetSize(sig_y), cell->getParam(ID::A_SIGNED).as_bool()); + } + forward_tags(sig_y, sig_a); + return; + } + + if (cell->type.in(ID($and), ID($or), ID($xor), ID($xnor), ID($bweqx))) { + auto &sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); + auto sig_b = cell->getPort(ID::B); + if (cell->type.in(ID($and), ID($or), ID($xor), ID($xnor))) { + sig_a.extend_u0(GetSize(sig_y), cell->getParam(ID::A_SIGNED).as_bool()); + sig_b.extend_u0(GetSize(sig_y), cell->getParam(ID::B_SIGNED).as_bool()); + } + forward_tags(sig_y, sig_a); + forward_tags(sig_y, sig_b); + return; + } + + if (cell->type.in(ID($mux), ID($bwmux))) { + auto &sig_y = cell->getPort(ID::Y); + auto &sig_a = cell->getPort(ID::A); + auto &sig_b = cell->getPort(ID::B); + auto sig_s = cell->getPort(ID::S); + + if (cell->type == ID($mux)) + sig_s = SigSpec(sig_s[0], GetSize(sig_y)); + + forward_tags(sig_y, sig_a); + forward_tags(sig_y, sig_b); + forward_tags(sig_y, sig_s); + return; + } + + if (RTLIL::builtin_ff_cell_types().count(cell->type) || cell->type == ID($anyinit)) { + FfData ff(&initvals, cell); + + if (ff.has_clk || ff.has_gclk) + forward_tags(ff.sig_q, ff.sig_d); + return; + } + + // Single output but, sensitive to all inputs + if (cell->type.in( + ID($le), ID($lt), ID($ge), ID($gt), + ID($reduce_and), ID($reduce_or), ID($reduce_xor), ID($reduce_xnor), + ID($reduce_bool), ID($logic_not), ID($logic_or), ID($logic_and), + ID($eq), ID($ne) + )) { + auto &sig_y = cell->getPort(ID::Y); + + add_tags(sig_y[0], tags(cell)); + return; + } + + + // Fallback, propagate tags from all inputs to all outputs + add_tags(cell, tags(cell)); + + if (cell->type.in( + ID($_AND_), ID($_OR_), ID($_NAND_), ID($_NOR_), ID($_ANDNOT_), ID($_ORNOT_), + ID($_XOR_), ID($_XNOR_), ID($_NOT_), ID($_BUF_), ID($_MUX_), + + ID($assert), ID($assume) + )) { + return; + } + + // This isn't a correctness concern (unless cell is a module generating + // tags), but we may end up generating a lot of extra logic when + // reaching this + if (!warned_cells.insert(cell).second) + return; + if (cell->type.isPublic()) + log_warning("Unhandled cell %s (%s) during tag propagation\n", log_id(cell), log_id(cell->type)); + else + log_debug("Unhandled cell %s (%s) during tag propagation\n", log_id(cell), log_id(cell->type)); + } + + void process_cell(IdString tag, Cell *cell) + { + if (cell->type == ID($set_tag)) { + IdString cell_tag = stringf("\\%s", cell->getParam(ID::TAG).decode_string().c_str()); + + auto tag_sig_a = tag_signal(tag, cell->getPort(ID::A)); + auto &sig_y = cell->getPort(ID::Y); + + if (cell_tag == tag) { + auto &sig_set = cell->getPort(ID::SET); + auto &sig_clr = cell->getPort(ID::CLR); + tag_sig_a = autoAnd(NEW_ID, tag_sig_a, autoNot(NEW_ID, sig_clr)); + tag_sig_a = autoOr(NEW_ID, tag_sig_a, sig_set); + } + + emit_tag_signal(tag, sig_y, tag_sig_a); + return; + } + + if (cell->type == ID($get_tag)) { + log_assert(false); + } + + if (cell->type.in(ID($not), ID($pos), ID($_NOT_), ID($_BUF_))) { + auto &sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); + if (cell->type.in(ID($not), ID($or))) { + sig_a.extend_u0(GetSize(sig_y), cell->getParam(ID::A_SIGNED).as_bool()); + } + emit_tag_signal(tag, sig_y, tag_signal(tag, sig_a)); + return; + } + + if (cell->type.in( + ID($and), ID($or), + ID($_AND_), ID($_OR_), ID($_NAND_), ID($_NOR_), ID($_ANDNOT_), ID($_ORNOT_) + )) { + auto &sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); + auto sig_b = cell->getPort(ID::B); + if (cell->type.in(ID($and), ID($or))) { + sig_a.extend_u0(GetSize(sig_y), cell->getParam(ID::A_SIGNED).as_bool()); + sig_b.extend_u0(GetSize(sig_y), cell->getParam(ID::B_SIGNED).as_bool()); + } + + bool inv_a = false; + bool inv_b = false; + + if (cell->type.in(ID($or), ID($_OR_), ID($_NOR_), ID($_ORNOT_))) + inv_a ^= true, inv_b ^= true; + if (cell->type.in(ID($_ANDNOT_), ID($_ORNOT_))) + inv_b ^= true; + + if (inv_a) + sig_a = autoNot(NEW_ID, sig_a); + if (inv_b) + sig_b = autoNot(NEW_ID, sig_b); + + auto group_sig_a = tag_group_signal(tag, sig_a); + auto group_sig_b = tag_group_signal(tag, sig_b); + + auto tag_sig_a = tag_signal(tag, sig_a); + auto tag_sig_b = tag_signal(tag, sig_b); + + + // Does this input allow propagating (doesn't fix output or same tag group) + sig_a = autoOr(NEW_ID, sig_a, group_sig_a); + sig_b = autoOr(NEW_ID, sig_b, group_sig_b); + + // Mask input tags by whether the other side allows propagation + tag_sig_a = autoAnd(NEW_ID, tag_sig_a, sig_b); + tag_sig_b = autoAnd(NEW_ID, tag_sig_b, sig_a); + + + auto tag_sig = autoOr(NEW_ID, tag_sig_a, tag_sig_b); + emit_tag_signal(tag, sig_y, tag_sig); + return; + } + + if (cell->type.in(ID($xor), ID($xnor), ID($bweqx), ID($_XOR_), ID($_XNOR_))) { + auto &sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); + auto sig_b = cell->getPort(ID::B); + if (cell->type.in(ID($xor), ID($xnor))) { + sig_a.extend_u0(GetSize(sig_y), cell->getParam(ID::A_SIGNED).as_bool()); + sig_b.extend_u0(GetSize(sig_y), cell->getParam(ID::B_SIGNED).as_bool()); + } + + auto tag_sig_a = tag_signal(tag, sig_a); + auto tag_sig_b = tag_signal(tag, sig_b); + + auto tag_sig = autoOr(NEW_ID, tag_sig_a, tag_sig_b); + emit_tag_signal(tag, sig_y, tag_sig); + return; + } + + + if (cell->type.in(ID($_MUX_), ID($mux), ID($bwmux))) { + auto &sig_y = cell->getPort(ID::Y); + auto &sig_a = cell->getPort(ID::A); + auto &sig_b = cell->getPort(ID::B); + auto sig_s = cell->getPort(ID::S); + + if (cell->type == ID($mux)) + sig_s = SigSpec(sig_s[0], GetSize(sig_y)); + + auto group_sig_a = tag_group_signal(tag, sig_a); + auto group_sig_b = tag_group_signal(tag, sig_b); + auto group_sig_s = tag_group_signal(tag, sig_s); + + auto prop_s = autoOr(NEW_ID, + autoXor(NEW_ID, sig_a, sig_b), + autoOr(NEW_ID, group_sig_a, group_sig_b)); + + auto prop_a = autoOr(NEW_ID, autoNot(NEW_ID, sig_s), group_sig_s); + auto prop_b = autoOr(NEW_ID, sig_s, group_sig_s); + + auto tag_sig_a = tag_signal(tag, sig_a); + auto tag_sig_b = tag_signal(tag, sig_b); + auto tag_sig_s = tag_signal(tag, sig_s); + + tag_sig_a = autoAnd(NEW_ID, tag_sig_a, prop_a); + tag_sig_b = autoAnd(NEW_ID, tag_sig_b, prop_b); + tag_sig_s = autoAnd(NEW_ID, tag_sig_s, prop_s); + + auto tag_sig = autoOr(NEW_ID, tag_sig_s, + autoOr(NEW_ID, tag_sig_a, tag_sig_b)); + emit_tag_signal(tag, sig_y, tag_sig); + return; + } + + if (cell->type.in(ID($eq), ID($ne), ID($eqx), ID($nex))) { + auto &sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); + auto sig_b = cell->getPort(ID::B); + int width = std::max(GetSize(sig_a), GetSize(sig_b)); + sig_a.extend_u0(width, cell->getParam(ID::A_SIGNED).as_bool()); + sig_b.extend_u0(width, cell->getParam(ID::B_SIGNED).as_bool()); + + auto group_sig_a = tag_group_signal(tag, sig_a); + auto group_sig_b = tag_group_signal(tag, sig_b); + + auto tag_sig_a = tag_signal(tag, sig_a); + auto tag_sig_b = tag_signal(tag, sig_b); + + auto group_sig = autoOr(NEW_ID, group_sig_a, group_sig_b); + // The output can only be affected by the tagged inputs if all group-untagged bits are equal + + auto masked_a = autoOr(NEW_ID, sig_a, group_sig); + auto masked_b = autoOr(NEW_ID, sig_b, group_sig); + + auto prop = autoEq(NEW_ID, masked_a, masked_b); + + auto tag_sig = autoAnd(NEW_ID, prop, autoReduceOr(NEW_ID, {tag_sig_a, tag_sig_b})); + tag_sig.extend_u0(GetSize(sig_y), false); + emit_tag_signal(tag, sig_y, tag_sig); + return; + } + + + if (cell->type.in(ID($lt), ID($gt), ID($le), ID($ge))) { + auto &sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); + auto sig_b = cell->getPort(ID::B); + int width = std::max(GetSize(sig_a), GetSize(sig_b)); + sig_a.extend_u0(width, cell->getParam(ID::A_SIGNED).as_bool()); + sig_b.extend_u0(width, cell->getParam(ID::B_SIGNED).as_bool()); + + if (cell->type.in(ID($gt), ID($le))) + std::swap(sig_a, sig_b); + + auto group_sig_a = tag_group_signal(tag, sig_a); + auto group_sig_b = tag_group_signal(tag, sig_b); + + auto tag_sig_a = tag_signal(tag, sig_a); + auto tag_sig_b = tag_signal(tag, sig_b); + + auto group_sig = autoOr(NEW_ID, group_sig_a, group_sig_b); + // The output can only be affected by the tagged inputs if the greatest possible sig_a is + // greater or equal to the least possible sig_b + auto masked_a = autoOr(NEW_ID, sig_a, group_sig); + auto masked_b = autoAnd(NEW_ID, sig_b, autoNot(NEW_ID, group_sig)); + + auto prop = autoGe(NEW_ID, masked_a, masked_b); + + auto tag_sig = autoAnd(NEW_ID, prop, autoReduceOr(NEW_ID, {tag_sig_a, tag_sig_b})); + tag_sig.extend_u0(GetSize(sig_y), false); + emit_tag_signal(tag, sig_y, tag_sig); + return; + } + + if (cell->type.in(ID($reduce_and), ID($reduce_or), ID($reduce_bool), ID($logic_not))) { + auto &sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); + + auto group_sig_a = tag_group_signal(tag, sig_a); + auto tag_sig_a = tag_signal(tag, sig_a); + + if (cell->type.in(ID($reduce_or), ID($reduce_bool), ID($logic_not))) + sig_a = autoNot(NEW_ID, sig_a); + + auto filled = autoOr(NEW_ID, sig_a, group_sig_a); + + auto prop = autoReduceAnd(NEW_ID, filled); + auto tagged = autoReduceOr(NEW_ID, tag_sig_a); + auto tag_sig = autoAnd(NEW_ID, prop, tagged); + tag_sig.extend_u0(GetSize(sig_y), false); + emit_tag_signal(tag, sig_y, tag_sig); + return; + } + + if (RTLIL::builtin_ff_cell_types().count(cell->type) || cell->type == ID($anyinit)) { + FfData ff(&initvals, cell); + // TODO handle some more variants + if ((ff.has_clk || ff.has_gclk) && !ff.has_ce && !ff.has_aload && !ff.has_srst && !ff.has_arst && !ff.has_sr) { + if (ff.has_clk && !tags(ff.sig_clk).empty()) + log_warning("Tags on CLK input ignored for %s (%s)\n", log_id(cell), log_id(cell->type)); + + int width = ff.width; + + auto sig_q = ff.sig_q; + auto sig_d = ff.sig_d; + + ff.name = NEW_ID; + ff.cell = nullptr; + ff.sig_d = tag_signal(tag, ff.sig_d); + ff.sig_q = module->addWire(NEW_ID, width); + ff.is_anyinit = false; + ff.val_init = Const(0, width); + ff.emit(); + + emit_tag_signal(tag, sig_q, ff.sig_q); + return; + } else { + log_warning("Unhandled FF-cell %s (%s), consider running clk2fflogic, async2sync and/or dffunmap\n", log_id(cell), log_id(cell->type)); + + // For unhandled FFs, the default propagation would cause combinational loops + emit_tag_signal(tag, ff.sig_q, Const(0, ff.width)); + return; + } + } + + // Fallback + SigSpec tag_input; + + for (auto &conn : cell->connections()) { + if (cell->input(conn.first)) { + auto tag_sig = tag_signal(tag, conn.second); + tag_input.append(tag_sig); + } + } + + SigBit any_tagged = autoReduceOr(NEW_ID, tag_input); + + for (auto &conn : cell->connections()) { + if (cell->output(conn.first)) { + emit_tag_signal(tag, conn.second, SigSpec(any_tagged, GetSize(conn.second))); + } + } + + // As fallback we propagate all tags from all inputs to all outputs, + // which is an over-approximation (unless the cell is a module that + // generates tags itself in which case it could be arbitrary). + if (warned_cells.insert(cell).second) + log_warning("Unhandled cell %s (%s) while emitting tag signals\n", log_id(cell), log_id(cell->type)); + } + + void emit_tags() + { + warned_cells.clear(); + std::vector get_tag_cells; + for (auto cell : module->selected_cells()) + if (cell->type == ID($get_tag)) + get_tag_cells.push_back(cell); + + for (auto cell : get_tag_cells) { + auto &sig_a = cell->getPort(ID::A); + IdString tag = stringf("\\%s", cell->getParam(ID::TAG).decode_string().c_str()); + + tag_signal(tag, sig_a); + } + + if (options.tag_public) + { + std::vector public_wires; + + for (auto wire : module->selected_wires()) + if (wire->name.isPublic()) + public_wires.push_back(wire); + + for (auto wire : public_wires) { + for (auto tag : tag_pool(tags(SigSpec(wire)))) { + auto tag_sig = tag_signal(tag, SigSpec(wire)); + if (tag_sig.is_fully_zero()) + continue; + + int index = 0; + auto name = module->uniquify(stringf("%s:%s", wire->name.c_str(), tag.c_str() + 1), index); + auto hdlname = wire->get_hdlname_attribute(); + + if (!hdlname.empty()) + hdlname.back() += index ? + stringf(":%s_%d", tag.c_str() + 1, index) : + stringf(":%s", tag.c_str() + 1); + + auto tag_wire = module->addWire(name, wire->width); + + tag_wire->set_bool_attribute(ID::keep); + tag_wire->set_bool_attribute(ID(dft_tag)); + if (!hdlname.empty()) + tag_wire->set_hdlname_attribute(hdlname); + + module->connect(tag_wire, tag_sig); + } + } + } + } + + void replace_dft_cells() + { + std::vector get_tag_cells; + std::vector set_tag_cells; + for (auto cell : module->cells()) { + if (cell->type == ID($get_tag)) + get_tag_cells.push_back(cell); + + if (cell->type == ID($set_tag)) + set_tag_cells.push_back(cell); + + log_assert(!cell->type.in(ID($overwrite_tag), ID($original_tag))); + } + + for (auto cell : set_tag_cells) { + auto &sig_a = cell->getPort(ID::A); + auto &sig_y = cell->getPort(ID::Y); + module->connect(sig_y, sig_a); + module->remove(cell); + } + + for (auto cell : get_tag_cells) { + auto &sig_a = cell->getPort(ID::A); + auto &sig_y = cell->getPort(ID::Y); + IdString tag = stringf("\\%s", cell->getParam(ID::TAG).decode_string().c_str()); + + auto tag_sig = tag_signal(tag, sig_a); + module->connect(sig_y, tag_sig); + module->remove(cell); + } + } + + + SigSpec autoAnd(IdString name, const SigSpec &sig_a, const SigSpec &sig_b) + { + log_assert(GetSize(sig_a) == GetSize(sig_b)); + if (sig_a.is_fully_zero() || sig_b.is_fully_ones() || sig_a == sig_b) + return sig_a; + if (sig_a.is_fully_ones() || sig_b.is_fully_zero()) + return sig_b; + + return module->And(name, sig_a, sig_b); + } + + SigSpec autoOr(IdString name, const SigSpec &sig_a, const SigSpec &sig_b) + { + log_assert(GetSize(sig_a) == GetSize(sig_b)); + if (sig_a.is_fully_ones() || sig_b.is_fully_zero() || sig_a == sig_b) + return sig_a; + if (sig_a.is_fully_zero() || sig_b.is_fully_ones()) + return sig_b; + + return module->Or(name, sig_a, sig_b); + } + + SigSpec autoXor(IdString name, const SigSpec &sig_a, const SigSpec &sig_b) + { + log_assert(GetSize(sig_a) == GetSize(sig_b)); + if (sig_a == sig_b) + return Const(State::S0, GetSize(sig_a)); + if (sig_a.is_fully_zero()) + return sig_b; + if (sig_b.is_fully_zero()) + return sig_a; + if (sig_a.is_fully_ones()) + return autoNot(name, sig_b); + if (sig_b.is_fully_ones()) + return autoNot(name, sig_a); + return module->Xor(name, sig_a, sig_b); + } + + SigSpec autoXnor(IdString name, const SigSpec &sig_a, const SigSpec &sig_b) + { + log_assert(GetSize(sig_a) == GetSize(sig_b)); + if (sig_a == sig_b) + return Const(State::S1, GetSize(sig_a)); + if (sig_a.is_fully_ones()) + return sig_b; + if (sig_b.is_fully_ones()) + return sig_a; + if (sig_a.is_fully_zero()) + return autoNot(name, sig_b); + if (sig_b.is_fully_zero()) + return autoNot(name, sig_a); + return module->Xnor(name, sig_a, sig_b); + } + + SigSpec autoNot(IdString name, const SigSpec &sig_a) + { + if (sig_a.is_fully_const()) { + auto const_val = sig_a.as_const(); + for (auto &bit : const_val.bits) + bit = bit == State::S0 ? State::S1 : bit == State::S1 ? State::S0 : bit; + return const_val; + } + return module->Not(name, sig_a); + } + + SigSpec autoEq(IdString name, const SigSpec &sig_a, const SigSpec &sig_b) + { + log_assert(GetSize(sig_a) == GetSize(sig_b)); + if (sig_a == sig_b) + return State::S1; + for (int i = 0; i < GetSize(sig_a); i++) { + auto bit_a = sig_a[i]; + auto bit_b = sig_b[i]; + if (bit_a.is_wire() || bit_b.is_wire()) + continue; + if ((bit_a.data == State::S0 && bit_b.data == State::S1) || + (bit_a.data == State::S1 && bit_b.data == State::S0)) + return State::S0; + } + + return module->Eq(name, sig_a, sig_b); + } + + SigSpec autoGe(IdString name, const SigSpec &sig_a, const SigSpec &sig_b) + { + log_assert(GetSize(sig_a) == GetSize(sig_b)); + if (sig_a == sig_b || sig_a.is_fully_ones()) + return State::S1; + if (sig_b.is_fully_zero()) + return State::S1; + + return module->Ge(name, sig_a, sig_b); + } + + SigSpec autoReduceAnd(IdString name, const SigSpec &sig_a) + { + if (GetSize(sig_a) == 0) + return State::S1; + + if (GetSize(sig_a) == 1 || sig_a == SigSpec(sig_a[0], GetSize(sig_a))) + return sig_a[0]; + for (auto bit : sig_a) + if (!bit.is_wire() && bit.data == State::S0) + return State::S0; + if (sig_a.is_fully_ones()) + return State::S1; + return module->ReduceAnd(name, sig_a); + } + + SigSpec autoReduceOr(IdString name, const SigSpec &sig_a) + { + if (GetSize(sig_a) == 0) + return State::S0; + + if (GetSize(sig_a) == 1 || sig_a == SigSpec(sig_a[0], GetSize(sig_a))) + return sig_a[0]; + for (auto bit : sig_a) + if (!bit.is_wire() && bit.data == State::S1) + return State::S1; + if (sig_a.is_fully_zero()) + return State::S0; + return module->ReduceOr(name, sig_a); + } +}; + +struct DftTagPass : public Pass { + DftTagPass() : Pass("dft_tag", "create tagging logic for data flow tracking") {} + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" dft_tag [options] [selection]\n"); + log("\n"); + log("This pass... TODO\n"); + log("\n"); + log(" -overwrite-only\n"); + log(" Only process $overwrite_tag and $original_tag cells.\n"); + log(" -tag-public\n"); + log(" For each public wire that may carry tagged data, create a new public\n"); + log(" wire (named :) that carries the tag bits. Note\n"); + log(" that without this, tagging logic will only be emitted as required\n"); + log(" for uses of $get_tag.\n"); + log("\n"); + } + + void execute(std::vector args, RTLIL::Design *design) override + { + DftTagOptions options; + + log_header(design, "Executing DFT_TAG pass.\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-tag-public") { + options.tag_public = true; + continue; + } + if (args[argidx] == "-overwrite-only") { + options.overwrite_only = true; + continue; + } + break; + } + + extra_args(args, argidx, design); + + for (auto module : design->selected_modules()) { + DftTagWorker worker(module, options); + + log_debug("Resolve overwrite_tag and original_tag.\n"); + worker.resolve_overwrites(); + + if (options.overwrite_only) + continue; + + log_debug("Propagate tagged signals.\n"); + worker.propagate_tags(); + + log_debug("Emit tag signals and logic.\n"); + worker.emit_tags(); + + log_debug("Replace dft cells.\n"); + worker.replace_dft_cells(); + } + } +} DftTagPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/cmds/future.cc b/passes/cmds/future.cc new file mode 100644 index 00000000000..b03613c9bd2 --- /dev/null +++ b/passes/cmds/future.cc @@ -0,0 +1,140 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2023 Jannis Harder + * + * 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. + * + */ + +#include "kernel/celltypes.h" +#include "kernel/ff.h" +#include "kernel/ffinit.h" +#include "kernel/modtools.h" +#include "kernel/sigtools.h" +#include "kernel/utils.h" +#include "kernel/yosys.h" +#include + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct FutureOptions { +}; + +struct FutureWorker { + Module *module; + FutureOptions options; + ModWalker modwalker; + SigMap &sigmap; + FfInitVals initvals; + + dict future_ff_signals; + + FutureWorker(Module *module, FutureOptions options) : + module(module), options(options), modwalker(module->design), sigmap(modwalker.sigmap) + { + modwalker.setup(module); + initvals.set(&modwalker.sigmap, module); + + std::vector replaced_cells; + for (auto cell : module->selected_cells()) { + if (cell->type != ID($future_ff)) + continue; + + module->connect(cell->getPort(ID::Y), future_ff(cell->getPort(ID::A))); + replaced_cells.push_back(cell); + } + + for (auto cell : replaced_cells) { + module->remove(cell); + } + } + + SigSpec future_ff(SigSpec sig) + { + for (auto &bit : sig) { + bit = future_ff(bit); + } + return sig; + } + + SigBit future_ff(SigBit bit) + { + if (!bit.is_wire()) + return bit; + + auto found = future_ff_signals.find(bit); + if (found != future_ff_signals.end()) + return found->second; + + auto found_driver = modwalker.signal_drivers.find(bit); + if (found_driver == modwalker.signal_drivers.end() || found_driver->second.size() < 1) + log_error("No driver for future_ff target signal %s found\n", log_signal(bit)); + if (found_driver->second.size() > 1) + log_error("Found multiple drivers for future_ff target signal %s\n", log_signal(bit)); + auto driver = *found_driver->second.begin(); + if (!RTLIL::builtin_ff_cell_types().count(driver.cell->type) && driver.cell->type != ID($anyinit)) + log_error("Driver for future_ff target signal %s has non-FF cell type %s\n", log_signal(bit), log_id(driver.cell->type)); + + FfData ff(&initvals, driver.cell); + + if (!ff.has_clk && !ff.has_gclk) + log_error("Driver for future_ff target signal %s has cell type %s, which is not clocked\n", log_signal(bit), + log_id(driver.cell->type)); + + ff.unmap_ce_srst(); + + // We insert all bits into the mapping, because unmap_ce_srst might + // have removed the cell which is still present in the modwalker data. + // By inserting all bits driven by th FF we ensure that we'll never use + // that stale modwalker data again. + + for (int i = 0; i < ff.width; ++i) { + future_ff_signals.emplace(ff.sig_q[i], ff.sig_d[i]); + } + + return future_ff_signals.at(bit); + } +}; + +struct FuturePass : public Pass { + FuturePass() : Pass("future", "resolve future sampled value functions") {} + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" future [options] [selection]\n"); + log("\n"); + } + void execute(std::vector args, RTLIL::Design *design) override + { + FutureOptions options; + + log_header(design, "Executing FUTURE pass.\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + + break; + } + + extra_args(args, argidx, design); + + for (auto module : design->selected_modules()) { + FutureWorker worker(module, options); + } + } +} FuturePass; + +PRIVATE_NAMESPACE_END diff --git a/passes/cmds/glift.cc b/passes/cmds/glift.cc index 439ded07685..8553b02a541 100644 --- a/passes/cmds/glift.cc +++ b/passes/cmds/glift.cc @@ -582,7 +582,7 @@ struct GliftPass : public Pass { for (auto cell : module->selected_cells()) { RTLIL::Module *tpl = design->module(cell->type); if (tpl != nullptr) { - if (topo_modules.database.count(tpl) == 0) + if (!topo_modules.has_node(tpl)) worklist.push_back(tpl); topo_modules.edge(tpl, module); non_top_modules.insert(cell->type); diff --git a/passes/cmds/scc.cc b/passes/cmds/scc.cc index 81881832cd7..197bd931966 100644 --- a/passes/cmds/scc.cc +++ b/passes/cmds/scc.cc @@ -27,7 +27,6 @@ #include "kernel/log.h" #include #include -#include USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN @@ -39,18 +38,18 @@ struct SccWorker SigMap sigmap; CellTypes ct, specifyCells; - std::set workQueue; - std::map> cellToNextCell; - std::map cellToPrevSig, cellToNextSig; + pool workQueue; + dict> cellToNextCell; + dict cellToPrevSig, cellToNextSig; - std::map> cellLabels; - std::map cellDepth; - std::set cellsOnStack; + dict> cellLabels; + dict cellDepth; + pool cellsOnStack; std::vector cellStack; int labelCounter; - std::map cell2scc; - std::vector> sccList; + dict cell2scc; + std::vector> sccList; void run(RTLIL::Cell *cell, int depth, int maxDepth) { @@ -85,7 +84,7 @@ struct SccWorker else { log("Found an SCC:"); - std::set scc; + pool scc; while (cellsOnStack.count(cell) > 0) { RTLIL::Cell *c = cellStack.back(); cellStack.pop_back(); @@ -199,11 +198,11 @@ struct SccWorker for (auto cell : workQueue) { - cellToNextCell[cell] = sigToNextCells.find(cellToNextSig[cell]); + sigToNextCells.find(cellToNextSig[cell], cellToNextCell[cell]); if (!nofeedbackMode && cellToNextCell[cell].count(cell)) { log("Found an SCC:"); - std::set scc; + pool scc; log(" %s", RTLIL::id2cstr(cell->name)); cell2scc[cell] = sccList.size(); scc.insert(cell); @@ -231,7 +230,7 @@ struct SccWorker { for (int i = 0; i < int(sccList.size()); i++) { - std::set &cells = sccList[i]; + pool &cells = sccList[i]; RTLIL::SigSpec prevsig, nextsig, sig; for (auto cell : cells) { @@ -295,7 +294,7 @@ struct SccPass : public Pass { } void execute(std::vector args, RTLIL::Design *design) override { - std::map setAttr; + dict setAttr; bool allCellTypes = false; bool selectMode = false; bool nofeedbackMode = false; diff --git a/passes/cmds/show.cc b/passes/cmds/show.cc index 0dc5c452c09..d57cbc6bb49 100644 --- a/passes/cmds/show.cc +++ b/passes/cmds/show.cc @@ -575,7 +575,7 @@ struct ShowWorker } else { net_conn_map[right_node].in.insert({stringf("x%d", single_idx_count), GetSize(conn.first)}); net_conn_map[left_node].out.insert({stringf("x%d", single_idx_count), GetSize(conn.first)}); - fprintf(f, "x%d [shape=box, style=rounded, label=\"BUF\", %s];\n", single_idx_count++, findColor(conn).c_str()); + fprintf(f, "x%d [shape=point, %s];\n", single_idx_count++, findColor(conn).c_str()); } } } diff --git a/passes/cmds/xprop.cc b/passes/cmds/xprop.cc index 5e78ff9fce4..310d6d773d5 100644 --- a/passes/cmds/xprop.cc +++ b/passes/cmds/xprop.cc @@ -493,8 +493,9 @@ struct XpropWorker auto sig_b = cell->getPort(ID::B); auto name = cell->name; + auto type = cell->type; module->remove(cell); - if (cell->type == ID($eqx)) + if (type == ID($eqx)) module->addEq(name, sig_a, sig_b, sig_y); else module->addNe(name, sig_a, sig_b, sig_y); @@ -534,7 +535,7 @@ struct XpropWorker auto enc_b = encoded(sig_b); auto enc_y = encoded(sig_y, true); - if (cell->type.in(ID($or), ID($_OR_))) + if (cell->type.in(ID($or), ID($_OR_), ID($_NOR_), ID($_ORNOT_))) enc_a.invert(), enc_b.invert(), enc_y.invert(); if (cell->type.in(ID($_NAND_), ID($_NOR_))) enc_y.invert(); @@ -1027,12 +1028,25 @@ struct XpropWorker for (auto wire : module->selected_wires()) { if (wire->port_input || wire->port_output || !wire->name.isPublic()) continue; - auto name_d = module->uniquify(stringf("%s_d", wire->name.c_str())); - auto name_x = module->uniquify(stringf("%s_x", wire->name.c_str())); + int index_d = 0; + int index_x = 0; + auto name_d = module->uniquify(stringf("%s_d", wire->name.c_str()), index_d); + auto name_x = module->uniquify(stringf("%s_x", wire->name.c_str()), index_x); + + auto hdlname = wire->get_hdlname_attribute(); auto wire_d = module->addWire(name_d, GetSize(wire)); auto wire_x = module->addWire(name_x, GetSize(wire)); + if (!hdlname.empty()) { + auto hdlname_d = hdlname; + auto hdlname_x = hdlname; + hdlname_d.back() += index_d ? stringf("_d_%d", index_d) : "_d"; + hdlname_x.back() += index_x ? stringf("_x_%d", index_x) : "_x"; + wire_d->set_hdlname_attribute(hdlname_d); + wire_x->set_hdlname_attribute(hdlname_x); + } + auto enc = encoded(wire); module->connect(wire_d, enc.is_1); module->connect(wire_x, enc.is_x); diff --git a/passes/memory/memlib.md b/passes/memory/memlib.md index fdc2d4bed27..855aa1345fb 100644 --- a/passes/memory/memlib.md +++ b/passes/memory/memlib.md @@ -267,7 +267,7 @@ The address is always `abits` wide. If a non-narrowest width is used, the appro bits will be tied to 0. -### Port `width` prooperty +### Port `width` property If the RAM has `per_port` widths, the available width selection can be further described on per-port basis, by using one of the following properties: diff --git a/passes/memory/memory_libmap.cc b/passes/memory/memory_libmap.cc index 6e5a806fd0e..ec181a142c4 100644 --- a/passes/memory/memory_libmap.cc +++ b/passes/memory/memory_libmap.cc @@ -481,18 +481,58 @@ void MemMapping::dump_config(MemConfig &cfg) { } } +std::pair search_for_attribute(Mem mem, IdString attr) { + // priority of attributes: + // 1. attributes on memory itself + // 2. attributes on a read or write port + // 3. attributes on data signal of a read or write port + // 4. attributes on address signal of a read or write port + + if (mem.has_attribute(attr)) + return std::make_pair(true, mem.attributes.at(attr)); + + for (auto &port: mem.rd_ports) + if (port.has_attribute(attr)) + return std::make_pair(true, port.attributes.at(attr)); + for (auto &port: mem.wr_ports) + if (port.has_attribute(attr)) + return std::make_pair(true, port.attributes.at(attr)); + + for (auto &port: mem.rd_ports) + for (SigBit bit: port.data) + if (bit.is_wire() && bit.wire->has_attribute(attr)) + return std::make_pair(true, bit.wire->attributes.at(attr)); + for (auto &port: mem.wr_ports) + for (SigBit bit: port.data) + if (bit.is_wire() && bit.wire->has_attribute(attr)) + return std::make_pair(true, bit.wire->attributes.at(attr)); + + for (auto &port: mem.rd_ports) + for (SigBit bit: port.addr) + if (bit.is_wire() && bit.wire->has_attribute(attr)) + return std::make_pair(true, bit.wire->attributes.at(attr)); + for (auto &port: mem.wr_ports) + for (SigBit bit: port.addr) + if (bit.is_wire() && bit.wire->has_attribute(attr)) + return std::make_pair(true, bit.wire->attributes.at(attr)); + + return std::make_pair(false, Const()); +} + // Go through memory attributes to determine user-requested mapping style. void MemMapping::determine_style() { kind = RamKind::Auto; style = ""; - if (mem.get_bool_attribute(ID::lram)) { + auto find_attr = search_for_attribute(mem, ID::lram); + if (find_attr.first && find_attr.second.as_bool()) { kind = RamKind::Huge; log("found attribute 'lram' on memory %s.%s, forced mapping to huge RAM\n", log_id(mem.module->name), log_id(mem.memid)); return; } for (auto attr: {ID::ram_block, ID::rom_block, ID::ram_style, ID::rom_style, ID::ramstyle, ID::romstyle, ID::syn_ramstyle, ID::syn_romstyle}) { - if (mem.has_attribute(attr)) { - Const val = mem.attributes.at(attr); + find_attr = search_for_attribute(mem, attr); + if (find_attr.first) { + Const val = find_attr.second; if (val == 1) { kind = RamKind::NotLogic; log("found attribute '%s = 1' on memory %s.%s, disabled mapping to FF\n", log_id(attr), log_id(mem.module->name), log_id(mem.memid)); @@ -526,8 +566,11 @@ void MemMapping::determine_style() { return; } } - if (mem.get_bool_attribute(ID::logic_block)) - kind = RamKind::Logic; + for (auto attr: {ID::logic_block, ID::no_ram}){ + find_attr = search_for_attribute(mem, attr); + if (find_attr.first && find_attr.second.as_bool()) + kind = RamKind::Logic; + } } // Determine whether the memory can be mapped entirely to soft logic. @@ -667,7 +710,7 @@ void MemMapping::assign_wr_ports() { if (used >= GetSize(pg.names)) { log_reject(*cfg.def, pg, "not enough unassigned ports remaining"); continue; - } + } for (int pvi = 0; pvi < GetSize(pg.variants); pvi++) { auto &def = pg.variants[pvi]; // Make sure the target is a write port. @@ -2114,7 +2157,7 @@ struct MemoryLibMapPass : public Pass { log(" memory_libmap -lib [-D ] [selection]\n"); log("\n"); log("This pass takes a description of available RAM cell types and maps\n"); - log("all selected memories to one of them, or leaves them to be mapped to FFs.\n"); + log("all selected memories to one of them, or leaves them to be mapped to FFs.\n"); log("\n"); log(" -lib \n"); log(" Selects a library file containing RAM cell definitions. This option\n"); diff --git a/passes/opt/opt_clean.cc b/passes/opt/opt_clean.cc index 4da67cf630f..a219e470813 100644 --- a/passes/opt/opt_clean.cc +++ b/passes/opt/opt_clean.cc @@ -76,6 +76,9 @@ struct keep_cache_t if (cell->type.in(ID($assert), ID($assume), ID($live), ID($fair), ID($cover))) return true; + if (cell->type.in(ID($overwrite_tag))) + return true; + if (!ignore_specify && cell->type.in(ID($specify2), ID($specify3), ID($specrule))) return true; diff --git a/passes/opt/opt_expr.cc b/passes/opt/opt_expr.cc index 46773a344b1..3eadd35c6af 100644 --- a/passes/opt/opt_expr.cc +++ b/passes/opt/opt_expr.cc @@ -424,13 +424,19 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons for (auto &bit : sig) outbit_to_cell[bit].insert(cell); } - cells.node(cell); + cells.node(cell); } - for (auto &it_right : cell_to_inbit) - for (auto &it_sigbit : it_right.second) - for (auto &it_left : outbit_to_cell[it_sigbit]) - cells.edge(it_left, it_right.first); + // Build the graph for the topological sort. + for (auto &it_right : cell_to_inbit) { + const int r_index = cells.node(it_right.first); + for (auto &it_sigbit : it_right.second) { + for (auto &it_left : outbit_to_cell[it_sigbit]) { + const int l_index = cells.node(it_left); + cells.edge(l_index, r_index); + } + } + } cells.sort(); diff --git a/passes/opt/opt_merge.cc b/passes/opt/opt_merge.cc index e9d98cd4304..248ba80913b 100644 --- a/passes/opt/opt_merge.cc +++ b/passes/opt/opt_merge.cc @@ -41,7 +41,6 @@ struct OptMergeWorker CellTypes ct; int total_count; - SHA1 checksum; static void sort_pmux_conn(dict &conn) { @@ -78,7 +77,7 @@ struct OptMergeWorker return str; } - std::string hash_cell_parameters_and_connections(const RTLIL::Cell *cell) + uint64_t hash_cell_parameters_and_connections(const RTLIL::Cell *cell) { vector hash_conn_strings; std::string hash_string = cell->type.str() + "\n"; @@ -149,8 +148,7 @@ struct OptMergeWorker for (auto it : hash_conn_strings) hash_string += it; - checksum.update(hash_string); - return checksum.final(); + return std::hash{}(hash_string); } bool compare_cell_parameters_and_connections(const RTLIL::Cell *cell1, const RTLIL::Cell *cell2) @@ -268,13 +266,13 @@ struct OptMergeWorker } did_something = false; - dict sharemap; + dict sharemap; for (auto cell : cells) { if ((!mode_share_all && !ct.cell_known(cell->type)) || !cell->known()) continue; - auto hash = hash_cell_parameters_and_connections(cell); + uint64_t hash = hash_cell_parameters_and_connections(cell); auto r = sharemap.insert(std::make_pair(hash, cell)); if (!r.second) { if (compare_cell_parameters_and_connections(cell, r.first->second)) { diff --git a/passes/opt/share.cc b/passes/opt/share.cc index abef719370b..586bd9dfe9d 100644 --- a/passes/opt/share.cc +++ b/passes/opt/share.cc @@ -1032,7 +1032,7 @@ struct ShareWorker } bool found_scc = !toposort.sort(); - topo_cell_drivers = std::move(toposort.database); + topo_cell_drivers = toposort.get_database(); if (found_scc && toposort.analyze_loops) for (auto &loop : toposort.loops) { diff --git a/passes/pmgen/Makefile.inc b/passes/pmgen/Makefile.inc index a7ef642825c..884e1252224 100644 --- a/passes/pmgen/Makefile.inc +++ b/passes/pmgen/Makefile.inc @@ -42,7 +42,9 @@ GENFILES += passes/pmgen/peepopt_pm.h passes/pmgen/peepopt.o: passes/pmgen/peepopt_pm.h $(eval $(call add_extra_objs,passes/pmgen/peepopt_pm.h)) -PEEPOPT_PATTERN = passes/pmgen/peepopt_shiftmul.pmg +PEEPOPT_PATTERN = passes/pmgen/peepopt_shiftmul_right.pmg +PEEPOPT_PATTERN += passes/pmgen/peepopt_shiftmul_left.pmg +PEEPOPT_PATTERN += passes/pmgen/peepopt_shiftadd.pmg PEEPOPT_PATTERN += passes/pmgen/peepopt_muldiv.pmg passes/pmgen/peepopt_pm.h: passes/pmgen/pmgen.py $(PEEPOPT_PATTERN) diff --git a/passes/pmgen/README.md b/passes/pmgen/README.md index 39560839f0f..3205be1b55e 100644 --- a/passes/pmgen/README.md +++ b/passes/pmgen/README.md @@ -212,7 +212,7 @@ second argument, and the matcher will iterate over those options: index port(eq, BA) === bar set eq_ab AB set eq_ba BA - generate + endmatch Notice how `define` can be used to define additional local variables similar to the loop variables defined by `slice` and `choice`. diff --git a/passes/pmgen/peepopt.cc b/passes/pmgen/peepopt.cc index 9a497c91441..edd3b18a815 100644 --- a/passes/pmgen/peepopt.cc +++ b/passes/pmgen/peepopt.cc @@ -24,11 +24,8 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN bool did_something; -dict initbits; -pool rminitbits; #include "passes/pmgen/peepopt_pm.h" -#include "generate.h" struct PeepoptPass : public Pass { PeepoptPass() : Pass("peepopt", "collection of peephole optimizers") { } @@ -40,38 +37,32 @@ struct PeepoptPass : public Pass { log("\n"); log("This pass applies a collection of peephole optimizers to the current design.\n"); log("\n"); + log("This pass employs the following rules:\n"); + log("\n"); + log(" * muldiv - Replace (A*B)/B with A\n"); + log("\n"); + log(" * shiftmul - Replace A>>(B*C) with A'>>(B<>(B+D) with (A'>>D)>>(B) where D is constant and\n"); + log(" A' is derived from A by padding or cutting inaccessible bits.\n"); + log("\n"); } void execute(std::vector args, RTLIL::Design *design) override { - std::string genmode; - log_header(design, "Executing PEEPOPT pass (run peephole optimizers).\n"); size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { - if (args[argidx] == "-generate" && argidx+1 < args.size()) { - genmode = args[++argidx]; - continue; - } break; } extra_args(args, argidx, design); - if (!genmode.empty()) - { - initbits.clear(); - rminitbits.clear(); - - if (genmode == "shiftmul") - GENERATE_PATTERN(peepopt_pm, shiftmul); - else if (genmode == "muldiv") - GENERATE_PATTERN(peepopt_pm, muldiv); - else - log_abort(); - return; - } - for (auto module : design->selected_modules()) { did_something = true; @@ -79,47 +70,15 @@ struct PeepoptPass : public Pass { while (did_something) { did_something = false; - initbits.clear(); - rminitbits.clear(); peepopt_pm pm(module); - for (auto w : module->wires()) { - auto it = w->attributes.find(ID::init); - if (it != w->attributes.end()) { - SigSpec sig = pm.sigmap(w); - Const val = it->second; - int len = std::min(GetSize(sig), GetSize(val)); - for (int i = 0; i < len; i++) { - if (sig[i].wire == nullptr) - continue; - if (val[i] != State::S0 && val[i] != State::S1) - continue; - initbits[sig[i]] = val[i]; - } - } - } - pm.setup(module->selected_cells()); - pm.run_shiftmul(); + pm.run_shiftadd(); + pm.run_shiftmul_right(); + pm.run_shiftmul_left(); pm.run_muldiv(); - - for (auto w : module->wires()) { - auto it = w->attributes.find(ID::init); - if (it != w->attributes.end()) { - SigSpec sig = pm.sigmap(w); - Const &val = it->second; - int len = std::min(GetSize(sig), GetSize(val)); - for (int i = 0; i < len; i++) { - if (rminitbits.count(sig[i])) - val[i] = State::Sx; - } - } - } - - initbits.clear(); - rminitbits.clear(); } } } diff --git a/passes/pmgen/peepopt_shiftadd.pmg b/passes/pmgen/peepopt_shiftadd.pmg new file mode 100644 index 00000000000..f9c930eae3d --- /dev/null +++ b/passes/pmgen/peepopt_shiftadd.pmg @@ -0,0 +1,121 @@ +pattern shiftadd +// +// Transforms add/sub+shift pairs that result from expressions such as data[s*W +C +:W2] +// specifically something like: out[W2-1:0] = data >> (s*W +C) +// will be transformed into: out[W2-1:0] = (data >> C) >> (s*W) +// this can then be optimized using peepopt_shiftmul_right.pmg +// + +match shift + select shift->type.in($shift, $shiftx, $shr) + filter !port(shift, \B).empty() +endmatch + +// the right shift amount +state shift_amount +// log2 scale factor in interpreting of shift_amount +// due to zero padding on the shift cell's B port +state log2scale +// zeros at the MSB position make it unsigned +state msb_zeros + +code shift_amount log2scale msb_zeros + shift_amount = port(shift, \B); + + log2scale = 0; + while (shift_amount[0] == State::S0) { + shift_amount.remove(0); + if (shift_amount.empty()) reject; + log2scale++; + } + + msb_zeros = 0; + while (shift_amount.bits().back() == State::S0) { + msb_zeros = true; + shift_amount.remove(GetSize(shift_amount) - 1); + if (shift_amount.empty()) reject; + } +endcode + +state var_signed +state var_signal +// offset: signed constant value c in data[var+c +:W1] (constant shift-right amount) +state offset + +match add + // either data[var+c +:W1] or data[var-c +:W1] + select add->type.in($add, $sub) + index port(add, \Y) === shift_amount + + // one must be constant, the other is variable + choice constport {\A, \B} + select !port(add, constport).empty() + select port(add, constport).is_fully_const() + define varport (constport == \A ? \B : \A) + + // if a value of var is able to wrap the output, the transformation might give wrong results + // an addition/substraction can at most flip one more bit than the largest operand (the carry bit) + // as long as the output can show this bit, no wrap should occur (assuming all signed-ness make sense) + select ( GetSize(port(add, \Y)) > max(GetSize(port(add, \A)), GetSize(port(add, \B))) ) + + define varport_A (varport == \A) + define is_sub add->type.in($sub) + + define constport_signed param(add, !varport_A ? \A_SIGNED : \B_SIGNED).as_bool() + define varport_signed param(add, varport_A ? \A_SIGNED : \B_SIGNED).as_bool(); + define offset_negative ((port(add, constport).bits().back() == State::S1) ^ (is_sub && varport_A)) + + // checking some value boundaries as well: + // data[...-c +:W1] is fine for +/-var (pad at LSB, all data still accessible) + // data[...+c +:W1] is only fine for +var(add) and var unsigned + // (+c cuts lower C bits, making them inaccessible, a signed var could try to access them) + // either its an add or the variable port is A (it must be positive) + select (add->type.in($add) || varport == \A) + + // -> data[var+c +:W1] (with var signed) is illegal + filter !(!offset_negative && varport_signed) + + // state-variables are assigned at the end only: + // shift the log2scale offset in-front of add to get true value: (var+c)< (var<getPort(varport) +endmatch + +code +{ + // positive constant offset with a signed variable (index) cannot be handled + // the above filter should get rid of this case but 'offset' is calculated differently + // due to limitations of state-variables in pmgen + // it should only differ if previous passes create invalid data + log_assert(!(offset>0 && var_signed)); + + did_something = true; + log("shiftadd pattern in %s: shift=%s, add/sub=%s, offset: %d\n", \ + log_id(module), log_id(shift), log_id(add), offset); + + SigSpec old_a = port(shift, \A), new_a; + if(offset<0) { + // data >> (...-c) transformed to {data, c'X} >> (...) + SigSpec padding( (shift->type.in($shiftx) ? State::Sx : State::S0), -offset ); + new_a.append(padding); + new_a.append(old_a); + } else { + // data >> (...+c) transformed to data[MAX:c] >> (...) + new_a.append(old_a.extract_end(offset)); + + } + + SigSpec new_b = {var_signal, SigSpec(State::S0, log2scale)}; + if (msb_zeros || !var_signed) + new_b.append(State::S0); + + shift->setPort(\A, new_a); + shift->setParam(\A_WIDTH, GetSize(new_a)); + shift->setPort(\B, new_b); + shift->setParam(\B_WIDTH, GetSize(new_b)); + blacklist(add); + accept; +} +endcode diff --git a/passes/pmgen/peepopt_shiftmul.pmg b/passes/pmgen/peepopt_shiftmul.pmg deleted file mode 100644 index d71fbf74449..00000000000 --- a/passes/pmgen/peepopt_shiftmul.pmg +++ /dev/null @@ -1,92 +0,0 @@ -pattern shiftmul -// -// Optimize mul+shift pairs that result from expressions such as foo[s*W+:W] -// - -state shamt - -match shift - select shift->type.in($shift, $shiftx, $shr) -endmatch - -code shamt - shamt = port(shift, \B); - if (shamt.empty()) - reject; - if (shamt[GetSize(shamt)-1] == State::S0) { - do { - shamt.remove(GetSize(shamt)-1); - if (shamt.empty()) - reject; - } while (shamt[GetSize(shamt)-1] == State::S0); - } else - if (shift->type.in($shift, $shiftx) && param(shift, \B_SIGNED).as_bool()) { - reject; - } - if (GetSize(shamt) > 20) - reject; -endcode - -match mul - select mul->type.in($mul) - select port(mul, \A).is_fully_const() || port(mul, \B).is_fully_const() - index port(mul, \Y) === shamt - filter !param(mul, \A_SIGNED).as_bool() -endmatch - -code -{ - IdString const_factor_port = port(mul, \A).is_fully_const() ? \A : \B; - Const const_factor_cnst = port(mul, const_factor_port).as_const(); - int const_factor = const_factor_cnst.as_int(); - - if (GetSize(const_factor_cnst) == 0) - reject; - - if (GetSize(const_factor_cnst) > 20) - reject; - - if (GetSize(port(shift, \Y)) > const_factor) - reject; - - int factor_bits = ceil_log2(const_factor); - SigSpec mul_din = port(mul, const_factor_port == \A ? \B : \A); - - if (GetSize(shamt) < factor_bits+GetSize(mul_din)) - reject; - - did_something = true; - log("shiftmul pattern in %s: shift=%s, mul=%s\n", log_id(module), log_id(shift), log_id(mul)); - - int new_const_factor = 1 << factor_bits; - SigSpec padding(State::Sx, new_const_factor-const_factor); - SigSpec old_a = port(shift, \A), new_a; - int trunc = 0; - - if (GetSize(old_a) % const_factor != 0) { - trunc = const_factor - GetSize(old_a) % const_factor; - old_a.append(SigSpec(State::Sx, trunc)); - } - - for (int i = 0; i*const_factor < GetSize(old_a); i++) { - SigSpec slice = old_a.extract(i*const_factor, const_factor); - new_a.append(slice); - new_a.append(padding); - } - - if (trunc > 0) - new_a.remove(GetSize(new_a)-trunc, trunc); - - SigSpec new_b = {mul_din, SigSpec(State::S0, factor_bits)}; - if (param(shift, \B_SIGNED).as_bool()) - new_b.append(State::S0); - - shift->setPort(\A, new_a); - shift->setParam(\A_WIDTH, GetSize(new_a)); - shift->setPort(\B, new_b); - shift->setParam(\B_WIDTH, GetSize(new_b)); - - blacklist(shift); - accept; -} -endcode diff --git a/passes/pmgen/peepopt_shiftmul_left.pmg b/passes/pmgen/peepopt_shiftmul_left.pmg new file mode 100644 index 00000000000..607f8368c53 --- /dev/null +++ b/passes/pmgen/peepopt_shiftmul_left.pmg @@ -0,0 +1,160 @@ +pattern shiftmul_left +// +// Optimize mul+shift pairs that result from expressions such as foo[s*W+:W] +// + +match shift + select shift->type.in($shift, $shiftx, $shl) + select shift->type.in($shl) || param(shift, \B_SIGNED).as_bool() + filter !port(shift, \B).empty() +endmatch + +match neg + if shift->type.in($shift, $shiftx) + select neg->type == $neg + index port(neg, \Y) === port(shift, \B) + filter !port(shift, \A).empty() +endmatch + +// the left shift amount +state shift_amount +// log2 scale factor in interpreting of shift_amount +// due to zero padding on the shift cell's B port +state log2scale + +code shift_amount log2scale + if (neg) { + // case of `$shift`, `$shiftx` + shift_amount = port(neg, \A); + if (!param(neg, \A_SIGNED).as_bool()) + shift_amount.append(State::S0); + } else { + // case of `$shl` + shift_amount = port(shift, \B); + if (!param(shift, \B_SIGNED).as_bool()) + shift_amount.append(State::S0); + } + + // at this point shift_amount is signed, make + // sure we can never go negative + if (shift_amount.bits().back() != State::S0) + reject; + + while (shift_amount.bits().back() == State::S0) { + shift_amount.remove(GetSize(shift_amount) - 1); + if (shift_amount.empty()) reject; + } + + log2scale = 0; + while (shift_amount[0] == State::S0) { + shift_amount.remove(0); + if (shift_amount.empty()) reject; + log2scale++; + } + + if (GetSize(shift_amount) > 20) + reject; +endcode + +state mul_din +state mul_const + +match mul + select mul->type.in($mul) + index port(mul, \Y) === shift_amount + filter !param(mul, \A_SIGNED).as_bool() + + choice constport {\A, \B} + filter port(mul, constport).is_fully_const() + + define varport (constport == \A ? \B : \A) + set mul_const SigSpec({port(mul, constport), SigSpec(State::S0, log2scale)}).as_const() + // get mul_din unmapped (so no `port()` shorthand) + // because we will be using it to set the \A port + // on the shift cell, and we want to stay close + // to the original design + set mul_din mul->getPort(varport) +endmatch + +code +{ + if (mul_const.empty() || GetSize(mul_const) > 20) + reject; + + // make sure there's no overlap in the signal + // selections by the shiftmul pattern + if (GetSize(port(shift, \A)) > mul_const.as_int()) + reject; + + int factor_bits = ceil_log2(mul_const.as_int()); + // make sure the multiplication never wraps around + if (GetSize(shift_amount) < factor_bits + GetSize(mul_din)) + reject; + + if (neg) { + // make sure the negation never wraps around + if (GetSize(port(shift, \B)) < factor_bits + GetSize(mul_din) + + log2scale + 1) + reject; + } + + did_something = true; + log("left shiftmul pattern in %s: shift=%s, mul=%s\n", log_id(module), log_id(shift), log_id(mul)); + + int const_factor = mul_const.as_int(); + int new_const_factor = 1 << factor_bits; + SigSpec padding(State::Sm, new_const_factor-const_factor); + SigSpec old_y = port(shift, \Y), new_y; + int trunc = 0; + + if (GetSize(old_y) % const_factor != 0) { + trunc = const_factor - GetSize(old_y) % const_factor; + old_y.append(SigSpec(State::Sm, trunc)); + } + + for (int i = 0; i*const_factor < GetSize(old_y); i++) { + SigSpec slice = old_y.extract(i*const_factor, const_factor); + new_y.append(slice); + new_y.append(padding); + } + + if (trunc > 0) + new_y.remove(GetSize(new_y)-trunc, trunc); + + { + // Now replace occurences of Sm in new_y with bits + // of a dummy wire + int padbits = 0; + for (auto bit : new_y) + if (bit == SigBit(State::Sm)) + padbits++; + + SigSpec padwire = module->addWire(NEW_ID, padbits); + + for (int i = new_y.size() - 1; i >= 0; i--) + if (new_y[i] == SigBit(State::Sm)) { + new_y[i] = padwire.bits().back(); + padwire.remove(padwire.size() - 1); + } + } + + SigSpec new_b = {mul_din, SigSpec(State::S0, factor_bits)}; + + shift->setPort(\Y, new_y); + shift->setParam(\Y_WIDTH, GetSize(new_y)); + if (shift->type == $shl) { + if (param(shift, \B_SIGNED).as_bool()) + new_b.append(State::S0); + shift->setPort(\B, new_b); + shift->setParam(\B_WIDTH, GetSize(new_b)); + } else { + SigSpec b_neg = module->addWire(NEW_ID, GetSize(new_b) + 1); + module->addNeg(NEW_ID, new_b, b_neg); + shift->setPort(\B, b_neg); + shift->setParam(\B_WIDTH, GetSize(b_neg)); + } + + blacklist(shift); + accept; +} +endcode diff --git a/passes/pmgen/peepopt_shiftmul_right.pmg b/passes/pmgen/peepopt_shiftmul_right.pmg new file mode 100644 index 00000000000..71e0980234a --- /dev/null +++ b/passes/pmgen/peepopt_shiftmul_right.pmg @@ -0,0 +1,113 @@ +pattern shiftmul_right +// +// Optimize mul+shift pairs that result from expressions such as foo[s*W+:W] +// + +match shift + select shift->type.in($shift, $shiftx, $shr) + filter !port(shift, \B).empty() +endmatch + +// the right shift amount +state shift_amount +// log2 scale factor in interpreting of shift_amount +// due to zero padding on the shift cell's B port +state log2scale + +code shift_amount log2scale + shift_amount = port(shift, \B); + if (shift->type.in($shr) || !param(shift, \B_SIGNED).as_bool()) + shift_amount.append(State::S0); + + // at this point shift_amount is signed, make + // sure we can never go negative + if (shift_amount.bits().back() != State::S0) + reject; + + while (shift_amount.bits().back() == State::S0) { + shift_amount.remove(GetSize(shift_amount) - 1); + if (shift_amount.empty()) reject; + } + + log2scale = 0; + while (shift_amount[0] == State::S0) { + shift_amount.remove(0); + if (shift_amount.empty()) reject; + log2scale++; + } + + if (GetSize(shift_amount) > 20) + reject; +endcode + +state mul_din +state mul_const + +match mul + select mul->type.in($mul) + index port(mul, \Y) === shift_amount + filter !param(mul, \A_SIGNED).as_bool() + + choice constport {\A, \B} + filter port(mul, constport).is_fully_const() + + define varport (constport == \A ? \B : \A) + set mul_const SigSpec({port(mul, constport), SigSpec(State::S0, log2scale)}).as_const() + // get mul_din unmapped (so no `port()` shorthand) + // because we will be using it to set the \A port + // on the shift cell, and we want to stay close + // to the original design + set mul_din mul->getPort(varport) +endmatch + +code +{ + if (mul_const.empty() || GetSize(mul_const) > 20) + reject; + + // make sure there's no overlap in the signal + // selections by the shiftmul pattern + if (GetSize(port(shift, \Y)) > mul_const.as_int()) + reject; + + int factor_bits = ceil_log2(mul_const.as_int()); + // make sure the multiplication never wraps around + if (GetSize(shift_amount) + log2scale < factor_bits + GetSize(mul_din)) + reject; + + did_something = true; + log("right shiftmul pattern in %s: shift=%s, mul=%s\n", log_id(module), log_id(shift), log_id(mul)); + + int const_factor = mul_const.as_int(); + int new_const_factor = 1 << factor_bits; + SigSpec padding(State::Sx, new_const_factor-const_factor); + SigSpec old_a = port(shift, \A), new_a; + int trunc = 0; + + if (GetSize(old_a) % const_factor != 0) { + trunc = const_factor - GetSize(old_a) % const_factor; + old_a.append(SigSpec(State::Sx, trunc)); + } + + for (int i = 0; i*const_factor < GetSize(old_a); i++) { + SigSpec slice = old_a.extract(i*const_factor, const_factor); + new_a.append(slice); + new_a.append(padding); + } + + if (trunc > 0) + new_a.remove(GetSize(new_a)-trunc, trunc); + + SigSpec new_b = {mul_din, SigSpec(State::S0, factor_bits)}; + if (param(shift, \B_SIGNED).as_bool()) + new_b.append(State::S0); + + shift->setPort(\A, new_a); + shift->setParam(\A_WIDTH, GetSize(new_a)); + shift->setPort(\B, new_b); + shift->setParam(\B_WIDTH, GetSize(new_b)); + + blacklist(shift); + accept; +} +endcode diff --git a/passes/sat/recover_names.cc b/passes/sat/recover_names.cc index 2d7e7f01cfa..4c30a363202 100644 --- a/passes/sat/recover_names.cc +++ b/passes/sat/recover_names.cc @@ -29,13 +29,6 @@ USING_YOSYS_NAMESPACE -template<> struct hashlib::hash_ops : hashlib::hash_int_ops -{ - static inline unsigned int hash(uint64_t a) { - return mkhash((unsigned int)(a), (unsigned int)(a >> 32)); - } -}; - PRIVATE_NAMESPACE_BEGIN // xorshift128 params @@ -453,7 +446,7 @@ struct RecoverNamesWorker { pool comb_whiteboxes, buffer_types; // class -> (gold, (gate, inverted)) - dict, dict>> cls2bits; + dict, dict>> cls2bits; void analyse_boxes() { diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index 325e123201c..a8516470cfe 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -25,6 +25,7 @@ #include "kernel/ff.h" #include "kernel/yw.h" #include "kernel/json.h" +#include "kernel/fmt.h" #include @@ -109,6 +110,8 @@ struct SimShared int next_output_id = 0; int step = 0; std::vector triggered_assertions; + bool serious_asserts = false; + bool initstate = true; }; void zinit(State &v) @@ -168,11 +171,38 @@ struct SimInstance Const data; }; + struct print_state_t + { + Const past_trg; + Const past_en; + Const past_args; + + Cell *cell; + Fmt fmt; + + std::tuple _sort_label() const + { + return std::make_tuple( + cell->getParam(ID::TRG_ENABLE).as_bool(), // Group by trigger + cell->getPort(ID::TRG), + cell->getParam(ID::TRG_POLARITY), + -cell->getParam(ID::PRIORITY).as_int(), // Then sort by descending PRIORITY + cell + ); + } + + bool operator<(const print_state_t &other) const + { + return _sort_label() < other._sort_label(); + } + }; + dict ff_database; dict mem_database; pool formal_database; pool initstate_database; dict mem_cells; + std::vector print_database; std::vector memories; @@ -189,9 +219,13 @@ struct SimInstance log_assert(module); if (module->get_blackbox_attribute(true)) - log_error("Cannot simulate blackbox module %s (instanced at %s).\n", + log_error("Cannot simulate blackbox module %s (instantiated at %s).\n", log_id(module->name), hiername().c_str()); + if (module->has_processes()) + log_error("Found processes in simulation hierarchy (in module %s at %s). Run 'proc' first.\n", + log_id(module), hiername().c_str()); + if (parent) { log_assert(parent->children.count(instance) == 0); parent->children[instance] = this; @@ -289,13 +323,26 @@ struct SimInstance if (shared->fst) fst_memories[name] = shared->fst->getMemoryHandles(scope + "." + RTLIL::unescape_id(name)); } - if (cell->type.in(ID($assert), ID($cover), ID($assume))) { + + if (cell->type.in(ID($assert), ID($cover), ID($assume))) formal_database.insert(cell); - } + if (cell->type == ID($initstate)) initstate_database.insert(cell); + + if (cell->type == ID($print)) { + print_database.emplace_back(); + auto &print = print_database.back(); + print.cell = cell; + print.fmt.parse_rtlil(cell); + print.past_trg = Const(State::Sx, cell->getPort(ID::TRG).size()); + print.past_args = Const(State::Sx, cell->getPort(ID::ARGS).size()); + print.past_en = State::Sx; + } } + std::sort(print_database.begin(), print_database.end()); + if (shared->zinit) { for (auto &it : ff_database) @@ -519,6 +566,9 @@ struct SimInstance return; } + if (cell->type == ID($print)) + return; + log_error("Unsupported cell type: %s (%s.%s)\n", log_id(cell->type), log_id(module), log_id(cell)); } @@ -533,7 +583,7 @@ struct SimInstance Const data = Const(State::Sx, mem.width << port.wide_log2); if (port.clk_enable) - log_error("Memory %s.%s has clocked read ports. Run 'memory' with -nordff.\n", log_id(module), log_id(mem.memid)); + log_error("Memory %s.%s has clocked read ports. Run 'memory_nordff' to transform the circuit to remove those.\n", log_id(module), log_id(mem.memid)); if (addr.is_fully_def()) { int addr_int = addr.as_int(); @@ -760,6 +810,50 @@ struct SimInstance } } + // Do prints *before* assertions + for (auto &print : print_database) { + Cell *cell = print.cell; + bool triggered = false; + + Const trg = get_state(cell->getPort(ID::TRG)); + Const en = get_state(cell->getPort(ID::EN)); + Const args = get_state(cell->getPort(ID::ARGS)); + + if (!en.as_bool()) + goto update_print; + + if (cell->getParam(ID::TRG_ENABLE).as_bool()) { + Const trg_pol = cell->getParam(ID::TRG_POLARITY); + for (int i = 0; i < trg.size(); i++) { + bool pol = trg_pol[i] == State::S1; + State curr = trg[i], past = print.past_trg[i]; + if (pol && curr == State::S1 && past == State::S0) + triggered = true; + if (!pol && curr == State::S0 && past == State::S1) + triggered = true; + } + } else { + if (args != print.past_args || en != print.past_en) + triggered = true; + } + + if (triggered) { + int pos = 0; + for (auto &part : print.fmt.parts) { + part.sig = args.extract(pos, part.sig.size()); + pos += part.sig.size(); + } + + std::string rendered = print.fmt.render(); + log("%s", rendered.c_str()); + } + + update_print: + print.past_trg = trg; + print.past_en = en; + print.past_args = args; + } + if (check_assertions) { for (auto cell : formal_database) @@ -781,8 +875,12 @@ struct SimInstance if (cell->type == ID($assume) && en == State::S1 && a != State::S1) log("Assumption %s.%s (%s) failed.\n", hiername().c_str(), log_id(cell), label.c_str()); - if (cell->type == ID($assert) && en == State::S1 && a != State::S1) - log_warning("Assert %s.%s (%s) failed.\n", hiername().c_str(), log_id(cell), label.c_str()); + if (cell->type == ID($assert) && en == State::S1 && a != State::S1) { + if (shared->serious_asserts) + log_error("Assert %s.%s (%s) failed.\n", hiername().c_str(), log_id(cell), label.c_str()); + else + log_warning("Assert %s.%s (%s) failed.\n", hiername().c_str(), log_id(cell), label.c_str()); + } } } @@ -1263,6 +1361,8 @@ struct SimWorker : SimShared set_inports(clock, State::Sx); set_inports(clockn, State::Sx); + top->set_initstate_outputs(initstate ? State::S1 : State::S0); + update(false); register_output_step(0); @@ -1279,6 +1379,9 @@ struct SimWorker : SimShared update(true); register_output_step(10*cycle + 5); + if (cycle == 0) + top->set_initstate_outputs(State::S0); + if (debug) log("\n===== %d =====\n", 10*cycle + 10); else if (verbose) @@ -1860,7 +1963,7 @@ struct SimWorker : SimShared if (yw.steps.empty()) { log_warning("Yosys witness file `%s` contains no time steps\n", yw.filename.c_str()); } else { - top->set_initstate_outputs(State::S1); + top->set_initstate_outputs(initstate ? State::S1 : State::S0); set_yw_state(yw, hierarchy, 0); set_yw_clocks(yw, hierarchy, true); initialize_stable_past(); @@ -2453,6 +2556,9 @@ struct SimPass : public Pass { log(" -n \n"); log(" number of clock cycles to simulate (default: 20)\n"); log("\n"); + log(" -noinitstate\n"); + log(" do not activate $initstate cells during the first cycle\n"); + log("\n"); log(" -a\n"); log(" use all nets in VCD/FST operations, not just those with public names\n"); log("\n"); @@ -2497,6 +2603,10 @@ struct SimPass : public Pass { log(" -sim-gate\n"); log(" co-simulation, x in FST can match any value in simulation\n"); log("\n"); + log(" -assert\n"); + log(" fail the simulation command if, in the course of simulating,\n"); + log(" any of the asserts in the design fail\n"); + log("\n"); log(" -q\n"); log(" disable per-cycle/sample log message\n"); log("\n"); @@ -2549,6 +2659,10 @@ struct SimPass : public Pass { worker.cycles_set = true; continue; } + if (args[argidx] == "-noinitstate") { + worker.initstate = false; + continue; + } if (args[argidx] == "-rstlen" && argidx+1 < args.size()) { worker.rstlen = atoi(args[++argidx].c_str()); continue; @@ -2651,6 +2765,10 @@ struct SimPass : public Pass { worker.sim_mode = SimulationMode::gate; continue; } + if (args[argidx] == "-assert") { + worker.serious_asserts = true; + continue; + } if (args[argidx] == "-x") { worker.ignore_x = true; continue; diff --git a/passes/techmap/Makefile.inc b/passes/techmap/Makefile.inc index 22557e88390..3ff4959e16b 100644 --- a/passes/techmap/Makefile.inc +++ b/passes/techmap/Makefile.inc @@ -6,6 +6,7 @@ OBJS += passes/techmap/techmap.o OBJS += passes/techmap/simplemap.o OBJS += passes/techmap/dfflibmap.o OBJS += passes/techmap/maccmap.o +OBJS += passes/techmap/booth.o OBJS += passes/techmap/libparse.o ifeq ($(ENABLE_ABC),1) diff --git a/passes/techmap/abc.cc b/passes/techmap/abc.cc index 364a8e54458..42287966288 100644 --- a/passes/techmap/abc.cc +++ b/passes/techmap/abc.cc @@ -127,10 +127,15 @@ bool clk_polarity, en_polarity, arst_polarity, srst_polarity; RTLIL::SigSpec clk_sig, en_sig, arst_sig, srst_sig; dict pi_map, po_map; +int undef_bits_lost; + int map_signal(RTLIL::SigBit bit, gate_type_t gate_type = G(NONE), int in1 = -1, int in2 = -1, int in3 = -1, int in4 = -1) { assign_map.apply(bit); + if (bit == State::Sx) + undef_bits_lost++; + if (signal_map.count(bit) == 0) { gate_t gate; gate.id = signal_list.size(); @@ -702,7 +707,7 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin std::vector &liberty_files, std::vector &genlib_files, std::string constr_file, bool cleanup, vector lut_costs, bool dff_mode, std::string clk_str, bool keepff, std::string delay_target, std::string sop_inputs, std::string sop_products, std::string lutin_shared, bool fast_mode, - const std::vector &cells, bool show_tempdir, bool sop_mode, bool abc_dress) + const std::vector &cells, bool show_tempdir, bool sop_mode, bool abc_dress, std::vector &dont_use_cells) { module = current_module; map_autoidx = autoidx++; @@ -795,8 +800,13 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin std::string abc_script = stringf("read_blif \"%s/input.blif\"; ", tempdir_name.c_str()); if (!liberty_files.empty() || !genlib_files.empty()) { - for (std::string liberty_file : liberty_files) - abc_script += stringf("read_lib -w \"%s\"; ", liberty_file.c_str()); + std::string dont_use_args; + for (std::string dont_use_cell : dont_use_cells) { + dont_use_args += stringf("-X \"%s\" ", dont_use_cell.c_str()); + } + for (std::string liberty_file : liberty_files) { + abc_script += stringf("read_lib %s -w \"%s\" ; ", dont_use_args.c_str(), liberty_file.c_str()); + } for (std::string liberty_file : genlib_files) abc_script += stringf("read_library \"%s\"; ", liberty_file.c_str()); if (!constr_file.empty()) @@ -880,10 +890,15 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin } } + undef_bits_lost = 0; + had_init = false; for (auto c : cells) extract_cell(c, keepff); + if (undef_bits_lost) + log("Replacing %d occurrences of constant undef bits with constant zero bits\n", undef_bits_lost); + for (auto wire : module->wires()) { if (wire->port_id > 0 || wire->get_bool_attribute(ID::keep)) mark_port(wire); @@ -1503,6 +1518,10 @@ struct AbcPass : public Pass { log(" generate netlists for the specified cell library (using the liberty\n"); log(" file format).\n"); log("\n"); + log(" -dont_use \n"); + log(" generate netlists for the specified cell library (using the liberty\n"); + log(" file format).\n"); + log("\n"); log(" -genlib \n"); log(" generate netlists for the specified cell library (using the SIS Genlib\n"); log(" file format).\n"); @@ -1639,7 +1658,7 @@ struct AbcPass : public Pass { std::string exe_file = yosys_abc_executable; std::string script_file, default_liberty_file, constr_file, clk_str; - std::vector liberty_files, genlib_files; + std::vector liberty_files, genlib_files, dont_use_cells; std::string delay_target, sop_inputs, sop_products, lutin_shared = "-S 1"; bool fast_mode = false, dff_mode = false, keepff = false, cleanup = true; bool show_tempdir = false, sop_mode = false; @@ -1722,6 +1741,10 @@ struct AbcPass : public Pass { liberty_files.push_back(args[++argidx]); continue; } + if (arg == "-dont_use" && argidx+1 < args.size()) { + dont_use_cells.push_back(args[++argidx]); + continue; + } if (arg == "-genlib" && argidx+1 < args.size()) { genlib_files.push_back(args[++argidx]); continue; @@ -2028,7 +2051,7 @@ struct AbcPass : public Pass { if (!dff_mode || !clk_str.empty()) { abc_module(design, mod, script_file, exe_file, liberty_files, genlib_files, constr_file, cleanup, lut_costs, dff_mode, clk_str, keepff, - delay_target, sop_inputs, sop_products, lutin_shared, fast_mode, mod->selected_cells(), show_tempdir, sop_mode, abc_dress); + delay_target, sop_inputs, sop_products, lutin_shared, fast_mode, mod->selected_cells(), show_tempdir, sop_mode, abc_dress, dont_use_cells); continue; } @@ -2190,7 +2213,7 @@ struct AbcPass : public Pass { srst_polarity = std::get<6>(it.first); srst_sig = assign_map(std::get<7>(it.first)); abc_module(design, mod, script_file, exe_file, liberty_files, genlib_files, constr_file, cleanup, lut_costs, !clk_sig.empty(), "$", - keepff, delay_target, sop_inputs, sop_products, lutin_shared, fast_mode, it.second, show_tempdir, sop_mode, abc_dress); + keepff, delay_target, sop_inputs, sop_products, lutin_shared, fast_mode, it.second, show_tempdir, sop_mode, abc_dress, dont_use_cells); assign_map.set(mod); } } diff --git a/passes/techmap/booth.cc b/passes/techmap/booth.cc new file mode 100644 index 00000000000..e1e6b360ab6 --- /dev/null +++ b/passes/techmap/booth.cc @@ -0,0 +1,1276 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2023 Andy Fox https://www.linkedin.com/in/awfox/ + * + * 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. + * + */ + +/* + Booth Pass + ---------- + + Replace $mul with booth encoded multipliers. Two different + architectures used for signed/unsigned. + + References: + Signed architecture: A Low Power Radix-4 Booth Multipliers with Pre-Encoded Mechanism, IEEE Access + https://ieeexplore.ieee.org/document/9121226 + + Unsigned architecture: Gary Bewick, Fast Multiplication algorithms and implementation. Stanford PhD: + http://i.stanford.edu/pub/cstr/reports/csl/tr/94/617/CSL-TR-94-617.pdf + + How to use: + Add booth pass to your yosys script eg: + + read_verilog smultiply5_rtl.v + opt + wreduce + opt + booth + alumacc + maccmap + opt + techmap -map ./techmap.v + dfflibmap -liberty NangateOpenCellLibrary_typical.lib + abc -liberty NangateOpenCellLibrary_typical.lib + stat -liberty NangateOpenCellLibrary_typical.lib + write_verilog -norename booth_final.v + +or in generic synthesis call with -booth argument: +synth -top my_design -booth +*/ + +//FIXME: These debug prints are broken now, should be fixed or removed. +//#define DEBUG_CPA + +#include "kernel/sigtools.h" +#include "kernel/yosys.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct BoothPassWorker { + + RTLIL::Module *module; + SigMap sigmap; + int booth_counter; + + BoothPassWorker(RTLIL::Module *module) : module(module), sigmap(module) { booth_counter = 0; } + + // Booth unsigned decoder lsb + SigBit Bur4d_lsb(std::string name, SigBit lsb_i, SigBit one_i, SigBit s_i) + { + SigBit and_op = module->AndGate(NEW_ID_SUFFIX(name), lsb_i, one_i); + return module->XorGate(NEW_ID_SUFFIX(name), and_op, s_i); + } + + // Booth unsigned radix4 decoder + SigBit Bur4d_n(std::string name, SigBit yn_i, SigBit ynm1_i, SigBit one_i, SigBit two_i, SigBit s_i) + { + // ppij = ((yn & one) | (ynm1 & two)) ^ s; + SigBit an1 = module->AndGate(NEW_ID_SUFFIX(name), yn_i, one_i); + SigBit an2 = module->AndGate(NEW_ID_SUFFIX(name), ynm1_i, two_i); + SigBit or1 = module->OrGate(NEW_ID_SUFFIX(name), an1, an2); + return module->XorGate(NEW_ID_SUFFIX(name), s_i, or1); + } + + // Booth unsigned radix4 decoder + SigBit Bur4d_msb(std::string name, SigBit msb_i, SigBit two_i, SigBit s_i) + { + // ppij = (msb & two) ^ s; + SigBit an1 = module->AndGate(NEW_ID_SUFFIX(name), msb_i, two_i); + return module->XorGate(NEW_ID_SUFFIX(name), s_i, an1); + } + + // half adder, used in CPA + void BuildHa(std::string name, SigBit a_i, SigBit b_i, SigBit &s_o, SigBit &c_o) + { + s_o = module->XorGate(NEW_ID_SUFFIX(name), a_i, b_i); + c_o = module->AndGate(NEW_ID_SUFFIX(name), a_i, b_i); + } + + // Booth unsigned radix 4 encoder + void BuildBur4e(std::string name, SigBit y0_i, SigBit y1_i, SigBit y2_i, + SigBit &one_o, SigBit &two_o, SigBit &s_o, SigBit &sb_o) + { + one_o = module->XorGate(NEW_ID_SUFFIX(name), y0_i, y1_i); + s_o = y2_i; + sb_o = module->NotGate(NEW_ID_SUFFIX(name), y2_i); + SigBit y1_xnor_y2 = module->XnorGate(NEW_ID_SUFFIX(name), y1_i, y2_i); + two_o = module->NorGate(NEW_ID_SUFFIX(name), y1_xnor_y2, one_o); + } + + void BuildBr4e(std::string name, SigBit y2_m1_i, + SigBit y2_i, // y2i + SigBit y2_p1_i, + SigBit &negi_o, SigBit &twoi_n_o, SigBit &onei_n_o, SigBit &cori_o) + { + auto y2_p1_n = module->NotGate(NEW_ID_SUFFIX(name), y2_p1_i); + auto y2_n = module->NotGate(NEW_ID_SUFFIX(name), y2_i); + auto y2_m1_n = module->NotGate(NEW_ID_SUFFIX(name), y2_m1_i); + + negi_o = y2_p1_i; + + // twoi_n = ~( + // (y2_p1_n & y2_i & y2_m1_i) | + // (y2_p1 & y2_n & y2_m1_n) + // ) + twoi_n_o = module->NorGate(NEW_ID_SUFFIX(name), + module->AndGate(NEW_ID_SUFFIX(name), y2_p1_n, module->AndGate(NEW_ID_SUFFIX(name), y2_i, y2_m1_i)), + module->AndGate(NEW_ID_SUFFIX(name), y2_p1_i, module->AndGate(NEW_ID_SUFFIX(name), y2_n, y2_m1_n)) + ); + + // onei_n = ~(y2_m1_i ^ y2_i); + onei_n_o = module->XnorGate(NEW_ID_SUFFIX(name), y2_m1_i, y2_i); + // cori = (y2_m1_n | y2_n) & y2_p1_i; + cori_o = module->AndGate(NEW_ID_SUFFIX(name), module->OrGate(NEW_ID_SUFFIX(name), y2_m1_n, y2_n), y2_p1_i); + } + + // + // signed booth radix 4 decoder + // + void BuildBr4d(std::string name, SigBit nxj_m1_i, SigBit twoi_n_i, SigBit xj_i, SigBit negi_i, SigBit onei_n_i, + SigBit &ppij_o, SigBit &nxj_o) + { + // nxj_in = xnor(xj,negi) + // nxj_o = xnj_in, + // ppij = ~( (nxj_m1_i | twoi_n_i) & (nxj_int | onei_n_i)); + nxj_o = module->XnorGate(NEW_ID_SUFFIX(name), xj_i, negi_i); + ppij_o = module->NandGate(NEW_ID_SUFFIX(name), + module->OrGate(NEW_ID_SUFFIX(name), nxj_m1_i, twoi_n_i), + module->OrGate(NEW_ID_SUFFIX(name), nxj_o, onei_n_i) + ); + } + + /* + In signed case 1st two bits best realised + using non-booth encoded logic. We can save a booth + encoder for the first couple of bits. + */ + void BuildBoothQ1(std::string name, SigBit negi_i, SigBit cori_i, SigBit x0_i, SigBit x1_i, SigBit y0_i, + SigBit y1_i, + SigBit &nxj_o, SigBit &cor_o, SigBit &pp0_o, SigBit &pp1_o) + { + /* + assign NXJO = ~(X1 ^ NEGI); + assign PP0 = (X0 & Y0); + //and terms for multiply + wire pp1_1_int = X1 & Y0; + wire pp1_2_int = X0 & Y1; + //sum generation for pp[1] + assign PP1 = pp1_1_int ^ pp1_2_int; + //correction propagation + assign CORO = (~PP1 & ~PP0)? CORI : 1'b0; + */ + nxj_o = module->XnorGate(NEW_ID_SUFFIX(name), x1_i, negi_i); + pp0_o = module->AndGate(NEW_ID_SUFFIX(name), x0_i, y0_i); + SigBit pp1_1_int = module->AndGate(NEW_ID_SUFFIX(name), x1_i, y0_i); + SigBit pp1_2_int = module->AndGate(NEW_ID_SUFFIX(name), x0_i, y1_i); + pp1_o = module->XorGate(NEW_ID_SUFFIX(name), pp1_1_int, pp1_2_int); + + SigBit pp1_nor_pp0 = module->NorGate(NEW_ID_SUFFIX(name), pp1_o, pp0_o); + cor_o = module->AndGate(NEW_ID_SUFFIX(name), pp1_nor_pp0, cori_i); + } + + void run() + { + for (auto cell : module->selected_cells()) { + if (cell->type != ID($mul)) + continue; + + SigSpec A = cell->getPort(ID::A); + SigSpec B = cell->getPort(ID::B); + SigSpec Y = cell->getPort(ID::Y); + int x_sz = GetSize(A), y_sz = GetSize(B), z_sz = GetSize(Y); + + if (x_sz < 4 || y_sz < 4 || z_sz < 8) { + log_debug("Not mapping cell %s sized at %dx%x, %x: size below threshold\n", + log_id(cell), x_sz, y_sz, z_sz); + continue; + } + + log_assert(cell->getParam(ID::A_SIGNED).as_bool() == cell->getParam(ID::B_SIGNED).as_bool()); + bool is_signed = cell->getParam(ID::A_SIGNED).as_bool(); + + log("Mapping cell %s to %s Booth multiplier\n", log_id(cell), is_signed ? "signed" : "unsigned"); + + // To simplify the generator size the arguments + // to be the same. Then allow logic synthesis to + // clean things up. Size to biggest + + int x_sz_revised = x_sz; + int y_sz_revised = y_sz; + + if (x_sz != y_sz) { + if (x_sz < y_sz) { + if (y_sz % 2 != 0) { + x_sz_revised = y_sz + 1; + y_sz_revised = y_sz + 1; + } else { + x_sz_revised = y_sz; + } + } else { + if (x_sz % 2 != 0) { + y_sz_revised = x_sz + 1; + x_sz_revised = x_sz + 1; + } else { + y_sz_revised = x_sz; + } + } + } else { + if (x_sz % 2 != 0) { + y_sz_revised = y_sz + 1; + x_sz_revised = x_sz + 1; + } + } + + log_assert((x_sz_revised == y_sz_revised) && (x_sz_revised % 2 == 0) && (y_sz_revised % 2 == 0)); + + + A.extend_u0(x_sz_revised, is_signed); + B.extend_u0(y_sz_revised, is_signed); + + // Make sure output domain is big enough to take + // all combinations. + // Later logic synthesis will kill unused + // portions of the output domain. + + int required_op_size = x_sz_revised + y_sz_revised; + + if (required_op_size != z_sz) { + SigSpec expanded_Y = module->addWire(NEW_ID, required_op_size); + SigSpec Y_driver = expanded_Y; + Y_driver.extend_u0(Y.size(), is_signed); + module->connect(Y, Y_driver); + Y = expanded_Y; + } + log_assert(GetSize(Y) == required_op_size); + + if (!is_signed) /* unsigned multiplier */ + CreateBoothUMult(module, + A, // multiplicand + B, // multiplier(scanned) + Y // result + ); + else /* signed multiplier */ + CreateBoothSMult(module, + A, // multiplicand + B, // multiplier(scanned) + Y // result (sized) + ); + + module->remove(cell); + booth_counter++; + } + } + + /* + Build Unsigned Multiplier. + ------------------------- + Create a booth unsigned multiplier. + Uses a generic booth multiplier with + extra row of decoders and extended multiplier + */ + + void CreateBoothUMult(RTLIL::Module *module, + SigSpec X, // multiplicand + SigSpec Y, // multiplier + SigSpec Z) + { // result + int x_sz = X.size(), z_sz = Z.size(); + + SigSpec one_int, two_int, s_int, sb_int; + int encoder_count = 0; + + BuildBoothUMultEncoders(Y, one_int, two_int, s_int, sb_int, module, encoder_count); + + // Build the decoder rows + // format of each Partial product to be passed to CSA + // tree builder: + // + // Bits to be added + // Shift + // Sign bit to be added + // + std::vector> ppij_int; + + // Row 0: special case 1. Format S/.S.S.C.Data + SigSpec ppij_row_0; + BuildBoothUMultDecoderRow0(module, X, s_int, sb_int, one_int, two_int, ppij_row_0); + + // data, shift, sign + ppij_int.push_back(std::make_tuple(ppij_row_0, 0, s_int[0])); + + for (int i = 1; i < encoder_count - 2; i++) { + // format 1,S.Data.shift = encoder_ix*2,sign = sb_int[i] + SigSpec ppij_row_n; + + BuildBoothUMultDecoderRowN(module, + X, // multiplicand + one_int[i], two_int[i], s_int[i], sb_int[i], ppij_row_n, i, + false, // include sign + false // include constant + ); + // data, shift, sign + ppij_int.push_back(std::make_tuple(ppij_row_n, i * 2, s_int[i])); + } + + // Build second to last row + // format S/,Data + sign bit + SigSpec ppij_row_em1; + BuildBoothUMultDecoderRowN(module, X, one_int[encoder_count - 2], two_int[encoder_count - 2], s_int[encoder_count - 2], + sb_int[encoder_count - 2], ppij_row_em1, encoder_count - 2, + false, // include sign + true // no constant + ); + ppij_int.push_back(std::make_tuple(ppij_row_em1, (encoder_count - 2) * 2, s_int[encoder_count - 2])); + // Build last row + // format Data + sign bit + SigSpec ppij_row_e; + BuildBoothUMultDecoderRowN(module, X, one_int[encoder_count - 1], two_int[encoder_count - 1], s_int[encoder_count - 1], + sb_int[encoder_count - 1], ppij_row_e, encoder_count - 1, + true, // no sign + true // no constant + ); + ppij_int.push_back(std::make_tuple(ppij_row_e, (encoder_count - 1) * 2, s_int[encoder_count - 1])); + + // Debug dump out partial products + // DebugDumpPP(ppij_int); + + // Summation of Partial Products (Wallace Tree) + std::vector aligned_pp; + aligned_pp.resize(encoder_count + 1); // make an entirely redundant row + // just for sign bit in lsb. (We then filter this out). + + // resize all to be same size as z + for (int i = 0; i < encoder_count + 1; i++) + aligned_pp[i].extend_u0(z_sz); + + AlignPP(x_sz, z_sz, ppij_int, aligned_pp); + + // Debug: dump out aligned partial products. + // Later on yosys will clean up unused constants + // DebugDumpAlignPP(aligned_pp); + + SigSpec s_vec; + SigSpec c_vec; + std::vector> debug_csa_trees; + + debug_csa_trees.resize(z_sz); + + BuildCSATree(module, aligned_pp, s_vec, c_vec, debug_csa_trees); + + // Debug code: Dump out the csa trees + // DumpCSATrees(debug_csa_trees); + // Build the CPA to do the final accumulation. + BuildCPA(module, s_vec, c_vec, Z); + } + + /* + Build Row 0 of decoders + */ + + void BuildBoothUMultDecoderRow0(RTLIL::Module *module, + SigSpec X, // multiplicand + SigSpec s_int, SigSpec sb_int, SigSpec one_int, + SigSpec two_int, SigSpec &ppij_vec) + { + (void)module; + int x_sz = GetSize(X); + SigBit ppij; + + // lsb + ppij_vec.append(Bur4d_lsb("row0_lsb_dec", X[0], one_int[0], s_int[0])); + + // 1..xsize -1 + for (int i = 1; i < x_sz; i++) + ppij_vec.append(Bur4d_n(stringf("row0_dec_%d", i), X[i], X[i - 1], + one_int[0], two_int[0], s_int[0])); + + // The redundant bit. Duplicate decoding of last bit. + ppij_vec.append(Bur4d_msb("row0_dec_msb", X[x_sz - 1], two_int[0], s_int[0])); + + // append the sign bits + ppij_vec.append(s_int[0]); + ppij_vec.append(s_int[0]); + ppij_vec.append(sb_int[0]); + } + + // Build a generic row of decoders. + + void BuildBoothUMultDecoderRowN(RTLIL::Module *module, + SigSpec X, // multiplicand + SigSpec one_int, SigSpec two_int, SigSpec s_int, SigSpec sb_int, + SigSpec &ppij_vec, int row_ix, bool no_sign, bool no_constant) + { + (void)module; + int x_sz = GetSize(X); + + // lsb + ppij_vec.append(Bur4d_lsb(stringf("row_%d_lsb_dec", row_ix), X[0], one_int, s_int)); + + // core bits + for (int i = 1; i < x_sz; i++) + ppij_vec.append(Bur4d_n(stringf("row_%d_dec_%d", row_ix, i), X[i], X[i - 1], + one_int, two_int, s_int)); + + // redundant bit + ppij_vec.append(Bur4d_msb("row_dec_red", X[x_sz - 1], two_int, s_int)); + + // sign bit + if (!no_sign) // if no sign is false then make a sign bit + ppij_vec.append(sb_int); + + // constant bit + if (!no_constant) // if non constant is false make a constant bit + ppij_vec.append(State::S1); + } + + void DebugDumpAlignPP(std::vector> &aligned_pp) + { + printf("Aligned & Padded Partial products\n"); + int pp_ix = 0; + for (auto pp_row : aligned_pp) { + printf("PP_%d \t", pp_ix); + for (unsigned i = 0; i < pp_row.size(); i++) + printf("[%d] %s ", i, pp_row[i] == nullptr ? " 0 " : pp_row[i]->name.c_str()); + printf("\n"); + pp_ix++; + } + } + + // Debug routines to inspect intermediate results + void DebugDumpPP(std::vector, int, RTLIL::Wire *>> &ppij_int) + { + printf("Debug dump of partial products\n"); + int pp_ix = 0; + + for (auto pp : ppij_int) { + int shift = get<1>(pp); + RTLIL::Wire *sign_bit = get<2>(pp); + + printf("PP %d\n", pp_ix); + printf("\tShift %d\n", shift); + printf("\tData (0 lsb)\n\t"); + int ix = 0; + + for (auto pp_wire : get<0>(pp)) { + RTLIL::IdString wire_name = pp_wire->name; + + printf(" [%d]:%s ", ix, wire_name.c_str()); + ix++; + } + printf("\n"); + printf("\tSign bit to add in: %s\n", sign_bit->name.c_str()); + + pp_ix++; + } + } + + void DumpCSATrees(std::vector> &debug_csa_trees) + { + int i = 0; + for (auto csa_tree : debug_csa_trees) { + printf("CSA Tree column %d\n", i); + int ix = 0; + for (auto csa_elem : csa_tree) { + printf("\tCell %d %s type %s\n", ix, csa_elem->name.c_str(), csa_elem->type.c_str()); + if (csa_elem->getPort(ID::A) == State::S0) + printf("\tA set to constant 0\n"); + else if (csa_elem->getPort(ID::A) == State::S1) + printf("\tA set to constant 1\n"); + else + printf("\tA driven by %s\n", csa_elem->getPort(ID::A).as_wire()->name.c_str()); + + if (csa_elem->getPort(ID::B) == State::S0) + printf("\tB set to constant 0\n"); + else if (csa_elem->getPort(ID::B) == State::S1) + printf("\tB set to constant 1\n"); + else + printf("\tB driven by %s\n", csa_elem->getPort(ID::B).as_wire()->name.c_str()); + + if (csa_elem->getPort(ID::C) == State::S0) + printf("\tC set to constant 0\n"); + else if (csa_elem->getPort(ID::C) == State::S1) + printf("\tC set to constant 1\n"); + else + printf("\tC driven by %s\n", csa_elem->getPort(ID::C).as_wire()->name.c_str()); + + printf("Carry out: %s\n", csa_elem->getPort(ID::X).as_wire()->name.c_str()); + printf("Sum out: %s\n", csa_elem->getPort(ID::Y).as_wire()->name.c_str()); + + ix++; + } + i++; + } + } + + void BuildCSATree(RTLIL::Module *module, std::vector &bits_to_reduce, SigSpec &s_vec, + SigSpec &c_vec, std::vector> &debug_csa_trees) + { + + if (!(bits_to_reduce.size() > 0)) + return; + + int column_size = bits_to_reduce[0].size(); + int row_size = bits_to_reduce.size(); + SigSpec carry_bits_to_add_to_next_column; + + for (int column_ix = 0; column_ix < column_size; column_ix++) { + + // get the bits in this column. + SigSpec column_bits; + for (int row_ix = 0; row_ix < row_size; row_ix++) { + if (bits_to_reduce[row_ix][column_ix] != State::S0) + column_bits.append(bits_to_reduce[row_ix][column_ix]); + } + for (auto c : carry_bits_to_add_to_next_column) { +#ifdef DEBUG_CSA + printf("\t Propagating column bit %s to column %d from column %d\n", c->name.c_str(), column_ix, column_ix - 1); +#endif + column_bits.append(c); + } + + carry_bits_to_add_to_next_column = {}; + +#ifdef DEBUG_CSA + printf("Column %d Reducing %d bits\n", column_ix, column_bits.size()); + for (auto b : column_bits) { + printf("\t %s\n", b->name.c_str()); + } + printf("\n"); +#endif + + SigBit s, c; +#ifdef DEBUG_CSA + int csa_count_before = debug_csa_trees[column_ix].size(); +#endif + + ReduceBits(module, column_ix, column_bits, s, c, carry_bits_to_add_to_next_column, debug_csa_trees); + + s_vec.append(s); + c_vec.append(c); + +#ifdef DEBUG_CSA + int csa_count_after = debug_csa_trees[column_ix].size(); + + printf("Column %d Created %d csa tree elements\n", column_ix, csa_count_after - csa_count_before); +#endif + } + } + + /* + Alignment: + --------- + + Concept traverse from last row. + Pad row by shift + Add sign bit from prior row to 2 bits right of end of data. + + Example + + SCDDDDDDD- +S + DDDDDDDD_ + + ==> + SCDDDDDDD- + DDDDDDDD_S <-- prior rows sign bit added 2 columns to right on next row. + + Pad out rows with zeros and left the opt pass clean them up. + + */ + void AlignPP(int x_sz, int z_sz, std::vector> &ppij_int, + std::vector &aligned_pp) + { + unsigned aligned_pp_ix = aligned_pp.size() - 1; + + // default is zero for everything (so don't have to think to hard + // about padding). + + for (unsigned i = 0; i < aligned_pp.size(); i++) { + for (int j = 0; j < z_sz; j++) { + aligned_pp[i][j] = State::S0; + } + } + + // for very last row we just have the sign bit + // Note that the aligned_pp is one row bigger + // than the ppij_int. We put the sign bit + // in first column of the last partial product + // which is at index corresponding to size of multiplicand + { + SigBit prior_row_sign = get<2>(ppij_int[aligned_pp_ix - 1]); + //if (prior_row_sign) { + log_assert(aligned_pp_ix < aligned_pp.size()); + log_assert(x_sz - 1 < (int)(aligned_pp[aligned_pp_ix].size())); + aligned_pp[aligned_pp_ix][x_sz - 1] = prior_row_sign; + //} + } + + for (int row_ix = aligned_pp_ix - 1; row_ix >= 0; row_ix--) { + int shift_amount = get<1>(ppij_int[row_ix]); + + // copy in data + int copy_ix = shift_amount; + for (auto w : get<0>(ppij_int[row_ix])) { + if (copy_ix < aligned_pp[row_ix].size()) { + aligned_pp[row_ix][copy_ix] = w; + } + copy_ix++; + } + + // copy in the sign bit from the prior row + if (row_ix > 0) { + // if sign bit on prior row, copy in + // the destination of the sign bit is the (row_ix -1)*2 + // eg destination for sign bit for row 0 is 0. + // eg destination for sign bit for row 1 is 1 + SigBit prior_row_sign = get<2>(ppij_int[row_ix - 1]); + copy_ix = (row_ix - 1) * 2; + aligned_pp[row_ix][copy_ix] = prior_row_sign; + } + } + } + + /* + Build a Carry Propagate Adder + ----------------------------- + First build the sum and carry vectors to be added. + */ + void BuildCPA(RTLIL::Module *module, SigSpec s_vec, SigSpec c_vec, SigSpec result) + { + static int cpa_id; + cpa_id++; + + log_assert(c_vec.size() == s_vec.size()); + log_assert(result.size() == s_vec.size()); + + SigBit carry; + for (int n = 0; n < s_vec.size(); n++) { + std::string carry_name; + + // Base Case: Bit 0 is sum 0 + if (n == 0) { + module->addBufGate(NEW_ID_SUFFIX(stringf("base_buf_%d_%d", cpa_id, n)), s_vec[0], result[0]); + +#ifdef DEBUG_CPA + printf("CPA bit [%d] Cell %s IP 0 %s \n", n, buf->name.c_str(), s_vec[0]->name.c_str()); +#endif + } + + // + // Base Case + // c,s = ha(s_vec[1],c_vec[0]) + // + else if (n == 1) { + std::string ha_name = "cpa_" + std::to_string(cpa_id) + "_ha_" + std::to_string(n); + SigBit ha_op; + BuildHa(ha_name, s_vec[n], c_vec[n - 1], ha_op, carry); + module->connect(result[n], ha_op); + +#ifdef DEBUG_CPA + printf("CPA bit [%d] Cell %s IPs [%s] [%s] \n", n, ha_cell->name.c_str(), s_vec[n]->name.c_str(), + c_vec[n - 1]->name.c_str()); +#endif + + } + // End Case + else if (n == s_vec.size() - 1) { + // Make the carry results.. Two extra bits after fa. + SigBit carry_out = module->addWire(NEW_ID, 1); + module->addFa(NEW_ID_SUFFIX(stringf("cpa_%d_fa_%d", cpa_id, n)), + /* A */ s_vec[n], + /* B */ c_vec[n - 1], + /* C */ carry, + /* X */ carry_out, + /* Y */ result[n] + ); + carry = carry_out; + +#ifdef DEBUG_CPA + printf("CPA bit [%d] Cell %s IPs [%s] [%s] [%s]\n", n, fa_cell->name.c_str(), s_vec[n]->name.c_str(), + c_vec[n - 1]->name.c_str(), carry->name.c_str()); +#endif + if (n + 1 < GetSize(result)) { + // Now make a half adder: c_vec[n] = carry + std::string ha_name = "cpa_" + std::to_string(cpa_id) + "_ha_" + std::to_string(n); + SigBit ha_sum; + SigBit ha_carry; + BuildHa(ha_name, c_vec[n], carry, ha_sum, ha_carry); + if (n + 1 < GetSize(result)) + module->connect(result[n + 1], ha_sum); + if (n + 2 < GetSize(result)) + module->connect(result[n + 2], ha_carry); + } + } + // Step case + else { + SigBit carry_out = module->addWire(NEW_ID_SUFFIX(stringf("cpa_%d_carry_%d", cpa_id, n)), 1); + module->addFa(NEW_ID_SUFFIX(stringf("cpa_%d_fa_%d", cpa_id, n)), + /* A */ s_vec[n], + /* B */ c_vec[n - 1], + /* C */ carry, + /* X */ carry_out, + /* Y */ result[n] + ); + carry = carry_out; +#ifdef DEBUG_CPA + printf("CPA bit [%d] Cell %s IPs [%s] [%s] [%s]\n", n, fa_cell->name.c_str(), s_vec[n]->name.c_str(), + c_vec[n - 1]->name.c_str(), carry->name.c_str()); +#endif + } + } + } + + // Sum the bits in the current column + // Pass the carry bits from each csa to the next + // column for summation. + + void ReduceBits(RTLIL::Module *module, int column_ix, SigSpec column_bits, SigBit &s_result, SigBit &c_result, + SigSpec &carry_bits_to_sum, std::vector> &debug_csa_trees) + { + + int csa_ix = 0; + int column_size = column_bits.size(); + + if (column_size > 0) { + int var_ix = 0; + SigSpec first_csa_ips; + // get the first 3 inputs, if possible + for (var_ix = 0; var_ix < column_bits.size() && first_csa_ips.size() != 3; var_ix++) { + if (column_bits[var_ix] != State::S0) + first_csa_ips.append(column_bits[var_ix]); + } + + if (first_csa_ips.size() > 0) { + // build the first csa + auto s_wire = module->addWire(NEW_ID_SUFFIX(stringf("csa_%d_%d_s", column_ix, csa_ix + 1)), 1); + auto c_wire = module->addWire(NEW_ID_SUFFIX(stringf("csa_%d_%d_c", column_ix, csa_ix + 1)), 1); + + auto csa = module->addFa(NEW_ID_SUFFIX(stringf("csa_%d_%d", column_ix, csa_ix)), + /* A */ first_csa_ips[0], + /* B */ first_csa_ips.size() > 1 ? first_csa_ips[1] : State::S0, + /* C */ first_csa_ips.size() > 2 ? first_csa_ips[2] : State::S0, + /* X */ c_wire, + /* Y */ s_wire + ); + + s_result = s_wire; + c_result = c_wire; + + debug_csa_trees[column_ix].push_back(csa); + csa_ix++; + + if (var_ix <= column_bits.size() - 1) + carry_bits_to_sum.append(c_wire); + + // Now build the rest of the tree if we can + while (var_ix <= column_bits.size() - 1) { + SigSpec csa_ips; + // get the next two variables to sum + for (; var_ix <= column_bits.size() - 1 && csa_ips.size() < 2;) { + // skip any empty bits + if (column_bits[var_ix] != State::S0) + csa_ips.append(column_bits[var_ix]); + var_ix++; + } + + if (csa_ips.size() > 0) { + auto c_wire = module->addWire(NEW_ID_SUFFIX(stringf("csa_%d_%d_c", column_ix, csa_ix + 1)), 1); + auto s_wire = module->addWire(NEW_ID_SUFFIX(stringf("csa_%d_%d_s", column_ix, csa_ix + 1)), 1); + + auto csa = module->addFa(NEW_ID_SUFFIX(stringf("csa_%d_%d", column_ix, csa_ix)), + /* A */ s_result, + /* B */ csa_ips[0], + /* C */ csa_ips.size() > 1 ? csa_ips[1] : State::S0, + /* X */ c_wire, + /* Y */ s_wire + ); + + debug_csa_trees[column_ix].push_back(csa); + csa_ix++; + + if (var_ix <= column_bits.size() - 1) + carry_bits_to_sum.append(c_wire); + + s_result = s_wire; + c_result = c_wire; + } + } + } + } + } + + void BuildBoothUMultEncoders(SigSpec Y, SigSpec &one_int, SigSpec &two_int, + SigSpec &s_int, SigSpec &sb_int, RTLIL::Module *module, int &encoder_ix) + { + int y_sz = GetSize(Y); + + for (int y_ix = 0; y_ix < y_sz;) { + std::string enc_name = stringf("bur_enc_%d", encoder_ix); + + two_int.append(module->addWire(NEW_ID_SUFFIX(stringf("two_int_%d", encoder_ix)), 1)); + one_int.append(module->addWire(NEW_ID_SUFFIX(stringf("one_int_%d", encoder_ix)), 1)); + s_int.append(module->addWire(NEW_ID_SUFFIX(stringf("s_int_%d", encoder_ix)), 1)); + sb_int.append(module->addWire(NEW_ID_SUFFIX(stringf("sb_int_%d", encoder_ix)), 1)); + + if (y_ix == 0) { + BuildBur4e(enc_name, State::S0, Y[y_ix], + Y[y_ix + 1], one_int[encoder_ix], two_int[encoder_ix], s_int[encoder_ix], + sb_int[encoder_ix]); + + y_ix = y_ix + 1; + encoder_ix++; + } else { + // + // step case. If multiplier ends on a boundary + // then add an extra booth encoder bounded by + // zeroes to ensure unsigned works. + // + SigBit y0, y1, y2; + + bool need_padded_cell = false; + + if (y_ix > y_sz - 1) { + y0 = State::S0; + need_padded_cell = false; + } else { + y0 = Y[y_ix]; + y_ix++; + } + + if (y_ix > y_sz - 1) { + need_padded_cell = false; + y1 = State::S0; + } else { + y1 = Y[y_ix]; + y_ix++; + } + + if (y_ix > y_sz - 1) { + need_padded_cell = false; + y2 = State::S0; + } else { + if (y_ix == y_sz - 1) + need_padded_cell = true; + else + need_padded_cell = false; + y2 = Y[y_ix]; + + BuildBur4e(enc_name, y0, y1, y2, one_int[encoder_ix], two_int[encoder_ix], s_int[encoder_ix], + sb_int[encoder_ix]); + } + + encoder_ix++; + + if (need_padded_cell == true) { + // make extra encoder cell + // y_ix at y0, rest 0 + + std::string enc_name = stringf("br_enc_pad_%d", encoder_ix); + + two_int.append(module->addWire(NEW_ID_SUFFIX(stringf("two_int_%d", encoder_ix)), 1)); + one_int.append(module->addWire(NEW_ID_SUFFIX(stringf("one_int_%d", encoder_ix)), 1)); + s_int.append(module->addWire(NEW_ID_SUFFIX(stringf("s_int_%d", encoder_ix)), 1)); + sb_int.append(module->addWire(NEW_ID_SUFFIX(stringf("sb_int_%d", encoder_ix)), 1)); + + SigBit one_o_int, two_o_int, s_o_int, sb_o_int; + BuildBur4e(enc_name, Y[y_ix], State::S0, + State::S0, one_o_int, two_o_int, s_o_int, sb_o_int); + + module->connect(one_int[encoder_ix], one_o_int); + module->connect(two_int[encoder_ix], two_o_int); + module->connect(s_int[encoder_ix], s_o_int); + module->connect(sb_int[encoder_ix], sb_o_int); + y_ix++; + encoder_ix++; + } + } + } + } + + /* + Signed Multiplier + */ + void CreateBoothSMult(RTLIL::Module *module, SigSpec X, SigSpec Y, SigSpec Z) + { // product + int x_sz = X.size(), y_sz = Y.size(), z_sz = Z.size(); + + unsigned enc_count = (y_sz / 2) + (((y_sz % 2) != 0) ? 1 : 0); + int dec_count = x_sz + 1; + + int fa_count = x_sz + 4; + int fa_row_count = enc_count - 1; + + log_debug("Mapping %d x %d -> %d multiplier: %d encoders %d decoders\n", x_sz, y_sz, z_sz, enc_count, dec_count); + + SigSpec negi_n_int, twoi_n_int, onei_n_int, cori_n_int; + + negi_n_int.extend_u0(enc_count); + twoi_n_int.extend_u0(enc_count); + onei_n_int.extend_u0(enc_count); + cori_n_int.extend_u0(enc_count); + + for (unsigned encoder_ix = 1; encoder_ix <= enc_count; encoder_ix++) { + std::string enc_name = stringf("enc_%d", encoder_ix); + negi_n_int[encoder_ix - 1] = module->addWire(NEW_ID_SUFFIX(stringf("negi_n_int_%d", encoder_ix)), 1); + twoi_n_int[encoder_ix - 1] = module->addWire(NEW_ID_SUFFIX(stringf("twoi_n_int_%d", encoder_ix)), 1); + onei_n_int[encoder_ix - 1] = module->addWire(NEW_ID_SUFFIX(stringf("onei_n_int_%d", encoder_ix)), 1); + cori_n_int[encoder_ix - 1] = module->addWire(NEW_ID_SUFFIX(stringf("cori_n_int_%d", encoder_ix)), 1); + + if (encoder_ix == 1) { + BuildBr4e(enc_name, State::S0, Y[0], Y[1], + negi_n_int[encoder_ix - 1], twoi_n_int[encoder_ix - 1], onei_n_int[encoder_ix - 1], + cori_n_int[encoder_ix - 1]); + + } else { + SigBit y1, y2, y3; + + y1 = Y[(encoder_ix - 1) * 2 - 1]; + + if ((encoder_ix - 1) * 2 >= (unsigned)y_sz) + y2 = State::S0; // constant 0 + else + y2 = Y[(encoder_ix - 1) * 2]; // 0 + + if (((encoder_ix - 1) * 2 + 1) >= (unsigned)y_sz) + y3 = State::S0; // constant 0 + else + y3 = Y[(encoder_ix - 1) * 2 + 1]; //+1 + + BuildBr4e(enc_name, y1, y2, y3, + negi_n_int[encoder_ix - 1], twoi_n_int[encoder_ix - 1], onei_n_int[encoder_ix - 1], + cori_n_int[encoder_ix - 1]); + } + } + + // Decoders and PP generation + SigSpec PPij(State::S0, enc_count * dec_count); + SigSpec nxj(State::S0, enc_count * dec_count); + + for (int encoder_ix = 1; encoder_ix <= (int)enc_count; encoder_ix++) { + for (int decoder_ix = 1; decoder_ix <= dec_count; decoder_ix++) { + PPij[((encoder_ix - 1) * dec_count) + decoder_ix - 1] = + module->addWire(NEW_ID_SUFFIX(stringf("ppij_%d_%d", encoder_ix, decoder_ix)), 1); + + nxj[((encoder_ix - 1) * dec_count) + decoder_ix - 1] = + module->addWire(NEW_ID_SUFFIX(stringf("nxj_%s%d_%d", decoder_ix == 1 ? "pre_dec_" : "", + encoder_ix, decoder_ix)), 1); + } + } + + // + // build decoder array + // + for (int encoder_ix = 1; encoder_ix <= (int)enc_count; encoder_ix++) { + // pre-decoder + std::string pre_dec_name = "pre_dec_" + std::to_string(encoder_ix) + "_"; + + if (encoder_ix == 1) { + // quadrant 1 optimization + } else { + module->addNotGate(NEW_ID_SUFFIX(stringf("pre_dec_%d", encoder_ix)), + negi_n_int[encoder_ix - 1], + nxj[(encoder_ix - 1) * dec_count] + ); + } + + for (int decoder_ix = 1; decoder_ix < dec_count; decoder_ix++) { + // range 1..8 + + // quadrant 1 optimization. + if ((decoder_ix == 1 || decoder_ix == 2) && encoder_ix == 1) + continue; + + std::string dec_name = stringf("dec_%d_%d", encoder_ix, decoder_ix); + BuildBr4d(dec_name, nxj[((encoder_ix - 1) * dec_count) + decoder_ix - 1], twoi_n_int[encoder_ix - 1], + X[decoder_ix - 1], negi_n_int[encoder_ix - 1], onei_n_int[encoder_ix - 1], + PPij[((encoder_ix - 1) * dec_count) + decoder_ix - 1], nxj[((encoder_ix - 1) * dec_count) + decoder_ix]); + } + + // duplicate end for sign fix + // applies to 9th decoder (xsz+1 decoder). + std::string dec_name = stringf("dec_%d_%d", encoder_ix, x_sz + 1); + SigBit unused_op; + BuildBr4d(dec_name, nxj[((encoder_ix - 1) * dec_count) + dec_count - 1], twoi_n_int[encoder_ix - 1], + X[dec_count - 2], negi_n_int[encoder_ix - 1], onei_n_int[encoder_ix - 1], + PPij[((encoder_ix - 1) * dec_count) + dec_count - 1], unused_op); + } + + // + // sum up the partial products + // + int fa_el_ix = 0; + int fa_row_ix = 0; + // use 1 d arrays (2d cannot have variable sized indices) + SigSpec fa_sum_n(State::S0, fa_row_count * fa_count); + SigSpec fa_carry_n(State::S0, fa_row_count * fa_count); + + for (fa_row_ix = 0; fa_row_ix < fa_row_count; fa_row_ix++) { + for (fa_el_ix = 0; fa_el_ix < fa_count; fa_el_ix++) { + fa_sum_n[(fa_row_ix * fa_count) + fa_el_ix] = + module->addWire(NEW_ID_SUFFIX(stringf("fa_sum_n_%d_%d", fa_row_ix, fa_el_ix)), 1); + fa_carry_n[(fa_row_ix * fa_count) + fa_el_ix] = + module->addWire(NEW_ID_SUFFIX(stringf("fa_carry_n_%d_%d", fa_row_ix, fa_el_ix)), 1); + } + } + + // full adder creation + std::string bfa_name; + std::string exc_inv_name; + for (fa_row_ix = 0; fa_row_ix < fa_row_count; fa_row_ix++) { + for (fa_el_ix = 0; fa_el_ix < fa_count; fa_el_ix++) { + // base case: 1st row. Inputs from decoders + // Note in rest of tree inputs from prior addition and a decoder + if (fa_row_ix == 0) { + // beginning + // base case: + // first two cells: have B input hooked to 0. + if (fa_el_ix == 0) { + // quadrant 1: we hard code these using non-booth + fa_el_ix++; + + } + // step case + else if (fa_el_ix >= 2 && fa_el_ix <= x_sz) { + // middle (2...x_sz cells) + module->addFa(NEW_ID_SUFFIX(stringf("bfa_0_step_%d_%d_L", fa_row_ix, fa_el_ix)), + /* A */ PPij[(0 * dec_count) + fa_el_ix], + /* B */ PPij[(1 * dec_count) + fa_el_ix - 2], + /* C */ fa_carry_n[(fa_row_ix * fa_count) + fa_el_ix - 1], + /* X */ fa_carry_n[(fa_row_ix * fa_count) + fa_el_ix], + /* Y */ fa_sum_n[(fa_row_ix * fa_count) + fa_el_ix] + ); + } + // end 3 cells: x_sz+1.2.3 + // + else { + // fa_el_ix = x_sz+1 + module->addFa(NEW_ID_SUFFIX(stringf("bfa_0_se_0_%d_%d_L", fa_row_ix, fa_el_ix)), + /* A */ PPij[(0 * dec_count) + x_sz], + /* B */ PPij[(1 * dec_count) + fa_el_ix - 2], + /* C */ fa_carry_n[(fa_row_ix * fa_count) + fa_el_ix - 1], + /* X */ fa_carry_n[(fa_row_ix * fa_count) + fa_el_ix], + /* Y */ fa_sum_n[(fa_row_ix * fa_count) + fa_el_ix] + ); + + // exception:invert ppi + fa_el_ix++; + SigBit d08_inv = module->NotGate(NEW_ID_SUFFIX(stringf("bfa_0_exc_inv1_%d_%d_L", fa_row_ix, fa_el_ix)), + PPij[(0 * dec_count) + dec_count - 1]); + + SigBit d18_inv = module->NotGate(NEW_ID_SUFFIX(stringf("bfa_0_exc_inv2_%d_%d_L", fa_row_ix, fa_el_ix)), + PPij[(1 * dec_count) + dec_count - 1]); + + module->addFa(NEW_ID_SUFFIX(stringf("bfa_0_se_1_%d_%d_L", fa_row_ix, fa_el_ix)), + /* A */ d08_inv, + /* B */ d18_inv, + /* C */ fa_carry_n[(fa_row_ix * fa_count) + fa_el_ix - 1], + /* X */ fa_carry_n[(fa_row_ix * fa_count) + fa_el_ix], + /* Y */ fa_sum_n[(fa_row_ix * fa_count) + fa_el_ix] + ); + + // sign extension + fa_el_ix++; + + module->addFa(NEW_ID_SUFFIX(stringf("bfa_0_se_2_%d_%d_L", fa_row_ix, fa_el_ix)), + /* A */ State::S0, + /* B */ State::S1, + /* C */ fa_carry_n[(fa_row_ix * fa_count) + fa_el_ix - 1], + /* X */ fa_carry_n[(fa_row_ix * fa_count) + fa_el_ix], + /* Y */ fa_sum_n[(fa_row_ix * fa_count) + fa_el_ix] + ); + } + } + + // step case: 2nd and rest of rows. (fa_row_ix == 1...n) + // special because these are driven by a decoder and prior fa. + else { + // beginning + if (fa_el_ix == 0) { + // first two cells: have B input hooked to 0. + // column is offset by row_ix*2 + + module->addFa(NEW_ID_SUFFIX(stringf("bfa_base_%d_%d_L", fa_row_ix, fa_el_ix)), + /* A */ fa_sum_n[(fa_row_ix - 1) * fa_count + 2], + /* B */ State::S0, + /* C */ cori_n_int[fa_row_ix], + /* X */ fa_carry_n[(fa_row_ix * fa_count) + fa_el_ix], + /* Y */ fa_sum_n[(fa_row_ix * fa_count) + fa_el_ix] + ); + fa_el_ix++; + + module->addFa(NEW_ID_SUFFIX(stringf("bfa_base_%d_%d_L", fa_row_ix, fa_el_ix)), + /* A */ fa_sum_n[(fa_row_ix - 1) * fa_count + 3], // from prior full adder row + /* B */ State::S0, + /* C */ fa_carry_n[(fa_row_ix * fa_count) + fa_el_ix - 1], + /* X */ fa_carry_n[(fa_row_ix * fa_count) + fa_el_ix], + /* Y */ fa_sum_n[(fa_row_ix * fa_count) + fa_el_ix] + ); + + } + + else if (fa_el_ix >= 2 && fa_el_ix <= x_sz + 1) { + // middle (2...x_sz+1 cells) + module->addFa(NEW_ID_SUFFIX(stringf("bfa_step_%d_%d_L", fa_row_ix, fa_el_ix)), + /* A */ fa_sum_n[(fa_row_ix - 1) * fa_count + fa_el_ix + 2], + /* B */ PPij[(fa_row_ix + 1) * dec_count + fa_el_ix - 2], + /* C */ fa_carry_n[(fa_row_ix * fa_count) + fa_el_ix - 1], + /* X */ fa_carry_n[(fa_row_ix * fa_count) + fa_el_ix], + /* Y */ fa_sum_n[(fa_row_ix * fa_count) + fa_el_ix] + ); + } + + else if (fa_el_ix > x_sz + 1) { + // end two bits: sign extension + SigBit d_inv = module->NotGate(NEW_ID_SUFFIX(stringf("bfa_se_inv_%d_%d_L", fa_row_ix, fa_el_ix)), + PPij[((fa_row_ix + 1) * dec_count) + dec_count - 1]); + + module->addFa(NEW_ID_SUFFIX(stringf("bfa_se_%d_%d_L", fa_row_ix, fa_el_ix)), + /* A */ fa_carry_n[((fa_row_ix - 1) * fa_count) + fa_count - 1], + /* B */ d_inv, + /* C */ fa_carry_n[(fa_row_ix * fa_count) + fa_el_ix - 1], + /* X */ fa_carry_n[(fa_row_ix * fa_count) + fa_el_ix], + /* Y */ fa_sum_n[(fa_row_ix * fa_count) + fa_el_ix] + ); + fa_el_ix++; + + // sign extension + module->addFa(NEW_ID_SUFFIX(stringf("bfa_se_%d_%d_L", fa_row_ix, fa_el_ix)), + /* A */ State::S0, + /* B */ State::S1, + /* C */ fa_carry_n[(fa_row_ix * fa_count) + fa_el_ix - 1], + /* X */ fa_carry_n[(fa_row_ix * fa_count) + fa_el_ix], + /* Y */ fa_sum_n[(fa_row_ix * fa_count) + fa_el_ix] + ); + } + } + } + } + + // instantiate the cpa + SigSpec cpa_carry; + + for (int cix = 0; cix < z_sz; cix++) + cpa_carry.append(module->addWire(NEW_ID_SUFFIX(stringf("cpa_carry_%d", cix)), 1)); + + for (int cpa_ix = 0; cpa_ix < z_sz; cpa_ix++) { + // The end case where we pass the last two summands + // from prior row directly to product output + // without using a cpa cell. This is always + // 0,1 index of prior fa row + if (cpa_ix <= fa_row_count * 2 - 1) { + int fa_row_ix = cpa_ix / 2; + + module->addBufGate(NEW_ID_SUFFIX(stringf("pp_buf_%d_driven_by_fa_row_%d", cpa_ix, fa_row_ix)), + fa_sum_n[(fa_row_ix * fa_count) + 0], Z[cpa_ix]); + + cpa_ix++; + module->addBufGate(NEW_ID_SUFFIX(stringf("pp_buf_%d_driven_by_fa_row_%d", cpa_ix, fa_row_ix)), + fa_sum_n[(fa_row_ix * fa_count) + 1], Z[cpa_ix]); + } else { + int offset = fa_row_count * 2; + bool base_case = cpa_ix - offset == 0 ? true : false; + std::string cpa_name = stringf("cpa_%d", cpa_ix - offset); + + SigBit ci; + if (base_case) + ci = cori_n_int[enc_count - 1]; + else + ci = cpa_carry[cpa_ix - offset - 1]; + + SigBit op; + BuildHa(cpa_name, fa_sum_n[(fa_row_count - 1) * fa_count + cpa_ix - offset + 2], ci, op, + cpa_carry[cpa_ix - offset]); + module->connect(Z[cpa_ix], op); + } + } + + // + // instantiate the quadrant 1 cell. This is the upper right + // quadrant which can be realized using non-booth encoded logic. + // + std::string q1_name = "icb_booth_q1_"; + + SigBit pp0_o_int; + SigBit pp1_o_int; + SigBit nxj_o_int; + SigBit cor_o_int; + + BuildBoothQ1(q1_name, + negi_n_int[0], // negi + cori_n_int[0], // cori + + X[0], X[1], Y[0], Y[1], + + nxj_o_int, cor_o_int, pp0_o_int, pp1_o_int); + + module->connect(fa_sum_n[(0 * fa_count) + 0], pp0_o_int); + module->connect(fa_sum_n[(0 * fa_count) + 1], pp1_o_int); + module->connect(fa_carry_n[(0 * fa_count) + 1], cor_o_int); + module->connect(nxj[(0 * dec_count) + 2], nxj_o_int); + } +}; + +struct BoothPass : public Pass { + BoothPass() : Pass("booth", "map $mul cells to Booth multipliers") {} + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" booth [selection]\n"); + log("\n"); + log("This pass replaces multiplier cells with an implementation based on the Booth\n"); + log("algorithm. It operates on $mul cells whose width of operands is at least 4x4\n"); + log("and whose width of result is at least 8. The detailed architecture is selected\n"); + log("from two options based on the signedness of the operands to the $mul cell.\n"); + log("\n"); + log("See the references below for the description of the architectures.\n"); + log("\n"); + log("Signed-multiplier architecture:\n"); + log("Y. J. Chang, Y. C. Cheng, S. C. Liao and C. H. Hsiao, \"A Low Power Radix-4 Booth\n"); + log("Multiplier With Pre-Encoded Mechanism,\" in IEEE Access, vol. 8, pp. 114842-114853,\n"); + log("2020, doi: 10.1109/ACCESS.2020.3003684\n"); + log("\n"); + log("Unsigned-multiplier architecture:\n"); + log("G. W. Bewick, \"Fast Multiplication: Algorithms and Implementations,\" PhD Thesis,\n"); + log("Department of Electrical Engineering, Stanford University, 1994\n"); + log("\n"); + } + void execute(vector args, RTLIL::Design *design) override + { + log_header(design, "Executing BOOTH pass (map to Booth multipliers).\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + break; + } + extra_args(args, argidx, design); + + int total = 0; + + for (auto mod : design->selected_modules()) { + if (!mod->has_processes_warn()) { + BoothPassWorker worker(mod); + worker.run(); + total += worker.booth_counter; + } + } + + log("Mapped %d multipliers.\n", total); + } +} MultPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/techmap/flatten.cc b/passes/techmap/flatten.cc index 7e6df5d2c1f..4ddc4aff1fa 100644 --- a/passes/techmap/flatten.cc +++ b/passes/techmap/flatten.cc @@ -312,7 +312,7 @@ struct FlattenPass : public Pass { for (auto cell : module->selected_cells()) { RTLIL::Module *tpl = design->module(cell->type); if (tpl != nullptr) { - if (topo_modules.database.count(tpl) == 0) + if (!topo_modules.has_node(tpl)) worklist.insert(tpl); topo_modules.edge(tpl, module); } diff --git a/passes/techmap/simplemap.cc b/passes/techmap/simplemap.cc index 11692b715ea..7461460fed8 100644 --- a/passes/techmap/simplemap.cc +++ b/passes/techmap/simplemap.cc @@ -36,7 +36,7 @@ void simplemap_not(RTLIL::Module *module, RTLIL::Cell *cell) for (int i = 0; i < GetSize(sig_y); i++) { RTLIL::Cell *gate = module->addCell(NEW_ID, ID($_NOT_)); - gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + gate->attributes[ID::src] = cell->attributes[ID::src]; gate->setPort(ID::A, sig_a[i]); gate->setPort(ID::Y, sig_y[i]); } @@ -73,7 +73,7 @@ void simplemap_bitop(RTLIL::Module *module, RTLIL::Cell *cell) for (int i = 0; i < GetSize(sig_y); i++) { RTLIL::Cell *gate = module->addCell(NEW_ID, gate_type); - gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + gate->attributes[ID::src] = cell->attributes[ID::src]; gate->setPort(ID::A, sig_a[i]); gate->setPort(ID::B, sig_b[i]); gate->setPort(ID::Y, sig_y[i]); @@ -124,7 +124,7 @@ void simplemap_reduce(RTLIL::Module *module, RTLIL::Cell *cell) } RTLIL::Cell *gate = module->addCell(NEW_ID, gate_type); - gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + gate->attributes[ID::src] = cell->attributes[ID::src]; gate->setPort(ID::A, sig_a[i]); gate->setPort(ID::B, sig_a[i+1]); gate->setPort(ID::Y, sig_t[i/2]); @@ -137,7 +137,7 @@ void simplemap_reduce(RTLIL::Module *module, RTLIL::Cell *cell) if (cell->type == ID($reduce_xnor)) { RTLIL::SigSpec sig_t = module->addWire(NEW_ID); RTLIL::Cell *gate = module->addCell(NEW_ID, ID($_NOT_)); - gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + gate->attributes[ID::src] = cell->attributes[ID::src]; gate->setPort(ID::A, sig_a); gate->setPort(ID::Y, sig_t); last_output_cell = gate; @@ -165,7 +165,7 @@ static void logic_reduce(RTLIL::Module *module, RTLIL::SigSpec &sig, RTLIL::Cell } RTLIL::Cell *gate = module->addCell(NEW_ID, ID($_OR_)); - gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + gate->attributes[ID::src] = cell->attributes[ID::src]; gate->setPort(ID::A, sig[i]); gate->setPort(ID::B, sig[i+1]); gate->setPort(ID::Y, sig_t[i/2]); @@ -194,7 +194,7 @@ void simplemap_lognot(RTLIL::Module *module, RTLIL::Cell *cell) } RTLIL::Cell *gate = module->addCell(NEW_ID, ID($_NOT_)); - gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + gate->attributes[ID::src] = cell->attributes[ID::src]; gate->setPort(ID::A, sig_a); gate->setPort(ID::Y, sig_y); } @@ -223,7 +223,7 @@ void simplemap_logbin(RTLIL::Module *module, RTLIL::Cell *cell) log_assert(!gate_type.empty()); RTLIL::Cell *gate = module->addCell(NEW_ID, gate_type); - gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + gate->attributes[ID::src] = cell->attributes[ID::src]; gate->setPort(ID::A, sig_a); gate->setPort(ID::B, sig_b); gate->setPort(ID::Y, sig_y); @@ -239,20 +239,20 @@ void simplemap_eqne(RTLIL::Module *module, RTLIL::Cell *cell) RTLIL::SigSpec xor_out = module->addWire(NEW_ID, max(GetSize(sig_a), GetSize(sig_b))); RTLIL::Cell *xor_cell = module->addXor(NEW_ID, sig_a, sig_b, xor_out, is_signed); - xor_cell->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + xor_cell->attributes[ID::src] = cell->attributes[ID::src]; simplemap_bitop(module, xor_cell); module->remove(xor_cell); RTLIL::SigSpec reduce_out = is_ne ? sig_y : module->addWire(NEW_ID); RTLIL::Cell *reduce_cell = module->addReduceOr(NEW_ID, xor_out, reduce_out); - reduce_cell->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + reduce_cell->attributes[ID::src] = cell->attributes[ID::src]; simplemap_reduce(module, reduce_cell); module->remove(reduce_cell); if (!is_ne) { RTLIL::Cell *not_cell = module->addLogicNot(NEW_ID, reduce_out, sig_y); - not_cell->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); - simplemap_lognot(module, not_cell); + not_cell->attributes[ID::src] = cell->attributes[ID::src]; + simplemap_lognot(module, not_cell); module->remove(not_cell); } } @@ -265,7 +265,7 @@ void simplemap_mux(RTLIL::Module *module, RTLIL::Cell *cell) for (int i = 0; i < GetSize(sig_y); i++) { RTLIL::Cell *gate = module->addCell(NEW_ID, ID($_MUX_)); - gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + gate->attributes[ID::src] = cell->attributes[ID::src]; gate->setPort(ID::A, sig_a[i]); gate->setPort(ID::B, sig_b[i]); gate->setPort(ID::S, cell->getPort(ID::S)); @@ -282,7 +282,7 @@ void simplemap_bwmux(RTLIL::Module *module, RTLIL::Cell *cell) for (int i = 0; i < GetSize(sig_y); i++) { RTLIL::Cell *gate = module->addCell(NEW_ID, ID($_MUX_)); - gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + gate->attributes[ID::src] = cell->attributes[ID::src]; gate->setPort(ID::A, sig_a[i]); gate->setPort(ID::B, sig_b[i]); gate->setPort(ID::S, sig_s[i]); @@ -298,7 +298,7 @@ void simplemap_tribuf(RTLIL::Module *module, RTLIL::Cell *cell) for (int i = 0; i < GetSize(sig_y); i++) { RTLIL::Cell *gate = module->addCell(NEW_ID, ID($_TBUF_)); - gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + gate->attributes[ID::src] = cell->attributes[ID::src]; gate->setPort(ID::A, sig_a[i]); gate->setPort(ID::E, sig_e); gate->setPort(ID::Y, sig_y[i]); @@ -316,7 +316,7 @@ void simplemap_bmux(RTLIL::Module *module, RTLIL::Cell *cell) for (int i = 0; i < GetSize(new_data); i += width) { for (int k = 0; k < width; k++) { RTLIL::Cell *gate = module->addCell(NEW_ID, ID($_MUX_)); - gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + gate->attributes[ID::src] = cell->attributes[ID::src]; gate->setPort(ID::A, data[i*2+k]); gate->setPort(ID::B, data[i*2+width+k]); gate->setPort(ID::S, sel[idx]); @@ -339,7 +339,7 @@ void simplemap_lut(RTLIL::Module *module, RTLIL::Cell *cell) SigSpec new_lut_data = module->addWire(NEW_ID, GetSize(lut_data)/2); for (int i = 0; i < GetSize(lut_data); i += 2) { RTLIL::Cell *gate = module->addCell(NEW_ID, ID($_MUX_)); - gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + gate->attributes[ID::src] = cell->attributes[ID::src]; gate->setPort(ID::A, lut_data[i]); gate->setPort(ID::B, lut_data[i+1]); gate->setPort(ID::S, lut_ctrl[idx]); diff --git a/techlibs/achronix/synth_achronix.cc b/techlibs/achronix/synth_achronix.cc index 9a0a7a3b57b..2b969182eaf 100644 --- a/techlibs/achronix/synth_achronix.cc +++ b/techlibs/achronix/synth_achronix.cc @@ -26,7 +26,7 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN struct SynthAchronixPass : public ScriptPass { - SynthAchronixPass() : ScriptPass("synth_achronix", "synthesis for Acrhonix Speedster22i FPGAs.") { } + SynthAchronixPass() : ScriptPass("synth_achronix", "synthesis for Achronix Speedster22i FPGAs.") { } void help() override { diff --git a/techlibs/common/Makefile.inc b/techlibs/common/Makefile.inc index 47f1ed60456..6b377855e3e 100644 --- a/techlibs/common/Makefile.inc +++ b/techlibs/common/Makefile.inc @@ -34,3 +34,4 @@ $(eval $(call add_share_file,share,techlibs/common/abc9_model.v)) $(eval $(call add_share_file,share,techlibs/common/abc9_map.v)) $(eval $(call add_share_file,share,techlibs/common/abc9_unmap.v)) $(eval $(call add_share_file,share,techlibs/common/cmp2lcu.v)) +$(eval $(call add_share_file,share,techlibs/common/cmp2softlogic.v)) diff --git a/techlibs/common/cmp2softlogic.v b/techlibs/common/cmp2softlogic.v new file mode 100644 index 00000000000..e480b4eee99 --- /dev/null +++ b/techlibs/common/cmp2softlogic.v @@ -0,0 +1,117 @@ +module constgtge(C, A, B, Y); +parameter A_WIDTH = 0; +parameter B_WIDTH = 0; + +(* force_downto *) +input [A_WIDTH-1:0] A; +(* force_downto *) +input [B_WIDTH-1:0] B; +output Y; +input C; + +wire [A_WIDTH:0] ch; +genvar n; +generate + if (B_WIDTH > A_WIDTH) begin + // Fail + end else begin + assign ch[0] = C; + for (n = 0; n < A_WIDTH; n = n + 1) begin + if (n < B_WIDTH) begin + assign ch[n + 1] = B[n] ? (ch[n] && A[n]) : (ch[n] || A[n]); + end else begin + assign ch[n + 1] = ch[n] || A[n]; + end + end + assign Y = ch[A_WIDTH]; + end +endgenerate +endmodule + +module constltle(C, A, B, Y); +parameter A_WIDTH = 0; +parameter B_WIDTH = 0; + +(* force_downto *) +input [A_WIDTH-1:0] A; +(* force_downto *) +input [B_WIDTH-1:0] B; +output Y; +input C; + +wire [A_WIDTH:0] ch; +genvar n; +generate + if (B_WIDTH > A_WIDTH) begin + // Fail + end else begin + assign ch[0] = C; + for (n = 0; n < A_WIDTH; n = n + 1) begin + if (n < B_WIDTH) begin + assign ch[n + 1] = !B[n] ? (ch[n] && !A[n]) : (ch[n] || !A[n]); + end else begin + assign ch[n + 1] = ch[n] && !A[n]; + end + end + assign Y = ch[A_WIDTH]; + end +endgenerate +endmodule + +(* techmap_celltype = "$ge $gt $le $lt" *) +module _map_const_cmp_(A, B, Y); +parameter A_WIDTH = 0; +parameter B_WIDTH = 0; +parameter Y_WIDTH = 0; +parameter A_SIGNED = 0; +parameter B_SIGNED = 0; + +(* force_downto *) +input [A_WIDTH-1:0] A; +(* force_downto *) +input [B_WIDTH-1:0] B; +(* force_downto *) +output [Y_WIDTH-1:0] Y; + +parameter _TECHMAP_CELLTYPE_ = ""; + +parameter _TECHMAP_CONSTMSK_A_ = 0; +parameter _TECHMAP_CONSTVAL_A_ = 0; +parameter _TECHMAP_CONSTMSK_B_ = 0; +parameter _TECHMAP_CONSTVAL_B_ = 0; + +wire [1023:0] _TECHMAP_DO_ = "opt -fast;"; + +wire [A_WIDTH:0] ch; + +genvar n; +generate + if (Y_WIDTH != 1 || A_SIGNED || B_SIGNED) + wire _TECHMAP_FAIL_ = 1; + else if (&_TECHMAP_CONSTMSK_A_) begin + if (A_WIDTH > B_WIDTH) + wire _TECHMAP_FAIL_ = 1; + else if (_TECHMAP_CELLTYPE_ == "$lt" || _TECHMAP_CELLTYPE_ == "$le") + constgtge #(.A_WIDTH(B_WIDTH), .B_WIDTH(A_WIDTH)) + _TECHMAP_REPLACE_(.A(B), .B(A), .Y(Y), + .C(_TECHMAP_CELLTYPE_ == "$lt")); + else + constltle #(.A_WIDTH(B_WIDTH), .B_WIDTH(A_WIDTH)) + _TECHMAP_REPLACE_(.A(B), .B(A), .Y(Y), + .C(_TECHMAP_CELLTYPE_ == "$gt")); + end else if (&_TECHMAP_CONSTMSK_B_) begin + if (B_WIDTH > A_WIDTH) + wire _TECHMAP_FAIL_ = 1; + else if (_TECHMAP_CELLTYPE_ == "$lt" || _TECHMAP_CELLTYPE_ == "$le") + constltle #(.A_WIDTH(A_WIDTH), .B_WIDTH(B_WIDTH)) + _TECHMAP_REPLACE_(.A(A), .B(B), .Y(Y), + .C(_TECHMAP_CELLTYPE_ == "$le")); + else + constgtge #(.A_WIDTH(A_WIDTH), .B_WIDTH(B_WIDTH)) + _TECHMAP_REPLACE_(.A(A), .B(B), .Y(Y), + .C(_TECHMAP_CELLTYPE_ == "$ge")); + end else + wire _TECHMAP_FAIL_ = 1; +endgenerate + +endmodule diff --git a/techlibs/common/prep.cc b/techlibs/common/prep.cc index c354956bcf4..e9176304d48 100644 --- a/techlibs/common/prep.cc +++ b/techlibs/common/prep.cc @@ -189,6 +189,7 @@ struct PrepPass : public ScriptPass run(ifxmode ? "proc -ifx" : "proc"); if (help_mode || flatten) run("flatten", "(if -flatten)"); + run("future"); run(nokeepdc ? "opt_expr" : "opt_expr -keepdc"); run("opt_clean"); run("check"); diff --git a/techlibs/common/simlib.v b/techlibs/common/simlib.v index cdb6e02e762..fd804786fd8 100644 --- a/techlibs/common/simlib.v +++ b/techlibs/common/simlib.v @@ -2671,3 +2671,73 @@ endmodule `endif // -------------------------------------------------------- + +module \$set_tag (A, SET, CLR, Y); + +parameter TAG = ""; +parameter WIDTH = 0; + +input [WIDTH-1:0] A; +input [WIDTH-1:0] SET; +input [WIDTH-1:0] CLR; +output [WIDTH-1:0] Y; + +assign Y = A; + +endmodule + +// -------------------------------------------------------- + +module \$get_tag (A, Y); + +parameter TAG = ""; +parameter WIDTH = 0; + +input [WIDTH-1:0] A; +output [WIDTH-1:0] Y; + +assign Y = A; + +endmodule + +// -------------------------------------------------------- + +module \$overwrite_tag (A, SET, CLR); + +parameter TAG = ""; +parameter WIDTH = 0; + +input [WIDTH-1:0] A; +input [WIDTH-1:0] SET; +input [WIDTH-1:0] CLR; + +endmodule + +// -------------------------------------------------------- + +module \$original_tag (A, Y); + +parameter TAG = ""; +parameter WIDTH = 0; + +input [WIDTH-1:0] A; +output [WIDTH-1:0] Y; + +assign Y = A; + +endmodule + +// -------------------------------------------------------- + +module \$future_ff (A, Y); + +parameter WIDTH = 0; + +input [WIDTH-1:0] A; +output [WIDTH-1:0] Y; + +assign Y = A; + +endmodule + +// -------------------------------------------------------- diff --git a/techlibs/common/synth.cc b/techlibs/common/synth.cc index 63395c36802..006a3c8dd84 100644 --- a/techlibs/common/synth.cc +++ b/techlibs/common/synth.cc @@ -17,17 +17,16 @@ * */ -#include "kernel/register.h" #include "kernel/celltypes.h" -#include "kernel/rtlil.h" #include "kernel/log.h" +#include "kernel/register.h" +#include "kernel/rtlil.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN -struct SynthPass : public ScriptPass -{ - SynthPass() : ScriptPass("synth", "generic synthesis script") { } +struct SynthPass : public ScriptPass { + SynthPass() : ScriptPass("synth", "generic synthesis script") {} void help() override { @@ -60,6 +59,9 @@ struct SynthPass : public ScriptPass log(" -noabc\n"); log(" do not run abc (as if yosys was compiled without ABC support)\n"); log("\n"); + log(" -booth\n"); + log(" run the booth pass to convert $mul to Booth encoded multipliers"); + log("\n"); log(" -noalumacc\n"); log(" do not run 'alumacc' pass. i.e. keep arithmetic operators in\n"); log(" their direct form ($add, $sub, etc.).\n"); @@ -93,7 +95,8 @@ struct SynthPass : public ScriptPass } string top_module, fsm_opts, memory_opts, abc; - bool autotop, flatten, noalumacc, nofsm, noabc, noshare, flowmap; + bool autotop, flatten, noalumacc, nofsm, noabc, noshare, flowmap, booth; + int lut; void clear_flags() override @@ -110,6 +113,7 @@ struct SynthPass : public ScriptPass noabc = false; noshare = false; flowmap = false; + booth = false; abc = "abc"; } @@ -119,24 +123,23 @@ struct SynthPass : public ScriptPass clear_flags(); size_t argidx; - for (argidx = 1; argidx < args.size(); argidx++) - { - if (args[argidx] == "-top" && argidx+1 < args.size()) { + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-top" && argidx + 1 < args.size()) { top_module = args[++argidx]; continue; } - if (args[argidx] == "-encfile" && argidx+1 < args.size()) { + if (args[argidx] == "-encfile" && argidx + 1 < args.size()) { fsm_opts = " -encfile " + args[++argidx]; continue; } - if (args[argidx] == "-run" && argidx+1 < args.size()) { - size_t pos = args[argidx+1].find(':'); + if (args[argidx] == "-run" && argidx + 1 < args.size()) { + size_t pos = args[argidx + 1].find(':'); if (pos == std::string::npos) { run_from = args[++argidx]; run_to = args[argidx]; } else { run_from = args[++argidx].substr(0, pos); - run_to = args[argidx].substr(pos+1); + run_to = args[argidx].substr(pos + 1); } continue; } @@ -164,6 +167,11 @@ struct SynthPass : public ScriptPass noalumacc = true; continue; } + if (args[argidx] == "-booth") { + booth = true; + continue; + } + if (args[argidx] == "-nordff") { memory_opts += " -nordff"; continue; @@ -206,8 +214,7 @@ struct SynthPass : public ScriptPass void script() override { - if (check_label("begin")) - { + if (check_label("begin")) { if (help_mode) { run("hierarchy -check [-top | -auto-top]"); } else { @@ -221,8 +228,7 @@ struct SynthPass : public ScriptPass } } - if (check_label("coarse")) - { + if (check_label("coarse")) { run("proc"); if (help_mode || flatten) run("flatten", " (if -flatten)"); @@ -240,6 +246,8 @@ struct SynthPass : public ScriptPass run("techmap -map +/cmp2lut.v -map +/cmp2lcu.v", " (if -lut)"); else if (lut) run(stringf("techmap -map +/cmp2lut.v -map +/cmp2lcu.v -D LUT_WIDTH=%d", lut)); + if (booth) + run("booth"); if (!noalumacc) run("alumacc", " (unless -noalumacc)"); if (!noshare) @@ -249,50 +257,40 @@ struct SynthPass : public ScriptPass run("opt_clean"); } - if (check_label("fine")) - { + if (check_label("fine")) { run("opt -fast -full"); run("memory_map"); run("opt -full"); run("techmap"); - if (help_mode) - { + if (help_mode) { run("techmap -map +/gate2lut.v", "(if -noabc and -lut)"); run("clean; opt_lut", " (if -noabc and -lut)"); run("flowmap -maxlut K", " (if -flowmap and -lut)"); - } - else if (noabc && lut) - { + } else if (noabc && lut) { run(stringf("techmap -map +/gate2lut.v -D LUT_WIDTH=%d", lut)); run("clean; opt_lut"); - } - else if (flowmap) - { + } else if (flowmap) { run(stringf("flowmap -maxlut %d", lut)); } run("opt -fast"); if (!noabc && !flowmap) { - #ifdef YOSYS_ENABLE_ABC - if (help_mode) - { +#ifdef YOSYS_ENABLE_ABC + if (help_mode) { run(abc + " -fast", " (unless -noabc, unless -lut)"); run(abc + " -fast -lut k", "(unless -noabc, if -lut)"); - } - else - { + } else { if (lut) run(stringf("%s -fast -lut %d", abc.c_str(), lut)); else run(abc + " -fast"); } run("opt -fast", " (unless -noabc)"); - #endif +#endif } } - if (check_label("check")) - { + if (check_label("check")) { run("hierarchy -check"); run("stat"); run("check"); diff --git a/techlibs/ecp5/synth_ecp5.cc b/techlibs/ecp5/synth_ecp5.cc index fdc36e55267..f6215987f7f 100644 --- a/techlibs/ecp5/synth_ecp5.cc +++ b/techlibs/ecp5/synth_ecp5.cc @@ -93,8 +93,8 @@ struct SynthEcp5Pass : public ScriptPass log(" -abc2\n"); log(" run two passes of 'abc' for slightly improved logic density\n"); log("\n"); - log(" -abc9\n"); - log(" use new ABC9 flow (EXPERIMENTAL)\n"); + log(" -noabc9\n"); + log(" disable use of new ABC9 flow\n"); log("\n"); log(" -vpr\n"); log(" generate an output netlist (and BLIF file) suitable for VPR\n"); @@ -137,7 +137,7 @@ struct SynthEcp5Pass : public ScriptPass retime = false; abc2 = false; vpr = false; - abc9 = false; + abc9 = true; iopad = false; nodsp = false; no_rw_check = false; @@ -224,7 +224,11 @@ struct SynthEcp5Pass : public ScriptPass continue; } if (args[argidx] == "-abc9") { - abc9 = true; + // removed, ABC9 is on by default. + continue; + } + if (args[argidx] == "-noabc9") { + abc9 = false; continue; } if (args[argidx] == "-iopad") { diff --git a/techlibs/gowin/cells_sim.v b/techlibs/gowin/cells_sim.v index 86bd677e229..b268690803f 100644 --- a/techlibs/gowin/cells_sim.v +++ b/techlibs/gowin/cells_sim.v @@ -197,7 +197,7 @@ module DFFE (output reg Q, input D, CLK, CE); end endmodule // DFFE (positive clock edge; clock enable) -(* abc9_box, lib_whitebox *) +(* abc9_flop, lib_whitebox *) module DFFS (output reg Q, input D, CLK, SET); parameter [0:0] INIT = 1'b1; initial Q = INIT; @@ -216,7 +216,7 @@ module DFFS (output reg Q, input D, CLK, SET); end endmodule // DFFS (positive clock edge; synchronous set) -(* abc9_box, lib_whitebox *) +(* abc9_flop, lib_whitebox *) module DFFSE (output reg Q, input D, CLK, CE, SET); parameter [0:0] INIT = 1'b1; initial Q = INIT; @@ -282,7 +282,7 @@ module DFFP (output reg Q, input D, CLK, PRESET); specify (posedge CLK => (Q : D)) = (480, 660); - (posedge PRESET => (Q : 1'b1)) = (1800, 2679); + (PRESET => Q) = (1800, 2679); $setup(D, posedge CLK, 576); endspecify @@ -301,7 +301,7 @@ module DFFPE (output reg Q, input D, CLK, CE, PRESET); specify if (CE) (posedge CLK => (Q : D)) = (480, 660); - (posedge PRESET => (Q : 1'b1)) = (1800, 2679); + (PRESET => Q) = (1800, 2679); $setup(D, posedge CLK &&& CE, 576); $setup(CE, posedge CLK, 63); endspecify @@ -321,7 +321,7 @@ module DFFC (output reg Q, input D, CLK, CLEAR); specify (posedge CLK => (Q : D)) = (480, 660); - (posedge CLEAR => (Q : 1'b0)) = (1800, 2679); + (CLEAR => Q) = (1800, 2679); $setup(D, posedge CLK, 576); endspecify @@ -340,7 +340,7 @@ module DFFCE (output reg Q, input D, CLK, CE, CLEAR); specify if (CE) (posedge CLK => (Q : D)) = (480, 660); - (posedge CLEAR => (Q : 1'b0)) = (1800, 2679); + (CLEAR => Q) = (1800, 2679); $setup(D, posedge CLK &&& CE, 576); $setup(CE, posedge CLK, 63); endspecify @@ -384,7 +384,7 @@ module DFFNE (output reg Q, input D, CLK, CE); end endmodule // DFFNE (negative clock edge; clock enable) -(* abc9_box, lib_whitebox *) +(* abc9_flop, lib_whitebox *) module DFFNS (output reg Q, input D, CLK, SET); parameter [0:0] INIT = 1'b1; initial Q = INIT; @@ -403,7 +403,7 @@ module DFFNS (output reg Q, input D, CLK, SET); end endmodule // DFFNS (negative clock edge; synchronous set) -(* abc9_box, lib_whitebox *) +(* abc9_flop, lib_whitebox *) module DFFNSE (output reg Q, input D, CLK, CE, SET); parameter [0:0] INIT = 1'b1; initial Q = INIT; @@ -469,7 +469,7 @@ module DFFNP (output reg Q, input D, CLK, PRESET); specify (negedge CLK => (Q : D)) = (480, 660); - (posedge PRESET => (Q : 1'b1)) = (1800, 2679); + (PRESET => Q) = (1800, 2679); $setup(D, negedge CLK, 576); endspecify @@ -488,7 +488,7 @@ module DFFNPE (output reg Q, input D, CLK, CE, PRESET); specify if (CE) (negedge CLK => (Q : D)) = (480, 660); - (posedge PRESET => (Q : 1'b1)) = (1800, 2679); + (PRESET => Q) = (1800, 2679); $setup(D, negedge CLK &&& CE, 576); $setup(CE, negedge CLK, 63); endspecify @@ -508,7 +508,7 @@ module DFFNC (output reg Q, input D, CLK, CLEAR); specify (negedge CLK => (Q : D)) = (480, 660); - (posedge CLEAR => (Q : 1'b0)) = (1800, 2679); + (CLEAR => Q) = (1800, 2679); $setup(D, negedge CLK, 576); endspecify @@ -527,7 +527,7 @@ module DFFNCE (output reg Q, input D, CLK, CE, CLEAR); specify if (CE) (negedge CLK => (Q : D)) = (480, 660); - (posedge CLEAR => (Q : 1'b0)) = (1800, 2679); + (CLEAR => Q) = (1800, 2679); $setup(D, negedge CLK &&& CE, 576); $setup(CE, negedge CLK, 63); endspecify @@ -957,7 +957,7 @@ end endmodule - +(* abc9_flop, lib_whitebox *) module RAM16S1 (DO, DI, AD, WRE, CLK); parameter INIT_0 = 16'h0000; @@ -992,7 +992,7 @@ end endmodule - +(* abc9_flop, lib_whitebox *) module RAM16S2 (DO, DI, AD, WRE, CLK); parameter INIT_0 = 16'h0000; @@ -1031,7 +1031,7 @@ end endmodule - +(* abc9_flop, lib_whitebox *) module RAM16S4 (DO, DI, AD, WRE, CLK); parameter INIT_0 = 16'h0000; diff --git a/techlibs/gowin/synth_gowin.cc b/techlibs/gowin/synth_gowin.cc index 3b9d7424a7b..302ba76e5af 100644 --- a/techlibs/gowin/synth_gowin.cc +++ b/techlibs/gowin/synth_gowin.cc @@ -78,8 +78,8 @@ struct SynthGowinPass : public ScriptPass log(" -noalu\n"); log(" do not use ALU cells\n"); log("\n"); - log(" -abc9\n"); - log(" use new ABC9 flow (EXPERIMENTAL)\n"); + log(" -noabc9\n"); + log(" disable use of new ABC9 flow\n"); log("\n"); log(" -no-rw-check\n"); log(" marks all recognized read ports as \"return don't-care value on\n"); @@ -106,7 +106,7 @@ struct SynthGowinPass : public ScriptPass nodffe = false; nolutram = false; nowidelut = false; - abc9 = false; + abc9 = true; noiopads = false; noalu = false; no_rw_check = false; @@ -170,7 +170,11 @@ struct SynthGowinPass : public ScriptPass continue; } if (args[argidx] == "-abc9") { - abc9 = true; + // removed, ABC9 is on by default. + continue; + } + if (args[argidx] == "-noabc9") { + abc9 = false; continue; } if (args[argidx] == "-noiopads") { diff --git a/techlibs/ice40/synth_ice40.cc b/techlibs/ice40/synth_ice40.cc index 2ae859efe32..a9982649b35 100644 --- a/techlibs/ice40/synth_ice40.cc +++ b/techlibs/ice40/synth_ice40.cc @@ -106,8 +106,8 @@ struct SynthIce40Pass : public ScriptPass log(" generate an output netlist (and BLIF file) suitable for VPR\n"); log(" (this feature is experimental and incomplete)\n"); log("\n"); - log(" -abc9\n"); - log(" use new ABC9 flow (EXPERIMENTAL)\n"); + log(" -noabc9\n"); + log(" disable use of new ABC9 flow\n"); log("\n"); log(" -flowmap\n"); log(" use FlowMap LUT techmapping instead of abc (EXPERIMENTAL)\n"); @@ -144,7 +144,7 @@ struct SynthIce40Pass : public ScriptPass noabc = false; abc2 = false; vpr = false; - abc9 = false; + abc9 = true; flowmap = false; device_opt = "hx"; no_rw_check = false; @@ -235,7 +235,11 @@ struct SynthIce40Pass : public ScriptPass continue; } if (args[argidx] == "-abc9") { - abc9 = true; + // removed, ABC9 is on by default. + continue; + } + if (args[argidx] == "-noabc9") { + abc9 = false; continue; } if (args[argidx] == "-dff") { diff --git a/techlibs/lattice/Makefile.inc b/techlibs/lattice/Makefile.inc index 4a908fa3ead..fd9ec2ed5e0 100644 --- a/techlibs/lattice/Makefile.inc +++ b/techlibs/lattice/Makefile.inc @@ -15,6 +15,7 @@ $(eval $(call add_share_file,share/lattice,techlibs/lattice/cells_sim_xo3d.v)) $(eval $(call add_share_file,share/lattice,techlibs/lattice/cells_bb_ecp5.v)) $(eval $(call add_share_file,share/lattice,techlibs/lattice/cells_bb_xo2.v)) $(eval $(call add_share_file,share/lattice,techlibs/lattice/cells_bb_xo3.v)) +$(eval $(call add_share_file,share/lattice,techlibs/lattice/cells_bb_xo3d.v)) $(eval $(call add_share_file,share/lattice,techlibs/lattice/lutrams_map.v)) $(eval $(call add_share_file,share/lattice,techlibs/lattice/lutrams.txt)) $(eval $(call add_share_file,share/lattice,techlibs/lattice/brams_map_16kd.v)) diff --git a/techlibs/lattice/synth_lattice.cc b/techlibs/lattice/synth_lattice.cc index e2987d0259e..f54f52c3df1 100644 --- a/techlibs/lattice/synth_lattice.cc +++ b/techlibs/lattice/synth_lattice.cc @@ -127,6 +127,10 @@ struct SynthLatticePass : public ScriptPass log(" read/write collision\" (same result as setting the no_rw_check\n"); log(" attribute on all memories).\n"); log("\n"); + log(" -cmp2softlogic\n"); + log(" implement constant comparisons in soft logic, do not involve\n"); + log(" hard carry chains\n"); + log("\n"); log("\n"); log("The following commands are executed by this synthesis command:\n"); help_script(); @@ -135,6 +139,7 @@ struct SynthLatticePass : public ScriptPass string top_opt, edif_file, json_file, family; bool noccu2, nodffe, nobram, nolutram, nowidelut, asyncprld, flatten, dff, retime, abc2, abc9, iopad, nodsp, no_rw_check, have_dsp; + bool cmp2softlogic; string postfix, arith_map, brams_map, dsp_map; void clear_flags() override @@ -162,6 +167,7 @@ struct SynthLatticePass : public ScriptPass brams_map = ""; dsp_map = ""; have_dsp = false; + cmp2softlogic = false; } void execute(std::vector args, RTLIL::Design *design) override @@ -263,6 +269,10 @@ struct SynthLatticePass : public ScriptPass no_rw_check = true; continue; } + if (args[argidx] == "-cmp2softlogic") { + cmp2softlogic = true; + continue; + } break; } extra_args(args, argidx, design); @@ -343,6 +353,8 @@ struct SynthLatticePass : public ScriptPass run("peepopt"); run("opt_clean"); run("share"); + if (cmp2softlogic) + run("techmap -map +/cmp2softlogic.v"); run("techmap -map +/cmp2lut.v -D LUT_WIDTH=4"); run("opt_expr"); run("opt_clean"); @@ -373,7 +385,7 @@ struct SynthLatticePass : public ScriptPass { run("opt -fast -mux_undef -undriven -fine"); run("memory_map"); - run("opt -undriven -fine"); + run("opt -undriven -fine -mux_undef"); } if (check_label("map_gates")) @@ -409,6 +421,7 @@ struct SynthLatticePass : public ScriptPass dfflegalize_args += " -cell $_DLATCH_?_ x"; } run("dfflegalize" + dfflegalize_args, "($_ALDFF_*_ only if -asyncprld, $_DLATCH_* only if not -asyncprld, $_*DFFE_* only if not -nodffe)"); + run("opt_merge"); if ((abc9 && dff) || help_mode) run("zinit -all w:* t:$_DFF_?_ t:$_DFFE_??_ t:$_SDFF*", "(only if -abc9 and -dff)"); run("techmap -D NO_LUT -map +/lattice/cells_map.v"); diff --git a/tests/arch/ecp5/add_sub.ys b/tests/arch/ecp5/add_sub.ys index d85ce792ee6..c3ce8c56dae 100644 --- a/tests/arch/ecp5/add_sub.ys +++ b/tests/arch/ecp5/add_sub.ys @@ -4,6 +4,9 @@ proc equiv_opt -assert -map +/ecp5/cells_sim.v synth_ecp5 # equivalency check design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) cd top # Constrain all select calls below inside the top module -select -assert-count 10 t:LUT4 -select -assert-none t:LUT4 %% t:* %D +select -assert-min 25 t:LUT4 +select -assert-max 26 t:LUT4 +select -assert-count 10 t:PFUMX +select -assert-count 6 t:L6MUX21 +select -assert-none t:LUT4 t:PFUMX t:L6MUX21 %% t:* %D diff --git a/tests/arch/ecp5/counter.ys b/tests/arch/ecp5/counter.ys index e46001ffede..e3f713228c3 100644 --- a/tests/arch/ecp5/counter.ys +++ b/tests/arch/ecp5/counter.ys @@ -5,6 +5,7 @@ flatten equiv_opt -assert -multiclock -map +/ecp5/cells_sim.v synth_ecp5 # equivalency check design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) cd top # Constrain all select calls below inside the top module +select -assert-count 1 t:LUT4 select -assert-count 4 t:CCU2C select -assert-count 8 t:TRELLIS_FF -select -assert-none t:CCU2C t:TRELLIS_FF %% t:* %D +select -assert-none t:LUT4 t:CCU2C t:TRELLIS_FF %% t:* %D diff --git a/tests/arch/gowin/counter.ys b/tests/arch/gowin/counter.ys index bdbc7ee2432..ec6ca0f5ee2 100644 --- a/tests/arch/gowin/counter.ys +++ b/tests/arch/gowin/counter.ys @@ -6,10 +6,11 @@ equiv_opt -assert -multiclock -map +/gowin/cells_sim.v synth_gowin # equivalency design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) cd top # Constrain all select calls below inside the top module +select -assert-count 1 t:LUT1 select -assert-count 8 t:DFFC select -assert-count 8 t:ALU select -assert-count 1 t:GND select -assert-count 1 t:VCC select -assert-count 2 t:IBUF select -assert-count 8 t:OBUF -select -assert-none t:DFFC t:ALU t:GND t:VCC t:IBUF t:OBUF %% t:* %D +select -assert-none t:LUT1 t:DFFC t:ALU t:GND t:VCC t:IBUF t:OBUF %% t:* %D diff --git a/tests/arch/gowin/init.ys b/tests/arch/gowin/init.ys index fba7c2fa583..d522801c63d 100644 --- a/tests/arch/gowin/init.ys +++ b/tests/arch/gowin/init.ys @@ -1,5 +1,5 @@ read_verilog init.v -read_verilog -lib +/gowin/cells_sim.v +read_verilog -lib -specify +/gowin/cells_sim.v design -save read proc diff --git a/tests/arch/gowin/mux.ys b/tests/arch/gowin/mux.ys index 33b092284fa..d5978f4ea5c 100644 --- a/tests/arch/gowin/mux.ys +++ b/tests/arch/gowin/mux.ys @@ -32,10 +32,17 @@ proc equiv_opt -assert -map +/gowin/cells_sim.v synth_gowin # equivalency check design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) cd mux8 # Constrain all select calls below inside the top module +select -assert-count 1 t:LUT1 +select -assert-count 10 t:LUT3 +select -assert-count 1 t:LUT4 +select -assert-count 5 t:MUX2_LUT5 +select -assert-count 2 t:MUX2_LUT6 +select -assert-count 1 t:MUX2_LUT7 select -assert-count 11 t:IBUF select -assert-count 1 t:OBUF +select -assert-count 1 t:GND -select -assert-none t:LUT* t:MUX2_LUT6 t:MUX2_LUT5 t:IBUF t:OBUF %% t:* %D +select -assert-none t:LUT* t:MUX2_LUT7 t:MUX2_LUT6 t:MUX2_LUT5 t:IBUF t:OBUF t:GND %% t:* %D design -load read hierarchy -top mux16 diff --git a/tests/arch/ice40/add_sub.ys b/tests/arch/ice40/add_sub.ys index 578ec080380..74b83b8ee91 100644 --- a/tests/arch/ice40/add_sub.ys +++ b/tests/arch/ice40/add_sub.ys @@ -3,7 +3,7 @@ hierarchy -top top equiv_opt -assert -map +/ice40/cells_sim.v synth_ice40 # equivalency check design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) cd top # Constrain all select calls below inside the top module -select -assert-count 11 t:SB_LUT4 +select -assert-count 10 t:SB_LUT4 select -assert-count 6 t:SB_CARRY select -assert-none t:SB_LUT4 t:SB_CARRY %% t:* %D diff --git a/tests/arch/ice40/mux.ys b/tests/arch/ice40/mux.ys index 2b661fd6b1a..977d6975063 100644 --- a/tests/arch/ice40/mux.ys +++ b/tests/arch/ice40/mux.ys @@ -15,7 +15,7 @@ proc equiv_opt -assert -map +/ice40/cells_sim.v synth_ice40 # equivalency check design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) cd mux4 # Constrain all select calls below inside the top module -select -assert-count 2 t:SB_LUT4 +select -assert-count 3 t:SB_LUT4 select -assert-none t:SB_LUT4 %% t:* %D @@ -25,7 +25,7 @@ proc equiv_opt -assert -map +/ice40/cells_sim.v synth_ice40 # equivalency check design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) cd mux8 # Constrain all select calls below inside the top module -select -assert-count 5 t:SB_LUT4 +select -assert-count 6 t:SB_LUT4 select -assert-none t:SB_LUT4 %% t:* %D @@ -35,7 +35,7 @@ proc equiv_opt -assert -map +/ice40/cells_sim.v synth_ice40 # equivalency check design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) cd mux16 # Constrain all select calls below inside the top module -select -assert-min 11 t:SB_LUT4 -select -assert-max 12 t:SB_LUT4 +select -assert-min 13 t:SB_LUT4 +select -assert-max 14 t:SB_LUT4 select -assert-none t:SB_LUT4 %% t:* %D diff --git a/tests/arch/quicklogic/dffs.ys b/tests/arch/quicklogic/dffs.ys index 2e0a34540ec..e1fbef635d8 100644 --- a/tests/arch/quicklogic/dffs.ys +++ b/tests/arch/quicklogic/dffs.ys @@ -7,14 +7,27 @@ hierarchy -top my_dff proc equiv_opt -async2sync -assert -map +/quicklogic/pp3_cells_sim.v -map +/quicklogic/cells_sim.v synth_quicklogic # equivalency check design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) -cd dff # Constrain all select calls below inside the top module -select -assert-none t:* +cd my_dff # Constrain all select calls below inside the top module +select -assert-count 1 t:ckpad +select -assert-count 1 t:dffepc +select -assert-count 1 t:inpad +select -assert-count 1 t:logic_0 +select -assert-count 1 t:logic_1 +select -assert-count 1 t:outpad + +select -assert-none t:ckpad t:dffepc t:inpad t:logic_0 t:logic_1 t:outpad %% t:* %D design -load read hierarchy -top my_dffe proc equiv_opt -async2sync -assert -map +/quicklogic/pp3_cells_sim.v -map +/quicklogic/cells_sim.v synth_quicklogic # equivalency check design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) -cd dffe # Constrain all select calls below inside the top module +cd my_dffe # Constrain all select calls below inside the top module + +select -assert-count 1 t:ckpad +select -assert-count 1 t:dffepc +select -assert-count 2 t:inpad +select -assert-count 1 t:logic_0 +select -assert-count 1 t:outpad -select -assert-none t:* \ No newline at end of file +select -assert-none t:ckpad t:dffepc t:inpad t:logic_0 t:outpad %% t:* %D diff --git a/tests/memlib/generate.py b/tests/memlib/generate.py index f40210501db..46eff6b43e3 100644 --- a/tests/memlib/generate.py +++ b/tests/memlib/generate.py @@ -1513,6 +1513,28 @@ def __init__(self, name, src, libs, defs, cells): ["block_sp_full"], defs, {"RAM_BLOCK_SP": 1, "$*": add_logic} )) + +ROM_CASE = """ +module rom(input clk, input [2:0] addr, {attr}output reg [7:0] data); + +always @(posedge clk) begin + case (addr) + 3'b000: data <= 8'h12; + 3'b001: data <= 8'hAB; + 3'b010: data <= 8'h42; + 3'b011: data <= 8'h23; + 3'b100: data <= 8'h66; + 3'b101: data <= 8'hC0; + 3'b110: data <= 8'h3F; + 3'b111: data <= 8'h95; + endcase +end + +endmodule +""" + +TESTS.append(Test("rom_case", ROM_CASE.format(attr=""), ["block_sdp"], [], {"RAM_BLOCK_SDP" : 0})) +TESTS.append(Test("rom_case_block", ROM_CASE.format(attr="(* rom_style = \"block\" *) "), ["block_sdp"], [], {"RAM_BLOCK_SDP" : 1})) with open("run-test.mk", "w") as mf: mf.write("ifneq ($(strip $(SEED)),)\n") diff --git a/tests/simple/partsel.v b/tests/simple/partsel.v index 5e9730d6bd1..722ed7b1bd9 100644 --- a/tests/simple/partsel.v +++ b/tests/simple/partsel.v @@ -110,3 +110,42 @@ module partsel_test007 ( dout[n+1] = din[n]; end endmodule + + +module partsel_test008 ( + input [127:0] din, + input [3:0] idx, + input [4:0] uoffset, + input signed [4:0] soffset, + output [ 7:0] dout0, + output [ 7:0] dout1, + output [ 7:0] dout2, + output [ 7:0] dout3, + output [ 3:0] dout4, + output [ 3:0] dout5, + output [ 3:0] dout6, + output [ 3:0] dout7, + output [ 3:0] dout8, + output [11:0] dout9, + output [11:0] dout10, + output [11:0] dout11 +); + +// common: block-select with offsets +assign dout0 = din[idx*8 +uoffset +:8]; +assign dout1 = din[idx*8 -uoffset +:8]; +assign dout2 = din[idx*8 +soffset +:8]; +assign dout3 = din[idx*8 -soffset +:8]; + +// only partial block used +assign dout4 = din[idx*8 +uoffset +:4]; +assign dout5 = din[idx*8 -uoffset +:4]; +assign dout6 = din[idx*8 +soffset +:4]; +assign dout7 = din[idx*8 -soffset +:4]; + +// uncommon: more than one block used +assign dout8 = din[idx*8 +uoffset +:12]; +assign dout9 = din[idx*8 -uoffset +:12]; +assign dout10 = din[idx*8 +soffset +:12]; +assign dout11 = din[idx*8 -soffset +:12]; +endmodule diff --git a/tests/sva/sva_value_change_changed.sv b/tests/sva/sva_value_change_changed.sv index 8f3a05a2f2e..3ce2078500c 100644 --- a/tests/sva/sva_value_change_changed.sv +++ b/tests/sva/sva_value_change_changed.sv @@ -8,9 +8,11 @@ module top ( $changed(b) ); + wire x = 'x; + `ifndef FAIL assume property ( - b !== 'x ##1 $changed(b) + b !== x ##1 $changed(b) ); `endif diff --git a/tests/techmap/booth.ys b/tests/techmap/booth.ys new file mode 100644 index 00000000000..55dfdb8e9cd --- /dev/null +++ b/tests/techmap/booth.ys @@ -0,0 +1,15 @@ +read_verilog < data <= X"12"; + when "001" => data <= X"AB"; + when "010" => data <= X"42"; + when "011" => data <= X"23"; + when "100" => data <= X"66"; + when "101" => data <= X"C0"; + when "110" => data <= X"3F"; + when others => data <= X"95"; + end case; + end if; + end process p_rom; + +end architecture rtl; +EOF +hierarchy -top rom_example +proc +opt +opt -full +memory -nomap +dump +memory_libmap -lib ../memlib/memlib_block_sdp.txt +memory_map +stat +select -assert-count 1 t:RAM_BLOCK_SDP \ No newline at end of file diff --git a/tests/verilog/asgn_expr.sv b/tests/verilog/asgn_expr.sv new file mode 100644 index 00000000000..25f9caa33d1 --- /dev/null +++ b/tests/verilog/asgn_expr.sv @@ -0,0 +1,73 @@ +module top; + integer x, y, z; + task check; + input integer a, b, c; + assert (x == a); + assert (y == b); + assert (z == c); + endtask + always_comb begin + x = 0; y = 0; z = 0; + check(0, 0, 0); + + // post-increment/decrement statements + x++; + check(1, 0, 0); + (* bar *) y (* foo *) ++; + check(1, 1, 0); + z--; + check(1, 1, -1); + (* bar *) z (* foo *) --; + check(1, 1, -2); + + // pre-increment/decrement statements are equivalent + ++z; + check(1, 1, -1); + (* bar *) ++ (* foo *) z; + check(1, 1, 0); + --x; + check(0, 1, 0); + (* bar *) -- (* foo *) y; + check(0, 0, 0); + + // procedural pre-increment/decrement expressions + z = ++x; + check(1, 0, 1); + z = ++ (* foo *) x; + check(2, 0, 2); + y = --x; + check(1, 1, 2); + y = -- (* foo *) x; + + // procedural post-increment/decrement expressions + // TODO: support attributes on post-increment/decrement + check(0, 0, 2); + y = x++; + check(1, 0, 2); + y = x--; + check(0, 1, 2); + + // procedural assignment expressions + x = (y = (z = 99) + 1) + 1; + check(101, 100, 99); + x = (y *= 2); + check(200, 200, 99); + x = (z >>= 2) * 4; + check(96, 200, 24); + y = (z >>= 1'sb1) * 2; // shift is implicitly cast to unsigned + check(96, 24, 12); + + // check width of post-increment expressions + z = (y = 0); + begin + byte w; + w = 0; + x = {1'b1, ++w}; + check(257, 0, 0); + assert (w == 1); + x = {2'b10, w++}; + check(513, 0, 0); + assert (w == 2); + end + end +endmodule diff --git a/tests/verilog/asgn_expr.ys b/tests/verilog/asgn_expr.ys new file mode 100644 index 00000000000..18180c7854f --- /dev/null +++ b/tests/verilog/asgn_expr.ys @@ -0,0 +1,3 @@ +read_verilog -sv asgn_expr.sv +proc +sat -verify -prove-asserts -show-all diff --git a/tests/verilog/asgn_expr_not_proc_1.ys b/tests/verilog/asgn_expr_not_proc_1.ys new file mode 100644 index 00000000000..72ba0bd89dc --- /dev/null +++ b/tests/verilog/asgn_expr_not_proc_1.ys @@ -0,0 +1,7 @@ +logger -expect error "Assignments within expressions are only permitted within procedures." 1 +read_verilog -sv <