mirror of
				https://github.com/YosysHQ/yosys
				synced 2025-11-04 05:19:11 +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/index_passes_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];
 | 
						|
 |