diff --git a/CHANGELOG b/CHANGELOG index 1430f8e2f..0d433d4a3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,9 +2,22 @@ List of major changes and improvements between releases ======================================================= -Yosys 0.29 .. Yosys 0.29-dev +Yosys 0.30 .. Yosys 0.31-dev -------------------------- +Yosys 0.29 .. Yosys 0.30 +-------------------------- + * New commands and options + - Added "recover_names" pass to recover names post-mapping. + + * Gowin support + - Added remaining primitives blackboxes. + + * Various + - "show -colorattr" will now color the cells, wires, and + connection arrows. + - "show -viewer none" will not execute viewer. + Yosys 0.28 .. Yosys 0.29 -------------------------- * New commands and options diff --git a/Makefile b/Makefile index 99ee10c43..cf2d8f200 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LDLIBS += -lrt endif endif -YOSYS_VER := 0.29+44 +YOSYS_VER := 0.30+48 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo @@ -157,7 +157,7 @@ endif OBJS = kernel/version_$(GIT_REV).o bumpversion: - sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline 9c5a60e.. | wc -l`/;" Makefile + sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline f7a8284.. | wc -l`/;" Makefile # set 'ABCREV = default' to use abc/ as it is # @@ -165,7 +165,7 @@ bumpversion: # is just a symlink to your actual ABC working directory, as 'make mrproper' # will remove the 'abc' directory and you do not want to accidentally # delete your work on ABC.. -ABCREV = 2c1c83f +ABCREV = bb64142 ABCPULL = 1 ABCURL ?= https://github.com/YosysHQ/abc ABCMKARGS = CC="$(CXX)" CXX="$(CXX)" ABC_USE_LIBSTDCXX=1 ABC_USE_NAMESPACE=abc VERBOSE=$(Q) @@ -523,6 +523,7 @@ CXXFLAGS += -I$(GHDL_INCLUDE_DIR) -DYOSYS_ENABLE_GHDL LDLIBS += $(GHDL_LIB_DIR)/libghdl.a $(file <$(GHDL_LIB_DIR)/libghdl.link) endif +LDLIBS_VERIFIC = ifeq ($(ENABLE_VERIFIC),1) VERIFIC_DIR ?= /usr/local/src/verific_lib VERIFIC_COMPONENTS ?= verilog database util containers hier_tree @@ -548,9 +549,9 @@ CXXFLAGS += -DYOSYSHQ_VERIFIC_EXTENSIONS endif CXXFLAGS += $(patsubst %,-I$(VERIFIC_DIR)/%,$(VERIFIC_COMPONENTS)) -DYOSYS_ENABLE_VERIFIC ifeq ($(OS), Darwin) -LDLIBS += $(patsubst %,$(VERIFIC_DIR)/%/*-mac.a,$(VERIFIC_COMPONENTS)) -lz +LDLIBS_VERIFIC += $(foreach comp,$(patsubst %,$(VERIFIC_DIR)/%/*-mac.a,$(VERIFIC_COMPONENTS)),-Wl,-force_load $(comp)) -lz else -LDLIBS += $(patsubst %,$(VERIFIC_DIR)/%/*-linux.a,$(VERIFIC_COMPONENTS)) -lz +LDLIBS_VERIFIC += -Wl,--whole-archive $(patsubst %,$(VERIFIC_DIR)/%/*-linux.a,$(VERIFIC_COMPONENTS)) -Wl,--no-whole-archive -lz endif endif @@ -740,13 +741,13 @@ yosys.js: $(filter-out yosysjs-$(YOSYS_VER).zip,$(EXTRA_TARGETS)) endif $(PROGRAM_PREFIX)yosys$(EXE): $(OBJS) - $(P) $(LD) -o $(PROGRAM_PREFIX)yosys$(EXE) $(EXE_LDFLAGS) $(LDFLAGS) $(OBJS) $(LDLIBS) + $(P) $(LD) -o $(PROGRAM_PREFIX)yosys$(EXE) $(EXE_LDFLAGS) $(LDFLAGS) $(OBJS) $(LDLIBS) $(LDLIBS_VERIFIC) libyosys.so: $(filter-out kernel/driver.o,$(OBJS)) ifeq ($(OS), Darwin) - $(P) $(LD) -o libyosys.so -shared -Wl,-install_name,$(LIBDIR)/libyosys.so $(LDFLAGS) $^ $(LDLIBS) + $(P) $(LD) -o libyosys.so -shared -Wl,-install_name,$(LIBDIR)/libyosys.so $(LDFLAGS) $^ $(LDLIBS) $(LDLIBS_VERIFIC) else - $(P) $(LD) -o libyosys.so -shared -Wl,-soname,$(LIBDIR)/libyosys.so $(LDFLAGS) $^ $(LDLIBS) + $(P) $(LD) -o libyosys.so -shared -Wl,-soname,$(LIBDIR)/libyosys.so $(LDFLAGS) $^ $(LDLIBS) $(LDLIBS_VERIFIC) endif %.o: %.cc @@ -797,13 +798,13 @@ ifneq ($(ABCREV),default) $(Q) if test -d abc && test -d abc/.git && ! git -C abc diff-index --quiet HEAD; then \ echo 'REEBE: NOP pbagnvaf ybpny zbqvsvpngvbaf! Frg NOPERI=qrsnhyg va Lbflf Znxrsvyr!' | tr 'A-Za-z' 'N-ZA-Mn-za-m'; false; \ fi - $(Q) if test -d abc && ! test -d abc/.git && ! test "`cat abc/.gitcommit | cut -c1-7`" == "$(ABCREV)"; then \ + $(Q) if test -d abc && ! test -d abc/.git && ! test "`cat abc/.gitcommit | cut -c1-7`" = "$(ABCREV)"; then \ echo 'REEBE: Qbjaybnqrq NOP irefvbaf qbrf abg zngpu! Qbjaybnq sebz:' | tr 'A-Za-z' 'N-ZA-Mn-za-m'; echo $(ABCURL)/archive/$(ABCREV).tar.gz; false; \ fi # set a variable so the test fails if git fails to run - when comparing outputs directly, empty string would match empty string - $(Q) if test -d abc && ! test -d abc/.git && test "`cat abc/.gitcommit | cut -c1-7`" == "$(ABCREV)"; then \ + $(Q) if test -d abc && ! test -d abc/.git && test "`cat abc/.gitcommit | cut -c1-7`" = "$(ABCREV)"; then \ echo "Compiling local copy of ABC"; \ - elif ! (cd abc 2> /dev/null && rev="`git rev-parse $(ABCREV)`" && test "`git rev-parse HEAD`" == "$$rev"); then \ + elif ! (cd abc 2> /dev/null && rev="`git rev-parse $(ABCREV)`" && test "`git rev-parse HEAD`" = "$$rev"); then \ test $(ABCPULL) -ne 0 || { echo 'REEBE: NOP abg hc gb qngr naq NOPCHYY frg gb 0 va Znxrsvyr!' | tr 'A-Za-z' 'N-ZA-Mn-za-m'; exit 1; }; \ echo "Pulling ABC from $(ABCURL):"; set -x; \ test -d abc || git clone $(ABCURL) abc; \ diff --git a/backends/aiger/aiger.cc b/backends/aiger/aiger.cc index 689864153..bb804f230 100644 --- a/backends/aiger/aiger.cc +++ b/backends/aiger/aiger.cc @@ -736,6 +736,9 @@ struct AigerWriter auto sig_qy = cell->getPort(cell->type.in(ID($anyconst), ID($anyseq)) ? ID::Y : ID::Q); SigSpec sig = sigmap(sig_qy); + if (cell->get_bool_attribute(ID(clk2fflogic))) + sig_qy = cell->getPort(ID::D); // For a clk2fflogic $_FF_ the named signal is the D input not the Q output + for (int i = 0; i < GetSize(sig_qy); i++) { if (sig_qy[i].wire == nullptr || sig[i].wire == nullptr) continue; diff --git a/backends/btor/btor.cc b/backends/btor/btor.cc index 4c43e91e7..9cfd967e5 100644 --- a/backends/btor/btor.cc +++ b/backends/btor/btor.cc @@ -728,7 +728,10 @@ struct BtorWorker else btorf("%d state %d %s\n", nid, sid, log_id(symbol)); - ywmap_state(sig_q); + if (cell->get_bool_attribute(ID(clk2fflogic))) + ywmap_state(cell->getPort(ID::D)); // For a clk2fflogic FF the named signal is the D input not the Q output + else + ywmap_state(sig_q); if (nid_init_val >= 0) { int nid_init = next_nid++; diff --git a/backends/cxxrtl/cxxrtl.h b/backends/cxxrtl/cxxrtl.h index 073921cc4..5d0596f0d 100644 --- a/backends/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/cxxrtl.h @@ -1595,6 +1595,25 @@ value modfloor_ss(const value &a, const value &b) { return r; } +template +CXXRTL_ALWAYS_INLINE +value divfloor_uu(const value &a, const value &b) { + return divmod_uu(a, b).first; +} + +// Divfloor. Similar to above: returns q=a//b, where q has the sign of a*b and a=b*q+N. +// In other words, returns (truncating) a/b, except if a and b have different signs +// and there's non-zero remainder, subtract one more towards floor. +template +CXXRTL_ALWAYS_INLINE +value divfloor_ss(const value &a, const value &b) { + value q, r; + std::tie(q, r) = divmod_ss(a, b); + if ((b.is_neg() != a.is_neg()) && !r.is_zero()) + return sub_uu(q, value<1> { 1u }); + return q; + +} // Memory helper struct memory_index { diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index 62768bd33..1b13985ab 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -185,7 +185,7 @@ bool is_binary_cell(RTLIL::IdString type) 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), ID($modfloor)); + ID($add), ID($sub), ID($mul), ID($div), ID($mod), ID($modfloor), ID($divfloor)); } bool is_extending_cell(RTLIL::IdString type) diff --git a/backends/edif/edif.cc b/backends/edif/edif.cc index 7722d0c33..00fd7f54e 100644 --- a/backends/edif/edif.cc +++ b/backends/edif/edif.cc @@ -120,6 +120,9 @@ struct EdifBackend : public Backend { log(" sets the delimiting character for module port rename clauses to\n"); log(" parentheses, square brackets, or angle brackets.\n"); log("\n"); + log(" -lsbidx\n"); + log(" use index 0 for the LSB bit of a net or port instead of MSB.\n"); + log("\n"); log("Unfortunately there are different \"flavors\" of the EDIF file format. This\n"); log("command generates EDIF files for the Xilinx place&route tools. It might be\n"); log("necessary to make small modifications to this command when a different tool\n"); @@ -132,6 +135,7 @@ struct EdifBackend : public Backend { std::string top_module_name; bool port_rename = false; bool attr_properties = false; + bool lsbidx = false; std::map> lib_cell_ports; bool nogndvcc = false, gndvccy = false, keepmode = false; CellTypes ct(design); @@ -173,6 +177,10 @@ struct EdifBackend : public Backend { } continue; } + if (args[argidx] == "-lsbidx") { + lsbidx = true; + continue; + } break; } extra_args(f, filename, args, argidx); @@ -184,6 +192,14 @@ struct EdifBackend : public Backend { for (auto module : design->modules()) { + lib_cell_ports[module->name]; + + for (auto port : module->ports) + { + Wire *wire = module->wire(port); + lib_cell_ports[module->name][port] = std::max(lib_cell_ports[module->name][port], GetSize(wire)); + } + if (module->get_blackbox_attribute()) continue; @@ -200,7 +216,7 @@ struct EdifBackend : public Backend { if (design->module(cell->type) == nullptr || design->module(cell->type)->get_blackbox_attribute()) { lib_cell_ports[cell->type]; for (auto p : cell->connections()) - lib_cell_ports[cell->type][p.first] = GetSize(p.second); + lib_cell_ports[cell->type][p.first] = std::max(lib_cell_ports[cell->type][p.first], GetSize(p.second)); } } } @@ -437,7 +453,7 @@ struct EdifBackend : public Backend { *f << ")\n"; for (int i = 0; i < wire->width; i++) { RTLIL::SigSpec sig = sigmap(RTLIL::SigSpec(wire, i)); - net_join_db[sig].insert(make_pair(stringf("(portRef (member %s %d))", EDIF_REF(wire->name), GetSize(wire)-i-1), wire->port_input)); + net_join_db[sig].insert(make_pair(stringf("(portRef (member %s %d))", EDIF_REF(wire->name), lsbidx ? i : GetSize(wire)-i-1), wire->port_input)); } } } @@ -468,13 +484,13 @@ struct EdifBackend : public Backend { log_warning("Bit %d of cell port %s.%s.%s driven by %s will be left unconnected in EDIF output.\n", i, log_id(module), log_id(cell), log_id(p.first), log_signal(sig[i])); else { - int member_idx = GetSize(sig)-i-1; + int member_idx = lsbidx ? i : GetSize(sig)-i-1; auto m = design->module(cell->type); int width = sig.size(); if (m) { auto w = m->wire(p.first); if (w) { - member_idx = GetSize(w)-i-1; + member_idx = lsbidx ? i : GetSize(w)-i-1; width = GetSize(w); } } diff --git a/backends/smt2/smt2.cc b/backends/smt2/smt2.cc index 0ca3fbcac..5e63e6237 100644 --- a/backends/smt2/smt2.cc +++ b/backends/smt2/smt2.cc @@ -626,8 +626,9 @@ struct Smt2Worker } bool init_only = cell->type.in(ID($anyconst), ID($anyinit), ID($allconst)); + bool clk2fflogic = cell->type == ID($anyinit) && cell->get_bool_attribute(ID(clk2fflogic)); int smtoffset = 0; - for (auto chunk : cell->getPort(QY).chunks()) { + for (auto chunk : cell->getPort(clk2fflogic ? ID::D : QY).chunks()) { if (chunk.is_wire()) decls.push_back(witness_signal(init_only ? "init" : "seq", chunk.width, chunk.offset, "", idcounter, chunk.wire, smtoffset)); smtoffset += chunk.width; @@ -772,7 +773,7 @@ struct Smt2Worker int arrayid = idcounter++; memarrays[mem] = arrayid; - int abits = ceil_log2(mem->size); + int abits = max(1, ceil_log2(mem->size)); bool has_sync_wr = false; bool has_async_wr = false; @@ -1219,7 +1220,7 @@ struct Smt2Worker { int arrayid = memarrays.at(mem); - int abits = ceil_log2(mem->size);; + int abits = max(1, ceil_log2(mem->size)); bool has_sync_wr = false; bool has_async_wr = false; diff --git a/backends/smt2/smtbmc.py b/backends/smt2/smtbmc.py index 6b81740a2..02e15a1b5 100644 --- a/backends/smt2/smtbmc.py +++ b/backends/smt2/smtbmc.py @@ -174,6 +174,8 @@ def help(): further failed assertions. To output multiple traces covering all found failed assertions, the character '%' is replaced in all dump filenames with an increasing number. + In cover mode, don't stop when a cover trace contains a failed + assertion. --check-witness check that the used witness file contains sufficient @@ -1739,7 +1741,7 @@ elif covermode: smt_pop() smt.write("(define-fun covers_%d ((state |%s_s|)) (_ BitVec %d) (bvand (covers_%d state) #b%s))" % (coveridx, topmod, len(cover_desc), coveridx-1, cover_mask)) - if found_failed_assert: + if found_failed_assert and not keep_going: break if "1" not in cover_mask: diff --git a/backends/smt2/smtio.py b/backends/smt2/smtio.py index a73745896..8094747bc 100644 --- a/backends/smt2/smtio.py +++ b/backends/smt2/smtio.py @@ -768,7 +768,7 @@ class SmtIo: if self.timeinfo: i = 0 - s = "/-\|" + s = r"/-\|" count = 0 num_bs = 0 @@ -1171,7 +1171,7 @@ class MkVcd: def escape_name(self, name): name = re.sub(r"\[([0-9a-zA-Z_]*[a-zA-Z_][0-9a-zA-Z_]*)\]", r"<\1>", name) - if re.match("[\[\]]", name) and name[0] != "\\": + if re.match(r"[\[\]]", name) and name[0] != "\\": name = "\\" + name return name diff --git a/backends/smt2/witness.py b/backends/smt2/witness.py index 8d0cc8112..7d5a2469e 100644 --- a/backends/smt2/witness.py +++ b/backends/smt2/witness.py @@ -194,7 +194,7 @@ def aiw2yw(input, mapfile, output): values = WitnessValues() for i, v in enumerate(inline): - if v == "x" or outyw.t > 0 and i in aiger_map.init_inputs: + if outyw.t > 0 and i in aiger_map.init_inputs: continue try: diff --git a/docs/source/CHAPTER_CellLib.rst b/docs/source/CHAPTER_CellLib.rst index c5db434a6..c89040868 100644 --- a/docs/source/CHAPTER_CellLib.rst +++ b/docs/source/CHAPTER_CellLib.rst @@ -571,7 +571,7 @@ The ``$mem_v2`` cell has the following ports: signals for the read ports. ``\RD_DATA`` - This input is ``\RD_PORTS*\WIDTH`` bits wide, containing all data + This output is ``\RD_PORTS*\WIDTH`` bits wide, containing all data signals for the read ports. ``\RD_ARST`` diff --git a/docs/source/CHAPTER_Memorymap.rst b/docs/source/CHAPTER_Memorymap.rst new file mode 100644 index 000000000..cdc381eed --- /dev/null +++ b/docs/source/CHAPTER_Memorymap.rst @@ -0,0 +1,654 @@ +.. _chapter:memorymap: + +Memory mapping +============== + +Documentation for the Yosys ``memory_libmap`` memory mapper. Note that not all supported patterns +are included in this document, of particular note is that combinations of multiple patterns should +generally work. For example, `Write port with byte enables`_ could be used in conjunction with any +of the simple dual port (SDP) models. In general if a hardware memory definition does not support a +given configuration, additional logic will be instantiated to guarantee behaviour is consistent with +simulation. + +See also: `passes/memory/memlib.md `_ + +Additional notes +---------------- + +Memory kind selection +~~~~~~~~~~~~~~~~~~~~~ + +The memory inference code will automatically pick target memory primitive based on memory geometry +and features used. Depending on the target, there can be up to four memory primitive classes +available for selection: + +- FF RAM (aka logic): no hardware primitive used, memory lowered to a bunch of FFs and multiplexers + + - Can handle arbitrary number of write ports, as long as all write ports are in the same clock domain + - Can handle arbitrary number and kind of read ports + +- LUT RAM (aka distributed RAM): uses LUT storage as RAM + + - Supported on most FPGAs (with notable exception of ice40) + - Usually has one synchronous write port, one or more asynchronous read ports + - Small + - Will never be used for ROMs (lowering to plain LUTs is always better) + +- Block RAM: dedicated memory tiles + + - Supported on basically all FPGAs + - Supports only synchronous reads + - Two ports with separate clocks + - Usually supports true dual port (with notable exception of ice40 that only supports SDP) + - Usually supports asymmetric memories and per-byte write enables + - Several kilobits in size + +- Huge RAM: + + - Only supported on several targets: + + - Some Xilinx UltraScale devices (UltraRAM) + + - Two ports, both with mutually exclusive synchronous read and write + - Single clock + - Initial data must be all-0 + + - Some ice40 devices (SPRAM) + + - Single port with mutually exclusive synchronous read and write + - Does not support initial data + + - Nexus (large RAM) + + - Two ports, both with mutually exclusive synchronous read and write + - Single clock + + - Will not be automatically selected by memory inference code, needs explicit opt-in via + ram_style attribute + +In general, you can expect the automatic selection process to work roughly like this: + +- If any read port is asynchronous, only LUT RAM (or FF RAM) can be used. +- If there is more than one write port, only block RAM can be used, and this needs to be a + hardware-supported true dual port pattern + + - … unless all write ports are in the same clock domain, in which case FF RAM can also be used, + but this is generally not what you want for anything but really small memories + +- Otherwise, either FF RAM, LUT RAM, or block RAM will be used, depending on memory size + +This process can be overridden by attaching a ram_style attribute to the memory: + +- `(* ram_style = "logic" *)` selects FF RAM +- `(* ram_style = "distributed" *)` selects LUT RAM +- `(* ram_style = "block" *)` selects block RAM +- `(* ram_style = "huge" *)` selects huge RAM + +It is an error if this override cannot be realized for the given target. + +Many alternate spellings of the attribute are also accepted, for compatibility with other software. + +Initial data +~~~~~~~~~~~~ + +Most FPGA targets support initializing all kinds of memory to user-provided values. If explicit +initialization is not used the initial memory value is undefined. Initial data can be provided by +either initial statements writing memory cells one by one of ``$readmemh`` or ``$readmemb`` system +tasks. For an example pattern, see `Synchronous read port with initial value`_. + +Write port with byte enables +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Byte enables can be used with any supported pattern +- To ensure that multiple writes will be merged into one port, they need to have disjoint bit + ranges, have the same address, and the same clock +- Any write enable granularity will be accepted (down to per-bit write enables), but using smaller + granularity than natively supported by the target is very likely to be inefficient (eg. using + 4-bit bytes on ECP5 will result in either padding the bytes with 5 dummy bits to native 9-bit + units or splitting the RAM into two block RAMs) + +.. code:: verilog + + reg [31 : 0] mem [2**ADDR_WIDTH - 1 : 0]; + + always @(posedge clk) begin + if (write_enable[0]) + mem[write_addr][7:0] <= write_data[7:0]; + if (write_enable[1]) + mem[write_addr][15:8] <= write_data[15:8]; + if (write_enable[2]) + mem[write_addr][23:16] <= write_data[23:16]; + if (write_enable[3]) + mem[write_addr][31:24] <= write_data[31:24]; + if (read_enable) + read_data <= mem[read_addr]; + end + +Simple dual port (SDP) memory patterns +-------------------------------------- + +Asynchronous-read SDP +~~~~~~~~~~~~~~~~~~~~~ + +- This will result in LUT RAM on supported targets + +.. code:: verilog + + reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; + always @(posedge clk) + if (write_enable) + mem[write_addr] <= write_data; + assign read_data = mem[read_addr]; + +Synchronous SDP with clock domain crossing +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Will result in block RAM or LUT RAM depending on size +- No behavior guarantees in case of simultaneous read and write to the same address + +.. code:: verilog + + reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; + + always @(posedge write_clk) begin + if (write_enable) + mem[write_addr] <= write_data; + end + + always @(posedge read_clk) begin + if (read_enable) + read_data <= mem[read_addr]; + end + +Synchronous SDP read first +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- The read and write parts can be in the same or different processes. +- Will result in block RAM or LUT RAM depending on size +- As long as the same clock is used for both, yosys will ensure read-first behavior. This may + require extra circuitry on some targets for block RAM. If this is not necessary, use one of the + patterns below. + +.. code:: verilog + + reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; + + always @(posedge clk) begin + if (write_enable) + mem[write_addr] <= write_data; + if (read_enable) + read_data <= mem[read_addr]; + end + +Synchronous SDP with undefined collision behavior +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Like above, but the read value is undefined when read and write ports target the same address in + the same cycle + +.. code:: verilog + + reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; + + always @(posedge clk) begin + if (write_enable) + mem[write_addr] <= write_data; + + if (read_enable) begin + read_data <= mem[read_addr]; + + // 👇 this if block 👇 + if (write_enable && read_addr == write_addr) + read_data <= 'x; + end + end + +- Or below, using the no_rw_check attribute + +.. code:: verilog + + (* no_rw_check *) + reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; + + always @(posedge clk) begin + if (write_enable) + mem[write_addr] <= write_data; + + if (read_enable) + read_data <= mem[read_addr]; + end + +Synchronous SDP with write-first behavior +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Will result in block RAM or LUT RAM depending on size +- May use additional circuitry for block RAM if write-first is not natively supported. Will always + use additional circuitry for LUT RAM. + +.. code:: verilog + + reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; + + always @(posedge clk) begin + if (write_enable) + mem[write_addr] <= write_data; + + if (read_enable) begin + read_data <= mem[read_addr]; + if (write_enable && read_addr == write_addr) + read_data <= write_data; + end + end + +Synchronous SDP with write-first behavior (alternate pattern) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- This pattern is supported for compatibility, but is much less flexible than the above + +.. code:: verilog + + reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; + + always @(posedge clk) begin + if (write_enable) + mem[write_addr] <= write_data; + read_addr_reg <= read_addr; + end + + assign read_data = mem[read_addr_reg]; + +Single-port RAM memory patterns +------------------------------- + +Asynchronous-read single-port RAM +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Will result in single-port LUT RAM on supported targets + +.. code:: verilog + + reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; + always @(posedge clk) + if (write_enable) + mem[addr] <= write_data; + assign read_data = mem[addr]; + +Synchronous single-port RAM with mutually exclusive read/write +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Will result in single-port block RAM or LUT RAM depending on size +- This is the correct pattern to infer ice40 SPRAM (with manual ram_style selection) +- On targets that don't support read/write block RAM ports (eg. ice40), will result in SDP block RAM instead +- For block RAM, will use "NO_CHANGE" mode if available + +.. code:: verilog + + reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; + + always @(posedge clk) begin + if (write_enable) + mem[addr] <= write_data; + else if (read_enable) + read_data <= mem[addr]; + end + +Synchronous single-port RAM with read-first behavior +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Will only result in single-port block RAM when read-first behavior is natively supported; + otherwise, SDP RAM with additional circuitry will be used +- Many targets (Xilinx, ECP5, …) can only natively support read-first/write-first single-port RAM + (or TDP RAM) where the write_enable signal implies the read_enable signal (ie. can never write + without reading). The memory inference code will run a simple SAT solver on the control signals to + determine if this is the case, and insert emulation circuitry if it cannot be easily proven. + +.. code:: verilog + + reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; + + always @(posedge clk) begin + if (write_enable) + mem[addr] <= write_data; + if (read_enable) + read_data <= mem[addr]; + end + +Synchronous single-port RAM with write-first behavior +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Will result in single-port block RAM or LUT RAM when supported +- Block RAMs will require extra circuitry if write-first behavior not natively supported + +.. code:: verilog + + reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; + + always @(posedge clk) begin + if (write_enable) + mem[addr] <= write_data; + if (read_enable) + if (write_enable) + read_data <= write_data; + else + read_data <= mem[addr]; + end + +Synchronous read port with initial value +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Initial read port values can be combined with any other supported pattern +- If block RAM is used and initial read port values are not natively supported by the target, small + emulation circuit will be inserted + +.. code:: verilog + + reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; + reg [DATA_WIDTH - 1 : 0] read_data; + initial read_data = 'h1234; + + always @(posedge clk) begin + if (write_enable) + mem[write_addr] <= write_data; + if (read_enable) + read_data <= mem[read_addr]; + end + +Read register reset patterns +---------------------------- + +Resets can be combined with any other supported pattern (except that synchronous reset and +asynchronous reset cannot both be used on a single read port). If block RAM is used and the +selected reset (synchronous or asynchronous) is used but not natively supported by the target, small +emulation circuitry will be inserted. + +Synchronous reset, reset priority over enable +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: verilog + + reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; + + always @(posedge clk) begin + if (write_enable) + mem[write_addr] <= write_data; + + if (read_reset) + read_data <= {sval}; + else if (read_enable) + read_data <= mem[read_addr]; + end + +Synchronous reset, enable priority over reset +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: verilog + + reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; + + always @(posedge clk) begin + if (write_enable) + mem[write_addr] <= write_data; + if (read_enable) + if (read_reset) + read_data <= 'h1234; + else + read_data <= mem[read_addr]; + end + +Synchronous read port with asynchronous reset +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: verilog + + reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; + + always @(posedge clk) begin + if (write_enable) + mem[write_addr] <= write_data; + end + + always @(posedge clk, posedge reset_read) begin + if (reset_read) + read_data <= 'h1234; + else if (read_enable) + read_data <= mem[read_addr]; + end + +Asymmetric memory patterns +-------------------------- + +To construct an asymmetric memory (memory with read/write ports of differing widths): + +- Declare the memory with the width of the narrowest intended port +- Split all wide ports into multiple narrow ports +- To ensure the wide ports will be correctly merged: + + - For the address, use a concatenation of actual address in the high bits and a constant in the + low bits + - Ensure the actual address is identical for all ports belonging to the wide port + - Ensure that clock is identical + - For read ports, ensure that enable/reset signals are identical (for write ports, the enable + signal may vary — this will result in using the byte enable functionality) + +Asymmetric memory is supported on all targets, but may require emulation circuitry where not +natively supported. Note that when the memory is larger than the underlying block RAM primitive, +hardware asymmetric memory support is likely not to be used even if present as it is more expensive. + +Wide synchronous read port +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: verilog + + reg [7:0] mem [0:255]; + wire [7:0] write_addr; + wire [5:0] read_addr; + wire [7:0] write_data; + reg [31:0] read_data; + + always @(posedge clk) begin + if (write_enable) + mem[write_addr] <= write_data; + if (read_enable) begin + read_data[7:0] <= mem[{read_addr, 2'b00}]; + read_data[15:8] <= mem[{read_addr, 2'b01}]; + read_data[23:16] <= mem[{read_addr, 2'b10}]; + read_data[31:24] <= mem[{read_addr, 2'b11}]; + end + end + +Wide asynchronous read port +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Note: the only target natively supporting this pattern is Xilinx UltraScale + +.. code:: verilog + + reg [7:0] mem [0:511]; + wire [8:0] write_addr; + wire [5:0] read_addr; + wire [7:0] write_data; + wire [63:0] read_data; + + always @(posedge clk) begin + if (write_enable) + mem[write_addr] <= write_data; + end + + assign read_data[7:0] = mem[{read_addr, 3'b000}]; + assign read_data[15:8] = mem[{read_addr, 3'b001}]; + assign read_data[23:16] = mem[{read_addr, 3'b010}]; + assign read_data[31:24] = mem[{read_addr, 3'b011}]; + assign read_data[39:32] = mem[{read_addr, 3'b100}]; + assign read_data[47:40] = mem[{read_addr, 3'b101}]; + assign read_data[55:48] = mem[{read_addr, 3'b110}]; + assign read_data[63:56] = mem[{read_addr, 3'b111}]; + +Wide write port +~~~~~~~~~~~~~~~ + +.. code:: verilog + + reg [7:0] mem [0:255]; + wire [5:0] write_addr; + wire [7:0] read_addr; + wire [31:0] write_data; + reg [7:0] read_data; + + always @(posedge clk) begin + if (write_enable[0]) + mem[{write_addr, 2'b00}] <= write_data[7:0]; + if (write_enable[1]) + mem[{write_addr, 2'b01}] <= write_data[15:8]; + if (write_enable[2]) + mem[{write_addr, 2'b10}] <= write_data[23:16]; + if (write_enable[3]) + mem[{write_addr, 2'b11}] <= write_data[31:24]; + if (read_enable) + read_data <= mem[read_addr]; + end + +True dual port (TDP) patterns +----------------------------- + +- Many different variations of true dual port memory can be created by combining two single-port RAM + patterns on the same memory +- When TDP memory is used, memory inference code has much less maneuver room to create requested + semantics compared to individual single-port patterns (which can end up lowered to SDP memory + where necessary) — supported patterns depend strongly on the target +- In particular, when both ports have the same clock, it's likely that "undefined collision" mode + needs to be manually selected to enable TDP memory inference +- The examples below are non-exhaustive — many more combinations of port types are possible +- Note: if two write ports are in the same process, this defines a priority relation between them + (if both ports are active in the same clock, the later one wins). On almost all targets, this will + result in a bit of extra circuitry to ensure the priority semantics. If this is not what you want, + put them in separate processes. + + - Priority is not supported when using the verific front end and any priority semantics are ignored. + +TDP with different clocks, exclusive read/write +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: verilog + + reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; + + always @(posedge clk_a) begin + if (write_enable_a) + mem[addr_a] <= write_data_a; + else if (read_enable_a) + read_data_a <= mem[addr_a]; + end + + always @(posedge clk_b) begin + if (write_enable_b) + mem[addr_b] <= write_data_b; + else if (read_enable_b) + read_data_b <= mem[addr_b]; + end + +TDP with same clock, read-first behavior +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- This requires hardware inter-port read-first behavior, and will only work on some targets (Xilinx, Nexus) + +.. code:: verilog + + reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; + + always @(posedge clk) begin + if (write_enable_a) + mem[addr_a] <= write_data_a; + if (read_enable_a) + read_data_a <= mem[addr_a]; + end + + always @(posedge clk) begin + if (write_enable_b) + mem[addr_b] <= write_data_b; + if (read_enable_b) + read_data_b <= mem[addr_b]; + end + +TDP with multiple read ports +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- The combination of a single write port with an arbitrary amount of read ports is supported on all + targets — if a multi-read port primitive is available (like Xilinx RAM64M), it'll be used as + appropriate. Otherwise, the memory will be automatically split into multiple primitives. + +.. code:: verilog + + reg [31:0] mem [0:31]; + + always @(posedge clk) begin + if (write_enable) + mem[write_addr] <= write_data; + end + + assign read_data_a = mem[read_addr_a]; + assign read_data_b = mem[read_addr_b]; + assign read_data_c = mem[read_addr_c]; + +Not yet supported patterns +-------------------------- + +Synchronous SDP with write-first behavior via blocking assignments +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Would require modifications to the Yosys Verilog frontend. +- Use `Synchronous SDP with write-first behavior`_ instead + +.. code:: verilog + + reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; + + always @(posedge clk) begin + if (write_enable) + mem[write_addr] = write_data; + + if (read_enable) + read_data <= mem[read_addr]; + end + +Asymmetric memories via part selection +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Would require major changes to the Verilog frontend. +- Build wide ports out of narrow ports instead (see `Wide synchronous read port`_) + +.. code:: verilog + + reg [31:0] mem [2**ADDR_WIDTH - 1 : 0]; + + wire [1:0] byte_lane; + wire [7:0] write_data; + + always @(posedge clk) begin + if (write_enable) + mem[write_addr][byte_lane * 8 +: 8] <= write_data; + + if (read_enable) + read_data <= mem[read_addr]; + end + + +Undesired patterns +------------------ + +Asynchronous writes +~~~~~~~~~~~~~~~~~~~ + +- Not supported in modern FPGAs +- Not supported in yosys code anyhow + +.. code:: verilog + + reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; + + always @* begin + if (write_enable) + mem[write_addr] = write_data; + end + + assign read_data = mem[read_addr]; + diff --git a/docs/source/index.rst b/docs/source/index.rst index fb8643072..111aea873 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -42,6 +42,7 @@ Yosys manual CHAPTER_Verilog.rst CHAPTER_Optimize.rst CHAPTER_Techmap.rst + CHAPTER_Memorymap.rst CHAPTER_Eval.rst .. raw:: latex diff --git a/frontends/verific/verific.cc b/frontends/verific/verific.cc index 5d93954a7..0dd785ec3 100644 --- a/frontends/verific/verific.cc +++ b/frontends/verific/verific.cc @@ -112,15 +112,26 @@ void msg_func(msg_type_t msg_type, const char *message_id, linefile_type linefil string message = linefile ? stringf("%s:%d: ", LineFile::GetFileName(linefile), LineFile::GetLineNo(linefile)) : ""; message += vstringf(msg, args); - if (msg_type == VERIFIC_ERROR || msg_type == VERIFIC_WARNING || msg_type == VERIFIC_PROGRAM_ERROR) - log_warning_noprefix("%s%s\n", message_prefix.c_str(), message.c_str()); - else - log("%s%s\n", message_prefix.c_str(), message.c_str()); - + if (log_verific_callback) { + string full_message = stringf("%s%s\n", message_prefix.c_str(), message.c_str()); + log_verific_callback(int(msg_type), message_id, LineFile::GetFileName(linefile), LineFile::GetLineNo(linefile), full_message.c_str()); + } else { + if (msg_type == VERIFIC_ERROR || msg_type == VERIFIC_WARNING || msg_type == VERIFIC_PROGRAM_ERROR) + log_warning_noprefix("%s%s\n", message_prefix.c_str(), message.c_str()); + else + log("%s%s\n", message_prefix.c_str(), message.c_str()); + } if (verific_error_msg.empty() && (msg_type == VERIFIC_ERROR || msg_type == VERIFIC_PROGRAM_ERROR)) verific_error_msg = message; } +void set_verific_logging(void (*cb)(int msg_type, const char *message_id, const char* file_path, unsigned int line_no, const char *msg)) +{ + Message::SetConsoleOutput(0); + Message::RegisterCallBackMsg(msg_func); + log_verific_callback = cb; +} + string get_full_netlist_name(Netlist *nl) { if (nl->NumOfRefs() == 1) { @@ -1638,6 +1649,7 @@ void VerificImporter::import_netlist(RTLIL::Design *design, Netlist *nl, std::ma cell->parameters[ID::TRANSPARENT] = false; cell->parameters[ID::ABITS] = GetSize(addr); cell->parameters[ID::WIDTH] = GetSize(data); + import_attributes(cell->attributes, inst); cell->setPort(ID::CLK, RTLIL::State::Sx); cell->setPort(ID::EN, RTLIL::State::Sx); cell->setPort(ID::ADDR, addr); @@ -1667,6 +1679,7 @@ void VerificImporter::import_netlist(RTLIL::Design *design, Netlist *nl, std::ma cell->parameters[ID::PRIORITY] = 0; cell->parameters[ID::ABITS] = GetSize(addr); cell->parameters[ID::WIDTH] = GetSize(data); + import_attributes(cell->attributes, inst); cell->setPort(ID::EN, RTLIL::SigSpec(net_map_at(inst->GetControl())).repeat(GetSize(data))); cell->setPort(ID::CLK, RTLIL::State::S0); cell->setPort(ID::ADDR, addr); @@ -1994,7 +2007,10 @@ void VerificImporter::import_netlist(RTLIL::Design *design, Netlist *nl, std::ma initval[i] = State::Sx; } - if (initval.is_fully_undef()) + if (wire->port_input) { + wire->attributes[ID::defaultvalue] = Const(initval); + wire->attributes.erase(ID::init); + } else if (initval.is_fully_undef()) wire->attributes.erase(ID::init); } } diff --git a/kernel/log.cc b/kernel/log.cc index 75a1ffb45..985165a97 100644 --- a/kernel/log.cc +++ b/kernel/log.cc @@ -59,6 +59,7 @@ bool log_quiet_warnings = false; int log_verbose_level; string log_last_error; void (*log_error_atexit)() = NULL; +void (*log_verific_callback)(int msg_type, const char *message_id, const char* file_path, unsigned int line_no, const char *msg) = NULL; int log_make_debug = 0; int log_force_debug = 0; diff --git a/kernel/log.h b/kernel/log.h index 3a6ec8758..3e4c2edfd 100644 --- a/kernel/log.h +++ b/kernel/log.h @@ -130,6 +130,9 @@ void log_header(RTLIL::Design *design, const char *format, ...) YS_ATTRIBUTE(for void log_warning(const char *format, ...) YS_ATTRIBUTE(format(printf, 1, 2)); void log_experimental(const char *format, ...) YS_ATTRIBUTE(format(printf, 1, 2)); +void set_verific_logging(void (*cb)(int msg_type, const char *message_id, const char* file_path, unsigned int line_no, const char *msg)); +extern void (*log_verific_callback)(int msg_type, const char *message_id, const char* file_path, unsigned int line_no, const char *msg); + // Log with filename to report a problem in a source file. void log_file_warning(const std::string &filename, int lineno, const char *format, ...) YS_ATTRIBUTE(format(printf, 3, 4)); void log_file_info(const std::string &filename, int lineno, const char *format, ...) YS_ATTRIBUTE(format(printf, 3, 4)); diff --git a/misc/py_wrap_generator.py b/misc/py_wrap_generator.py index 4d9a60113..7fe78e03a 100644 --- a/misc/py_wrap_generator.py +++ b/misc/py_wrap_generator.py @@ -178,6 +178,8 @@ class WType: t.cont = None t.attr_type = attr_types.default if str_def.find("<") != -1:# and str_def.find("<") < str_def.find(" "): + str_def = str_def.replace("const ", "") + candidate = WContainer.from_string(str_def, containing_file, line_number) if candidate == None: return None @@ -203,8 +205,12 @@ class WType: prefix = "" + if str.startswith(str_def, "const "): + if "char_p" in str_def: + prefix = "const " + str_def = str_def[6:] if str.startswith(str_def, "unsigned "): - prefix = "unsigned " + prefix = "unsigned " + prefix str_def = str_def[9:] while str.startswith(str_def, "long "): prefix= "long " + prefix @@ -1285,7 +1291,7 @@ class WFunction: prefix = "" i = 0 for part in parts: - if part in ["unsigned", "long", "short"]: + if part in ["unsigned", "long", "short", "const"]: prefix += part + " " i += 1 else: @@ -1361,10 +1367,17 @@ class WFunction: func.args.append(parsed) return func + @property + def mangled_name(self): + mangled_typename = lambda code: code.replace("::", "_").replace("<","_").replace(">","_") \ + .replace(" ","").replace("*","").replace(",","") + + return self.name + "".join( + f"__{mangled_typename(arg.wtype.gen_text_cpp())}" for arg in self.args + ) + def gen_alias(self): - self.alias = self.name - for arg in self.args: - self.alias += "__" + arg.wtype.gen_text_cpp().replace("::", "_").replace("<","_").replace(">","_").replace(" ","").replace("*","").replace(",","") + self.alias = self.mangled_name def gen_decl(self): if self.duplicate: @@ -2190,12 +2203,15 @@ def clean_duplicates(): for fun in class_.found_funs: if fun.gen_decl_hash_py() in known_decls: debug("Multiple declarations of " + fun.gen_decl_hash_py(),3) + other = known_decls[fun.gen_decl_hash_py()] - other.gen_alias() - fun.gen_alias() - if fun.gen_decl_hash_py() == other.gen_decl_hash_py(): + if fun.mangled_name == other.mangled_name: fun.duplicate = True debug("Disabled \"" + fun.gen_decl_hash_py() + "\"", 3) + continue + + other.gen_alias() + fun.gen_alias() else: known_decls[fun.gen_decl_hash_py()] = fun known_decls = [] diff --git a/passes/cmds/check.cc b/passes/cmds/check.cc index ee0f0a58f..d1c83f04d 100644 --- a/passes/cmds/check.cc +++ b/passes/cmds/check.cc @@ -112,11 +112,10 @@ struct CheckPass : public Pass { for (size_t i = 0; i < all_cases.size(); i++) { for (auto action : all_cases[i]->actions) { for (auto bit : sigmap(action.first)) - if (bit.wire) { - wire_drivers[bit].push_back( - stringf("action %s <= %s (case rule) in process %s", - log_signal(action.first), log_signal(action.second), log_id(proc_it.first))); - } + wire_drivers[bit].push_back( + stringf("action %s <= %s (case rule) in process %s", + log_signal(action.first), log_signal(action.second), log_id(proc_it.first))); + for (auto bit : sigmap(action.second)) if (bit.wire) used_wires.insert(bit); } @@ -134,10 +133,9 @@ struct CheckPass : public Pass { if (bit.wire) used_wires.insert(bit); for (auto action : sync->actions) { for (auto bit : sigmap(action.first)) - if (bit.wire) - wire_drivers[bit].push_back( - stringf("action %s <= %s (sync rule) in process %s", - log_signal(action.first), log_signal(action.second), log_id(proc_it.first))); + wire_drivers[bit].push_back( + stringf("action %s <= %s (sync rule) in process %s", + log_signal(action.first), log_signal(action.second), log_id(proc_it.first))); for (auto bit : sigmap(action.second)) if (bit.wire) used_wires.insert(bit); } @@ -176,7 +174,8 @@ struct CheckPass : public Pass { if (logic_cell) topo.edge(stringf("cell %s (%s)", log_id(cell), log_id(cell->type)), stringf("wire %s", log_signal(sig[i]))); - if (sig[i].wire) + + if (sig[i].wire || !cell->input(conn.first)) wire_drivers[sig[i]].push_back(stringf("port %s[%d] of cell %s (%s)", log_id(conn.first), i, log_id(cell), log_id(cell->type))); } @@ -192,7 +191,8 @@ struct CheckPass : public Pass { if (wire->port_input) { SigSpec sig = sigmap(wire); for (int i = 0; i < GetSize(sig); i++) - wire_drivers[sig[i]].push_back(stringf("module input %s[%d]", log_id(wire), i)); + if (sig[i].wire || !wire->port_output) + wire_drivers[sig[i]].push_back(stringf("module input %s[%d]", log_id(wire), i)); } if (wire->port_output) for (auto bit : sigmap(wire)) @@ -212,6 +212,15 @@ struct CheckPass : public Pass { } } + for (auto state : {State::S0, State::S1, State::Sx}) + if (wire_drivers.count(state)) { + string message = stringf("Drivers conflicting with a constant %s driver:\n", log_signal(state)); + for (auto str : wire_drivers[state]) + message += stringf(" %s\n", str.c_str()); + log_warning("%s", message.c_str()); + counter++; + } + for (auto it : wire_drivers) if (wire_drivers_count[it.first] > 1) { string message = stringf("multiple conflicting drivers for %s.%s:\n", log_id(module), log_signal(it.first)); diff --git a/passes/cmds/rename.cc b/passes/cmds/rename.cc index 6bd317ed0..da4ba2f17 100644 --- a/passes/cmds/rename.cc +++ b/passes/cmds/rename.cc @@ -139,7 +139,12 @@ static bool rename_witness(RTLIL::Design *design, dict &ca if (cell->type.in(ID($anyconst), ID($anyseq), ID($anyinit), ID($allconst), ID($allseq))) { has_witness_signals = true; - auto QY = cell->type == ID($anyinit) ? ID::Q : ID::Y; + IdString QY; + bool clk2fflogic = false; + if (cell->type == ID($anyinit)) + QY = (clk2fflogic = cell->get_bool_attribute(ID(clk2fflogic))) ? ID::D : ID::Q; + else + QY = ID::Y; auto sig_out = cell->getPort(QY); for (auto chunk : sig_out.chunks()) { @@ -151,7 +156,10 @@ static bool rename_witness(RTLIL::Design *design, dict &ca auto new_id = module->uniquify("\\_witness_." + name); auto new_wire = module->addWire(new_id, GetSize(sig_out)); new_wire->set_hdlname_attribute({ "_witness_", strstr(new_id.c_str(), ".") + 1 }); - module->connect({sig_out, new_wire}); + if (clk2fflogic) + module->connect({new_wire, sig_out}); + else + module->connect({sig_out, new_wire}); cell->setPort(QY, new_wire); break; } diff --git a/passes/cmds/show.cc b/passes/cmds/show.cc index dd7de8273..0dc5c452c 100644 --- a/passes/cmds/show.cc +++ b/passes/cmds/show.cc @@ -84,7 +84,7 @@ struct ShowWorker std::string nextColor() { if (currentColor == 0) - return "color=\"black\""; + return "color=\"black\", fontcolor=\"black\""; return stringf("colorscheme=\"dark28\", color=\"%d\", fontcolor=\"%d\"", currentColor%8+1, currentColor%8+1); } @@ -97,19 +97,16 @@ struct ShowWorker std::string nextColor(RTLIL::SigSpec sig, std::string defaultColor) { - sig.sort_and_unify(); - for (auto &c : sig.chunks()) { - 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()); - } + std::string color = findColor(sig); + if (!color.empty()) return color; return defaultColor; } std::string nextColor(const RTLIL::SigSig &conn, std::string defaultColor) { - return nextColor(conn.first, nextColor(conn.second, defaultColor)); + std::string color = findColor(conn); + if (!color.empty()) return color; + return defaultColor; } std::string nextColor(const RTLIL::SigSpec &sig) @@ -131,12 +128,28 @@ struct ShowWorker return stringf("style=\"setlinewidth(3)\", label=\"<%d>\"", bits); } - const char *findColor(std::string member_name) + std::string findColor(RTLIL::SigSpec sig) + { + sig.sort_and_unify(); + for (auto &c : sig.chunks()) { + if (c.wire != nullptr) + return findColor(c.wire->name); + } + return ""; + } + + std::string findColor(const RTLIL::SigSig &conn) + { + std::string firstColor = findColor(conn.first); + if (findColor(conn.second) == firstColor) return firstColor; + return ""; + } + + std::string findColor(IdString member_name) { for (auto &s : color_selections) if (s.second.selected_member(module->name, member_name)) { - dot_escape_store.push_back(stringf(", color=\"%s\"", s.first.c_str())); - return dot_escape_store.back().c_str(); + return stringf("color=\"%s\", fontcolor=\"%s\"", s.first.c_str(), s.first.c_str()); } RTLIL::Const colorattr_value; @@ -155,8 +168,7 @@ struct ShowWorker colorattr_cache[colorattr_value] = (next_id % 8) + 1; } - dot_escape_store.push_back(stringf(", colorscheme=\"dark28\", color=\"%d\", fontcolor=\"%d\"", colorattr_cache.at(colorattr_value), colorattr_cache.at(colorattr_value))); - return dot_escape_store.back().c_str(); + return stringf("colorscheme=\"dark28\", color=\"%d\", fontcolor=\"%d\"", colorattr_cache.at(colorattr_value), colorattr_cache.at(colorattr_value)); } const char *findLabel(std::string member_name) @@ -189,6 +201,12 @@ struct ShowWorker if (id[0] == '\\') id = id.substr(1); + // TODO: optionally include autoname + print correspondence in case of ambiguity + size_t max_label_len = abbreviateIds ? 256 : 16384; + if (id.size() > max_label_len) { + id = id.substr(0,max_label_len-3) + "..."; + } + std::string str; for (char ch : id) { if (ch == '\\') { @@ -196,7 +214,7 @@ struct ShowWorker str += "╲"; continue; } - if (ch == '"') + if (ch == '"' || ch == '<' || ch == '>') str += "\\"; str += ch; } @@ -317,7 +335,7 @@ struct ShowWorker } code += stringf("x%d [ shape=record, style=rounded, label=\"", dot_idx) \ - + join_label_pieces(label_pieces) + "\" ];\n"; + + join_label_pieces(label_pieces) + stringf("\", %s ];\n", nextColor(sig).c_str()); if (!port.empty()) { currentColor = xorshift32(currentColor); @@ -414,9 +432,9 @@ struct ShowWorker if (wire->port_input || wire->port_output) shape = "octagon"; if (wire->name.isPublic()) { - fprintf(f, "n%d [ shape=%s, label=\"%s\", %s, fontcolor=\"black\" ];\n", + fprintf(f, "n%d [ shape=%s, label=\"%s\", %s ];\n", id2num(wire->name), shape, findLabel(wire->name.str()), - nextColor(RTLIL::SigSpec(wire), "color=\"black\"").c_str()); + nextColor(RTLIL::SigSpec(wire), "color=\"black\", fontcolor=\"black\"").c_str()); if (wire->port_input) all_sources.insert(stringf("n%d", id2num(wire->name))); else if (wire->port_output) @@ -481,11 +499,11 @@ struct ShowWorker #ifdef CLUSTER_CELLS_AND_PORTBOXES if (!code.empty()) fprintf(f, "subgraph cluster_c%d {\nc%d [ shape=record, label=\"%s\"%s ];\n%s}\n", - id2num(cell->name), id2num(cell->name), label_string.c_str(), findColor(cell->name), code.c_str()); + id2num(cell->name), id2num(cell->name), label_string.c_str(), color.c_str(), code.c_str()); else #endif - fprintf(f, "c%d [ shape=record, label=\"%s\"%s ];\n%s", - id2num(cell->name), label_string.c_str(), findColor(cell->name.str()), code.c_str()); + fprintf(f, "c%d [ shape=record, label=\"%s\", %s ];\n%s", + id2num(cell->name), label_string.c_str(), findColor(cell->name).c_str(), code.c_str()); } for (auto &it : module->processes) @@ -555,9 +573,9 @@ struct ShowWorker } else if (right_node[0] == 'x') { net_conn_map[left_node].out.insert({right_node, GetSize(conn.first)}); } else { - net_conn_map[right_node].in.insert({stringf("x%d:e", single_idx_count), GetSize(conn.first)}); - net_conn_map[left_node].out.insert({stringf("x%d:w", single_idx_count), GetSize(conn.first)}); - fprintf(f, "x%d [shape=box, style=rounded, label=\"BUF\"];\n", single_idx_count++); + net_conn_map[right_node].in.insert({stringf("x%d", single_idx_count), GetSize(conn.first)}); + net_conn_map[left_node].out.insert({stringf("x%d", single_idx_count), GetSize(conn.first)}); + fprintf(f, "x%d [shape=box, style=rounded, label=\"BUF\", %s];\n", single_idx_count++, findColor(conn).c_str()); } } } @@ -643,6 +661,7 @@ struct ShowPass : public Pass { log(" -viewer \n"); log(" Run the specified command with the graphics file as parameter.\n"); log(" On Windows, this pauses yosys until the viewer exits.\n"); + log(" Use \"-viewer none\" to not run any command.\n"); log("\n"); log(" -format \n"); log(" Generate a graphics file in the specified format. Use 'dot' to just\n"); @@ -903,28 +922,30 @@ struct ShowPass : public Pass { #if defined(YOSYS_DISABLE_SPAWN) log_assert(viewer_exe.empty() && !format.empty()); #else - if (!viewer_exe.empty()) { - #ifdef _WIN32 - // system()/cmd.exe does not understand single quotes nor - // background tasks on Windows. So we have to pause yosys - // until the viewer exits. - std::string cmd = stringf("%s \"%s\"", viewer_exe.c_str(), out_file.c_str()); - #else - std::string cmd = stringf("%s '%s' %s", viewer_exe.c_str(), out_file.c_str(), background.c_str()); - #endif - log("Exec: %s\n", cmd.c_str()); - if (run_command(cmd) != 0) - log_cmd_error("Shell command failed!\n"); - } else - if (format.empty()) { - #ifdef __APPLE__ - std::string cmd = stringf("ps -fu %d | grep -q '[ ]%s' || xdot '%s' %s", getuid(), dot_file.c_str(), dot_file.c_str(), background.c_str()); - #else - std::string cmd = stringf("{ test -f '%s.pid' && fuser -s '%s.pid' 2> /dev/null; } || ( echo $$ >&3; exec xdot '%s'; ) 3> '%s.pid' %s", dot_file.c_str(), dot_file.c_str(), dot_file.c_str(), dot_file.c_str(), background.c_str()); - #endif - log("Exec: %s\n", cmd.c_str()); - if (run_command(cmd) != 0) - log_cmd_error("Shell command failed!\n"); + if (viewer_exe != "none") { + if (!viewer_exe.empty()) { + #ifdef _WIN32 + // system()/cmd.exe does not understand single quotes nor + // background tasks on Windows. So we have to pause yosys + // until the viewer exits. + std::string cmd = stringf("%s \"%s\"", viewer_exe.c_str(), out_file.c_str()); + #else + std::string cmd = stringf("%s '%s' %s", viewer_exe.c_str(), out_file.c_str(), background.c_str()); + #endif + log("Exec: %s\n", cmd.c_str()); + if (run_command(cmd) != 0) + log_cmd_error("Shell command failed!\n"); + } else + if (format.empty()) { + #ifdef __APPLE__ + std::string cmd = stringf("ps -fu %d | grep -q '[ ]%s' || xdot '%s' %s", getuid(), dot_file.c_str(), dot_file.c_str(), background.c_str()); + #else + std::string cmd = stringf("{ test -f '%s.pid' && fuser -s '%s.pid' 2> /dev/null; } || ( echo $$ >&3; exec xdot '%s'; ) 3> '%s.pid' %s", dot_file.c_str(), dot_file.c_str(), dot_file.c_str(), dot_file.c_str(), background.c_str()); + #endif + log("Exec: %s\n", cmd.c_str()); + if (run_command(cmd) != 0) + log_cmd_error("Shell command failed!\n"); + } } #endif diff --git a/passes/opt/opt_clean.cc b/passes/opt/opt_clean.cc index dde7c5299..cb2490dc7 100644 --- a/passes/opt/opt_clean.cc +++ b/passes/opt/opt_clean.cc @@ -292,10 +292,12 @@ bool rmunused_module_signals(RTLIL::Module *module, bool purge_mode, bool verbos if (!purge_mode) for (auto &it : module->cells_) { RTLIL::Cell *cell = it.second; - if (ct_reg.cell_known(cell->type)) + if (ct_reg.cell_known(cell->type)) { + bool clk2fflogic = cell->get_bool_attribute(ID(clk2fflogic)); for (auto &it2 : cell->connections()) - if (ct_reg.cell_output(cell->type, it2.first)) + if (clk2fflogic ? it2.first == ID::D : ct_reg.cell_output(cell->type, it2.first)) register_signals.add(it2.second); + } for (auto &it2 : cell->connections()) connected_signals.add(it2.second); } diff --git a/passes/proc/proc_prune.cc b/passes/proc/proc_prune.cc index 9f1080ef6..3433557ee 100644 --- a/passes/proc/proc_prune.cc +++ b/passes/proc/proc_prune.cc @@ -91,7 +91,7 @@ struct PruneWorker if (GetSize(new_lhs) == 0) { if (GetSize(conn_lhs) == 0) removed_count++; - cs->actions.erase((it++).base() - 1); + it = decltype(cs->actions)::reverse_iterator(cs->actions.erase(it.base() - 1)); } else { it->first = new_lhs; it->second = new_rhs; diff --git a/passes/sat/clk2fflogic.cc b/passes/sat/clk2fflogic.cc index bba2cbbec..3dc96ecce 100644 --- a/passes/sat/clk2fflogic.cc +++ b/passes/sat/clk2fflogic.cc @@ -80,15 +80,27 @@ struct Clk2fflogicPass : public Pass { return module->Eqx(NEW_ID, {sampled_sig, sig}, polarity ? SigSpec {State::S0, State::S1} : SigSpec {State::S1, State::S0}); } // Sampled and current value of a data signal. - SampledSig sample_data(Module *module, SigSpec sig, RTLIL::Const init, bool is_fine) { + SampledSig sample_data(Module *module, SigSpec sig, RTLIL::Const init, bool is_fine, bool set_attribute = false) { std::string sig_str = log_signal(sig); sig_str.erase(std::remove(sig_str.begin(), sig_str.end(), ' '), sig_str.end()); + + Wire *sampled_sig = module->addWire(NEW_ID_SUFFIX(stringf("%s#sampled", sig_str.c_str())), GetSize(sig)); sampled_sig->attributes[ID::init] = init; + + Cell *cell; if (is_fine) - module->addFfGate(NEW_ID, sig, sampled_sig); + cell = module->addFfGate(NEW_ID, sig, sampled_sig); else - module->addFf(NEW_ID, sig, sampled_sig); + cell = module->addFf(NEW_ID, sig, sampled_sig); + + if (set_attribute) { + for (auto &chunk : sig.chunks()) + if (chunk.wire != nullptr) + chunk.wire->set_bool_attribute(ID::keep); + cell->set_bool_attribute(ID(clk2fflogic)); + } + return {sampled_sig, sig}; } SigSpec mux(Module *module, SigSpec a, SigSpec b, SigSpec s, bool is_fine) { @@ -213,7 +225,7 @@ struct Clk2fflogicPass : public Pass { if (ff.has_clk) ff.unmap_ce_srst(); - auto next_q = sample_data(module, ff.sig_q, ff.val_init, ff.is_fine).sampled; + auto next_q = sample_data(module, ff.sig_q, ff.val_init, ff.is_fine, true).sampled; if (ff.has_clk) { // The init value for the sampled d is never used, so we can set it to fixed zero, reducing uninit'd FFs diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index 273e9db86..2f353672b 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -140,6 +140,7 @@ struct SimInstance dict> upd_outports; dict in_parent_drivers; + dict clk2fflogic_drivers; pool dirty_bits; pool dirty_cells; @@ -270,6 +271,11 @@ struct SimInstance ff.past_srst = State::Sx; ff.data = ff_data; ff_database[cell] = ff; + + if (cell->get_bool_attribute(ID(clk2fflogic))) { + for (int i = 0; i < ff_data.width; i++) + clk2fflogic_drivers.emplace(sigmap(ff_data.sig_d[i]), sigmap(ff_data.sig_q[i])); + } } if (cell->is_mem_cell()) @@ -389,6 +395,10 @@ struct SimInstance auto sigbit = sig[i]; auto sigval = value[i]; + auto clk2fflogic_driver = clk2fflogic_drivers.find(sigbit); + if (clk2fflogic_driver != clk2fflogic_drivers.end()) + sigbit = clk2fflogic_driver->second; + auto in_parent_driver = in_parent_drivers.find(sigbit); if (in_parent_driver == in_parent_drivers.end()) set_state(sigbit, sigval); @@ -589,7 +599,7 @@ struct SimInstance } } - bool update_ph2(bool gclk) + bool update_ph2(bool gclk, bool stable_past_update = false) { bool did_something = false; @@ -600,7 +610,7 @@ struct SimInstance Const current_q = get_state(ff.data.sig_q); - if (ff_data.has_clk) { + if (ff_data.has_clk && !stable_past_update) { // flip-flops State current_clk = get_state(ff_data.sig_clk)[0]; if (ff_data.pol_clk ? (ff.past_clk == State::S0 && current_clk != State::S0) : @@ -621,7 +631,7 @@ struct SimInstance if (ff_data.has_aload) { State current_aload = get_state(ff_data.sig_aload)[0]; if (current_aload == (ff_data.pol_aload ? State::S1 : State::S0)) { - current_q = ff_data.has_clk ? ff.past_ad : get_state(ff.data.sig_ad); + current_q = ff_data.has_clk && !stable_past_update ? ff.past_ad : get_state(ff.data.sig_ad); } } // async reset @@ -672,6 +682,8 @@ struct SimInstance } else { + if (stable_past_update) + continue; if (port.clk_polarity ? (mdb.past_wr_clk[port_idx] == State::S1 || get_state(port.clk) != State::S1) : (mdb.past_wr_clk[port_idx] == State::S0 || get_state(port.clk) != State::S0)) @@ -701,7 +713,7 @@ struct SimInstance } for (auto it : children) - if (it.second->update_ph2(gclk)) { + if (it.second->update_ph2(gclk, stable_past_update)) { dirty_children.insert(it.second); did_something = true; } @@ -1197,9 +1209,21 @@ struct SimWorker : SimShared void initialize_stable_past() { - if (debug) - log("\n-- ph1 (initialize) --\n"); - top->update_ph1(); + + while (1) + { + if (debug) + log("\n-- ph1 (initialize) --\n"); + + top->update_ph1(); + + if (debug) + log("\n-- ph2 (initialize) --\n"); + + if (!top->update_ph2(false, true)) + break; + } + if (debug) log("\n-- ph3 (initialize) --\n"); top->update_ph3(true); diff --git a/techlibs/gatemate/cells_bb.v b/techlibs/gatemate/cells_bb.v index 87b91764f..f928a3d7a 100644 --- a/techlibs/gatemate/cells_bb.v +++ b/techlibs/gatemate/cells_bb.v @@ -131,70 +131,3 @@ module CC_USR_RSTN ( output USR_RSTN ); endmodule - -(* blackbox *) -module CC_FIFO_40K ( - output A_ECC_1B_ERR, - output B_ECC_1B_ERR, - output A_ECC_2B_ERR, - output B_ECC_2B_ERR, - // FIFO pop port - output [39:0] A_DO, - output [39:0] B_DO, - (* clkbuf_sink *) - input A_CLK, - input A_EN, - // FIFO push port - input [39:0] A_DI, - input [39:0] B_DI, - input [39:0] A_BM, - input [39:0] B_BM, - (* clkbuf_sink *) - input B_CLK, - input B_EN, - input B_WE, - // FIFO control - input F_RST_N, - input [12:0] F_ALMOST_FULL_OFFSET, - input [12:0] F_ALMOST_EMPTY_OFFSET, - // FIFO status signals - output F_FULL, - output F_EMPTY, - output F_ALMOST_FULL, - output F_ALMOST_EMPTY, - output F_RD_ERROR, - output F_WR_ERROR, - output [15:0] F_RD_PTR, - output [15:0] F_WR_PTR -); - // Location format: D(0..N-1)X(0..3)Y(0..7) or UNPLACED - parameter LOC = "UNPLACED"; - - // Offset configuration - parameter [12:0] ALMOST_FULL_OFFSET = 12'b0; - parameter [12:0] ALMOST_EMPTY_OFFSET = 12'b0; - - // Port Widths - parameter A_WIDTH = 0; - parameter B_WIDTH = 0; - - // RAM and Write Modes - parameter RAM_MODE = "SDP"; // "TPD" or "SDP" - parameter FIFO_MODE = "SYNC"; // "ASYNC" or "SYNC" - - // Inverting Control Pins - parameter A_CLK_INV = 1'b0; - parameter B_CLK_INV = 1'b0; - parameter A_EN_INV = 1'b0; - parameter B_EN_INV = 1'b0; - parameter A_WE_INV = 1'b0; - parameter B_WE_INV = 1'b0; - - // Output Register - parameter A_DO_REG = 1'b0; - parameter B_DO_REG = 1'b0; - - // Error Checking and Correction - parameter A_ECC_EN = 1'b0; - parameter B_ECC_EN = 1'b0; -endmodule diff --git a/techlibs/gatemate/cells_sim.v b/techlibs/gatemate/cells_sim.v index 12e01d2df..e05ce811c 100644 --- a/techlibs/gatemate/cells_sim.v +++ b/techlibs/gatemate/cells_sim.v @@ -733,13 +733,12 @@ module CC_BRAM_20K ( // SDP read port always @(posedge clkb) begin - // "NO_CHANGE" only for (k=0; k < B_RD_WIDTH; k=k+1) begin if (k < 20) begin - if (enb && !wea) A_DO_out[k] <= memory[addrb+k]; + if (enb) A_DO_out[k] <= memory[addrb+k]; end else begin // use both ports - if (enb && !wea) B_DO_out[k-20] <= memory[addrb+k]; + if (enb) B_DO_out[k-20] <= memory[addrb+k]; end end end @@ -1274,13 +1273,12 @@ module CC_BRAM_40K ( // SDP read port always @(posedge clkb) begin - // "NO_CHANGE" only for (k=0; k < B_RD_WIDTH; k=k+1) begin if (k < 40) begin - if (enb && !wea) A_DO_out[k] <= memory[addrb+k]; + if (enb) A_DO_out[k] <= memory[addrb+k]; end else begin // use both ports - if (enb && !wea) B_DO_out[k-40] <= memory[addrb+k]; + if (enb) B_DO_out[k-40] <= memory[addrb+k]; end end end @@ -1412,6 +1410,393 @@ module CC_BRAM_40K ( endgenerate endmodule +module CC_FIFO_40K ( + output A_ECC_1B_ERR, + output B_ECC_1B_ERR, + output A_ECC_2B_ERR, + output B_ECC_2B_ERR, + // FIFO pop port + output [39:0] A_DO, + output [39:0] B_DO, + (* clkbuf_sink *) + input A_CLK, + input A_EN, + // FIFO push port + input [39:0] A_DI, + input [39:0] B_DI, + input [39:0] A_BM, + input [39:0] B_BM, + (* clkbuf_sink *) + input B_CLK, + input B_EN, + input B_WE, + // FIFO control + input F_RST_N, + input [14:0] F_ALMOST_FULL_OFFSET, + input [14:0] F_ALMOST_EMPTY_OFFSET, + // FIFO status signals + output F_FULL, + output F_EMPTY, + output F_ALMOST_FULL, + output F_ALMOST_EMPTY, + output F_RD_ERROR, + output F_WR_ERROR, + output [15:0] F_RD_PTR, + output [15:0] F_WR_PTR +); + // Location format: D(0..N-1)X(0..3)Y(0..7) or UNPLACED + parameter LOC = "UNPLACED"; + + // Offset configuration + parameter DYN_STAT_SELECT = 1'b0; + parameter [14:0] ALMOST_FULL_OFFSET = 15'b0; + parameter [14:0] ALMOST_EMPTY_OFFSET = 15'b0; + + // Port Widths + parameter A_WIDTH = 0; + parameter B_WIDTH = 0; + + // RAM and Write Modes + parameter RAM_MODE = "TDP"; // "TDP" or "SDP" + parameter FIFO_MODE = "SYNC"; // "ASYNC" or "SYNC" + + // Inverting Control Pins + parameter A_CLK_INV = 1'b0; + parameter B_CLK_INV = 1'b0; + parameter A_EN_INV = 1'b0; + parameter B_EN_INV = 1'b0; + parameter A_WE_INV = 1'b0; + parameter B_WE_INV = 1'b0; + + // Output Register + parameter A_DO_REG = 1'b0; + parameter B_DO_REG = 1'b0; + + // Error Checking and Correction + parameter A_ECC_EN = 1'b0; + parameter B_ECC_EN = 1'b0; + + integer i, k; + + // 512 x 80 bit + reg [40959:0] memory = 40960'b0; + + reg [15:0] counter_max; + reg [15:0] sram_depth; + localparam tp = (A_WIDTH == 1) ? 15 : + (A_WIDTH == 2) ? 14 : + (A_WIDTH == 5) ? 13 : + (A_WIDTH == 10) ? 12 : + (A_WIDTH == 20) ? 11 : + (A_WIDTH == 40) ? 10 : 9; + + initial begin + // Check parameters + if ((RAM_MODE != "SDP") && (RAM_MODE != "TDP")) begin + $display("ERROR: Illegal RAM MODE %d.", RAM_MODE); + $finish(); + end + if ((FIFO_MODE != "ASYNC") && (FIFO_MODE != "SYNC")) begin + $display("ERROR: Illegal FIFO MODE %d.", FIFO_MODE); + $finish(); + end + if ((RAM_MODE == "SDP") && (DYN_STAT_SELECT == 1)) begin + $display("ERROR: Dynamic offset configuration is not supported in %s mode.", RAM_MODE); + $finish(); + end + if ((RAM_MODE == "SDP") && ((A_WIDTH != 80) || (B_WIDTH != 80))) begin + $display("ERROR: SDP is ony supported in 80 bit mode."); + $finish(); + end + if ((A_WIDTH == 80) && (RAM_MODE == "TDP")) begin + $display("ERROR: Port A width of 80 bits is only supported in SDP mode."); + $finish(); + end + if ((B_WIDTH == 80) && (RAM_MODE == "TDP")) begin + $display("ERROR: Port B width of 80 bits is only supported in SDP mode."); + $finish(); + end + if ((A_WIDTH != 80) && (A_WIDTH != 40) && (A_WIDTH != 20) && (A_WIDTH != 10) && + (A_WIDTH != 5) && (A_WIDTH != 2) && (A_WIDTH != 1) && (A_WIDTH != 0)) begin + $display("ERROR: Illegal %s Port A width configuration %d.", RAM_MODE, A_WIDTH); + $finish(); + end + if ((B_WIDTH != 80) && (B_WIDTH != 40) && (B_WIDTH != 20) && (B_WIDTH != 10) && + (B_WIDTH != 5) && (B_WIDTH != 2) && (B_WIDTH != 1) && (B_WIDTH != 0)) begin + $display("ERROR: Illegal %s Port B width configuration %d.", RAM_MODE, B_WIDTH); + $finish(); + end + if (A_WIDTH != B_WIDTH) begin + $display("ERROR: The values of A_WIDTH and B_WIDTH must be equal."); + end + if ((A_ECC_EN == 1'b1) && (RAM_MODE != "SDP") && (A_WIDTH != 40)) begin + $display("ERROR: Illegal ECC Port A configuration. ECC mode requires TDP >=40 bit or SDP 80 bit, but is %s %d.", RAM_MODE, A_WIDTH); + $finish(); + end + // Set local parameters + if (A_WIDTH == 1) begin // A_WIDTH=B_WIDTH + counter_max = 2 * 32*1024 - 1; + sram_depth = 32*1024; + end + else if (A_WIDTH == 2) begin + counter_max = 2 * 16*1024 - 1; + sram_depth = 16*1024; + end + else if (A_WIDTH == 5) begin + counter_max = 2 * 8*1024 - 1; + sram_depth = 8*1024; + end + else if (A_WIDTH == 10) begin + counter_max = 2 * 4*1024 - 1; + sram_depth = 4*1024; + end + else if (A_WIDTH == 20) begin + counter_max = 2 * 2*1024 - 1; + sram_depth = 2*1024; + end + else if (A_WIDTH == 40) begin + counter_max = 2 * 1*1024 - 1; + sram_depth = 1*1024; + end + else begin // 80 bit SDP + counter_max = 2 * 512 - 1; + sram_depth = 512; + end + end + + // Internal signals + wire fifo_rdclk = A_CLK ^ A_CLK_INV; + wire fifo_wrclk = (FIFO_MODE == "ASYNC") ? (B_CLK ^ B_CLK_INV) : (A_CLK ^ A_CLK_INV); + wire [15:0] almost_full_offset = DYN_STAT_SELECT ? F_ALMOST_FULL_OFFSET : ALMOST_FULL_OFFSET; + wire [15:0] almost_empty_offset = DYN_STAT_SELECT ? F_ALMOST_EMPTY_OFFSET : ALMOST_EMPTY_OFFSET; + reg [39:0] A_DO_out = 0, A_DO_reg = 0; + reg [39:0] B_DO_out = 0, B_DO_reg = 0; + + // Status signals + reg fifo_full; + reg fifo_empty; + reg fifo_almost_full; + reg fifo_almost_empty; + assign F_FULL = fifo_full; + assign F_EMPTY = fifo_empty; + assign F_ALMOST_FULL = fifo_almost_full; + assign F_ALMOST_EMPTY = fifo_almost_empty; + assign F_WR_ERROR = (F_FULL && (B_EN ^ B_EN_INV) && (B_WE ^ B_WE_INV)); + assign F_RD_ERROR = (F_EMPTY && (A_EN ^ A_EN_INV)); + wire ram_we = (~F_FULL && (B_EN ^ B_EN_INV) && (B_WE ^ B_WE_INV)); + wire ram_en = (~F_EMPTY && (A_EN ^ A_EN_INV)); + + // Reset synchronizers + reg [1:0] aclk_reset_q, bclk_reset_q; + wire fifo_sync_rstn = aclk_reset_q; + wire fifo_async_wrrstn = bclk_reset_q; + wire fifo_async_rdrstn = aclk_reset_q; + + always @(posedge fifo_rdclk or negedge F_RST_N) + begin + if (F_RST_N == 1'b0) begin + aclk_reset_q <= 2'b0; + end + else begin + aclk_reset_q[1] <= aclk_reset_q[0]; + aclk_reset_q[0] <= 1'b1; + end + end + + always @(posedge fifo_wrclk or negedge F_RST_N) + begin + if (F_RST_N == 1'b0) begin + bclk_reset_q <= 2'b0; + end + else begin + bclk_reset_q[1] <= bclk_reset_q[0]; + bclk_reset_q[0] <= 1'b1; + end + end + + // Push/pop pointers + reg [15:0] rd_pointer, rd_pointer_int; + reg [15:0] wr_pointer, wr_pointer_int; + reg [15:0] rd_pointer_cmp, wr_pointer_cmp; + wire [15:0] rd_pointer_nxt; + wire [15:0] wr_pointer_nxt; + reg [15:0] fifo_rdaddr, rdaddr; + reg [15:0] fifo_wraddr, wraddr; + assign F_RD_PTR = fifo_rdaddr; + assign F_WR_PTR = fifo_wraddr; + + always @(posedge fifo_rdclk or negedge F_RST_N) + begin + if (F_RST_N == 1'b0) begin + rd_pointer <= 0; + rd_pointer_int <= 0; + end + else if (ram_en) begin + rd_pointer <= rd_pointer_nxt; + rd_pointer_int <= rd_pointer_nxt[15:1] ^ rd_pointer_nxt[14:0]; + end + end + + assign rd_pointer_nxt = (rd_pointer == counter_max) ? (0) : (rd_pointer + 1'b1); + + always @(posedge fifo_wrclk or negedge F_RST_N) + begin + if (F_RST_N == 1'b0) begin + wr_pointer <= 0; + wr_pointer_int <= 0; + end + else if (ram_we) begin + wr_pointer <= wr_pointer_nxt; + wr_pointer_int <= wr_pointer_nxt[15:1] ^ wr_pointer_nxt[14:0]; + end + end + + assign wr_pointer_nxt = (wr_pointer == counter_max) ? (0) : (wr_pointer + 1'b1); + + // Address synchronizers + reg [15:0] rd_pointer_sync, wr_pointer_sync; + reg [15:0] rd_pointer_sync_0, rd_pointer_sync_1; + reg [15:0] wr_pointer_sync_0, wr_pointer_sync_1; + + always @(posedge fifo_rdclk or negedge F_RST_N) + begin + if (F_RST_N == 1'b0) begin + wr_pointer_sync_0 <= 0; + wr_pointer_sync_1 <= 0; + end + else begin + wr_pointer_sync_0 <= wraddr; + wr_pointer_sync_1 <= wr_pointer_sync_0; + end + end + + always @(posedge fifo_wrclk or negedge F_RST_N) + begin + if (F_RST_N == 1'b0) begin + rd_pointer_sync_0 <= 0; + rd_pointer_sync_1 <= 0; + end + else begin + rd_pointer_sync_0 <= rdaddr; + rd_pointer_sync_1 <= rd_pointer_sync_0; + end + end + + always @(*) begin + fifo_wraddr = {wr_pointer[tp-1:0], {(15-tp){1'b0}}}; + fifo_rdaddr = {rd_pointer[tp-1:0], {(15-tp){1'b0}}}; + + rdaddr = {rd_pointer[tp], rd_pointer_int[tp-1:0]}; + wraddr = {{(15-tp){1'b0}}, wr_pointer[tp], wr_pointer_int[tp:0]}; + + if (FIFO_MODE == "ASYNC") + fifo_full = (wraddr[tp-2:0] == rd_pointer_sync_1[tp-2:0] ) && (wraddr[tp] != rd_pointer_sync_1[tp] ) && ( wraddr[tp-1] != rd_pointer_sync_1[tp-1] ); + else + fifo_full = (wr_pointer[tp-1:0] == rd_pointer[tp-1:0]) && (wr_pointer[tp] ^ rd_pointer[tp]); + + if (FIFO_MODE == "ASYNC") + fifo_empty = (wr_pointer_sync_1[tp:0] == rdaddr[tp:0]); + else + fifo_empty = (wr_pointer[tp:0] == rd_pointer[tp:0]); + + rd_pointer_cmp = (FIFO_MODE == "ASYNC") ? rd_pointer_sync : rd_pointer; + if (wr_pointer[tp] == rd_pointer_cmp[tp]) + fifo_almost_full = ((wr_pointer[tp-1:0] - rd_pointer_cmp[tp-1:0]) >= (sram_depth - almost_full_offset)); + else + fifo_almost_full = ((rd_pointer_cmp[tp-1:0] - wr_pointer[tp-1:0]) <= almost_full_offset); + + wr_pointer_cmp = (FIFO_MODE == "ASYNC") ? wr_pointer_sync : wr_pointer; + if (wr_pointer_cmp[tp] == rd_pointer[tp]) + fifo_almost_empty = ((wr_pointer_cmp[tp-1:0] - rd_pointer[tp-1:0]) <= almost_empty_offset); + else + fifo_almost_empty = ((rd_pointer[tp-1:0] - wr_pointer_cmp[tp-1:0]) >= (sram_depth - almost_empty_offset)); + end + + generate + always @(*) begin + wr_pointer_sync = 0; + rd_pointer_sync = 0; + for (i=tp; i >= 0; i=i-1) begin + if (i == tp) begin + wr_pointer_sync[i] = wr_pointer_sync_1[i]; + rd_pointer_sync[i] = rd_pointer_sync_1[i]; + end + else begin + wr_pointer_sync[i] = wr_pointer_sync_1[i] ^ wr_pointer_sync[i+1]; + rd_pointer_sync[i] = rd_pointer_sync_1[i] ^ rd_pointer_sync[i+1]; + end + end + end + if (RAM_MODE == "SDP") begin + // SDP push ports A+B + always @(posedge fifo_wrclk) + begin + for (k=0; k < A_WIDTH; k=k+1) begin + if (k < 40) begin + if (ram_we && A_BM[k]) memory[fifo_wraddr+k] <= A_DI[k]; + end + else begin // use both ports + if (ram_we && B_BM[k-40]) memory[fifo_wraddr+k] <= B_DI[k-40]; + end + end + end + // SDP pop ports A+B + always @(posedge fifo_rdclk) + begin + for (k=0; k < B_WIDTH; k=k+1) begin + if (k < 40) begin + if (ram_en) A_DO_out[k] <= memory[fifo_rdaddr+k]; + end + else begin // use both ports + if (ram_en) B_DO_out[k-40] <= memory[fifo_rdaddr+k]; + end + end + end + end + else if (RAM_MODE == "TDP") begin + // TDP pop port A + always @(posedge fifo_rdclk) + begin + for (i=0; i < A_WIDTH; i=i+1) begin + if (ram_en) begin + A_DO_out[i] <= memory[fifo_rdaddr+i]; + end + end + end + // TDP push port B + always @(posedge fifo_wrclk) + begin + for (i=0; i < B_WIDTH; i=i+1) begin + if (ram_we && B_BM[i]) + memory[fifo_wraddr+i] <= B_DI[i]; + end + end + end + endgenerate + + // Optional output register + generate + if (A_DO_REG) begin + always @(posedge fifo_rdclk) begin + A_DO_reg <= A_DO_out; + end + assign A_DO = A_DO_reg; + end + else begin + assign A_DO = A_DO_out; + end + if (B_DO_REG) begin + always @(posedge fifo_rdclk) begin + B_DO_reg <= B_DO_out; + end + assign B_DO = B_DO_reg; + end + else begin + assign B_DO = B_DO_out; + end + endgenerate +endmodule + // Models of the LUT2 tree primitives module CC_L2T4( output O, diff --git a/tests/arch/anlogic/mux.ys b/tests/arch/anlogic/mux.ys index 3d5fe7c9a..89014b5e0 100644 --- a/tests/arch/anlogic/mux.ys +++ b/tests/arch/anlogic/mux.ys @@ -26,10 +26,12 @@ proc equiv_opt -assert -map +/anlogic/cells_sim.v synth_anlogic # equivalency check design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) cd mux8 # Constrain all select calls below inside the top module -select -assert-count 3 t:AL_MAP_LUT4 -select -assert-count 1 t:AL_MAP_LUT6 +select -assert-max 3 t:AL_MAP_LUT3 +select -assert-max 3 t:AL_MAP_LUT4 +select -assert-max 1 t:AL_MAP_LUT5 +select -assert-max 1 t:AL_MAP_LUT6 -select -assert-none t:AL_MAP_LUT4 t:AL_MAP_LUT6 %% t:* %D +select -assert-none t:AL_MAP_LUT3 t:AL_MAP_LUT4 t:AL_MAP_LUT5 t:AL_MAP_LUT6 %% t:* %D design -load read hierarchy -top mux16 diff --git a/tests/arch/ecp5/mux.ys b/tests/arch/ecp5/mux.ys index db63dda5f..daa9e86f2 100644 --- a/tests/arch/ecp5/mux.ys +++ b/tests/arch/ecp5/mux.ys @@ -28,8 +28,8 @@ equiv_opt -assert -map +/ecp5/cells_sim.v synth_ecp5 # equivalency check design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) cd mux8 # Constrain all select calls below inside the top module select -assert-max 1 t:L6MUX21 -select -assert-max 7 t:LUT4 -select -assert-max 2 t:PFUMX +select -assert-max 8 t:LUT4 +select -assert-max 3 t:PFUMX select -assert-none t:LUT4 t:L6MUX21 t:PFUMX %% t:* %D diff --git a/tests/arch/gatemate/fsm.ys b/tests/arch/gatemate/fsm.ys index 6b43ead7a..506862c90 100644 --- a/tests/arch/gatemate/fsm.ys +++ b/tests/arch/gatemate/fsm.ys @@ -15,6 +15,6 @@ cd fsm # Constrain all select calls below inside the top module select -assert-count 1 t:CC_BUFG select -assert-count 6 t:CC_DFF select -assert-max 5 t:CC_LUT2 -select -assert-max 4 t:CC_LUT3 +select -assert-max 6 t:CC_LUT3 select -assert-max 9 t:CC_LUT4 select -assert-none t:CC_BUFG t:CC_DFF t:CC_LUT2 t:CC_LUT3 t:CC_LUT4 %% t:* %D diff --git a/tests/arch/ice40/rom.ys b/tests/arch/ice40/rom.ys index 41d214e2a..d795e206b 100644 --- a/tests/arch/ice40/rom.ys +++ b/tests/arch/ice40/rom.ys @@ -4,5 +4,5 @@ flatten equiv_opt -assert -map +/ice40/cells_sim.v synth_ice40 # equivalency check design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) cd top # Constrain all select calls below inside the top module -select -assert-count 5 t:SB_LUT4 +select -assert-max 6 t:SB_LUT4 select -assert-none t:SB_LUT4 %% t:* %D diff --git a/tests/arch/intel_alm/mux.ys b/tests/arch/intel_alm/mux.ys index 6fb6ae80a..20969ead3 100644 --- a/tests/arch/intel_alm/mux.ys +++ b/tests/arch/intel_alm/mux.ys @@ -69,7 +69,7 @@ proc equiv_opt -assert -map +/intel_alm/common/alm_sim.v synth_intel_alm -family cyclonev -noiopad -noclkbuf # equivalency check design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) cd mux16 # Constrain all select calls below inside the top module -select -assert-count 1 t:MISTRAL_ALUT3 +select -assert-max 1 t:MISTRAL_ALUT3 select -assert-max 2 t:MISTRAL_ALUT5 select -assert-max 5 t:MISTRAL_ALUT6 select -assert-none t:MISTRAL_ALUT3 t:MISTRAL_ALUT5 t:MISTRAL_ALUT6 %% t:* %D @@ -81,8 +81,8 @@ proc equiv_opt -assert -map +/intel_alm/common/alm_sim.v synth_intel_alm -family cyclone10gx -noiopad -noclkbuf # equivalency check design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) cd mux16 # Constrain all select calls below inside the top module -select -assert-count 1 t:MISTRAL_ALUT3 -select -assert-count 2 t:MISTRAL_ALUT5 -select -assert-count 4 t:MISTRAL_ALUT6 +select -assert-max 1 t:MISTRAL_ALUT3 +select -assert-max 2 t:MISTRAL_ALUT5 +select -assert-max 5 t:MISTRAL_ALUT6 select -assert-none t:MISTRAL_ALUT3 t:MISTRAL_ALUT5 t:MISTRAL_ALUT6 %% t:* %D diff --git a/tests/arch/nexus/mux.ys b/tests/arch/nexus/mux.ys index 0e12d674a..280d3e48f 100644 --- a/tests/arch/nexus/mux.ys +++ b/tests/arch/nexus/mux.ys @@ -36,8 +36,7 @@ proc equiv_opt -assert -map +/nexus/cells_sim.v synth_nexus # equivalency check design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) cd mux16 # Constrain all select calls below inside the top module -select -assert-min 11 t:LUT4 select -assert-max 12 t:LUT4 -select -assert-count 1 t:WIDEFN9 +select -assert-max 2 t:WIDEFN9 select -assert-none t:IB t:OB t:VLO t:VHI t:LUT4 t:WIDEFN9 %% t:* %D diff --git a/tests/arch/xilinx/mux_lut4.ys b/tests/arch/xilinx/mux_lut4.ys index 3e3256993..147601dce 100644 --- a/tests/arch/xilinx/mux_lut4.ys +++ b/tests/arch/xilinx/mux_lut4.ys @@ -30,12 +30,13 @@ proc equiv_opt -assert -map +/xilinx/cells_sim.v synth_xilinx -family xc3se -noiopad # equivalency check design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) cd mux8 # Constrain all select calls below inside the top module -select -assert-count 4 t:LUT1 -select -assert-count 3 t:LUT4 -select -assert-count 2 t:MUXF5 +select -assert-max 5 t:LUT1 +select -assert-max 3 t:LUT3 +select -assert-max 3 t:LUT4 +select -assert-max 3 t:MUXF5 select -assert-count 1 t:MUXF6 -select -assert-none t:LUT1 t:LUT4 t:MUXF5 t:MUXF6 %% t:* %D +select -assert-none t:LUT1 t:LUT3 t:LUT4 t:MUXF5 t:MUXF6 %% t:* %D design -load read diff --git a/tests/various/constant_drive_conflict.ys b/tests/various/constant_drive_conflict.ys new file mode 100644 index 000000000..1246cbcae --- /dev/null +++ b/tests/various/constant_drive_conflict.ys @@ -0,0 +1,51 @@ +read_verilog <