diff options
49 files changed, 4087 insertions, 325 deletions
@@ -42,6 +42,7 @@ SANITIZER = # SANITIZER = undefined # SANITIZER = cfi +PROGRAM_PREFIX := OS := $(shell uname -s) PREFIX ?= /usr/local @@ -51,16 +52,20 @@ ifneq ($(wildcard Makefile.conf),) include Makefile.conf endif +ifeq ($(ENABLE_PYOSYS),1) +ENABLE_LIBYOSYS := 1 +endif + BINDIR := $(PREFIX)/bin -LIBDIR := $(PREFIX)/lib -DATDIR := $(PREFIX)/share/yosys +LIBDIR := $(PREFIX)/lib/$(PROGRAM_PREFIX)yosys +DATDIR := $(PREFIX)/share/$(PROGRAM_PREFIX)yosys EXE = OBJS = GENFILES = EXTRA_OBJS = EXTRA_TARGETS = -TARGETS = yosys$(EXE) yosys-config +TARGETS = $(PROGRAM_PREFIX)yosys$(EXE) $(PROGRAM_PREFIX)yosys-config PRETTY = 1 SMALL = 0 @@ -245,7 +250,7 @@ LDFLAGS += $(EMCCFLAGS) LDLIBS = EXE = .js -TARGETS := $(filter-out yosys-config,$(TARGETS)) +TARGETS := $(filter-out $(PROGRAM_PREFIX)yosys-config,$(TARGETS)) EXTRA_TARGETS += yosysjs-$(YOSYS_VER).zip ifeq ($(ENABLE_ABC),1) @@ -459,7 +464,7 @@ LDLIBS += -lpthread endif else ifeq ($(ABCEXTERNAL),) -TARGETS += yosys-abc$(EXE) +TARGETS += $(PROGRAM_PREFIX)yosys-abc$(EXE) endif endif endif @@ -509,8 +514,8 @@ endef ifeq ($(PRETTY), 1) P_STATUS = 0 P_OFFSET = 0 -P_UPDATE = $(eval P_STATUS=$(shell echo $(OBJS) yosys$(EXE) | $(AWK) 'BEGIN { RS = " "; I = $(P_STATUS)+0; } $$1 == "$@" && NR > I { I = NR; } END { print I; }')) -P_SHOW = [$(shell $(AWK) "BEGIN { N=$(words $(OBJS) yosys$(EXE)); printf \"%3d\", $(P_OFFSET)+90*$(P_STATUS)/N; exit; }")%] +P_UPDATE = $(eval P_STATUS=$(shell echo $(OBJS) $(PROGRAM_PREFIX)yosys$(EXE) | $(AWK) 'BEGIN { RS = " "; I = $(P_STATUS)+0; } $$1 == "$@" && NR > I { I = NR; } END { print I; }')) +P_SHOW = [$(shell $(AWK) "BEGIN { N=$(words $(OBJS) $(PROGRAM_PREFIX)yosys$(EXE)); printf \"%3d\", $(P_OFFSET)+90*$(P_STATUS)/N; exit; }")%] P = @echo "$(if $(findstring $@,$(TARGETS) $(EXTRA_TARGETS)),$(eval P_OFFSET = 10))$(call P_UPDATE)$(call P_SHOW) Building $@"; Q = @ S = -s @@ -529,6 +534,7 @@ $(eval $(call add_include_file,kernel/register.h)) $(eval $(call add_include_file,kernel/celltypes.h)) $(eval $(call add_include_file,kernel/celledges.h)) $(eval $(call add_include_file,kernel/consteval.h)) +$(eval $(call add_include_file,kernel/constids.inc)) $(eval $(call add_include_file,kernel/sigtools.h)) $(eval $(call add_include_file,kernel/modtools.h)) $(eval $(call add_include_file,kernel/macc.h)) @@ -541,12 +547,13 @@ $(eval $(call add_include_file,libs/json11/json11.hpp)) $(eval $(call add_include_file,passes/fsm/fsmdata.h)) $(eval $(call add_include_file,frontends/ast/ast.h)) $(eval $(call add_include_file,backends/ilang/ilang_backend.h)) +$(eval $(call add_include_file,backends/cxxrtl/cxxrtl.h)) OBJS += kernel/driver.o kernel/register.o kernel/rtlil.o kernel/log.o kernel/calc.o kernel/yosys.o OBJS += kernel/cellaigs.o kernel/celledges.o kernel/log.o: CXXFLAGS += -DYOSYS_SRC='"$(YOSYS_SRC)"' -kernel/yosys.o: CXXFLAGS += -DYOSYS_DATDIR='"$(DATDIR)"' +kernel/yosys.o: CXXFLAGS += -DYOSYS_DATDIR='"$(DATDIR)"' -DYOSYS_PROGRAM_PREFIX='"$(PROGRAM_PREFIX)"' OBJS += libs/bigint/BigIntegerAlgorithms.o libs/bigint/BigInteger.o libs/bigint/BigIntegerUtils.o OBJS += libs/bigint/BigUnsigned.o libs/bigint/BigUnsignedInABase.o @@ -599,7 +606,7 @@ include techlibs/common/Makefile.inc endif ifeq ($(LINK_ABC),1) -OBJS += yosys-libabc.a +OBJS += $(PROGRAM_PREFIX)yosys-libabc.a endif top-all: $(TARGETS) $(EXTRA_TARGETS) @@ -611,14 +618,14 @@ ifeq ($(CONFIG),emcc) yosys.js: $(filter-out yosysjs-$(YOSYS_VER).zip,$(EXTRA_TARGETS)) endif -yosys$(EXE): $(OBJS) - $(P) $(LD) -o yosys$(EXE) $(LDFLAGS) $(OBJS) $(LDLIBS) +$(PROGRAM_PREFIX)yosys$(EXE): $(OBJS) + $(P) $(LD) -o $(PROGRAM_PREFIX)yosys$(EXE) $(LDFLAGS) $(OBJS) $(LDLIBS) libyosys.so: $(filter-out kernel/driver.o,$(OBJS)) ifeq ($(OS), Darwin) - $(P) $(LD) -o libyosys.so -shared -Wl,-install_name,libyosys.so $(LDFLAGS) $^ $(LDLIBS) + $(P) $(LD) -o libyosys.so -shared -Wl,-install_name,$(DESTDIR)$(LIBDIR)/libyosys.so $(LDFLAGS) $^ $(LDLIBS) else - $(P) $(LD) -o libyosys.so -shared -Wl,-soname,libyosys.so $(LDFLAGS) $^ $(LDLIBS) + $(P) $(LD) -o libyosys.so -shared -Wl,-soname,$(DESTDIR)$(LIBDIR)/libyosys.so $(LDFLAGS) $^ $(LDLIBS) endif %.o: %.cc @@ -654,11 +661,11 @@ CXXFLAGS_NOVERIFIC = $(CXXFLAGS) LDLIBS_NOVERIFIC = $(LDLIBS) endif -yosys-config: misc/yosys-config.in +$(PROGRAM_PREFIX)yosys-config: misc/yosys-config.in $(P) $(SED) -e 's#@CXXFLAGS@#$(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))#;' \ - -e 's#@BINDIR@#$(strip $(BINDIR))#;' -e 's#@DATDIR@#$(strip $(DATDIR))#;' < $< > yosys-config - $(Q) chmod +x yosys-config + -e 's#@BINDIR@#$(strip $(BINDIR))#;' -e 's#@DATDIR@#$(strip $(DATDIR))#;' < $< > $(PROGRAM_PREFIX)yosys-config + $(Q) chmod +x $(PROGRAM_PREFIX)yosys-config abc/abc-$(ABCREV)$(EXE) abc/libabc-$(ABCREV).a: $(P) @@ -685,11 +692,11 @@ ifeq ($(ABCREV),default) .PHONY: abc/libabc-$(ABCREV).a endif -yosys-abc$(EXE): abc/abc-$(ABCREV)$(EXE) - $(P) cp abc/abc-$(ABCREV)$(EXE) yosys-abc$(EXE) +$(PROGRAM_PREFIX)yosys-abc$(EXE): abc/abc-$(ABCREV)$(EXE) + $(P) cp abc/abc-$(ABCREV)$(EXE) $(PROGRAM_PREFIX)yosys-abc$(EXE) -yosys-libabc.a: abc/libabc-$(ABCREV).a - $(P) cp abc/libabc-$(ABCREV).a yosys-libabc.a +$(PROGRAM_PREFIX)yosys-libabc.a: abc/libabc-$(ABCREV).a + $(P) cp abc/libabc-$(ABCREV).a $(PROGRAM_PREFIX)yosys-libabc.a ifneq ($(SEED),) SEEDOPT="-S $(SEED)" @@ -769,14 +776,14 @@ clean-unit-test: install: $(TARGETS) $(EXTRA_TARGETS) $(INSTALL_SUDO) mkdir -p $(DESTDIR)$(BINDIR) $(INSTALL_SUDO) cp $(filter-out libyosys.so,$(TARGETS)) $(DESTDIR)$(BINDIR) -ifneq ($(filter yosys,$(TARGETS)),) - $(INSTALL_SUDO) $(STRIP) -S $(DESTDIR)$(BINDIR)/yosys +ifneq ($(filter $(PROGRAM_PREFIX)yosys,$(TARGETS)),) + $(INSTALL_SUDO) $(STRIP) -S $(DESTDIR)$(BINDIR)/$(PROGRAM_PREFIX)yosys endif -ifneq ($(filter yosys-abc,$(TARGETS)),) - $(INSTALL_SUDO) $(STRIP) $(DESTDIR)$(BINDIR)/yosys-abc +ifneq ($(filter $(PROGRAM_PREFIX)yosys-abc,$(TARGETS)),) + $(INSTALL_SUDO) $(STRIP) $(DESTDIR)$(BINDIR)/$(PROGRAM_PREFIX)yosys-abc endif -ifneq ($(filter yosys-filterlib,$(TARGETS)),) - $(INSTALL_SUDO) $(STRIP) $(DESTDIR)$(BINDIR)/yosys-filterlib +ifneq ($(filter $(PROGRAM_PREFIX)yosys-filterlib,$(TARGETS)),) + $(INSTALL_SUDO) $(STRIP) $(DESTDIR)$(BINDIR)/$(PROGRAM_PREFIX)yosys-filterlib endif $(INSTALL_SUDO) mkdir -p $(DESTDIR)$(DATDIR) $(INSTALL_SUDO) cp -r share/. $(DESTDIR)$(DATDIR)/. @@ -785,9 +792,9 @@ ifeq ($(ENABLE_LIBYOSYS),1) $(INSTALL_SUDO) cp libyosys.so $(DESTDIR)$(LIBDIR)/ $(INSTALL_SUDO) $(STRIP) -S $(DESTDIR)$(LIBDIR)/libyosys.so ifeq ($(ENABLE_PYOSYS),1) - $(INSTALL_SUDO) mkdir -p $(PYTHON_DESTDIR)/pyosys - $(INSTALL_SUDO) cp libyosys.so $(PYTHON_DESTDIR)/pyosys/ - $(INSTALL_SUDO) cp misc/__init__.py $(PYTHON_DESTDIR)/pyosys/ + $(INSTALL_SUDO) mkdir -p $(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys + $(INSTALL_SUDO) cp libyosys.so $(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys/libyosys.so + $(INSTALL_SUDO) cp misc/__init__.py $(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys/ endif endif @@ -797,14 +804,14 @@ uninstall: ifeq ($(ENABLE_LIBYOSYS),1) $(INSTALL_SUDO) rm -vf $(DESTDIR)$(LIBDIR)/libyosys.so ifeq ($(ENABLE_PYOSYS),1) - $(INSTALL_SUDO) rm -vf $(PYTHON_DESTDIR)/pyosys/libyosys.so - $(INSTALL_SUDO) rm -vf $(PYTHON_DESTDIR)/pyosys/__init__.py - $(INSTALL_SUDO) rmdir $(PYTHON_DESTDIR)/pyosys + $(INSTALL_SUDO) rm -vf $(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys/libyosys.so + $(INSTALL_SUDO) rm -vf $(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys/__init__.py + $(INSTALL_SUDO) rmdir $(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys endif endif update-manual: $(TARGETS) $(EXTRA_TARGETS) - cd manual && ../yosys -p 'help -write-tex-command-reference-manual' + cd manual && ../$(PROGRAM_PREFIX)yosys -p 'help -write-tex-command-reference-manual' manual: $(TARGETS) $(EXTRA_TARGETS) cd manual && bash appnotes.sh @@ -830,13 +837,13 @@ clean: clean-abc: $(MAKE) -C abc DEP= clean - rm -f yosys-abc$(EXE) yosys-libabc.a abc/abc-[0-9a-f]* abc/libabc-[0-9a-f]*.a + rm -f $(PROGRAM_PREFIX)yosys-abc$(EXE) $(PROGRAM_PREFIX)yosys-libabc.a abc/abc-[0-9a-f]* abc/libabc-[0-9a-f]*.a mrproper: clean git clean -xdf coverage: - ./yosys -qp 'help; help -all' + ./$(PROGRAM_PREFIX)yosys -qp 'help; help -all' rm -rf coverage.info coverage_html lcov --capture -d . --no-external -o coverage.info genhtml coverage.info --output-directory coverage_html @@ -862,9 +869,9 @@ ifeq ($(CONFIG),mxe) mxebin: $(TARGETS) $(EXTRA_TARGETS) rm -rf yosys-win32-mxebin-$(YOSYS_VER){,.zip} mkdir -p yosys-win32-mxebin-$(YOSYS_VER) - cp -r yosys.exe share/ yosys-win32-mxebin-$(YOSYS_VER)/ + cp -r $(PROGRAM_PREFIX)yosys.exe share/ yosys-win32-mxebin-$(YOSYS_VER)/ ifeq ($(ENABLE_ABC),1) - cp -r yosys-abc.exe abc/lib/x86/pthreadVC2.dll yosys-win32-mxebin-$(YOSYS_VER)/ + cp -r $(PROGRAM_PREFIX)yosys-abc.exe abc/lib/x86/pthreadVC2.dll yosys-win32-mxebin-$(YOSYS_VER)/ endif echo -en 'This is Yosys $(YOSYS_VER) for Win32.\r\n' > yosys-win32-mxebin-$(YOSYS_VER)/readme.txt echo -en 'Documentation at http://www.clifford.at/yosys/.\r\n' >> yosys-win32-mxebin-$(YOSYS_VER)/readme.txt diff --git a/backends/cxxrtl/Makefile.inc b/backends/cxxrtl/Makefile.inc new file mode 100644 index 000000000..f93e65f85 --- /dev/null +++ b/backends/cxxrtl/Makefile.inc @@ -0,0 +1,2 @@ + +OBJS += backends/cxxrtl/cxxrtl.o diff --git a/backends/cxxrtl/cxxrtl.cc b/backends/cxxrtl/cxxrtl.cc new file mode 100644 index 000000000..465882858 --- /dev/null +++ b/backends/cxxrtl/cxxrtl.cc @@ -0,0 +1,1688 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2019-2020 whitequark <whitequark@whitequark.org> + * + * 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/rtlil.h" +#include "kernel/register.h" +#include "kernel/sigtools.h" +#include "kernel/utils.h" +#include "kernel/celltypes.h" +#include "kernel/log.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +// [[CITE]] +// Peter Eades; Xuemin Lin; W. F. Smyth, "A Fast Effective Heuristic For The Feedback Arc Set Problem" +// Information Processing Letters, Vol. 47, pp 319-323, 1993 +// https://pdfs.semanticscholar.org/c7ed/d9acce96ca357876540e19664eb9d976637f.pdf + +// A topological sort (on a cell/wire graph) is always possible in a fully flattened RTLIL design without +// processes or logic loops where every wire has a single driver. Logic loops are illegal in RTLIL and wires +// with multiple drivers can be split by the `splitnets` pass; however, interdependencies between processes +// or module instances can create strongly connected components without introducing evaluation nondeterminism. +// We wish to support designs with such benign SCCs (as well as designs with multiple drivers per wire), so +// we sort the graph in a way that minimizes feedback arcs. If there are no feedback arcs in the sorted graph, +// then a more efficient evaluation method is possible, since eval() will always immediately converge. +template<class T> +struct Scheduler { + struct Vertex { + T *data; + Vertex *prev, *next; + pool<Vertex*, hash_ptr_ops> preds, succs; + + Vertex() : data(NULL), prev(this), next(this) {} + Vertex(T *data) : data(data), prev(NULL), next(NULL) {} + + bool empty() const + { + log_assert(data == NULL); + if (next == this) { + log_assert(prev == next); + return true; + } + return false; + } + + void link(Vertex *list) + { + log_assert(prev == NULL && next == NULL); + next = list; + prev = list->prev; + list->prev->next = this; + list->prev = this; + } + + void unlink() + { + log_assert(prev->next == this && next->prev == this); + prev->next = next; + next->prev = prev; + next = prev = NULL; + } + + int delta() const + { + return succs.size() - preds.size(); + } + }; + + std::vector<Vertex*> vertices; + Vertex *sources = new Vertex; + Vertex *sinks = new Vertex; + dict<int, Vertex*> bins; + + ~Scheduler() + { + delete sources; + delete sinks; + for (auto bin : bins) + delete bin.second; + for (auto vertex : vertices) + delete vertex; + } + + Vertex *add(T *data) + { + Vertex *vertex = new Vertex(data); + vertices.push_back(vertex); + return vertex; + } + + void relink(Vertex *vertex) + { + if (vertex->succs.empty()) + vertex->link(sinks); + else if (vertex->preds.empty()) + vertex->link(sources); + else { + int delta = vertex->delta(); + if (!bins.count(delta)) + bins[delta] = new Vertex; + vertex->link(bins[delta]); + } + } + + Vertex *remove(Vertex *vertex) + { + vertex->unlink(); + for (auto pred : vertex->preds) { + if (pred == vertex) + continue; + log_assert(pred->succs[vertex]); + pred->unlink(); + pred->succs.erase(vertex); + relink(pred); + } + for (auto succ : vertex->succs) { + if (succ == vertex) + continue; + log_assert(succ->preds[vertex]); + succ->unlink(); + succ->preds.erase(vertex); + relink(succ); + } + vertex->preds.clear(); + vertex->succs.clear(); + return vertex; + } + + std::vector<Vertex*> schedule() + { + std::vector<Vertex*> s1, s2r; + for (auto vertex : vertices) + relink(vertex); + bool bins_empty = false; + while (!(sinks->empty() && sources->empty() && bins_empty)) { + while (!sinks->empty()) + s2r.push_back(remove(sinks->next)); + while (!sources->empty()) + s1.push_back(remove(sources->next)); + // Choosing u in this implementation isn't O(1), but the paper handwaves which data structure they suggest + // using to get O(1) relinking *and* find-max-key ("it is clear"... no it isn't), so this code uses a very + // naive implementation of find-max-key. + bins_empty = true; + bins.template sort<std::greater<int>>(); + for (auto bin : bins) { + if (!bin.second->empty()) { + bins_empty = false; + s1.push_back(remove(bin.second->next)); + break; + } + } + } + s1.insert(s1.end(), s2r.rbegin(), s2r.rend()); + return s1; + } +}; + +static bool is_unary_cell(RTLIL::IdString type) +{ + return type.in( + ID($not), ID($logic_not), ID($reduce_and), ID($reduce_or), ID($reduce_xor), ID($reduce_xnor), ID($reduce_bool), + ID($pos), ID($neg)); +} + +static bool is_binary_cell(RTLIL::IdString type) +{ + return type.in( + ID($and), ID($or), ID($xor), ID($xnor), ID($logic_and), ID($logic_or), + ID($shl), ID($sshl), ID($shr), ID($sshr), ID($shift), ID($shiftx), + ID($eq), ID($ne), ID($eqx), ID($nex), ID($gt), ID($ge), ID($lt), ID($le), + ID($add), ID($sub), ID($mul), ID($div), ID($mod)); +} + +static bool is_elidable_cell(RTLIL::IdString type) +{ + return is_unary_cell(type) || is_binary_cell(type) || type.in( + ID($mux), ID($concat), ID($slice)); +} + +static bool is_sync_ff_cell(RTLIL::IdString type) +{ + return type.in( + ID($dff), ID($dffe)); +} + +static bool is_ff_cell(RTLIL::IdString type) +{ + return is_sync_ff_cell(type) || type.in( + ID($adff), ID($dffsr), ID($dlatch), ID($dlatchsr), ID($sr)); +} + +static bool is_internal_cell(RTLIL::IdString type) +{ + return type[0] == '$' && !type.begins_with("$paramod\\"); +} + +struct FlowGraph { + struct Node { + enum class Type { + CONNECT, + CELL, + PROCESS + }; + + Type type; + RTLIL::SigSig connect = {}; + const RTLIL::Cell *cell = NULL; + const RTLIL::Process *process = NULL; + }; + + std::vector<Node*> nodes; + dict<const RTLIL::Wire*, pool<Node*, hash_ptr_ops>> wire_defs, wire_uses; + dict<const RTLIL::Wire*, bool> wire_def_elidable, wire_use_elidable; + + ~FlowGraph() + { + for (auto node : nodes) + delete node; + } + + void add_defs(Node *node, const RTLIL::SigSpec &sig, bool elidable) + { + for (auto chunk : sig.chunks()) + if (chunk.wire) + wire_defs[chunk.wire].insert(node); + // Only defs of an entire wire in the right order can be elided. + if (sig.is_wire()) + wire_def_elidable[sig.as_wire()] = elidable; + } + + void add_uses(Node *node, const RTLIL::SigSpec &sig) + { + for (auto chunk : sig.chunks()) + if (chunk.wire) { + wire_uses[chunk.wire].insert(node); + // Only a single use of an entire wire in the right order can be elided. + // (But the use can include other chunks.) + if (!wire_use_elidable.count(chunk.wire)) + wire_use_elidable[chunk.wire] = true; + else + wire_use_elidable[chunk.wire] = false; + } + } + + bool is_elidable(const RTLIL::Wire *wire) const + { + if (wire_def_elidable.count(wire) && wire_use_elidable.count(wire)) + return wire_def_elidable.at(wire) && wire_use_elidable.at(wire); + return false; + } + + // Connections + void add_connect_defs_uses(Node *node, const RTLIL::SigSig &conn) + { + add_defs(node, conn.first, /*elidable=*/true); + add_uses(node, conn.second); + } + + Node *add_node(const RTLIL::SigSig &conn) + { + Node *node = new Node; + node->type = Node::Type::CONNECT; + node->connect = conn; + nodes.push_back(node); + add_connect_defs_uses(node, conn); + return node; + } + + // Cells + void add_cell_defs_uses(Node *node, const RTLIL::Cell *cell) + { + log_assert(cell->known()); + for (auto conn : cell->connections()) { + if (cell->output(conn.first)) { + if (is_sync_ff_cell(cell->type) || (cell->type == ID($memrd) && cell->getParam(ID(CLK_ENABLE)).as_bool())) + /* non-combinatorial outputs do not introduce defs */; + else if (is_elidable_cell(cell->type)) + add_defs(node, conn.second, /*elidable=*/true); + else if (is_internal_cell(cell->type)) + add_defs(node, conn.second, /*elidable=*/false); + else { + // Unlike outputs of internal cells (which generate code that depends on the ability to set the output + // wire bits), outputs of user cells are normal wires, and the wires connected to them can be elided. + add_defs(node, conn.second, /*elidable=*/true); + } + } + if (cell->input(conn.first)) + add_uses(node, conn.second); + } + } + + Node *add_node(const RTLIL::Cell *cell) + { + Node *node = new Node; + node->type = Node::Type::CELL; + node->cell = cell; + nodes.push_back(node); + add_cell_defs_uses(node, cell); + return node; + } + + // Processes + void add_case_defs_uses(Node *node, const RTLIL::CaseRule *case_) + { + for (auto &action : case_->actions) { + add_defs(node, action.first, /*elidable=*/false); + add_uses(node, action.second); + } + for (auto sub_switch : case_->switches) { + add_uses(node, sub_switch->signal); + for (auto sub_case : sub_switch->cases) { + for (auto &compare : sub_case->compare) + add_uses(node, compare); + add_case_defs_uses(node, sub_case); + } + } + } + + void add_process_defs_uses(Node *node, const RTLIL::Process *process) + { + add_case_defs_uses(node, &process->root_case); + for (auto sync : process->syncs) + for (auto action : sync->actions) { + if (sync->type == RTLIL::STp || sync->type == RTLIL::STn || sync->type == RTLIL::STe) + /* sync actions do not introduce feedback */; + else + add_defs(node, action.first, /*elidable=*/false); + add_uses(node, action.second); + } + } + + Node *add_node(const RTLIL::Process *process) + { + Node *node = new Node; + node->type = Node::Type::PROCESS; + node->process = process; + nodes.push_back(node); + add_process_defs_uses(node, process); + return node; + } +}; + +struct CxxrtlWorker { + bool elide_internal = false; + bool elide_public = false; + bool localize_internal = false; + bool localize_public = false; + bool run_splitnets = false; + + std::ostream &f; + std::string indent; + int temporary = 0; + + dict<const RTLIL::Module*, SigMap> sigmaps; + pool<const RTLIL::Wire*> sync_wires; + dict<RTLIL::SigBit, RTLIL::SyncType> sync_types; + pool<const RTLIL::Memory*> writable_memories; + dict<const RTLIL::Cell*, pool<const RTLIL::Cell*>> transparent_for; + dict<const RTLIL::Cell*, dict<RTLIL::Wire*, RTLIL::IdString>> cell_wire_defs; + dict<const RTLIL::Wire*, FlowGraph::Node> elided_wires; + dict<const RTLIL::Module*, std::vector<FlowGraph::Node>> schedule; + pool<const RTLIL::Wire*> localized_wires; + + CxxrtlWorker(std::ostream &f) : f(f) {} + + void inc_indent() { + indent += "\t"; + } + void dec_indent() { + indent.resize(indent.size() - 1); + } + + // RTLIL allows any characters in names other than whitespace. This presents an issue for generating C++ code + // because C++ identifiers may be only alphanumeric, cannot clash with C++ keywords, and cannot clash with cxxrtl + // identifiers. This issue can be solved with a name mangling scheme. We choose a name mangling scheme that results + // in readable identifiers, does not depend on an up-to-date list of C++ keywords, and is easy to apply. Its rules: + // 1. All generated identifiers start with `_`. + // 1a. Generated identifiers for public names (beginning with `\`) start with `p_`. + // 1b. Generated identifiers for internal names (beginning with `$`) start with `i_`. + // 2. An underscore is escaped with another underscore, i.e. `__`. + // 3. Any other non-alnum character is escaped with underscores around its lowercase hex code, e.g. `@` as `_40_`. + std::string mangle_name(const RTLIL::IdString &name) + { + std::string mangled; + bool first = true; + for (char c : name.str()) { + if (first) { + first = false; + if (c == '\\') + mangled += "p_"; + else if (c == '$') + mangled += "i_"; + else + log_assert(false); + } else { + if (isalnum(c)) { + mangled += c; + } else if (c == '_') { + mangled += "__"; + } else { + char l = c & 0xf, h = (c >> 4) & 0xf; + mangled += '_'; + mangled += (h < 10 ? '0' + h : 'a' + h - 10); + mangled += (l < 10 ? '0' + l : 'a' + l - 10); + mangled += '_'; + } + } + } + return mangled; + } + + std::string mangle_module_name(const RTLIL::IdString &name) + { + // Class namespace. + return mangle_name(name); + } + + std::string mangle_memory_name(const RTLIL::IdString &name) + { + // Class member namespace. + return "memory_" + mangle_name(name); + } + + std::string mangle_cell_name(const RTLIL::IdString &name) + { + // Class member namespace. + return "cell_" + mangle_name(name); + } + + std::string mangle_wire_name(const RTLIL::IdString &name) + { + // Class member namespace. + return mangle_name(name); + } + + std::string mangle(const RTLIL::Module *module) + { + return mangle_module_name(module->name); + } + + std::string mangle(const RTLIL::Memory *memory) + { + return mangle_memory_name(memory->name); + } + + std::string mangle(const RTLIL::Cell *cell) + { + return mangle_cell_name(cell->name); + } + + std::string mangle(const RTLIL::Wire *wire) + { + return mangle_wire_name(wire->name); + } + + std::string mangle(RTLIL::SigBit sigbit) + { + log_assert(sigbit.wire != NULL); + if (sigbit.wire->width == 1) + return mangle(sigbit.wire); + return mangle(sigbit.wire) + "_" + std::to_string(sigbit.offset); + } + + std::string fresh_temporary() + { + return stringf("tmp_%d", temporary++); + } + + void dump_attrs(const RTLIL::AttrObject *object) + { + for (auto attr : object->attributes) { + f << indent << "// " << attr.first.str() << ": "; + if (attr.second.flags & RTLIL::CONST_FLAG_STRING) { + f << attr.second.decode_string(); + } else { + f << attr.second.as_int(/*is_signed=*/attr.second.flags & RTLIL::CONST_FLAG_SIGNED); + } + f << "\n"; + } + } + + void dump_const_init(const RTLIL::Const &data, int width, int offset = 0, bool fixed_width = false) + { + f << "{"; + while (width > 0) { + const int CHUNK_SIZE = 32; + uint32_t chunk = data.extract(offset, width > CHUNK_SIZE ? CHUNK_SIZE : width).as_int(); + if (fixed_width) + f << stringf("0x%08xu", chunk); + else + f << stringf("%#xu", chunk); + if (width > CHUNK_SIZE) + f << ','; + offset += CHUNK_SIZE; + width -= CHUNK_SIZE; + } + f << "}"; + } + + void dump_const_init(const RTLIL::Const &data) + { + dump_const_init(data, data.size()); + } + + void dump_const(const RTLIL::Const &data, int width, int offset = 0, bool fixed_width = false) + { + f << "value<" << width << ">"; + dump_const_init(data, width, offset, fixed_width); + } + + void dump_const(const RTLIL::Const &data) + { + dump_const(data, data.size()); + } + + bool dump_sigchunk(const RTLIL::SigChunk &chunk, bool is_lhs) + { + if (chunk.wire == NULL) { + dump_const(chunk.data, chunk.width, chunk.offset); + return false; + } else { + if (!is_lhs && elided_wires.count(chunk.wire)) { + const FlowGraph::Node &node = elided_wires[chunk.wire]; + switch (node.type) { + case FlowGraph::Node::Type::CONNECT: + dump_connect_elided(node.connect); + break; + case FlowGraph::Node::Type::CELL: + if (is_elidable_cell(node.cell->type)) { + dump_cell_elided(node.cell); + } else { + f << mangle(node.cell) << "." << mangle_wire_name(cell_wire_defs[node.cell][chunk.wire]) << ".curr"; + } + break; + default: + log_assert(false); + } + } else if (localized_wires[chunk.wire]) { + f << mangle(chunk.wire); + } else { + f << mangle(chunk.wire) << (is_lhs ? ".next" : ".curr"); + } + if (chunk.width == chunk.wire->width && chunk.offset == 0) + return false; + else if (chunk.width == 1) + f << ".slice<" << chunk.offset << ">()"; + else + f << ".slice<" << chunk.offset+chunk.width-1 << "," << chunk.offset << ">()"; + return true; + } + } + + bool dump_sigspec(const RTLIL::SigSpec &sig, bool is_lhs) + { + if (sig.empty()) { + f << "value<0>()"; + return false; + } else if (sig.is_chunk()) { + return dump_sigchunk(sig.as_chunk(), is_lhs); + } else { + dump_sigchunk(*sig.chunks().rbegin(), is_lhs); + for (auto it = sig.chunks().rbegin() + 1; it != sig.chunks().rend(); ++it) { + f << ".concat("; + dump_sigchunk(*it, is_lhs); + f << ")"; + } + return true; + } + } + + void dump_sigspec_lhs(const RTLIL::SigSpec &sig) + { + dump_sigspec(sig, /*is_lhs=*/true); + } + + void dump_sigspec_rhs(const RTLIL::SigSpec &sig) + { + // In the contexts where we want template argument deduction to occur for `template<size_t Bits> ... value<Bits>`, + // it is necessary to have the argument to already be a `value<N>`, since template argument deduction and implicit + // type conversion are mutually exclusive. In these contexts, we use dump_sigspec_rhs() to emit an explicit + // type conversion, but only if the expression needs it. + bool is_complex = dump_sigspec(sig, /*is_lhs=*/false); + if (is_complex) + f << ".val()"; + } + + void collect_sigspec_rhs(const RTLIL::SigSpec &sig, std::vector<RTLIL::IdString> &cells) + { + for (auto chunk : sig.chunks()) { + if (!chunk.wire || !elided_wires.count(chunk.wire)) + continue; + + const FlowGraph::Node &node = elided_wires[chunk.wire]; + switch (node.type) { + case FlowGraph::Node::Type::CONNECT: + collect_connect(node.connect, cells); + break; + case FlowGraph::Node::Type::CELL: + collect_cell(node.cell, cells); + break; + default: + log_assert(false); + } + } + } + + void dump_connect_elided(const RTLIL::SigSig &conn) + { + dump_sigspec_rhs(conn.second); + } + + bool is_connect_elided(const RTLIL::SigSig &conn) + { + return conn.first.is_wire() && elided_wires.count(conn.first.as_wire()); + } + + void collect_connect(const RTLIL::SigSig &conn, std::vector<RTLIL::IdString> &cells) + { + if (!is_connect_elided(conn)) + return; + + collect_sigspec_rhs(conn.second, cells); + } + + void dump_connect(const RTLIL::SigSig &conn) + { + if (is_connect_elided(conn)) + return; + + f << indent << "// connection\n"; + f << indent; + dump_sigspec_lhs(conn.first); + f << " = "; + dump_connect_elided(conn); + f << ";\n"; + } + + void dump_cell_elided(const RTLIL::Cell *cell) + { + // Unary cells + if (is_unary_cell(cell->type)) { + f << cell->type.substr(1) << '_' << + (cell->getParam(ID(A_SIGNED)).as_bool() ? 's' : 'u') << + "<" << cell->getParam(ID(Y_WIDTH)).as_int() << ">("; + dump_sigspec_rhs(cell->getPort(ID(A))); + f << ")"; + // Binary cells + } else if (is_binary_cell(cell->type)) { + f << cell->type.substr(1) << '_' << + (cell->getParam(ID(A_SIGNED)).as_bool() ? 's' : 'u') << + (cell->getParam(ID(B_SIGNED)).as_bool() ? 's' : 'u') << + "<" << cell->getParam(ID(Y_WIDTH)).as_int() << ">("; + dump_sigspec_rhs(cell->getPort(ID(A))); + f << ", "; + dump_sigspec_rhs(cell->getPort(ID(B))); + f << ")"; + // Muxes + } else if (cell->type == ID($mux)) { + f << "("; + dump_sigspec_rhs(cell->getPort(ID(S))); + f << " ? "; + dump_sigspec_rhs(cell->getPort(ID(B))); + f << " : "; + dump_sigspec_rhs(cell->getPort(ID(A))); + f << ")"; + // Concats + } else if (cell->type == ID($concat)) { + dump_sigspec_rhs(cell->getPort(ID(B))); + f << ".concat("; + dump_sigspec_rhs(cell->getPort(ID(A))); + f << ").val()"; + // Slices + } else if (cell->type == ID($slice)) { + dump_sigspec_rhs(cell->getPort(ID(A))); + f << ".slice<"; + f << cell->getParam(ID(OFFSET)).as_int() + cell->getParam(ID(Y_WIDTH)).as_int() - 1; + f << ","; + f << cell->getParam(ID(OFFSET)).as_int(); + f << ">().val()"; + } else { + log_assert(false); + } + } + + bool is_cell_elided(const RTLIL::Cell *cell) + { + return is_elidable_cell(cell->type) && cell->hasPort(ID(Y)) && cell->getPort(ID(Y)).is_wire() && + elided_wires.count(cell->getPort(ID(Y)).as_wire()); + } + + void collect_cell(const RTLIL::Cell *cell, std::vector<RTLIL::IdString> &cells) + { + if (!is_cell_elided(cell)) + return; + + cells.push_back(cell->name); + for (auto port : cell->connections()) + if (port.first != ID(Y)) + collect_sigspec_rhs(port.second, cells); + } + + void dump_cell(const RTLIL::Cell *cell) + { + if (is_cell_elided(cell)) + return; + if (cell->type == ID($meminit)) + return; // Handled elsewhere. + + std::vector<RTLIL::IdString> elided_cells; + if (is_elidable_cell(cell->type)) { + for (auto port : cell->connections()) + if (port.first != ID(Y)) + collect_sigspec_rhs(port.second, elided_cells); + } + if (elided_cells.empty()) { + dump_attrs(cell); + f << indent << "// cell " << cell->name.str() << "\n"; + } else { + f << indent << "// cells"; + for (auto elided_cell : elided_cells) + f << " " << elided_cell.str(); + f << "\n"; + } + + // Elidable cells + if (is_elidable_cell(cell->type)) { + f << indent; + dump_sigspec_lhs(cell->getPort(ID(Y))); + f << " = "; + dump_cell_elided(cell); + f << ";\n"; + // Parallel (one-hot) muxes + } else if (cell->type == ID($pmux)) { + int width = cell->getParam(ID(WIDTH)).as_int(); + int s_width = cell->getParam(ID(S_WIDTH)).as_int(); + bool first = true; + for (int part = 0; part < s_width; part++) { + f << (first ? indent : " else "); + first = false; + f << "if ("; + dump_sigspec_rhs(cell->getPort(ID(S)).extract(part)); + f << ") {\n"; + inc_indent(); + f << indent; + dump_sigspec_lhs(cell->getPort(ID(Y))); + f << " = "; + dump_sigspec_rhs(cell->getPort(ID(B)).extract(part * width, width)); + f << ";\n"; + dec_indent(); + f << indent << "}"; + } + f << " else {\n"; + inc_indent(); + f << indent; + dump_sigspec_lhs(cell->getPort(ID(Y))); + f << " = "; + dump_sigspec_rhs(cell->getPort(ID(A))); + f << ";\n"; + dec_indent(); + f << indent << "}\n"; + // Flip-flops + } else if (is_ff_cell(cell->type)) { + if (cell->hasPort(ID(CLK)) && cell->getPort(ID(CLK)).is_wire()) { + // Edge-sensitive logic + RTLIL::SigBit clk_bit = cell->getPort(ID(CLK))[0]; + clk_bit = sigmaps[clk_bit.wire->module](clk_bit); + f << indent << "if (" << (cell->getParam(ID(CLK_POLARITY)).as_bool() ? "posedge_" : "negedge_") + << mangle(clk_bit) << ") {\n"; + inc_indent(); + if (cell->type == ID($dffe)) { + f << indent << "if ("; + dump_sigspec_rhs(cell->getPort(ID(EN))); + f << " == value<1> {" << cell->getParam(ID(EN_POLARITY)).as_bool() << "u}) {\n"; + inc_indent(); + } + f << indent; + dump_sigspec_lhs(cell->getPort(ID(Q))); + f << " = "; + dump_sigspec_rhs(cell->getPort(ID(D))); + f << ";\n"; + if (cell->type == ID($dffe)) { + dec_indent(); + f << indent << "}\n"; + } + dec_indent(); + f << indent << "}\n"; + } else if (cell->hasPort(ID(EN))) { + // Level-sensitive logic + f << indent << "if ("; + dump_sigspec_rhs(cell->getPort(ID(EN))); + f << " == value<1> {" << cell->getParam(ID(EN_POLARITY)).as_bool() << "u}) {\n"; + inc_indent(); + f << indent; + dump_sigspec_lhs(cell->getPort(ID(Q))); + f << " = "; + dump_sigspec_rhs(cell->getPort(ID(D))); + f << ";\n"; + dec_indent(); + f << indent << "}\n"; + } + if (cell->hasPort(ID(ARST))) { + // Asynchronous reset (entire coarse cell at once) + f << indent << "if ("; + dump_sigspec_rhs(cell->getPort(ID(ARST))); + f << " == value<1> {" << cell->getParam(ID(ARST_POLARITY)).as_bool() << "u}) {\n"; + inc_indent(); + f << indent; + dump_sigspec_lhs(cell->getPort(ID(Q))); + f << " = "; + dump_const(cell->getParam(ID(ARST_VALUE))); + f << ";\n"; + dec_indent(); + f << indent << "}\n"; + } + if (cell->hasPort(ID(SET))) { + // Asynchronous set (for individual bits) + f << indent; + dump_sigspec_lhs(cell->getPort(ID(Q))); + f << " = "; + dump_sigspec_lhs(cell->getPort(ID(Q))); + f << ".update("; + dump_const(RTLIL::Const(RTLIL::S1, cell->getParam(ID(WIDTH)).as_int())); + f << ", "; + dump_sigspec_rhs(cell->getPort(ID(SET))); + f << (cell->getParam(ID(SET_POLARITY)).as_bool() ? "" : ".bit_not()") << ");\n"; + } + if (cell->hasPort(ID(CLR))) { + // Asynchronous clear (for individual bits; priority over set) + f << indent; + dump_sigspec_lhs(cell->getPort(ID(Q))); + f << " = "; + dump_sigspec_lhs(cell->getPort(ID(Q))); + f << ".update("; + dump_const(RTLIL::Const(RTLIL::S0, cell->getParam(ID(WIDTH)).as_int())); + f << ", "; + dump_sigspec_rhs(cell->getPort(ID(CLR))); + f << (cell->getParam(ID(CLR_POLARITY)).as_bool() ? "" : ".bit_not()") << ");\n"; + } + // Memory ports + } else if (cell->type.in(ID($memrd), ID($memwr))) { + if (cell->getParam(ID(CLK_ENABLE)).as_bool()) { + RTLIL::SigBit clk_bit = cell->getPort(ID(CLK))[0]; + clk_bit = sigmaps[clk_bit.wire->module](clk_bit); + f << indent << "if (" << (cell->getParam(ID(CLK_POLARITY)).as_bool() ? "posedge_" : "negedge_") + << mangle(clk_bit) << ") {\n"; + inc_indent(); + } + RTLIL::Memory *memory = cell->module->memories[cell->getParam(ID(MEMID)).decode_string()]; + std::string valid_index_temp = fresh_temporary(); + f << indent << "auto " << valid_index_temp << " = memory_index("; + dump_sigspec_rhs(cell->getPort(ID(ADDR))); + f << ", " << memory->start_offset << ", " << memory->size << ");\n"; + if (cell->type == ID($memrd)) { + if (!cell->getPort(ID(EN)).is_fully_ones()) { + f << indent << "if ("; + dump_sigspec_rhs(cell->getPort(ID(EN))); + f << ") {\n"; + inc_indent(); + } + // The generated code has two bounds checks; one in an assertion, and another that guards the read. + // This is done so that the code does not invoke undefined behavior under any conditions, but nevertheless + // loudly crashes if an illegal condition is encountered. The assert may be turned off with -NDEBUG not + // just for release builds, but also to make sure the simulator (which is presumably embedded in some + // larger program) will never crash the code that calls into it. + // + // If assertions are disabled, out of bounds reads are defined to return zero. + f << indent << "assert(" << valid_index_temp << ".valid && \"out of bounds read\");\n"; + f << indent << "if(" << valid_index_temp << ".valid) {\n"; + inc_indent(); + if (writable_memories[memory]) { + std::string addr_temp = fresh_temporary(); + f << indent << "const value<" << cell->getPort(ID(ADDR)).size() << "> &" << addr_temp << " = "; + dump_sigspec_rhs(cell->getPort(ID(ADDR))); + f << ";\n"; + std::string lhs_temp = fresh_temporary(); + f << indent << "value<" << memory->width << "> " << lhs_temp << " = " + << mangle(memory) << "[" << valid_index_temp << ".index];\n"; + std::vector<const RTLIL::Cell*> memwr_cells(transparent_for[cell].begin(), transparent_for[cell].end()); + std::sort(memwr_cells.begin(), memwr_cells.end(), + [](const RTLIL::Cell *a, const RTLIL::Cell *b) { + return a->getParam(ID(PRIORITY)).as_int() < b->getParam(ID(PRIORITY)).as_int(); + }); + for (auto memwr_cell : memwr_cells) { + f << indent << "if (" << addr_temp << " == "; + dump_sigspec_rhs(memwr_cell->getPort(ID(ADDR))); + f << ") {\n"; + inc_indent(); + f << indent << lhs_temp << " = " << lhs_temp; + f << ".update("; + dump_sigspec_rhs(memwr_cell->getPort(ID(DATA))); + f << ", "; + dump_sigspec_rhs(memwr_cell->getPort(ID(EN))); + f << ");\n"; + dec_indent(); + f << indent << "}\n"; + } + f << indent; + dump_sigspec_lhs(cell->getPort(ID(DATA))); + f << " = " << lhs_temp << ";\n"; + } else { + f << indent; + dump_sigspec_lhs(cell->getPort(ID(DATA))); + f << " = " << mangle(memory) << "[" << valid_index_temp << ".index];\n"; + } + dec_indent(); + f << indent << "} else {\n"; + inc_indent(); + f << indent; + dump_sigspec_lhs(cell->getPort(ID(DATA))); + f << " = value<" << memory->width << "> {};\n"; + dec_indent(); + f << indent << "}\n"; + if (!cell->getPort(ID(EN)).is_fully_ones()) { + dec_indent(); + f << indent << "}\n"; + } + } else /*if (cell->type == ID($memwr))*/ { + log_assert(writable_memories[memory]); + // See above for rationale of having both the assert and the condition. + // + // If assertions are disabled, out of bounds writes are defined to do nothing. + f << indent << "assert(" << valid_index_temp << ".valid && \"out of bounds write\");\n"; + f << indent << "if (" << valid_index_temp << ".valid) {\n"; + inc_indent(); + f << indent << mangle(memory) << ".update(" << valid_index_temp << ".index, "; + dump_sigspec_rhs(cell->getPort(ID(DATA))); + f << ", "; + dump_sigspec_rhs(cell->getPort(ID(EN))); + f << ", " << cell->getParam(ID(PRIORITY)).as_int() << ");\n"; + dec_indent(); + f << indent << "}\n"; + } + if (cell->getParam(ID(CLK_ENABLE)).as_bool()) { + dec_indent(); + f << indent << "}\n"; + } + // Internal cells + } else if (is_internal_cell(cell->type)) { + log_cmd_error("Unsupported internal cell `%s'.\n", cell->type.c_str()); + // User cells + } else { + log_assert(cell->known()); + for (auto conn : cell->connections()) + if (cell->input(conn.first)) { + f << indent << mangle(cell) << "." << mangle_wire_name(conn.first) << ".next = "; + dump_sigspec_rhs(conn.second); + f << ";\n"; + } + f << indent << mangle(cell) << ".eval();\n"; + for (auto conn : cell->connections()) { + if (conn.second.is_wire()) { + RTLIL::Wire *wire = conn.second.as_wire(); + if (elided_wires.count(wire) && cell_wire_defs[cell].count(wire)) + continue; + } + if (cell->output(conn.first)) { + f << indent; + dump_sigspec_lhs(conn.second); + f << " = " << mangle(cell) << "." << mangle_wire_name(conn.first) << ".curr;\n"; + } + } + } + } + + void dump_assign(const RTLIL::SigSig &sigsig) + { + f << indent; + dump_sigspec_lhs(sigsig.first); + f << " = "; + dump_sigspec_rhs(sigsig.second); + f << ";\n"; + } + + void dump_case_rule(const RTLIL::CaseRule *rule) + { + for (auto action : rule->actions) + dump_assign(action); + for (auto switch_ : rule->switches) + dump_switch_rule(switch_); + } + + void dump_switch_rule(const RTLIL::SwitchRule *rule) + { + // The switch attributes are printed before the switch condition is captured. + dump_attrs(rule); + std::string signal_temp = fresh_temporary(); + f << indent << "const value<" << rule->signal.size() << "> &" << signal_temp << " = "; + dump_sigspec(rule->signal, /*is_lhs=*/false); + f << ";\n"; + + bool first = true; + for (auto case_ : rule->cases) { + // The case attributes (for nested cases) are printed before the if/else if/else statement. + dump_attrs(rule); + f << indent; + if (!first) + f << "} else "; + first = false; + if (!case_->compare.empty()) { + f << "if ("; + bool first = true; + for (auto &compare : case_->compare) { + if (!first) + f << " || "; + first = false; + if (compare.is_fully_def()) { + f << signal_temp << " == "; + dump_sigspec(compare, /*is_lhs=*/false); + } else if (compare.is_fully_const()) { + RTLIL::Const compare_mask, compare_value; + for (auto bit : compare.as_const()) { + switch (bit) { + case RTLIL::S0: + case RTLIL::S1: + compare_mask.bits.push_back(RTLIL::S1); + compare_value.bits.push_back(bit); + break; + + case RTLIL::Sx: + case RTLIL::Sz: + case RTLIL::Sa: + compare_mask.bits.push_back(RTLIL::S0); + compare_value.bits.push_back(RTLIL::S0); + break; + + default: + log_assert(false); + } + } + f << "and_uu<" << compare.size() << ">(" << signal_temp << ", "; + dump_const(compare_mask); + f << ") == "; + dump_const(compare_value); + } else { + log_assert(false); + } + } + f << ") "; + } + f << "{\n"; + inc_indent(); + dump_case_rule(case_); + dec_indent(); + } + f << indent << "}\n"; + } + + void dump_process(const RTLIL::Process *proc) + { + dump_attrs(proc); + f << indent << "// process " << proc->name.str() << "\n"; + // The case attributes (for root case) are always empty. + log_assert(proc->root_case.attributes.empty()); + dump_case_rule(&proc->root_case); + for (auto sync : proc->syncs) { + RTLIL::SigBit sync_bit = sync->signal[0]; + sync_bit = sigmaps[sync_bit.wire->module](sync_bit); + + pool<std::string> events; + switch (sync->type) { + case RTLIL::STp: + events.insert("posedge_" + mangle(sync_bit)); + break; + case RTLIL::STn: + events.insert("negedge_" + mangle(sync_bit)); + case RTLIL::STe: + events.insert("posedge_" + mangle(sync_bit)); + events.insert("negedge_" + mangle(sync_bit)); + break; + + case RTLIL::ST0: + case RTLIL::ST1: + case RTLIL::STa: + case RTLIL::STg: + case RTLIL::STi: + log_assert(false); + } + if (!events.empty()) { + f << indent << "if ("; + bool first = true; + for (auto &event : events) { + if (!first) + f << " || "; + first = false; + f << event; + } + f << ") {\n"; + inc_indent(); + for (auto action : sync->actions) + dump_assign(action); + dec_indent(); + f << indent << "}\n"; + } + } + } + + void dump_wire(const RTLIL::Wire *wire, bool is_local) + { + if (elided_wires.count(wire)) + return; + + if (is_local) { + if (!localized_wires.count(wire)) + return; + + dump_attrs(wire); + f << indent << "value<" << wire->width << "> " << mangle(wire) << ";\n"; + } else { + if (localized_wires.count(wire)) + return; + + dump_attrs(wire); + f << indent << "wire<" << wire->width << "> " << mangle(wire); + if (wire->attributes.count(ID(init))) { + f << " "; + dump_const_init(wire->attributes.at(ID(init))); + } + f << ";\n"; + if (sync_wires[wire]) { + for (auto sync_type : sync_types) { + if (sync_type.first.wire == wire) { + if (sync_type.second != RTLIL::STn) + f << indent << "bool posedge_" << mangle(sync_type.first) << " = false;\n"; + if (sync_type.second != RTLIL::STp) + f << indent << "bool negedge_" << mangle(sync_type.first) << " = false;\n"; + } + } + } + } + } + + void dump_memory(RTLIL::Module *module, const RTLIL::Memory *memory) + { + vector<const RTLIL::Cell*> init_cells; + for (auto cell : module->cells()) + if (cell->type == ID($meminit) && cell->getParam(ID(MEMID)).decode_string() == memory->name.str()) + init_cells.push_back(cell); + + std::sort(init_cells.begin(), init_cells.end(), [](const RTLIL::Cell *a, const RTLIL::Cell *b) { + int a_addr = a->getPort(ID(ADDR)).as_int(), b_addr = b->getPort(ID(ADDR)).as_int(); + int a_prio = a->getParam(ID(PRIORITY)).as_int(), b_prio = b->getParam(ID(PRIORITY)).as_int(); + return a_prio > b_prio || (a_prio == b_prio && a_addr < b_addr); + }); + + dump_attrs(memory); + f << indent << (writable_memories[memory] ? "" : "const ") + << "memory<" << memory->width << "> " << mangle(memory) + << " { " << memory->size << "u"; + if (init_cells.empty()) { + f << " };\n"; + } else { + f << ",\n"; + inc_indent(); + for (auto cell : init_cells) { + dump_attrs(cell); + RTLIL::Const data = cell->getPort(ID(DATA)).as_const(); + size_t width = cell->getParam(ID(WIDTH)).as_int(); + size_t words = cell->getParam(ID(WORDS)).as_int(); + f << indent << "memory<" << memory->width << ">::init<" << words << "> { " + << stringf("%#x", cell->getPort(ID(ADDR)).as_int()) << ", {"; + inc_indent(); + for (size_t n = 0; n < words; n++) { + if (n % 4 == 0) + f << "\n" << indent; + else + f << " "; + dump_const(data, width, n * width, /*fixed_width=*/true); + f << ","; + } + dec_indent(); + f << "\n" << indent << "}},\n"; + } + dec_indent(); + f << indent << "};\n"; + } + } + + void dump_module(RTLIL::Module *module) + { + dump_attrs(module); + f << "struct " << mangle(module) << " : public module {\n"; + inc_indent(); + for (auto wire : module->wires()) + dump_wire(wire, /*is_local=*/false); + f << "\n"; + bool has_memories = false; + for (auto memory : module->memories) { + dump_memory(module, memory.second); + has_memories = true; + } + if (has_memories) + f << "\n"; + bool has_cells = false; + for (auto cell : module->cells()) { + if (is_internal_cell(cell->type)) + continue; + f << indent << mangle_module_name(cell->type) << " " << mangle(cell) << ";\n"; + has_cells = true; + } + if (has_cells) + f << "\n"; + f << indent << "void eval() override;\n"; + f << indent << "bool commit() override;\n"; + dec_indent(); + f << "}; // struct " << mangle(module) << "\n"; + f << "\n"; + + f << "void " << mangle(module) << "::eval() {\n"; + inc_indent(); + for (auto wire : module->wires()) + dump_wire(wire, /*is_local=*/true); + for (auto node : schedule[module]) { + switch (node.type) { + case FlowGraph::Node::Type::CONNECT: + dump_connect(node.connect); + break; + case FlowGraph::Node::Type::CELL: + dump_cell(node.cell); + break; + case FlowGraph::Node::Type::PROCESS: + dump_process(node.process); + break; + } + } + for (auto sync_type : sync_types) { + if (sync_type.first.wire->module == module) { + if (sync_type.second != RTLIL::STn) + f << indent << "posedge_" << mangle(sync_type.first) << " = false;\n"; + if (sync_type.second != RTLIL::STp) + f << indent << "negedge_" << mangle(sync_type.first) << " = false;\n"; + } + } + dec_indent(); + f << "}\n"; + f << "\n"; + + f << "bool " << mangle(module) << "::commit() {\n"; + inc_indent(); + f << indent << "bool changed = false;\n"; + for (auto wire : module->wires()) { + if (elided_wires.count(wire) || localized_wires.count(wire)) + continue; + if (sync_wires[wire]) { + std::string wire_prev = mangle(wire) + "_prev"; + std::string wire_curr = mangle(wire) + ".curr"; + std::string wire_edge = mangle(wire) + "_edge"; + f << indent << "value<" << wire->width << "> " << wire_prev << " = " << wire_curr << ";\n"; + f << indent << "if (" << mangle(wire) << ".commit()) {\n"; + inc_indent(); + f << indent << "value<" << wire->width << "> " << wire_edge << " = " + << wire_prev << ".bit_xor(" << wire_curr << ");\n"; + for (auto sync_type : sync_types) { + if (sync_type.first.wire != wire) + continue; + if (sync_type.second != RTLIL::STn) { + f << indent << "if (" << wire_edge << ".slice<" << sync_type.first.offset << ">().val() && " + << wire_curr << ".slice<" << sync_type.first.offset << ">().val())\n"; + inc_indent(); + f << indent << "posedge_" << mangle(sync_type.first) << " = true;\n"; + dec_indent(); + } + if (sync_type.second != RTLIL::STp) { + f << indent << "if (" << wire_edge << ".slice<" << sync_type.first.offset << ">().val() && " + << "!" << wire_curr << ".slice<" << sync_type.first.offset << ">().val())\n"; + inc_indent(); + f << indent << "negedge_" << mangle(sync_type.first) << " = true;\n"; + dec_indent(); + } + f << indent << "changed = true;\n"; + } + dec_indent(); + f << indent << "}\n"; + } else { + f << indent << "changed |= " << mangle(wire) << ".commit();\n"; + } + } + for (auto memory : module->memories) { + if (!writable_memories[memory.second]) + continue; + f << indent << "changed |= " << mangle(memory.second) << ".commit();\n"; + } + for (auto cell : module->cells()) { + if (is_internal_cell(cell->type)) + continue; + f << indent << "changed |= " << mangle(cell) << ".commit();\n"; + } + f << indent << "return changed;\n"; + dec_indent(); + f << "}\n"; + f << "\n"; + } + + void dump_design(RTLIL::Design *design) + { + TopoSort<RTLIL::Module*> topo_design; + for (auto module : design->modules()) { + if (module->get_blackbox_attribute() || !design->selected_module(module)) + continue; + topo_design.node(module); + + for (auto cell : module->cells()) { + if (is_internal_cell(cell->type)) + continue; + log_assert(design->has(cell->type)); + topo_design.edge(design->module(cell->type), module); + } + } + log_assert(topo_design.sort()); + + f << "#include <cxxrtl.h>\n"; + f << "\n"; + f << "using namespace cxxrtl_yosys;\n"; + f << "\n"; + f << "namespace cxxrtl_design {\n"; + f << "\n"; + for (auto module : topo_design.sorted) { + if (!design->selected_module(module)) + continue; + dump_module(module); + } + f << "} // namespace cxxrtl_design\n"; + } + + // Edge-type sync rules require us to emit edge detectors, which require coordination between + // eval and commit phases. To do this we need to collect them upfront. + // + // Note that the simulator commit phase operates at wire granularity but edge-type sync rules + // operate at wire bit granularity; it is possible to have code similar to: + // wire [3:0] clocks; + // always @(posedge clocks[0]) ... + // To handle this we track edge sensitivity both for wires and wire bits. + void register_edge_signal(SigMap &sigmap, RTLIL::SigSpec signal, RTLIL::SyncType type) + { + signal = sigmap(signal); + log_assert(signal.is_wire() && signal.is_bit()); + log_assert(type == RTLIL::STp || type == RTLIL::STn || type == RTLIL::STe); + + RTLIL::SigBit sigbit = signal[0]; + if (!sync_types.count(sigbit)) + sync_types[sigbit] = type; + else if (sync_types[sigbit] != type) + sync_types[sigbit] = RTLIL::STe; + sync_wires.insert(signal.as_wire()); + } + + void analyze_design(RTLIL::Design *design) + { + bool has_feedback_arcs = false; + for (auto module : design->modules()) { + if (!design->selected_module(module)) + continue; + + FlowGraph flow; + SigMap &sigmap = sigmaps[module]; + sigmap.set(module); + + for (auto conn : module->connections()) + flow.add_node(conn); + + dict<const RTLIL::Cell*, FlowGraph::Node*> memrw_cell_nodes; + dict<std::pair<RTLIL::SigBit, const RTLIL::Memory*>, + pool<const RTLIL::Cell*>> memwr_per_domain; + for (auto cell : module->cells()) { + FlowGraph::Node *node = flow.add_node(cell); + + // Various DFF cells are treated like posedge/negedge processes, see above for details. + if (cell->type.in(ID($dff), ID($dffe), ID($adff), ID($dffsr))) { + if (cell->getPort(ID(CLK)).is_wire()) + register_edge_signal(sigmap, cell->getPort(ID(CLK)), + cell->parameters[ID(CLK_POLARITY)].as_bool() ? RTLIL::STp : RTLIL::STn); + // The $adff and $dffsr cells are level-sensitive, not edge-sensitive (in spite of the fact that they + // are inferred from an edge-sensitive Verilog process) and do not correspond to an edge-type sync rule. + } + // Similar for memory port cells. + if (cell->type.in(ID($memrd), ID($memwr))) { + if (cell->getParam(ID(CLK_ENABLE)).as_bool()) { + if (cell->getPort(ID(CLK)).is_wire()) + register_edge_signal(sigmap, cell->getPort(ID(CLK)), + cell->parameters[ID(CLK_POLARITY)].as_bool() ? RTLIL::STp : RTLIL::STn); + } + memrw_cell_nodes[cell] = node; + } + // Optimize access to read-only memories. + if (cell->type == ID($memwr)) + writable_memories.insert(module->memories[cell->getParam(ID(MEMID)).decode_string()]); + // Collect groups of memory write ports in the same domain. + if (cell->type == ID($memwr) && cell->getParam(ID(CLK_ENABLE)).as_bool() && cell->getPort(ID(CLK)).is_wire()) { + RTLIL::SigBit clk_bit = sigmap(cell->getPort(ID(CLK)))[0]; + const RTLIL::Memory *memory = module->memories[cell->getParam(ID(MEMID)).decode_string()]; + memwr_per_domain[{clk_bit, memory}].insert(cell); + } + // Handling of packed memories is delegated to the `memory_unpack` pass, so we can rely on the presence + // of RTLIL memory objects and $memrd/$memwr/$meminit cells. + if (cell->type.in(ID($mem))) + log_assert(false); + } + for (auto cell : module->cells()) { + // Collect groups of memory write ports read by every transparent read port. + if (cell->type == ID($memrd) && cell->getParam(ID(CLK_ENABLE)).as_bool() && cell->getPort(ID(CLK)).is_wire() && + cell->getParam(ID(TRANSPARENT)).as_bool()) { + RTLIL::SigBit clk_bit = sigmap(cell->getPort(ID(CLK)))[0]; + const RTLIL::Memory *memory = module->memories[cell->getParam(ID(MEMID)).decode_string()]; + for (auto memwr_cell : memwr_per_domain[{clk_bit, memory}]) { + transparent_for[cell].insert(memwr_cell); + // Our implementation of transparent $memrd cells reads \EN, \ADDR and \DATA from every $memwr cell + // in the same domain, which isn't directly visible in the netlist. Add these uses explicitly. + flow.add_uses(memrw_cell_nodes[cell], memwr_cell->getPort(ID(EN))); + flow.add_uses(memrw_cell_nodes[cell], memwr_cell->getPort(ID(ADDR))); + flow.add_uses(memrw_cell_nodes[cell], memwr_cell->getPort(ID(DATA))); + } + } + } + + for (auto proc : module->processes) { + flow.add_node(proc.second); + + for (auto sync : proc.second->syncs) + switch (sync->type) { + // Edge-type sync rules require pre-registration. + case RTLIL::STp: + case RTLIL::STn: + case RTLIL::STe: + register_edge_signal(sigmap, sync->signal, sync->type); + break; + + // Level-type sync rules require no special handling. + case RTLIL::ST0: + case RTLIL::ST1: + case RTLIL::STa: + break; + + // Handling of init-type sync rules is delegated to the `proc_init` pass, so we can use the wire + // attribute regardless of input. + case RTLIL::STi: + log_assert(false); + + case RTLIL::STg: + log_cmd_error("Global clock is not supported.\n"); + } + } + + for (auto wire : module->wires()) { + if (!flow.is_elidable(wire)) continue; + if (wire->port_id != 0) continue; + if (wire->get_bool_attribute(ID(keep))) continue; + if (wire->name.begins_with("$") && !elide_internal) continue; + if (wire->name.begins_with("\\") && !elide_public) continue; + if (sync_wires[wire]) continue; + log_assert(flow.wire_defs[wire].size() == 1); + elided_wires[wire] = **flow.wire_defs[wire].begin(); + } + + // Elided wires that are outputs of internal cells are always connected to a well known port (Y). + // For user cells, there could be multiple of them, and we need a way to look up the port name + // knowing only the wire. + for (auto cell : module->cells()) + for (auto conn : cell->connections()) + if (conn.second.is_wire() && elided_wires.count(conn.second.as_wire())) + cell_wire_defs[cell][conn.second.as_wire()] = conn.first; + + dict<FlowGraph::Node*, pool<const RTLIL::Wire*>, hash_ptr_ops> node_defs; + for (auto wire_def : flow.wire_defs) + for (auto node : wire_def.second) + node_defs[node].insert(wire_def.first); + + Scheduler<FlowGraph::Node> scheduler; + dict<FlowGraph::Node*, Scheduler<FlowGraph::Node>::Vertex*, hash_ptr_ops> node_map; + for (auto node : flow.nodes) + node_map[node] = scheduler.add(node); + for (auto node_def : node_defs) { + auto vertex = node_map[node_def.first]; + for (auto wire : node_def.second) + for (auto succ_node : flow.wire_uses[wire]) { + auto succ_vertex = node_map[succ_node]; + vertex->succs.insert(succ_vertex); + succ_vertex->preds.insert(vertex); + } + } + + auto eval_order = scheduler.schedule(); + pool<FlowGraph::Node*, hash_ptr_ops> evaluated; + pool<const RTLIL::Wire*> feedback_wires; + for (auto vertex : eval_order) { + auto node = vertex->data; + schedule[module].push_back(*node); + // Any wire that is an output of node vo and input of node vi where vo is scheduled later than vi + // is a feedback wire. Feedback wires indicate apparent logic loops in the design, which may be + // caused by a true logic loop, but usually are a benign result of dependency tracking that works + // on wire, not bit, level. Nevertheless, feedback wires cannot be localized. + evaluated.insert(node); + for (auto wire : node_defs[node]) + for (auto succ_node : flow.wire_uses[wire]) + if (evaluated[succ_node]) { + feedback_wires.insert(wire); + // Feedback wires may never be elided because feedback requires state, but the point of elision + // (and localization) is to eliminate state. + elided_wires.erase(wire); + } + } + + if (!feedback_wires.empty()) { + has_feedback_arcs = true; + log("Module `%s` contains feedback arcs through wires:\n", module->name.c_str()); + for (auto wire : feedback_wires) { + log(" %s\n", wire->name.c_str()); + } + } + + for (auto wire : module->wires()) { + if (feedback_wires[wire]) continue; + if (wire->port_id != 0) continue; + if (wire->get_bool_attribute(ID(keep))) continue; + if (wire->name.begins_with("$") && !localize_internal) continue; + if (wire->name.begins_with("\\") && !localize_public) continue; + if (sync_wires[wire]) continue; + // Outputs of FF/$memrd cells and LHS of sync actions do not end up in defs. + if (flow.wire_defs[wire].size() != 1) continue; + localized_wires.insert(wire); + } + } + if (has_feedback_arcs) { + log("Feedback arcs require delta cycles during evaluation.\n"); + } + } + + void check_design(RTLIL::Design *design, bool &has_sync_init, bool &has_packed_mem) + { + has_sync_init = has_packed_mem = false; + + for (auto module : design->modules()) { + if (module->get_blackbox_attribute()) + continue; + + if (!design->selected_whole_module(module)) + if (design->selected_module(module)) + log_cmd_error("Can't handle partially selected module `%s`!\n", id2cstr(module->name)); + if (!design->selected_module(module)) + continue; + + for (auto proc : module->processes) + for (auto sync : proc.second->syncs) + if (sync->type == RTLIL::STi) + has_sync_init = true; + + for (auto cell : module->cells()) + if (cell->type == ID($mem)) + has_packed_mem = true; + } + } + + void prepare_design(RTLIL::Design *design) + { + bool has_sync_init, has_packed_mem; + check_design(design, has_sync_init, has_packed_mem); + if (has_sync_init) { + // We're only interested in proc_init, but it depends on proc_prune and proc_clean, so call those + // in case they weren't already. (This allows `yosys foo.v -o foo.cc` to work.) + Pass::call(design, "proc_prune"); + Pass::call(design, "proc_clean"); + Pass::call(design, "proc_init"); + } + if (has_packed_mem) + Pass::call(design, "memory_unpack"); + // Recheck the design if it was modified. + if (has_sync_init || has_packed_mem) + check_design(design, has_sync_init, has_packed_mem); + log_assert(!(has_sync_init || has_packed_mem)); + + if (run_splitnets) { + Pass::call(design, "splitnets -driver"); + Pass::call(design, "opt_clean -purge"); + } + log("\n"); + analyze_design(design); + } +}; + +struct CxxrtlBackend : public Backend { + static const int DEFAULT_OPT_LEVEL = 5; + + CxxrtlBackend() : Backend("cxxrtl", "convert design to C++ RTL simulation") { } + void help() YS_OVERRIDE + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" write_cxxrtl [options] [filename]\n"); + log("\n"); + log("Write C++ code for simulating the design. The generated code requires a driver;\n"); + log("the following simple driver is provided as an example:\n"); + log("\n"); + log(" #include \"top.cc\"\n"); + log("\n"); + log(" int main() {\n"); + log(" cxxrtl_design::p_top top;\n"); + log(" while (1) {\n"); + log(" top.p_clk.next = value<1> {1u};\n"); + log(" top.step();\n"); + log(" top.p_clk.next = value<1> {0u};\n"); + log(" top.step();\n"); + log(" }\n"); + log(" }\n"); + log("\n"); + log("The following options are supported by this backend:\n"); + log("\n"); + log(" -O <level>\n"); + log(" set the optimization level. the default is -O%d. higher optimization\n", DEFAULT_OPT_LEVEL); + log(" levels dramatically decrease compile and run time, and highest level\n"); + log(" possible for a design should be used.\n"); + log("\n"); + log(" -O0\n"); + log(" no optimization.\n"); + log("\n"); + log(" -O1\n"); + log(" elide internal wires if possible.\n"); + log("\n"); + log(" -O2\n"); + log(" like -O1, and localize internal wires if possible.\n"); + log("\n"); + log(" -O3\n"); + log(" like -O2, and elide public wires not marked (*keep*) if possible.\n"); + log("\n"); + log(" -O4\n"); + log(" like -O3, and localize public wires not marked (*keep*) if possible.\n"); + log("\n"); + log(" -O5\n"); + log(" like -O4, and run `splitnets -driver; opt_clean -purge` first.\n"); + log("\n"); + } + void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE + { + int opt_level = DEFAULT_OPT_LEVEL; + + log_header(design, "Executing CXXRTL backend.\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) + { + if (args[argidx] == "-O" && argidx+1 < args.size()) { + opt_level = std::stoi(args[++argidx]); + continue; + } + if (args[argidx].substr(0, 2) == "-O" && args[argidx].size() == 3 && isdigit(args[argidx][2])) { + opt_level = std::stoi(args[argidx].substr(2)); + continue; + } + break; + } + extra_args(f, filename, args, argidx); + + CxxrtlWorker worker(*f); + switch (opt_level) { + case 5: + worker.run_splitnets = true; + case 4: + worker.localize_public = true; + case 3: + worker.elide_public = true; + case 2: + worker.localize_internal = true; + case 1: + worker.elide_internal = true; + case 0: + break; + default: + log_cmd_error("Invalid optimization level %d.\n", opt_level); + } + worker.prepare_design(design); + worker.dump_design(design); + } +} CxxrtlBackend; + +PRIVATE_NAMESPACE_END diff --git a/backends/cxxrtl/cxxrtl.h b/backends/cxxrtl/cxxrtl.h new file mode 100644 index 000000000..593c31c28 --- /dev/null +++ b/backends/cxxrtl/cxxrtl.h @@ -0,0 +1,1138 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2019-2020 whitequark <whitequark@whitequark.org> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +// This file is included by the designs generated with `write_cxxrtl`. It is not used in Yosys itself. + +#ifndef CXXRTL_H +#define CXXRTL_H + +#include <cstddef> +#include <cstdint> +#include <cassert> +#include <limits> +#include <type_traits> +#include <tuple> +#include <vector> +#include <algorithm> +#include <sstream> + +// The cxxrtl support library implements compile time specialized arbitrary width arithmetics, as well as provides +// composite lvalues made out of bit slices and concatenations of lvalues. This allows the `write_cxxrtl` pass +// to perform a straightforward translation of RTLIL structures to readable C++, relying on the C++ compiler +// to unwrap the abstraction and generate efficient code. +namespace cxxrtl { + +// All arbitrary-width values in cxxrtl are backed by arrays of unsigned integers called chunks. The chunk size +// is the same regardless of the value width to simplify manipulating values via FFI interfaces, e.g. driving +// and introspecting the simulation in Python. +// +// It is practical to use chunk sizes between 32 bits and platform register size because when arithmetics on +// narrower integer types is legalized by the C++ compiler, it inserts code to clear the high bits of the register. +// However, (a) most of our operations do not change those bits in the first place because of invariants that are +// invisible to the compiler, (b) we often operate on non-power-of-2 values and have to clear the high bits anyway. +// Therefore, using relatively wide chunks and clearing the high bits explicitly and only when we know they may be +// clobbered results in simpler generated code. +template<typename T> +struct chunk_traits { + static_assert(std::is_integral<T>::value && std::is_unsigned<T>::value, + "chunk type must be an unsigned integral type"); + using type = T; + static constexpr size_t bits = std::numeric_limits<T>::digits; + static constexpr T mask = std::numeric_limits<T>::max(); +}; + +template<class T> +struct expr_base; + +template<size_t Bits> +struct value : public expr_base<value<Bits>> { + static constexpr size_t bits = Bits; + + using chunk = chunk_traits<uint32_t>; + static constexpr chunk::type msb_mask = (Bits % chunk::bits == 0) ? chunk::mask + : chunk::mask >> (chunk::bits - (Bits % chunk::bits)); + + static constexpr size_t chunks = (Bits + chunk::bits - 1) / chunk::bits; + chunk::type data[chunks] = {}; + + value() = default; + template<typename... Init> + explicit constexpr value(Init ...init) : data{init...} {} + + value(const value<Bits> &) = default; + value(value<Bits> &&) = default; + value<Bits> &operator=(const value<Bits> &) = default; + + // A (no-op) helper that forces the cast to value<>. + const value<Bits> &val() const { + return *this; + } + + std::string str() const { + std::stringstream ss; + ss << *this; + return ss.str(); + } + + // Operations with compile-time parameters. + // + // These operations are used to implement slicing, concatenation, and blitting. + // The trunc, zext and sext operations add or remove most significant bits (i.e. on the left); + // the rtrunc and rzext operations add or remove least significant bits (i.e. on the right). + template<size_t NewBits> + value<NewBits> trunc() const { + static_assert(NewBits <= Bits, "trunc() may not increase width"); + value<NewBits> result; + for (size_t n = 0; n < result.chunks; n++) + result.data[n] = data[n]; + result.data[result.chunks - 1] &= result.msb_mask; + return result; + } + + template<size_t NewBits> + value<NewBits> zext() const { + static_assert(NewBits >= Bits, "zext() may not decrease width"); + value<NewBits> result; + for (size_t n = 0; n < chunks; n++) + result.data[n] = data[n]; + return result; + } + + template<size_t NewBits> + value<NewBits> sext() const { + static_assert(NewBits >= Bits, "sext() may not decrease width"); + value<NewBits> result; + for (size_t n = 0; n < chunks; n++) + result.data[n] = data[n]; + if (is_neg()) { + result.data[chunks - 1] |= ~msb_mask; + for (size_t n = chunks; n < result.chunks; n++) + result.data[n] = chunk::mask; + result.data[result.chunks - 1] &= result.msb_mask; + } + return result; + } + + template<size_t NewBits> + value<NewBits> rtrunc() const { + static_assert(NewBits <= Bits, "rtrunc() may not increase width"); + value<NewBits> result; + constexpr size_t shift_chunks = (Bits - NewBits) / chunk::bits; + constexpr size_t shift_bits = (Bits - NewBits) % chunk::bits; + chunk::type carry = 0; + if (shift_chunks + result.chunks < chunks) { + carry = (shift_bits == 0) ? 0 + : data[shift_chunks + result.chunks] << (chunk::bits - shift_bits); + } + for (size_t n = result.chunks; n > 0; n--) { + result.data[n - 1] = carry | (data[shift_chunks + n - 1] >> shift_bits); + carry = (shift_bits == 0) ? 0 + : data[shift_chunks + n - 1] << (chunk::bits - shift_bits); + } + return result; + } + + template<size_t NewBits> + value<NewBits> rzext() const { + static_assert(NewBits >= Bits, "rzext() may not decrease width"); + value<NewBits> result; + constexpr size_t shift_chunks = (NewBits - Bits) / chunk::bits; + constexpr size_t shift_bits = (NewBits - Bits) % chunk::bits; + chunk::type carry = 0; + for (size_t n = 0; n < chunks; n++) { + result.data[shift_chunks + n] = (data[n] << shift_bits) | carry; + carry = (shift_bits == 0) ? 0 + : data[n] >> (chunk::bits - shift_bits); + } + if (carry != 0) + result.data[result.chunks - 1] = carry; + return result; + } + + // Bit blit operation, i.e. a partial read-modify-write. + template<size_t Stop, size_t Start> + value<Bits> blit(const value<Stop - Start + 1> &source) const { + static_assert(Stop >= Start, "blit() may not reverse bit order"); + constexpr chunk::type start_mask = ~(chunk::mask << (Start % chunk::bits)); + constexpr chunk::type stop_mask = (Stop % chunk::bits + 1 == chunk::bits) ? 0 + : (chunk::mask << (Stop % chunk::bits + 1)); + value<Bits> masked = *this; + if (Start / chunk::bits == Stop / chunk::bits) { + masked.data[Start / chunk::bits] &= stop_mask | start_mask; + } else { + masked.data[Start / chunk::bits] &= start_mask; + for (size_t n = Start / chunk::bits + 1; n < Stop / chunk::bits; n++) + masked.data[n] = 0; + masked.data[Stop / chunk::bits] &= stop_mask; + } + value<Bits> shifted = source + .template rzext<Stop + 1>() + .template zext<Bits>(); + return masked.bit_or(shifted); + } + + // Helpers for selecting extending or truncating operation depending on whether the result is wider or narrower + // than the operand. In C++17 these can be replaced with `if constexpr`. + template<size_t NewBits, typename = void> + struct zext_cast { + value<NewBits> operator()(const value<Bits> &val) { + return val.template zext<NewBits>(); + } + }; + + template<size_t NewBits> + struct zext_cast<NewBits, typename std::enable_if<(NewBits < Bits)>::type> { + value<NewBits> operator()(const value<Bits> &val) { + return val.template trunc<NewBits>(); + } + }; + + template<size_t NewBits, typename = void> + struct sext_cast { + value<NewBits> operator()(const value<Bits> &val) { + return val.template sext<NewBits>(); + } + }; + + template<size_t NewBits> + struct sext_cast<NewBits, typename std::enable_if<(NewBits < Bits)>::type> { + value<NewBits> operator()(const value<Bits> &val) { + return val.template trunc<NewBits>(); + } + }; + + template<size_t NewBits> + value<NewBits> zcast() const { + return zext_cast<NewBits>()(*this); + } + + template<size_t NewBits> + value<NewBits> scast() const { + return sext_cast<NewBits>()(*this); + } + + // Operations with run-time parameters (offsets, amounts, etc). + // + // These operations are used for computations. + bool bit(size_t offset) const { + return data[offset / chunk::bits] & (1 << (offset % chunk::bits)); + } + + void set_bit(size_t offset, bool value = true) { + size_t offset_chunks = offset / chunk::bits; + size_t offset_bits = offset % chunk::bits; + data[offset_chunks] &= ~(1 << offset_bits); + data[offset_chunks] |= value ? 1 << offset_bits : 0; + } + + bool is_zero() const { + for (size_t n = 0; n < chunks; n++) + if (data[n] != 0) + return false; + return true; + } + + explicit operator bool() const { + return !is_zero(); + } + + bool is_neg() const { + return data[chunks - 1] & (1 << ((Bits - 1) % chunk::bits)); + } + + bool operator ==(const value<Bits> &other) const { + for (size_t n = 0; n < chunks; n++) + if (data[n] != other.data[n]) + return false; + return true; + } + + bool operator !=(const value<Bits> &other) const { + return !(*this == other); + } + + value<Bits> bit_not() const { + value<Bits> result; + for (size_t n = 0; n < chunks; n++) + result.data[n] = ~data[n]; + result.data[chunks - 1] &= msb_mask; + return result; + } + + value<Bits> bit_and(const value<Bits> &other) const { + value<Bits> result; + for (size_t n = 0; n < chunks; n++) + result.data[n] = data[n] & other.data[n]; + return result; + } + + value<Bits> bit_or(const value<Bits> &other) const { + value<Bits> result; + for (size_t n = 0; n < chunks; n++) + result.data[n] = data[n] | other.data[n]; + return result; + } + + value<Bits> bit_xor(const value<Bits> &other) const { + value<Bits> result; + for (size_t n = 0; n < chunks; n++) + result.data[n] = data[n] ^ other.data[n]; + return result; + } + + value<Bits> update(const value<Bits> &val, const value<Bits> &mask) const { + return bit_and(mask.bit_not()).bit_or(val.bit_and(mask)); + } + + template<size_t AmountBits> + value<Bits> shl(const value<AmountBits> &amount) const { + // Ensure our early return is correct by prohibiting values larger than 4 Gbit. + static_assert(Bits <= chunk::mask, "shl() of unreasonably large values is not supported"); + // Detect shifts definitely large than Bits early. + for (size_t n = 1; n < amount.chunks; n++) + if (amount.data[n] != 0) + return {}; + // Past this point we can use the least significant chunk as the shift size. + size_t shift_chunks = amount.data[0] / chunk::bits; + size_t shift_bits = amount.data[0] % chunk::bits; + if (shift_chunks >= chunks) + return {}; + value<Bits> result; + chunk::type carry = 0; + for (size_t n = 0; n < chunks - shift_chunks; n++) { + result.data[shift_chunks + n] = (data[n] << shift_bits) | carry; + carry = (shift_bits == 0) ? 0 + : data[n] >> (chunk::bits - shift_bits); + } + return result; + } + + template<size_t AmountBits, bool Signed = false> + value<Bits> shr(const value<AmountBits> &amount) const { + // Ensure our early return is correct by prohibiting values larger than 4 Gbit. + static_assert(Bits <= chunk::mask, "shr() of unreasonably large values is not supported"); + // Detect shifts definitely large than Bits early. + for (size_t n = 1; n < amount.chunks; n++) + if (amount.data[n] != 0) + return {}; + // Past this point we can use the least significant chunk as the shift size. + size_t shift_chunks = amount.data[0] / chunk::bits; + size_t shift_bits = amount.data[0] % chunk::bits; + if (shift_chunks >= chunks) + return {}; + value<Bits> result; + chunk::type carry = 0; + for (size_t n = 0; n < chunks - shift_chunks; n++) { + result.data[chunks - shift_chunks - 1 - n] = carry | (data[chunks - 1 - n] >> shift_bits); + carry = (shift_bits == 0) ? 0 + : data[chunks - 1 - n] << (chunk::bits - shift_bits); + } + if (Signed && is_neg()) { + for (size_t n = chunks - shift_chunks; n < chunks; n++) + result.data[n] = chunk::mask; + if (shift_bits != 0) + result.data[chunks - shift_chunks] |= chunk::mask << (chunk::bits - shift_bits); + } + return result; + } + + template<size_t AmountBits> + value<Bits> sshr(const value<AmountBits> &amount) const { + return shr<AmountBits, /*Signed=*/true>(amount); + } + + size_t ctpop() const { + size_t count = 0; + for (size_t n = 0; n < chunks; n++) { + // This loop implements the population count idiom as recognized by LLVM and GCC. + for (chunk::type x = data[n]; x != 0; count++) + x = x & (x - 1); + } + return count; + } + + size_t ctlz() const { + size_t count = 0; + for (size_t n = 0; n < chunks; n++) { + chunk::type x = data[chunks - 1 - n]; + if (x == 0) { + count += (n == 0 ? Bits % chunk::bits : chunk::bits); + } else { + // This loop implements the find first set idiom as recognized by LLVM. + for (; x != 0; count++) + x >>= 1; + } + } + return count; + } + + template<bool Invert, bool CarryIn> + std::pair<value<Bits>, bool /*CarryOut*/> alu(const value<Bits> &other) const { + value<Bits> result; + bool carry = CarryIn; + for (size_t n = 0; n < result.chunks; n++) { + result.data[n] = data[n] + (Invert ? ~other.data[n] : other.data[n]) + carry; + carry = (result.data[n] < data[n]) || + (result.data[n] == data[n] && carry); + } + result.data[result.chunks - 1] &= result.msb_mask; + return {result, carry}; + } + + value<Bits> add(const value<Bits> &other) const { + return alu</*Invert=*/false, /*CarryIn=*/false>(other).first; + } + + value<Bits> sub(const value<Bits> &other) const { + return alu</*Invert=*/true, /*CarryIn=*/true>(other).first; + } + + value<Bits> neg() const { + return value<Bits> { 0u }.sub(*this); + } + + bool ucmp(const value<Bits> &other) const { + bool carry; + std::tie(std::ignore, carry) = alu</*Invert=*/true, /*CarryIn=*/true>(other); + return !carry; // a.ucmp(b) ≡ a u< b + } + + bool scmp(const value<Bits> &other) const { + value<Bits> result; + bool carry; + std::tie(result, carry) = alu</*Invert=*/true, /*CarryIn=*/true>(other); + bool overflow = (is_neg() == !other.is_neg()) && (is_neg() != result.is_neg()); + return result.is_neg() ^ overflow; // a.scmp(b) ≡ a s< b + } +}; + +// Expression template for a slice, usable as lvalue or rvalue, and composable with other expression templates here. +template<class T, size_t Stop, size_t Start> +struct slice_expr : public expr_base<slice_expr<T, Stop, Start>> { + static_assert(Stop >= Start, "slice_expr() may not reverse bit order"); + static_assert(Start < T::bits && Stop < T::bits, "slice_expr() must be within bounds"); + static constexpr size_t bits = Stop - Start + 1; + + T &expr; + + slice_expr(T &expr) : expr(expr) {} + slice_expr(const slice_expr<T, Stop, Start> &) = delete; + + operator value<bits>() const { + return static_cast<const value<T::bits> &>(expr) + .template rtrunc<T::bits - Start>() + .template trunc<bits>(); + } + + slice_expr<T, Stop, Start> &operator=(const value<bits> &rhs) { + // Generic partial assignment implemented using a read-modify-write operation on the sliced expression. + expr = static_cast<const value<T::bits> &>(expr) + .template blit<Stop, Start>(rhs); + return *this; + } + + // A helper that forces the cast to value<>, which allows deduction to work. + value<bits> val() const { + return static_cast<const value<bits> &>(*this); + } +}; + +// Expression template for a concatenation, usable as lvalue or rvalue, and composable with other expression templates here. +template<class T, class U> +struct concat_expr : public expr_base<concat_expr<T, U>> { + static constexpr size_t bits = T::bits + U::bits; + + T &ms_expr; + U &ls_expr; + + concat_expr(T &ms_expr, U &ls_expr) : ms_expr(ms_expr), ls_expr(ls_expr) {} + concat_expr(const concat_expr<T, U> &) = delete; + + operator value<bits>() const { + value<bits> ms_shifted = static_cast<const value<T::bits> &>(ms_expr) + .template rzext<bits>(); + value<bits> ls_extended = static_cast<const value<U::bits> &>(ls_expr) + .template zext<bits>(); + return ms_shifted.bit_or(ls_extended); + } + + concat_expr<T, U> &operator=(const value<bits> &rhs) { + ms_expr = rhs.template rtrunc<T::bits>(); + ls_expr = rhs.template trunc<U::bits>(); + return *this; + } + + // A helper that forces the cast to value<>, which allows deduction to work. + value<bits> val() const { + return static_cast<const value<bits> &>(*this); + } +}; + +// Base class for expression templates, providing helper methods for operations that are valid on both rvalues and lvalues. +// +// Note that expression objects (slices and concatenations) constructed in this way should NEVER be captured because +// they refer to temporaries that will, in general, only live until the end of the statement. For example, both of +// these snippets perform use-after-free: +// +// const auto &a = val.slice<7,0>().slice<1>(); +// value<1> b = a; +// +// auto &&c = val.slice<7,0>().slice<1>(); +// c = value<1>{1u}; +// +// An easy way to write code using slices and concatenations safely is to follow two simple rules: +// * Never explicitly name any type except `value<W>` or `const value<W> &`. +// * Never use a `const auto &` or `auto &&` in any such expression. +// Then, any code that compiles will be well-defined. +template<class T> +struct expr_base { + template<size_t Stop, size_t Start = Stop> + slice_expr<const T, Stop, Start> slice() const { + return {*static_cast<const T *>(this)}; + } + + template<size_t Stop, size_t Start = Stop> + slice_expr<T, Stop, Start> slice() { + return {*static_cast<T *>(this)}; + } + + template<class U> + concat_expr<const T, typename std::remove_reference<const U>::type> concat(const U &other) const { + return {*static_cast<const T *>(this), other}; + } + + template<class U> + concat_expr<T, typename std::remove_reference<U>::type> concat(U &&other) { + return {*static_cast<T *>(this), other}; + } +}; + +template<size_t Bits> +std::ostream &operator<<(std::ostream &os, const value<Bits> &val) { + auto old_flags = os.flags(std::ios::right); + auto old_width = os.width(0); + auto old_fill = os.fill('0'); + os << val.bits << '\'' << std::hex; + for (size_t n = val.chunks - 1; n != (size_t)-1; n--) { + if (n == val.chunks - 1 && Bits % value<Bits>::chunk::bits != 0) + os.width((Bits % value<Bits>::chunk::bits + 3) / 4); + else + os.width((value<Bits>::chunk::bits + 3) / 4); + os << val.data[n]; + } + os.fill(old_fill); + os.width(old_width); + os.flags(old_flags); + return os; +} + +template<size_t Bits> +struct wire { + static constexpr size_t bits = Bits; + + value<Bits> curr; + value<Bits> next; + + wire() = default; + constexpr wire(const value<Bits> &init) : curr(init), next(init) {} + template<typename... Init> + explicit constexpr wire(Init ...init) : curr{init...}, next{init...} {} + + wire(const wire<Bits> &) = delete; + wire(wire<Bits> &&) = default; + wire<Bits> &operator=(const wire<Bits> &) = delete; + + bool commit() { + if (curr != next) { + curr = next; + return true; + } + return false; + } +}; + +template<size_t Bits> +std::ostream &operator<<(std::ostream &os, const wire<Bits> &val) { + os << val.curr; + return os; +} + +template<size_t Width> +struct memory { + std::vector<value<Width>> data; + + size_t depth() const { + return data.size(); + } + + memory() = delete; + explicit memory(size_t depth) : data(depth) {} + + memory(const memory<Width> &) = delete; + memory<Width> &operator=(const memory<Width> &) = delete; + + // The only way to get the compiler to put the initializer in .rodata and do not copy it on stack is to stuff it + // into a plain array. You'd think an std::initializer_list would work here, but it doesn't, because you can't + // construct an initializer_list in a constexpr (or something) and so if you try to do that the whole thing is + // first copied on the stack (probably overflowing it) and then again into `data`. + template<size_t Size> + struct init { + size_t offset; + value<Width> data[Size]; + }; + + template<size_t... InitSize> + explicit memory(size_t depth, const init<InitSize> &...init) : data(depth) { + data.resize(depth); + // This utterly reprehensible construct is the most reasonable way to apply a function to every element + // of a parameter pack, if the elements all have different types and so cannot be cast to an initializer list. + auto _ = {std::move(std::begin(init.data), std::end(init.data), data.begin() + init.offset)...}; + } + + value<Width> &operator [](size_t index) { + assert(index < data.size()); + return data[index]; + } + + const value<Width> &operator [](size_t index) const { + assert(index < data.size()); + return data[index]; + } + + // A simple way to make a writable memory would be to use an array of wires instead of an array of values. + // However, there are two significant downsides to this approach: first, it has large overhead (2× space + // overhead, and O(depth) time overhead during commit); second, it does not simplify handling write port + // priorities. Although in principle write ports could be ordered or conditionally enabled in generated + // code based on their priorities and selected addresses, the feedback arc set problem is computationally + // expensive, and the heuristic based algorithms are not easily modified to guarantee (rather than prefer) + // a particular write port evaluation order. + // + // The approach used here instead is to queue writes into a buffer during the eval phase, then perform + // the writes during the commit phase in the priority order. This approach has low overhead, with both space + // and time proportional to the amount of write ports. Because virtually every memory in a practical design + // has at most two write ports, linear search is used on every write, being the fastest and simplest approach. + struct write { + size_t index; + value<Width> val; + value<Width> mask; + int priority; + }; + std::vector<write> write_queue; + + void update(size_t index, const value<Width> &val, const value<Width> &mask, int priority = 0) { + assert(index < data.size()); + write_queue.emplace_back(write { index, val, mask, priority }); + } + + bool commit() { + bool changed = false; + std::sort(write_queue.begin(), write_queue.end(), + [](const write &a, const write &b) { return a.priority < b.priority; }); + for (const write &entry : write_queue) { + value<Width> elem = data[entry.index]; + elem = elem.update(entry.val, entry.mask); + changed |= (data[entry.index] != elem); + data[entry.index] = elem; + } + write_queue.clear(); + return changed; + } +}; + +struct module { + module() {} + virtual ~module() {} + + module(const module &) = delete; + module &operator=(const module &) = delete; + + virtual void eval() = 0; + virtual bool commit() = 0; + + size_t step() { + size_t deltas = 0; + do { + eval(); + deltas++; + } while (commit()); + return deltas; + } +}; + +} // namespace cxxrtl + +// Definitions of internal Yosys cells. Other than the functions in this namespace, cxxrtl is fully generic +// and indepenent of Yosys implementation details. +// +// The `write_cxxrtl` pass translates internal cells (cells with names that start with `$`) to calls of these +// functions. All of Yosys arithmetic and logical cells perform sign or zero extension on their operands, +// whereas basic operations on arbitrary width values require operands to be of the same width. These functions +// bridge the gap by performing the necessary casts. They are named similar to `cell_A[B]`, where A and B are `u` +// if the corresponding operand is unsigned, and `s` if it is signed. +namespace cxxrtl_yosys { + +using namespace cxxrtl; + +// std::max isn't constexpr until C++14 for no particular reason (it's an oversight), so we define our own. +template<class T> +constexpr T max(const T &a, const T &b) { + return a > b ? a : b; +} + +// Logic operations +template<size_t BitsY, size_t BitsA> +value<BitsY> not_u(const value<BitsA> &a) { + return a.template zcast<BitsY>().bit_not(); +} + +template<size_t BitsY, size_t BitsA> +value<BitsY> not_s(const value<BitsA> &a) { + return a.template scast<BitsY>().bit_not(); +} + +template<size_t BitsY, size_t BitsA> +value<BitsY> logic_not_u(const value<BitsA> &a) { + return value<BitsY> { a ? 0u : 1u }; +} + +template<size_t BitsY, size_t BitsA> +value<BitsY> logic_not_s(const value<BitsA> &a) { + return value<BitsY> { a ? 0u : 1u }; +} + +template<size_t BitsY, size_t BitsA> +value<BitsY> reduce_and_u(const value<BitsA> &a) { + return value<BitsY> { a.bit_not().is_zero() ? 1u : 0u }; +} + +template<size_t BitsY, size_t BitsA> +value<BitsY> reduce_and_s(const value<BitsA> &a) { + return value<BitsY> { a.bit_not().is_zero() ? 1u : 0u }; +} + +template<size_t BitsY, size_t BitsA> +value<BitsY> reduce_or_u(const value<BitsA> &a) { + return value<BitsY> { a ? 1u : 0u }; +} + +template<size_t BitsY, size_t BitsA> +value<BitsY> reduce_or_s(const value<BitsA> &a) { + return value<BitsY> { a ? 1u : 0u }; +} + +template<size_t BitsY, size_t BitsA> +value<BitsY> reduce_xor_u(const value<BitsA> &a) { + return value<BitsY> { (a.ctpop() % 2) ? 1u : 0u }; +} + +template<size_t BitsY, size_t BitsA> +value<BitsY> reduce_xor_s(const value<BitsA> &a) { + return value<BitsY> { (a.ctpop() % 2) ? 1u : 0u }; +} + +template<size_t BitsY, size_t BitsA> +value<BitsY> reduce_xnor_u(const value<BitsA> &a) { + return value<BitsY> { (a.ctpop() % 2) ? 0u : 1u }; +} + +template<size_t BitsY, size_t BitsA> +value<BitsY> reduce_xnor_s(const value<BitsA> &a) { + return value<BitsY> { (a.ctpop() % 2) ? 0u : 1u }; +} + +template<size_t BitsY, size_t BitsA> +value<BitsY> reduce_bool_u(const value<BitsA> &a) { + return value<BitsY> { a ? 1u : 0u }; +} + +template<size_t BitsY, size_t BitsA> +value<BitsY> reduce_bool_s(const value<BitsA> &a) { + return value<BitsY> { a ? 1u : 0u }; +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> and_uu(const value<BitsA> &a, const value<BitsB> &b) { + return a.template zcast<BitsY>().bit_and(b.template zcast<BitsY>()); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> and_ss(const value<BitsA> &a, const value<BitsB> &b) { + return a.template scast<BitsY>().bit_and(b.template scast<BitsY>()); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> or_uu(const value<BitsA> &a, const value<BitsB> &b) { + return a.template zcast<BitsY>().bit_or(b.template zcast<BitsY>()); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> or_ss(const value<BitsA> &a, const value<BitsB> &b) { + return a.template scast<BitsY>().bit_or(b.template scast<BitsY>()); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> xor_uu(const value<BitsA> &a, const value<BitsB> &b) { + return a.template zcast<BitsY>().bit_xor(b.template zcast<BitsY>()); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> xor_ss(const value<BitsA> &a, const value<BitsB> &b) { + return a.template scast<BitsY>().bit_xor(b.template scast<BitsY>()); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> xnor_uu(const value<BitsA> &a, const value<BitsB> &b) { + return a.template zcast<BitsY>().bit_xor(b.template zcast<BitsY>()).bit_not(); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> xnor_ss(const value<BitsA> &a, const value<BitsB> &b) { + return a.template scast<BitsY>().bit_xor(b.template scast<BitsY>()).bit_not(); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> logic_and_uu(const value<BitsA> &a, const value<BitsB> &b) { + return value<BitsY> { (bool(a) & bool(b)) ? 1u : 0u }; +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> logic_and_ss(const value<BitsA> &a, const value<BitsB> &b) { + return value<BitsY> { (bool(a) & bool(b)) ? 1u : 0u }; +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> logic_or_uu(const value<BitsA> &a, const value<BitsB> &b) { + return value<BitsY> { (bool(a) | bool(b)) ? 1u : 0u }; +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> logic_or_ss(const value<BitsA> &a, const value<BitsB> &b) { + return value<BitsY> { (bool(a) | bool(b)) ? 1u : 0u }; +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> shl_uu(const value<BitsA> &a, const value<BitsB> &b) { + return a.template zcast<BitsY>().template shl(b); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> shl_su(const value<BitsA> &a, const value<BitsB> &b) { + return a.template scast<BitsY>().template shl(b); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> sshl_uu(const value<BitsA> &a, const value<BitsB> &b) { + return a.template zcast<BitsY>().template shl(b); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> sshl_su(const value<BitsA> &a, const value<BitsB> &b) { + return a.template scast<BitsY>().template shl(b); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> shr_uu(const value<BitsA> &a, const value<BitsB> &b) { + return a.template shr(b).template zcast<BitsY>(); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> shr_su(const value<BitsA> &a, const value<BitsB> &b) { + return a.template shr(b).template scast<BitsY>(); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> sshr_uu(const value<BitsA> &a, const value<BitsB> &b) { + return a.template shr(b).template zcast<BitsY>(); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> sshr_su(const value<BitsA> &a, const value<BitsB> &b) { + return a.template shr(b).template scast<BitsY>(); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> shift_uu(const value<BitsA> &a, const value<BitsB> &b) { + return shr_uu<BitsY>(a, b); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> shift_su(const value<BitsA> &a, const value<BitsB> &b) { + return shr_su<BitsY>(a, b); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> shift_us(const value<BitsA> &a, const value<BitsB> &b) { + return b.is_neg() ? shl_uu<BitsY>(a, b.template sext<BitsB + 1>().neg()) : shr_uu<BitsY>(a, b); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> shift_ss(const value<BitsA> &a, const value<BitsB> &b) { + return b.is_neg() ? shl_su<BitsY>(a, b.template sext<BitsB + 1>().neg()) : shr_su<BitsY>(a, b); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> shiftx_uu(const value<BitsA> &a, const value<BitsB> &b) { + return shift_uu<BitsY>(a, b); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> shiftx_su(const value<BitsA> &a, const value<BitsB> &b) { + return shift_su<BitsY>(a, b); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> shiftx_us(const value<BitsA> &a, const value<BitsB> &b) { + return shift_us<BitsY>(a, b); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> shiftx_ss(const value<BitsA> &a, const value<BitsB> &b) { + return shift_ss<BitsY>(a, b); +} + +// Comparison operations +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> eq_uu(const value<BitsA> &a, const value<BitsB> &b) { + constexpr size_t BitsExt = max(BitsA, BitsB); + return value<BitsY>{ a.template zext<BitsExt>() == b.template zext<BitsExt>() ? 1u : 0u }; +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> eq_ss(const value<BitsA> &a, const value<BitsB> &b) { + constexpr size_t BitsExt = max(BitsA, BitsB); + return value<BitsY>{ a.template sext<BitsExt>() == b.template sext<BitsExt>() ? 1u : 0u }; +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> ne_uu(const value<BitsA> &a, const value<BitsB> &b) { + constexpr size_t BitsExt = max(BitsA, BitsB); + return value<BitsY>{ a.template zext<BitsExt>() != b.template zext<BitsExt>() ? 1u : 0u }; +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> ne_ss(const value<BitsA> &a, const value<BitsB> &b) { + constexpr size_t BitsExt = max(BitsA, BitsB); + return value<BitsY>{ a.template sext<BitsExt>() != b.template sext<BitsExt>() ? 1u : 0u }; +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> eqx_uu(const value<BitsA> &a, const value<BitsB> &b) { + return eq_uu<BitsY>(a, b); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> eqx_ss(const value<BitsA> &a, const value<BitsB> &b) { + return eq_ss<BitsY>(a, b); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> nex_uu(const value<BitsA> &a, const value<BitsB> &b) { + return ne_uu<BitsY>(a, b); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> nex_ss(const value<BitsA> &a, const value<BitsB> &b) { + return ne_ss<BitsY>(a, b); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> gt_uu(const value<BitsA> &a, const value<BitsB> &b) { + constexpr size_t BitsExt = max(BitsA, BitsB); + return value<BitsY> { b.template zext<BitsExt>().ucmp(a.template zext<BitsExt>()) ? 1u : 0u }; +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> gt_ss(const value<BitsA> &a, const value<BitsB> &b) { + constexpr size_t BitsExt = max(BitsA, BitsB); + return value<BitsY> { b.template sext<BitsExt>().scmp(a.template sext<BitsExt>()) ? 1u : 0u }; +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> ge_uu(const value<BitsA> &a, const value<BitsB> &b) { + constexpr size_t BitsExt = max(BitsA, BitsB); + return value<BitsY> { !a.template zext<BitsExt>().ucmp(b.template zext<BitsExt>()) ? 1u : 0u }; +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> ge_ss(const value<BitsA> &a, const value<BitsB> &b) { + constexpr size_t BitsExt = max(BitsA, BitsB); + return value<BitsY> { !a.template sext<BitsExt>().scmp(b.template sext<BitsExt>()) ? 1u : 0u }; +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> lt_uu(const value<BitsA> &a, const value<BitsB> &b) { + constexpr size_t BitsExt = max(BitsA, BitsB); + return value<BitsY> { a.template zext<BitsExt>().ucmp(b.template zext<BitsExt>()) ? 1u : 0u }; +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> lt_ss(const value<BitsA> &a, const value<BitsB> &b) { + constexpr size_t BitsExt = max(BitsA, BitsB); + return value<BitsY> { a.template sext<BitsExt>().scmp(b.template sext<BitsExt>()) ? 1u : 0u }; +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> le_uu(const value<BitsA> &a, const value<BitsB> &b) { + constexpr size_t BitsExt = max(BitsA, BitsB); + return value<BitsY> { !b.template zext<BitsExt>().ucmp(a.template zext<BitsExt>()) ? 1u : 0u }; +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> le_ss(const value<BitsA> &a, const value<BitsB> &b) { + constexpr size_t BitsExt = max(BitsA, BitsB); + return value<BitsY> { !b.template sext<BitsExt>().scmp(a.template sext<BitsExt>()) ? 1u : 0u }; +} + +// Arithmetic operations +template<size_t BitsY, size_t BitsA> +value<BitsY> pos_u(const value<BitsA> &a) { + return a.template zcast<BitsY>(); +} + +template<size_t BitsY, size_t BitsA> +value<BitsY> pos_s(const value<BitsA> &a) { + return a.template scast<BitsY>(); +} + +template<size_t BitsY, size_t BitsA> +value<BitsY> neg_u(const value<BitsA> &a) { + return a.template zcast<BitsY>().neg(); +} + +template<size_t BitsY, size_t BitsA> +value<BitsY> neg_s(const value<BitsA> &a) { + return a.template scast<BitsY>().neg(); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> add_uu(const value<BitsA> &a, const value<BitsB> &b) { + return a.template zcast<BitsY>().add(b.template zcast<BitsY>()); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> add_ss(const value<BitsA> &a, const value<BitsB> &b) { + return a.template scast<BitsY>().add(b.template scast<BitsY>()); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> sub_uu(const value<BitsA> &a, const value<BitsB> &b) { + return a.template zcast<BitsY>().sub(b.template zcast<BitsY>()); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> sub_ss(const value<BitsA> &a, const value<BitsB> &b) { + return a.template scast<BitsY>().sub(b.template scast<BitsY>()); +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> mul_uu(const value<BitsA> &a, const value<BitsB> &b) { + value<BitsY> product; + value<BitsY> multiplicand = a.template zcast<BitsY>(); + const value<BitsB> &multiplier = b; + uint32_t multiplicand_shift = 0; + for (size_t step = 0; step < BitsB; step++) { + if (multiplier.bit(step)) { + multiplicand = multiplicand.shl(value<32> { multiplicand_shift }); + product = product.add(multiplicand); + multiplicand_shift = 0; + } + multiplicand_shift++; + } + return product; +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> mul_ss(const value<BitsA> &a, const value<BitsB> &b) { + value<BitsB + 1> ub = b.template sext<BitsB + 1>(); + if (ub.is_neg()) ub = ub.neg(); + value<BitsY> y = mul_uu<BitsY>(a.template scast<BitsY>(), ub); + return b.is_neg() ? y.neg() : y; +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +std::pair<value<BitsY>, value<BitsY>> divmod_uu(const value<BitsA> &a, const value<BitsB> &b) { + constexpr size_t Bits = max(BitsY, max(BitsA, BitsB)); + value<Bits> quotient; + value<Bits> dividend = a.template zext<Bits>(); + value<Bits> divisor = b.template zext<Bits>(); + if (dividend.ucmp(divisor)) + return {/*quotient=*/value<BitsY> { 0u }, /*remainder=*/dividend.template trunc<BitsY>()}; + uint32_t divisor_shift = dividend.ctlz() - divisor.ctlz(); + divisor = divisor.shl(value<32> { divisor_shift }); + for (size_t step = 0; step <= divisor_shift; step++) { + quotient = quotient.shl(value<1> { 1u }); + if (!dividend.ucmp(divisor)) { + dividend = dividend.sub(divisor); + quotient.set_bit(0, true); + } + divisor = divisor.shr(value<1> { 1u }); + } + return {quotient.template trunc<BitsY>(), /*remainder=*/dividend.template trunc<BitsY>()}; +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +std::pair<value<BitsY>, value<BitsY>> divmod_ss(const value<BitsA> &a, const value<BitsB> &b) { + value<BitsA + 1> ua = a.template sext<BitsA + 1>(); + value<BitsB + 1> ub = b.template sext<BitsB + 1>(); + if (ua.is_neg()) ua = ua.neg(); + if (ub.is_neg()) ub = ub.neg(); + value<BitsY> y, r; + std::tie(y, r) = divmod_uu<BitsY>(ua, ub); + if (a.is_neg() != b.is_neg()) y = y.neg(); + if (a.is_neg()) r = r.neg(); + return {y, r}; +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> div_uu(const value<BitsA> &a, const value<BitsB> &b) { + return divmod_uu<BitsY>(a, b).first; +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> div_ss(const value<BitsA> &a, const value<BitsB> &b) { + return divmod_ss<BitsY>(a, b).first; +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> mod_uu(const value<BitsA> &a, const value<BitsB> &b) { + return divmod_uu<BitsY>(a, b).second; +} + +template<size_t BitsY, size_t BitsA, size_t BitsB> +value<BitsY> mod_ss(const value<BitsA> &a, const value<BitsB> &b) { + return divmod_ss<BitsY>(a, b).second; +} + +// Memory helper +struct memory_index { + bool valid; + size_t index; + + template<size_t BitsAddr> + memory_index(const value<BitsAddr> &addr, size_t offset, size_t depth) { + static_assert(value<BitsAddr>::chunks <= 1, "memory address is too wide"); + size_t offset_index = addr.data[0]; + + valid = (offset_index >= offset && offset_index < offset + depth); + index = offset_index - offset; + } +}; + +} // namespace cxxrtl_yosys + +#endif diff --git a/backends/smt2/Makefile.inc b/backends/smt2/Makefile.inc index 68394a909..fb01308bd 100644 --- a/backends/smt2/Makefile.inc +++ b/backends/smt2/Makefile.inc @@ -6,23 +6,23 @@ ifneq ($(CONFIG),emcc) # MSYS targets support yosys-smtbmc, but require a launcher script ifeq ($(CONFIG),$(filter $(CONFIG),msys2 msys2-64)) -TARGETS += yosys-smtbmc.exe yosys-smtbmc-script.py +TARGETS += $(PROGRAM_PREFIX)yosys-smtbmc.exe $(PROGRAM_PREFIX)yosys-smtbmc-script.py # Needed to find the Python interpreter for yosys-smtbmc scripts. # Override if necessary, it is only used for msys2 targets. PYTHON := $(shell cygpath -w -m $(PREFIX)/bin/python3) -yosys-smtbmc-script.py: backends/smt2/smtbmc.py - $(P) sed -e 's|##yosys-sys-path##|sys.path += [os.path.dirname(os.path.realpath(__file__)) + p for p in ["/share/python3", "/../share/yosys/python3"]]|;' \ +$(PROGRAM_PREFIX)yosys-smtbmc-script.py: backends/smt2/smtbmc.py + $(P) sed -e 's|##yosys-sys-path##|sys.path += [os.path.dirname(os.path.realpath(__file__)) + p for p in ["/share/python3", "/../share/$(PROGRAM_PREFIX)yosys/python3"]]|;' \ -e "s|#!/usr/bin/env python3|#!$(PYTHON)|" < $< > $@ -yosys-smtbmc.exe: misc/launcher.c yosys-smtbmc-script.py +$(PROGRAM_PREFIX)yosys-smtbmc.exe: misc/launcher.c $(PROGRAM_PREFIX)yosys-smtbmc-script.py $(P) $(CXX) -DGUI=0 -O -s -o $@ $< # Other targets else -TARGETS += yosys-smtbmc +TARGETS += $(PROGRAM_PREFIX)yosys-smtbmc -yosys-smtbmc: backends/smt2/smtbmc.py - $(P) sed 's|##yosys-sys-path##|sys.path += [os.path.dirname(os.path.realpath(__file__)) + p for p in ["/share/python3", "/../share/yosys/python3"]]|;' < $< > $@.new +$(PROGRAM_PREFIX)yosys-smtbmc: backends/smt2/smtbmc.py + $(P) sed 's|##yosys-sys-path##|sys.path += [os.path.dirname(os.path.realpath(__file__)) + p for p in ["/share/python3", "/../share/$(PROGRAM_PREFIX)yosys/python3"]]|;' < $< > $@.new $(Q) chmod +x $@.new $(Q) mv $@.new $@ endif diff --git a/backends/smt2/smtbmc.py b/backends/smt2/smtbmc.py index 630464419..5e6f43277 100644 --- a/backends/smt2/smtbmc.py +++ b/backends/smt2/smtbmc.py @@ -53,8 +53,7 @@ so = SmtOpts() def usage(): - print(""" -yosys-smtbmc [options] <yosys_smt2_output> + print(os.path.basename(sys.argv[0]) + """ [options] <yosys_smt2_output> -t <num_steps> -t <skip_steps>:<num_steps> diff --git a/frontends/aiger/aigerparse.cc b/frontends/aiger/aigerparse.cc index cbce0c990..92cf92fa8 100644 --- a/frontends/aiger/aigerparse.cc +++ b/frontends/aiger/aigerparse.cc @@ -30,7 +30,9 @@ #include <libkern/OSByteOrder.h> #define __builtin_bswap32 OSSwapInt32 #endif +#ifndef __STDC_FORMAT_MACROS #define __STDC_FORMAT_MACROS +#endif #include <inttypes.h> #include "kernel/yosys.h" diff --git a/frontends/ilang/ilang_lexer.l b/frontends/ilang/ilang_lexer.l index 4fd0ae855..62f53d18e 100644 --- a/frontends/ilang/ilang_lexer.l +++ b/frontends/ilang/ilang_lexer.l @@ -29,6 +29,7 @@ #pragma clang diagnostic ignored "-Wdeprecated-register" #endif +#include <cstdlib> #include "frontends/ilang/ilang_frontend.h" #include "ilang_parser.tab.hh" @@ -88,7 +89,16 @@ USING_YOSYS_NAMESPACE "."[0-9]+ { rtlil_frontend_ilang_yylval.string = strdup(yytext); return TOK_ID; } [0-9]+'[01xzm-]* { rtlil_frontend_ilang_yylval.string = strdup(yytext); return TOK_VALUE; } --?[0-9]+ { rtlil_frontend_ilang_yylval.integer = atoi(yytext); return TOK_INT; } +-?[0-9]+ { + char *end = nullptr; + long value = strtol(yytext, &end, 10); + if (end != yytext + strlen(yytext)) + return TOK_INVALID; // literal out of range of long + if (value < INT_MIN || value > INT_MAX) + return TOK_INVALID; // literal out of range of int (relevant mostly for LP64 platforms) + rtlil_frontend_ilang_yylval.integer = value; + return TOK_INT; +} \" { BEGIN(STRING); } <STRING>\\. { yymore(); } @@ -136,4 +146,3 @@ USING_YOSYS_NAMESPACE void *rtlil_frontend_ilang_avoid_input_warnings() { return (void*)&yyinput; } - diff --git a/frontends/ilang/ilang_parser.y b/frontends/ilang/ilang_parser.y index 4e0b62edd..0522fa72a 100644 --- a/frontends/ilang/ilang_parser.y +++ b/frontends/ilang/ilang_parser.y @@ -169,7 +169,7 @@ wire_stmt: current_wire->attributes = attrbuf; attrbuf.clear(); } wire_options TOK_ID EOL { - if (current_module->wires_.count($4) != 0) + if (current_module->wire($4) != nullptr) rtlil_frontend_ilang_yyerror(stringf("ilang error: redefinition of wire %s.", $4).c_str()); current_module->rename(current_wire, $4); free($4); @@ -179,6 +179,9 @@ wire_options: wire_options TOK_WIDTH TOK_INT { current_wire->width = $3; } | + wire_options TOK_WIDTH TOK_INVALID { + rtlil_frontend_ilang_yyerror("ilang error: invalid wire width"); + } | wire_options TOK_UPTO { current_wire->upto = true; } | @@ -229,7 +232,7 @@ memory_options: cell_stmt: TOK_CELL TOK_ID TOK_ID EOL { - if (current_module->cells_.count($3) != 0) + if (current_module->cell($3) != nullptr) rtlil_frontend_ilang_yyerror(stringf("ilang error: redefinition of cell %s.", $3).c_str()); current_cell = current_module->addCell($3, $2); current_cell->attributes = attrbuf; @@ -424,9 +427,9 @@ sigspec: delete $1; } | TOK_ID { - if (current_module->wires_.count($1) == 0) + if (current_module->wire($1) == nullptr) rtlil_frontend_ilang_yyerror(stringf("ilang error: wire %s not found", $1).c_str()); - $$ = new RTLIL::SigSpec(current_module->wires_[$1]); + $$ = new RTLIL::SigSpec(current_module->wire($1)); free($1); } | sigspec '[' TOK_INT ']' { diff --git a/frontends/verilog/verilog_parser.y b/frontends/verilog/verilog_parser.y index 3bffa3986..bd51aa12b 100644 --- a/frontends/verilog/verilog_parser.y +++ b/frontends/verilog/verilog_parser.y @@ -1735,7 +1735,6 @@ single_cell: ast_stack.back()->children.push_back(new AstNode(AST_CELLARRAY, $2, astbuf2)); } '(' cell_port_list ')'{ SET_AST_NODE_LOC(astbuf2, @1, @$); - SET_AST_NODE_LOC(astbuf3, @1, @$); }; prim_list: diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 00c116115..d9003f28c 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -42,11 +42,58 @@ int RTLIL::IdString::last_created_idx_ptr_; #endif #define X(_id) IdString RTLIL::ID::_id; -#include "constids.inc" +#include "kernel/constids.inc" #undef X dict<std::string, std::string> RTLIL::constpad; +const pool<IdString> &RTLIL::builtin_ff_cell_types() { + static const pool<IdString> res = { + ID($sr), + ID($ff), + ID($dff), + ID($dffe), + ID($dffsr), + ID($adff), + ID($dlatch), + ID($dlatchsr), + ID($_DFFE_NN_), + ID($_DFFE_NP_), + ID($_DFFE_PN_), + ID($_DFFE_PP_), + ID($_DFFSR_NNN_), + ID($_DFFSR_NNP_), + ID($_DFFSR_NPN_), + ID($_DFFSR_NPP_), + ID($_DFFSR_PNN_), + ID($_DFFSR_PNP_), + ID($_DFFSR_PPN_), + ID($_DFFSR_PPP_), + ID($_DFF_NN0_), + ID($_DFF_NN1_), + ID($_DFF_NP0_), + ID($_DFF_NP1_), + ID($_DFF_N_), + ID($_DFF_PN0_), + ID($_DFF_PN1_), + ID($_DFF_PP0_), + ID($_DFF_PP1_), + ID($_DFF_P_), + ID($_DLATCHSR_NNN_), + ID($_DLATCHSR_NNP_), + ID($_DLATCHSR_NPN_), + ID($_DLATCHSR_NPP_), + ID($_DLATCHSR_PNN_), + ID($_DLATCHSR_PNP_), + ID($_DLATCHSR_PPN_), + ID($_DLATCHSR_PPP_), + ID($_DLATCH_N_), + ID($_DLATCH_P_), + ID($_FF_), + }; + return res; +} + RTLIL::Const::Const() { flags = RTLIL::CONST_FLAG_NONE; diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 7279835ea..17f038e36 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -377,12 +377,14 @@ namespace RTLIL namespace ID { #define X(_id) extern IdString _id; -#include "constids.inc" +#include "kernel/constids.inc" #undef X }; extern dict<std::string, std::string> constpad; + const pool<IdString> &builtin_ff_cell_types(); + static inline std::string escape_id(const std::string &str) { if (str.size() > 0 && str[0] != '\\' && str[0] != '$') return "\\" + str; diff --git a/kernel/yosys.cc b/kernel/yosys.cc index 380f7030b..01131601f 100644 --- a/kernel/yosys.cc +++ b/kernel/yosys.cc @@ -516,7 +516,7 @@ void yosys_setup() already_setup = true; #define X(_id) RTLIL::ID::_id = "\\" # _id; -#include "constids.inc" +#include "kernel/constids.inc" #undef X #ifdef WITH_PYTHON @@ -835,7 +835,7 @@ std::string proc_share_dirname() std::string proc_share_path = proc_self_path + "share/"; if (check_file_exists(proc_share_path, true)) return proc_share_path; - proc_share_path = proc_self_path + "../share/yosys/"; + proc_share_path = proc_self_path + "../share/" + proc_program_prefix()+ "yosys/"; if (check_file_exists(proc_share_path, true)) return proc_share_path; # ifdef YOSYS_DATDIR @@ -848,6 +848,15 @@ std::string proc_share_dirname() } #endif +std::string proc_program_prefix() +{ + std::string program_prefix; +#ifdef YOSYS_PROGRAM_PREFIX + program_prefix = YOSYS_PROGRAM_PREFIX; +#endif + return program_prefix; +} + bool fgetline(FILE *f, std::string &buffer) { buffer = ""; @@ -1034,6 +1043,8 @@ void run_backend(std::string filename, std::string command, RTLIL::Design *desig command = "verilog"; else if (filename.size() > 3 && filename.compare(filename.size()-3, std::string::npos, ".il") == 0) command = "ilang"; + else if (filename.size() > 3 && filename.compare(filename.size()-3, std::string::npos, ".cc") == 0) + command = "cxxrtl"; else if (filename.size() > 4 && filename.compare(filename.size()-4, std::string::npos, ".aig") == 0) command = "aiger"; else if (filename.size() > 5 && filename.compare(filename.size()-5, std::string::npos, ".blif") == 0) diff --git a/kernel/yosys.h b/kernel/yosys.h index 16e0aaf1c..5ad47054c 100644 --- a/kernel/yosys.h +++ b/kernel/yosys.h @@ -207,6 +207,7 @@ namespace RTLIL { struct SigSpec; struct Wire; struct Cell; + struct Memory; struct Module; struct Design; struct Monitor; @@ -229,6 +230,7 @@ using RTLIL::Design; namespace hashlib { template<> struct hash_ops<RTLIL::Wire*> : hash_obj_ops {}; template<> struct hash_ops<RTLIL::Cell*> : hash_obj_ops {}; + template<> struct hash_ops<RTLIL::Memory*> : hash_obj_ops {}; template<> struct hash_ops<RTLIL::Module*> : hash_obj_ops {}; template<> struct hash_ops<RTLIL::Design*> : hash_obj_ops {}; template<> struct hash_ops<RTLIL::Monitor*> : hash_obj_ops {}; @@ -236,6 +238,7 @@ namespace hashlib { template<> struct hash_ops<const RTLIL::Wire*> : hash_obj_ops {}; template<> struct hash_ops<const RTLIL::Cell*> : hash_obj_ops {}; + template<> struct hash_ops<const RTLIL::Memory*> : hash_obj_ops {}; template<> struct hash_ops<const RTLIL::Module*> : hash_obj_ops {}; template<> struct hash_ops<const RTLIL::Design*> : hash_obj_ops {}; template<> struct hash_ops<const RTLIL::Monitor*> : hash_obj_ops {}; @@ -321,6 +324,7 @@ namespace ID = RTLIL::ID; RTLIL::Design *yosys_get_design(); std::string proc_self_dirname(); std::string proc_share_dirname(); +std::string proc_program_prefix(); const char *create_prompt(RTLIL::Design *design, int recursion_counter); std::vector<std::string> glob_filename(const std::string &filename_pattern); void rewrite_filename(std::string &filename); diff --git a/misc/yosys-config.in b/misc/yosys-config.in index 1473948cf..370835f8f 100644 --- a/misc/yosys-config.in +++ b/misc/yosys-config.in @@ -18,21 +18,21 @@ help() { echo "" echo "Use --exec to call a command instead of generating output. Example usage:" echo "" - echo " yosys-config --exec --cxx --cxxflags --ldflags -o plugin.so -shared plugin.cc --ldlibs" + echo " $0 --exec --cxx --cxxflags --ldflags -o plugin.so -shared plugin.cc --ldlibs" echo "" echo "The above command can be abbreviated as:" echo "" - echo " yosys-config --build plugin.so plugin.cc" + echo " $0 --build plugin.so plugin.cc" echo "" echo "Use --prefix to change the prefix for the special args from '--' to" echo "something else. Example:" echo "" - echo " yosys-config --prefix @ bindir: @bindir" + echo " $0 --prefix @ bindir: @bindir" echo "" echo "The args --bindir and --datdir can be directly followed by a slash and" echo "additional text. Example:" echo "" - echo " yosys-config --datdir/simlib.v" + echo " $0 --datdir/simlib.v" echo "" } >&2 exit 1 diff --git a/passes/cmds/check.cc b/passes/cmds/check.cc index 63703b848..ba29e6f4b 100644 --- a/passes/cmds/check.cc +++ b/passes/cmds/check.cc @@ -98,49 +98,6 @@ struct CheckPass : public Pass { log_header(design, "Executing CHECK pass (checking for obvious problems).\n"); - pool<IdString> fftypes; - fftypes.insert(ID($sr)); - fftypes.insert(ID($ff)); - fftypes.insert(ID($dff)); - fftypes.insert(ID($dffe)); - fftypes.insert(ID($dffsr)); - fftypes.insert(ID($adff)); - fftypes.insert(ID($dlatch)); - fftypes.insert(ID($dlatchsr)); - fftypes.insert(ID($_DFFE_NN_)); - fftypes.insert(ID($_DFFE_NP_)); - fftypes.insert(ID($_DFFE_PN_)); - fftypes.insert(ID($_DFFE_PP_)); - fftypes.insert(ID($_DFFSR_NNN_)); - fftypes.insert(ID($_DFFSR_NNP_)); - fftypes.insert(ID($_DFFSR_NPN_)); - fftypes.insert(ID($_DFFSR_NPP_)); - fftypes.insert(ID($_DFFSR_PNN_)); - fftypes.insert(ID($_DFFSR_PNP_)); - fftypes.insert(ID($_DFFSR_PPN_)); - fftypes.insert(ID($_DFFSR_PPP_)); - fftypes.insert(ID($_DFF_NN0_)); - fftypes.insert(ID($_DFF_NN1_)); - fftypes.insert(ID($_DFF_NP0_)); - fftypes.insert(ID($_DFF_NP1_)); - fftypes.insert(ID($_DFF_N_)); - fftypes.insert(ID($_DFF_PN0_)); - fftypes.insert(ID($_DFF_PN1_)); - fftypes.insert(ID($_DFF_PP0_)); - fftypes.insert(ID($_DFF_PP1_)); - fftypes.insert(ID($_DFF_P_)); - fftypes.insert(ID($_DLATCHSR_NNN_)); - fftypes.insert(ID($_DLATCHSR_NNP_)); - fftypes.insert(ID($_DLATCHSR_NPN_)); - fftypes.insert(ID($_DLATCHSR_NPP_)); - fftypes.insert(ID($_DLATCHSR_PNN_)); - fftypes.insert(ID($_DLATCHSR_PNP_)); - fftypes.insert(ID($_DLATCHSR_PPN_)); - fftypes.insert(ID($_DLATCHSR_PPP_)); - fftypes.insert(ID($_DLATCH_N_)); - fftypes.insert(ID($_DLATCH_P_)); - fftypes.insert(ID($_FF_)); - for (auto module : design->selected_whole_modules_warn()) { if (module->has_processes_warn()) @@ -242,7 +199,7 @@ struct CheckPass : public Pass { { for (auto cell : module->cells()) { - if (fftypes.count(cell->type) == 0) + if (RTLIL::builtin_ff_cell_types().count(cell->type) == 0) continue; for (auto bit : sigmap(cell->getPort(ID::Q))) diff --git a/passes/cmds/connect.cc b/passes/cmds/connect.cc index f93bada27..0b0868dfb 100644 --- a/passes/cmds/connect.cc +++ b/passes/cmds/connect.cc @@ -32,9 +32,9 @@ static void unset_drivers(RTLIL::Design *design, RTLIL::Module *module, SigMap & RTLIL::Wire *dummy_wire = module->addWire(NEW_ID, sig.size()); - for (auto &it : module->cells_) - for (auto &port : it.second->connections_) - if (ct.cell_output(it.second->type, port.first)) + for (auto cell : module->cells()) + for (auto &port : cell->connections_) + if (ct.cell_output(cell->type, port.first)) sigmap(port.second).replace(sig, dummy_wire, &port.second); for (auto &conn : module->connections_) @@ -77,15 +77,13 @@ struct ConnectPass : public Pass { } void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE { - RTLIL::Module *module = NULL; - for (auto &it : design->modules_) { - if (!design->selected(it.second)) - continue; - if (module != NULL) - log_cmd_error("Multiple modules selected: %s, %s\n", RTLIL::id2cstr(module->name), RTLIL::id2cstr(it.first)); - module = it.second; + 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 == NULL) + if (module == nullptr) log_cmd_error("No modules selected.\n"); if (!module->processes.empty()) log_cmd_error("Found processes in selected module.\n"); @@ -130,7 +128,7 @@ struct ConnectPass : public Pass { std::vector<RTLIL::SigBit> lhs = it.first.to_sigbit_vector(); std::vector<RTLIL::SigBit> rhs = it.first.to_sigbit_vector(); for (size_t i = 0; i < lhs.size(); i++) - if (rhs[i].wire != NULL) + if (rhs[i].wire != nullptr) sigmap.add(lhs[i], rhs[i]); } @@ -172,14 +170,14 @@ struct ConnectPass : public Pass { if (flag_nounset) log_cmd_error("Can't use -port together with -nounset.\n"); - if (module->cells_.count(RTLIL::escape_id(port_cell)) == 0) + if (module->cell(RTLIL::escape_id(port_cell)) == nullptr) log_cmd_error("Can't find cell %s.\n", port_cell.c_str()); RTLIL::SigSpec sig; if (!RTLIL::SigSpec::parse_sel(sig, design, module, port_expr)) log_cmd_error("Failed to parse port expression `%s'.\n", port_expr.c_str()); - module->cells_.at(RTLIL::escape_id(port_cell))->setPort(RTLIL::escape_id(port_port), sigmap(sig)); + module->cell(RTLIL::escape_id(port_cell))->setPort(RTLIL::escape_id(port_port), sigmap(sig)); } else log_cmd_error("Expected -set, -unset, or -port.\n"); diff --git a/passes/cmds/setundef.cc b/passes/cmds/setundef.cc index 5afd40923..2556d188a 100644 --- a/passes/cmds/setundef.cc +++ b/passes/cmds/setundef.cc @@ -359,34 +359,9 @@ struct SetundefPass : public Pass { pool<SigBit> ffbits; pool<Wire*> initwires; - pool<IdString> fftypes; - fftypes.insert(ID($dff)); - fftypes.insert(ID($dffe)); - fftypes.insert(ID($dffsr)); - fftypes.insert(ID($adff)); - - std::vector<char> list_np = {'N', 'P'}, list_01 = {'0', '1'}; - - for (auto c1 : list_np) - fftypes.insert(stringf("$_DFF_%c_", c1)); - - for (auto c1 : list_np) - for (auto c2 : list_np) - fftypes.insert(stringf("$_DFFE_%c%c_", c1, c2)); - - for (auto c1 : list_np) - for (auto c2 : list_np) - for (auto c3 : list_01) - fftypes.insert(stringf("$_DFF_%c%c%c_", c1, c2, c3)); - - for (auto c1 : list_np) - for (auto c2 : list_np) - for (auto c3 : list_np) - fftypes.insert(stringf("$_DFFSR_%c%c%c_", c1, c2, c3)); - for (auto cell : module->cells()) { - if (!fftypes.count(cell->type)) + if (!RTLIL::builtin_ff_cell_types().count(cell->type)) continue; for (auto bit : sigmap(cell->getPort(ID::Q))) diff --git a/passes/cmds/show.cc b/passes/cmds/show.cc index c0e07b6e1..155ed0fcd 100644 --- a/passes/cmds/show.cc +++ b/passes/cmds/show.cc @@ -41,8 +41,6 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN -using RTLIL::id2cstr; - #undef CLUSTER_CELLS_AND_PORTBOXES struct ShowWorker @@ -101,7 +99,7 @@ struct ShowWorker { sig.sort_and_unify(); for (auto &c : sig.chunks()) { - if (c.wire != NULL) + if (c.wire != nullptr) for (auto &s : color_selections) if (s.second.selected_members.count(module->name) > 0 && s.second.selected_members.at(module->name).count(c.wire->name) > 0) return stringf("color=\"%s\"", s.first.c_str()); @@ -218,7 +216,7 @@ struct ShowWorker if (sig.is_chunk()) { const RTLIL::SigChunk &c = sig.as_chunk(); - if (c.wire != NULL && design->selected_member(module->name, c.wire->name)) { + if (c.wire != nullptr && design->selected_member(module->name, c.wire->name)) { if (!range_check || c.wire->width == c.width) return stringf("n%d", id2num(c.wire->name)); } else { @@ -230,7 +228,7 @@ struct ShowWorker return std::string(); } - std::string gen_portbox(std::string port, RTLIL::SigSpec sig, bool driver, std::string *node = NULL) + std::string gen_portbox(std::string port, RTLIL::SigSpec sig, bool driver, std::string *node = nullptr) { std::string code; std::string net = gen_signode_simple(sig); @@ -287,7 +285,7 @@ struct ShowWorker else code += stringf("x%d:e -> %s:w [arrowhead=odiamond, arrowtail=odiamond, dir=both, %s, %s];\n", idx, port.c_str(), nextColor(sig).c_str(), widthLabel(sig.size()).c_str()); } - if (node != NULL) + if (node != nullptr) *node = stringf("x%d", idx); } else @@ -300,7 +298,7 @@ struct ShowWorker net_conn_map[net].bits = sig.size(); net_conn_map[net].color = nextColor(sig, net_conn_map[net].color); } - if (node != NULL) + if (node != nullptr) *node = net; } return code; @@ -366,22 +364,20 @@ struct ShowWorker std::set<std::string> all_sources, all_sinks; std::map<std::string, std::string> wires_on_demand; - for (auto &it : module->wires_) { - if (!design->selected_member(module->name, it.first)) - continue; + for (auto wire : module->selected_wires()) { const char *shape = "diamond"; - if (it.second->port_input || it.second->port_output) + if (wire->port_input || wire->port_output) shape = "octagon"; - if (it.first[0] == '\\') { + if (wire->name[0] == '\\') { fprintf(f, "n%d [ shape=%s, label=\"%s\", %s, fontcolor=\"black\" ];\n", - id2num(it.first), shape, findLabel(it.first.str()), - nextColor(RTLIL::SigSpec(it.second), "color=\"black\"").c_str()); - if (it.second->port_input) - all_sources.insert(stringf("n%d", id2num(it.first))); - else if (it.second->port_output) - all_sinks.insert(stringf("n%d", id2num(it.first))); + id2num(wire->name), shape, findLabel(wire->name.str()), + nextColor(RTLIL::SigSpec(wire), "color=\"black\"").c_str()); + if (wire->port_input) + all_sources.insert(stringf("n%d", id2num(wire->name))); + else if (wire->port_output) + all_sinks.insert(stringf("n%d", id2num(wire->name))); } else { - wires_on_demand[stringf("n%d", id2num(it.first))] = it.first.str(); + wires_on_demand[stringf("n%d", id2num(wire->name))] = wire->name.str(); } } @@ -398,15 +394,12 @@ struct ShowWorker fprintf(f, "}\n"); } - for (auto &it : module->cells_) + for (auto cell : module->selected_cells()) { - if (!design->selected_member(module->name, it.first)) - continue; - std::vector<RTLIL::IdString> in_ports, out_ports; - for (auto &conn : it.second->connections()) { - if (!ct.cell_output(it.second->type, conn.first)) + for (auto &conn : cell->connections()) { + if (!ct.cell_output(cell->type, conn.first)) in_ports.push_back(conn.first); else out_ports.push_back(conn.first); @@ -419,12 +412,12 @@ struct ShowWorker for (auto &p : in_ports) label_string += stringf("<p%d> %s%s|", id2num(p), escape(p.str()), - genSignedLabels && it.second->hasParam(p.str() + "_SIGNED") && - it.second->getParam(p.str() + "_SIGNED").as_bool() ? "*" : ""); + genSignedLabels && cell->hasParam(p.str() + "_SIGNED") && + cell->getParam(p.str() + "_SIGNED").as_bool() ? "*" : ""); if (label_string[label_string.size()-1] == '|') label_string = label_string.substr(0, label_string.size()-1); - label_string += stringf("}|%s\\n%s|{", findLabel(it.first.str()), escape(it.second->type.str())); + label_string += stringf("}|%s\\n%s|{", findLabel(cell->name.str()), escape(cell->type.str())); for (auto &p : out_ports) label_string += stringf("<p%d> %s|", id2num(p), escape(p.str())); @@ -434,19 +427,19 @@ struct ShowWorker label_string += "}}"; std::string code; - for (auto &conn : it.second->connections()) { - code += gen_portbox(stringf("c%d:p%d", id2num(it.first), id2num(conn.first)), - conn.second, ct.cell_output(it.second->type, conn.first)); + for (auto &conn : cell->connections()) { + code += gen_portbox(stringf("c%d:p%d", id2num(cell->name), id2num(conn.first)), + conn.second, ct.cell_output(cell->type, conn.first)); } #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(it.first), id2num(it.first), label_string.c_str(), findColor(it.first), code.c_str()); + id2num(cell->name), id2num(cell->name), label_string.c_str(), findColor(cell->name), code.c_str()); else #endif fprintf(f, "c%d [ shape=record, label=\"%s\"%s ];\n%s", - id2num(it.first), label_string.c_str(), findColor(it.first.str()), code.c_str()); + id2num(cell->name), label_string.c_str(), findColor(cell->name.str()), code.c_str()); } for (auto &it : module->processes) @@ -491,12 +484,12 @@ struct ShowWorker { bool found_lhs_wire = false; for (auto &c : conn.first.chunks()) { - if (c.wire == NULL || design->selected_member(module->name, c.wire->name)) + if (c.wire == nullptr || design->selected_member(module->name, c.wire->name)) found_lhs_wire = true; } bool found_rhs_wire = false; for (auto &c : conn.second.chunks()) { - if (c.wire == NULL || design->selected_member(module->name, c.wire->name)) + if (c.wire == nullptr || design->selected_member(module->name, c.wire->name)) found_rhs_wire = true; } if (!found_lhs_wire || !found_rhs_wire) @@ -572,23 +565,21 @@ struct ShowWorker design->optimize(); page_counter = 0; - for (auto &mod_it : design->modules_) + for (auto mod : design->selected_modules()) { - module = mod_it.second; - if (!design->selected_module(module->name)) - continue; + module = mod; if (design->selected_whole_module(module->name)) { if (module->get_blackbox_attribute()) { - // log("Skipping blackbox module %s.\n", id2cstr(module->name)); + // log("Skipping blackbox module %s.\n", log_id(module->name)); continue; } else - if (module->cells_.empty() && module->connections().empty() && module->processes.empty()) { - log("Skipping empty module %s.\n", id2cstr(module->name)); + if (module->cells().size() == 0 && module->connections().empty() && module->processes.empty()) { + log("Skipping empty module %s.\n", log_id(module->name)); continue; } else - log("Dumping module %s to page %d.\n", id2cstr(module->name), ++page_counter); + log("Dumping module %s to page %d.\n", log_id(module->name), ++page_counter); } else - log("Dumping selected parts of module %s to page %d.\n", id2cstr(module->name), ++page_counter); + log("Dumping selected parts of module %s to page %d.\n", log_id(module->name), ++page_counter); handle_module(); } } @@ -802,13 +793,12 @@ struct ShowPass : public Pass { if (format != "ps" && format != "dot") { int modcount = 0; - for (auto &mod_it : design->modules_) { - if (mod_it.second->get_blackbox_attribute()) + for (auto module : design->selected_modules()) { + if (module->get_blackbox_attribute()) continue; - if (mod_it.second->cells_.empty() && mod_it.second->connections().empty()) + if (module->cells().size() == 0 && module->connections().empty()) continue; - if (design->selected_module(mod_it.first)) - modcount++; + modcount++; } if (modcount > 1) log_cmd_error("For formats different than 'ps' or 'dot' only one module must be selected.\n"); @@ -835,7 +825,7 @@ struct ShowPass : public Pass { FILE *f = fopen(dot_file.c_str(), "w"); if (custom_prefix) yosys_output_files.insert(dot_file); - if (f == NULL) { + if (f == nullptr) { for (auto lib : libs) delete lib; log_cmd_error("Can't open dot file `%s' for writing.\n", dot_file.c_str()); @@ -889,8 +879,8 @@ struct ShowPass : public Pass { if (flag_pause) { #ifdef YOSYS_ENABLE_READLINE - char *input = NULL; - while ((input = readline("Press ENTER to continue (or type 'shell' to open a shell)> ")) != NULL) { + char *input = nullptr; + while ((input = readline("Press ENTER to continue (or type 'shell' to open a shell)> ")) != nullptr) { if (input[strspn(input, " \t\r\n")] == 0) break; char *p = input + strspn(input, " \t\r\n"); diff --git a/passes/cmds/splitnets.cc b/passes/cmds/splitnets.cc index bf693e3d4..1e7dedd70 100644 --- a/passes/cmds/splitnets.cc +++ b/passes/cmds/splitnets.cc @@ -141,6 +141,9 @@ struct SplitnetsPass : public Pass { for (auto module : design->selected_modules()) { + if (module->has_processes_warn()) + continue; + SplitnetsWorker worker; if (flag_ports) diff --git a/passes/hierarchy/submod.cc b/passes/hierarchy/submod.cc index f5701acee..2db7cf26b 100644 --- a/passes/hierarchy/submod.cc +++ b/passes/hierarchy/submod.cc @@ -51,7 +51,7 @@ struct SubmodWorker RTLIL::Wire *new_wire; RTLIL::Const is_int_driven; bool is_int_used, is_ext_driven, is_ext_used; - wire_flags_t(RTLIL::Wire* wire) : new_wire(NULL), is_int_driven(State::S0, GetSize(wire)), is_int_used(false), is_ext_driven(false), is_ext_used(false) { } + wire_flags_t(RTLIL::Wire* wire) : new_wire(nullptr), is_int_driven(State::S0, GetSize(wire)), is_int_used(false), is_ext_driven(false), is_ext_used(false) { } }; std::map<RTLIL::Wire*, wire_flags_t> wire_flags; bool flag_found_something; @@ -75,7 +75,7 @@ struct SubmodWorker void flag_signal(const RTLIL::SigSpec &sig, bool create, bool set_int_driven, bool set_int_used, bool set_ext_driven, bool set_ext_used) { for (auto &c : sig.chunks()) - if (c.wire != NULL) { + if (c.wire != nullptr) { flag_wire(c.wire, create, set_int_used, set_ext_driven, set_ext_used); if (set_int_driven) for (int i = c.offset; i < c.offset+c.width; i++) { @@ -100,8 +100,7 @@ struct SubmodWorker flag_signal(conn.second, true, true, true, false, false); } } - for (auto &it : module->cells_) { - RTLIL::Cell *cell = it.second; + for (auto cell : module->cells()) { if (submod.cells.count(cell) > 0) continue; if (ct.cell_known(cell->type)) { @@ -211,7 +210,7 @@ struct SubmodWorker RTLIL::Cell *new_cell = new_mod->addCell(cell->name, cell); for (auto &conn : new_cell->connections_) for (auto &bit : conn.second) - if (bit.wire != NULL) { + if (bit.wire != nullptr) { log_assert(wire_flags.count(bit.wire) > 0); bit.wire = wire_flags.at(bit.wire).new_wire; } @@ -274,12 +273,11 @@ struct SubmodWorker if (opt_name.empty()) { - for (auto &it : module->wires_) - it.second->attributes.erase(ID::submod); + for (auto wire : module->wires()) + wire->attributes.erase(ID::submod); - for (auto &it : module->cells_) + for (auto cell : module->cells()) { - RTLIL::Cell *cell = it.second; if (cell->attributes.count(ID::submod) == 0 || cell->attributes[ID::submod].bits.size() == 0) { cell->attributes.erase(ID::submod); continue; @@ -291,7 +289,7 @@ struct SubmodWorker if (submodules.count(submod_str) == 0) { submodules[submod_str].name = submod_str; submodules[submod_str].full_name = module->name.str() + "_" + submod_str; - while (design->modules_.count(submodules[submod_str].full_name) != 0 || + while (design->module(submodules[submod_str].full_name) != nullptr || module->count_id(submodules[submod_str].full_name) != 0) submodules[submod_str].full_name += "_"; } @@ -301,9 +299,8 @@ struct SubmodWorker } else { - for (auto &it : module->cells_) + for (auto cell : module->cells()) { - RTLIL::Cell *cell = it.second; if (!design->selected(module, cell)) continue; submodules[opt_name].name = opt_name; @@ -392,12 +389,12 @@ struct SubmodPass : public Pass { while (did_something) { did_something = false; std::vector<RTLIL::IdString> queued_modules; - for (auto &mod_it : design->modules_) - if (handled_modules.count(mod_it.first) == 0 && design->selected_whole_module(mod_it.first)) - queued_modules.push_back(mod_it.first); + for (auto mod : design->modules()) + if (handled_modules.count(mod->name) == 0 && design->selected_whole_module(mod->name)) + queued_modules.push_back(mod->name); for (auto &modname : queued_modules) - if (design->modules_.count(modname) != 0) { - SubmodWorker worker(design, design->modules_[modname], copy_mode, hidden_mode); + if (design->module(modname) != nullptr) { + SubmodWorker worker(design, design->module(modname), copy_mode, hidden_mode); handled_modules.insert(modname); did_something = true; } @@ -407,15 +404,13 @@ struct SubmodPass : public Pass { } else { - RTLIL::Module *module = NULL; - for (auto &mod_it : design->modules_) { - if (!design->selected_module(mod_it.first)) - continue; - if (module != NULL) - log_cmd_error("More than one module selected: %s %s\n", module->name.c_str(), mod_it.first.c_str()); - module = mod_it.second; + RTLIL::Module *module = nullptr; + for (auto mod : design->selected_modules()) { + if (module != nullptr) + log_cmd_error("More than one module selected: %s %s\n", module->name.c_str(), mod->name.c_str()); + module = mod; } - if (module == NULL) + if (module == nullptr) log("Nothing selected -> do nothing.\n"); else { Pass::call_on_module(design, module, "opt_clean"); diff --git a/passes/memory/memory_bram.cc b/passes/memory/memory_bram.cc index 52ee1e99d..a25b4536a 100644 --- a/passes/memory/memory_bram.cc +++ b/passes/memory/memory_bram.cc @@ -137,9 +137,26 @@ struct rules_t vector<vector<std::tuple<bool,IdString,Const>>> attributes; }; + bool attr_icase; dict<IdString, vector<bram_t>> brams; vector<match_t> matches; + std::string map_case(std::string value) const + { + if (attr_icase) { + for (char &c : value) + c = tolower(c); + } + return value; + } + + RTLIL::Const map_case(RTLIL::Const value) const + { + if (value.flags & RTLIL::CONST_FLAG_STRING) + return map_case(value.decode_string()); + return value; + } + std::ifstream infile; vector<string> tokens; vector<string> labels; @@ -337,7 +354,7 @@ struct rules_t IdString key = RTLIL::escape_id(tokens[idx].substr(c1, c2)); Const val = c2 != std::string::npos ? tokens[idx].substr(c2+1) : RTLIL::Const(1); - data.attributes.back().emplace_back(exists, key, val); + data.attributes.back().emplace_back(exists, key, map_case(val)); } continue; } @@ -351,6 +368,7 @@ struct rules_t rewrite_filename(filename); infile.open(filename); linecount = 0; + attr_icase = false; if (infile.fail()) log_error("Can't open rules file `%s'.\n", filename.c_str()); @@ -360,6 +378,11 @@ struct rules_t if (!labels.empty()) syntax_error(); + if (GetSize(tokens) == 2 && tokens[0] == "attr_icase") { + attr_icase = atoi(tokens[1].c_str()); + continue; + } + if (tokens[0] == "bram") { parse_bram(); continue; @@ -843,7 +866,7 @@ grow_read_ports:; } else if (!exists) continue; - if (it->second != value) + if (rules.map_case(it->second) != value) continue; found = true; break; @@ -855,7 +878,7 @@ grow_read_ports:; ss << "!"; IdString key = std::get<1>(sums.front()); ss << log_id(key); - const Const &value = std::get<2>(sums.front()); + const Const &value = rules.map_case(std::get<2>(sums.front())); if (exists && value != Const(1)) ss << "=\"" << value.decode_string() << "\""; @@ -1167,7 +1190,7 @@ void handle_cell(Cell *cell, const rules_t &rules) } else if (!exists) continue; - if (it->second != value) + if (rules.map_case(it->second) != value) continue; found = true; break; @@ -1179,7 +1202,7 @@ void handle_cell(Cell *cell, const rules_t &rules) ss << "!"; IdString key = std::get<1>(sums.front()); ss << log_id(key); - const Const &value = std::get<2>(sums.front()); + const Const &value = rules.map_case(std::get<2>(sums.front())); if (exists && value != Const(1)) ss << "=\"" << value.decode_string() << "\""; @@ -1252,8 +1275,13 @@ struct MemoryBramPass : public Pass { log("The given rules file describes the available resources and how they should be\n"); log("used.\n"); log("\n"); - log("The rules file contains a set of block ram description and a sequence of match\n"); - log("rules. A block ram description looks like this:\n"); + log("The rules file contains configuration options, a set of block ram description\n"); + log("and a sequence of match rules.\n"); + log("\n"); + log("The option 'attr_icase' configures how attribute values are matched. The value 0\n"); + log("means case-sensitive, 1 means case-insensitive.\n"); + log("\n"); + log("A block ram description looks like this:\n"); log("\n"); log(" bram RAMB1024X32 # name of BRAM cell\n"); log(" init 1 # set to '1' if BRAM can be initialized\n"); diff --git a/passes/memory/memory_collect.cc b/passes/memory/memory_collect.cc index a62dcc2c4..ef8b07811 100644 --- a/passes/memory/memory_collect.cc +++ b/passes/memory/memory_collect.cc @@ -60,8 +60,7 @@ Cell *handle_memory(Module *module, RTLIL::Memory *memory) int addr_bits = 0; std::vector<Cell*> memcells; - for (auto &cell_it : module->cells_) { - Cell *cell = cell_it.second; + for (auto cell : module->cells()) if (cell->type.in(ID($memrd), ID($memwr), ID($meminit)) && memory->name == cell->parameters[ID::MEMID].decode_string()) { SigSpec addr = sigmap(cell->getPort(ID::ADDR)); for (int i = 0; i < GetSize(addr); i++) @@ -69,7 +68,6 @@ Cell *handle_memory(Module *module, RTLIL::Memory *memory) addr_bits = std::max(addr_bits, i+1); memcells.push_back(cell); } - } if (memory->start_offset == 0 && addr_bits < 30 && (1 << addr_bits) < memory->size) memory->size = 1 << addr_bits; @@ -260,9 +258,8 @@ struct MemoryCollectPass : public Pass { void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE { log_header(design, "Executing MEMORY_COLLECT pass (generating $mem cells).\n"); extra_args(args, 1, design); - for (auto &mod_it : design->modules_) - if (design->selected(mod_it.second)) - handle_module(design, mod_it.second); + for (auto module : design->selected_modules()) + handle_module(design, module); } } MemoryCollectPass; diff --git a/passes/memory/memory_map.cc b/passes/memory/memory_map.cc index da0673c8f..9d455f55b 100644 --- a/passes/memory/memory_map.cc +++ b/passes/memory/memory_map.cc @@ -28,11 +28,32 @@ PRIVATE_NAMESPACE_BEGIN struct MemoryMapWorker { + bool attr_icase = false; + dict<RTLIL::IdString, std::vector<RTLIL::Const>> attributes; + RTLIL::Design *design; RTLIL::Module *module; std::map<std::pair<RTLIL::SigSpec, RTLIL::SigSpec>, RTLIL::SigBit> decoder_cache; + MemoryMapWorker(RTLIL::Design *design, RTLIL::Module *module) : design(design), module(module) {} + + std::string map_case(std::string value) const + { + if (attr_icase) { + for (char &c : value) + c = tolower(c); + } + return value; + } + + RTLIL::Const map_case(RTLIL::Const value) const + { + if (value.flags & RTLIL::CONST_FLAG_STRING) + return map_case(value.decode_string()); + return value; + } + std::string genid(RTLIL::IdString name, std::string token1 = "", int i = -1, std::string token2 = "", int j = -1, std::string token3 = "", int k = -1, std::string token4 = "") { std::stringstream sstr; @@ -98,6 +119,36 @@ struct MemoryMapWorker return; } + // check if attributes allow us to infer FFRAM for this cell + for (const auto &attr : attributes) { + if (cell->attributes.count(attr.first)) { + const auto &cell_attr = cell->attributes[attr.first]; + if (attr.second.empty()) { + log("Not mapping memory cell %s in module %s (attribute %s is set).\n", + cell->name.c_str(), module->name.c_str(), attr.first.c_str()); + return; + } + + bool found = false; + for (auto &value : attr.second) { + if (map_case(cell_attr) == map_case(value)) { + found = true; + break; + } + } + if (!found) { + if (cell_attr.flags & RTLIL::CONST_FLAG_STRING) { + log("Not mapping memory cell %s in module %s (attribute %s is set to \"%s\").\n", + cell->name.c_str(), module->name.c_str(), attr.first.c_str(), cell_attr.decode_string().c_str()); + } else { + log("Not mapping memory cell %s in module %s (attribute %s is set to %d).\n", + cell->name.c_str(), module->name.c_str(), attr.first.c_str(), cell_attr.as_int()); + } + return; + } + } + } + // all write ports must share the same clock RTLIL::SigSpec clocks = cell->getPort(ID::WR_CLK); RTLIL::Const clocks_pol = cell->parameters[ID::WR_CLK_POLARITY]; @@ -339,7 +390,7 @@ struct MemoryMapWorker module->remove(cell); } - MemoryMapWorker(RTLIL::Design *design, RTLIL::Module *module) : design(design), module(module) + void run() { std::vector<RTLIL::Cell*> cells; for (auto cell : module->selected_cells()) @@ -356,17 +407,73 @@ struct MemoryMapPass : public Pass { { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); - log(" memory_map [selection]\n"); + log(" memory_map [options] [selection]\n"); log("\n"); log("This pass converts multiport memory cells as generated by the memory_collect\n"); log("pass to word-wide DFFs and address decoders.\n"); log("\n"); + log(" -attr !<name>\n"); + log(" do not map memories that have attribute <name> set.\n"); + log("\n"); + log(" -attr <name>[=<value>]\n"); + log(" for memories that have attribute <name> set, only map them if its value\n"); + log(" is a string <value> (if specified), or an integer 1 (otherwise). if this\n"); + log(" option is specified multiple times, map the memory if the attribute is\n"); + log(" to any of the values.\n"); + log("\n"); + log(" -iattr\n"); + log(" for -attr, ignore case of <value>.\n"); + log("\n"); } - void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE { + void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE + { + bool attr_icase = false; + dict<RTLIL::IdString, std::vector<RTLIL::Const>> attributes; + log_header(design, "Executing MEMORY_MAP pass (converting $mem cells to logic and flip-flops).\n"); - extra_args(args, 1, design); - for (auto mod : design->selected_modules()) - MemoryMapWorker(design, mod); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) + { + if (args[argidx] == "-attr" && argidx + 1 < args.size()) + { + std::string attr_arg = args[++argidx]; + std::string name; + RTLIL::Const value; + size_t eq_at = attr_arg.find('='); + if (eq_at != std::string::npos) { + name = attr_arg.substr(0, eq_at); + value = attr_arg.substr(eq_at + 1); + } else { + name = attr_arg; + value = RTLIL::Const(1); + } + if (attr_arg.size() > 1 && attr_arg[0] == '!') { + if (value != RTLIL::Const(1)) { + --argidx; + break; // we don't support -attr !<name>=<value> + } + attributes[RTLIL::escape_id(name.substr(1))].clear(); + } else { + attributes[RTLIL::escape_id(name)].push_back(value); + } + continue; + } + if (args[argidx] == "-iattr") + { + attr_icase = true; + continue; + } + break; + } + extra_args(args, argidx, design); + + for (auto mod : design->selected_modules()) { + MemoryMapWorker worker(design, mod); + worker.attr_icase = attr_icase; + worker.attributes = attributes; + worker.run(); + } } } MemoryMapPass; diff --git a/passes/memory/memory_unpack.cc b/passes/memory/memory_unpack.cc index 9173c791b..8d284edcd 100644 --- a/passes/memory/memory_unpack.cc +++ b/passes/memory/memory_unpack.cc @@ -118,11 +118,11 @@ void handle_memory(RTLIL::Module *module, RTLIL::Cell *memory) void handle_module(RTLIL::Design *design, RTLIL::Module *module) { std::vector<RTLIL::IdString> memcells; - for (auto &cell_it : module->cells_) - if (cell_it.second->type == ID($mem) && design->selected(module, cell_it.second)) - memcells.push_back(cell_it.first); + for (auto cell : module->cells()) + if (cell->type == ID($mem) && design->selected(module, cell)) + memcells.push_back(cell->name); for (auto &it : memcells) - handle_memory(module, module->cells_.at(it)); + handle_memory(module, module->cell(it)); } struct MemoryUnpackPass : public Pass { @@ -140,9 +140,8 @@ struct MemoryUnpackPass : public Pass { void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE { log_header(design, "Executing MEMORY_UNPACK pass (generating $memrd/$memwr cells form $mem cells).\n"); extra_args(args, 1, design); - for (auto &mod_it : design->modules_) - if (design->selected(mod_it.second)) - handle_module(design, mod_it.second); + for (auto module : design->selected_modules()) + handle_module(design, module); } } MemoryUnpackPass; diff --git a/passes/opt/opt_expr.cc b/passes/opt/opt_expr.cc index 1a4dd9239..0222df38a 100644 --- a/passes/opt/opt_expr.cc +++ b/passes/opt/opt_expr.cc @@ -1426,6 +1426,39 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons goto next_cell; } } + + sig_a = assign_map(cell->getPort(ID::A)); + sig_b = assign_map(cell->getPort(ID::B)); + int a_zeros, b_zeros; + for (a_zeros = 0; a_zeros < GetSize(sig_a); a_zeros++) + if (sig_a[a_zeros] != RTLIL::State::S0) + break; + for (b_zeros = 0; b_zeros < GetSize(sig_b); b_zeros++) + if (sig_b[b_zeros] != RTLIL::State::S0) + break; + if (a_zeros || b_zeros) { + int y_zeros = a_zeros + b_zeros; + cover("opt.opt_expr.mul_low_zeros"); + + log_debug("Removing low %d A and %d B bits from cell `%s' in module `%s'.\n", + a_zeros, b_zeros, cell->name.c_str(), module->name.c_str()); + + if (a_zeros) { + cell->setPort(ID::A, sig_a.extract_end(a_zeros)); + cell->parameters[ID::A_WIDTH] = GetSize(sig_a) - a_zeros; + } + if (b_zeros) { + cell->setPort(ID::B, sig_b.extract_end(b_zeros)); + cell->parameters[ID::B_WIDTH] = GetSize(sig_b) - b_zeros; + } + cell->setPort(ID::Y, sig_y.extract_end(y_zeros)); + cell->parameters[ID::Y_WIDTH] = GetSize(sig_y) - y_zeros; + module->connect(RTLIL::SigSig(sig_y.extract(0, y_zeros), RTLIL::SigSpec(0, y_zeros))); + cell->check(); + + did_something = true; + goto next_cell; + } } if (!keepdc && cell->type.in(ID($div), ID($mod))) diff --git a/passes/opt/opt_merge.cc b/passes/opt/opt_merge.cc index a861bd7a4..d845926fc 100644 --- a/passes/opt/opt_merge.cc +++ b/passes/opt/opt_merge.cc @@ -118,9 +118,7 @@ struct OptMergeWorker for (auto &it : *conn) { RTLIL::SigSpec sig; if (cell->output(it.first)) { - if (it.first == ID::Q && (cell->type.begins_with("$dff") || cell->type.begins_with("$dlatch") || - cell->type.begins_with("$_DFF") || cell->type.begins_with("$_DLATCH") || cell->type.begins_with("$_SR_") || - cell->type.in(ID($adff), ID($sr), ID($ff), ID($_FF_)))) { + if (it.first == ID::Q && RTLIL::builtin_ff_cell_types().count(cell->type)) { // For the 'Q' output of state elements, // use its (* init *) attribute value for (const auto &b : dff_init_map(it.second)) diff --git a/passes/techmap/Makefile.inc b/passes/techmap/Makefile.inc index c16db0d57..bb3f6da98 100644 --- a/passes/techmap/Makefile.inc +++ b/passes/techmap/Makefile.inc @@ -59,10 +59,10 @@ passes/techmap/techmap.inc: techlibs/common/techmap.v passes/techmap/techmap.o: passes/techmap/techmap.inc ifneq ($(CONFIG),emcc) -TARGETS += yosys-filterlib$(EXE) +TARGETS += $(PROGRAM_PREFIX)yosys-filterlib$(EXE) EXTRA_OBJS += passes/techmap/filterlib.o -yosys-filterlib$(EXE): passes/techmap/filterlib.o +$(PROGRAM_PREFIX)yosys-filterlib$(EXE): passes/techmap/filterlib.o $(Q) mkdir -p $(dir $@) - $(P) $(LD) -o yosys-filterlib$(EXE) $(LDFLAGS) $^ $(LDLIBS) + $(P) $(LD) -o $(PROGRAM_PREFIX)yosys-filterlib$(EXE) $(LDFLAGS) $^ $(LDLIBS) endif diff --git a/passes/techmap/abc.cc b/passes/techmap/abc.cc index 78ecab1e7..0ee495abd 100644 --- a/passes/techmap/abc.cc +++ b/passes/techmap/abc.cc @@ -702,7 +702,7 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin if (dff_mode && clk_sig.empty()) log_cmd_error("Clock domain %s not found.\n", clk_str.c_str()); - std::string tempdir_name = "/tmp/yosys-abc-XXXXXX"; + std::string tempdir_name = "/tmp/" + proc_program_prefix()+ "yosys-abc-XXXXXX"; if (!cleanup) tempdir_name[0] = tempdir_name[4] = '_'; tempdir_name = make_temp_dir(tempdir_name); @@ -1305,7 +1305,7 @@ struct AbcPass : public Pass { #ifdef ABCEXTERNAL log(" use the specified command instead of \"" ABCEXTERNAL "\" to execute ABC.\n"); #else - log(" use the specified command instead of \"<yosys-bindir>/yosys-abc\" to execute ABC.\n"); + log(" use the specified command instead of \"<yosys-bindir>/%syosys-abc\" to execute ABC.\n", proc_program_prefix().c_str()); #endif log(" This can e.g. be used to call a specific version of ABC or a wrapper.\n"); log("\n"); @@ -1491,7 +1491,7 @@ struct AbcPass : public Pass { #ifdef ABCEXTERNAL std::string exe_file = ABCEXTERNAL; #else - std::string exe_file = proc_self_dirname() + "yosys-abc"; + std::string exe_file = proc_self_dirname() + proc_program_prefix() + "yosys-abc"; #endif std::string script_file, liberty_file, constr_file, clk_str; std::string delay_target, sop_inputs, sop_products, lutin_shared = "-S 1"; @@ -1509,8 +1509,8 @@ struct AbcPass : public Pass { #ifdef _WIN32 #ifndef ABCEXTERNAL - if (!check_file_exists(exe_file + ".exe") && check_file_exists(proc_self_dirname() + "..\\yosys-abc.exe")) - exe_file = proc_self_dirname() + "..\\yosys-abc"; + if (!check_file_exists(exe_file + ".exe") && check_file_exists(proc_self_dirname() + "..\\" + proc_program_prefix()+ "yosys-abc.exe")) + exe_file = proc_self_dirname() + "..\\" + proc_program_prefix() + "yosys-abc"; #endif #endif diff --git a/passes/techmap/abc9.cc b/passes/techmap/abc9.cc index 9757b1539..1b3d5ff06 100644 --- a/passes/techmap/abc9.cc +++ b/passes/techmap/abc9.cc @@ -100,7 +100,7 @@ struct Abc9Pass : public ScriptPass #ifdef ABCEXTERNAL log(" use the specified command instead of \"" ABCEXTERNAL "\" to execute ABC.\n"); #else - log(" use the specified command instead of \"<yosys-bindir>/yosys-abc\" to execute ABC.\n"); + log(" use the specified command instead of \"<yosys-bindir>/%syosys-abc\" to execute ABC.\n", proc_program_prefix().c_str()); #endif log(" This can e.g. be used to call a specific version of ABC or a wrapper.\n"); log("\n"); @@ -326,7 +326,7 @@ struct Abc9Pass : public ScriptPass if (!active_design->selected_whole_module(mod)) log_error("Can't handle partially selected module %s!\n", log_id(mod)); - std::string tempdir_name = "/tmp/yosys-abc-XXXXXX"; + std::string tempdir_name = "/tmp/" + proc_program_prefix() + "yosys-abc-XXXXXX"; if (!cleanup) tempdir_name[0] = tempdir_name[4] = '_'; tempdir_name = make_temp_dir(tempdir_name); diff --git a/passes/techmap/abc9_exe.cc b/passes/techmap/abc9_exe.cc index 898285c69..303b04402 100644 --- a/passes/techmap/abc9_exe.cc +++ b/passes/techmap/abc9_exe.cc @@ -293,7 +293,7 @@ struct Abc9ExePass : public Pass { #ifdef ABCEXTERNAL log(" use the specified command instead of \"" ABCEXTERNAL "\" to execute ABC.\n"); #else - log(" use the specified command instead of \"<yosys-bindir>/yosys-abc\" to execute ABC.\n"); + log(" use the specified command instead of \"<yosys-bindir>/%syosys-abc\" to execute ABC.\n", proc_program_prefix().c_str()); #endif log(" This can e.g. be used to call a specific version of ABC or a wrapper.\n"); log("\n"); @@ -367,7 +367,7 @@ struct Abc9ExePass : public Pass { #ifdef ABCEXTERNAL std::string exe_file = ABCEXTERNAL; #else - std::string exe_file = proc_self_dirname() + "yosys-abc"; + std::string exe_file = proc_self_dirname() + proc_program_prefix()+ "yosys-abc"; #endif std::string script_file, clk_str, box_file, lut_file; std::string delay_target, lutin_shared = "-S 1", wire_delay; @@ -383,8 +383,8 @@ struct Abc9ExePass : public Pass { #ifdef _WIN32 #ifndef ABCEXTERNAL - if (!check_file_exists(exe_file + ".exe") && check_file_exists(proc_self_dirname() + "..\\yosys-abc.exe")) - exe_file = proc_self_dirname() + "..\\yosys-abc"; + if (!check_file_exists(exe_file + ".exe") && check_file_exists(proc_self_dirname() + "..\\" + proc_program_prefix() + "yosys-abc.exe")) + exe_file = proc_self_dirname() + "..\\" + proc_program_prefix() + "yosys-abc"; #endif #endif diff --git a/passes/techmap/hilomap.cc b/passes/techmap/hilomap.cc index 9ec651aef..5aeb5ea79 100644 --- a/passes/techmap/hilomap.cc +++ b/passes/techmap/hilomap.cc @@ -105,13 +105,9 @@ struct HilomapPass : public Pass { } extra_args(args, argidx, design); - for (auto &it : design->modules_) + for (auto mod : design->selected_modules()) { - module = it.second; - - if (!design->selected(module)) - continue; - + module = mod; last_hi = RTLIL::State::Sm; last_lo = RTLIL::State::Sm; diff --git a/passes/techmap/zinit.cc b/passes/techmap/zinit.cc index a427c4987..9eb47ff6d 100644 --- a/passes/techmap/zinit.cc +++ b/passes/techmap/zinit.cc @@ -57,8 +57,7 @@ struct ZinitPass : public Pass { for (auto module : design->selected_modules()) { SigMap sigmap(module); - dict<SigBit, State> initbits; - pool<SigBit> donebits; + dict<SigBit, std::pair<State,SigBit>> initbits; for (auto wire : module->selected_wires()) { @@ -67,7 +66,6 @@ struct ZinitPass : public Pass { SigSpec wirebits = sigmap(wire); Const initval = wire->attributes.at(ID::init); - wire->attributes.erase(ID::init); for (int i = 0; i < GetSize(wirebits) && i < GetSize(initval); i++) { @@ -78,22 +76,25 @@ struct ZinitPass : public Pass { continue; if (initbits.count(bit)) { - if (initbits.at(bit) != val) + if (initbits.at(bit).first != val) log_error("Conflicting init values for signal %s (%s = %s != %s).\n", log_signal(bit), log_signal(SigBit(wire, i)), - log_signal(val), log_signal(initbits.at(bit))); + log_signal(val), log_signal(initbits.at(bit).first)); continue; } - initbits[bit] = val; + initbits[bit] = std::make_pair(val,SigBit(wire,i)); } } pool<IdString> dff_types = { - ID($ff), ID($dff), ID($dffe), ID($dffsr), ID($adff), + // FIXME: It would appear that supporting + // $dffsr/$_DFFSR_* would require a new + // cell type where S has priority over R + ID($ff), ID($dff), ID($dffe), /*ID($dffsr),*/ ID($adff), ID($_FF_), ID($_DFFE_NN_), ID($_DFFE_NP_), ID($_DFFE_PN_), ID($_DFFE_PP_), - ID($_DFFSR_NNN_), ID($_DFFSR_NNP_), ID($_DFFSR_NPN_), ID($_DFFSR_NPP_), - ID($_DFFSR_PNN_), ID($_DFFSR_PNP_), ID($_DFFSR_PPN_), ID($_DFFSR_PPP_), + /*ID($_DFFSR_NNN_), ID($_DFFSR_NNP_), ID($_DFFSR_NPN_), ID($_DFFSR_NPP_), + ID($_DFFSR_PNN_), ID($_DFFSR_PNP_), ID($_DFFSR_PPN_), ID($_DFFSR_PPP_),*/ ID($_DFF_N_), ID($_DFF_NN0_), ID($_DFF_NN1_), ID($_DFF_NP0_), ID($_DFF_NP1_), ID($_DFF_P_), ID($_DFF_PN0_), ID($_DFF_PN1_), ID($_DFF_PP0_), ID($_DFF_PP1_) }; @@ -113,8 +114,10 @@ struct ZinitPass : public Pass { for (int i = 0; i < GetSize(sig_q); i++) { if (initbits.count(sig_q[i])) { - initval.bits.push_back(initbits.at(sig_q[i])); - donebits.insert(sig_q[i]); + const auto &d = initbits.at(sig_q[i]); + initval.bits.push_back(d.first); + const auto &b = d.second; + b.wire->attributes.at(ID::init)[b.offset] = State::Sx; } else initval.bits.push_back(all_mode ? State::S0 : State::Sx); } @@ -123,11 +126,11 @@ struct ZinitPass : public Pass { initwire->attributes[ID::init] = initval; for (int i = 0; i < GetSize(initwire); i++) - if (initval.bits.at(i) == State::S1) + if (initval[i] == State::S1) { sig_d[i] = module->NotGate(NEW_ID, sig_d[i]); module->addNotGate(NEW_ID, SigSpec(initwire, i), sig_q[i]); - initwire->attributes[ID::init].bits.at(i) = State::S0; + initwire->attributes[ID::init][i] = State::S0; } else { @@ -139,11 +142,24 @@ struct ZinitPass : public Pass { cell->setPort(ID::D, sig_d); cell->setPort(ID::Q, initwire); - } - for (auto &it : initbits) - if (donebits.count(it.first) == 0) - log_error("Failed to handle init bit %s = %s.\n", log_signal(it.first), log_signal(it.second)); + if (cell->type == ID($adff)) { + auto val = cell->getParam(ID::ARST_VALUE); + for (int i = 0; i < GetSize(initwire); i++) + if (initval[i] == State::S1) + val[i] = (val[i] == State::S1 ? State::S0 : State::S1); + cell->setParam(ID::ARST_VALUE, std::move(val)); + } + else if (cell->type.in(ID($_DFF_NN0_), ID($_DFF_NN1_), ID($_DFF_NP0_), ID($_DFF_NP1_), + ID($_DFF_PN0_), ID($_DFF_PN1_), ID($_DFF_PP0_), ID($_DFF_PP1_))) + { + if (initval == State::S1) { + std::string t = cell->type.str(); + t[8] = (t[8] == '0' ? '1' : '0'); + cell->type = t; + } + } + } } } } ZinitPass; diff --git a/techlibs/ecp5/brams.txt b/techlibs/ecp5/brams.txt index 777ccaa2e..d34d9ec07 100644 --- a/techlibs/ecp5/brams.txt +++ b/techlibs/ecp5/brams.txt @@ -37,7 +37,17 @@ bram $__ECP5_DP16KD clkpol 2 3 endbram +# The syn_* attributes are described in: +# https://www.latticesemi.com/-/media/LatticeSemi/Documents/Tutorials/AK/LatticeDiamondTutorial311.ashx +attr_icase 1 + match $__ECP5_PDPW16KD + # implicitly requested RAM or ROM + attribute !syn_ramstyle syn_ramstyle=auto + attribute !syn_romstyle syn_romstyle=auto + attribute !ram_block + attribute !rom_block + attribute !logic_block min bits 2048 min efficiency 5 shuffle_enable A @@ -45,8 +55,60 @@ match $__ECP5_PDPW16KD or_next_if_better endmatch +match $__ECP5_PDPW16KD + # explicitly requested RAM + attribute syn_ramstyle=block_ram ram_block + attribute !syn_romstyle + attribute !rom_block + attribute !logic_block + min wports 1 + shuffle_enable A + make_transp + or_next_if_better +endmatch + +match $__ECP5_PDPW16KD + # explicitly requested ROM + attribute syn_romstyle=ebr rom_block + attribute !syn_ramstyle + attribute !ram_block + attribute !logic_block + max wports 0 + shuffle_enable A + make_transp + or_next_if_better +endmatch + match $__ECP5_DP16KD + # implicitly requested RAM or ROM + attribute !syn_ramstyle syn_ramstyle=auto + attribute !syn_romstyle syn_romstyle=auto + attribute !ram_block + attribute !rom_block + attribute !logic_block min bits 2048 min efficiency 5 shuffle_enable A + or_next_if_better +endmatch + +match $__ECP5_DP16KD + # explicitly requested RAM + attribute syn_ramstyle=block_ram ram_block + attribute !syn_romstyle + attribute !rom_block + attribute !logic_block + min wports 1 + shuffle_enable A + or_next_if_better +endmatch + +match $__ECP5_DP16KD + # explicitly requested ROM + attribute syn_romstyle=ebr rom_block + attribute !syn_ramstyle + attribute !ram_block + attribute !logic_block + max wports 0 + shuffle_enable A endmatch diff --git a/techlibs/ecp5/lutrams.txt b/techlibs/ecp5/lutrams.txt index b94357429..9e6a23eba 100644 --- a/techlibs/ecp5/lutrams.txt +++ b/techlibs/ecp5/lutrams.txt @@ -11,7 +11,16 @@ bram $__TRELLIS_DPR16X4 clkpol 0 2 endbram +# The syn_* attributes are described in: +# https://www.latticesemi.com/-/media/LatticeSemi/Documents/Tutorials/AK/LatticeDiamondTutorial311.ashx +attr_icase 1 + match $__TRELLIS_DPR16X4 + attribute !syn_ramstyle syn_ramstyle=auto syn_ramstyle=distributed + attribute !syn_romstyle syn_romstyle=auto + attribute !ram_block + attribute !rom_block + attribute !logic_block make_outreg min wports 1 endmatch diff --git a/techlibs/ecp5/synth_ecp5.cc b/techlibs/ecp5/synth_ecp5.cc index 9916fdafb..6f5790a14 100644 --- a/techlibs/ecp5/synth_ecp5.cc +++ b/techlibs/ecp5/synth_ecp5.cc @@ -279,7 +279,9 @@ struct SynthEcp5Pass : public ScriptPass if (check_label("map_ffram")) { run("opt -fast -mux_undef -undriven -fine"); - run("memory_map"); + run("memory_map -iattr -attr !ram_block -attr !rom_block -attr logic_block " + "-attr syn_ramstyle=auto -attr syn_ramstyle=registers " + "-attr syn_romstyle=auto -attr syn_romstyle=logic"); run("opt -undriven -fine"); } diff --git a/techlibs/ice40/brams.txt b/techlibs/ice40/brams.txt index 03d596111..36dfddab2 100644 --- a/techlibs/ice40/brams.txt +++ b/techlibs/ice40/brams.txt @@ -28,13 +28,73 @@ bram $__ICE40_RAM4K_M123 clkpol 2 3 endbram +# The syn_* attributes are described in: +# https://www.latticesemi.com/-/media/LatticeSemi/Documents/Tutorials/AK/LatticeDiamondTutorial311.ashx +attr_icase 1 + match $__ICE40_RAM4K_M0 + # implicitly requested RAM or ROM + attribute !syn_ramstyle syn_ramstyle=auto + attribute !syn_romstyle syn_romstyle=auto + attribute !ram_block + attribute !rom_block + attribute !logic_block min efficiency 2 make_transp or_next_if_better endmatch +match $__ICE40_RAM4K_M0 + # explicitly requested RAM + attribute syn_ramstyle=block_ram ram_block + attribute !syn_romstyle + attribute !rom_block + attribute !logic_block + min wports 1 + make_transp + or_next_if_better +endmatch + +match $__ICE40_RAM4K_M0 + # explicitly requested ROM + attribute syn_romstyle=ebr rom_block + attribute !syn_ramstyle + attribute !ram_block + attribute !logic_block + max wports 0 + make_transp + or_next_if_better +endmatch + match $__ICE40_RAM4K_M123 + # implicitly requested RAM or ROM + attribute !syn_ramstyle syn_ramstyle=auto + attribute !syn_romstyle syn_romstyle=auto + attribute !ram_block + attribute !rom_block + attribute !logic_block min efficiency 2 make_transp + or_next_if_better +endmatch + +match $__ICE40_RAM4K_M123 + # explicitly requested RAM + attribute syn_ramstyle=block_ram ram_block + attribute !syn_romstyle + attribute !rom_block + attribute !logic_block + min wports 1 + make_transp + or_next_if_better +endmatch + +match $__ICE40_RAM4K_M123 + # explicitly requested ROM + attribute syn_romstyle=ebr rom_block + attribute !syn_ramstyle + attribute !ram_block + attribute !logic_block + max wports 0 + make_transp endmatch diff --git a/techlibs/ice40/synth_ice40.cc b/techlibs/ice40/synth_ice40.cc index 59ada8bae..463e80ee2 100644 --- a/techlibs/ice40/synth_ice40.cc +++ b/techlibs/ice40/synth_ice40.cc @@ -319,7 +319,9 @@ struct SynthIce40Pass : public ScriptPass if (check_label("map_ffram")) { run("opt -fast -mux_undef -undriven -fine"); - run("memory_map"); + run("memory_map -iattr -attr !ram_block -attr !rom_block -attr logic_block " + "-attr syn_ramstyle=auto -attr syn_ramstyle=registers " + "-attr syn_romstyle=auto -attr syn_romstyle=logic"); run("opt -undriven -fine"); } diff --git a/tests/arch/common/blockram.v b/tests/arch/common/blockram.v index dbc6ca65c..5ed0736d0 100644 --- a/tests/arch/common/blockram.v +++ b/tests/arch/common/blockram.v @@ -5,19 +5,20 @@ module sync_ram_sp #(parameter DATA_WIDTH=8, ADDRESS_WIDTH=10) input wire [ADDRESS_WIDTH-1:0] address_in, output wire [DATA_WIDTH-1:0] data_out); - localparam WORD = (DATA_WIDTH-1); - localparam DEPTH = (2**ADDRESS_WIDTH-1); + localparam WORD = (DATA_WIDTH-1); + localparam DEPTH = (2**ADDRESS_WIDTH-1); - reg [WORD:0] data_out_r; - reg [WORD:0] memory [0:DEPTH]; + reg [WORD:0] data_out_r; + reg [WORD:0] memory [0:DEPTH]; - always @(posedge clk) begin - if (write_enable) - memory[address_in] <= data_in; - data_out_r <= memory[address_in]; - end + always @(posedge clk) begin + if (write_enable) + memory[address_in] <= data_in; + data_out_r <= memory[address_in]; + end + + assign data_out = data_out_r; - assign data_out = data_out_r; endmodule // sync_ram_sp @@ -28,18 +29,19 @@ module sync_ram_sdp #(parameter DATA_WIDTH=8, ADDRESS_WIDTH=10) input wire [ADDRESS_WIDTH-1:0] address_in_r, address_in_w, output wire [DATA_WIDTH-1:0] data_out); - localparam WORD = (DATA_WIDTH-1); - localparam DEPTH = (2**ADDRESS_WIDTH-1); + localparam WORD = (DATA_WIDTH-1); + localparam DEPTH = (2**ADDRESS_WIDTH-1); + + reg [WORD:0] data_out_r; + reg [WORD:0] memory [0:DEPTH]; - reg [WORD:0] data_out_r; - reg [WORD:0] memory [0:DEPTH]; + always @(posedge clk) begin + if (write_enable) + memory[address_in_w] <= data_in; + data_out_r <= memory[address_in_r]; + end - always @(posedge clk) begin - if (write_enable) - memory[address_in_w] <= data_in; - data_out_r <= memory[address_in_r]; - end + assign data_out = data_out_r; - assign data_out = data_out_r; endmodule // sync_ram_sdp diff --git a/tests/arch/common/blockrom.v b/tests/arch/common/blockrom.v new file mode 100644 index 000000000..93f5c9ddf --- /dev/null +++ b/tests/arch/common/blockrom.v @@ -0,0 +1,31 @@ +`default_nettype none +module sync_rom #(parameter DATA_WIDTH=8, ADDRESS_WIDTH=10) + (input wire clk, + input wire [ADDRESS_WIDTH-1:0] address_in, + output wire [DATA_WIDTH-1:0] data_out); + + localparam WORD = (DATA_WIDTH-1); + localparam DEPTH = (2**ADDRESS_WIDTH-1); + + reg [WORD:0] data_out_r; + reg [WORD:0] memory [0:DEPTH]; + + integer i,j = 64'hF4B1CA8127865242; + initial + for (i = 0; i <= DEPTH; i++) begin + // In case this ROM will be implemented in fabric: fill the memory with some data + // uncorrelated with the address, or Yosys might see through the ruse and e.g. not + // emit any LUTs at all for `memory[i] = i;`, just a latch. + memory[i] = j * 64'h2545F4914F6CDD1D; + j = j ^ (j >> 12); + j = j ^ (j << 25); + j = j ^ (j >> 27); + end + + always @(posedge clk) begin + data_out_r <= memory[address_in]; + end + + assign data_out = data_out_r; + +endmodule // sync_rom diff --git a/tests/arch/ecp5/memories.ys b/tests/arch/ecp5/memories.ys new file mode 100644 index 000000000..e1f748e26 --- /dev/null +++ b/tests/arch/ecp5/memories.ys @@ -0,0 +1,330 @@ +# ================================ RAM ================================ +# RAM bits <= 18K; Data width <= 36; Address width <= 9: -> PDPW16KD + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 9 -set DATA_WIDTH 36 sync_ram_sdp +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:PDPW16KD + +## With parameters + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 36 sync_ram_sdp +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 0 t:PDPW16KD # too inefficient +select -assert-count 9 t:TRELLIS_DPR16X4 + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 36 sync_ram_sdp +setattr -set syn_ramstyle "block_ram" m:memory +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:PDPW16KD + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 36 sync_ram_sdp +setattr -set syn_ramstyle "Block_RAM" m:memory +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:PDPW16KD # any case works + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 36 sync_ram_sdp +setattr -set ram_block 1 m:memory +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:PDPW16KD + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 36 sync_ram_sdp +setattr -set syn_ramstyle "registers" m:memory +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 0 t:PDPW16KD # requested FFRAM explicitly +select -assert-count 180 t:TRELLIS_FF + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 36 sync_ram_sdp +setattr -set logic_block 1 m:memory +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 0 t:PDPW16KD # requested FFRAM explicitly +select -assert-count 180 t:TRELLIS_FF + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 36 sync_ram_sdp +setattr -set syn_romstyle "ebr" m:memory +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:$mem # requested BROM but this is a RAM + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 36 sync_ram_sdp +setattr -set rom_block 1 m:memory +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:$mem # requested BROM but this is a RAM + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 36 sync_ram_sdp +setattr -set syn_ramstyle "block_ram" m:memory +synth_ecp5 -top sync_ram_sdp -nobram; cd sync_ram_sdp +select -assert-count 1 t:$mem # requested BRAM but BRAM is disabled + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 36 sync_ram_sdp +setattr -set ram_block 1 m:memory +synth_ecp5 -top sync_ram_sdp -nobram; cd sync_ram_sdp +select -assert-count 1 t:$mem # requested BRAM but BRAM is disabled + +# RAM bits <= 18K; Data width <= 18; Address width <= 10: -> DP16KD + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 10 -set DATA_WIDTH 18 sync_ram_sdp +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:DP16KD + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 11 -set DATA_WIDTH 9 sync_ram_sdp +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:DP16KD + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 12 -set DATA_WIDTH 4 sync_ram_sdp +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:DP16KD + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 13 -set DATA_WIDTH 2 sync_ram_sdp +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:DP16KD + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 14 -set DATA_WIDTH 1 sync_ram_sdp +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:DP16KD + +## With parameters + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 18 sync_ram_sdp +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 0 t:DP16KD # too inefficient +select -assert-count 5 t:TRELLIS_DPR16X4 + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 18 sync_ram_sdp +setattr -set syn_ramstyle "block_ram" m:memory +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:DP16KD + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 18 sync_ram_sdp +setattr -set syn_ramstyle "Block_RAM" m:memory +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:DP16KD # any case works + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 18 sync_ram_sdp +setattr -set ram_block 1 m:memory +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:DP16KD + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 18 sync_ram_sdp +setattr -set syn_ramstyle "registers" m:memory +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 0 t:DP16KD # requested FFRAM explicitly +select -assert-count 90 t:TRELLIS_FF + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 18 sync_ram_sdp +setattr -set logic_block 1 m:memory +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 0 t:DP16KD # requested FFRAM explicitly +select -assert-count 90 t:TRELLIS_FF + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 18 sync_ram_sdp +setattr -set syn_romstyle "ebr" m:memory +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:$mem # requested BROM but this is a RAM + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 18 sync_ram_sdp +setattr -set rom_block 1 m:memory +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:$mem # requested BROM but this is a RAM + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 18 sync_ram_sdp +setattr -set syn_ramstyle "block_ram" m:memory +synth_ecp5 -top sync_ram_sdp -nobram; cd sync_ram_sdp +select -assert-count 1 t:$mem # requested BRAM but BRAM is disabled + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 18 sync_ram_sdp +setattr -set ram_block 1 m:memory +synth_ecp5 -top sync_ram_sdp -nobram; cd sync_ram_sdp +select -assert-count 1 t:$mem # requested BRAM but BRAM is disabled + +# RAM bits <= 64; Data width <= 4; Address width <= 4: -> DPR16X4 + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 4 -set DATA_WIDTH 4 sync_ram_sdp +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:TRELLIS_DPR16X4 + +## With parameters + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 4 -set DATA_WIDTH 4 sync_ram_sdp +setattr -set syn_ramstyle "distributed" m:memory +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:TRELLIS_DPR16X4 + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 4 -set DATA_WIDTH 4 sync_ram_sdp +setattr -set syn_ramstyle "registers" m:memory +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 0 t:TRELLIS_DPR16X4 # requested FFRAM explicitly +select -assert-count 68 t:TRELLIS_FF + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 4 -set DATA_WIDTH 4 sync_ram_sdp +setattr -set logic_block 1 m:memory +synth_ecp5 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 0 t:TRELLIS_DPR16X4 # requested FFRAM explicitly +select -assert-count 68 t:TRELLIS_FF + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 4 -set DATA_WIDTH 4 sync_ram_sdp +setattr -set syn_ramstyle "distributed" m:memory +synth_ecp5 -top sync_ram_sdp -nolutram; cd sync_ram_sdp +select -assert-count 1 t:$mem # requested LUTRAM but LUTRAM is disabled + +# ================================ ROM ================================ +# ROM bits <= 18K; Data width <= 36; Address width <= 9: -> PDPW16KD + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 9 -set DATA_WIDTH 36 sync_rom +synth_ecp5 -top sync_rom; cd sync_rom +select -assert-count 1 t:PDPW16KD + +## With parameters + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 3 -set DATA_WIDTH 36 sync_rom +write_ilang +synth_ecp5 -top sync_rom; cd sync_rom +select -assert-count 0 t:PDPW16KD # too inefficient +select -assert-min 18 t:LUT4 + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 36 sync_rom +setattr -set syn_romstyle "ebr" m:memory +synth_ecp5 -top sync_rom; cd sync_rom +select -assert-count 1 t:PDPW16KD + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 36 sync_rom +setattr -set rom_block 1 m:memory +synth_ecp5 -top sync_rom; cd sync_rom +select -assert-count 1 t:PDPW16KD + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 3 -set DATA_WIDTH 36 sync_rom +setattr -set syn_romstyle "logic" m:memory +synth_ecp5 -top sync_rom; cd sync_rom +select -assert-count 0 t:PDPW16KD # requested LUTROM explicitly +select -assert-min 18 t:LUT4 + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 3 -set DATA_WIDTH 36 sync_rom +setattr -set logic_block 1 m:memory +synth_ecp5 -top sync_rom; cd sync_rom +select -assert-count 0 t:PDPW16KD # requested LUTROM explicitly +select -assert-min 18 t:LUT4 + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 36 sync_rom +setattr -set syn_ramstyle "block_ram" m:memory +synth_ecp5 -top sync_rom; cd sync_rom +select -assert-count 1 t:$mem # requested BRAM but this is a ROM + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 36 sync_rom +setattr -set ram_block 1 m:memory +synth_ecp5 -top sync_rom; cd sync_rom +select -assert-count 1 t:$mem # requested BRAM but this is a ROM + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 36 sync_rom +setattr -set syn_ramstyle "block_rom" m:memory +synth_ecp5 -top sync_rom -nobram; cd sync_rom +select -assert-count 1 t:$mem # requested BROM but BRAM is disabled + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 36 sync_rom +setattr -set rom_block 1 m:memory +synth_ecp5 -top sync_rom -nobram; cd sync_rom +select -assert-count 1 t:$mem # requested BROM but BRAM is disabled + +# ROM bits <= 18K; Data width <= 18; Address width <= 10: -> DP16KD + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 10 -set DATA_WIDTH 18 sync_rom +synth_ecp5 -top sync_rom; cd sync_rom +select -assert-count 1 t:DP16KD + +## With parameters + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 3 -set DATA_WIDTH 18 sync_rom +write_ilang +synth_ecp5 -top sync_rom; cd sync_rom +select -assert-count 0 t:DP16KD # too inefficient +select -assert-min 9 t:LUT4 + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 18 sync_rom +setattr -set syn_romstyle "ebr" m:memory +synth_ecp5 -top sync_rom; cd sync_rom +select -assert-count 1 t:DP16KD + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 18 sync_rom +setattr -set rom_block 1 m:memory +synth_ecp5 -top sync_rom; cd sync_rom +select -assert-count 1 t:DP16KD + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 3 -set DATA_WIDTH 18 sync_rom +setattr -set syn_romstyle "logic" m:memory +synth_ecp5 -top sync_rom; cd sync_rom +select -assert-count 0 t:DP16KD # requested LUTROM explicitly +select -assert-min 9 t:LUT4 + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 3 -set DATA_WIDTH 18 sync_rom +setattr -set logic_block 1 m:memory +synth_ecp5 -top sync_rom; cd sync_rom +select -assert-count 0 t:DP16KD # requested LUTROM explicitly +select -assert-min 9 t:LUT4 + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 18 sync_rom +setattr -set syn_ramstyle "block_ram" m:memory +synth_ecp5 -top sync_rom; cd sync_rom +select -assert-count 1 t:$mem # requested BRAM but this is a ROM + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 18 sync_rom +setattr -set ram_block 1 m:memory +synth_ecp5 -top sync_rom; cd sync_rom +select -assert-count 1 t:$mem # requested BRAM but this is a ROM + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 18 sync_rom +setattr -set syn_ramstyle "block_rom" m:memory +synth_ecp5 -top sync_rom -nobram; cd sync_rom +select -assert-count 1 t:$mem # requested BROM but BRAM is disabled + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 18 sync_rom +setattr -set rom_block 1 m:memory +synth_ecp5 -top sync_rom -nobram; cd sync_rom +select -assert-count 1 t:$mem # requested BROM but BRAM is disabled diff --git a/tests/arch/ice40/lutram.ys b/tests/arch/ice40/lutram.ys deleted file mode 100644 index 1ba40f8ec..000000000 --- a/tests/arch/ice40/lutram.ys +++ /dev/null @@ -1,15 +0,0 @@ -read_verilog ../common/lutram.v -hierarchy -top lutram_1w1r -proc -memory -nomap -equiv_opt -run :prove -map +/ice40/cells_sim.v synth_ice40 -memory -opt -full - -miter -equiv -flatten -make_assert -make_outputs gold gate miter -sat -verify -prove-asserts -seq 5 -set-init-zero -show-inputs -show-outputs miter - -design -load postopt -cd lutram_1w1r -select -assert-count 1 t:SB_RAM40_4K -select -assert-none t:SB_RAM40_4K %% t:* %D diff --git a/tests/arch/ice40/memories.ys b/tests/arch/ice40/memories.ys new file mode 100644 index 000000000..571edec1d --- /dev/null +++ b/tests/arch/ice40/memories.ys @@ -0,0 +1,168 @@ +# ================================ RAM ================================ +# RAM bits <= 4K; Data width <= 16; Address width <= 11: -> SB_RAM40_4K + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 11 -set DATA_WIDTH 2 sync_ram_sdp +synth_ice40 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:SB_RAM40_4K + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 10 -set DATA_WIDTH 4 sync_ram_sdp +synth_ice40 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:SB_RAM40_4K + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 9 -set DATA_WIDTH 8 sync_ram_sdp +synth_ice40 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:SB_RAM40_4K + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 8 -set DATA_WIDTH 16 sync_ram_sdp +synth_ice40 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:SB_RAM40_4K + +## With parameters + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 8 sync_ram_sdp +synth_ice40 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 0 t:SB_RAM40_4K # too inefficient +select -assert-min 1 t:SB_DFFE + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 8 sync_ram_sdp +setattr -set syn_ramstyle "block_ram" m:memory +synth_ice40 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:SB_RAM40_4K + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 8 sync_ram_sdp +setattr -set syn_ramstyle "Block_RAM" m:memory +synth_ice40 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:SB_RAM40_4K # any case works + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 8 sync_ram_sdp +setattr -set ram_block 1 m:memory +synth_ice40 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:SB_RAM40_4K + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 8 sync_ram_sdp +setattr -set syn_ramstyle "registers" m:memory +synth_ice40 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 0 t:SB_RAM40_4K # requested FFRAM explicitly +select -assert-min 1 t:SB_DFFE + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 8 sync_ram_sdp +setattr -set logic_block 1 m:memory +synth_ice40 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 0 t:SB_RAM40_4K # requested FFRAM explicitly +select -assert-min 1 t:SB_DFFE + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 8 sync_ram_sdp +setattr -set syn_romstyle "ebr" m:memory +synth_ice40 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:$mem # requested BROM but this is a RAM + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 8 sync_ram_sdp +setattr -set rom_block 1 m:memory +synth_ice40 -top sync_ram_sdp; cd sync_ram_sdp +select -assert-count 1 t:$mem # requested BROM but this is a RAM + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 8 sync_ram_sdp +setattr -set syn_ramstyle "block_ram" m:memory +synth_ice40 -top sync_ram_sdp -nobram; cd sync_ram_sdp +select -assert-count 1 t:$mem # requested BRAM but BRAM is disabled + +design -reset; read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 8 sync_ram_sdp +setattr -set ram_block 1 m:memory +synth_ice40 -top sync_ram_sdp -nobram; cd sync_ram_sdp +select -assert-count 1 t:$mem # requested BRAM but BRAM is disabled + +# ================================ ROM ================================ +# ROM bits <= 4K; Data width <= 16; Address width <= 11: -> SB_RAM40_4K + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 11 -set DATA_WIDTH 2 sync_rom +synth_ice40 -top sync_rom; cd sync_rom +select -assert-count 1 t:SB_RAM40_4K + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 10 -set DATA_WIDTH 4 sync_rom +synth_ice40 -top sync_rom; cd sync_rom +select -assert-count 1 t:SB_RAM40_4K + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 9 -set DATA_WIDTH 8 sync_rom +synth_ice40 -top sync_rom; cd sync_rom +select -assert-count 1 t:SB_RAM40_4K + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 8 -set DATA_WIDTH 16 sync_rom +synth_ice40 -top sync_rom; cd sync_rom +select -assert-count 1 t:SB_RAM40_4K + +## With parameters + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 8 sync_rom +write_ilang +synth_ice40 -top sync_rom; cd sync_rom +select -assert-count 0 t:SB_RAM40_4K # too inefficient +select -assert-min 1 t:SB_LUT4 + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 8 sync_rom +setattr -set syn_romstyle "ebr" m:memory +synth_ice40 -top sync_rom; cd sync_rom +select -assert-count 1 t:SB_RAM40_4K + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 8 sync_rom +setattr -set rom_block 1 m:memory +synth_ice40 -top sync_rom; cd sync_rom +select -assert-count 1 t:SB_RAM40_4K + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 8 sync_rom +setattr -set syn_romstyle "logic" m:memory +synth_ice40 -top sync_rom; cd sync_rom +select -assert-count 0 t:SB_RAM40_4K # requested LUTROM explicitly +select -assert-min 1 t:SB_LUT4 + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 8 sync_rom +setattr -set logic_block 1 m:memory +synth_ice40 -top sync_rom; cd sync_rom +select -assert-count 0 t:SB_RAM40_4K # requested LUTROM explicitly +select -assert-min 1 t:SB_LUT4 + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 8 sync_rom +setattr -set syn_ramstyle "block_ram" m:memory +synth_ice40 -top sync_rom; cd sync_rom +select -assert-count 1 t:$mem # requested BRAM but this is a ROM + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 8 sync_rom +setattr -set ram_block 1 m:memory +synth_ice40 -top sync_rom; cd sync_rom +select -assert-count 1 t:$mem # requested BRAM but this is a ROM + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 8 sync_rom +setattr -set syn_romstyle "ebr" m:memory +synth_ice40 -top sync_rom -nobram; cd sync_rom +select -assert-count 1 t:$mem # requested BROM but BRAM is disabled + +design -reset; read_verilog ../common/blockrom.v +chparam -set ADDRESS_WIDTH 2 -set DATA_WIDTH 8 sync_rom +setattr -set rom_block 1 m:memory +synth_ice40 -top sync_rom -nobram; cd sync_rom +select -assert-count 1 t:$mem # requested BROM but BRAM is disabled diff --git a/tests/opt/opt_expr.ys b/tests/opt/opt_expr.ys index e0acead82..7c446afd1 100644 --- a/tests/opt/opt_expr.ys +++ b/tests/opt/opt_expr.ys @@ -291,3 +291,31 @@ check equiv_opt -assert opt_expr -keepdc design -load postopt select -assert-count 1 t:$shift r:A_WIDTH=13 %i + +########### + +design -reset +read_verilog -icells <<EOT +module opt_expr_mul_low_bits(input [2:0] a, input [2:0] b, output [7:0] y); + \$mul #(.A_SIGNED(0), .B_SIGNED(0), .A_WIDTH(4), .B_WIDTH(4), .Y_WIDTH(8)) mul (.A({a, 1'b0}), .B({b, 1'b0}), .Y(y)); +endmodule +EOT +check + +equiv_opt -assert opt_expr +design -load postopt +select -assert-count 1 t:$mul r:A_WIDTH=3 %i r:B_WIDTH=3 %i r:Y_WIDTH=6 %i + +########### + +design -reset +read_verilog -icells <<EOT +module opt_expr_mul_low_bits(input [2:0] a, input [2:0] b, output [7:0] y); + \$mul #(.A_SIGNED(0), .B_SIGNED(0), .A_WIDTH(4), .B_WIDTH(4), .Y_WIDTH(8)) mul (.A({a, 1'b0}), .B({b, 1'b0}), .Y(y)); +endmodule +EOT +check + +equiv_opt -assert opt_expr -keepdc +design -load postopt +select -assert-count 1 t:$mul r:A_WIDTH=4 %i r:B_WIDTH=4 %i r:Y_WIDTH=8 %i diff --git a/tests/select/.gitignore b/tests/select/.gitignore new file mode 100644 index 000000000..50e13221d --- /dev/null +++ b/tests/select/.gitignore @@ -0,0 +1 @@ +/*.log diff --git a/tests/techmap/zinit.ys b/tests/techmap/zinit.ys new file mode 100644 index 000000000..18b17621f --- /dev/null +++ b/tests/techmap/zinit.ys @@ -0,0 +1,57 @@ +read_verilog -icells <<EOT +module top(input C, R, input [1:0] D, (* init = {2'b10, 2'b01, 1'b1, {8{1'b1}}} *) output [12:0] Q); + +(* init = 1'b1 *) +wire unused; + +$_DFF_NN0_ dff0 (.C(C), .D(D[0]), .R(R), .Q(Q[0])); +$_DFF_NN1_ dff1 (.C(C), .D(D[0]), .R(R), .Q(Q[1])); +$_DFF_NP0_ dff2 (.C(C), .D(D[0]), .R(R), .Q(Q[2])); +$_DFF_NP1_ dff3 (.C(C), .D(D[0]), .R(R), .Q(Q[3])); +$_DFF_PN0_ dff4 (.C(C), .D(D[0]), .R(R), .Q(Q[4])); +$_DFF_PN1_ dff5 (.C(C), .D(D[0]), .R(R), .Q(Q[5])); +$_DFF_PP0_ dff6 (.C(C), .D(D[0]), .R(R), .Q(Q[6])); +$_DFF_PP1_ dff7 (.C(C), .D(D[0]), .R(R), .Q(Q[7])); + +$adff #(.WIDTH(2), .CLK_POLARITY(1), .ARST_POLARITY(1'b0), .ARST_VALUE(2'b10)) dff8 (.CLK(C), .ARST(R), .D(D), .Q(Q[10:9])); +$adff #(.WIDTH(2), .CLK_POLARITY(0), .ARST_POLARITY(1'b1), .ARST_VALUE(2'b01)) dff9 (.CLK(C), .ARST(R), .D(D), .Q(Q[12:11])); +endmodule +EOT +equiv_opt -assert -map +/simcells.v -multiclock zinit +design -load postopt + +select -assert-count 20 t:$_NOT_ +select -assert-count 1 w:unused a:init %i +select -assert-count 1 w:Q a:init=13'bxxxx1xxxxxxxx %i +select -assert-count 4 c:dff0 c:dff2 c:dff4 c:dff6 %% t:$_DFF_??1_ %i +select -assert-count 4 c:dff1 c:dff3 c:dff5 c:dff7 %% t:$_DFF_??0_ %i + + +design -reset +read_verilog -icells <<EOT +module top(input C, R, input [1:0] D, (* init = {2'bx0, 2'b0x, 1'b1, {8{1'b0}}} *) output [12:0] Q); + +(* init = 1'b1 *) +wire unused; + +$_DFF_NN0_ dff0 (.C(C), .D(D[0]), .R(R), .Q(Q[0])); +$_DFF_NN1_ dff1 (.C(C), .D(D[0]), .R(R), .Q(Q[1])); +$_DFF_NP0_ dff2 (.C(C), .D(D[0]), .R(R), .Q(Q[2])); +$_DFF_NP1_ dff3 (.C(C), .D(D[0]), .R(R), .Q(Q[3])); +$_DFF_PN0_ dff4 (.C(C), .D(D[0]), .R(R), .Q(Q[4])); +$_DFF_PN1_ dff5 (.C(C), .D(D[0]), .R(R), .Q(Q[5])); +$_DFF_PP0_ dff6 (.C(C), .D(D[0]), .R(R), .Q(Q[6])); +$_DFF_PP1_ dff7 (.C(C), .D(D[0]), .R(R), .Q(Q[7])); + +$adff #(.WIDTH(2), .CLK_POLARITY(1), .ARST_POLARITY(1'b0), .ARST_VALUE(2'b10)) dff8 (.CLK(C), .ARST(R), .D(D), .Q(Q[10:9])); +$adff #(.WIDTH(2), .CLK_POLARITY(0), .ARST_POLARITY(1'b1), .ARST_VALUE(2'b01)) dff9 (.CLK(C), .ARST(R), .D(D), .Q(Q[12:11])); +endmodule +EOT +equiv_opt -assert -map +/simcells.v -multiclock zinit +design -load postopt + +select -assert-count 0 t:$_NOT_ +select -assert-count 1 w:unused a:init %i +select -assert-count 1 w:Q a:init=13'bxxxx1xxxxxxxx %i +select -assert-count 4 c:dff0 c:dff2 c:dff4 c:dff6 %% t:$_DFF_??0_ %i +select -assert-count 4 c:dff1 c:dff3 c:dff5 c:dff7 %% t:$_DFF_??1_ %i diff --git a/tests/various/.gitignore b/tests/various/.gitignore index 4b286fd61..12d4e5048 100644 --- a/tests/various/.gitignore +++ b/tests/various/.gitignore @@ -3,3 +3,4 @@ /write_gzip.v /write_gzip.v.gz /run-test.mk +/plugin.so diff --git a/tests/various/plugin.cc b/tests/various/plugin.cc new file mode 100644 index 000000000..be305fbda --- /dev/null +++ b/tests/various/plugin.cc @@ -0,0 +1,15 @@ +#include "kernel/rtlil.h" + +YOSYS_NAMESPACE_BEGIN + +struct TestPass : public Pass { + TestPass() : Pass("test", "test") { } + void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE + { + size_t argidx = 1; + extra_args(args, argidx, design); + log("Plugin test passed!\n"); + } +} TestPass; + +YOSYS_NAMESPACE_END diff --git a/tests/various/plugin.sh b/tests/various/plugin.sh new file mode 100644 index 000000000..d6d4aee59 --- /dev/null +++ b/tests/various/plugin.sh @@ -0,0 +1,6 @@ +set -e +rm -f plugin.so +CXXFLAGS=$(../../yosys-config --cxxflags) +CXXFLAGS=${CXXFLAGS// -I\/usr\/local\/share\/yosys\/include/ -I..\/..\/share\/include} +../../yosys-config --exec --cxx ${CXXFLAGS} --ldflags -shared -o plugin.so plugin.cc +../../yosys -m ./plugin.so -p "test" | grep -q "Plugin test passed!" |