diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index a0b18185ad4..c2588f43b4b 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -121,7 +121,6 @@ jobs: run: | git clone https://github.com/steveicarus/iverilog.git cd iverilog - git checkout 192b6aec96fde982e6ddcb28b346d5893aa8e874 echo "IVERILOG_GIT=$(git rev-parse HEAD)" >> $GITHUB_ENV - name: Cache iverilog diff --git a/CHANGELOG b/CHANGELOG index bf7d3527294..ed91d5e7c0a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,9 @@ List of major changes and improvements between releases ======================================================= +Yosys 0.38 .. Yosys 0.39-dev +-------------------------- + Yosys 0.37 .. Yosys 0.38 -------------------------- * New commands and options diff --git a/Makefile b/Makefile index b0142b98e9d..da596a8f09c 100644 --- a/Makefile +++ b/Makefile @@ -92,14 +92,14 @@ VPATH := $(YOSYS_SRC) CXXSTD ?= c++11 CXXFLAGS := $(CXXFLAGS) -Wall -Wextra -ggdb -I. -I"$(YOSYS_SRC)" -MD -MP -D_YOSYS_ -fPIC -I$(PREFIX)/include -LDLIBS := $(LDLIBS) -lstdc++ -lm -PLUGIN_LDFLAGS := -PLUGIN_LDLIBS := -EXE_LDFLAGS := +LIBS := $(LIBS) -lstdc++ -lm +PLUGIN_LINKFLAGS := +PLUGIN_LIBS := +EXE_LINKFLAGS := ifeq ($(OS), MINGW) -EXE_LDFLAGS := -Wl,--export-all-symbols -Wl,--out-implib,libyosys_exe.a -PLUGIN_LDFLAGS += -L"$(LIBDIR)" -PLUGIN_LDLIBS := -lyosys_exe +EXE_LINKFLAGS := -Wl,--export-all-symbols -Wl,--out-implib,libyosys_exe.a +PLUGIN_LINKFLAGS += -L"$(LIBDIR)" +PLUGIN_LIBS := -lyosys_exe endif PKG_CONFIG ?= pkg-config @@ -109,7 +109,7 @@ STRIP ?= strip AWK ?= awk ifeq ($(OS), Darwin) -PLUGIN_LDFLAGS += -undefined dynamic_lookup +PLUGIN_LINKFLAGS += -undefined dynamic_lookup # homebrew search paths ifneq ($(shell :; command -v brew),) @@ -117,10 +117,10 @@ BREW_PREFIX := $(shell brew --prefix)/opt $(info $$BREW_PREFIX is [${BREW_PREFIX}]) ifeq ($(ENABLE_PYOSYS),1) CXXFLAGS += -I$(BREW_PREFIX)/boost/include/boost -LDFLAGS += -L$(BREW_PREFIX)/boost/lib +LINKFLAGS += -L$(BREW_PREFIX)/boost/lib endif CXXFLAGS += -I$(BREW_PREFIX)/readline/include -LDFLAGS += -L$(BREW_PREFIX)/readline/lib +LINKFLAGS += -L$(BREW_PREFIX)/readline/lib PKG_CONFIG_PATH := $(BREW_PREFIX)/libffi/lib/pkgconfig:$(PKG_CONFIG_PATH) PKG_CONFIG_PATH := $(BREW_PREFIX)/tcl-tk/lib/pkgconfig:$(PKG_CONFIG_PATH) export PATH := $(BREW_PREFIX)/bison/bin:$(BREW_PREFIX)/gettext/bin:$(BREW_PREFIX)/flex/bin:$(PATH) @@ -129,19 +129,19 @@ export PATH := $(BREW_PREFIX)/bison/bin:$(BREW_PREFIX)/gettext/bin:$(BREW_PREFIX else ifneq ($(shell :; command -v port),) PORT_PREFIX := $(patsubst %/bin/port,%,$(shell :; command -v port)) CXXFLAGS += -I$(PORT_PREFIX)/include -LDFLAGS += -L$(PORT_PREFIX)/lib +LINKFLAGS += -L$(PORT_PREFIX)/lib PKG_CONFIG_PATH := $(PORT_PREFIX)/lib/pkgconfig:$(PKG_CONFIG_PATH) export PATH := $(PORT_PREFIX)/bin:$(PATH) endif else -LDFLAGS += -rdynamic +LINKFLAGS += -rdynamic ifneq ($(OS), OpenBSD) -LDLIBS += -lrt +LIBS += -lrt endif endif -YOSYS_VER := 0.38 +YOSYS_VER := 0.38+113 # 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 a5c7f69.. | wc -l`/;" Makefile + sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline 543faed.. | wc -l`/;" Makefile # set 'ABCREV = default' to use abc/ as it is # @@ -215,41 +215,38 @@ ABC_ARCHFLAGS += "-DABC_NO_RLIMIT" endif ifeq ($(CONFIG),clang) -CXX = clang -LD = clang++ +CXX = clang++ CXXFLAGS += -std=$(CXXSTD) -Os ABCMKARGS += ARCHFLAGS="-DABC_USE_STDINT_H -Wno-c++11-narrowing $(ABC_ARCHFLAGS)" ifneq ($(SANITIZER),) $(info [Clang Sanitizer] $(SANITIZER)) CXXFLAGS += -g -O1 -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=$(SANITIZER) -LDFLAGS += -g -fsanitize=$(SANITIZER) +LINKFLAGS += -g -fsanitize=$(SANITIZER) ifneq ($(findstring address,$(SANITIZER)),) ENABLE_COVER := 0 endif ifneq ($(findstring memory,$(SANITIZER)),) CXXFLAGS += -fPIE -fsanitize-memory-track-origins -LDFLAGS += -fPIE -fsanitize-memory-track-origins +LINKFLAGS += -fPIE -fsanitize-memory-track-origins endif ifneq ($(findstring cfi,$(SANITIZER)),) CXXFLAGS += -flto -LDFLAGS += -flto +LINKFLAGS += -flto endif endif else ifeq ($(CONFIG),gcc) -CXX = gcc -LD = gcc +CXX = g++ CXXFLAGS += -std=$(CXXSTD) -Os ABCMKARGS += ARCHFLAGS="-DABC_USE_STDINT_H $(ABC_ARCHFLAGS)" else ifeq ($(CONFIG),gcc-static) -LD = $(CXX) -LDFLAGS := $(filter-out -rdynamic,$(LDFLAGS)) -static -LDLIBS := $(filter-out -lrt,$(LDLIBS)) +LINKFLAGS := $(filter-out -rdynamic,$(LINKFLAGS)) -static +LIBS := $(filter-out -lrt,$(LIBS)) CXXFLAGS := $(filter-out -fPIC,$(CXXFLAGS)) CXXFLAGS += -std=$(CXXSTD) -Os -ABCMKARGS = CC="$(CC)" CXX="$(CXX)" LD="$(LD)" ABC_USE_LIBSTDCXX=1 LIBS="-lm -lpthread -static" OPTFLAGS="-O" \ +ABCMKARGS = CC="$(CC)" CXX="$(CXX)" LD="$(CXX)" ABC_USE_LIBSTDCXX=1 LIBS="-lm -lpthread -static" OPTFLAGS="-O" \ ARCHFLAGS="-DABC_USE_STDINT_H -DABC_NO_DYNAMIC_LINKING=1 -Wno-unused-but-set-variable $(ARCHFLAGS)" ABC_USE_NO_READLINE=1 ifeq ($(DISABLE_ABC_THREADS),1) ABCMKARGS += "ABC_USE_NO_PTHREADS=1" @@ -257,31 +254,28 @@ endif else ifeq ($(CONFIG),afl-gcc) CXX = AFL_QUIET=1 AFL_HARDEN=1 afl-gcc -LD = AFL_QUIET=1 AFL_HARDEN=1 afl-gcc CXXFLAGS += -std=$(CXXSTD) -Os ABCMKARGS += ARCHFLAGS="-DABC_USE_STDINT_H" else ifeq ($(CONFIG),cygwin) -CXX = gcc -LD = gcc +CXX = g++ CXXFLAGS += -std=gnu++11 -Os ABCMKARGS += ARCHFLAGS="-DABC_USE_STDINT_H" else ifeq ($(CONFIG),emcc) CXX = emcc -LD = emcc CXXFLAGS := -std=$(CXXSTD) $(filter-out -fPIC -ggdb,$(CXXFLAGS)) ABCMKARGS += ARCHFLAGS="-DABC_USE_STDINT_H -DABC_MEMALIGN=8 -Wno-c++11-narrowing" EMCC_CXXFLAGS := -Os -Wno-warn-absolute-paths -EMCC_LDFLAGS := --memory-init-file 0 --embed-file share -EMCC_LDFLAGS += -s NO_EXIT_RUNTIME=1 -EMCC_LDFLAGS += -s EXPORTED_FUNCTIONS="['_main','_run','_prompt','_errmsg','_memset']" -EMCC_LDFLAGS += -s TOTAL_MEMORY=134217728 -EMCC_LDFLAGS += -s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' +EMCC_LINKFLAGS := --memory-init-file 0 --embed-file share +EMCC_LINKFLAGS += -s NO_EXIT_RUNTIME=1 +EMCC_LINKFLAGS += -s EXPORTED_FUNCTIONS="['_main','_run','_prompt','_errmsg','_memset']" +EMCC_LINKFLAGS += -s TOTAL_MEMORY=134217728 +EMCC_LINKFLAGS += -s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' # https://github.com/kripken/emscripten/blob/master/src/settings.js CXXFLAGS += $(EMCC_CXXFLAGS) -LDFLAGS += $(EMCC_LDFLAGS) -LDLIBS = +LINKFLAGS += $(EMCC_LINKFLAGS) +LIBS = EXE = .js DISABLE_SPAWN := 1 @@ -309,21 +303,19 @@ yosys.html: misc/yosys.html else ifeq ($(CONFIG),wasi) ifeq ($(WASI_SDK),) -CXX = clang -LD = clang++ +CXX = clang++ AR = llvm-ar RANLIB = llvm-ranlib WASIFLAGS := -target wasm32-wasi --sysroot $(WASI_SYSROOT) $(WASIFLAGS) else -CXX = $(WASI_SDK)/bin/clang -LD = $(WASI_SDK)/bin/clang++ +CXX = $(WASI_SDK)/bin/clang++ AR = $(WASI_SDK)/bin/ar RANLIB = $(WASI_SDK)/bin/ranlib WASIFLAGS := --sysroot $(WASI_SDK)/share/wasi-sysroot $(WASIFLAGS) endif 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 := -lwasi-emulated-process-clocks $(filter-out -lrt,$(LDLIBS)) +LINKFLAGS := $(WASIFLAGS) -Wl,-z,stack-size=1048576 $(filter-out -rdynamic,$(LINKFLAGS)) +LIBS := -lwasi-emulated-process-clocks $(filter-out -lrt,$(LIBS)) ABCMKARGS += AR="$(AR)" RANLIB="$(RANLIB)" 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" @@ -339,34 +331,31 @@ endif else ifeq ($(CONFIG),mxe) PKG_CONFIG = /usr/local/src/mxe/usr/bin/i686-w64-mingw32.static-pkg-config CXX = /usr/local/src/mxe/usr/bin/i686-w64-mingw32.static-g++ -LD = /usr/local/src/mxe/usr/bin/i686-w64-mingw32.static-g++ CXXFLAGS += -std=$(CXXSTD) -Os -D_POSIX_SOURCE -DYOSYS_MXE_HACKS -Wno-attributes CXXFLAGS := $(filter-out -fPIC,$(CXXFLAGS)) -LDFLAGS := $(filter-out -rdynamic,$(LDFLAGS)) -s -LDLIBS := $(filter-out -lrt,$(LDLIBS)) +LINKFLAGS := $(filter-out -rdynamic,$(LINKFLAGS)) -s +LIBS := $(filter-out -lrt,$(LIBS)) ABCMKARGS += ARCHFLAGS="-DWIN32_NO_DLL -DHAVE_STRUCT_TIMESPEC -fpermissive -w" # TODO: Try to solve pthread linking issue in more appropriate way -ABCMKARGS += LIBS="lib/x86/pthreadVC2.lib -s" LDFLAGS="-Wl,--allow-multiple-definition" ABC_USE_NO_READLINE=1 CC="/usr/local/src/mxe/usr/bin/i686-w64-mingw32.static-gcc" +ABCMKARGS += LIBS="lib/x86/pthreadVC2.lib -s" LINKFLAGS="-Wl,--allow-multiple-definition" ABC_USE_NO_READLINE=1 CC="/usr/local/src/mxe/usr/bin/i686-w64-mingw32.static-gcc" EXE = .exe else ifeq ($(CONFIG),msys2-32) CXX = i686-w64-mingw32-g++ -LD = i686-w64-mingw32-g++ CXXFLAGS += -std=$(CXXSTD) -Os -D_POSIX_SOURCE -DYOSYS_WIN32_UNIX_DIR CXXFLAGS := $(filter-out -fPIC,$(CXXFLAGS)) -LDFLAGS := $(filter-out -rdynamic,$(LDFLAGS)) -s -LDLIBS := $(filter-out -lrt,$(LDLIBS)) +LINKFLAGS := $(filter-out -rdynamic,$(LINKFLAGS)) -s +LIBS := $(filter-out -lrt,$(LIBS)) ABCMKARGS += ARCHFLAGS="-DABC_USE_STDINT_H -DWIN32_NO_DLL -DHAVE_STRUCT_TIMESPEC -fpermissive -w" ABCMKARGS += LIBS="-lpthread -lshlwapi -s" ABC_USE_NO_READLINE=0 CC="i686-w64-mingw32-gcc" CXX="$(CXX)" EXE = .exe else ifeq ($(CONFIG),msys2-64) CXX = x86_64-w64-mingw32-g++ -LD = x86_64-w64-mingw32-g++ CXXFLAGS += -std=$(CXXSTD) -Os -D_POSIX_SOURCE -DYOSYS_WIN32_UNIX_DIR CXXFLAGS := $(filter-out -fPIC,$(CXXFLAGS)) -LDFLAGS := $(filter-out -rdynamic,$(LDFLAGS)) -s -LDLIBS := $(filter-out -lrt,$(LDLIBS)) +LINKFLAGS := $(filter-out -rdynamic,$(LINKFLAGS)) -s +LIBS := $(filter-out -lrt,$(LIBS)) ABCMKARGS += ARCHFLAGS="-DABC_USE_STDINT_H -DWIN32_NO_DLL -DHAVE_STRUCT_TIMESPEC -fpermissive -w" ABCMKARGS += LIBS="-lpthread -lshlwapi -s" ABC_USE_NO_READLINE=0 CC="x86_64-w64-mingw32-gcc" CXX="$(CXX)" EXE = .exe @@ -393,9 +382,9 @@ ifeq ($(BOOST_PYTHON_LIB),) $(error BOOST_PYTHON_LIB could not be detected. Please define manually) endif -LDLIBS += $(shell $(PYTHON_CONFIG) --libs) $(BOOST_PYTHON_LIB) -lboost_system -lboost_filesystem -# python-config --ldflags includes LDLIBS for some reason -LDFLAGS += $(filter-out -l%,$(shell $(PYTHON_CONFIG) --ldflags)) +LIBS += $(shell $(PYTHON_CONFIG) --libs) $(BOOST_PYTHON_LIB) -lboost_system -lboost_filesystem +# python-config --ldflags includes LIBS for some reason +LINKFLAGS += $(filter-out -l%,$(shell $(PYTHON_CONFIG) --ldflags)) CXXFLAGS += $(shell $(PYTHON_CONFIG) --includes) -DWITH_PYTHON PY_WRAPPER_FILE = kernel/python_wrappers @@ -409,22 +398,22 @@ CXXFLAGS += -DYOSYS_ENABLE_READLINE ifeq ($(OS), $(filter $(OS),FreeBSD OpenBSD NetBSD)) CXXFLAGS += -I/usr/local/include endif -LDLIBS += -lreadline +LIBS += -lreadline ifeq ($(LINK_CURSES),1) -LDLIBS += -lcurses +LIBS += -lcurses ABCMKARGS += "ABC_READLINE_LIBRARIES=-lcurses -lreadline" endif ifeq ($(LINK_TERMCAP),1) -LDLIBS += -ltermcap +LIBS += -ltermcap ABCMKARGS += "ABC_READLINE_LIBRARIES=-lreadline -ltermcap" endif ifeq ($(CONFIG),mxe) -LDLIBS += -ltermcap +LIBS += -ltermcap endif else ifeq ($(ENABLE_EDITLINE),1) CXXFLAGS += -DYOSYS_ENABLE_EDITLINE -LDLIBS += -ledit -ltinfo -lbsd +LIBS += -ledit -ltinfo -lbsd else ABCMKARGS += "ABC_USE_NO_READLINE=1" endif @@ -443,9 +432,9 @@ CXXFLAGS += $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) $(PKG_CONFIG) --silence-e ifeq ($(OS), MINGW) CXXFLAGS += -Ilibs/dlfcn-win32 endif -LDLIBS += $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) $(PKG_CONFIG) --silence-errors --libs libffi || echo -lffi) +LIBS += $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) $(PKG_CONFIG) --silence-errors --libs libffi || echo -lffi) ifneq ($(OS), $(filter $(OS),FreeBSD OpenBSD NetBSD MINGW)) -LDLIBS += -ldl +LIBS += -ldl endif endif @@ -455,7 +444,7 @@ endif ifeq ($(ENABLE_ZLIB),1) CXXFLAGS += -DYOSYS_ENABLE_ZLIB -LDLIBS += -lz +LIBS += -lz endif @@ -472,21 +461,21 @@ endif ifeq ($(CONFIG),mxe) CXXFLAGS += -DYOSYS_ENABLE_TCL -LDLIBS += -ltcl86 -lwsock32 -lws2_32 -lnetapi32 -lz -luserenv +LIBS += -ltcl86 -lwsock32 -lws2_32 -lnetapi32 -lz -luserenv else CXXFLAGS += $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) $(PKG_CONFIG) --silence-errors --cflags tcl || echo -I$(TCL_INCLUDE)) -DYOSYS_ENABLE_TCL -LDLIBS += $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) $(PKG_CONFIG) --silence-errors --libs tcl || echo $(TCL_LIBS)) +LIBS += $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) $(PKG_CONFIG) --silence-errors --libs tcl || echo $(TCL_LIBS)) endif endif ifeq ($(ENABLE_GCOV),1) CXXFLAGS += --coverage -LDFLAGS += --coverage +LINKFLAGS += --coverage endif ifeq ($(ENABLE_GPROF),1) CXXFLAGS += -pg -LDFLAGS += -pg +LINKFLAGS += -pg endif ifeq ($(ENABLE_NDEBUG),1) @@ -506,7 +495,7 @@ CXXFLAGS += -DYOSYS_ENABLE_ABC ifeq ($(LINK_ABC),1) CXXFLAGS += -DYOSYS_LINK_ABC ifeq ($(DISABLE_ABC_THREADS),0) -LDLIBS += -lpthread +LIBS += -lpthread endif else ifeq ($(ABCEXTERNAL),) @@ -520,10 +509,10 @@ GHDL_PREFIX ?= $(PREFIX) GHDL_INCLUDE_DIR ?= $(GHDL_PREFIX)/include GHDL_LIB_DIR ?= $(GHDL_PREFIX)/lib CXXFLAGS += -I$(GHDL_INCLUDE_DIR) -DYOSYS_ENABLE_GHDL -LDLIBS += $(GHDL_LIB_DIR)/libghdl.a $(file <$(GHDL_LIB_DIR)/libghdl.link) +LIBS += $(GHDL_LIB_DIR)/libghdl.a $(file <$(GHDL_LIB_DIR)/libghdl.link) endif -LDLIBS_VERIFIC = +LIBS_VERIFIC = ifeq ($(ENABLE_VERIFIC),1) VERIFIC_DIR ?= /usr/local/src/verific_lib VERIFIC_COMPONENTS ?= verilog database util containers hier_tree @@ -549,9 +538,9 @@ CXXFLAGS += -DYOSYSHQ_VERIFIC_EXTENSIONS endif CXXFLAGS += $(patsubst %,-I$(VERIFIC_DIR)/%,$(VERIFIC_COMPONENTS)) -DYOSYS_ENABLE_VERIFIC ifeq ($(OS), Darwin) -LDLIBS_VERIFIC += $(foreach comp,$(patsubst %,$(VERIFIC_DIR)/%/*-mac.a,$(VERIFIC_COMPONENTS)),-Wl,-force_load $(comp)) -lz +LIBS_VERIFIC += $(foreach comp,$(patsubst %,$(VERIFIC_DIR)/%/*-mac.a,$(VERIFIC_COMPONENTS)),-Wl,-force_load $(comp)) -lz else -LDLIBS_VERIFIC += -Wl,--whole-archive $(patsubst %,$(VERIFIC_DIR)/%/*-linux.a,$(VERIFIC_COMPONENTS)) -Wl,--no-whole-archive -lz +LIBS_VERIFIC += -Wl,--whole-archive $(patsubst %,$(VERIFIC_DIR)/%/*-linux.a,$(VERIFIC_COMPONENTS)) -Wl,--no-whole-archive -lz endif endif @@ -630,6 +619,7 @@ $(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/scopeinfo.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)) @@ -647,16 +637,10 @@ $(eval $(call add_include_file,frontends/ast/ast.h)) $(eval $(call add_include_file,frontends/ast/ast_binding.h)) $(eval $(call add_include_file,frontends/blif/blifparse.h)) $(eval $(call add_include_file,backends/rtlil/rtlil_backend.h)) -$(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/cxxrtl.h)) -$(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/cxxrtl_vcd.h)) -$(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.cc)) -$(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.h)) -$(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi_vcd.cc)) -$(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi_vcd.h)) OBJS += kernel/driver.o kernel/register.o kernel/rtlil.o kernel/log.o kernel/calc.o kernel/yosys.o OBJS += kernel/binding.o -OBJS += kernel/cellaigs.o kernel/celledges.o kernel/satgen.o kernel/qcsat.o kernel/mem.o kernel/ffmerge.o kernel/ff.o kernel/yw.o kernel/json.o kernel/fmt.o +OBJS += kernel/cellaigs.o kernel/celledges.o kernel/satgen.o kernel/scopeinfo.o kernel/qcsat.o kernel/mem.o kernel/ffmerge.o kernel/ff.o kernel/yw.o kernel/json.o kernel/fmt.o ifeq ($(ENABLE_ZLIB),1) OBJS += kernel/fstdata.o endif @@ -751,13 +735,13 @@ yosys.js: $(filter-out yosysjs-$(YOSYS_VER).zip,$(EXTRA_TARGETS)) endif $(PROGRAM_PREFIX)yosys$(EXE): $(OBJS) - $(P) $(LD) -o $(PROGRAM_PREFIX)yosys$(EXE) $(EXE_LDFLAGS) $(LDFLAGS) $(OBJS) $(LDLIBS) $(LDLIBS_VERIFIC) + $(P) $(CXX) -o $(PROGRAM_PREFIX)yosys$(EXE) $(EXE_LINKFLAGS) $(LINKFLAGS) $(OBJS) $(LIBS) $(LIBS_VERIFIC) libyosys.so: $(filter-out kernel/driver.o,$(OBJS)) ifeq ($(OS), Darwin) - $(P) $(LD) -o libyosys.so -shared -Wl,-install_name,$(LIBDIR)/libyosys.so $(LDFLAGS) $^ $(LDLIBS) $(LDLIBS_VERIFIC) + $(P) $(CXX) -o libyosys.so -shared -Wl,-install_name,$(LIBDIR)/libyosys.so $(LINKFLAGS) $^ $(LIBS) $(LIBS_VERIFIC) else - $(P) $(LD) -o libyosys.so -shared -Wl,-soname,$(LIBDIR)/libyosys.so $(LDFLAGS) $^ $(LDLIBS) $(LDLIBS_VERIFIC) + $(P) $(CXX) -o libyosys.so -shared -Wl,-soname,$(LIBDIR)/libyosys.so $(LINKFLAGS) $^ $(LIBS) $(LIBS_VERIFIC) endif %.o: %.cc @@ -766,7 +750,7 @@ endif %.pyh: %.h $(Q) mkdir -p $(dir $@) - $(P) cat $< | grep -E -v "#[ ]*(include|error)" | $(LD) $(CXXFLAGS) -x c++ -o $@ -E -P - + $(P) cat $< | grep -E -v "#[ ]*(include|error)" | $(CXX) $(CXXFLAGS) -x c++ -o $@ -E -P - ifeq ($(ENABLE_PYOSYS),1) $(PY_WRAPPER_FILE).cc: misc/$(PY_GEN_SCRIPT).py $(PY_WRAP_INCLUDES) @@ -787,15 +771,15 @@ kernel/version_$(GIT_REV).cc: $(YOSYS_SRC)/Makefile ifeq ($(ENABLE_VERIFIC),1) CXXFLAGS_NOVERIFIC = $(foreach v,$(CXXFLAGS),$(if $(findstring $(VERIFIC_DIR),$(v)),,$(v))) -LDLIBS_NOVERIFIC = $(foreach v,$(LDLIBS),$(if $(findstring $(VERIFIC_DIR),$(v)),,$(v))) +LIBS_NOVERIFIC = $(foreach v,$(LIBS),$(if $(findstring $(VERIFIC_DIR),$(v)),,$(v))) else CXXFLAGS_NOVERIFIC = $(CXXFLAGS) -LDLIBS_NOVERIFIC = $(LDLIBS) +LIBS_NOVERIFIC = $(LIBS) endif $(PROGRAM_PREFIX)yosys-config: misc/yosys-config.in $(P) $(SED) -e 's#@CXXFLAGS@#$(subst -Ilibs/dlfcn-win32,,$(subst -I. -I"$(YOSYS_SRC)",-I"$(DATDIR)/include",$(strip $(CXXFLAGS_NOVERIFIC))))#;' \ - -e 's#@CXX@#$(strip $(CXX))#;' -e 's#@LDFLAGS@#$(strip $(LDFLAGS) $(PLUGIN_LDFLAGS))#;' -e 's#@LDLIBS@#$(strip $(LDLIBS_NOVERIFIC) $(PLUGIN_LDLIBS))#;' \ + -e 's#@CXX@#$(strip $(CXX))#;' -e 's#@LINKFLAGS@#$(strip $(LINKFLAGS) $(PLUGIN_LINKFLAGS))#;' -e 's#@LIBS@#$(strip $(LIBS_NOVERIFIC) $(PLUGIN_LIBS))#;' \ -e 's#@BINDIR@#$(strip $(BINDIR))#;' -e 's#@DATDIR@#$(strip $(DATDIR))#;' < $< > $(PROGRAM_PREFIX)yosys-config $(Q) chmod +x $(PROGRAM_PREFIX)yosys-config @@ -847,9 +831,22 @@ else ABCOPT="" endif +# When YOSYS_NOVERIFIC is set as a make variable, also export it to the +# enviornment, so that `YOSYS_NOVERIFIC=1 make test` _and_ +# `make test YOSYS_NOVERIFIC=1` will run with verific disabled. +ifeq ($(YOSYS_NOVERIFIC),1) +export YOSYS_NOVERIFIC +endif + test: $(TARGETS) $(EXTRA_TARGETS) ifeq ($(ENABLE_VERIFIC),1) +ifeq ($(YOSYS_NOVERIFIC),1) + @echo + @echo "Running tests without verific support due to YOSYS_NOVERIFIC=1" + @echo +else +cd tests/verific && bash run-test.sh $(SEEDOPT) +endif endif +cd tests/simple && bash run-test.sh $(SEEDOPT) +cd tests/simple_abc9 && bash run-test.sh $(SEEDOPT) @@ -921,7 +918,7 @@ ystests: $(TARGETS) $(EXTRA_TARGETS) # Unit test unit-test: libyosys.so @$(MAKE) -C $(UNITESTPATH) CXX="$(CXX)" CPPFLAGS="$(CPPFLAGS)" \ - CXXFLAGS="$(CXXFLAGS)" LDLIBS="$(LDLIBS)" ROOTPATH="$(CURDIR)" + CXXFLAGS="$(CXXFLAGS)" LIBS="$(LIBS)" ROOTPATH="$(CURDIR)" clean-unit-test: @$(MAKE) -C $(UNITESTPATH) clean diff --git a/README.md b/README.md index 69a227b7fb5..660bd5c6d57 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ``` yosys -- Yosys Open SYnthesis Suite -Copyright (C) 2012 - 2020 Claire Xenia Wolf +Copyright (C) 2012 - 2024 Claire Xenia Wolf Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -587,7 +587,13 @@ from SystemVerilog: - enums are supported (including inside packages) - but are currently not strongly typed -- packed structs and unions are supported. +- packed structs and unions are supported + - arrays of packed structs/unions are currently not supported + - structure literals are currently not supported + +- multidimensional arrays are supported + - array assignment of unpacked arrays is currently not supported + - array literals are currently not supported - SystemVerilog interfaces (SVIs) are supported. Modports for specifying whether ports are inputs or outputs are supported. diff --git a/backends/aiger/aiger.cc b/backends/aiger/aiger.cc index f77a6497845..fe4f7681db3 100644 --- a/backends/aiger/aiger.cc +++ b/backends/aiger/aiger.cc @@ -320,6 +320,9 @@ struct AigerWriter continue; } + if (cell->type == ID($scopeinfo)) + continue; + log_error("Unsupported cell type: %s (%s)\n", log_id(cell->type), log_id(cell)); } diff --git a/backends/blif/blif.cc b/backends/blif/blif.cc index 8e2c088c484..788b7f951f2 100644 --- a/backends/blif/blif.cc +++ b/backends/blif/blif.cc @@ -226,6 +226,9 @@ struct BlifDumper for (auto cell : module->cells()) { + if (cell->type == ID($scopeinfo)) + continue; + if (config->unbuf_types.count(cell->type)) { auto portnames = config->unbuf_types.at(cell->type); f << stringf(".names %s %s\n1 1\n", diff --git a/backends/cxxrtl/Makefile.inc b/backends/cxxrtl/Makefile.inc index aaa304502e0..dd77d2ad360 100644 --- a/backends/cxxrtl/Makefile.inc +++ b/backends/cxxrtl/Makefile.inc @@ -1,2 +1,11 @@ OBJS += backends/cxxrtl/cxxrtl_backend.o + +$(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/cxxrtl.h)) +$(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/cxxrtl_vcd.h)) +$(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/cxxrtl_time.h)) +$(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h)) +$(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.cc)) +$(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.h)) +$(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi_vcd.cc)) +$(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi_vcd.h)) diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index c60b43d3faa..877a829d37a 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -25,6 +25,7 @@ #include "kernel/mem.h" #include "kernel/log.h" #include "kernel/fmt.h" +#include "kernel/scopeinfo.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN @@ -1518,8 +1519,9 @@ struct CxxrtlWorker { } else if (is_internal_cell(cell->type)) { log_cmd_error("Unsupported internal cell `%s'.\n", cell->type.c_str()); // User cells + } else if (for_debug) { + // Outlines are called on demand when computing the value of a debug item. Nothing to do here. } else { - log_assert(!for_debug); log_assert(cell->known()); bool buffered_inputs = false; const char *access = is_cxxrtl_blackbox_cell(cell) ? "->" : "."; @@ -2310,11 +2312,14 @@ struct CxxrtlWorker { dict attributes = object->attributes; // Inherently necessary to get access to the object, so a waste of space to emit. attributes.erase(ID::hdlname); + // Internal Yosys attribute that should be removed but isn't. + attributes.erase(ID::module_not_derived); dump_metadata_map(attributes); } void dump_debug_info_method(RTLIL::Module *module) { + size_t count_scopes = 0; size_t count_public_wires = 0; size_t count_member_wires = 0; size_t count_undriven = 0; @@ -2327,153 +2332,188 @@ struct CxxrtlWorker { size_t count_skipped_wires = 0; inc_indent(); f << indent << "assert(path.empty() || path[path.size() - 1] == ' ');\n"; - for (auto wire : module->wires()) { - const auto &debug_wire_type = debug_wire_types[wire]; - if (!wire->name.isPublic()) - continue; - count_public_wires++; - switch (debug_wire_type.type) { - case WireType::BUFFERED: - case WireType::MEMBER: { - // Member wire - std::vector flags; - - if (wire->port_input && wire->port_output) - flags.push_back("INOUT"); - else if (wire->port_output) - flags.push_back("OUTPUT"); - else if (wire->port_input) - flags.push_back("INPUT"); - - bool has_driven_sync = false; - bool has_driven_comb = false; - bool has_undriven = false; - if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) { - for (auto bit : SigSpec(wire)) - if (!bit_has_state.count(bit)) - has_undriven = true; - else if (bit_has_state[bit]) - has_driven_sync = true; - else - has_driven_comb = true; - } else if (wire->port_output) { - switch (cxxrtl_port_type(module, wire->name)) { - case CxxrtlPortType::SYNC: - has_driven_sync = true; - break; - case CxxrtlPortType::COMB: - has_driven_comb = true; - break; - case CxxrtlPortType::UNKNOWN: - has_driven_sync = has_driven_comb = true; - break; - } - } else { - has_undriven = true; - } - if (has_undriven) - flags.push_back("UNDRIVEN"); - if (!has_driven_sync && !has_driven_comb && has_undriven) - count_undriven++; - if (has_driven_sync) - flags.push_back("DRIVEN_SYNC"); - if (has_driven_sync && !has_driven_comb && !has_undriven) - count_driven_sync++; - if (has_driven_comb) - flags.push_back("DRIVEN_COMB"); - if (!has_driven_sync && has_driven_comb && !has_undriven) - count_driven_comb++; - if (has_driven_sync + has_driven_comb + has_undriven > 1) - count_mixed_driver++; - - f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire)); - f << ", debug_item(" << mangle(wire) << ", " << wire->start_offset; - bool first = true; - for (auto flag : flags) { - if (first) { - first = false; - f << ", "; + f << indent << "if (scopes) {\n"; + inc_indent(); + // The module is responsible for adding its own scope. + f << indent << "scopes->add(path.empty() ? path : path.substr(0, path.size() - 1), "; + f << escape_cxx_string(get_hdl_name(module)) << ", "; + dump_debug_attrs(module); + f << ", std::move(cell_attrs));\n"; + count_scopes++; + // If there were any submodules that were flattened, the module is also responsible for adding them. + for (auto cell : module->cells()) { + if (cell->type != ID($scopeinfo)) continue; + if (cell->getParam(ID::TYPE).decode_string() == "module") { + auto module_attrs = scopeinfo_attributes(cell, ScopeinfoAttrs::Module); + auto cell_attrs = scopeinfo_attributes(cell, ScopeinfoAttrs::Cell); + cell_attrs.erase(ID::module_not_derived); + f << indent << "scopes->add(path + " << escape_cxx_string(get_hdl_name(cell)) << ", "; + f << escape_cxx_string(cell->get_string_attribute(ID(module))) << ", "; + dump_metadata_map(module_attrs); + f << ", "; + dump_metadata_map(cell_attrs); + f << ");\n"; + } else log_assert(false && "Unknown $scopeinfo type"); + count_scopes++; + } + dec_indent(); + f << indent << "}\n"; + f << indent << "if (items) {\n"; + inc_indent(); + for (auto wire : module->wires()) { + const auto &debug_wire_type = debug_wire_types[wire]; + if (!wire->name.isPublic()) + continue; + count_public_wires++; + switch (debug_wire_type.type) { + case WireType::BUFFERED: + case WireType::MEMBER: { + // Member wire + std::vector flags; + + if (wire->port_input && wire->port_output) + flags.push_back("INOUT"); + else if (wire->port_output) + flags.push_back("OUTPUT"); + else if (wire->port_input) + flags.push_back("INPUT"); + + bool has_driven_sync = false; + bool has_driven_comb = false; + bool has_undriven = false; + if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) { + for (auto bit : SigSpec(wire)) + if (!bit_has_state.count(bit)) + has_undriven = true; + else if (bit_has_state[bit]) + has_driven_sync = true; + else + has_driven_comb = true; + } else if (wire->port_output) { + switch (cxxrtl_port_type(module, wire->name)) { + case CxxrtlPortType::SYNC: + has_driven_sync = true; + break; + case CxxrtlPortType::COMB: + has_driven_comb = true; + break; + case CxxrtlPortType::UNKNOWN: + has_driven_sync = has_driven_comb = true; + break; + } } else { - f << "|"; + has_undriven = true; + } + if (has_undriven) + flags.push_back("UNDRIVEN"); + if (!has_driven_sync && !has_driven_comb && has_undriven) + count_undriven++; + if (has_driven_sync) + flags.push_back("DRIVEN_SYNC"); + if (has_driven_sync && !has_driven_comb && !has_undriven) + count_driven_sync++; + if (has_driven_comb) + flags.push_back("DRIVEN_COMB"); + if (!has_driven_sync && has_driven_comb && !has_undriven) + count_driven_comb++; + if (has_driven_sync + has_driven_comb + has_undriven > 1) + count_mixed_driver++; + + f << indent << "items->add(path + " << escape_cxx_string(get_hdl_name(wire)); + f << ", debug_item(" << mangle(wire) << ", " << wire->start_offset; + bool first = true; + for (auto flag : flags) { + if (first) { + first = false; + f << ", "; + } else { + f << "|"; + } + f << "debug_item::" << flag; } - f << "debug_item::" << flag; + f << "), "; + dump_debug_attrs(wire); + f << ");\n"; + count_member_wires++; + break; + } + case WireType::ALIAS: { + // Alias of a member wire + const RTLIL::Wire *aliasee = debug_wire_type.sig_subst.as_wire(); + f << indent << "items->add(path + " << escape_cxx_string(get_hdl_name(wire)); + f << ", debug_item("; + // If the aliasee is an outline, then the alias must be an outline, too; otherwise downstream + // tooling has no way to find out about the outline. + if (debug_wire_types[aliasee].is_outline()) + f << "debug_eval_outline"; + else + f << "debug_alias()"; + f << ", " << mangle(aliasee) << ", " << wire->start_offset << "), "; + dump_debug_attrs(aliasee); + f << ");\n"; + count_alias_wires++; + break; + } + case WireType::CONST: { + // Wire tied to a constant + f << indent << "static const value<" << wire->width << "> const_" << mangle(wire) << " = "; + 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 << "), "; + 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 << "), "; + dump_debug_attrs(wire); + f << ");\n"; + count_inline_wires++; + break; + } + default: { + // Localized or inlined wire with no debug information + count_skipped_wires++; + break; } - f << "), "; - dump_debug_attrs(wire); - f << ");\n"; - count_member_wires++; - break; - } - case WireType::ALIAS: { - // Alias of a member wire - const RTLIL::Wire *aliasee = debug_wire_type.sig_subst.as_wire(); - f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire)); - f << ", debug_item("; - // If the aliasee is an outline, then the alias must be an outline, too; otherwise downstream - // tooling has no way to find out about the outline. - if (debug_wire_types[aliasee].is_outline()) - f << "debug_eval_outline"; - else - f << "debug_alias()"; - f << ", " << mangle(aliasee) << ", " << wire->start_offset << "), "; - dump_debug_attrs(aliasee); - f << ");\n"; - count_alias_wires++; - break; - } - case WireType::CONST: { - // Wire tied to a constant - f << indent << "static const value<" << wire->width << "> const_" << mangle(wire) << " = "; - 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 << "), "; - 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 << "), "; - dump_debug_attrs(wire); + } + if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) { + for (auto &mem : mod_memories[module]) { + if (!mem.memid.isPublic()) + 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 << "), "; + if (mem.packed) { + dump_debug_attrs(mem.cell); + } else { + dump_debug_attrs(mem.mem); + } f << ");\n"; - count_inline_wires++; - break; - } - default: { - // Localized or inlined wire with no debug information - count_skipped_wires++; - break; } } - } + dec_indent(); + f << indent << "}\n"; if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) { - for (auto &mem : mod_memories[module]) { - if (!mem.memid.isPublic()) - 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 << "), "; - 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)) continue; const char *access = is_cxxrtl_blackbox_cell(cell) ? "->" : "."; - f << indent << mangle(cell) << access << "debug_info(items, "; - f << "path + " << escape_cxx_string(get_hdl_name(cell) + ' ') << ");\n"; + f << indent << mangle(cell) << access; + f << "debug_info(items, scopes, path + " << escape_cxx_string(get_hdl_name(cell) + ' ') << ", "; + dump_debug_attrs(cell); + f << ");\n"; } } dec_indent(); log_debug("Debug information statistics for module `%s':\n", log_id(module)); + log_debug(" Scopes: %zu", count_scopes); log_debug(" Public wires: %zu, of which:\n", count_public_wires); log_debug(" Member wires: %zu, of which:\n", count_member_wires); log_debug(" Undriven: %zu (incl. inputs)\n", count_undriven); @@ -2511,18 +2551,18 @@ struct CxxrtlWorker { dump_eval_method(module); f << indent << "}\n"; f << "\n"; - f << indent << "template\n"; - f << indent << "bool commit(ObserverT &observer) {\n"; + f << indent << "virtual bool commit(observer &observer) {\n"; dump_commit_method(module); f << indent << "}\n"; f << "\n"; f << indent << "bool commit() override {\n"; f << indent << indent << "observer observer;\n"; - f << indent << indent << "return commit<>(observer);\n"; + f << indent << indent << "return commit(observer);\n"; f << indent << "}\n"; if (debug_info) { f << "\n"; - f << indent << "void debug_info(debug_items &items, std::string path = \"\") override {\n"; + f << indent << "void debug_info(debug_items *items, debug_scopes *scopes, " + << "std::string path, metadata_map &&cell_attrs = {}) override {\n"; dump_debug_info_method(module); f << indent << "}\n"; } @@ -2631,7 +2671,8 @@ struct CxxrtlWorker { } } f << "\n"; - f << indent << "void debug_info(debug_items &items, std::string path = \"\") override;\n"; + f << indent << "void debug_info(debug_items *items, debug_scopes *scopes, " + << "std::string path, metadata_map &&cell_attrs = {}) override;\n"; } dec_indent(); f << indent << "}; // struct " << mangle(module) << "\n"; @@ -2659,7 +2700,8 @@ struct CxxrtlWorker { } f << "\n"; f << indent << "CXXRTL_EXTREMELY_COLD\n"; - f << indent << "void " << mangle(module) << "::debug_info(debug_items &items, std::string path) {\n"; + f << indent << "void " << mangle(module) << "::debug_info(debug_items *items, debug_scopes *scopes, " + << "std::string path, metadata_map &&cell_attrs) {\n"; dump_debug_info_method(module); f << indent << "}\n"; } @@ -3421,8 +3463,7 @@ struct CxxrtlBackend : public Backend { log(" wire<8> p_o_data;\n"); log("\n"); log(" bool eval(performer *performer) override;\n"); - log(" template\n"); - log(" bool commit(ObserverT &observer);\n"); + log(" virtual bool commit(observer &observer);\n"); log(" bool commit() override;\n"); log("\n"); log(" static std::unique_ptr\n"); diff --git a/backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.cc b/backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.cc index 593e067a181..3c62401ddea 100644 --- a/backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.cc +++ b/backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.cc @@ -35,19 +35,19 @@ cxxrtl_handle cxxrtl_create(cxxrtl_toplevel design) { return cxxrtl_create_at(design, ""); } -cxxrtl_handle cxxrtl_create_at(cxxrtl_toplevel design, const char *root) { - std::string path = root; - if (!path.empty()) { +cxxrtl_handle cxxrtl_create_at(cxxrtl_toplevel design, const char *top_path_) { + std::string top_path = top_path_; + if (!top_path.empty()) { // module::debug_info() accepts either an empty path, or a path ending in space to simplify // the logic in generated code. While this is sketchy at best to expose in the C++ API, this // would be a lot worse in the C API, so don't expose it here. - assert(path.back() != ' '); - path += ' '; + assert(top_path.back() != ' '); + top_path += ' '; } cxxrtl_handle handle = new _cxxrtl_handle; handle->module = std::move(design->module); - handle->module->debug_info(handle->objects, path); + handle->module->debug_info(handle->objects, top_path); delete design; return handle; } diff --git a/backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.h b/backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.h index e5c84bf656a..ae42733ad7f 100644 --- a/backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.h +++ b/backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.h @@ -55,8 +55,8 @@ cxxrtl_handle cxxrtl_create(cxxrtl_toplevel design); // Create a design handle at a given hierarchy position from a design toplevel. // // This operation is similar to `cxxrtl_create`, except the full hierarchical name of every object -// is prepended with `root`. -cxxrtl_handle cxxrtl_create_at(cxxrtl_toplevel design, const char *root); +// is prepended with `top_path`. +cxxrtl_handle cxxrtl_create_at(cxxrtl_toplevel design, const char *top_path); // Release all resources used by a design and its handle. void cxxrtl_destroy(cxxrtl_handle handle); @@ -240,6 +240,11 @@ struct cxxrtl_object { // through wires, the bits are double buffered. To avoid race conditions, user code should // always read from `curr` and write to `next`. The `curr` pointer is always valid; for objects // that cannot be modified, or cannot be modified in a race-free way, `next` is NULL. + // + // In case where `width == 0`, `curr` is a non-NULL pointer unique for the wire. That is, + // there is a 1-to-1 correspondence between simulation objects and `curr` pointers, regardless + // of whether they have storage or not. (Aliases' `curr` pointer equals that of some other + // simulated object.) uint32_t *curr; uint32_t *next; diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index ee55011e86d..b834cd12019 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -1176,7 +1176,7 @@ struct debug_item : ::cxxrtl_object { template debug_item(value &item, size_t lsb_offset = 0, uint32_t flags_ = 0) { - static_assert(sizeof(item) == value::chunks * sizeof(chunk_t), + static_assert(Bits == 0 || sizeof(item) == value::chunks * sizeof(chunk_t), "value is not compatible with C layout"); type = VALUE; flags = flags_; @@ -1192,7 +1192,7 @@ struct debug_item : ::cxxrtl_object { template debug_item(const value &item, size_t lsb_offset = 0) { - static_assert(sizeof(item) == value::chunks * sizeof(chunk_t), + static_assert(Bits == 0 || sizeof(item) == value::chunks * sizeof(chunk_t), "value is not compatible with C layout"); type = VALUE; flags = DRIVEN_COMB; @@ -1208,8 +1208,9 @@ struct debug_item : ::cxxrtl_object { template debug_item(wire &item, size_t lsb_offset = 0, uint32_t flags_ = 0) { - static_assert(sizeof(item.curr) == value::chunks * sizeof(chunk_t) && - sizeof(item.next) == value::chunks * sizeof(chunk_t), + static_assert(Bits == 0 || + (sizeof(item.curr) == value::chunks * sizeof(chunk_t) && + sizeof(item.next) == value::chunks * sizeof(chunk_t)), "wire is not compatible with C layout"); type = WIRE; flags = flags_; @@ -1225,7 +1226,7 @@ struct debug_item : ::cxxrtl_object { template debug_item(memory &item, size_t zero_offset = 0) { - static_assert(sizeof(item.data[0]) == value::chunks * sizeof(chunk_t), + static_assert(Width == 0 || sizeof(item.data[0]) == value::chunks * sizeof(chunk_t), "memory is not compatible with C layout"); type = MEMORY; flags = 0; @@ -1241,7 +1242,7 @@ struct debug_item : ::cxxrtl_object { template debug_item(debug_alias, const value &item, size_t lsb_offset = 0) { - static_assert(sizeof(item) == value::chunks * sizeof(chunk_t), + static_assert(Bits == 0 || sizeof(item) == value::chunks * sizeof(chunk_t), "value is not compatible with C layout"); type = ALIAS; flags = DRIVEN_COMB; @@ -1257,8 +1258,9 @@ struct debug_item : ::cxxrtl_object { template debug_item(debug_alias, const wire &item, size_t lsb_offset = 0) { - static_assert(sizeof(item.curr) == value::chunks * sizeof(chunk_t) && - sizeof(item.next) == value::chunks * sizeof(chunk_t), + static_assert(Bits == 0 || + (sizeof(item.curr) == value::chunks * sizeof(chunk_t) && + sizeof(item.next) == value::chunks * sizeof(chunk_t)), "wire is not compatible with C layout"); type = ALIAS; flags = DRIVEN_COMB; @@ -1274,7 +1276,7 @@ struct debug_item : ::cxxrtl_object { template debug_item(debug_outline &group, const value &item, size_t lsb_offset = 0) { - static_assert(sizeof(item) == value::chunks * sizeof(chunk_t), + static_assert(Bits == 0 || sizeof(item) == value::chunks * sizeof(chunk_t), "value is not compatible with C layout"); type = OUTLINE; flags = DRIVEN_COMB; @@ -1318,17 +1320,26 @@ namespace cxxrtl { using debug_attrs = ::_cxxrtl_attr_set; struct debug_items { + // Debug items may be composed of multiple parts, but the attributes are shared between all of them. + // There are additional invariants, not all of which are not checked by this code: + // - Memories and non-memories cannot be mixed together. + // - Bit indices (considering `lsb_at` and `width`) must not overlap. + // - Row indices (considering `depth` and `zero_at`) must be the same. + // - The `INPUT` and `OUTPUT` flags must be the same for all parts. + // Other than that, the parts can be quite different, e.g. it is OK to mix a value, a wire, an alias, + // and an outline, in the debug information for a single name in four parts. std::map> table; std::map> attrs_table; - void add(const std::string &name, debug_item &&item, metadata_map &&item_attrs = {}) { - std::unique_ptr &attrs = attrs_table[name]; + void add(const std::string &path, debug_item &&item, metadata_map &&item_attrs = {}) { + assert((path.empty() || path[path.size() - 1] != ' ') && path.find(" ") == std::string::npos); + std::unique_ptr &attrs = attrs_table[path]; 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]; + std::vector &parts = table[path]; parts.emplace_back(item); std::sort(parts.begin(), parts.end(), [](const debug_item &a, const debug_item &b) { @@ -1336,35 +1347,71 @@ struct debug_items { }); } - size_t count(const std::string &name) const { - if (table.count(name) == 0) + size_t count(const std::string &path) const { + if (table.count(path) == 0) return 0; - return table.at(name).size(); + return table.at(path).size(); } - const std::vector &parts_at(const std::string &name) const { - return table.at(name); + const std::vector &at(const std::string &path) const { + return table.at(path); } - const debug_item &at(const std::string &name) const { - const std::vector &parts = table.at(name); + // Like `at()`, but operates only on single-part debug items. + const debug_item &operator [](const std::string &path) const { + const std::vector &parts = table.at(path); assert(parts.size() == 1); return parts.at(0); } - const debug_item &operator [](const std::string &name) const { - return at(name); + bool is_memory(const std::string &path) const { + return at(path).at(0).type == debug_item::MEMORY; } - const metadata_map &attrs(const std::string &name) const { - return attrs_table.at(name)->map; + const metadata_map &attrs(const std::string &path) const { + return attrs_table.at(path)->map; } }; -// Tag class to disambiguate the default constructor used by the toplevel module that calls reset(), +// Only `module` scopes are defined. The type is implicit, since Yosys does not currently support +// any other scope types. +struct debug_scope { + std::string module_name; + std::unique_ptr module_attrs; + std::unique_ptr cell_attrs; +}; + +struct debug_scopes { + std::map table; + + void add(const std::string &path, const std::string &module_name, metadata_map &&module_attrs, metadata_map &&cell_attrs) { + assert((path.empty() || path[path.size() - 1] != ' ') && path.find(" ") == std::string::npos); + assert(table.count(path) == 0); + debug_scope &scope = table[path]; + scope.module_name = module_name; + scope.module_attrs = std::unique_ptr(new debug_attrs { module_attrs }); + scope.cell_attrs = std::unique_ptr(new debug_attrs { cell_attrs }); + } + + size_t contains(const std::string &path) const { + return table.count(path); + } + + const debug_scope &operator [](const std::string &path) const { + return table.at(path); + } +}; + +// Tag class to disambiguate the default constructor used by the toplevel module that calls `reset()`, // and the constructor of interior modules that should not call it. struct interior {}; +// The core API of the `module` class consists of only four virtual methods: `reset()`, `eval()`, +// `commit`, and `debug_info()`. (The virtual destructor is made necessary by C++.) Every other method +// is a convenience method, and exists solely to simplify some common pattern for C++ API consumers. +// No behavior may be added to such convenience methods that other parts of CXXRTL can rely on, since +// there is no guarantee they will be called (and, for example, other CXXRTL libraries will often call +// the `eval()` and `commit()` directly instead, as well as being exposed in the C API). struct module { module() {} virtual ~module() {} @@ -1380,8 +1427,14 @@ struct module { virtual void reset() = 0; + // The `eval()` callback object, `performer`, is included in the virtual call signature since + // the generated code has broadly identical performance properties. virtual bool eval(performer *performer = nullptr) = 0; - virtual bool commit() = 0; // commit observer isn't available since it avoids virtual calls + + // The `commit()` callback object, `observer`, is not included in the virtual call signature since + // the generated code is severely pessimized by it. To observe commit events, the non-virtual + // `commit(observer *)` overload must be called directly on a `module` subclass. + virtual bool commit() = 0; size_t step(performer *performer = nullptr) { size_t deltas = 0; @@ -1393,8 +1446,16 @@ struct module { return deltas; } - virtual void debug_info(debug_items &items, std::string path = "") { - (void)items, (void)path; + virtual void debug_info(debug_items *items, debug_scopes *scopes, std::string path, metadata_map &&cell_attrs = {}) { + (void)items, (void)scopes, (void)path, (void)cell_attrs; + } + + // Compatibility method. +#if __has_attribute(deprecated) + __attribute__((deprecated("Use `debug_info(path, &items, /*scopes=*/nullptr);` instead. (`path` could be \"top \".)"))) +#endif + void debug_info(debug_items &items, std::string path) { + debug_info(&items, /*scopes=*/nullptr, path); } }; diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h index d824fdb83c8..454895a1f27 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h @@ -491,9 +491,9 @@ class recorder { template recorder(Args &&...args) : writer(std::forward(args)...) {} - void start(module &module) { + void start(module &module, std::string top_path = "") { debug_items items; - module.debug_info(items); + module.debug_info(&items, /*scopes=*/nullptr, top_path); start(items); } @@ -556,7 +556,7 @@ class recorder { bool record_incremental(ModuleT &module) { assert(streaming); - struct { + struct : observer { std::unordered_map *ident_lookup; spool::writer *writer; @@ -569,7 +569,9 @@ class recorder { void on_update(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) { writer->write_change(ident_lookup->at(base), chunks, value, index); } - } record_observer = { &ident_lookup, &writer }; + } record_observer; + record_observer.ident_lookup = &ident_lookup; + record_observer.writer = &writer; writer.write_sample(/*incremental=*/true, pointer++, timestamp); for (auto input_index : inputs) { @@ -619,9 +621,10 @@ class player { template player(Args &&...args) : reader(std::forward(args)...) {} - void start(module &module) { + // The `top_path` must match the one given to the recorder. + void start(module &module, std::string top_path = "") { debug_items items; - module.debug_info(items); + module.debug_info(&items, /*scopes=*/nullptr, top_path); start(items); } @@ -641,7 +644,7 @@ class player { assert(items.count(name) != 0); assert(part_index < items.count(name)); - const debug_item &part = items.parts_at(name).at(part_index); + const debug_item &part = items.at(name).at(part_index); assert(chunks == (part.width + sizeof(chunk_t) * 8 - 1) / (sizeof(chunk_t) * 8)); assert(depth == part.depth); diff --git a/backends/edif/edif.cc b/backends/edif/edif.cc index 00fd7f54e39..553eb23d645 100644 --- a/backends/edif/edif.cc +++ b/backends/edif/edif.cc @@ -213,6 +213,9 @@ struct EdifBackend : public Backend { for (auto cell : module->cells()) { + if (cell->type == ID($scopeinfo)) + continue; + if (design->module(cell->type) == nullptr || design->module(cell->type)->get_blackbox_attribute()) { lib_cell_ports[cell->type]; for (auto p : cell->connections()) diff --git a/backends/firrtl/firrtl.cc b/backends/firrtl/firrtl.cc index fc1d628915c..dc76dbeecf5 100644 --- a/backends/firrtl/firrtl.cc +++ b/backends/firrtl/firrtl.cc @@ -980,6 +980,9 @@ struct FirrtlWorker register_reverse_wire_map(y_id, cell->getPort(ID::Y)); continue; } + + if (cell->type == ID($scopeinfo)) + continue; log_error("Cell type not supported: %s (%s.%s)\n", log_id(cell->type), log_id(module), log_id(cell)); } diff --git a/backends/json/json.cc b/backends/json/json.cc index fd2c922fd5d..2f442c494f7 100644 --- a/backends/json/json.cc +++ b/backends/json/json.cc @@ -192,6 +192,10 @@ struct JsonWriter for (auto c : module->cells()) { if (use_selection && !module->selected(c)) continue; + // Eventually we will want to emit $scopeinfo, but currently this + // will break JSON netlist consumers like nextpnr + if (c->type == ID($scopeinfo)) + continue; f << stringf("%s\n", first ? "" : ","); f << stringf(" %s: {\n", get_name(c->name).c_str()); f << stringf(" \"hide_name\": %s,\n", c->name[0] == '$' ? "1" : "0"); diff --git a/backends/smt2/smtio.py b/backends/smt2/smtio.py index 0ec7f08f4dc..ebf364f069a 100644 --- a/backends/smt2/smtio.py +++ b/backends/smt2/smtio.py @@ -920,7 +920,7 @@ def get_list(self, expr_list): if len(expr_list) == 0: return [] self.write("(get-value (%s))" % " ".join(expr_list)) - return [n[1] for n in self.parse(self.read())] + return [n[1] for n in self.parse(self.read()) if n] def get_path(self, mod, path): assert mod in self.modinfo diff --git a/backends/smv/smv.cc b/backends/smv/smv.cc index 49c2cc7a6c4..44e20038441 100644 --- a/backends/smv/smv.cc +++ b/backends/smv/smv.cc @@ -573,6 +573,9 @@ struct SmvWorker continue; } + if (cell->type == ID($scopeinfo)) + continue; + if (cell->type[0] == '$') { if (cell->type.in(ID($dffe), ID($sdff), ID($sdffe), ID($sdffce)) || cell->type.str().substr(0, 6) == "$_SDFF" || (cell->type.str().substr(0, 6) == "$_DFFE" && cell->type.str().size() == 10)) { log_error("Unsupported cell type %s for cell %s.%s -- please run `dffunmap` before `write_smv`.\n", diff --git a/backends/spice/spice.cc b/backends/spice/spice.cc index f260276eb93..1160a01a188 100644 --- a/backends/spice/spice.cc +++ b/backends/spice/spice.cc @@ -72,6 +72,9 @@ static void print_spice_module(std::ostream &f, RTLIL::Module *module, RTLIL::De for (auto cell : module->cells()) { + if (cell->type == ID($scopeinfo)) + continue; + f << stringf("X%d", cell_counter++); std::vector port_sigs; diff --git a/backends/verilog/verilog_backend.cc b/backends/verilog/verilog_backend.cc index 988eef6588f..05b7c6c4008 100644 --- a/backends/verilog/verilog_backend.cc +++ b/backends/verilog/verilog_backend.cc @@ -1871,6 +1871,13 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) void dump_cell(std::ostream &f, std::string indent, RTLIL::Cell *cell) { + // To keep the output compatible with other tools we ignore $scopeinfo + // cells that exist only to hold metadata. If in the future that metadata + // should be exposed as part of the write_verilog output it should be + // opt-in and/or represented as something else than a $scopeinfo cell. + if (cell->type == ID($scopeinfo)) + return; + // Handled by dump_memory if (cell->is_mem_cell()) return; diff --git a/docs/source/CHAPTER_Basics.rst b/docs/source/CHAPTER_Basics.rst index 618bec545a9..31230101a17 100644 --- a/docs/source/CHAPTER_Basics.rst +++ b/docs/source/CHAPTER_Basics.rst @@ -503,7 +503,7 @@ This process is illustrated in :numref:`Fig. %s `. :name: fig:Basics_flow Typical design flow. Green boxes represent manually created models. - Orange boxes represent modesl generated by synthesis tools. + Orange boxes represent models generated by synthesis tools. In this example the System Level Model and the Behavioural Model are both diff --git a/docs/source/CHAPTER_CellLib.rst b/docs/source/CHAPTER_CellLib.rst index 3b6f1b4d387..70a0e2150f7 100644 --- a/docs/source/CHAPTER_CellLib.rst +++ b/docs/source/CHAPTER_CellLib.rst @@ -195,7 +195,7 @@ from ``\S`` is set the output is undefined. Cells of this type are used to model by an optimization). The ``$tribuf`` cell is used to implement tristate logic. Cells of this type -have a ``\B`` parameter and inputs ``\A`` and ``\EN`` and an output ``\Y``. The +have a ``\WIDTH`` parameter and inputs ``\A`` and ``\EN`` and an output ``\Y``. The ``\A`` input and ``\Y`` output are ``\WIDTH`` bits wide, and the ``\EN`` input is one bit wide. When ``\EN`` is 0, the output is not driven. When ``\EN`` is 1, the value from ``\A`` input is sent to the ``\Y`` output. Therefore, the diff --git a/examples/cxx-api/scopeinfo_example.cc b/examples/cxx-api/scopeinfo_example.cc new file mode 100644 index 00000000000..f163dff9eee --- /dev/null +++ b/examples/cxx-api/scopeinfo_example.cc @@ -0,0 +1,144 @@ +/* + * 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. + * + */ + +// build: yosys-config --build scopeinfo_example.so scopeinfo_example.cc +// use: yosys -m scopeinfo_example.so + +#include "backends/rtlil/rtlil_backend.h" +#include "kernel/scopeinfo.h" +#include "kernel/yosys.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct ScopeinfoExamplePass : public Pass { + ScopeinfoExamplePass() : Pass("scopeinfo_example", "dump scopeinfo") {} + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" scopeinfo_example [options] [selection]\n"); + log("\n"); + } + + void execute(std::vector args, RTLIL::Design *design) override + { + log_header(design, "Executing SCOPEINFO_EXAMPLE pass.\n"); + + bool do_wires = false; + bool do_common = false; + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-wires") { + do_wires = true; + continue; + } + if (args[argidx] == "-common") { + do_common = true; + continue; + } + break; + } + extra_args(args, argidx, design); + + + if (do_wires) { + for (auto module : design->selected_modules()) { + log("Source hierarchy for all selected wires within %s:\n", log_id(module)); + ModuleHdlnameIndex index(module); + + index.index_scopeinfo_cells(); + + for (auto wire : module->selected_wires()) { + if (!wire->name.isPublic()) + continue; + + auto wire_scope = index.containing_scope(wire); + + if (!wire_scope.first.valid()) { + log_warning("Couldn't find containing scope for %s in index\n", log_id(wire)); + continue; + } + + log("%s %s\n", wire_scope.first.path_str().c_str(), log_id(wire_scope.second)); + for (auto src : index.sources(wire)) + log(" - %s\n", src.c_str()); + } + } + } + + if (do_common) { + for (auto module : design->selected_modules()) { + std::vector wires = module->selected_wires(); + + // Shuffle wires so this example produces more interesting outputs + std::sort(wires.begin(), wires.end(), [](Wire *a, Wire *b) { + return mkhash_xorshift(a->name.hash() * 0x2c9277b5) < mkhash_xorshift(b->name.hash() * 0x2c9277b5); + }); + + ModuleHdlnameIndex index(module); + + index.index_scopeinfo_cells(); + + for (auto wire_i = wires.begin(), wire_end = wires.end(); wire_i != wire_end; ++wire_i) { + if (!(*wire_i)->name.isPublic()) + continue; + + std::pair scope_i = index.containing_scope(*wire_i); + if (!scope_i.first.valid()) + continue; + + int limit = 0; + + for (auto wire_j = wire_i + 1; wire_j != wire_end; ++wire_j) { + if (!(*wire_j)->name.isPublic()) + continue; + + std::pair scope_j = index.containing_scope(*wire_j); + if (!scope_j.first.valid()) + continue; + + // Skip wires in the same hierarchy level + if (scope_i.first == scope_j.first) + continue; + + + ModuleHdlnameIndex::Cursor common = scope_i.first.common_ancestor(scope_j.first); + + // Try to show at least some non-root common ancestors + if (common.is_root() && limit > 5) + continue; + + log("common_ancestor(%s %s%s%s, %s %s%s%s) = %s %s\n", + log_id(module), scope_i.first.path_str().c_str(), scope_i.first.is_root() ? "" : " ", log_id(scope_i.second), + log_id(module), scope_j.first.path_str().c_str(), scope_j.first.is_root() ? "" : " ", log_id(scope_j.second), + log_id(module), common.path_str().c_str() + ); + + if (++limit == 10) + break; + } + } + } + } + } +} ScopeinfoExamplePass; + +PRIVATE_NAMESPACE_END diff --git a/frontends/ast/ast.cc b/frontends/ast/ast.cc index fe075b27062..996f6715d05 100644 --- a/frontends/ast/ast.cc +++ b/frontends/ast/ast.cc @@ -224,6 +224,7 @@ AstNode::AstNode(AstNodeType type, AstNode *child1, AstNode *child2, AstNode *ch port_id = 0; range_left = -1; range_right = 0; + unpacked_dimensions = 0; integer = 0; realvalue = 0; id2ast = NULL; @@ -349,17 +350,15 @@ void AstNode::dumpAst(FILE *f, std::string indent) const fprintf(f, " int=%u", (int)integer); if (realvalue != 0) fprintf(f, " real=%e", realvalue); - if (!multirange_dimensions.empty()) { - fprintf(f, " multirange=["); - for (int v : multirange_dimensions) - fprintf(f, " %d", v); - fprintf(f, " ]"); - } - if (!multirange_swapped.empty()) { - fprintf(f, " multirange_swapped=["); - for (bool v : multirange_swapped) - fprintf(f, " %d", v); - fprintf(f, " ]"); + if (!dimensions.empty()) { + fprintf(f, " dimensions="); + for (auto &dim : dimensions) { + int left = dim.range_right + dim.range_width - 1; + int right = dim.range_right; + if (dim.range_swapped) + std::swap(left, right); + fprintf(f, "[%d:%d]", left, right); + } } if (is_enum) { fprintf(f, " type=enum"); @@ -489,6 +488,20 @@ void AstNode::dumpVlog(FILE *f, std::string indent) const fprintf(f, ";\n"); break; + if (0) { case AST_MEMRD: txt = "@memrd@"; } + if (0) { case AST_MEMINIT: txt = "@meminit@"; } + if (0) { case AST_MEMWR: txt = "@memwr@"; } + fprintf(f, "%s%s", indent.c_str(), txt.c_str()); + for (auto child : children) { + fprintf(f, first ? "(" : ", "); + child->dumpVlog(f, ""); + first = false; + } + fprintf(f, ")"); + if (type != AST_MEMRD) + fprintf(f, ";\n"); + break; + case AST_RANGE: if (range_valid) { if (range_swapped) @@ -505,6 +518,11 @@ void AstNode::dumpVlog(FILE *f, std::string indent) const } break; + case AST_MULTIRANGE: + for (auto child : children) + child->dumpVlog(f, ""); + break; + case AST_ALWAYS: fprintf(f, "%s" "always @", indent.c_str()); for (auto child : children) { @@ -542,7 +560,7 @@ void AstNode::dumpVlog(FILE *f, std::string indent) const case AST_IDENTIFIER: { - AST::AstNode *member_node = AST::get_struct_member(this); + AstNode *member_node = get_struct_member(); if (member_node) fprintf(f, "%s[%d:%d]", id2vl(str).c_str(), member_node->range_left, member_node->range_right); else @@ -552,6 +570,12 @@ void AstNode::dumpVlog(FILE *f, std::string indent) const child->dumpVlog(f, ""); break; + case AST_STRUCT: + case AST_UNION: + case AST_STRUCT_ITEM: + fprintf(f, "%s", id2vl(str).c_str()); + break; + case AST_CONSTANT: if (!str.empty()) fprintf(f, "\"%s\"", str.c_str()); @@ -1796,7 +1820,7 @@ std::string AstModule::derive_common(RTLIL::Design *design, const dictclone(); if (!new_ast->attributes.count(ID::hdlname)) - new_ast->set_attribute(ID::hdlname, AstNode::mkconst_str(stripped_name)); + new_ast->set_attribute(ID::hdlname, AstNode::mkconst_str(stripped_name.substr(1))); para_counter = 0; for (auto child : new_ast->children) { diff --git a/frontends/ast/ast.h b/frontends/ast/ast.h index c447461312a..f05b568be22 100644 --- a/frontends/ast/ast.h +++ b/frontends/ast/ast.h @@ -202,9 +202,17 @@ namespace AST // set for IDs typed to an enumeration, not used bool is_enum; - // if this is a multirange memory then this vector contains offset and length of each dimension - std::vector multirange_dimensions; - std::vector multirange_swapped; // true if range is swapped + // Declared range for array dimension. + struct dimension_t { + int range_right; // lsb in [msb:lsb] + int range_width; // msb - lsb + 1 + bool range_swapped; // if the declared msb < lsb, msb and lsb above are swapped + }; + // Packed and unpacked dimensions for arrays. + // Unpacked dimensions go first, to follow the order of indexing. + std::vector dimensions; + // Number of unpacked dimensions. + int unpacked_dimensions; // this is set by simplify and used during RTLIL generation AstNode *id2ast; @@ -371,6 +379,10 @@ namespace AST // localized fixups after modifying children/attributes of a particular node void fixup_hierarchy_flags(bool force_descend = false); + // helpers for indexing + AstNode *make_index_range(AstNode *node, bool unpacked_range = false); + AstNode *get_struct_member() const; + // helper to print errors from simplify/genrtlil code [[noreturn]] void input_error(const char *format, ...) const YS_ATTRIBUTE(format(printf, 2, 3)); }; @@ -416,10 +428,6 @@ namespace AST // Helper for setting the src attribute. void set_src_attr(RTLIL::AttrObject *obj, const AstNode *ast); - // struct helper exposed from simplify for genrtlil - AstNode *make_struct_member_range(AstNode *node, AstNode *member_node); - AstNode *get_struct_member(const AstNode *node); - // generate standard $paramod... derived module name; parameters should be // in the order they are declared in the instantiated module std::string derived_module_name(std::string stripped_name, const std::vector> ¶meters); diff --git a/frontends/ast/genrtlil.cc b/frontends/ast/genrtlil.cc index 03697ebf314..fe67f00c692 100644 --- a/frontends/ast/genrtlil.cc +++ b/frontends/ast/genrtlil.cc @@ -1045,7 +1045,7 @@ void AstNode::detectSignWidthWorker(int &width_hint, bool &sign_hint, bool *foun if (children.size() > 1) range = children[1]; } else if (id_ast->type == AST_STRUCT_ITEM || id_ast->type == AST_STRUCT || id_ast->type == AST_UNION) { - AstNode *tmp_range = make_struct_member_range(this, id_ast); + AstNode *tmp_range = make_index_range(id_ast); this_width = tmp_range->range_left - tmp_range->range_right + 1; delete tmp_range; } else @@ -1584,7 +1584,7 @@ RTLIL::SigSpec AstNode::genRTLIL(int width_hint, bool sign_hint) chunk.width = wire->width; chunk.offset = 0; - if ((member_node = get_struct_member(this))) { + if ((member_node = get_struct_member())) { // Clamp wire chunk to range of member within struct/union. chunk.width = member_node->range_left - member_node->range_right + 1; chunk.offset = member_node->range_right; diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc index 8e0de299470..43a4e03a29d 100644 --- a/frontends/ast/simplify.cc +++ b/frontends/ast/simplify.cc @@ -259,33 +259,22 @@ static int range_width(AstNode *node, AstNode *rnode) { log_assert(rnode->type==AST_RANGE); if (!rnode->range_valid) { - node->input_error("Size must be constant in packed struct/union member %s\n", node->str.c_str()); - + node->input_error("Non-constant range in declaration of %s\n", node->str.c_str()); } // note: range swapping has already been checked for return rnode->range_left - rnode->range_right + 1; } -[[noreturn]] static void struct_array_packing_error(AstNode *node) -{ - node->input_error("Unpacked array in packed struct/union member %s\n", node->str.c_str()); -} - -static void save_struct_range_dimensions(AstNode *node, AstNode *rnode) -{ - node->multirange_dimensions.push_back(rnode->range_right); - node->multirange_dimensions.push_back(range_width(node, rnode)); - node->multirange_swapped.push_back(rnode->range_swapped); -} - -static int get_struct_range_offset(AstNode *node, int dimension) +static int add_dimension(AstNode *node, AstNode *rnode) { - return node->multirange_dimensions[2*dimension]; + int width = range_width(node, rnode); + node->dimensions.push_back({ rnode->range_right, width, rnode->range_swapped }); + return width; } -static int get_struct_range_width(AstNode *node, int dimension) +[[noreturn]] static void struct_array_packing_error(AstNode *node) { - return node->multirange_dimensions[2*dimension + 1]; + node->input_error("Unpacked array in packed struct/union member %s\n", node->str.c_str()); } static int size_packed_struct(AstNode *snode, int base_offset) @@ -303,10 +292,6 @@ static int size_packed_struct(AstNode *snode, int base_offset) if (node->type == AST_STRUCT || node->type == AST_UNION) { // embedded struct or union width = size_packed_struct(node, base_offset + offset); - // set range of struct - node->range_right = base_offset + offset; - node->range_left = base_offset + offset + width - 1; - node->range_valid = true; } else { log_assert(node->type == AST_STRUCT_ITEM); @@ -318,18 +303,16 @@ static int size_packed_struct(AstNode *snode, int base_offset) // and integer data types are allowed in packed structs / unions in SystemVerilog. if (node->children[1]->type == AST_RANGE) { // Unpacked array, e.g. bit [63:0] a [0:3] + // Pretend it's declared as a packed array, e.g. bit [0:3][63:0] a auto rnode = node->children[1]; if (rnode->children.size() == 1) { // C-style array size, e.g. bit [63:0] a [4] - node->multirange_dimensions.push_back(0); - node->multirange_dimensions.push_back(rnode->range_left); - node->multirange_swapped.push_back(true); + node->dimensions.push_back({ 0, rnode->range_left, true }); width *= rnode->range_left; } else { - save_struct_range_dimensions(node, rnode); - width *= range_width(node, rnode); + width *= add_dimension(node, rnode); } - save_struct_range_dimensions(node, node->children[0]); + add_dimension(node, node->children[0]); } else { // The Yosys extension for unpacked arrays in packed structs / unions @@ -338,7 +321,7 @@ static int size_packed_struct(AstNode *snode, int base_offset) } } else { // Vector - save_struct_range_dimensions(node, node->children[0]); + add_dimension(node, node->children[0]); } // range nodes are now redundant for (AstNode *child : node->children) @@ -354,8 +337,7 @@ static int size_packed_struct(AstNode *snode, int base_offset) } width = 1; for (auto rnode : node->children[0]->children) { - save_struct_range_dimensions(node, rnode); - width *= range_width(node, rnode); + width *= add_dimension(node, rnode); } // range nodes are now redundant for (AstNode *child : node->children) @@ -365,6 +347,7 @@ static int size_packed_struct(AstNode *snode, int base_offset) else if (node->range_left < 0) { // 1 bit signal: bit, logic or reg width = 1; + node->dimensions.push_back({ 0, width, false }); } else { // already resolved and compacted @@ -395,12 +378,15 @@ static int size_packed_struct(AstNode *snode, int base_offset) offset += width; } } - return (is_union ? packed_width : offset); -} -[[noreturn]] static void struct_op_error(AstNode *node) -{ - node->input_error("Unsupported operation for struct/union member %s\n", node->str.c_str()+1); + int width = is_union ? packed_width : offset; + + snode->range_right = base_offset; + snode->range_left = base_offset + width - 1; + snode->range_valid = true; + snode->dimensions.push_back({ 0, width, false }); + + return width; } static AstNode *node_int(int ival) @@ -413,113 +399,123 @@ static AstNode *multiply_by_const(AstNode *expr_node, int stride) return new AstNode(AST_MUL, expr_node, node_int(stride)); } -static AstNode *normalize_struct_index(AstNode *expr, AstNode *member_node, int dimension) +static AstNode *normalize_index(AstNode *expr, AstNode *decl_node, int dimension) { expr = expr->clone(); - int offset = get_struct_range_offset(member_node, dimension); + int offset = decl_node->dimensions[dimension].range_right; if (offset) { expr = new AstNode(AST_SUB, expr, node_int(offset)); } - if (member_node->multirange_swapped[dimension]) { - // The dimension has swapped range; swap index into the struct accordingly. - int msb = get_struct_range_width(member_node, dimension) - 1; - expr = new AstNode(AST_SUB, node_int(msb), expr); + // Packed dimensions are normally indexed by lsb, while unpacked dimensions are normally indexed by msb. + if ((dimension < decl_node->unpacked_dimensions) ^ decl_node->dimensions[dimension].range_swapped) { + // Swap the index if the dimension is declared the "wrong" way. + int left = decl_node->dimensions[dimension].range_width - 1; + expr = new AstNode(AST_SUB, node_int(left), expr); } return expr; } -static AstNode *struct_index_lsb_offset(AstNode *lsb_offset, AstNode *rnode, AstNode *member_node, int dimension, int &stride) +static AstNode *index_offset(AstNode *offset, AstNode *rnode, AstNode *decl_node, int dimension, int &stride) { - stride /= get_struct_range_width(member_node, dimension); - auto right = normalize_struct_index(rnode->children.back(), member_node, dimension); - auto offset = stride > 1 ? multiply_by_const(right, stride) : right; - return lsb_offset ? new AstNode(AST_ADD, lsb_offset, offset) : offset; + stride /= decl_node->dimensions[dimension].range_width; + auto right = normalize_index(rnode->children.back(), decl_node, dimension); + auto add_offset = stride > 1 ? multiply_by_const(right, stride) : right; + return offset ? new AstNode(AST_ADD, offset, add_offset) : add_offset; } -static AstNode *struct_index_msb_offset(AstNode *lsb_offset, AstNode *rnode, AstNode *member_node, int dimension, int stride) +static AstNode *index_msb_offset(AstNode *lsb_offset, AstNode *rnode, AstNode *decl_node, int dimension, int stride) { log_assert(rnode->children.size() <= 2); // Offset to add to LSB - AstNode *offset; + AstNode *add_offset; if (rnode->children.size() == 1) { // Index, e.g. s.a[i] - offset = node_int(stride - 1); + add_offset = node_int(stride - 1); } else { // rnode->children.size() == 2 // Slice, e.g. s.a[i:j] - auto left = normalize_struct_index(rnode->children[0], member_node, dimension); - auto right = normalize_struct_index(rnode->children[1], member_node, dimension); - offset = new AstNode(AST_SUB, left, right); + auto left = normalize_index(rnode->children[0], decl_node, dimension); + auto right = normalize_index(rnode->children[1], decl_node, dimension); + add_offset = new AstNode(AST_SUB, left, right); if (stride > 1) { // offset = (msb - lsb + 1)*stride - 1 - auto slice_width = new AstNode(AST_ADD, offset, node_int(1)); - offset = new AstNode(AST_SUB, multiply_by_const(slice_width, stride), node_int(1)); + auto slice_width = new AstNode(AST_ADD, add_offset, node_int(1)); + add_offset = new AstNode(AST_SUB, multiply_by_const(slice_width, stride), node_int(1)); } } - return new AstNode(AST_ADD, lsb_offset, offset); + return new AstNode(AST_ADD, lsb_offset, add_offset); } -AstNode *AST::make_struct_member_range(AstNode *node, AstNode *member_node) +AstNode *AstNode::make_index_range(AstNode *decl_node, bool unpacked_range) { // Work out the range in the packed array that corresponds to a struct member // taking into account any range operations applicable to the current node // such as array indexing or slicing - int range_left = member_node->range_left; - int range_right = member_node->range_right; - if (node->children.empty()) { + if (children.empty()) { // no range operations apply, return the whole width - return make_range(range_left - range_right, 0); + return make_range(decl_node->range_left - decl_node->range_right, 0); } - if (node->children.size() != 1) { - struct_op_error(node); - } + log_assert(children.size() == 1); // Range operations - auto rnode = node->children[0]; - AstNode *lsb_offset = NULL; - int stride = range_left - range_right + 1; - size_t i = 0; + AstNode *rnode = children[0]; + AstNode *offset = NULL; + int dim = unpacked_range ? 0 : decl_node->unpacked_dimensions; + int max_dim = unpacked_range ? decl_node->unpacked_dimensions : GetSize(decl_node->dimensions); + + int stride = 1; + for (int i = dim; i < max_dim; i++) { + stride *= decl_node->dimensions[i].range_width; + } // Calculate LSB offset for the final index / slice if (rnode->type == AST_RANGE) { - lsb_offset = struct_index_lsb_offset(lsb_offset, rnode, member_node, i, stride); + offset = index_offset(offset, rnode, decl_node, dim, stride); } else if (rnode->type == AST_MULTIRANGE) { // Add offset for each dimension - auto mrnode = rnode; - for (i = 0; i < mrnode->children.size(); i++) { - rnode = mrnode->children[i]; - lsb_offset = struct_index_lsb_offset(lsb_offset, rnode, member_node, i, stride); + AstNode *mrnode = rnode; + int stop_dim = std::min(GetSize(mrnode->children), max_dim); + for (; dim < stop_dim; dim++) { + rnode = mrnode->children[dim]; + offset = index_offset(offset, rnode, decl_node, dim, stride); } - i--; // Step back to the final index / slice + dim--; // Step back to the final index / slice } else { - struct_op_error(node); + input_error("Unsupported range operation for %s\n", str.c_str()); + } + + AstNode *index_range = new AstNode(AST_RANGE); + + if (!unpacked_range && (stride > 1 || GetSize(rnode->children) == 2)) { + // Calculate MSB offset for the final index / slice of packed dimensions. + AstNode *msb_offset = index_msb_offset(offset->clone(), rnode, decl_node, dim, stride); + index_range->children.push_back(msb_offset); } - // Calculate MSB offset for the final index / slice - auto msb_offset = struct_index_msb_offset(lsb_offset->clone(), rnode, member_node, i, stride); + index_range->children.push_back(offset); - return new AstNode(AST_RANGE, msb_offset, lsb_offset); + return index_range; } -AstNode *AST::get_struct_member(const AstNode *node) +AstNode *AstNode::get_struct_member() const { - AST::AstNode *member_node; - if (node->attributes.count(ID::wiretype) && (member_node = node->attributes.at(ID::wiretype)) && + AstNode *member_node; + if (attributes.count(ID::wiretype) && (member_node = attributes.at(ID::wiretype)) && (member_node->type == AST_STRUCT_ITEM || member_node->type == AST_STRUCT || member_node->type == AST_UNION)) { return member_node; } - return NULL; + return nullptr; } static void add_members_to_scope(AstNode *snode, std::string name) @@ -537,22 +533,10 @@ static void add_members_to_scope(AstNode *snode, std::string name) } } -static int get_max_offset(AstNode *node) -{ - // get the width from the MS member in the struct - // as members are laid out from left to right in the packed wire - log_assert(node->type==AST_STRUCT || node->type==AST_UNION); - while (node->type != AST_STRUCT_ITEM) { - node = node->children[0]; - } - return node->range_left; -} - static AstNode *make_packed_struct(AstNode *template_node, std::string &name, decltype(AstNode::attributes) &attributes) { // create a wire for the packed struct - int offset = get_max_offset(template_node); - auto wnode = new AstNode(AST_WIRE, make_range(offset, 0)); + auto wnode = new AstNode(AST_WIRE, make_range(template_node->range_left, 0)); wnode->str = name; wnode->is_logic = true; wnode->range_valid = true; @@ -560,6 +544,8 @@ static AstNode *make_packed_struct(AstNode *template_node, std::string &name, de for (auto &pair : attributes) { wnode->set_attribute(pair.first, pair.second->clone()); } + // resolve packed dimension + while (wnode->simplify(true, 1, -1, false)) {} // 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 @@ -567,6 +553,22 @@ static AstNode *make_packed_struct(AstNode *template_node, std::string &name, de return wnode; } +static void prepend_ranges(AstNode *&range, AstNode *range_add) +{ + // Convert range to multirange. + if (range->type == AST_RANGE) + range = new AstNode(AST_MULTIRANGE, range); + + // Add range or ranges. + if (range_add->type == AST_RANGE) + range->children.insert(range->children.begin(), range_add->clone()); + else { + int i = 0; + for (auto child : range_add->children) + range->children.insert(range->children.begin() + i++, child->clone()); + } +} + // check if a node or its children contains an assignment to the given variable static bool node_contains_assignment_to(const AstNode* node, const AstNode* var) { @@ -1436,57 +1438,16 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin case AST_STRUCT_ITEM: if (is_custom_type) { - log_assert(children.size() == 1); + log_assert(children.size() >= 1); log_assert(children[0]->type == AST_WIRETYPE); - auto type_name = children[0]->str; - if (!current_scope.count(type_name)) { - log_file_error(filename, location.first_line, "Unknown identifier `%s' used as type name\n", type_name.c_str()); - } - AstNode *resolved_type_node = current_scope.at(type_name); - if (resolved_type_node->type != AST_TYPEDEF) - log_file_error(filename, location.first_line, "`%s' does not name a type\n", type_name.c_str()); - log_assert(resolved_type_node->children.size() == 1); - AstNode *template_node = resolved_type_node->children[0]; - - // Ensure typedef itself is fully simplified - while (template_node->simplify(const_fold, stage, width_hint, sign_hint)) {}; - // Remove type reference - delete children[0]; - children.pop_back(); - - switch (template_node->type) { - case AST_WIRE: + // Pretend it's just a wire in order to resolve the type. + type = AST_WIRE; + while (is_custom_type && simplify(const_fold, stage, width_hint, sign_hint)) {}; + if (type == AST_WIRE) type = AST_STRUCT_ITEM; - break; - case AST_STRUCT: - case AST_UNION: - type = template_node->type; - break; - default: - log_file_error(filename, location.first_line, "Invalid type for struct member: %s", type2str(template_node->type).c_str()); - } - - is_reg = template_node->is_reg; - is_logic = template_node->is_logic; - is_signed = template_node->is_signed; - is_string = template_node->is_string; - is_custom_type = template_node->is_custom_type; - - range_valid = template_node->range_valid; - range_swapped = template_node->range_swapped; - range_left = template_node->range_left; - range_right = template_node->range_right; - - set_attribute(ID::wiretype, mkconst_str(resolved_type_node->str)); - - // Copy clones of children from template - for (auto template_child : template_node->children) { - children.push_back(template_child->clone()); - } did_something = true; - } log_assert(!is_custom_type); break; @@ -1892,8 +1853,10 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin log_assert(resolved_type_node->children.size() == 1); AstNode *template_node = resolved_type_node->children[0]; - // Ensure typedef itself is fully simplified - while (template_node->simplify(const_fold, stage, width_hint, sign_hint)) {}; + // Resolve the typedef from the bottom up, recursing within the current + // block of code. Defer further simplification until the complete type is + // resolved. + while (template_node->is_custom_type && 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 @@ -1906,89 +1869,70 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin goto apply_newNode; } - // Remove type reference - delete children[0]; - children.erase(children.begin()); - - if (type == AST_WIRE) - type = template_node->type; - is_reg = template_node->is_reg; - is_logic = template_node->is_logic; - is_signed = template_node->is_signed; - is_string = template_node->is_string; - is_custom_type = template_node->is_custom_type; + // Prepare replacement node. + newNode = template_node->clone(); + newNode->str = str; + newNode->set_attribute(ID::wiretype, mkconst_str(resolved_type_node->str)); + newNode->is_input = is_input; + newNode->is_output = is_output; + newNode->is_wand = is_wand; + newNode->is_wor = is_wor; + for (auto &pair : attributes) + newNode->set_attribute(pair.first, pair.second->clone()); - range_valid = template_node->range_valid; - range_swapped = template_node->range_swapped; - range_left = template_node->range_left; - range_right = template_node->range_right; + // if an enum then add attributes to support simulator tracing + newNode->annotateTypedEnums(template_node); - set_attribute(ID::wiretype, mkconst_str(resolved_type_node->str)); + bool add_packed_dimensions = (type == AST_WIRE && GetSize(children) > 1) || (type == AST_MEMORY && GetSize(children) > 2); - // if an enum then add attributes to support simulator tracing - annotateTypedEnums(template_node); + // Cannot add packed dimensions if unpacked dimensions are already specified. + if (add_packed_dimensions && newNode->type == AST_MEMORY) + input_error("Cannot extend unpacked type `%s' with packed dimensions\n", type_name.c_str()); - // Insert clones children from template at beginning - for (int i = 0; i < GetSize(template_node->children); i++) - children.insert(children.begin() + i, template_node->children[i]->clone()); + // Add packed dimensions. + if (add_packed_dimensions) { + AstNode *packed = children[1]; + if (newNode->children.empty()) + newNode->children.insert(newNode->children.begin(), packed->clone()); + else + prepend_ranges(newNode->children[0], packed); + } - if (type == AST_MEMORY && GetSize(children) == 1) { - // Single-bit memories must have [0:0] range - AstNode *rng = make_range(0, 0); - children.insert(children.begin(), rng); + // Add unpacked dimensions. + if (type == AST_MEMORY) { + AstNode *unpacked = children.back(); + if (GetSize(newNode->children) < 2) + newNode->children.push_back(unpacked->clone()); + else + prepend_ranges(newNode->children[1], unpacked); + newNode->type = type; } - fixup_hierarchy_flags(); - did_something = true; + + // Prepare to generate dimensions metadata for the resolved type. + newNode->dimensions.clear(); + newNode->unpacked_dimensions = 0; + + goto apply_newNode; } - log_assert(!is_custom_type); } // resolve types of parameters if (type == AST_LOCALPARAM || type == AST_PARAMETER) { if (is_custom_type) { - log_assert(children.size() == 2); + log_assert(children.size() >= 2); log_assert(children[1]->type == AST_WIRETYPE); - auto type_name = children[1]->str; - if (!current_scope.count(type_name)) { - input_error("Unknown identifier `%s' used as type name\n", type_name.c_str()); - } - AstNode *resolved_type_node = current_scope.at(type_name); - if (resolved_type_node->type != AST_TYPEDEF) - input_error("`%s' does not name a type\n", type_name.c_str()); - log_assert(resolved_type_node->children.size() == 1); - AstNode *template_node = resolved_type_node->children[0]; - - // Ensure typedef itself is fully simplified - while (template_node->simplify(const_fold, 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->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 - delete children[1]; - children.pop_back(); - newNode->children.insert(newNode->children.begin(), children[0]->clone()); - goto apply_newNode; - } - delete children[1]; - children.pop_back(); + // Pretend it's just a wire in order to resolve the type in the code block above. + AstNodeType param_type = type; + type = AST_WIRE; + AstNode *expr = children[0]; + children.erase(children.begin()); + while (is_custom_type && simplify(const_fold, stage, width_hint, sign_hint)) {}; + type = param_type; + children.insert(children.begin(), expr); - if (template_node->type == AST_MEMORY) + if (children[1]->type == AST_MEMORY) input_error("unpacked array type `%s' cannot be used for a parameter\n", children[1]->str.c_str()); - is_signed = template_node->is_signed; - is_string = template_node->is_string; - is_custom_type = template_node->is_custom_type; - - range_valid = template_node->range_valid; - range_swapped = template_node->range_swapped; - range_left = template_node->range_left; - range_right = template_node->range_right; - 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; } @@ -2046,9 +1990,7 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin if (old_range_valid != range_valid) did_something = true; if (range_valid && range_right > range_left) { - int tmp = range_right; - range_right = range_left; - range_left = tmp; + std::swap(range_left, range_right); range_swapped = true; } } @@ -2093,58 +2035,83 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin } } - // resolve multiranges on memory decl - if (type == AST_MEMORY && children.size() > 1 && children[1]->type == AST_MULTIRANGE) - { - int total_size = 1; - multirange_dimensions.clear(); - multirange_swapped.clear(); - for (auto range : children[1]->children) { - if (!range->range_valid) - input_error("Non-constant range on memory decl.\n"); - multirange_dimensions.push_back(min(range->range_left, range->range_right)); - multirange_dimensions.push_back(max(range->range_left, range->range_right) - min(range->range_left, range->range_right) + 1); - multirange_swapped.push_back(range->range_swapped); - total_size *= multirange_dimensions.back(); + // Resolve packed and unpacked ranges in declarations. + if ((type == AST_WIRE || type == AST_MEMORY) && dimensions.empty()) { + if (!children.empty()) { + // Unpacked ranges first, then packed ranges. + for (int i = std::min(GetSize(children), 2) - 1; i >= 0; i--) { + if (children[i]->type == AST_MULTIRANGE) { + int width = 1; + for (auto range : children[i]->children) { + width *= add_dimension(this, range); + if (i) unpacked_dimensions++; + } + delete children[i]; + int left = width - 1, right = 0; + if (i) + std::swap(left, right); + children[i] = new AstNode(AST_RANGE, mkconst_int(left, true), mkconst_int(right, true)); + fixup_hierarchy_flags(); + did_something = true; + } else if (children[i]->type == AST_RANGE) { + add_dimension(this, children[i]); + if (i) unpacked_dimensions++; + } + } + } else { + // 1 bit signal: bit, logic or reg + dimensions.push_back({ 0, 1, false }); } - 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; } - // resolve multiranges on memory access - if (type == AST_IDENTIFIER && id2ast && id2ast->type == AST_MEMORY && children.size() > 0 && children[0]->type == AST_MULTIRANGE) + // Resolve multidimensional array access. + if (type == AST_IDENTIFIER && !basic_prep && id2ast && (id2ast->type == AST_WIRE || id2ast->type == AST_MEMORY) && + children.size() > 0 && (children[0]->type == AST_RANGE || children[0]->type == AST_MULTIRANGE)) { - AstNode *index_expr = nullptr; - - integer = children[0]->children.size(); // save original number of dimensions for $size() etc. - for (int i = 0; 2*i < GetSize(id2ast->multirange_dimensions); i++) - { - if (GetSize(children[0]->children) <= i) - input_error("Insufficient number of array indices for %s.\n", log_id(str)); - - AstNode *new_index_expr = children[0]->children[i]->children.at(0)->clone(); - - if (id2ast->multirange_dimensions[2*i]) - new_index_expr = new AstNode(AST_SUB, new_index_expr, AstNode::mkconst_int(id2ast->multirange_dimensions[2*i], true)); + int dims_sel = children[0]->type == AST_MULTIRANGE ? children[0]->children.size() : 1; + // Save original number of dimensions for $size() etc. + integer = dims_sel; + + // Split access into unpacked and packed parts. + AstNode *unpacked_range = nullptr; + AstNode *packed_range = nullptr; + + if (id2ast->unpacked_dimensions) { + if (id2ast->unpacked_dimensions > 1) { + // Flattened range for access to unpacked dimensions. + unpacked_range = make_index_range(id2ast, true); + } else { + // Index into one-dimensional unpacked part; unlink simple range node. + AstNode *&range = children[0]->type == AST_MULTIRANGE ? children[0]->children[0] : children[0]; + unpacked_range = range; + range = nullptr; + } + } - if (i == 0) - index_expr = new_index_expr; - else - index_expr = new AstNode(AST_ADD, new AstNode(AST_MUL, index_expr, AstNode::mkconst_int(id2ast->multirange_dimensions[2*i+1], true)), new_index_expr); + if (dims_sel > id2ast->unpacked_dimensions) { + if (GetSize(id2ast->dimensions) - id2ast->unpacked_dimensions > 1) { + // Flattened range for access to packed dimensions. + packed_range = make_index_range(id2ast, false); + } else { + // Index into one-dimensional packed part; unlink simple range node. + AstNode *&range = children[0]->type == AST_MULTIRANGE ? children[0]->children[dims_sel - 1] : children[0]; + packed_range = range; + range = nullptr; + } } - for (int i = GetSize(id2ast->multirange_dimensions)/2; i < GetSize(children[0]->children); i++) - children.push_back(children[0]->children[i]->clone()); + for (auto &it : children) + delete it; + children.clear(); - delete children[0]; - if (index_expr == nullptr) - children.erase(children.begin()); - else - children[0] = new AstNode(AST_RANGE, index_expr); + if (unpacked_range) + children.push_back(unpacked_range); + + if (packed_range) + children.push_back(packed_range); fixup_hierarchy_flags(); + basic_prep = true; did_something = true; } @@ -2210,12 +2177,12 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin if (found_sname) { // structure member, rewrite this node to reference the packed struct wire - auto range = make_struct_member_range(this, item_node); + auto range = make_index_range(item_node); newNode = new AstNode(AST_IDENTIFIER, range); newNode->str = sname; // save type and original number of dimensions for $size() etc. newNode->set_attribute(ID::wiretype, item_node->clone()); - if (!item_node->multirange_dimensions.empty() && children.size() > 0) { + if (!item_node->dimensions.empty() && children.size() > 0) { if (children[0]->type == AST_RANGE) newNode->integer = 1; else if (children[0]->type == AST_MULTIRANGE) @@ -2835,7 +2802,7 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin if (!children[0]->id2ast->range_valid) goto skip_dynamic_range_lvalue_expansion; - AST::AstNode *member_node = get_struct_member(children[0]); + AST::AstNode *member_node = children[0]->get_struct_member(); int wire_width = member_node ? member_node->range_left - member_node->range_right + 1 : children[0]->id2ast->range_left - children[0]->id2ast->range_right + 1; @@ -2881,7 +2848,7 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin int dims = children[0]->integer; stride = wire_width; for (int dim = 0; dim < dims; dim++) { - stride /= get_struct_range_width(member_node, dim); + stride /= member_node->dimensions[dim].range_width; } bitno_div = stride; } else { @@ -3042,6 +3009,8 @@ skip_dynamic_range_lvalue_expansion:; // found right-hand side identifier for memory -> replace with memory read port if (stage > 1 && type == AST_IDENTIFIER && id2ast != NULL && id2ast->type == AST_MEMORY && !in_lvalue && children.size() == 1 && children[0]->type == AST_RANGE && children[0]->children.size() == 1) { + if (integer < (unsigned)id2ast->unpacked_dimensions) + input_error("Insufficient number of array indices for %s.\n", log_id(str)); newNode = new AstNode(AST_MEMRD, children[0]->children[0]->clone()); newNode->str = str; newNode->id2ast = id2ast; @@ -3100,6 +3069,9 @@ skip_dynamic_range_lvalue_expansion:; children[0]->id2ast->children[0]->range_valid && children[0]->id2ast->children[1]->range_valid && (children[0]->children.size() == 1 || children[0]->children.size() == 2) && children[0]->children[0]->type == AST_RANGE) { + if (children[0]->integer < (unsigned)children[0]->id2ast->unpacked_dimensions) + input_error("Insufficient number of array indices for %s.\n", log_id(str)); + std::stringstream sstr; sstr << "$memwr$" << children[0]->str << "$" << RTLIL::encode_filename(filename) << ":" << location.first_line << "$" << (autoidx++); std::string id_addr = sstr.str() + "_ADDR", id_data = sstr.str() + "_DATA", id_en = sstr.str() + "_EN"; @@ -3471,10 +3443,11 @@ skip_dynamic_range_lvalue_expansion:; goto apply_newNode; } - if (str == "\\$size" || str == "\\$bits" || str == "\\$high" || str == "\\$low" || str == "\\$left" || str == "\\$right") + if (str == "\\$dimensions" || str == "\\$unpacked_dimensions" || + str == "\\$increment" || str == "\\$size" || str == "\\$bits" || str == "\\$high" || str == "\\$low" || str == "\\$left" || str == "\\$right") { int dim = 1; - if (str == "\\$bits") { + if (str == "\\$dimensions" || str == "\\$unpacked_dimensions" || str == "\\$bits") { if (children.size() != 1) input_error("System function %s got %d arguments, expected 1.\n", RTLIL::unescape_id(str).c_str(), int(children.size())); @@ -3493,10 +3466,9 @@ skip_dynamic_range_lvalue_expansion:; AstNode *buf = children[0]->clone(); int mem_depth = 1; int result, high = 0, low = 0, left = 0, right = 0, width = 1; // defaults for a simple wire + int expr_dimensions = 0, expr_unpacked_dimensions = 0; AstNode *id_ast = NULL; - // Is this needed? - //while (buf->simplify(true, false, stage, width_hint, sign_hint, false)) { } buf->detectSignWidth(width_hint, sign_hint); if (buf->type == AST_IDENTIFIER) { @@ -3506,107 +3478,46 @@ skip_dynamic_range_lvalue_expansion:; if (!id_ast) input_error("Failed to resolve identifier %s for width detection!\n", buf->str.c_str()); - // Check for item in packed struct / union - AST::AstNode *item_node = get_struct_member(buf); - if (id_ast->type == AST_WIRE && item_node) { + if (id_ast->type == AST_WIRE || id_ast->type == AST_MEMORY) { + // Check for item in packed struct / union + AstNode *item_node = buf->get_struct_member(); + if (item_node) + id_ast = item_node; + // The dimension of the original array expression is saved in the 'integer' field dim += buf->integer; - if (item_node->multirange_dimensions.empty()) { - if (dim != 1) - input_error("Dimension %d out of range in `%s', as it only has one dimension!\n", dim, item_node->str.c_str()); - left = high = item_node->range_left; - right = low = item_node->range_right; - } else { - int dims = GetSize(item_node->multirange_dimensions)/2; - if (dim < 1 || dim > dims) - input_error("Dimension %d out of range in `%s', as it only has dimensions 1..%d!\n", dim, item_node->str.c_str(), dims); - right = low = get_struct_range_offset(item_node, dim - 1); - left = high = low + get_struct_range_width(item_node, dim - 1) - 1; - if (item_node->multirange_swapped[dim - 1]) { - std::swap(left, right); - } - for (int i = dim; i < dims; i++) { - mem_depth *= get_struct_range_width(item_node, i); - } - } - } - // Otherwise, we have 4 cases: - // wire x; ==> AST_WIRE, no AST_RANGE children - // wire [1:0]x; ==> AST_WIRE, AST_RANGE children - // wire [1:0]x[1:0]; ==> AST_MEMORY, two AST_RANGE children (1st for packed, 2nd for unpacked) - // wire [1:0]x[1:0][1:0]; ==> AST_MEMORY, one AST_RANGE child (0) for packed, then AST_MULTIRANGE child (1) for unpacked - // (updated: actually by the time we are here, AST_MULTIRANGE is converted into one big AST_RANGE) - // case 0 handled by default - else if ((id_ast->type == AST_WIRE || id_ast->type == AST_MEMORY) && id_ast->children.size() > 0) { - // handle packed array left/right for case 1, and cases 2/3 when requesting the last dimension (packed side) - AstNode *wire_range = id_ast->children[0]; - left = wire_range->children[0]->integer; - right = wire_range->children[1]->integer; - high = max(left, right); - low = min(left, right); - } - if (id_ast->type == AST_MEMORY) { - // a slice of our identifier means we advance to the next dimension, e.g. $size(a[3]) - if (buf->children.size() > 0) { - // something is hanging below this identifier - if (buf->children[0]->type == AST_RANGE && buf->integer == 0) - // if integer == 0, this node was originally created as AST_RANGE so it's dimension is 1 - dim++; - // more than one range, e.g. $size(a[3][2]) - else // created an AST_MULTIRANGE, converted to AST_RANGE, but original dimension saved in 'integer' field - dim += buf->integer; // increment by multirange size - } - // We got here only if the argument is a memory - // Otherwise $size() and $bits() return the expression width - AstNode *mem_range = id_ast->children[1]; - if (str == "\\$bits") { - if (mem_range->type == AST_RANGE) { - if (!mem_range->range_valid) - input_error("Failed to detect width of memory access `%s'!\n", buf->str.c_str()); - mem_depth = mem_range->range_left - mem_range->range_right + 1; - } else - input_error("Unknown memory depth AST type in `%s'!\n", buf->str.c_str()); - } else { - // $size(), $left(), $right(), $high(), $low() - int dims = 1; - if (mem_range->type == AST_RANGE) { - if (id_ast->multirange_dimensions.empty()) { - if (!mem_range->range_valid) - input_error("Failed to detect width of memory access `%s'!\n", buf->str.c_str()); - if (dim == 1) { - left = mem_range->range_right; - right = mem_range->range_left; - high = max(left, right); - low = min(left, right); - } - } else { - dims = GetSize(id_ast->multirange_dimensions)/2; - if (dim <= dims) { - width_hint = id_ast->multirange_dimensions[2*dim-1]; - high = id_ast->multirange_dimensions[2*dim-2] + id_ast->multirange_dimensions[2*dim-1] - 1; - low = id_ast->multirange_dimensions[2*dim-2]; - if (id_ast->multirange_swapped[dim-1]) { - left = low; - right = high; - } else { - right = low; - left = high; - } - } else if ((dim > dims+1) || (dim < 0)) - input_error("Dimension %d out of range in `%s', as it only has dimensions 1..%d!\n", dim, buf->str.c_str(), dims+1); - } - } else { - input_error("Unknown memory depth AST type in `%s'!\n", buf->str.c_str()); - } + int dims = GetSize(id_ast->dimensions); + // TODO: IEEE Std 1800-2017 20.7: "If the first argument to an array query function would cause $dimensions to return 0 + // or if the second argument is out of range, then 'x shall be returned." + if (dim < 1 || dim > dims) + input_error("Dimension %d out of range in `%s', as it only has %d dimensions!\n", dim, id_ast->str.c_str(), dims); + + expr_dimensions = dims - dim + 1; + expr_unpacked_dimensions = std::max(id_ast->unpacked_dimensions - dim + 1, 0); + + right = low = id_ast->dimensions[dim - 1].range_right; + left = high = low + id_ast->dimensions[dim - 1].range_width - 1; + if (id_ast->dimensions[dim - 1].range_swapped) { + std::swap(left, right); + } + for (int i = dim; i < dims; i++) { + mem_depth *= id_ast->dimensions[i].range_width; } } width = high - low + 1; } else { width = width_hint; + right = low = 0; + left = high = width - 1; + expr_dimensions = 1; } delete buf; - if (str == "\\$high") + if (str == "\\$dimensions") + result = expr_dimensions; + else if (str == "\\$unpacked_dimensions") + result = expr_unpacked_dimensions; + else if (str == "\\$high") result = high; else if (str == "\\$low") result = low; @@ -3614,6 +3525,8 @@ skip_dynamic_range_lvalue_expansion:; result = left; else if (str == "\\$right") result = right; + else if (str == "\\$increment") + result = left >= right ? 1 : -1; else if (str == "\\$size") result = width; else { // str == "\\$bits" @@ -4180,7 +4093,7 @@ replace_fcall_later:; tmp_range_left = (param_width + 2*param_offset) - children[0]->range_right - 1; tmp_range_right = (param_width + 2*param_offset) - children[0]->range_left - 1; } - AST::AstNode *member_node = get_struct_member(this); + AstNode *member_node = get_struct_member(); int chunk_offset = member_node ? member_node->range_right : 0; log_assert(!(chunk_offset && param_upto)); for (int i = tmp_range_right; i <= tmp_range_left; i++) { @@ -4820,6 +4733,9 @@ void AstNode::mem2reg_as_needed_pass1(dict> &mem2reg { AstNode *mem = id2ast; + if (integer < (unsigned)mem->unpacked_dimensions) + input_error("Insufficient number of array indices for %s.\n", log_id(str)); + // flag if used after blocking assignment (in same proc) if ((proc_flags[mem] & AstNode::MEM2REG_FL_EQ1) && !(mem2reg_candidates[mem] & AstNode::MEM2REG_FL_EQ2)) { mem2reg_places[mem].insert(stringf("%s:%d", RTLIL::encode_filename(filename).c_str(), location.first_line)); @@ -5103,7 +5019,7 @@ bool AstNode::mem2reg_as_needed_pass2(pool &mem2reg_set, AstNode *mod, int width; if (bit_part_sel) { - bit_part_sel->dumpAst(nullptr, "? "); + // bit_part_sel->dumpAst(nullptr, "? "); if (bit_part_sel->children.size() == 1) width = 0; else @@ -5746,7 +5662,7 @@ std::string AstNode::try_pop_module_prefix() const if (current_scope.count(new_str)) { std::string prefix = str.substr(0, pos); auto it = current_scope_ast->attributes.find(ID::hdlname); - if ((it != current_scope_ast->attributes.end() && it->second->str == prefix) + if ((it != current_scope_ast->attributes.end() && it->second->str == prefix.substr(1)) || prefix == current_scope_ast->str) return new_str; } diff --git a/frontends/verific/verific.cc b/frontends/verific/verific.cc index dff9c777b84..faa0e1bcdd3 100644 --- a/frontends/verific/verific.cc +++ b/frontends/verific/verific.cc @@ -1990,6 +1990,7 @@ void VerificImporter::import_netlist(RTLIL::Design *design, Netlist *nl, std::ma } RTLIL::Cell *cell = module->addCell(inst_name, inst_type); + import_attributes(cell->attributes, inst); if (inst->IsPrimitive() && mode_keep) cell->attributes[ID::keep] = 1; diff --git a/frontends/verilog/verilog_parser.y b/frontends/verilog/verilog_parser.y index 039e83491e7..15a04eb2885 100644 --- a/frontends/verilog/verilog_parser.y +++ b/frontends/verilog/verilog_parser.y @@ -197,9 +197,20 @@ static AstNode *checkRange(AstNode *type_node, AstNode *range_node) range_node = makeRange(type_node->range_left, type_node->range_right, false); } } - if (range_node && range_node->children.size() != 2) { - frontend_verilog_yyerror("wire/reg/logic packed dimension must be of the form: [:], [+:], or [-:]"); + + if (range_node) { + bool valid = true; + if (range_node->type == AST_RANGE) { + valid = range_node->children.size() == 2; + } else { // AST_MULTIRANGE + for (auto child : range_node->children) { + valid = valid && child->children.size() == 2; + } + } + if (!valid) + frontend_verilog_yyerror("wire/reg/logic packed dimension must be of the form [:]"); } + return range_node; } @@ -672,7 +683,7 @@ module_arg: ast_stack.back()->children.push_back(astbuf2); delete astbuf1; // really only needed if multiple instances of same type. } module_arg_opt_assignment | - attr wire_type range TOK_ID { + attr wire_type range_or_multirange TOK_ID { AstNode *node = $2; node->str = *$4; SET_AST_NODE_LOC(node, @4, @4); @@ -1165,7 +1176,7 @@ task_func_args: task_func_port | task_func_args ',' task_func_port; task_func_port: - attr wire_type range { + attr wire_type range_or_multirange { bool prev_was_input = true; bool prev_was_output = false; if (albuf) { @@ -1889,10 +1900,11 @@ struct_member_type: { astbuf1 = new AstNode(AST_STRUCT_ITEM); } member_type_toke ; member_type_token: - member_type - | hierarchical_type_id { - addWiretypeNode($1, astbuf1); - } + member_type range_or_multirange { + AstNode *range = checkRange(astbuf1, $2); + if (range) + astbuf1->children.push_back(range); + } | { delete astbuf1; } struct_union { @@ -1908,7 +1920,8 @@ member_type_token: ; member_type: type_atom type_signing - | type_vec type_signing range_or_multirange { if ($3) astbuf1->children.push_back($3); } + | type_vec type_signing + | hierarchical_type_id { addWiretypeNode($1, astbuf1); } ; struct_var_list: struct_var @@ -1928,7 +1941,7 @@ struct_var: TOK_ID { auto *var_node = astbuf2->clone(); ///////// wire_decl: - attr wire_type range { + attr wire_type range_or_multirange { albuf = $1; astbuf1 = $2; astbuf2 = checkRange(astbuf1, $3); @@ -2104,14 +2117,14 @@ type_name: TOK_ID // first time seen ; typedef_decl: - TOK_TYPEDEF typedef_base_type range type_name range_or_multirange ';' { + TOK_TYPEDEF typedef_base_type range_or_multirange type_name range_or_multirange ';' { astbuf1 = $2; astbuf2 = checkRange(astbuf1, $3); if (astbuf2) astbuf1->children.push_back(astbuf2); if ($5 != NULL) { - if (!astbuf2) { + if (!astbuf2 && !astbuf1->is_custom_type) { addRange(astbuf1, 0, 0, false); } rewriteAsMemoryNode(astbuf1, $5); diff --git a/kernel/celltypes.h b/kernel/celltypes.h index 77cb3e324c2..fde6624e179 100644 --- a/kernel/celltypes.h +++ b/kernel/celltypes.h @@ -108,6 +108,7 @@ struct CellTypes 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}); + setup_type(ID($scopeinfo), {}, {}); } void setup_internals_eval() diff --git a/kernel/driver.cc b/kernel/driver.cc index 8d9ecc91adc..58da1bc32e3 100644 --- a/kernel/driver.cc +++ b/kernel/driver.cc @@ -92,8 +92,15 @@ int getopt(int argc, char **argv, const char *optstring) return optopt; } - optarg = argv[++optind]; + if (++optind >= argc) { + fprintf(stderr, "%s: option '-%c' expects an argument\n", argv[0], optopt); + optopt = '?'; + return optopt; + } + + optarg = argv[optind]; optind++, optcur = 1; + return optopt; } diff --git a/kernel/register.cc b/kernel/register.cc index 1853e94d56b..b5485e06d11 100644 --- a/kernel/register.cc +++ b/kernel/register.cc @@ -992,4 +992,44 @@ struct MinisatSatSolver : public SatSolver { } } MinisatSatSolver; +struct LicensePass : public Pass { + LicensePass() : Pass("license", "print license terms") { } + void help() override + { + log("\n"); + log(" license\n"); + log("\n"); + log("This command produces the following notice.\n"); + notice(); + } + void execute(std::vector args, RTLIL::Design*) override + { + notice(); + } + void notice() + { + log("\n"); + log(" /----------------------------------------------------------------------------\\\n"); + log(" | |\n"); + log(" | yosys -- Yosys Open SYnthesis Suite |\n"); + log(" | |\n"); + log(" | Copyright (C) 2012 - 2024 Claire Xenia Wolf |\n"); + log(" | |\n"); + log(" | Permission to use, copy, modify, and/or distribute this software for any |\n"); + log(" | purpose with or without fee is hereby granted, provided that the above |\n"); + log(" | copyright notice and this permission notice appear in all copies. |\n"); + log(" | |\n"); + log(" | THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |\n"); + log(" | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |\n"); + log(" | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |\n"); + log(" | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |\n"); + log(" | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |\n"); + log(" | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |\n"); + log(" | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |\n"); + log(" | |\n"); + log(" \\----------------------------------------------------------------------------/\n"); + log("\n"); + } +} LicensePass; + YOSYS_NAMESPACE_END diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 125730f2924..8781b6a8946 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -1769,6 +1769,15 @@ namespace { return; } + if (cell->type == ID($scopeinfo)) { + param(ID::TYPE); + check_expected(); + std::string scope_type = cell->getParam(ID::TYPE).decode_string(); + if (scope_type != "module" && scope_type != "struct") + error(__LINE__); + return; + } + if (cell->type == ID($_BUF_)) { port(ID::A,1); port(ID::Y,1); check_expected(); return; } if (cell->type == ID($_NOT_)) { port(ID::A,1); port(ID::Y,1); check_expected(); return; } if (cell->type == ID($_AND_)) { port(ID::A,1); port(ID::B,1); port(ID::Y,1); check_expected(); return; } diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 928bc044049..40422dce56d 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -712,7 +712,7 @@ struct RTLIL::Const inline unsigned int hash() const { unsigned int h = mkhash_init; for (auto b : bits) - mkhash(h, b); + h = mkhash(h, b); return h; } }; diff --git a/kernel/satgen.cc b/kernel/satgen.cc index 3a2fa473568..baaf22d1faf 100644 --- a/kernel/satgen.cc +++ b/kernel/satgen.cc @@ -1379,6 +1379,11 @@ bool SatGen::importCell(RTLIL::Cell *cell, int timestep) return true; } + if (cell->type == ID($scopeinfo)) + { + return true; + } + // Unsupported internal cell types: $pow $fsm $mem* // .. and all sequential cells with asynchronous inputs return false; diff --git a/kernel/scopeinfo.cc b/kernel/scopeinfo.cc new file mode 100644 index 00000000000..7ed9ebf33f7 --- /dev/null +++ b/kernel/scopeinfo.cc @@ -0,0 +1,129 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 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/scopeinfo.h" + +YOSYS_NAMESPACE_BEGIN + +template void ModuleHdlnameIndex::index_items(I begin, I end, Filter filter) +{ + for (; begin != end; ++begin) { + auto const &item = *begin; + + if (!filter(item)) + continue; + std::vector path = parse_hdlname(item); + if (!path.empty()) + lookup.emplace(item, tree.insert(path, item)); + } +} + +void ModuleHdlnameIndex::index() +{ + index_wires(); + index_cells(); +} + +void ModuleHdlnameIndex::index_wires() +{ + auto wires = module->wires(); + index_items(wires.begin(), wires.end(), [](Wire *) { return true; }); +} + +void ModuleHdlnameIndex::index_cells() +{ + auto cells = module->cells(); + index_items(cells.begin(), cells.end(), [](Cell *) { return true; }); +} + +void ModuleHdlnameIndex::index_scopeinfo_cells() +{ + auto cells = module->cells(); + index_items(cells.begin(), cells.end(), [](Cell *cell) { return cell->type == ID($scopeinfo); }); +} + +std::vector ModuleHdlnameIndex::scope_sources(Cursor cursor) +{ + std::vector result; + + for (; !cursor.is_root(); cursor = cursor.parent()) { + if (!cursor.has_entry()) { + result.push_back(""); + result.push_back(""); + continue; + } + Cell *cell = cursor.entry().cell(); + if (cell == nullptr || cell->type != ID($scopeinfo)) { + result.push_back(""); + result.push_back(""); + continue; + } + result.push_back(scopeinfo_get_attribute(cell, ScopeinfoAttrs::Module, ID::src).decode_string()); + result.push_back(scopeinfo_get_attribute(cell, ScopeinfoAttrs::Cell, ID::src).decode_string()); + } + + result.push_back(module->get_src_attribute()); + + std::reverse(result.begin(), result.end()); + + return result; +} + +static const char *attr_prefix(ScopeinfoAttrs attrs) +{ + switch (attrs) { + case ScopeinfoAttrs::Cell: + return "\\cell_"; + case ScopeinfoAttrs::Module: + return "\\module_"; + default: + log_abort(); + } +} + +bool scopeinfo_has_attribute(const RTLIL::Cell *scopeinfo, ScopeinfoAttrs attrs, const RTLIL::IdString &id) +{ + log_assert(scopeinfo->type == ID($scopeinfo)); + return scopeinfo->has_attribute(attr_prefix(attrs) + RTLIL::unescape_id(id)); +} + +RTLIL::Const scopeinfo_get_attribute(const RTLIL::Cell *scopeinfo, ScopeinfoAttrs attrs, const RTLIL::IdString &id) +{ + log_assert(scopeinfo->type == ID($scopeinfo)); + auto found = scopeinfo->attributes.find(attr_prefix(attrs) + RTLIL::unescape_id(id)); + if (found == scopeinfo->attributes.end()) + return RTLIL::Const(); + return found->second; +} + +dict scopeinfo_attributes(const RTLIL::Cell *scopeinfo, ScopeinfoAttrs attrs) +{ + dict attributes; + + const char *prefix = attr_prefix(attrs); + int prefix_len = strlen(prefix); + + for (auto const &entry : scopeinfo->attributes) + if (entry.first.begins_with(prefix)) + attributes.emplace(RTLIL::escape_id(entry.first.c_str() + prefix_len), entry.second); + + return attributes; +} + +YOSYS_NAMESPACE_END diff --git a/kernel/scopeinfo.h b/kernel/scopeinfo.h new file mode 100644 index 00000000000..71af70344ce --- /dev/null +++ b/kernel/scopeinfo.h @@ -0,0 +1,432 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 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. + * + */ + +#ifndef SCOPEINFO_H +#define SCOPEINFO_H + +#include +#include + +#include "kernel/yosys.h" +#include "kernel/celltypes.h" + +YOSYS_NAMESPACE_BEGIN + +template +class IdTree +{ +public: + struct Cursor; + +protected: + IdTree *parent = nullptr; + IdString scope_name; + int depth = 0; + + pool names; + dict entries; +public: // XXX + dict> subtrees; + + template + static Cursor do_insert(IdTree *tree, P begin, P end, T_ref &&value) + { + log_assert(begin != end && "path must be non-empty"); + while (true) { + IdString name = *begin; + ++begin; + log_assert(!name.empty()); + tree->names.insert(name); + if (begin == end) { + tree->entries.emplace(name, std::forward(value)); + return Cursor(tree, name); + } + auto &unique = tree->subtrees[name]; + if (!unique) { + unique.reset(new IdTree); + unique->scope_name = name; + unique->parent = tree; + unique->depth = tree->depth + 1; + } + tree = unique.get(); + } + } + +public: + IdTree() = default; + IdTree(const IdTree &) = delete; + IdTree(IdTree &&) = delete; + + // A cursor remains valid as long as the (sub-)IdTree it points at is alive + struct Cursor + { + friend class IdTree; + protected: + public: + IdTree *target; + IdString scope_name; + + Cursor() : target(nullptr) {} + Cursor(IdTree *target, IdString scope_name) : target(target), scope_name(scope_name) { + if (scope_name.empty()) + log_assert(target->parent == nullptr); + } + + Cursor do_first_child() { + IdTree *tree = nullptr; + if (scope_name.empty()) { + tree = target; + } else { + auto found = target->subtrees.find(scope_name); + if (found != target->subtrees.end()) { + tree = found->second.get(); + } else { + return Cursor(); + } + } + if (tree->names.empty()) { + return Cursor(); + } + return Cursor(tree, *tree->names.begin()); + } + + Cursor do_next_sibling() { + if (scope_name.empty()) + return Cursor(); + auto found = target->names.find(scope_name); + if (found == target->names.end()) + return Cursor(); + ++found; + if (found == target->names.end()) + return Cursor(); + return Cursor(target, *found); + } + + Cursor do_parent() { + if (scope_name.empty()) + return Cursor(); + if (target->parent != nullptr) + return Cursor(target->parent, target->scope_name); + return Cursor(target, IdString()); + } + + Cursor do_next_preorder() { + Cursor current = *this; + Cursor next = current.do_first_child(); + if (next.valid()) + return next; + while (current.valid()) { + if (next.valid()) + return next; + next = current.do_next_sibling(); + if (next.valid()) + return next; + current = current.do_parent(); + } + return current; + } + + Cursor do_child(IdString name) { + IdTree *tree = nullptr; + if (scope_name.empty()) { + tree = target; + } else { + auto found = target->subtrees.find(scope_name); + if (found != target->subtrees.end()) { + tree = found->second.get(); + } else { + return Cursor(); + } + } + auto found = tree->names.find(name); + if (found == tree->names.end()) { + return Cursor(); + } + return Cursor(tree, *found); + } + + public: + bool operator==(const Cursor &other) const { + return target == other.target && scope_name == other.scope_name; + } + bool operator!=(const Cursor &other) const { + return !(*this == other); + } + + bool valid() const { + return target != nullptr; + } + + int depth() const { + log_assert(valid()); + return target->depth + !scope_name.empty(); + } + + bool is_root() const { + return target != nullptr && scope_name.empty(); + } + + bool has_entry() const { + log_assert(valid()); + return !scope_name.empty() && target->entries.count(scope_name); + } + + T &entry() { + log_assert(!scope_name.empty()); + return target->entries.at(scope_name); + } + + void assign_path_to(std::vector &out_path) { + log_assert(valid()); + out_path.clear(); + if (scope_name.empty()) + return; + out_path.push_back(scope_name); + IdTree *current = target; + while (current->parent) { + out_path.push_back(current->scope_name); + current = current->parent; + } + std::reverse(out_path.begin(), out_path.end()); + } + + std::vector path() { + std::vector result; + assign_path_to(result); + return result; + } + + std::string path_str() { + std::string result; + for (const auto &item : path()) { + if (!result.empty()) + result.push_back(' '); + result += RTLIL::unescape_id(item); + } + return result; + } + + Cursor first_child() { + log_assert(valid()); + return do_first_child(); + } + + Cursor next_preorder() { + log_assert(valid()); + return do_next_preorder(); + } + + Cursor parent() { + log_assert(valid()); + return do_parent(); + } + + Cursor child(IdString name) { + log_assert(valid()); + return do_child(name); + } + + Cursor common_ancestor(Cursor other) { + Cursor current = *this; + + while (current != other) { + if (!current.valid() || !other.valid()) + return Cursor(); + int delta = current.depth() - other.depth(); + if (delta >= 0) + current = current.do_parent(); + if (delta <= 0) + other = other.do_parent(); + } + return current; + } + }; + + template + Cursor insert(P begin, P end, const T &value) { + return do_insert(this, begin, end, value); + } + + template + Cursor insert(P begin, P end, T &&value) { + return do_insert(this, begin, end, std::move(value)); + } + + template + Cursor insert(const P &path, const T &value) { + return do_insert(this, path.begin(), path.end(), value); + } + + template + Cursor insert(const P &path, T &&value) { + return do_insert(this, path.begin(), path.end(), std::move(value)); + } + + Cursor cursor() { + return parent ? Cursor(this->parent, this->scope_name) : Cursor(this, IdString()); + } + + template + Cursor cursor(P begin, P end) { + Cursor current = cursor(); + for (; begin != end; ++begin) { + current = current.do_child(*begin); + if (!current.valid()) + break; + } + return current; + } + + template + Cursor cursor(const P &path) { + return cursor(path.begin(), path.end()); + } +}; + + +struct ModuleItem { + enum class Type { + Wire, + Cell, + }; + Type type; + void *ptr; + + ModuleItem(Wire *wire) : type(Type::Wire), ptr(wire) {} + ModuleItem(Cell *cell) : type(Type::Cell), ptr(cell) {} + + bool is_wire() const { return type == Type::Wire; } + bool is_cell() const { return type == Type::Cell; } + + Wire *wire() const { return type == Type::Wire ? static_cast(ptr) : nullptr; } + Cell *cell() const { return type == Type::Cell ? static_cast(ptr) : nullptr; } + + bool operator==(const ModuleItem &other) const { return ptr == other.ptr && type == other.type; } + unsigned int hash() const { return (uintptr_t)ptr; } +}; + +static inline void log_dump_val_worker(typename IdTree::Cursor cursor ) { log("%p %s", cursor.target, log_id(cursor.scope_name)); } + +template +static inline void log_dump_val_worker(const typename std::unique_ptr &cursor ) { log("unique %p", cursor.get()); } + +template +std::vector parse_hdlname(const O* object) +{ + std::vector path; + if (!object->name.isPublic()) + return path; + for (auto const &item : object->get_hdlname_attribute()) + path.push_back("\\" + item); + if (path.empty()) + path.push_back(object->name); + return path; +} + +template +std::pair, IdString> parse_scopename(const O* object) +{ + std::vector path; + IdString trailing = object->name; + if (object->name.isPublic()) { + for (auto const &item : object->get_hdlname_attribute()) + path.push_back("\\" + item); + if (!path.empty()) { + trailing = path.back(); + path.pop_back(); + } + } else { + for (auto const &item : split_tokens(object->get_string_attribute(ID(scopename)), " ")) + path.push_back("\\" + item); + + } + return {path, trailing}; +} + +struct ModuleHdlnameIndex { + typedef IdTree::Cursor Cursor; + + RTLIL::Module *module; + IdTree tree; + dict lookup; + + ModuleHdlnameIndex(RTLIL::Module *module) : module(module) {} + +private: + template + void index_items(I begin, I end, Filter filter); + +public: + // Index all wires and cells of the module + void index(); + + // Index all wires of the module + void index_wires(); + + // Index all cells of the module + void index_cells(); + + // Index only the $scopeinfo cells of the module. + // This is sufficient when using `containing_scope`. + void index_scopeinfo_cells(); + + + // Return the cursor for the containing scope of some RTLIL object (Wire/Cell/...) + template + std::pair containing_scope(O *object) { + auto pair = parse_scopename(object); + return {tree.cursor(pair.first), pair.second}; + } + + // Return a vector of source locations starting from the indexed module to + // the scope represented by the cursor. The vector alternates module and + // module item source locations, using empty strings for missing src + // attributes. + std::vector scope_sources(Cursor cursor); + + // Return a vector of source locations starting from the indexed module to + // the passed RTLIL object (Wire/Cell/...). The vector alternates module + // and module item source locations, using empty strings for missing src + // attributes. + template + std::vector sources(O *object) { + auto pair = parse_scopename(object); + std::vector result = scope_sources(tree.cursor(pair.first)); + result.push_back(object->get_src_attribute()); + return result; + } +}; + +enum class ScopeinfoAttrs { + Module, + Cell, +}; + +// Check whether the flattened module or flattened cell corresponding to a $scopeinfo cell had a specific attribute. +bool scopeinfo_has_attribute(const RTLIL::Cell *scopeinfo, ScopeinfoAttrs attrs, const RTLIL::IdString &id); + +// Get a specific attribute from the flattened module or flattened cell corresponding to a $scopeinfo cell. +RTLIL::Const scopeinfo_get_attribute(const RTLIL::Cell *scopeinfo, ScopeinfoAttrs attrs, const RTLIL::IdString &id); + +// Get all attribute from the flattened module or flattened cell corresponding to a $scopeinfo cell. +dict scopeinfo_attributes(const RTLIL::Cell *scopeinfo, ScopeinfoAttrs attrs); + +YOSYS_NAMESPACE_END + +#endif diff --git a/kernel/yosys.cc b/kernel/yosys.cc index c7f5bebdab7..20f9791c61c 100644 --- a/kernel/yosys.cc +++ b/kernel/yosys.cc @@ -138,27 +138,11 @@ void yosys_banner() { log("\n"); log(" /----------------------------------------------------------------------------\\\n"); - log(" | |\n"); log(" | yosys -- Yosys Open SYnthesis Suite |\n"); - log(" | |\n"); - log(" | Copyright (C) 2012 - 2020 Claire Xenia Wolf |\n"); - log(" | |\n"); - log(" | Permission to use, copy, modify, and/or distribute this software for any |\n"); - log(" | purpose with or without fee is hereby granted, provided that the above |\n"); - log(" | copyright notice and this permission notice appear in all copies. |\n"); - log(" | |\n"); - log(" | THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |\n"); - log(" | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |\n"); - log(" | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |\n"); - log(" | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |\n"); - log(" | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |\n"); - log(" | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |\n"); - log(" | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |\n"); - log(" | |\n"); + log(" | Copyright (C) 2012 - 2024 Claire Xenia Wolf |\n"); + log(" | Distributed under an ISC-like license, type \"license\" to see terms |\n"); log(" \\----------------------------------------------------------------------------/\n"); - log("\n"); log(" %s\n", yosys_version_str); - log("\n"); } int ceil_log2(int x) diff --git a/libs/ezsat/Makefile b/libs/ezsat/Makefile index b1f86416093..c41038dc938 100644 --- a/libs/ezsat/Makefile +++ b/libs/ezsat/Makefile @@ -1,9 +1,9 @@ CC = clang -CXX = clang +CXX = clang++ CXXFLAGS = -MD -Wall -Wextra -ggdb CXXFLAGS += -std=c++11 -O0 -LDLIBS = ../minisat/Options.cc ../minisat/SimpSolver.cc ../minisat/Solver.cc ../minisat/System.cc -lm -lstdc++ +LIBS = ../minisat/Options.cc ../minisat/SimpSolver.cc ../minisat/Solver.cc ../minisat/System.cc -lm -lstdc++ all: demo_vec demo_bit demo_cmp testbench puzzle3d @@ -27,4 +27,3 @@ clean: .PHONY: all test clean -include *.d - diff --git a/libs/subcircuit/Makefile b/libs/subcircuit/Makefile index f81085b5bc3..3d93ad0a2e4 100644 --- a/libs/subcircuit/Makefile +++ b/libs/subcircuit/Makefile @@ -5,9 +5,9 @@ CONFIG := clang-debug # CONFIG := release CC = clang -CXX = clang +CXX = clang++ CXXFLAGS = -MD -Wall -Wextra -ggdb -LDLIBS = -lstdc++ +LIBS = -lstdc++ ifeq ($(CONFIG),clang-debug) CXXFLAGS += -std=c++11 -O0 @@ -15,19 +15,19 @@ endif ifeq ($(CONFIG),gcc-debug) CC = gcc -CXX = gcc +CXX = g++ CXXFLAGS += -std=gnu++0x -O0 endif ifeq ($(CONFIG),profile) CC = gcc -CXX = gcc +CXX = g++ CXXFLAGS += -std=gnu++0x -Os -DNDEBUG endif ifeq ($(CONFIG),release) CC = gcc -CXX = gcc +CXX = g++ CXXFLAGS += -std=gnu++0x -march=native -O3 -DNDEBUG endif @@ -50,4 +50,3 @@ clean: .PHONY: all test clean -include *.d - diff --git a/misc/yosys-config.in b/misc/yosys-config.in old mode 100644 new mode 100755 index f0f0f755231..dd42b7c877b --- a/misc/yosys-config.in +++ b/misc/yosys-config.in @@ -9,8 +9,10 @@ help() { echo "Replacement args:" echo " --cxx @CXX@" echo " --cxxflags $( echo '@CXXFLAGS@' | fmt -w60 | sed ':a;N;$!ba;s/\n/ \\\n /g' )" - echo " --ldflags @LDFLAGS@" - echo " --ldlibs @LDLIBS@" + echo " --linkflags @LINKFLAGS@" + echo " --ldflags (alias of --linkflags)" + echo " --libs @LIBS@" + echo " --ldlibs (alias of --libs)" echo " --bindir @BINDIR@" echo " --datdir @DATDIR@" echo "" @@ -18,7 +20,7 @@ help() { echo "" echo "Use --exec to call a command instead of generating output. Example usage:" echo "" - echo " $0 --exec --cxx --cxxflags --ldflags -o plugin.so -shared plugin.cc --ldlibs" + echo " $0 --exec --cxx --cxxflags --ldflags -o plugin.so -shared plugin.cc --libs" echo "" echo "The above command can be abbreviated as:" echo "" @@ -44,7 +46,7 @@ fi if [ "$1" == "--build" ]; then modname="$2"; shift 2 - set -- --exec --cxx --cxxflags --ldflags -o "$modname" -shared "$@" --ldlibs + set -- --exec --cxx --cxxflags --ldflags -o "$modname" -shared "$@" --libs fi prefix="--" @@ -63,10 +65,14 @@ for opt; do tokens=( "${tokens[@]}" @CXX@ ) ;; "$prefix"cxxflags) tokens=( "${tokens[@]}" @CXXFLAGS@ ) ;; + "$prefix"linkflags) + tokens=( "${tokens[@]}" @LINKFLAGS@ ) ;; + "$prefix"libs) + tokens=( "${tokens[@]}" @LIBS@ ) ;; "$prefix"ldflags) - tokens=( "${tokens[@]}" @LDFLAGS@ ) ;; + tokens=( "${tokens[@]}" @LINKFLAGS@ ) ;; "$prefix"ldlibs) - tokens=( "${tokens[@]}" @LDLIBS@ ) ;; + tokens=( "${tokens[@]}" @LIBS@ ) ;; "$prefix"bindir) tokens=( "${tokens[@]}" '@BINDIR@' ) ;; "$prefix"datdir) @@ -104,4 +110,3 @@ fi echo "${tokens[@]}" exit 0 - diff --git a/passes/cmds/connect.cc b/passes/cmds/connect.cc index 1bd52aab258..65292ef92e9 100644 --- a/passes/cmds/connect.cc +++ b/passes/cmds/connect.cc @@ -47,7 +47,7 @@ struct ConnectPass : public Pass { { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); - log(" connect [-nomap] [-nounset] -set \n"); + log(" connect [-nomap] [-nounset] -set [selection]\n"); log("\n"); log("Create a connection. This is equivalent to adding the statement 'assign\n"); log(" = ;' to the Verilog input. Per default, all existing\n"); @@ -55,12 +55,12 @@ struct ConnectPass : public Pass { log("the -nounset option.\n"); log("\n"); log("\n"); - log(" connect [-nomap] -unset \n"); + log(" connect [-nomap] -unset [selection]\n"); log("\n"); log("Unconnect all existing drivers for the specified expression.\n"); log("\n"); log("\n"); - log(" connect [-nomap] [-assert] -port \n"); + log(" connect [-nomap] [-assert] -port [selection]\n"); log("\n"); log("Connect the specified cell port to the specified cell port.\n"); log("\n"); @@ -80,17 +80,6 @@ struct ConnectPass : public Pass { } void execute(std::vector args, RTLIL::Design *design) override { - RTLIL::Module *module = nullptr; - for (auto mod : design->selected_modules()) { - if (module != nullptr) - log_cmd_error("Multiple modules selected: %s, %s\n", log_id(module->name), log_id(mod->name)); - module = mod; - } - if (module == nullptr) - log_cmd_error("No modules selected.\n"); - if (!module->processes.empty()) - log_cmd_error("Found processes in selected module.\n"); - bool flag_nounset = false, flag_nomap = false, flag_assert = false; std::string set_lhs, set_rhs, unset_expr; std::string port_cell, port_port, port_expr; @@ -128,6 +117,18 @@ struct ConnectPass : public Pass { } break; } + extra_args(args, argidx, design); + + RTLIL::Module *module = nullptr; + for (auto mod : design->selected_modules()) { + if (module != nullptr) + log_cmd_error("Multiple modules selected: %s, %s\n", log_id(module->name), log_id(mod->name)); + module = mod; + } + if (module == nullptr) + log_cmd_error("No modules selected.\n"); + if (!module->processes.empty()) + log_cmd_error("Found processes in selected module.\n"); SigMap sigmap; if (!flag_nomap) diff --git a/passes/cmds/show.cc b/passes/cmds/show.cc index 8c2695dbe7f..82b5c6bcf35 100644 --- a/passes/cmds/show.cc +++ b/passes/cmds/show.cc @@ -65,6 +65,7 @@ struct ShowWorker bool enumerateIds; bool abbreviateIds; bool notitle; + bool href; int page_counter; const std::vector> &color_selections; @@ -432,9 +433,13 @@ struct ShowWorker if (wire->port_input || wire->port_output) shape = "octagon"; if (wire->name.isPublic()) { - fprintf(f, "n%d [ shape=%s, label=\"%s\", %s ];\n", + std::string src_href; + if (href && wire->attributes.count(ID::src) > 0) + src_href = stringf(", href=\"%s\" ", escape(wire->attributes.at(ID::src).decode_string())); + fprintf(f, "n%d [ shape=%s, label=\"%s\", %s%s];\n", id2num(wire->name), shape, findLabel(wire->name.str()), - nextColor(RTLIL::SigSpec(wire), "color=\"black\", fontcolor=\"black\"").c_str()); + nextColor(RTLIL::SigSpec(wire), "color=\"black\", fontcolor=\"black\"").c_str(), + src_href.c_str()); if (wire->port_input) all_sources.insert(stringf("n%d", id2num(wire->name))); else if (wire->port_output) @@ -496,14 +501,18 @@ struct ShowWorker conn.second, ct.cell_output(cell->type, conn.first)); } + std::string src_href; + if (href && cell->attributes.count(ID::src) > 0) { + src_href = stringf("%shref=\"%s\" ", (findColor(cell->name).empty() ? "" :" , "), escape(cell->attributes.at(ID::src).decode_string())); + } #ifdef CLUSTER_CELLS_AND_PORTBOXES if (!code.empty()) - fprintf(f, "subgraph cluster_c%d {\nc%d [ shape=record, label=\"%s\"%s ];\n%s}\n", - id2num(cell->name), id2num(cell->name), label_string.c_str(), color.c_str(), code.c_str()); + fprintf(f, "subgraph cluster_c%d {\nc%d [ shape=record, label=\"%s\"%s%s ];\n%s}\n", + id2num(cell->name), id2num(cell->name), label_string.c_str(), color.c_str(), src_href.c_str(), code.c_str()); else #endif - fprintf(f, "c%d [ shape=record, label=\"%s\", %s ];\n%s", - id2num(cell->name), label_string.c_str(), findColor(cell->name).c_str(), code.c_str()); + fprintf(f, "c%d [ shape=record, label=\"%s\", %s%s ];\n%s", + id2num(cell->name), label_string.c_str(), findColor(cell->name).c_str(), src_href.c_str(), code.c_str()); } for (auto &it : module->processes) @@ -608,12 +617,12 @@ struct ShowWorker } ShowWorker(FILE *f, RTLIL::Design *design, std::vector &libs, uint32_t colorSeed, bool genWidthLabels, - bool genSignedLabels, bool stretchIO, bool enumerateIds, bool abbreviateIds, bool notitle, + bool genSignedLabels, bool stretchIO, bool enumerateIds, bool abbreviateIds, bool notitle, bool href, const std::vector> &color_selections, const std::vector> &label_selections, RTLIL::IdString colorattr) : f(f), design(design), currentColor(colorSeed), genWidthLabels(genWidthLabels), genSignedLabels(genSignedLabels), stretchIO(stretchIO), enumerateIds(enumerateIds), abbreviateIds(abbreviateIds), - notitle(notitle), color_selections(color_selections), label_selections(label_selections), colorattr(colorattr) + notitle(notitle), href(href), color_selections(color_selections), label_selections(label_selections), colorattr(colorattr) { ct.setup_internals(); ct.setup_internals_mem(); @@ -726,6 +735,10 @@ struct ShowPass : public Pass { log(" don't run viewer in the background, IE wait for the viewer tool to\n"); log(" exit before returning\n"); log("\n"); + log(" -href\n"); + log(" adds href attribute to all items representing cells and wires, using\n"); + log(" src attribute of origin\n"); + log("\n"); log("When no is specified, 'dot' is used. When no and is\n"); log("specified, 'xdot' is used to display the schematic (POSIX systems only).\n"); log("\n"); @@ -763,6 +776,7 @@ struct ShowPass : public Pass { bool flag_enum = false; bool flag_abbreviate = true; bool flag_notitle = false; + bool flag_href = false; bool custom_prefix = false; std::string background = "&"; RTLIL::IdString colorattr; @@ -850,6 +864,10 @@ struct ShowPass : public Pass { background= ""; continue; } + if (arg == "-href") { + flag_href = true; + continue; + } break; } extra_args(args, argidx, design); @@ -894,7 +912,7 @@ struct ShowPass : public Pass { delete lib; log_cmd_error("Can't open dot file `%s' for writing.\n", dot_file.c_str()); } - ShowWorker worker(f, design, libs, colorSeed, flag_width, flag_signed, flag_stretch, flag_enum, flag_abbreviate, flag_notitle, color_selections, label_selections, colorattr); + ShowWorker worker(f, design, libs, colorSeed, flag_width, flag_signed, flag_stretch, flag_enum, flag_abbreviate, flag_notitle, flag_href, color_selections, label_selections, colorattr); fclose(f); for (auto lib : libs) diff --git a/passes/cmds/stat.cc b/passes/cmds/stat.cc index d34373c1c0e..85107b68f33 100644 --- a/passes/cmds/stat.cc +++ b/passes/cmds/stat.cc @@ -28,6 +28,11 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN +struct cell_area_t { + double area; + bool is_sequential; +}; + struct statdata_t { #define STAT_INT_MEMBERS X(num_wires) X(num_wire_bits) X(num_pub_wires) X(num_pub_wire_bits) \ @@ -39,6 +44,7 @@ struct statdata_t STAT_INT_MEMBERS #undef X double area; + double sequential_area; string tech; std::map techinfo; @@ -74,7 +80,7 @@ struct statdata_t #undef X } - statdata_t(RTLIL::Design *design, RTLIL::Module *mod, bool width_mode, const dict &cell_area, string techname) + statdata_t(RTLIL::Design *design, RTLIL::Module *mod, bool width_mode, const dict &cell_area, string techname) { tech = techname; @@ -132,10 +138,16 @@ struct statdata_t } if (!cell_area.empty()) { - if (cell_area.count(cell_type)) - area += cell_area.at(cell_type); - else + if (cell_area.count(cell_type)) { + cell_area_t cell_data = cell_area.at(cell_type); + if (cell_data.is_sequential) { + sequential_area += cell_data.area; + } + area += cell_data.area; + } + else { unknown_cell_area.insert(cell_type); + } } num_cells++; @@ -244,6 +256,7 @@ struct statdata_t if (area != 0) { log("\n"); log(" Chip area for %smodule '%s': %f\n", (top_mod) ? "top " : "", mod_name.c_str(), area); + log(" of which used for sequential elements: %f (%.2f%%)\n", sequential_area, 100.0*sequential_area/area); } if (tech == "xilinx") @@ -325,7 +338,7 @@ statdata_t hierarchy_worker(std::map &mod_stat, RTL return mod_data; } -void read_liberty_cellarea(dict &cell_area, string liberty_file) +void read_liberty_cellarea(dict &cell_area, string liberty_file) { std::ifstream f; f.open(liberty_file.c_str()); @@ -341,8 +354,9 @@ void read_liberty_cellarea(dict &cell_area, string liberty_fil continue; LibertyAst *ar = cell->find("area"); + bool is_flip_flop = cell->find("ff") != nullptr; if (ar != nullptr && !ar->value.empty()) - cell_area["\\" + cell->args[0]] = atof(ar->value.c_str()); + cell_area["\\" + cell->args[0]] = {/*area=*/atof(ar->value.c_str()), is_flip_flop}; } } @@ -383,7 +397,7 @@ struct StatPass : public Pass { bool width_mode = false, json_mode = false; RTLIL::Module *top_mod = nullptr; std::map mod_stat; - dict cell_area; + dict cell_area; string techname; size_t argidx; diff --git a/passes/memory/memory_collect.cc b/passes/memory/memory_collect.cc index bf3bb34f8b0..157042c9c2f 100644 --- a/passes/memory/memory_collect.cc +++ b/passes/memory/memory_collect.cc @@ -39,6 +39,9 @@ struct MemoryCollectPass : public Pass { log_header(design, "Executing MEMORY_COLLECT pass (generating $mem cells).\n"); extra_args(args, 1, design); for (auto module : design->selected_modules()) { + if (module->has_processes_warn()) + continue; + for (auto &mem : Mem::get_selected_memories(module)) { if (!mem.packed) { mem.packed = true; diff --git a/passes/memory/memory_libmap.cc b/passes/memory/memory_libmap.cc index 2e683b8eb95..77a4eb81bbe 100644 --- a/passes/memory/memory_libmap.cc +++ b/passes/memory/memory_libmap.cc @@ -2229,6 +2229,9 @@ struct MemoryLibMapPass : public Pass { Library lib = parse_library(lib_files, defines); for (auto module : design->selected_modules()) { + if (module->has_processes_warn()) + continue; + MapWorker worker(module); auto mems = Mem::get_selected_memories(module); for (auto &mem : mems) diff --git a/passes/memory/memory_map.cc b/passes/memory/memory_map.cc index e2f74c2e1a5..cafc0aaf3e0 100644 --- a/passes/memory/memory_map.cc +++ b/passes/memory/memory_map.cc @@ -493,6 +493,9 @@ struct MemoryMapPass : public Pass { extra_args(args, argidx, design); for (auto mod : design->selected_modules()) { + if (mod->has_processes_warn()) + continue; + MemoryMapWorker worker(design, mod); worker.attr_icase = attr_icase; worker.attributes = attributes; diff --git a/passes/memory/memory_memx.cc b/passes/memory/memory_memx.cc index 7edc26caaa2..22aebb43f1b 100644 --- a/passes/memory/memory_memx.cc +++ b/passes/memory/memory_memx.cc @@ -50,7 +50,7 @@ struct MemoryMemxPass : public Pass { } void execute(std::vector args, RTLIL::Design *design) override { - log_header(design, "Executing MEMORY_MEMX pass (converting $mem cells to logic and flip-flops).\n"); + log_header(design, "Executing MEMORY_MEMX pass (emit soft logic for out-of-bounds handling).\n"); extra_args(args, 1, design); for (auto module : design->selected_modules()) diff --git a/passes/memory/memory_narrow.cc b/passes/memory/memory_narrow.cc index cf5e4346540..46c538bab1e 100644 --- a/passes/memory/memory_narrow.cc +++ b/passes/memory/memory_narrow.cc @@ -46,6 +46,9 @@ struct MemoryNarrowPass : public Pass { extra_args(args, argidx, design); for (auto module : design->selected_modules()) { + if (module->has_processes_warn()) + continue; + for (auto &mem : Mem::get_selected_memories(module)) { bool wide = false; diff --git a/passes/memory/memory_share.cc b/passes/memory/memory_share.cc index 8b2354ef861..e06989f4ab8 100644 --- a/passes/memory/memory_share.cc +++ b/passes/memory/memory_share.cc @@ -558,8 +558,12 @@ struct MemorySharePass : public Pass { extra_args(args, argidx, design); MemoryShareWorker msw(design, flag_widen, flag_sat); - for (auto module : design->selected_modules()) + for (auto module : design->selected_modules()) { + if (module->has_processes_warn()) + continue; + msw(module); + } } } MemorySharePass; diff --git a/passes/opt/opt_clean.cc b/passes/opt/opt_clean.cc index 2b24944cf08..b3e43c18ca7 100644 --- a/passes/opt/opt_clean.cc +++ b/passes/opt/opt_clean.cc @@ -35,10 +35,12 @@ struct keep_cache_t { Design *design; dict cache; + bool purge_mode = false; - void reset(Design *design = nullptr) + void reset(Design *design = nullptr, bool purge_mode = false) { this->design = design; + this->purge_mode = purge_mode; cache.clear(); } @@ -88,6 +90,9 @@ struct keep_cache_t if (cell->has_keep_attr()) return true; + if (!purge_mode && cell->type == ID($scopeinfo)) + return true; + if (cell->module && cell->module->design) return query(cell->module->design->module(cell->type)); @@ -236,6 +241,8 @@ int count_nontrivial_wire_attrs(RTLIL::Wire *w) { int count = w->attributes.size(); count -= w->attributes.count(ID::src); + count -= w->attributes.count(ID::hdlname); + count -= w->attributes.count(ID(scopename)); count -= w->attributes.count(ID::unused_bits); return count; } @@ -661,7 +668,7 @@ struct OptCleanPass : public Pass { } extra_args(args, argidx, design); - keep_cache.reset(design); + keep_cache.reset(design, purge_mode); ct_reg.setup_internals_mem(); ct_reg.setup_internals_anyinit(); diff --git a/passes/opt/opt_mem.cc b/passes/opt/opt_mem.cc index 885b6f97d83..9a2d8e6a56f 100644 --- a/passes/opt/opt_mem.cc +++ b/passes/opt/opt_mem.cc @@ -52,6 +52,9 @@ struct OptMemPass : public Pass { int total_count = 0; for (auto module : design->selected_modules()) { + if (module->has_processes_warn()) + continue; + SigMap sigmap(module); FfInitVals initvals(&sigmap, module); for (auto &mem : Mem::get_selected_memories(module)) { diff --git a/passes/opt/opt_merge.cc b/passes/opt/opt_merge.cc index 248ba80913b..eb3aa462e01 100644 --- a/passes/opt/opt_merge.cc +++ b/passes/opt/opt_merge.cc @@ -272,6 +272,9 @@ struct OptMergeWorker if ((!mode_share_all && !ct.cell_known(cell->type)) || !cell->known()) continue; + if (cell->type == ID($scopeinfo)) + continue; + uint64_t hash = hash_cell_parameters_and_connections(cell); auto r = sharemap.insert(std::make_pair(hash, cell)); if (!r.second) { diff --git a/passes/proc/proc_dlatch.cc b/passes/proc/proc_dlatch.cc index 8340e143197..5b392ce512b 100644 --- a/passes/proc/proc_dlatch.cc +++ b/passes/proc/proc_dlatch.cc @@ -46,7 +46,7 @@ struct proc_dlatch_db_t for (auto cell : module->cells()) { - if (cell->type.in(ID($mux), ID($pmux))) + if (cell->type.in(ID($mux), ID($pmux), ID($bwmux))) { auto sig_y = sigmap(cell->getPort(ID::Y)); for (int i = 0; i < GetSize(sig_y); i++) @@ -186,6 +186,8 @@ struct proc_dlatch_db_t Cell *cell = it->second.first; int index = it->second.second; + log_assert(cell->type.in(ID($mux), ID($pmux), ID($bwmux))); + bool is_bwmux = (cell->type == ID($bwmux)); SigSpec sig_a = sigmap(cell->getPort(ID::A)); SigSpec sig_b = sigmap(cell->getPort(ID::B)); SigSpec sig_s = sigmap(cell->getPort(ID::S)); @@ -200,12 +202,16 @@ struct proc_dlatch_db_t sig[index] = State::Sx; cell->setPort(ID::A, sig); } - for (int i = 0; i < GetSize(sig_s); i++) - n = make_inner(sig_s[i], State::S0, n); + if (!is_bwmux) { + for (int i = 0; i < GetSize(sig_s); i++) + n = make_inner(sig_s[i], State::S0, n); + } else { + n = make_inner(sig_s[index], State::S0, n); + } children.insert(n); } - for (int i = 0; i < GetSize(sig_s); i++) { + for (int i = 0; i < (is_bwmux ? 1 : GetSize(sig_s)); i++) { n = find_mux_feedback(sig_b[i*width + index], needle, set_undef); if (n != false_node) { if (set_undef && sig_b[i*width + index] == needle) { @@ -213,7 +219,7 @@ struct proc_dlatch_db_t sig[i*width + index] = State::Sx; cell->setPort(ID::B, sig); } - children.insert(make_inner(sig_s[i], State::S1, n)); + children.insert(make_inner(sig_s[is_bwmux ? index : i], State::S1, n)); } } diff --git a/passes/proc/proc_rom.cc b/passes/proc/proc_rom.cc index b83466ce75a..ebc2377aafa 100644 --- a/passes/proc/proc_rom.cc +++ b/passes/proc/proc_rom.cc @@ -66,6 +66,11 @@ struct RomWorker } } + if (lhs.empty()) { + log_debug("rejecting switch: lhs empty\n"); + return; + } + int swsigbits = 0; for (int i = 0; i < GetSize(sw->signal); i++) if (sw->signal[i] != State::S0) diff --git a/passes/sat/clk2fflogic.cc b/passes/sat/clk2fflogic.cc index 2c0e13f852e..bcefa7d8f92 100644 --- a/passes/sat/clk2fflogic.cc +++ b/passes/sat/clk2fflogic.cc @@ -238,7 +238,8 @@ struct Clk2fflogicPass : public Pass { cell->setPort(ID::EN, module->And(NEW_ID, sig_en_sampled, sig_trg_combined)); cell->setPort(ID::ARGS, sig_args_sampled); if (cell->type == ID($check)) { - SigBit sig_a_sampled = sample_data(module, sig_en, State::S1, false, false).sampled; + SigBit sig_a = cell->getPort(ID::A); + SigBit sig_a_sampled = sample_data(module, sig_a, State::S1, false, false).sampled; cell->setPort(ID::A, sig_a_sampled); } } diff --git a/passes/techmap/Makefile.inc b/passes/techmap/Makefile.inc index 97d8b76f3b8..9d57e3d71af 100644 --- a/passes/techmap/Makefile.inc +++ b/passes/techmap/Makefile.inc @@ -56,5 +56,5 @@ EXTRA_OBJS += passes/techmap/filterlib.o $(PROGRAM_PREFIX)yosys-filterlib$(EXE): passes/techmap/filterlib.o $(Q) mkdir -p $(dir $@) - $(P) $(LD) -o $(PROGRAM_PREFIX)yosys-filterlib$(EXE) $(LDFLAGS) $^ $(LDLIBS) + $(P) $(CXX) -o $(PROGRAM_PREFIX)yosys-filterlib$(EXE) $(LINKFLAGS) $^ $(LIBS) endif diff --git a/passes/techmap/dfflibmap.cc b/passes/techmap/dfflibmap.cc index 12c3a95de89..78bfe1586ff 100644 --- a/passes/techmap/dfflibmap.cc +++ b/passes/techmap/dfflibmap.cc @@ -115,7 +115,7 @@ static bool parse_pin(LibertyAst *cell, LibertyAst *attr, std::string &pin_name, return false; } -static void find_cell(LibertyAst *ast, IdString cell_type, bool clkpol, bool has_reset, bool rstpol, bool rstval) +static void find_cell(LibertyAst *ast, IdString cell_type, bool clkpol, bool has_reset, bool rstpol, bool rstval, std::vector &dont_use_cells) { LibertyAst *best_cell = nullptr; std::map best_cell_ports; @@ -135,6 +135,18 @@ static void find_cell(LibertyAst *ast, IdString cell_type, bool clkpol, bool has if (dn != nullptr && dn->value == "true") continue; + bool dont_use = false; + for (std::string &dont_use_cell : dont_use_cells) + { + if (patmatch(dont_use_cell.c_str(), cell->args[0].c_str())) + { + dont_use = true; + break; + } + } + if (dont_use) + continue; + LibertyAst *ff = cell->find("ff"); if (ff == nullptr) continue; @@ -227,7 +239,7 @@ static void find_cell(LibertyAst *ast, IdString cell_type, bool clkpol, bool has } } -static void find_cell_sr(LibertyAst *ast, IdString cell_type, bool clkpol, bool setpol, bool clrpol) +static void find_cell_sr(LibertyAst *ast, IdString cell_type, bool clkpol, bool setpol, bool clrpol, std::vector &dont_use_cells) { LibertyAst *best_cell = nullptr; std::map best_cell_ports; @@ -247,6 +259,18 @@ static void find_cell_sr(LibertyAst *ast, IdString cell_type, bool clkpol, bool if (dn != nullptr && dn->value == "true") continue; + bool dont_use = false; + for (std::string &dont_use_cell : dont_use_cells) + { + if (patmatch(dont_use_cell.c_str(), cell->args[0].c_str())) + { + dont_use = true; + break; + } + } + if (dont_use) + continue; + LibertyAst *ff = cell->find("ff"); if (ff == nullptr) continue; @@ -414,7 +438,7 @@ struct DfflibmapPass : public Pass { void help() override { log("\n"); - log(" dfflibmap [-prepare] [-map-only] [-info] -liberty [selection]\n"); + log(" dfflibmap [-prepare] [-map-only] [-info] [-dont_use ] -liberty [selection]\n"); log("\n"); log("Map internal flip-flop cells to the flip-flop cells in the technology\n"); log("library specified in the given liberty file.\n"); @@ -435,6 +459,11 @@ struct DfflibmapPass : public Pass { log("that would be passed to the dfflegalize pass. The design will not be\n"); log("changed.\n"); log("\n"); + log("When called with -dont_use, this command will not map to the specified cell\n"); + log("name as an alternative to setting the dont_use property in the Liberty file.\n"); + log("This argument can be called multiple times with different cell names. This\n"); + log("argument also supports simple glob patterns in the cell name.\n"); + log("\n"); } void execute(std::vector args, RTLIL::Design *design) override { @@ -446,6 +475,8 @@ struct DfflibmapPass : public Pass { bool map_only_mode = false; bool info_mode = false; + std::vector dont_use_cells; + size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { @@ -467,6 +498,10 @@ struct DfflibmapPass : public Pass { info_mode = true; continue; } + if (arg == "-dont_use" && argidx+1 < args.size()) { + dont_use_cells.push_back(args[++argidx]); + continue; + } break; } extra_args(args, argidx, design); @@ -491,26 +526,26 @@ struct DfflibmapPass : public Pass { LibertyParser libparser(f); f.close(); - find_cell(libparser.ast, ID($_DFF_N_), false, false, false, false); - find_cell(libparser.ast, ID($_DFF_P_), true, false, false, false); - - find_cell(libparser.ast, ID($_DFF_NN0_), false, true, false, false); - find_cell(libparser.ast, ID($_DFF_NN1_), false, true, false, true); - find_cell(libparser.ast, ID($_DFF_NP0_), false, true, true, false); - find_cell(libparser.ast, ID($_DFF_NP1_), false, true, true, true); - find_cell(libparser.ast, ID($_DFF_PN0_), true, true, false, false); - find_cell(libparser.ast, ID($_DFF_PN1_), true, true, false, true); - find_cell(libparser.ast, ID($_DFF_PP0_), true, true, true, false); - find_cell(libparser.ast, ID($_DFF_PP1_), true, true, true, true); - - find_cell_sr(libparser.ast, ID($_DFFSR_NNN_), false, false, false); - find_cell_sr(libparser.ast, ID($_DFFSR_NNP_), false, false, true); - find_cell_sr(libparser.ast, ID($_DFFSR_NPN_), false, true, false); - find_cell_sr(libparser.ast, ID($_DFFSR_NPP_), false, true, true); - find_cell_sr(libparser.ast, ID($_DFFSR_PNN_), true, false, false); - find_cell_sr(libparser.ast, ID($_DFFSR_PNP_), true, false, true); - find_cell_sr(libparser.ast, ID($_DFFSR_PPN_), true, true, false); - find_cell_sr(libparser.ast, ID($_DFFSR_PPP_), true, true, true); + find_cell(libparser.ast, ID($_DFF_N_), false, false, false, false, dont_use_cells); + find_cell(libparser.ast, ID($_DFF_P_), true, false, false, false, dont_use_cells); + + find_cell(libparser.ast, ID($_DFF_NN0_), false, true, false, false, dont_use_cells); + find_cell(libparser.ast, ID($_DFF_NN1_), false, true, false, true, dont_use_cells); + find_cell(libparser.ast, ID($_DFF_NP0_), false, true, true, false, dont_use_cells); + find_cell(libparser.ast, ID($_DFF_NP1_), false, true, true, true, dont_use_cells); + find_cell(libparser.ast, ID($_DFF_PN0_), true, true, false, false, dont_use_cells); + find_cell(libparser.ast, ID($_DFF_PN1_), true, true, false, true, dont_use_cells); + find_cell(libparser.ast, ID($_DFF_PP0_), true, true, true, false, dont_use_cells); + find_cell(libparser.ast, ID($_DFF_PP1_), true, true, true, true, dont_use_cells); + + find_cell_sr(libparser.ast, ID($_DFFSR_NNN_), false, false, false, dont_use_cells); + find_cell_sr(libparser.ast, ID($_DFFSR_NNP_), false, false, true, dont_use_cells); + find_cell_sr(libparser.ast, ID($_DFFSR_NPN_), false, true, false, dont_use_cells); + find_cell_sr(libparser.ast, ID($_DFFSR_NPP_), false, true, true, dont_use_cells); + find_cell_sr(libparser.ast, ID($_DFFSR_PNN_), true, false, false, dont_use_cells); + find_cell_sr(libparser.ast, ID($_DFFSR_PNP_), true, false, true, dont_use_cells); + find_cell_sr(libparser.ast, ID($_DFFSR_PPN_), true, true, false, dont_use_cells); + find_cell_sr(libparser.ast, ID($_DFFSR_PPP_), true, true, true, dont_use_cells); log(" final dff cell mappings:\n"); logmap_all(); diff --git a/passes/techmap/flatten.cc b/passes/techmap/flatten.cc index 4ddc4aff1fa..ea5855a09a2 100644 --- a/passes/techmap/flatten.cc +++ b/passes/techmap/flatten.cc @@ -46,24 +46,6 @@ IdString map_name(RTLIL::Cell *cell, T *object) return cell->module->uniquify(concat_name(cell, object->name)); } -template -void map_attributes(RTLIL::Cell *cell, T *object, IdString orig_object_name) -{ - if (object->has_attribute(ID::src)) - object->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); - - // Preserve original names via the hdlname attribute, but only for objects with a fully public name. - if (cell->name[0] == '\\' && (object->has_attribute(ID::hdlname) || orig_object_name[0] == '\\')) { - std::vector hierarchy; - if (object->has_attribute(ID::hdlname)) - hierarchy = object->get_hdlname_attribute(); - else - hierarchy.push_back(orig_object_name.str().substr(1)); - hierarchy.insert(hierarchy.begin(), cell->name.str().substr(1)); - object->set_hdlname_attribute(hierarchy); - } -} - void map_sigspec(const dict &map, RTLIL::SigSpec &sig, RTLIL::Module *into = nullptr) { vector chunks = sig; @@ -76,6 +58,54 @@ void map_sigspec(const dict &map, RTLIL::SigSpec &si struct FlattenWorker { bool ignore_wb = false; + bool create_scopeinfo = true; + bool create_scopename = false; + + template + void map_attributes(RTLIL::Cell *cell, T *object, IdString orig_object_name) + { + if (!create_scopeinfo && object->has_attribute(ID::src)) + object->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + + // Preserve original names via the hdlname attribute, but only for objects with a fully public name. + // If the '-scopename' option is used, also preserve the containing scope of private objects if their scope is fully public. + if (cell->name[0] == '\\') { + if (object->has_attribute(ID::hdlname) || orig_object_name[0] == '\\') { + std::string new_hdlname; + + if (cell->has_attribute(ID::hdlname)) { + new_hdlname = cell->get_string_attribute(ID(hdlname)); + } else { + log_assert(!cell->name.empty()); + new_hdlname = cell->name.c_str() + 1; + } + new_hdlname += ' '; + + if (object->has_attribute(ID::hdlname)) { + new_hdlname += object->get_string_attribute(ID(hdlname)); + } else { + log_assert(!orig_object_name.empty()); + new_hdlname += orig_object_name.c_str() + 1; + } + object->set_string_attribute(ID(hdlname), new_hdlname); + } else if (object->has_attribute(ID(scopename))) { + std::string new_scopename; + + if (cell->has_attribute(ID::hdlname)) { + new_scopename = cell->get_string_attribute(ID(hdlname)); + } else { + log_assert(!cell->name.empty()); + new_scopename = cell->name.c_str() + 1; + } + new_scopename += ' '; + new_scopename += object->get_string_attribute(ID(scopename)); + object->set_string_attribute(ID(scopename), new_scopename); + } else if (create_scopename) { + log_assert(!cell->name.empty()); + object->set_string_attribute(ID(scopename), cell->name.c_str() + 1); + } + } + } void flatten_cell(RTLIL::Design *design, RTLIL::Module *module, RTLIL::Cell *cell, RTLIL::Module *tpl, SigMap &sigmap, std::vector &new_cells) { @@ -220,7 +250,33 @@ struct FlattenWorker sigmap.add(new_conn.first, new_conn.second); } + RTLIL::Cell *scopeinfo = nullptr; + RTLIL::IdString cell_name = cell->name; + + if (create_scopeinfo && cell_name.isPublic()) + { + // The $scopeinfo's name will be changed below after removing the flattened cell + scopeinfo = module->addCell(NEW_ID, ID($scopeinfo)); + scopeinfo->setParam(ID::TYPE, RTLIL::Const("module")); + + for (auto const &attr : cell->attributes) + { + if (attr.first == ID::hdlname) + scopeinfo->attributes.insert(attr); + else + scopeinfo->attributes.emplace(stringf("\\cell_%s", RTLIL::unescape_id(attr.first).c_str()), attr.second); + } + + for (auto const &attr : tpl->attributes) + scopeinfo->attributes.emplace(stringf("\\module_%s", RTLIL::unescape_id(attr.first).c_str()), attr.second); + + scopeinfo->attributes.emplace(ID(module), RTLIL::unescape_id(tpl->name)); + } + module->remove(cell); + + if (scopeinfo != nullptr) + module->rename(scopeinfo, cell_name); } void flatten_module(RTLIL::Design *design, RTLIL::Module *module, pool &used_modules) @@ -275,6 +331,20 @@ struct FlattenPass : public Pass { log(" -wb\n"); log(" Ignore the 'whitebox' attribute on cell implementations.\n"); log("\n"); + log(" -noscopeinfo\n"); + log(" Do not create '$scopeinfo' cells that preserve attributes of cells and\n"); + log(" modules that were removed during flattening. With this option, the\n"); + log(" 'src' attribute of a given cell is merged into all objects replacing\n"); + log(" that cell, with multiple distinct 'src' locations separated by '|'.\n"); + log(" Without this option these 'src' locations can be found via the\n"); + log(" cell_src' and 'module_src' attribute of '$scopeinfo' cells.\n"); + log("\n"); + log(" -scopename\n"); + log(" Create 'scopename' attributes for objects with a private name. This\n"); + log(" attribute records the 'hdlname' of the enclosing scope. For objects\n"); + log(" with a public name the enclosing scope can be found via their\n"); + log(" 'hdlname' attribute.\n"); + log("\n"); } void execute(std::vector args, RTLIL::Design *design) override { @@ -289,6 +359,14 @@ struct FlattenPass : public Pass { worker.ignore_wb = true; continue; } + if (args[argidx] == "-noscopeinfo") { + worker.create_scopeinfo = false; + continue; + } + if (args[argidx] == "-scopename") { + worker.create_scopename = true; + continue; + } break; } extra_args(args, argidx, design); diff --git a/techlibs/common/simlib.v b/techlibs/common/simlib.v index 930d2000b4d..489281f26a0 100644 --- a/techlibs/common/simlib.v +++ b/techlibs/common/simlib.v @@ -2763,3 +2763,10 @@ assign Y = A; endmodule // -------------------------------------------------------- + +(* noblackbox *) +module \$scopeinfo (); + +parameter TYPE = ""; + +endmodule diff --git a/tests/gen-tests-makefile.sh b/tests/gen-tests-makefile.sh index 6bf91b4dc34..2b26d8c981c 100755 --- a/tests/gen-tests-makefile.sh +++ b/tests/gen-tests-makefile.sh @@ -16,8 +16,8 @@ generate_target() { # $ generate_ys_test ys_file [yosys_args] generate_ys_test() { ys_file=$1 - yosys_args=${2:-} - generate_target "$ys_file" "\"$YOSYS_BASEDIR/yosys\" -ql ${ys_file%.*}.log $yosys_args $ys_file" + yosys_args_=${2:-} + generate_target "$ys_file" "\"$YOSYS_BASEDIR/yosys\" -ql ${ys_file%.*}.log $yosys_args_ $ys_file" } # $ generate_bash_test bash_file diff --git a/tests/proc/proc_rom.ys b/tests/proc/proc_rom.ys index 0ef2e2c6119..93fd5002bd8 100644 --- a/tests/proc/proc_rom.ys +++ b/tests/proc/proc_rom.ys @@ -186,4 +186,27 @@ design -stash preopt equiv_opt -assert -run prepare: dummy +design -reset + +read_ilang < clk2fflogic_effects.iv.log -sort clk2fflogic_effects.iv.log > clk2fflogic_effects.iv.sorted.log -tail +3 clk2fflogic_effects.sim.log | sort > clk2fflogic_effects.sim.sorted.log -tail +3 clk2fflogic_effects.clk2fflogic.log | sort > clk2fflogic_effects.clk2fflogic.sorted.log +gawk '/([0-9]+):/{T=$1;print};/^Failed/{print T,$0}' clk2fflogic_effects.iv.log | sort > clk2fflogic_effects.iv.sorted.log +gawk '/([0-9]+):/{T=$1;print};/^Failed/{print T,$0}' clk2fflogic_effects.sim.log | sort > clk2fflogic_effects.sim.sorted.log +gawk '/([0-9]+):/{T=$1;print};/^Failed/{print T,$0}' clk2fflogic_effects.clk2fflogic.log | sort > clk2fflogic_effects.clk2fflogic.sorted.log +echo Comparing iverilog sim vs yosys sim cmp clk2fflogic_effects.iv.sorted.log clk2fflogic_effects.sim.sorted.log +echo Comparing iverilog sim vs yosys clk2fflogic sim cmp clk2fflogic_effects.iv.sorted.log clk2fflogic_effects.clk2fflogic.sorted.log diff --git a/tests/various/clk2fflogic_effects.sv b/tests/various/clk2fflogic_effects.sv index b571cf3fe7d..b38aba3c9bb 100644 --- a/tests/various/clk2fflogic_effects.sv +++ b/tests/various/clk2fflogic_effects.sv @@ -7,7 +7,7 @@ reg clk = 0; always @(posedge gclk) clk <= !clk; -reg [4:0] counter = 0; +reg [5:0] counter = 0; reg eff_0_trg = '0; reg eff_0_en = '0; @@ -20,6 +20,10 @@ reg eff_2_trgA = '0; reg eff_2_trgB = '0; reg eff_2_en = '0; +reg eff_3_trg = '0; +reg eff_3_en = '0; +reg eff_3_a = '0; + `ifdef FAST always @(posedge gclk) begin `else @@ -37,6 +41,10 @@ always @(posedge clk) begin eff_2_trgA = counter[0]; eff_2_trgB = !counter[0]; eff_2_en <= 32'b00000000000000000000001111111100 >> counter; + + eff_3_trg = 32'b10101010101010101010101010101010 >> counter; + eff_3_en <= 32'b11101110010001001110111001000100 >> counter; + eff_3_a <= 32'b11111010111110100101000001010000 >> counter; end always @(posedge eff_0_trg) @@ -71,11 +79,22 @@ always @(posedge eff_2_trgA, posedge eff_2_trgB) if (eff_2_en) $display("repeated"); +always @(posedge eff_3_trg) + if (eff_3_en) begin + $display("%02d: eff3 vvv", counter); +`ifdef NO_ASSERT + if (!eff_3_a) + $display("Failed assertion eff3 at"); +`else + eff3: assert(eff_3_a); +`endif + end + `ifdef __ICARUS__ initial gclk = 0; always @(gclk) gclk <= #5 !gclk; always @(posedge gclk) - if (counter == 31) + if (counter == 32) $finish(0); `endif diff --git a/tests/various/scopeinfo.ys b/tests/various/scopeinfo.ys new file mode 100644 index 00000000000..f8d4ca31b7f --- /dev/null +++ b/tests/various/scopeinfo.ys @@ -0,0 +1,110 @@ +read_verilog <