Memory inference and read ports
Write a memory as an array, see what this flow builds from it and what a real flow maps it to, and choose a read port by its latency and its read-during-write result.
- Lesson
- R5.1
- Time
- 30 min
- Runs on
- Your browser
- Check
- 5 questions
A 1024 x 32 buffer was written as an array with a clocked write and a combinational read. On this site's flow it became 32768 flops and 90859 cells, with 19 levels of logic. An FPGA flow would have used a block RAM; an ASIC flow needs a memory macro, and this flow has none.
| Words x bits | Cells | Flops | Levels of logic on this flow |
|---|---|---|---|
| 4 x 8 | 65 | 32 | 3 |
| 16 x 8 (the lab) | 361 | 128 | 7 |
| 64 x 8 | 1487 | 512 | 11 |
| 1024 x 32 (the hook) | 90859 | 32768 | 19 |
The lab's register file with a combinational read, synthesized at four sizes by Yosys 0.70. Every bit is a flop, and the cells grow with the bits.
What an array becomes
An unpacked array such as logic [7:0] mem [16], written in a clocked block, is a memory: words selected by an address. The synthesizer first collects it as one memory cell with a write port and a read port. What happens next depends on the flow.
- Write
The decoder turns
waddrandweinto one enable per word. At the edge, only the enabled word's eight flops loadwdata. - Store
The 128 flops are the storage: 16 words of 8 bits. Measured on this flow, they are 128 flops with an enable each.
- Read
Each of the 8 output bits chooses one of 16 flops by
raddr. That choice is the read mux, and it grows with the depth.
Read the diagram as text
- Write port: we, waddr, wdata
- Address decoder: one enable per word
- Flop array: 16 words x 8 flops with enables
- Read address: raddr
- Read mux: a 16-way choice per bit
- rdata: 8 bits, same cycle
- Write port → Address decoder: waddr, we
- Address decoder → Flop array: enables
- Write port → Flop array: wdata
- Flop array → Read mux: 128 bits
- Read address → Read mux: raddr
- Read mux → rdata: word
Yosys 0.70 builds this from the lab design: 361 cells, 128 flops, 7 levels of logic.
The pinned flow runs memory -nomap to collect the array, then memory_map, which builds word-wide flops and address decoders. It has no memory library to map to. A real ASIC flow gives the synthesizer a library of SRAM macros: dense memory blocks made by a memory compiler and placed like one big cell. Yosys can map to such cells with memory_libmap, but only when a library describes them.
Three read ports, one write
The read port is the design choice. A combinational (asynchronous) read returns mem[raddr] in the same cycle. A synchronous read puts a register in the path and returns the word one cycle later. The register can hold the address or the data.
Combinational read
assign rdata = mem[raddr];
Same-cycle data. Fine for flops; most SRAM macros cannot do it.
Registered address
logic [3:0] raddr_q;
always_ff @(posedge clk) raddr_q <= raddr;
assign rdata = mem[raddr_q];
One cycle of latency, the form a synchronous SRAM offers.
A read-during-write happens when one cycle writes and reads the same address. The memory may return the old word or the new one, and the brief must say which. The lab writes 0xa5 into word 5 while reading word 5, which held 0x55.
rq row is the value a register after the read port captured at that edge, for one read style.- Cycle 0
Edge 16: the combinational read gives word 3, 0x33, in its own cycle. Both registered reads still show the word read before, from address 0.
- Cycle 1
Edge 17: the registered reads give 0x33 now, one cycle late. Each row keeps that one-cycle shift.
- Cycle 3
Edge 19: this cycle writes 0xa5 into word 5 and reads word 5. The combinational read returns the old word, 0x55, because the write lands at this edge.
- Cycle 4
Edge 20: the registered address read returns the new word, 0xa5. The registered data read returns the old word, 0x55, sampled before the write landed.
Measured on the lab bench with each read style. Same memory, three answers to one read-during-write.
The lab: a register file
| Port | Direction | Width | Meaning |
|---|---|---|---|
| clk | input | 1 | clock |
| we | input | 1 | write at the rising edge when we is 1 |
| waddr | input | [$clog2(DEPTH)-1:0] | write address |
| wdata | input | [WIDTH-1:0] | write data |
| raddr | input | [$clog2(DEPTH)-1:0] | read address |
| rdata | output | [WIDTH-1:0] | the word at raddr in the same cycle |
- Latency
- read combinational:
rdatais the word atraddrin the same cycle; write at the rising edge - Parameters
DEPTHwords ofWIDTHbits, 16 x 8 by default; no reset, as in a RAM- Corner cases
- read and write of one address in one cycle; a change to the read style; another
DEPTH. Read-during-write: a combinational read in the writing cycle returns the old word
Run the lab. The bench writes 0x00, 0x11, up to 0xff, reads words 3, 9 and 12, then reads word 5 while writing it. Then change the read to the registered address of the compare and run again: every word arrives one edge later, and the read-during-write returns the new word.
| 16 x 8 read port | Cells | Flops | Levels of logic on this flow |
|---|---|---|---|
| Combinational (the lab) | 361 | 128 | 7 |
| Registered address | 323 | 132 | 6 |
| Registered data | 289 | 136 | 4 |
Measured with Yosys 0.70. The extra flops hold the 4-bit address or the 8-bit word. Fewer cells here is how the synthesizer mapped each design, not a rule.
Synthesize each, then change DEPTH to 4 and to 64 and synthesize again. The flops follow the bits exactly. The read mux follows the depth: each output bit chooses among DEPTH flops.
Asked in interviews
What does an unpacked array become in synthesis?
A memory. With a memory library it maps to an SRAM macro or block RAM if the ports fit. Without one, it becomes flops with write enables and a read mux per output bit.
Synchronous or asynchronous read?
An asynchronous read returns the word in the same cycle, which flops can do and most SRAMs cannot. A synchronous read registers the address or the data and returns the word one cycle later.
What is read-during-write behaviour?
What a read returns when the same cycle writes that address: the old word or the new one. It differs between memories, so the brief states it and the bench checks it.
Try it
Edges 0 to 15 write the pattern. Each later line shows the read address of the cycle that ended at that edge, and rq, the word a register captured there. A combinational read needs no extra cycle, so each address meets its own word.
With the registered address, the lines read 0x00, 0x33, 0x99, 0xcc, then 0xa5 at edges 20 and 21. With the registered data, they read 0x00, 0x33, 0x99, 0xcc, 0x55 and 0xa5.
Synthesize the design to measure its cells and inspect the mapped gates.
e16 raddr=3 rq=0x33 e17 raddr=9 rq=0x99 e18 raddr=12 rq=0xcc e19 raddr=5 write 5<-0xa5 rq=0x55 e20 raddr=5 rq=0xa5 e21 raddr=5 rq=0xa5Original design · 361 cells · 128 flip-flops · 0 latches · 7 logic levels. Synthesis uses Yosys; counts depend on the toolchain.
Read included file: tb.sv
`timescale 1ns/1ps
module tb;
logic clk = 0;
always #5 clk = ~clk; // rising edges at 5, 15, 25, ...
int edge_n = -1;
always @(posedge clk) edge_n = edge_n + 1;
logic we = 0;
logic [3:0] waddr = 0, raddr = 0;
logic [7:0] wdata = 0, rdata;
logic [7:0] rq; // a register after the read port: what it captures at each edge
always @(posedge clk) rq <= rdata;
bit show = 0;
task automatic write(input logic [3:0] a, input logic [7:0] d);
we = 1; waddr = a; wdata = d; tick(); we = 0;
endtask
regfile dut (.clk(clk), .we(we), .waddr(waddr), .wdata(wdata), .raddr(raddr), .rdata(rdata));
// Advance to the next rising edge, wait for the design to settle, then print one line.
task automatic tick();
@(posedge clk); #1;
if (show && we) $display("e%0d raddr=%0d write %0d<-0x%h rq=0x%h", edge_n, raddr, waddr, wdata, rq);
else if (show) $display("e%0d raddr=%0d rq=0x%h", edge_n, raddr, rq);
endtask
initial begin
for (int a = 0; a < 16; a++) write(4'(a), 8'(a * 17)); // 0x00, 0x11, ... 0xff
show = 1;
raddr = 3; tick();
raddr = 9; tick();
raddr = 12; tick();
we = 1; waddr = 5; wdata = 8'ha5; raddr = 5; tick(); // read and write address 5 in one cycle
we = 0; tick();
tick();
$finish(0); // quiet: no "$finish called" line
end
endmodule
Check yourself
Remember
- An unpacked array written in a clocked block is a memory; what it becomes depends on the flow's memory library.
- With no library, Yosys maps a memory to one flop per bit with write enables and a read mux per output bit; the cells grow with the bits.
- A combinational read returns the word in the same cycle; a synchronous read registers the address or the data and returns it one cycle later, the form SRAM macros offer.
- Read-during-write returns the old or the new word depending on the read port; the brief says which and the bench checks it.