mirror of
				https://github.com/YosysHQ/yosys
				synced 2025-10-31 11:42:30 +00:00 
			
		
		
		
	Initial version of memory mapping doc
This commit is contained in:
		
							parent
							
								
									cac1bc6fbe
								
							
						
					
					
						commit
						3aee765793
					
				
					 2 changed files with 653 additions and 0 deletions
				
			
		
							
								
								
									
										652
									
								
								docs/source/CHAPTER_Memorymap.rst
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										652
									
								
								docs/source/CHAPTER_Memorymap.rst
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,652 @@ | |||
| .. _chapter:memorymap: | ||||
| 
 | ||||
| Memory mapping | ||||
| ============== | ||||
| 
 | ||||
| Documentation for the Yosys memory_libmap memory mapper. | ||||
| 
 | ||||
| See also: `passes/memory/memlib.md <https://github.com/YosysHQ/yosys/blob/master/passes/memory/memlib.md>`_ | ||||
| 
 | ||||
| Supported patterns | ||||
| ------------------ | ||||
| 
 | ||||
| Asynchronous-read RAM | ||||
| ~~~~~~~~~~~~~~~~~~~~~ | ||||
| 
 | ||||
| - 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]; | ||||
| 
 | ||||
| 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 | ||||
| 
 | ||||
| Synchronous read port with synchronous reset (reset priority over enable) | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
| 
 | ||||
| - Synchronous resets can be combined with any other supported pattern (except that synchronous reset | ||||
|   and asynchronous reset cannot be used on a single read port) | ||||
| - If block RAM is used and synchronous resets 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]; | ||||
| 
 | ||||
| 	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 read port with synchronous reset (enable priority over reset) | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
| 
 | ||||
| - Synchronous resets can be combined with any other supported pattern (except that synchronous reset | ||||
|   and asynchronous reset cannot be used on a single read port) | ||||
| - If block RAM is used and synchronous resets 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]; | ||||
| 
 | ||||
| 	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 | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
| 
 | ||||
| - Asynchronous resets can be combined with any other supported pattern (except that synchronous | ||||
|   reset and asynchronous reset cannot be used on a single read port) | ||||
| - If block RAM is used and asynchronous resets 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]; | ||||
| 
 | ||||
| 	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 | ||||
| 
 | ||||
| Initial data | ||||
| ~~~~~~~~~~~~ | ||||
| 
 | ||||
| - Most FPGA targets support initializing all kinds of memory to user-provided values | ||||
| - If explicit initialization is not used, initial memory value is undefined | ||||
| - Initial data can be provided by either initial statements writing memory cells one by one or | ||||
|   $readmemh/$readmemb system tasks | ||||
| 
 | ||||
| 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 | ||||
| 
 | ||||
| Asymmetric memory — general notes | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
| 
 | ||||
| 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: 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 this is cheaper | ||||
| 
 | ||||
| Asymmetric memory with 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 memory — general notes | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
| 
 | ||||
| - 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. | ||||
| 
 | ||||
| True Dual Port — 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 | ||||
| 
 | ||||
| True Dual Port — 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 | ||||
| 
 | ||||
| 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]; | ||||
| 
 | ||||
| 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. | ||||
| 
 | ||||
| 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 `Asymmetric memory with 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]; | ||||
| 
 | ||||
|  | @ -42,6 +42,7 @@ Yosys manual | |||
| 	CHAPTER_Verilog.rst | ||||
| 	CHAPTER_Optimize.rst | ||||
| 	CHAPTER_Techmap.rst | ||||
| 	CHAPTER_Memorymap.rst | ||||
| 	CHAPTER_Eval.rst | ||||
| 
 | ||||
| .. raw:: latex | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue