Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • Logic Synthesis Glossary
  • Logic Synthesis Cheatsheet
  • Synthesis Foundations14
  • Logic Optimisation and Mapping20
  • Constraints and Timing15
  • Sequential, FSM and Memory19
    • Flops, Resets and Enables
    • FSM Synthesis
    • Memory and Macro Inference
      • Flop Arrays or RAM Macros
      • Read and Write Port Styles
      • Byte Enables and Bypass Wrappers
      • Compilers, Macros and Wrappers
    • Retiming and Pipelining
  • Datapath Synthesis14
  • Area, Power and Test14
  • Verification, ECO and Handoff15
  • Flows and Debugging15
  • Interview and Judgment11
Loading lesson

←Memory and Macro Inference
  1. Topics
  2. Logic Synthesis
  3. Sequential, FSM and Memory
  4. Memory and Macro Inference
  5. Flop Arrays or RAM Macros
On this page

    Part 4 of 9, lesson 11 of 19Part 4 of 9/Sequential, FSM and Memory/Lesson 11 of 19Intermediate

    Flop Arrays or RAM Macros

    Flop arrays versus RAM macros, and when a small table should stay flops.

    A crossover quoted as fact teaches nothing: without the recorded slope on one side and a labelled block on the other, the boundary is a slogan the next table size breaks.

    After this lesson you can: read a flop-array sweep as cost per bit, price the crossover against an assumed macro block, and say whether cost follows entries or bits.

    The question

    A synthesis tool turns each RTL array into storage plus access logic. A small array becomes one flop per bit with read muxes and write decode, all mapped with the surrounding logic. A large array becomes a macro instance instead: the tool connects a compiled block and never builds the storage from gates. This lesson records the flop side of that choice on five shapes that share one read style, a synchronous read with one write port, so that only size moves. Four depths at 8 bits wide trace the cost against entries; a fifth shape keeps 16 entries but widens the word to 32 bits, which tests whether the cost follows entries or total bits.

    The pass sequence

    Every shape runs the same passes, and the script asserts each stage: exactly one $mem_v2 cell after collection, and none after mapping. Proc turns the always block into memory write cells plus a register for the read data. Next memory with -nomap, the usual flow of proc followed by memory collection, gathers the writes and the read into that single cell. Then memory_map converts it into word-wide $dff cells plus decoders and read muxes. Finally techmap, library mapping through dfflibmap and ABC against course.lib with typical.constr, and stat -liberty, which is where the recorded chip area comes from. Quote chip area from stat -liberty, never the ABC summary line: the summary covers only mapped logic while stat totals the module including flops.

    yosys
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    read_verilog <<VERILOG
    module r08 (input clk, input we, input [2:0] waddr, input [2:0] raddr, input [7:0] wdata, output reg [7:0] rdata);
      reg [7:0] mem [0:7];
      always @(posedge clk) begin
        if (we) mem[waddr] <= wdata;
        rdata <= mem[raddr];
      end
    endmodule
    VERILOG
    hierarchy -check -top r08
    proc
    memory -nomap
    select -assert-count 1 t:$mem*
    memory_map
    select -assert-none t:$mem*
    tee -o gen_08.txt stat
    techmap
    opt
    dfflibmap -liberty course.lib
    tee -o abc_08.txt abc -liberty course.lib -constr typical.constr -D 100000
    tee -o stat_08.txt stat -liberty course.lib
    design -reset
    read_verilog <<VERILOG
    module r16 (input clk, input we, input [3:0] waddr, input [3:0] raddr, input [7:0] wdata, output reg [7:0] rdata);
      reg [7:0] mem [0:15];
      always @(posedge clk) begin
        if (we) mem[waddr] <= wdata;
        rdata <= mem[raddr];
      end
    endmodule
    VERILOG
    hierarchy -check -top r16
    proc
    memory -nomap
    select -assert-count 1 t:$mem*
    tee -o dump_16x8.txt dump
    memory_map
    select -assert-none t:$mem*
    tee -o gen_16.txt stat
    techmap
    opt
    dfflibmap -liberty course.lib
    tee -o abc_16.txt abc -liberty course.lib -constr typical.constr -D 100000
    tee -o stat_16.txt stat -liberty course.lib
    design -reset
    read_verilog <<VERILOG
    module r32 (input clk, input we, input [4:0] waddr, input [4:0] raddr, input [7:0] wdata, output reg [7:0] rdata);
      reg [7:0] mem [0:31];
      always @(posedge clk) begin
        if (we) mem[waddr] <= wdata;
        rdata <= mem[raddr];
      end
    endmodule
    VERILOG
    hierarchy -check -top r32
    proc
    memory -nomap
    select -assert-count 1 t:$mem*
    memory_map
    select -assert-none t:$mem*
    tee -o gen_32.txt stat
    techmap
    opt
    dfflibmap -liberty course.lib
    tee -o abc_32.txt abc -liberty course.lib -constr typical.constr -D 100000
    tee -o stat_32.txt stat -liberty course.lib
    design -reset
    read_verilog <<VERILOG
    module r64 (input clk, input we, input [5:0] waddr, input [5:0] raddr, input [7:0] wdata, output reg [7:0] rdata);
      reg [7:0] mem [0:63];
      always @(posedge clk) begin
        if (we) mem[waddr] <= wdata;
        rdata <= mem[raddr];
      end
    endmodule
    VERILOG
    hierarchy -check -top r64
    proc
    memory -nomap
    select -assert-count 1 t:$mem*
    memory_map
    select -assert-none t:$mem*
    tee -o gen_64.txt stat
    techmap
    opt
    dfflibmap -liberty course.lib
    tee -o abc_64.txt abc -liberty course.lib -constr typical.constr -D 100000
    tee -o stat_64.txt stat -liberty course.lib
    design -reset
    read_verilog <<VERILOG
    module rW32 (input clk, input we, input [3:0] waddr, input [3:0] raddr, input [31:0] wdata, output reg [31:0] rdata);
      reg [31:0] mem [0:15];
      always @(posedge clk) begin
        if (we) mem[waddr] <= wdata;
        rdata <= mem[raddr];
      end
    endmodule
    VERILOG
    hierarchy -check -top rW32
    proc
    memory -nomap
    select -assert-count 1 t:$mem*
    memory_map
    select -assert-none t:$mem*
    tee -o gen_w32.txt stat
    techmap
    opt
    dfflibmap -liberty course.lib
    tee -o abc_w32.txt abc -liberty course.lib -constr typical.constr -D 100000
    tee -o stat_w32.txt stat -liberty course.lib
    log PASS: five flop arrays collect to one memory each and map with recorded areas

    A commercial flow reaches the same fork one step later: it keeps the $mem_v2 cell and binds a compiled macro when one fits. Yosys cannot read a vendor macro, so everything here maps to flops; the macro side enters later as a labelled assumption.

    Capacity arithmetic

    Capacity is depth times width in bits, bytes are bits divided by eight, and the address width is the ceiling of log2 of the depth. The program below proves all three for the five shapes.

    example.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    import sys
    
    # Capacity arithmetic for the five sweep shapes: depth, width, bits, bytes, address bits.
    cases = [
        (8, 8, 64, 8, 3),
        (16, 8, 128, 16, 4),
        (32, 8, 256, 32, 5),
        (64, 8, 512, 64, 6),
        (16, 32, 512, 64, 4),
    ]
    
    def addr_bits(depth):
        n = 0
        while (1 << n) < depth:
            n = n + 1
        return n
    
    bad = 0
    for i in range(len(cases)):
        depth, width, want_bits, want_bytes, want_addr = cases[i]
        bits = depth * width
        total_bytes = bits // 8
        abits = addr_bits(depth)
        if bits != want_bits:
            print("FAIL: case " + str(i) + " bits " + str(bits))
            bad += 1
        if total_bytes != want_bytes:
            print("FAIL: case " + str(i) + " bytes " + str(total_bytes))
            bad += 1
        if abits != want_addr:
            print("FAIL: case " + str(i) + " addr " + str(abits))
            bad += 1
        if bits % 8 != 0:
            print("FAIL: case " + str(i) + " not a whole number of bytes")
            bad += 1
    
    if bad != 0:
        sys.exit(1)
    print("PASS: capacity arithmetic holds on 5 sweep shapes")

    Count the five shapes in bits from here on: 64, 128, 256, and 512 across the depth sweep, plus a second 512-bit shape built wide. If cost follows bits, the two 512-bit shapes land together.

    What the sweep printed

    Predict first Doubling the entries doubles the storage bits, but the read muxes and write decode grow too. Will the recorded chip area double cleanly, or does the access logic bend the curve?

    It doubles cleanly. The area moves from 1244 at 8 entries to 9736 at 64 entries, tracking the bits at 19 area units per bit, with no fixed block anywhere: each entry pays its own flops and muxing.

    Shape

    Bits

    Generic cells

    Word flops

    Read muxes

    Chip area (library units)

    8 x 8

    64

    165

    9

    74

    1244

    16 x 8

    128

    323

    17

    146

    2473

    32 x 8

    256

    637

    33

    290

    4890

    64 x 8

    512

    1255

    65

    578

    9736

    16 x 32

    512

    1091

    17

    530

    9745

    Read the table top to bottom. The generic cells climb from 165 to 1255, the word flops from 9 to 65, and the read muxes from 74 to 578. The 16-entry shape records 17 word flops: sixteen words of storage plus the registered read data that memory_dff absorbed into the port, as the next lesson shows. No column holds a fixed block: every entry pays its own flops and muxing, which is the linear cost the chart draws.

    Recorded chip area against depth for the four 8-bit-wide flop arrays, from 1244 units at 8 entries to 9736 units at 64 entries: a straight line through the bits.

    The dump file for the 16-entry shape shows what collection saw: one $mem_v2 cell with WIDTH 8, SIZE 16, and ABITS 4, one read port and one write port. The mapping that follows builds exactly that many words of flops and no macro.

    $mem_v2 parameter

    Recorded value

    WIDTH

    8

    SIZE

    16

    Address bits (ABITS)

    4

    Read ports

    1

    Write ports

    1

    The macro side of the crossover

    The open tools cannot produce a macro area, so the macro side of this lesson is a labelled assumption, and everything built on it says so. A compiled macro is one fixed block: periphery, decoders, and a timing model around dense bit cells, barely moving while the shape stays inside one compiled size. Against it the flop line keeps climbing at the recorded slope, so the two must cross. The calc block below prices that crossing with the recorded slope on one side and a hand-written block on the other.

    Diagram
    hand-written assumption, not a recording: smallest compiled macro block = 20000 area units (fixed block plus wrapper)
    recorded flop slope = 19 area units per bit (course.lib teaching units, from the depth sweep above)
    crossover bits = 20000 / 19 = about 1053 bits, roughly one kilobit

    State the rule plainly: while bits times 19 stays below the assumed block, flops win; here that is about 1053 bits, roughly one kilobit. Above it, price your own compiler's smallest macro, because the crossing moves with the macro list. A small table never reaches the crossing: at 256 bits the flop array costs a fraction of the assumed block and needs no wrapper, model, or compiler run. Port count, read style, and reset needs move the boundary further toward flops; the next three lessons record each effect.

    Break it: widen the word

    The fifth shape in the script is the break-it: the same 16 entries with 32-bit words, so 512 bits built wide rather than deep. If cost follows entries it should resemble the 16-entry run; if it follows bits it should land near the 64-entry run, which also holds 512 bits. The recording settles it: 1091 generic cells with 530 read muxes and area 9745, against 1255 cells and area 9736 for the deep shape. Same bits, same cost within the noise of two mux trees. Cost follows bits, not shape, which is why the crossover is written in bits.

    Common mistakes

    A large table with no bound macro still synthesizes: it becomes an enormous flop array with wide read muxes, discovered in the area report. Bind large tables deliberately. And never quote a crossover as measured unless both sides were measured: here only the flop slope is recorded, and the macro block is labelled an assumption every time.

    When does this table stay flops, and where is the boundary?

    • Weak answer: small tables stay flops and big ones become macros, with the boundary somewhere in the kilobytes.

    • Strong answer: the recorded flop slope is 19 area units per bit with no fixed block, so a 256-bit table costs a fraction of the assumed 20000-unit macro block and stays flops. Against that slope the crossing sits near 1053 bits, roughly one kilobit, and few ports, a sync read, and no per-bit reset are the other conditions the next lessons check.

    • Follow-up: your table needs an async read. What changes? The macro option disappears for most compiled macros, so the table stays flops at any size and the crossover never applies.

    Check yourself The 64-entry shape holds 8-bit values. How many bits and bytes is that, how many address bits, and what area did the run record?

    512 bits, which is 64 bytes, with 6 address bits. The run recorded area 9736, and 65 word flops: 64 words of storage plus the absorbed read register.

    Check yourself A 256-bit table needs an async read and per-bit reset. Flops or macro, and what does the crossover say?

    Flops. The crossover at roughly one kilobit against the assumed macro block already points at flops at 256 bits, and the async read plus per-bit reset each independently rule out most compiled macros, so no macro comparison is needed.

    • Flop cost grows linearly with bits: every entry pays its flops and its muxing, with no fixed block.

    • The recorded slope is about nineteen area units per bit in teaching units; the macro block is an assumption.

    • Cost follows bits, not shape: equal-bit wide and deep shapes land together.

    Rule of thumb: quote the slope from the run and label the macro block as an assumption every time.

    How this was checked

    Every number and picture on this page comes from Yosys runs of the script shown, recorded by the course checker (scripts/tools/run-rtl-testbenches.mjs with COURSE=synth): five 8-bit and 32-bit shapes run proc, memory -nomap, memory_map, techmap, dfflibmap, and ABC against course.lib with typical.constr, with one $mem_v2 cell asserted per shape and none after mapping. The capacity checks come from the stdlib-only Python program shown, which asserts every figure it prints. The macro block is a hand-written assumption labelled on the page, not a recording.

    • Interview: run a mock interview on this topic

    Related topics

    • Next: Read and Write Port Styles

    • Register files

    • Flops, Resets and Enables

    Previous

    62 / 139

    Next: Read and Write Port Styles

    Up next

    Up nextRead and Write Port Styles

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Byte Enables and Bypass Wrappers
    • Compilers, Macros and Wrappers