diff --git a/CHANGELOG b/CHANGELOG index 413a3236c..bdf30260e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,9 +2,29 @@ List of major changes and improvements between releases ======================================================= -Yosys 0.52 .. Yosys 0.53-dev +Yosys 0.53 .. Yosys 0.54-dev -------------------------- +Yosys 0.52 .. Yosys 0.53 +-------------------------- + * New commands and options + - Added "constmap" pass for technology mapping of coarse constant value. + - Added "timeest" pass to estimate the critical path in clock domain. + - Added "-blackbox" option to "cutpoint" pass to cut all instances of + blackboxes. + - Added "-noscopeinfo" option to "cutpoint" pass. + - Added "-nocleanup" option to "flatten" pass to prevent removal of + unused submodules. + - Added "-declockgate" option to "formalff" pass that turns clock + gating into clock enables. + + * Various + - Added "$scopeinfo" cells to preserve information during "cutpoint" pass. + - Added dataflow tracking documentation. + - share: Restrict activation patterns to potentially relevant signal. + - liberty: More robust parsing. + - verific: bit blast RAM if using mem2reg attribute. + Yosys 0.51 .. Yosys 0.52 -------------------------- * New commands and options diff --git a/Makefile b/Makefile index 76ac4bb18..5986b1091 100644 --- a/Makefile +++ b/Makefile @@ -160,7 +160,7 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.52+63 +YOSYS_VER := 0.53+3 YOSYS_MAJOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f1) YOSYS_MINOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f2 | cut -d'+' -f1) YOSYS_COMMIT := $(shell echo $(YOSYS_VER) | cut -d'+' -f2) @@ -183,7 +183,7 @@ endif OBJS = kernel/version_$(GIT_REV).o bumpversion: - sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline fee39a3.. | wc -l`/;" Makefile + sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline 53c22ab.. | wc -l`/;" Makefile ABCMKARGS = CC="$(CXX)" CXX="$(CXX)" ABC_USE_LIBSTDCXX=1 ABC_USE_NAMESPACE=abc VERBOSE=$(Q) @@ -396,6 +396,10 @@ ifeq ($(DISABLE_ABC_THREADS),1) ABCMKARGS += "ABC_USE_NO_PTHREADS=1" endif +ifeq ($(LINK_ABC),1) +ABCMKARGS += "ABC_USE_PIC=1" +endif + ifeq ($(DISABLE_SPAWN),1) CXXFLAGS += -DYOSYS_DISABLE_SPAWN endif @@ -787,7 +791,7 @@ $(PROGRAM_PREFIX)yosys-config: misc/yosys-config.in $(YOSYS_SRC)/Makefile .PHONY: check-git-abc check-git-abc: - @if [ ! -d "$(YOSYS_SRC)/abc" ]; then \ + @if [ ! -d "$(YOSYS_SRC)/abc" ] && git -C "$(YOSYS_SRC)" status >/dev/null 2>&1; then \ echo "Error: The 'abc' directory does not exist."; \ echo "Initialize the submodule: Run 'git submodule update --init' to set up 'abc' as a submodule."; \ exit 1; \ @@ -813,6 +817,12 @@ check-git-abc: echo "3. Initialize the submodule: Run 'git submodule update --init' to set up 'abc' as a submodule."; \ echo "4. Reapply your changes: Move your saved changes back to the 'abc' directory, if necessary."; \ exit 1; \ + elif ! git -C "$(YOSYS_SRC)" status >/dev/null 2>&1; then \ + echo "$(realpath $(YOSYS_SRC)) is not configured as a git repository, and 'abc' folder is missing."; \ + echo "If you already have ABC, set 'ABCEXTERNAL' make variable to point to ABC executable."; \ + echo "Otherwise, download release archive 'yosys.tar.gz' from https://github.com/YosysHQ/yosys/releases."; \ + echo " ('Source code' archive does not contain submodules.)"; \ + exit 1; \ else \ echo "Initialize the submodule: Run 'git submodule update --init' to set up 'abc' as a submodule."; \ exit 1; \ diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index 052699ad6..819b2c1df 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -2410,7 +2410,12 @@ struct CxxrtlWorker { auto cell_attrs = scopeinfo_attributes(cell, ScopeinfoAttrs::Cell); cell_attrs.erase(ID::module_not_derived); f << indent << "scopes->add(path, " << escape_cxx_string(get_hdl_name(cell)) << ", "; - f << escape_cxx_string(cell->get_string_attribute(ID(module))) << ", "; + if (module_attrs.count(ID(hdlname))) { + f << escape_cxx_string(module_attrs.at(ID(hdlname)).decode_string()); + } else { + f << escape_cxx_string(cell->get_string_attribute(ID(module))); + } + f << ", "; dump_serialized_metadata(module_attrs); f << ", "; dump_serialized_metadata(cell_attrs); diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index 37c84895f..9b4f5774f 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -1769,7 +1769,7 @@ value shr_uu(const value &a, const value &b) { template CXXRTL_ALWAYS_INLINE value shr_su(const value &a, const value &b) { - return a.shr(b).template scast(); + return a.template scast().shr(b); } template @@ -2010,7 +2010,7 @@ std::pair, value> divmod_uu(const value &a, const val value quotient; value remainder; value dividend = a.template zext(); - value divisor = b.template zext(); + value divisor = b.template trunc().template zext(); std::tie(quotient, remainder) = dividend.udivmod(divisor); return {quotient.template trunc(), remainder.template trunc()}; } diff --git a/docs/source/conf.py b/docs/source/conf.py index 74ebe16e0..bfcb28730 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -6,7 +6,7 @@ import os project = 'YosysHQ Yosys' author = 'YosysHQ GmbH' copyright ='2025 YosysHQ GmbH' -yosys_ver = "0.52" +yosys_ver = "0.53" # select HTML theme html_theme = 'furo-ys' diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc index d35756d4e..3411d6c03 100644 --- a/frontends/ast/simplify.cc +++ b/frontends/ast/simplify.cc @@ -1919,6 +1919,8 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin if (!str.empty() && str[0] == '\\' && (template_node->type == AST_STRUCT || template_node->type == AST_UNION)) { // replace instance with wire representing the packed structure newNode = make_packed_struct(template_node, str, attributes); + if (newNode->attributes.count(ID::wiretype)) + delete newNode->attributes[ID::wiretype]; newNode->set_attribute(ID::wiretype, mkconst_str(resolved_type_node->str)); // add original input/output attribute to resolved wire newNode->is_input = this->is_input; diff --git a/frontends/verific/verific.cc b/frontends/verific/verific.cc index 3418ebe50..95bede420 100644 --- a/frontends/verific/verific.cc +++ b/frontends/verific/verific.cc @@ -1446,6 +1446,25 @@ void VerificImporter::import_netlist(RTLIL::Design *design, Netlist *nl, std::ma module_name = "\\" + sha1_if_contain_spaces(module_name); } + { + Array ram_nets ; + MapIter mem_mi; + Net *mem_net; + FOREACH_NET_OF_NETLIST(nl, mem_mi, mem_net) + { + if (!mem_net->IsRamNet()) continue ; + + if (mem_net->GetAtt("mem2reg")) + ram_nets.Insert(mem_net) ; + } + unsigned i ; + FOREACH_ARRAY_ITEM(&ram_nets, i, mem_net) { + log("Bit blasting RAM for identifier '%s'\n", mem_net->Name()); + mem_net->BlastNet(); + } + nl->RemoveDanglingLogic(0); + } + netlist = nl; if (design->has(module_name)) { diff --git a/frontends/verilog/verilog_parser.y b/frontends/verilog/verilog_parser.y index fe86626b8..9d0956c8e 100644 --- a/frontends/verilog/verilog_parser.y +++ b/frontends/verilog/verilog_parser.y @@ -2249,7 +2249,8 @@ cell_parameter: node->children.push_back($1); } | '.' TOK_ID '(' ')' { - // just ignore empty parameters + // delete unused TOK_ID + delete $2; } | '.' TOK_ID '(' expr ')' { AstNode *node = new AstNode(AST_PARASET); diff --git a/kernel/celledges.cc b/kernel/celledges.cc index 8129e6b1b..8e52d0380 100644 --- a/kernel/celledges.cc +++ b/kernel/celledges.cc @@ -247,7 +247,7 @@ void shift_op(AbstractCellEdgesDatabase *db, RTLIL::Cell *cell) db->add_edge(cell, ID::A, a_width - 1, ID::Y, i, -1); } - for (int k = 0; k < b_width; k++) { + for (int k = 0; k < b_width_capped; k++) { // left shifts if (cell->type.in(ID($shl), ID($sshl))) { if (a_width == 1 && is_signed) { @@ -268,7 +268,7 @@ void shift_op(AbstractCellEdgesDatabase *db, RTLIL::Cell *cell) bool shift_in_bulk = i < a_width - 1; // can we jump into the zero-padding by toggling B[k]? bool zpad_jump = (((y_width - i) & ((1 << (k + 1)) - 1)) != 0 \ - && (((y_width - i) & ~(1 << k)) < (1 << b_width))); + && (((y_width - i) & ~(1 << k)) < (1 << b_width_capped))); if (shift_in_bulk || (cell->type.in(ID($shr), ID($shift), ID($shiftx)) && zpad_jump)) db->add_edge(cell, ID::B, k, ID::Y, i, -1); @@ -279,7 +279,7 @@ void shift_op(AbstractCellEdgesDatabase *db, RTLIL::Cell *cell) // bidirectional shifts (positive B shifts right, negative left) } else if (cell->type.in(ID($shift), ID($shiftx)) && is_b_signed) { if (is_signed) { - if (k != b_width - 1) { + if (k != b_width_capped - 1) { bool r_shift_in_bulk = i < a_width - 1; // assuming B is positive, can we jump into the upper zero-padding by toggling B[k]? bool r_zpad_jump = (((y_width - i) & ((1 << (k + 1)) - 1)) != 0 \ diff --git a/kernel/driver.cc b/kernel/driver.cc index eb1326ce0..b7f0268db 100644 --- a/kernel/driver.cc +++ b/kernel/driver.cc @@ -314,7 +314,7 @@ int main(int argc, char **argv) auto result = options.parse(argc, argv); if (result.count("M")) memhasher_on(); - if (result.count("X")) yosys_xtrace++; + if (result.count("X")) yosys_xtrace += result.count("X"); if (result.count("A")) call_abort = true; if (result.count("Q")) print_banner = false; if (result.count("T")) print_stats = false; diff --git a/kernel/gzip.cc b/kernel/gzip.cc index d44b03517..4567fe03b 100644 --- a/kernel/gzip.cc +++ b/kernel/gzip.cc @@ -100,11 +100,12 @@ gzip_istream::ibuf::~ibuf() { // Takes a successfully opened ifstream. If it's gzipped, returns an istream. Otherwise, // returns the original ifstream, rewound to the start. +// Never returns nullptr or failed state istream* std::istream* uncompressed(const std::string filename, std::ios_base::openmode mode) { std::ifstream* f = new std::ifstream(); f->open(filename, mode); if (f->fail()) - return f; + log_cmd_error("Can't open input file `%s' for reading: %s\n", filename.c_str(), strerror(errno)); // Check for gzip magic unsigned char magic[3]; int n = 0; @@ -124,7 +125,7 @@ std::istream* uncompressed(const std::string filename, std::ios_base::openmode m filename.c_str(), unsigned(magic[2])); gzip_istream* s = new gzip_istream(); delete f; - s->open(filename.c_str()); + log_assert(s->open(filename.c_str())); return s; #else log_cmd_error("File `%s' is a gzip file, but Yosys is compiled without zlib.\n", filename.c_str()); diff --git a/kernel/log.h b/kernel/log.h index e26ef072c..6c834c6c6 100644 --- a/kernel/log.h +++ b/kernel/log.h @@ -148,7 +148,7 @@ static inline bool ys_debug(int n = 0) { if (log_force_debug) return true; log_d #else static inline bool ys_debug(int = 0) { return false; } #endif -# define log_debug(...) do { if (ys_debug(1)) log(__VA_ARGS__); } while (0) +static inline void log_debug(const char *format, ...) { if (ys_debug(1)) { va_list args; va_start(args, format); logv(format, args); va_end(args); } } static inline void log_suppressed() { if (log_debug_suppressed && !log_make_debug) { diff --git a/kernel/register.cc b/kernel/register.cc index c52bfb5b8..a82f93555 100644 --- a/kernel/register.cc +++ b/kernel/register.cc @@ -472,8 +472,6 @@ void Frontend::extra_args(std::istream *&f, std::string &filename, std::vector undef_a = importUndefSigSpec(cell->getPort(ID::A), timestep); std::vector undef_b = importUndefSigSpec(cell->getPort(ID::B), timestep); + std::vector undef_c; + + if (cell->type == ID($macc_v2)) + undef_c = importUndefSigSpec(cell->getPort(ID::C), timestep); int undef_any_a = ez->expression(ezSAT::OpOr, undef_a); int undef_any_b = ez->expression(ezSAT::OpOr, undef_b); + int undef_any_c = ez->expression(ezSAT::OpOr, undef_c); + int undef_any = ez->OR(undef_any_a, ez->OR(undef_any_b, undef_any_c)); std::vector undef_y = importUndefSigSpec(cell->getPort(ID::Y), timestep); - ez->assume(ez->vec_eq(undef_y, std::vector(GetSize(y), ez->OR(undef_any_a, undef_any_b)))); + ez->assume(ez->vec_eq(undef_y, std::vector(GetSize(y), undef_any))); undefGating(y, tmp, undef_y); } diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc index af7e1bca6..4ecaea7dd 100644 --- a/passes/cmds/Makefile.inc +++ b/passes/cmds/Makefile.inc @@ -55,3 +55,4 @@ OBJS += passes/cmds/wrapcell.o OBJS += passes/cmds/setenv.o OBJS += passes/cmds/abstract.o OBJS += passes/cmds/test_select.o +OBJS += passes/cmds/timeest.o diff --git a/passes/cmds/clean_zerowidth.cc b/passes/cmds/clean_zerowidth.cc index a15be8a39..48a8864c0 100644 --- a/passes/cmds/clean_zerowidth.cc +++ b/passes/cmds/clean_zerowidth.cc @@ -128,7 +128,7 @@ struct CleanZeroWidthPass : public Pass { // A and B to 1-bit if their width is 0. if (cell->getParam(ID::Y_WIDTH).as_int() == 0) { module->remove(cell); - } else if (cell->type == ID($macc)) { + } else if (cell->type.in(ID($macc), ID($macc_v2))) { // TODO: fixing zero-width A and B not supported. } else { if (cell->getParam(ID::A_WIDTH).as_int() == 0) { diff --git a/passes/cmds/stat.cc b/passes/cmds/stat.cc index 6fae312b4..63926c6e7 100644 --- a/passes/cmds/stat.cc +++ b/passes/cmds/stat.cc @@ -40,7 +40,7 @@ struct statdata_t X(num_ports) X(num_port_bits) X(num_memories) X(num_memory_bits) X(num_cells) \ X(num_processes) - #define STAT_NUMERIC_MEMBERS STAT_INT_MEMBERS X(area) + #define STAT_NUMERIC_MEMBERS STAT_INT_MEMBERS X(area) X(sequential_area) #define X(_name) unsigned int _name; STAT_INT_MEMBERS @@ -350,8 +350,6 @@ void read_liberty_cellarea(dict &cell_area, string libert { std::istream* f = uncompressed(liberty_file.c_str()); yosys_input_files.insert(liberty_file); - if (f->fail()) - log_cmd_error("Can't open liberty file `%s': %s\n", liberty_file.c_str(), strerror(errno)); LibertyParser libparser(*f, liberty_file); delete f; diff --git a/passes/cmds/tee.cc b/passes/cmds/tee.cc index 39ed4a7a8..853f1bad3 100644 --- a/passes/cmds/tee.cc +++ b/passes/cmds/tee.cc @@ -72,7 +72,9 @@ struct TeePass : public Pass { } if ((args[argidx] == "-o" || args[argidx] == "-a") && argidx+1 < args.size()) { const char *open_mode = args[argidx] == "-o" ? "w" : "a+"; - FILE *f = fopen(args[++argidx].c_str(), open_mode); + auto path = args[++argidx]; + rewrite_filename(path); + FILE *f = fopen(path.c_str(), open_mode); yosys_input_files.insert(args[argidx]); if (f == NULL) { for (auto cf : files_to_close) diff --git a/passes/cmds/timeest.cc b/passes/cmds/timeest.cc new file mode 100644 index 000000000..05dd2a4b3 --- /dev/null +++ b/passes/cmds/timeest.cc @@ -0,0 +1,418 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Martin PoviĊĦer + * + * 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/sigtools.h" +#include "kernel/register.h" +#include "kernel/cellaigs.h" +#include "kernel/utils.h" +#include "kernel/ff.h" +#include "kernel/mem.h" + +#include +#include + +USING_YOSYS_NAMESPACE +template<> struct Yosys::hashlib::hash_ops : Yosys::hashlib::hash_ptr_ops {}; + +PRIVATE_NAMESPACE_BEGIN + +typedef long int arrivalint; +const arrivalint INF_PAST = std::numeric_limits::min(); + +// each clock domain must have its own EstimateSta structure +struct EstimateSta { + SigMap sigmap; + Module *m; + SigBit clk; + + dict>, Aig> aigs; + dict cell_aigs; + + std::vector> launchers; + std::vector> samplers; + bool all_paths = false; + bool select = false; + + void add_seq(Cell *cell, SigSpec launch, SigSpec sample) + { + sigmap.apply(launch); + sigmap.apply(sample); + launch.sort_and_unify(); + sample.sort_and_unify(); + for (auto bit : launch) + launchers.push_back(std::make_pair(cell, bit)); + for (auto bit : sample) + samplers.push_back(std::make_pair(cell, bit)); + } + + // we include a discount factor for cells that can be implemented using carry chain logic + // and to account for the AIG model not being balanced + int cell_type_factor(IdString type) + { + if (type.in(ID($gt), ID($ge), ID($lt), ID($le), ID($add), ID($sub), + ID($logic_not), ID($reduce_and), ID($reduce_or), ID($eq))) + return 1; + else + return 2; + } + + // TODO: ignores clock polarity + EstimateSta(Module *m, SigBit clk) + : sigmap(m), m(m), clk(clk) + { + sigmap.apply(clk); + } + + void run() + { + log("Domain %s\n", log_signal(clk)); + + // first, we collect launch and sample points and convert the combinational logic to AIG + std::vector combinational; + + for (auto cell : m->cells()) { + SigSpec launch, sample; + if (RTLIL::builtin_ff_cell_types().count(cell->type)) { + // collect launch and sample points for FF cell + FfData ff(nullptr, cell); + if (!ff.has_clk) { + log_warning("Ignoring unsupported storage element '%s' (%s)\n", + log_id(cell), log_id(cell->type)); + continue; + } + if (ff.sig_clk != clk) + continue; + launch.append(ff.sig_q); + sample.append(ff.sig_d); + if (ff.has_ce) + sample.append(ff.sig_ce); + if (ff.has_srst) + sample.append(ff.sig_srst); + add_seq(cell, launch, sample); + } else if (cell->is_mem_cell()) { + // memories handled separately + continue; + } else if (cell->type == ID($scopeinfo)) { + continue; + } else { + // find or build AIG model of combinational cell + auto fingerprint = std::make_pair(cell->type, cell->parameters); + if (!aigs.count(fingerprint)) { + aigs.emplace(fingerprint, Aig(cell)); + if (aigs.at(fingerprint).name.empty()) { + log_error("Unsupported cell '%s' in module '%s'", + log_id(cell->type), log_id(m)); + } + } + + combinational.push_back(cell); + continue; + } + } + + // since we're now taking reference into `aigs`, we can no longer modify it + // and thus have to fill `cell_aigs` in a separate loop + for (auto cell : combinational) { + auto fingerprint = std::make_pair(cell->type, cell->parameters); + cell_aigs.emplace(cell, &aigs.at(fingerprint)); + } + + // collect launch and sample points for memory cells + for (auto &mem : Mem::get_all_memories(m)) { + for (auto &rd : mem.rd_ports) { + if (!rd.clk_enable) { + log_error("Unsupported async memory port '%s'\n", log_id(rd.cell)); + continue; + } + if (sigmap(rd.clk) != clk) + continue; + add_seq(rd.cell, rd.data, {rd.addr, rd.srst, rd.en}); + } + for (auto &wr : mem.wr_ports) { + if (sigmap(wr.clk) != clk) + continue; + add_seq(wr.cell, {}, {wr.en, wr.addr, wr.data}); + } + } + + // now we toposort the combinational logic + + // each toposort node is either a SigBit or a pair of Cell * / AigNode * + TopoSort> topo; + + auto desc_aig = [&](Cell *cell, AigNode &node) { + return std::make_tuple(RTLIL::S0, cell, &node); + }; + auto desc_sig = [&](SigBit bit) { + return std::make_tuple(sigmap(bit), (Cell *) NULL, (AigNode *) NULL); + }; + + // collect edges of the AIG graph + for (auto cell : combinational) { + assert(cell_aigs.count(cell)); + Aig &aig = *cell_aigs.at(cell); + for (auto &node : aig.nodes) { + if (!node.portname.empty()) { + topo.edge( + desc_sig(cell->getPort(node.portname)[node.portbit]), + desc_aig(cell, node) + ); + } else if (node.left_parent < 0 && node.right_parent < 0) { + // constant, nothing to do + } else { + topo.edge( + desc_aig(cell, aig.nodes[node.left_parent]), + desc_aig(cell, node) + ); + topo.edge( + desc_aig(cell, aig.nodes[node.right_parent]), + desc_aig(cell, node) + ); + } + + for (auto &oport : node.outports) { + topo.edge( + desc_aig(cell, node), + desc_sig(cell->getPort(oport.first)[oport.second]) + ); + } + } + } + + if (!topo.sort()) + log_error("Module '%s' contains combinational loops", log_id(m)); + + // now we determine how long it takes for signals to stabilize + + // `levels` records the time after a clock edge after which a signal is stable + dict, arrivalint> levels; + + for (auto node : topo.sorted) + levels[node] = INF_PAST; + + // launch points are at 0 by definition + for (auto pair : launchers) + levels[desc_sig(pair.second)] = 0; + + for (auto node : topo.sorted) { + AigNode *aig_node = std::get<2>(node); + if (aig_node) { + Cell *cell = std::get<1>(node); + Aig &aig = *cell_aigs.at(cell); + if (!aig_node->portname.empty()) { + // for a cell port, copy `levels` value from port bit + SigBit bit = cell->getPort(aig_node->portname)[aig_node->portbit]; + levels[node] = levels[desc_sig(bit)]; + } else if (aig_node->left_parent < 0 && aig_node->right_parent < 0) { + // constant, nothing to do + } else { + // for each AIG node, find maximum of parents and add a cell-specific delay + int left = levels[desc_aig(cell, aig.nodes[aig_node->left_parent])]; + int right = levels[desc_aig(cell, aig.nodes[aig_node->right_parent])]; + levels[node] = (std::max(left, right) + cell_type_factor(cell->type)); + } + + // copy `levels` value to any output ports + for (auto &oport : aig_node->outports) { + levels[desc_sig(cell->getPort(oport.first)[oport.second])] = levels[node]; + } + } + } + + // now find the length of the critical path (slowest path in the design) + arrivalint crit = INF_PAST; + for (auto pair : samplers) + if (levels[desc_sig(pair.second)] > crit) + crit = levels[desc_sig(pair.second)]; + + if (crit < 0) { + log("No paths found\n"); + return; + } + + log("Critical path is %ld nodes long:\n\n", crit); + + // we use dict instead of pool because dict gives us + // some compile-time errors related to hashing + dict, bool> critical; + + // actually find one critical path, or all such paths if requested + for (auto pair : samplers) { + if (levels[desc_sig(pair.second)] == crit) { + critical[desc_sig(pair.second)] = true; + if (!all_paths) + break; + } + } + + // walk backwards through toposorted nodes and set critical flag on nodes in critical path + for (auto it = topo.sorted.rbegin(); it != topo.sorted.rend(); it++) { + auto node = *it; + AigNode *aig_node = std::get<2>(node); + if (aig_node) { + Cell *cell = std::get<1>(node); + Aig &aig = *cell_aigs.at(cell); + + for (auto &oport : aig_node->outports) { + if (critical.count(desc_sig(cell->getPort(oport.first)[oport.second]))) + critical[node] = true; + } + + if (!aig_node->portname.empty()) { + SigBit bit = cell->getPort(aig_node->portname)[aig_node->portbit]; + if (critical.count(node)) + critical[desc_sig(bit)] = true; + } else if (aig_node->left_parent < 0 && aig_node->right_parent < 0) { + // constant, nothing to do + } else { + // figure out which parent is on the critical path + auto left = desc_aig(cell, aig.nodes[aig_node->left_parent]); + auto right = desc_aig(cell, aig.nodes[aig_node->right_parent]); + int crit_input_lvl = levels[node] - cell_type_factor(cell->type); + if (critical.count(node)) { + bool left_critical = (levels[left] == crit_input_lvl); + bool right_critical = (levels[right] == crit_input_lvl); + if (all_paths) { + if (left_critical) + critical[left] = true; + if (right_critical) + critical[right] = true; + } else { + if (left_critical) + critical[left] = true; + else if (right_critical) + critical[right] = true; + } + } + } + } + } + + // finally print the path we found + SigPool bits_to_select; + pool to_select; + + pool printed; + for (auto node : topo.sorted) { + if (!critical.count(node)) + continue; + AigNode *aig_node = std::get<2>(node); + if (aig_node) { + Cell *cell = std::get<1>(node); + if (!printed.count(cell)) { + to_select.insert(cell->name); + std::string cell_src; + if (cell->has_attribute(ID::src)) { + std::string src_attr = cell->get_src_attribute(); + cell_src = stringf(" source: %s", src_attr.c_str()); + } + log(" cell %s (%s)%s\n", log_id(cell), log_id(cell->type), cell_src.c_str()); + printed.insert(cell); + } + } else { + SigBit bit = std::get<0>(node); + bits_to_select.add(bit); + std::string wire_src; + if (bit.wire && bit.wire->has_attribute(ID::src)) { + std::string src_attr = bit.wire->get_src_attribute(); + wire_src = stringf(" source: %s", src_attr.c_str()); + } + log(" wire %s%s (level %ld)\n", log_signal(bit), wire_src.c_str(), levels[node]); + } + } + + for (auto wire : m->wires()) { + if (bits_to_select.check_any(sigmap(wire))) + to_select.insert(wire->name); + } + + if (select) { + RTLIL::Selection sel(false); + for (auto member : to_select) + sel.selected_members[m->name].insert(member); + m->design->selection_stack.back() = sel; + m->design->selection_stack.back().optimize(m->design); + } + } +}; + +struct TimeestPass : Pass { + TimeestPass() : Pass("timeest", "estimate timing") {} + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" timeest [-clk ] [options] [selection]\n"); + log("\n"); + log("Estimate the critical path in clock domain by counting AIG nodes.\n"); + log("\n"); + log(" -all_paths\n"); + log(" Print or select nodes from all critical paths instead of focusing on\n"); + log(" a single illustratory path.\n"); + log("\n"); + log(" -select\n"); + log(" Select the nodes of a critical path\n"); + log("\n"); + } + void execute(std::vector args, RTLIL::Design *d) override + { + log_header(d, "Executing TIMEEST pass. (estimate timing)\n"); + + std::string clk; + bool all_paths = false; + bool select = false; + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-all_paths") { + all_paths = true; + continue; + } + if (args[argidx] == "-select") { + select = true; + continue; + } + if (args[argidx] == "-clk" && argidx + 1 < args.size()) { + clk = args[++argidx]; + continue; + } + break; + } + extra_args(args, argidx, d); + + if (clk.empty()) + log_cmd_error("No -clk argument provided\n"); + + if (select && d->selected_modules().size() > 1) + log_cmd_error("The -select option operates on a single selected module\n"); + + for (auto m : d->selected_modules()) { + if (!m->wire(RTLIL::escape_id(clk))) { + log_warning("No domain '%s' in module %s\n", clk.c_str(), log_id(m)); + continue; + } + + EstimateSta sta(m, SigBit(m->wire(RTLIL::escape_id(clk)), 0)); + sta.all_paths = all_paths; + sta.select = select; + sta.run(); + } + } +} TimeestPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/opt/opt_expr.cc b/passes/opt/opt_expr.cc index df969daf0..74f5b386a 100644 --- a/passes/opt/opt_expr.cc +++ b/passes/opt/opt_expr.cc @@ -1315,13 +1315,14 @@ skip_fine_alu: RTLIL::SigSpec sig_a = assign_map(cell->getPort(ID::A)); RTLIL::SigSpec sig_y(cell->type == ID($shiftx) ? RTLIL::State::Sx : RTLIL::State::S0, cell->getParam(ID::Y_WIDTH).as_int()); - // Limit indexing to the size of a, which is behaviourally identical (result is all 0) - // and avoids integer overflow of i + shift_bits when e.g. ID::B == INT_MAX - shift_bits = min(shift_bits, GetSize(sig_a)); - if (cell->type != ID($shiftx) && GetSize(sig_a) < GetSize(sig_y)) sig_a.extend_u0(GetSize(sig_y), cell->getParam(ID::A_SIGNED).as_bool()); + // Limit indexing to the size of a, which is behaviourally identical (result is all 0) + // and avoids integer overflow of i + shift_bits when e.g. ID::B == INT_MAX. + // We do this after sign-extending a so this accounts for the output size + shift_bits = min(shift_bits, GetSize(sig_a)); + for (int i = 0; i < GetSize(sig_y); i++) { int idx = i + shift_bits; if (0 <= idx && idx < GetSize(sig_a)) diff --git a/passes/sat/formalff.cc b/passes/sat/formalff.cc index f81d492c8..1d87fcc3b 100644 --- a/passes/sat/formalff.cc +++ b/passes/sat/formalff.cc @@ -22,6 +22,7 @@ #include "kernel/ffinit.h" #include "kernel/ff.h" #include "kernel/modtools.h" +#include "kernel/mem.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN @@ -537,6 +538,12 @@ struct FormalFfPass : public Pass { log(" Add assumptions that constrain wires with the 'replaced_by_gclk'\n"); log(" attribute to the value they would have before an active clock edge.\n"); log("\n"); + log(" -declockgate\n"); + log(" Detect clock-gating patterns and modify any FFs clocked by the gated\n"); + log(" clock to use the ungated clock with the gate signal as clock enable.\n"); + log(" This doesn't affect the design's behavior during FV but can enable the\n"); + log(" use of formal verification methods that only support a single global\n"); + log(" clock.\n"); // TODO: An option to check whether all FFs use the same clock before changing it to the global clock } @@ -549,6 +556,7 @@ struct FormalFfPass : public Pass { bool flag_setundef = false; bool flag_hierarchy = false; bool flag_assume = false; + bool flag_declockgate = false; log_header(design, "Executing FORMALFF pass.\n"); @@ -583,22 +591,237 @@ struct FormalFfPass : public Pass { flag_assume = true; continue; } + if (args[argidx] == "-declockgate") { + flag_declockgate = true; + continue; + } break; } extra_args(args, argidx, design); - if (!(flag_clk2ff || flag_ff2anyinit || flag_anyinit2ff || flag_hierarchy || flag_assume)) + if (!(flag_clk2ff || flag_ff2anyinit || flag_anyinit2ff || flag_hierarchy || flag_assume || flag_declockgate)) log_cmd_error("One of the options -clk2ff, -ff2anyinit, -anyinit2ff, -hierarchy or -assume must be specified.\n"); if (flag_ff2anyinit && flag_anyinit2ff) log_cmd_error("The options -ff2anyinit and -anyinit2ff are exclusive.\n"); + if (flag_ff2anyinit && flag_declockgate) + log_cmd_error("The options -ff2anyinit and -declockgate are exclusive.\n"); + if (flag_fine && !flag_anyinit2ff) log_cmd_error("The option -fine requries the -anyinit2ff option.\n"); if (flag_fine && flag_clk2ff) log_cmd_error("The options -fine and -clk2ff are exclusive.\n"); + if (flag_declockgate) + { + for (auto module : design->selected_modules()) + { + ModWalker modwalker(design); + modwalker.setup(module); + SigMap &sigmap = modwalker.sigmap; + FfInitVals initvals(&modwalker.sigmap, module); + + dict memories; + + for (auto mem : Mem::get_selected_memories(module)) { + if (!mem.packed) + continue; + memories.emplace(mem.cell->name, std::move(mem)); + } + + dict, vector> clk_bits; + pool input_bits; + pool> input_clk_bits; + for (auto cell : module->selected_cells()) { + if (RTLIL::builtin_ff_cell_types().count(cell->type)) { + FfData ff(&initvals, cell); + if (!ff.has_clk) + continue; + SigBit clk = sigmap(ff.sig_clk); + clk_bits[{clk, ff.pol_clk}].push_back(cell); + } else if (cell->type == ID($mem_v2)) { + auto const &mem = memories.at(cell->name); + for (auto &rd_port : mem.rd_ports) + if (rd_port.clk_enable) + clk_bits[{rd_port.clk, rd_port.clk_polarity}].push_back(mem.cell); + for (auto &wr_port : mem.wr_ports) + if (wr_port.clk_enable) + clk_bits[{wr_port.clk, wr_port.clk_polarity}].push_back(mem.cell); + } + // XXX $check $print + } + + log_debug("%s has %d clk bits\n", log_id(module), GetSize(clk_bits)); + + for (auto port : module->ports) { + Wire *wire = module->wire(port); + if (!wire->port_input) + continue; + for (auto bit : SigSpec(wire)) { + input_bits.insert(bit); + for (bool pol : {false, true}) { + if (clk_bits.count({bit, pol})) { + input_clk_bits.insert({bit, pol}); + clk_bits.erase({bit, pol}); + } + } + } + } + log_debug("%s has %d non-input clk bits\n", log_id(module), GetSize(clk_bits)); + + if (clk_bits.empty()) + continue; + + for (auto &clk_bit : clk_bits) + { + SigBit clk = clk_bit.first.first; + bool pol_clk = clk_bit.first.second; + vector &clocked_cells = clk_bit.second; + + if (!clk.is_wire()) { + log_debug("constant clk bit %s.%s\n", log_id(module), log_signal(SigSpec(clk))); + continue; + } + if (input_bits.count(clk)) { + log_debug("input clk bit %s.%s\n", log_id(module), log_signal(SigSpec(clk))); + continue; + } + auto found = modwalker.signal_drivers.find(clk); + if (found == modwalker.signal_drivers.end() || found->second.empty()) { + log_debug("undriven clk bit %s.%s\n", log_id(module), log_signal(SigSpec(clk))); + continue; + } + + if (found->second.size() > 1) { + log_debug("multiple drivers for clk bit %s.%s\n", log_id(module), log_signal(SigSpec(clk))); + continue; + } + + auto driver = *found->second.begin(); + + bool is_gate = + pol_clk ? driver.cell->type.in(ID($and), ID($_AND_)) : driver.cell->type.in(ID($or), ID($_OR_)); + + if (!is_gate) { + log_debug("unsupported gating logic %s.%s (%s) for clock %s %s.%s\n", log_id(module), + log_id(driver.cell), log_id(driver.cell->type), pol_clk ? "posedge" : "negedge", + log_id(module), log_signal(SigSpec(clk))); + + continue; + } + SigBit gate_clock = sigmap(driver.cell->getPort(ID::A)[driver.offset]); + SigBit gate_enable = sigmap(driver.cell->getPort(ID::B)[driver.offset]); + + std::swap(gate_clock, gate_enable); + for (int i = 0; i < 2; i++) { + std::swap(gate_clock, gate_enable); + + log_debug("clock %s.%s for gated clk bit %s.%s\n", log_id(module), log_signal(SigSpec(gate_clock)), + log_id(module), log_signal(SigSpec(clk))); + log_debug("enable %s.%s for gated clk bit %s.%s\n", log_id(module), log_signal(SigSpec(gate_enable)), + log_id(module), log_signal(SigSpec(clk))); + + found = modwalker.signal_drivers.find(gate_enable); + if (found == modwalker.signal_drivers.end() || found->second.empty()) { + log_debug("undriven gate enable %s.%s of gated clk bit %s.%s\n", log_id(module), + log_signal(SigSpec(gate_enable)), log_id(module), log_signal(SigSpec(clk))); + continue; + } + if (found->second.size() > 1) { + log_debug("multiple drivers for gate enable %s.%s of gated clk bit %s.%s\n", log_id(module), + log_signal(SigSpec(gate_enable)), log_id(module), log_signal(SigSpec(clk))); + continue; + } + + auto gate_driver = *found->second.begin(); + + if (!RTLIL::builtin_ff_cell_types().count(gate_driver.cell->type)) { + log_debug("non FF driver for gate enable %s.%s of gated clk bit %s.%s\n", log_id(module), + log_signal(SigSpec(gate_enable)), log_id(module), log_signal(SigSpec(clk))); + continue; + } + + FfData ff(&initvals, gate_driver.cell); + if (ff.has_gclk || ff.has_ce || ff.has_sr || ff.has_srst || ff.has_arst || (ff.has_aload && ff.has_clk)) { + log_debug( + "FF driver for gate enable %s.%s of gated clk bit %s.%s has incompatible type: %s\n", + log_id(module), log_signal(SigSpec(gate_enable)), log_id(module), log_signal(SigSpec(clk)), + log_id(gate_driver.cell->type)); + continue; + } + + if (ff.has_aload) { + // this ff is intentionally not emitted! + ff.has_aload = false; + ff.has_clk = true; + ff.pol_clk = !ff.pol_arst; + ff.sig_clk = ff.sig_aload; + ff.sig_d = ff.sig_ad; + } + + if (!ff.has_clk || sigmap(ff.sig_clk) != gate_clock || ff.pol_clk != pol_clk) { + log_debug("FF driver for gate enable %s.%s of gated clk bit %s.%s has incompatible clocking: " + "%s %s.%s\n", + log_id(module), log_signal(SigSpec(gate_enable)), log_id(module), + log_signal(SigSpec(clk)), ff.pol_clk ? "posedge" : "negedge", log_id(module), + log_signal(SigSpec(ff.sig_clk))); + continue; + } + + SigBit sig_gate = ff.sig_d[gate_driver.offset]; + + log_debug("found clock gate, rewriting %d cells\n", GetSize(clocked_cells)); + + for (auto clocked_cell : clocked_cells) { + log_debug("rewriting cell %s.%s (%s)\n", log_id(module), log_id(clocked_cell), + log_id(clocked_cell->type)); + + if (RTLIL::builtin_ff_cell_types().count(clocked_cell->type)) { + + FfData ff(&initvals, clocked_cell); + log_assert(ff.has_clk); + ff.unmap_ce(); + ff.pol_ce = pol_clk; + ff.sig_ce = sig_gate; + ff.has_ce = true; + ff.sig_clk = gate_clock; + ff.emit(); + } else if (clocked_cell->type == ID($mem_v2)) { + auto &mem = memories.at(clocked_cell->name); + bool changed = false; + for (auto &rd_port : mem.rd_ports) { + if (rd_port.clk_enable && rd_port.clk == clk && rd_port.clk_polarity == pol_clk) { + log_debug("patching rd port\n"); + changed = true; + rd_port.clk = gate_clock; + SigBit en_bit = pol_clk ? sig_gate : SigBit(module->Not(NEW_ID, sig_gate)); + SigSpec en_mask = SigSpec(en_bit, GetSize(rd_port.en)); + rd_port.en = module->And(NEW_ID, rd_port.en, en_mask); + } + } + for (auto &wr_port : mem.wr_ports) { + if (wr_port.clk_enable && wr_port.clk == clk && wr_port.clk_polarity == pol_clk) { + log_debug("patching wr port\n"); + changed = true; + wr_port.clk = gate_clock; + SigBit en_bit = pol_clk ? sig_gate : SigBit(module->Not(NEW_ID, sig_gate)); + SigSpec en_mask = SigSpec(en_bit, GetSize(wr_port.en)); + wr_port.en = module->And(NEW_ID, wr_port.en, en_mask); + } + } + if (changed) + mem.emit(); + } + } + + break; + } + } + } + } + for (auto module : design->selected_modules()) { if (flag_setundef) diff --git a/passes/techmap/clockgate.cc b/passes/techmap/clockgate.cc index 2305cfc94..508e66d23 100644 --- a/passes/techmap/clockgate.cc +++ b/passes/techmap/clockgate.cc @@ -310,8 +310,6 @@ struct ClockgatePass : public Pass { LibertyMergedCells merged; for (auto path : liberty_files) { std::istream* f = uncompressed(path); - if (f->fail()) - log_cmd_error("Can't open liberty file `%s': %s\n", path.c_str(), strerror(errno)); LibertyParser p(*f, path); merged.merge(p); delete f; diff --git a/passes/techmap/dfflibmap.cc b/passes/techmap/dfflibmap.cc index 84db7f157..d00fee83b 100644 --- a/passes/techmap/dfflibmap.cc +++ b/passes/techmap/dfflibmap.cc @@ -102,6 +102,9 @@ static bool parse_next_state(const LibertyAst *cell, const LibertyAst *attr, std } else if (expr[0] == '!') { data_name = expr.substr(1, expr.size()-1); data_not_inverted = false; + } else if (expr[0] == '(' && expr[expr.size() - 1] == ')') { + data_name = expr.substr(1, expr.size() - 2); + data_not_inverted = true; } else { data_name = expr; data_not_inverted = true; @@ -632,8 +635,6 @@ struct DfflibmapPass : public Pass { LibertyMergedCells merged; for (auto path : liberty_files) { std::istream* f = uncompressed(path); - if (f->fail()) - log_cmd_error("Can't open liberty file `%s': %s\n", path.c_str(), strerror(errno)); LibertyParser p(*f, path); merged.merge(p); delete f; diff --git a/passes/techmap/flatten.cc b/passes/techmap/flatten.cc index 3425509b1..6363b3432 100644 --- a/passes/techmap/flatten.cc +++ b/passes/techmap/flatten.cc @@ -349,6 +349,10 @@ struct FlattenPass : public Pass { log(" -separator \n"); log(" Use this separator char instead of '.' when concatenating design levels.\n"); log("\n"); + log(" -nocleanup\n"); + log(" Don't remove unused submodules, leave a flattened version of each\n"); + log(" submodule in the design.\n"); + log("\n"); } void execute(std::vector args, RTLIL::Design *design) override { @@ -360,6 +364,8 @@ struct FlattenPass : public Pass { if (design->scratchpad.count("flatten.separator")) worker.separator = design->scratchpad_get_string("flatten.separator"); + bool cleanup = true; + size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { if (args[argidx] == "-wb") { @@ -378,6 +384,10 @@ struct FlattenPass : public Pass { worker.separator = args[++argidx]; continue; } + if (args[argidx] == "-nocleanup") { + cleanup = false; + continue; + } break; } extra_args(args, argidx, design); @@ -414,7 +424,7 @@ struct FlattenPass : public Pass { for (auto module : topo_modules.sorted) worker.flatten_module(design, module, used_modules, worker.separator); - if (top != nullptr) + if (cleanup && top != nullptr) for (auto module : design->modules().to_vector()) if (!used_modules[module] && !module->get_blackbox_attribute(worker.ignore_wb)) { log("Deleting now unused module %s.\n", log_id(module)); diff --git a/techlibs/ecp5/cells_sim.v b/techlibs/ecp5/cells_sim.v index 950d12c91..eec211d6b 100644 --- a/techlibs/ecp5/cells_sim.v +++ b/techlibs/ecp5/cells_sim.v @@ -386,7 +386,7 @@ module TRELLIS_IO( ); parameter DIR = "INPUT"; reg T_pd; - always @(*) if (T === 1'bz) T_pd <= 1'b0; else T_pd <= T; + always @(*) if (T === 1'bz) T_pd = 1'b0; else T_pd = T; generate if (DIR == "INPUT") begin diff --git a/techlibs/gatemate/brams.txt b/techlibs/gatemate/brams.txt index be22856ac..d39aafcbf 100644 --- a/techlibs/gatemate/brams.txt +++ b/techlibs/gatemate/brams.txt @@ -34,7 +34,6 @@ ram block $__CC_BRAM_TDP_ { } portoption "WR_MODE" "WRITE_THROUGH" { rdwr new; - wrtrans all new; } wrbe_separate; optional_rw; diff --git a/techlibs/gatemate/brams_map.v b/techlibs/gatemate/brams_map.v index 171825f49..0b039db35 100644 --- a/techlibs/gatemate/brams_map.v +++ b/techlibs/gatemate/brams_map.v @@ -115,15 +115,15 @@ generate .A_CLK(PORT_A_CLK), .A_EN(PORT_A_CLK_EN), .A_WE(PORT_A_WR_EN), - .A_BM(PORT_A_WR_BE), - .A_DI(PORT_A_WR_DATA), + .A_BM({{(20-PORT_A_WR_BE_WIDTH){1'bx}}, PORT_A_WR_BE}), + .A_DI({{(20-PORT_A_WR_WIDTH){1'bx}}, PORT_A_WR_DATA}), .A_ADDR({PORT_A_ADDR[13:5], 1'b0, PORT_A_ADDR[4:0], 1'b0}), .A_DO(PORT_A_RD_DATA), .B_CLK(PORT_B_CLK), .B_EN(PORT_B_CLK_EN), .B_WE(PORT_B_WR_EN), - .B_BM(PORT_B_WR_BE), - .B_DI(PORT_B_WR_DATA), + .B_BM({{(20-PORT_B_WR_BE_WIDTH){1'bx}}, PORT_B_WR_BE}), + .B_DI({{(20-PORT_B_WR_WIDTH){1'bx}}, PORT_B_WR_DATA}), .B_ADDR({PORT_B_ADDR[13:5], 1'b0, PORT_B_ADDR[4:0], 1'b0}), .B_DO(PORT_B_RD_DATA), ); @@ -270,15 +270,15 @@ generate .A_CLK(PORT_A_CLK), .A_EN(PORT_A_CLK_EN), .A_WE(PORT_A_WR_EN), - .A_BM(PORT_A_WR_BE), - .A_DI(PORT_A_WR_DATA), + .A_BM({{(40-PORT_A_WR_BE_WIDTH){1'bx}}, PORT_A_WR_BE}), + .A_DI({{(40-PORT_A_WR_WIDTH){1'bx}}, PORT_A_WR_DATA}), .A_ADDR({PORT_A_ADDR[14:0], 1'b0}), .A_DO(PORT_A_RD_DATA), .B_CLK(PORT_B_CLK), .B_EN(PORT_B_CLK_EN), .B_WE(PORT_B_WR_EN), - .B_BM(PORT_B_WR_BE), - .B_DI(PORT_B_WR_DATA), + .B_BM({{(40-PORT_B_WR_BE_WIDTH){1'bx}}, PORT_B_WR_BE}), + .B_DI({{(40-PORT_B_WR_WIDTH){1'bx}}, PORT_B_WR_DATA}), .B_ADDR({PORT_B_ADDR[14:0], 1'b0}), .B_DO(PORT_B_RD_DATA), ); @@ -429,14 +429,14 @@ generate .A_CLK(PORT_A_CLK), .A_EN(PORT_A_CLK_EN), .A_WE(PORT_A_WR_EN), - .A_BM(PORT_A_WR_BE), - .A_DI(PORT_A_WR_DATA), + .A_BM({{(40-PORT_A_WR_BE_WIDTH){1'bx}}, PORT_A_WR_BE}), + .A_DI({{(40-PORT_A_WR_WIDTH){1'bx}}, PORT_A_WR_DATA}), .A_ADDR({PORT_A_ADDR[14:0], PORT_A_ADDR[15]}), .B_CLK(PORT_B_CLK), .B_EN(PORT_B_CLK_EN), .B_WE(PORT_B_WR_EN), - .B_BM(PORT_B_WR_BE), - .B_DI(PORT_B_WR_DATA), + .B_BM({{(40-PORT_B_WR_BE_WIDTH){1'bx}}, PORT_B_WR_BE}), + .B_DI({{(40-PORT_B_WR_WIDTH){1'bx}}, PORT_B_WR_DATA}), .B_ADDR({PORT_B_ADDR[14:0], PORT_B_ADDR[15]}), ); CC_BRAM_40K #( @@ -584,15 +584,15 @@ generate .A_CLK(PORT_A_CLK), .A_EN(PORT_A_CLK_EN), .A_WE(PORT_A_WR_EN), - .A_BM(PORT_A_WR_BE), - .A_DI(PORT_A_WR_DATA), + .A_BM({{(40-PORT_A_WR_BE_WIDTH){1'bx}}, PORT_A_WR_BE}), + .A_DI({{(40-PORT_A_WR_WIDTH){1'bx}}, PORT_A_WR_DATA}), .A_DO(PORT_A_RD_DATA), .A_ADDR({PORT_A_ADDR[14:0], PORT_A_ADDR[15]}), .B_CLK(PORT_B_CLK), .B_EN(PORT_B_CLK_EN), .B_WE(PORT_B_WR_EN), - .B_BM(PORT_B_WR_BE), - .B_DI(PORT_B_WR_DATA), + .B_BM({{(40-PORT_B_WR_BE_WIDTH){1'bx}}, PORT_B_WR_BE}), + .B_DI({{(40-PORT_B_WR_WIDTH){1'bx}}, PORT_B_WR_DATA}), .B_DO(PORT_B_RD_DATA), .B_ADDR({PORT_B_ADDR[14:0], PORT_B_ADDR[15]}), ); @@ -710,9 +710,9 @@ generate .A_EN(PORT_W_CLK_EN), .A_WE(PORT_W_WR_EN), .A_BM(PORT_W_WR_BE[19:0]), - .B_BM(PORT_W_WR_BE[39:20]), + .B_BM({{(40-PORT_W_WIDTH){1'bx}}, PORT_W_WR_BE[39:20]}), .A_DI(PORT_W_WR_DATA[19:0]), - .B_DI(PORT_W_WR_DATA[39:20]), + .B_DI({{(40-PORT_W_WIDTH){1'bx}}, PORT_W_WR_DATA[39:20]}), .A_ADDR({PORT_W_ADDR[13:5], 1'b0, PORT_W_ADDR[4:0], 1'b0}), .B_CLK(PORT_R_CLK), .B_EN(PORT_R_CLK_EN), @@ -865,9 +865,9 @@ generate .A_EN(PORT_W_CLK_EN), .A_WE(PORT_W_WR_EN), .A_BM(PORT_W_WR_BE[39:0]), - .B_BM(PORT_W_WR_BE[79:40]), + .B_BM({{(80-PORT_W_WIDTH){1'bx}}, PORT_W_WR_BE[79:40]}), .A_DI(PORT_W_WR_DATA[39:0]), - .B_DI(PORT_W_WR_DATA[79:40]), + .B_DI({{(80-PORT_W_WIDTH){1'bx}}, PORT_W_WR_DATA[79:40]}), .A_ADDR({PORT_W_ADDR[14:0], 1'b0}), .B_CLK(PORT_R_CLK), .B_EN(PORT_R_CLK_EN), diff --git a/techlibs/gatemate/cells_sim.v b/techlibs/gatemate/cells_sim.v index e05ce811c..d930b83f8 100644 --- a/techlibs/gatemate/cells_sim.v +++ b/techlibs/gatemate/cells_sim.v @@ -292,10 +292,10 @@ module CC_DLT #( always @(*) begin if (sr) begin - Q <= SR_VAL; + Q = SR_VAL; end else if (en) begin - Q <= D; + Q = D; end end @@ -407,7 +407,7 @@ module CC_MULT #( ); always @(*) begin - P <= A * B; + P = A * B; end endmodule diff --git a/techlibs/greenpak4/cells_sim_digital.v b/techlibs/greenpak4/cells_sim_digital.v index 43d35d08f..f1393be0c 100644 --- a/techlibs/greenpak4/cells_sim_digital.v +++ b/techlibs/greenpak4/cells_sim_digital.v @@ -48,7 +48,7 @@ module GP_COUNT14(input CLK, input wire RST, output reg OUT); //Combinatorially output underflow flag whenever we wrap low always @(*) begin - OUT <= (count == 14'h0); + OUT = (count == 14'h0); end //POR or SYSRST reset value is COUNT_TO. Datasheet is unclear but conversations w/ Silego confirm. @@ -133,10 +133,10 @@ module GP_COUNT14_ADV(input CLK, input RST, output reg OUT, //Combinatorially output underflow flag whenever we wrap low always @(*) begin if(UP) - OUT <= (count == 14'h3fff); + OUT = (count == 14'h3fff); else - OUT <= (count == 14'h0); - POUT <= count[7:0]; + OUT = (count == 14'h0); + POUT = count[7:0]; end //POR or SYSRST reset value is COUNT_TO. Datasheet is unclear but conversations w/ Silego confirm. @@ -272,10 +272,10 @@ module GP_COUNT8_ADV(input CLK, input RST, output reg OUT, //Combinatorially output underflow flag whenever we wrap low always @(*) begin if(UP) - OUT <= (count == 8'hff); + OUT = (count == 8'hff); else - OUT <= (count == 8'h0); - POUT <= count; + OUT = (count == 8'h0); + POUT = count; end //POR or SYSRST reset value is COUNT_TO. Datasheet is unclear but conversations w/ Silego confirm. @@ -413,8 +413,8 @@ module GP_COUNT8( //Combinatorially output underflow flag whenever we wrap low always @(*) begin - OUT <= (count == 8'h0); - POUT <= count; + OUT = (count == 8'h0); + POUT = count; end //POR or SYSRST reset value is COUNT_TO. Datasheet is unclear but conversations w/ Silego confirm. @@ -488,23 +488,23 @@ module GP_DCMPMUX(input[1:0] SEL, input[7:0] IN0, input[7:0] IN1, input[7:0] IN2 always @(*) begin case(SEL) 2'd00: begin - OUTA <= IN0; - OUTB <= IN3; + OUTA = IN0; + OUTB = IN3; end 2'd01: begin - OUTA <= IN1; - OUTB <= IN2; + OUTA = IN1; + OUTB = IN2; end 2'd02: begin - OUTA <= IN2; - OUTB <= IN1; + OUTA = IN2; + OUTB = IN1; end 2'd03: begin - OUTA <= IN3; - OUTB <= IN0; + OUTA = IN3; + OUTB = IN0; end endcase @@ -635,7 +635,7 @@ module GP_DLATCH(input D, input nCLK, output reg Q); initial Q = INIT; always @(*) begin if(!nCLK) - Q <= D; + Q = D; end endmodule @@ -644,7 +644,7 @@ module GP_DLATCHI(input D, input nCLK, output reg nQ); initial nQ = INIT; always @(*) begin if(!nCLK) - nQ <= ~D; + nQ = ~D; end endmodule @@ -653,9 +653,9 @@ module GP_DLATCHR(input D, input nCLK, input nRST, output reg Q); initial Q = INIT; always @(*) begin if(!nRST) - Q <= 1'b0; + Q = 1'b0; else if(!nCLK) - Q <= D; + Q = D; end endmodule @@ -664,9 +664,9 @@ module GP_DLATCHRI(input D, input nCLK, input nRST, output reg nQ); initial nQ = INIT; always @(*) begin if(!nRST) - nQ <= 1'b1; + nQ = 1'b1; else if(!nCLK) - nQ <= ~D; + nQ = ~D; end endmodule @@ -675,9 +675,9 @@ module GP_DLATCHS(input D, input nCLK, input nSET, output reg Q); initial Q = INIT; always @(*) begin if(!nSET) - Q <= 1'b1; + Q = 1'b1; else if(!nCLK) - Q <= D; + Q = D; end endmodule @@ -686,9 +686,9 @@ module GP_DLATCHSI(input D, input nCLK, input nSET, output reg nQ); initial nQ = INIT; always @(*) begin if(!nSET) - nQ <= 1'b0; + nQ = 1'b0; else if(!nCLK) - nQ <= ~D; + nQ = ~D; end endmodule @@ -698,9 +698,9 @@ module GP_DLATCHSR(input D, input nCLK, input nSR, output reg Q); initial Q = INIT; always @(*) begin if(!nSR) - Q <= SRMODE; + Q = SRMODE; else if(!nCLK) - Q <= D; + Q = D; end endmodule @@ -710,9 +710,9 @@ module GP_DLATCHSRI(input D, input nCLK, input nSR, output reg nQ); initial nQ = INIT; always @(*) begin if(!nSR) - nQ <= ~SRMODE; + nQ = ~SRMODE; else if(!nCLK) - nQ <= ~D; + nQ = ~D; end endmodule diff --git a/techlibs/ice40/ice40_dsp.pmg b/techlibs/ice40/ice40_dsp.pmg index 63bc8de4b..7e4c3ace2 100644 --- a/techlibs/ice40/ice40_dsp.pmg +++ b/techlibs/ice40/ice40_dsp.pmg @@ -23,7 +23,7 @@ match mul endmatch code sigA sigB sigH - auto unextend = [](const SigSpec &sig) { + auto unextend_signed = [](const SigSpec &sig) { int i; for (i = GetSize(sig)-1; i > 0; i--) if (sig[i] != sig[i-1]) @@ -32,8 +32,16 @@ code sigA sigB sigH ++i; return sig.extract(0, i); }; - sigA = unextend(port(mul, \A)); - sigB = unextend(port(mul, \B)); + auto unextend_unsigned = [](const SigSpec &sig) { + int i; + for (i = GetSize(sig)-1; i > 0; i--) + if (sig[i] != SigBit(State::S0)) + break; + ++i; + return sig.extract(0, i); + }; + sigA = param(mul, \A_SIGNED).as_bool() ? unextend_signed(port(mul, \A)) : unextend_unsigned(port(mul, \A)); + sigB = param(mul, \B_SIGNED).as_bool() ? unextend_signed(port(mul, \B)) : unextend_unsigned(port(mul, \B)); SigSpec O; if (mul->type == $mul) diff --git a/tests/arch/ice40/ice40_dsp_const.ys b/tests/arch/ice40/ice40_dsp_const.ys new file mode 100644 index 000000000..c9c76a1ac --- /dev/null +++ b/tests/arch/ice40/ice40_dsp_const.ys @@ -0,0 +1,80 @@ +read_verilog << EOT +module top(input wire [14:0] a, output wire [18:0] b); +assign b = a*$unsigned(5'b01111); +endmodule +EOT + +prep +ice40_dsp + +read_verilog << EOT +module ref(a, b); + wire _0_; + wire _1_; + wire _2_; + wire [12:0] _3_; + (* src = "</dev/null > $x.filtered ../../yosys-filterlib -verilogsim $x > $x.verilogsim - diff $x.filtered $x.filtered.ok && diff $x.verilogsim $x.verilogsim.ok -done || exit 1 + diff $x.filtered $x.filtered.ok + diff $x.verilogsim $x.verilogsim.ok + if [[ -e ${x%.lib}.log.ok ]]; then + ../../yosys -p "dfflibmap -info -liberty ${x}" -TqqQl ${x%.lib}.log + diff ${x%.lib}.log ${x%.lib}.log.ok + fi +done for x in *.ys; do echo "Running $x.." ../../yosys -q -s $x -l ${x%.ys}.log -done || exit 1 +done diff --git a/tests/opt/opt_expr_shift.ys b/tests/opt/opt_expr_shift.ys new file mode 100644 index 000000000..aac2e6f62 --- /dev/null +++ b/tests/opt/opt_expr_shift.ys @@ -0,0 +1,50 @@ +# Testing edge cases where ports are signed/have different widths/shift amounts +# greater than the size + +read_verilog <> 20; + assign shr_us = in_u >> 20; + assign shr_su = in_s >> 20; + assign shr_ss = in_s >> 20; + assign sshl_uu = in_u <<< 20; + assign sshl_us = in_u <<< 20; + assign sshl_su = in_s <<< 20; + assign sshl_ss = in_s <<< 20; + assign sshr_uu = in_u >>> 20; + assign sshr_us = in_u >>> 20; + assign sshr_su = in_s >>> 20; + assign sshr_ss = in_s >>> 20; +endmodule +EOT + +equiv_opt opt_expr + +design -load postopt +select -assert-none t:$shl +select -assert-none t:$shr +select -assert-none t:$sshl +select -assert-none t:$sshr diff --git a/tests/various/formalff_declockgate.ys b/tests/various/formalff_declockgate.ys new file mode 100644 index 000000000..3dd47ea78 --- /dev/null +++ b/tests/various/formalff_declockgate.ys @@ -0,0 +1,83 @@ +# based on the peepopt_formal.ys test +read_verilog -sv <