mirror of
				https://github.com/YosysHQ/yosys
				synced 2025-10-31 19:52:31 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			790 lines
		
	
	
	
		
			22 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
			
		
		
	
	
			790 lines
		
	
	
	
		
			22 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
| Memory handling
 | |
| ===============
 | |
| 
 | |
| The `memory` command
 | |
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | |
| 
 | |
| In the RTL netlist, memory reads and writes are individual cells. This makes
 | |
| consolidating the number of ports for a memory easier. The `memory` pass
 | |
| transforms memories to an implementation. Per default that is logic for address
 | |
| decoders and registers. It also is a macro command that calls the other common
 | |
| ``memory_*`` passes in a sensible order:
 | |
| 
 | |
| .. literalinclude:: /code_examples/macro_commands/memory.ys
 | |
|    :language: yoscrypt
 | |
|    :start-after: #end:
 | |
|    :caption: Passes called by `memory`
 | |
| 
 | |
| .. todo:: Make ``memory_*`` notes less quick
 | |
| 
 | |
| Some quick notes:
 | |
| 
 | |
| -  `memory_dff` merges registers into the memory read- and write cells.
 | |
| -  `memory_collect` collects all read and write cells for a memory and
 | |
|    transforms them into one multi-port memory cell.
 | |
| -  `memory_map` takes the multi-port memory cell and transforms it to address
 | |
|    decoder logic and registers.
 | |
| 
 | |
| For more information about `memory`, such as disabling certain sub commands, see
 | |
| :doc:`/cmd/memory`.
 | |
| 
 | |
| Example
 | |
| -------
 | |
| 
 | |
| .. todo:: describe ``memory`` images
 | |
| 
 | |
| |code_examples/synth_flow|_.
 | |
| 
 | |
| .. |code_examples/synth_flow| replace:: :file:`docs/source/code_examples/synth_flow`
 | |
| .. _code_examples/synth_flow: https://github.com/YosysHQ/yosys/tree/main/docs/source/code_examples/synth_flow
 | |
| 
 | |
| .. figure:: /_images/code_examples/synth_flow/memory_01.*
 | |
|    :class: width-helper invert-helper
 | |
| 
 | |
| .. literalinclude:: /code_examples/synth_flow/memory_01.ys
 | |
|    :language: yoscrypt
 | |
|    :caption: :file:`memory_01.ys`
 | |
| 
 | |
| .. literalinclude:: /code_examples/synth_flow/memory_01.v
 | |
|    :language: verilog
 | |
|    :caption: :file:`memory_01.v`
 | |
| 
 | |
| .. figure:: /_images/code_examples/synth_flow/memory_02.*
 | |
|    :class: width-helper invert-helper
 | |
| 
 | |
| .. literalinclude:: /code_examples/synth_flow/memory_02.v
 | |
|    :language: verilog
 | |
|    :caption: :file:`memory_02.v`
 | |
| 
 | |
| .. literalinclude:: /code_examples/synth_flow/memory_02.ys
 | |
|    :language: yoscrypt
 | |
|    :caption: :file:`memory_02.ys`
 | |
| 
 | |
| .. _memory_map:
 | |
| 
 | |
| Memory mapping
 | |
| ^^^^^^^^^^^^^^
 | |
| 
 | |
| Usually it is preferred to use architecture-specific RAM resources for memory.
 | |
| For example:
 | |
| 
 | |
| .. code-block:: yoscrypt
 | |
| 
 | |
|     memory -nomap
 | |
|     memory_libmap -lib my_memory_map.txt
 | |
|     techmap -map my_memory_map.v
 | |
|     memory_map
 | |
| 
 | |
| `memory_libmap` attempts to convert memory cells (`$mem_v2` etc) into hardware
 | |
| supported memory using a provided library (:file:`my_memory_map.txt` in the
 | |
| example above).  Where necessary, emulation logic is added to ensure functional
 | |
| equivalence before and after this conversion. :yoscrypt:`techmap -map
 | |
| my_memory_map.v` then uses `techmap` to map to hardware primitives. Any leftover
 | |
| memory cells unable to be converted are then picked up by `memory_map` and
 | |
| mapped to DFFs and address decoders.
 | |
| 
 | |
| .. note::
 | |
| 
 | |
|    More information about what mapping options are available and associated
 | |
|    costs of each can be found by enabling debug outputs.  This can be done with
 | |
|    the `debug` command, or by using the ``-g`` flag when calling Yosys to
 | |
|    globally enable debug messages.
 | |
| 
 | |
| For more on the lib format for `memory_libmap`, see
 | |
| `passes/memory/memlib.md
 | |
| <https://github.com/YosysHQ/yosys/blob/main/passes/memory/memlib.md>`_
 | |
| 
 | |
| Supported memory patterns
 | |
| ^^^^^^^^^^^^^^^^^^^^^^^^^
 | |
| 
 | |
| 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, `wbe`_ 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.
 | |
| 
 | |
| 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 `sr_init`_.
 | |
| 
 | |
| .. _wbe:
 | |
| 
 | |
| 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
 | |
| --------------------------------------
 | |
| 
 | |
| .. todo:: assorted enables, e.g. cen, wen+ren
 | |
| 
 | |
| 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
 | |
| 
 | |
| .. _no_rw_check:
 | |
| 
 | |
| 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];
 | |
| 		
 | |
| 		if (write_enable && read_addr == write_addr)
 | |
| 			// this if block
 | |
| 			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
 | |
| 
 | |
| .. _sdp_wf:
 | |
| 
 | |
| 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 [ADDR_WIDTH - 1 : 0] read_addr_reg;
 | |
| 	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
 | |
| 
 | |
| .. _sr_init:
 | |
| 
 | |
| 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 <= 'h1234;
 | |
| 		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 read_reset) begin
 | |
| 		if (read_reset)
 | |
| 			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_sr:
 | |
| 
 | |
| 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];
 | |
| 
 | |
| Patterns only supported with Verific
 | |
| ------------------------------------
 | |
| 
 | |
| The following patterns are only supported when the design is read in using the
 | |
| Verific front-end.
 | |
| 
 | |
| Synchronous SDP with write-first behavior via blocking assignments
 | |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | |
| 
 | |
| - Use `sdp_wf`_ for compatibility with Yosys
 | |
|   Verilog frontend.
 | |
| 
 | |
| .. 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
 | |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | |
| 
 | |
| - Build wide ports out of narrow ports instead (see `wide_sr`_) for
 | |
|   compatibility with Yosys Verilog frontend.
 | |
| 
 | |
| .. 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];
 | |
| 
 |