A memory compiler builds an SRAM macro for a stated depth, width and port count. It emits the layout block plus a timing and power model for that exact shape.
There is one macro per shape, not one generic RAM. A new depth or width means a new compiler run and a new model.
Compiler input | Compiler output |
|---|---|
Depth and width | Placed block with fixed pins |
Port count and type | Timing model per port |
Options (masks, test) | Power model and test interface, varying by compiler |
A synthesis tool treats a compiled macro as a black box. It connects the pins and meets the setup and hold requirements from the model; it never maps logic inside.
That boundary cuts both ways. The inside is free of synthesis effort, but no RTL change can fix a macro whose shape is wrong.
// not-run: illustrative excerpt, not a complete testbench
ram_macro u_mem (
.clk(clk), .addr(addr_q), .wdata(wdata_q), .we(we_q),
.rdata(mem_q)
);The listing above is an illustrative excerpt, not a runnable testbench.
A wrapper registers the macro boundary and absorbs the awkward cases. Address and control register on the way in, and the read data registers on the way out.
The wrapper also holds the bypass mux and any power or test hooks. Those hooks vary by macro, so the wrapper isolates them from the rest of the RTL.
Wrapper piece | Job |
|---|---|
Input registers | Meet macro setup with a clean pipeline stage |
Bypass mux | Forward write data to a same-cycle same-address read |
Output register | Absorb macro clock-to-data delay |
Ports set the accesses per cycle, and width times clock sets the bit rate. The program below checks address width, macro tiling and EXAMPLE port rates.
import sys
WIDTH = 32
FREQ_HZ = 500000000
DEPTH = 1024
bytes_per_access = WIDTH // 8
bw_single = WIDTH * FREQ_HZ
bw_dual = 2 * WIDTH * FREQ_HZ
addr = 0
p = 1
while p < DEPTH:
p = p * 2
addr = addr + 1
macros_for_1mib = 1048576 // 65536
checks = [
(bytes_per_access, 4),
(bw_single, 16000000000),
(bw_dual, 32000000000),
(addr, 10),
(macros_for_1mib, 16),
]
bad = 0
for i in range(len(checks)):
got, want = checks[i]
if got != want:
print("FAIL: check " + str(i) + " got " + str(got) + " want " + str(want))
bad += 1
if bad != 0:
sys.exit(1)
print("PASS: port and tiling arithmetic holds on 5 checks")The rates above use EXAMPLE parameters, not measurements of any real macro. The tiling count is exact: sixteen 64 KiB macros hold one MiB.
1 MiB = 1048576 bytes, one macro = 64 KiB = 65536 bytes
macros = 1048576 / 65536 = 16
address bits for 1024 rows = 10, since 2^10 = 1024
EXAMPLE port rate: 32 bits x 500000000 cycles/s = 16000000000 bit/sTen address bits, since 2^10 is 1024. Sixteen 64 KiB macros hold one MiB.
16000000000 bit/s: 32 times 500000000. A dual-port shape doing one read and one write per cycle doubles it to 32000000000 bit/s.