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

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • RTL Foundations36
  • Combinational Design25
  • Sequential Design27
  • FSM & Control27
  • Timing & Clocking39
  • Reset & CDC34
  • Memory & Buses57
    • SRAM and ROM
      • Inference Starts from Coding Style
      • Single-Port Synchronous RAM Template
      • Read-First vs Write-First
      • Dual-Port RAM Patterns
      • True Dual-Port RAM and Collisions
      • ROM Inference
    • Register Files
    • Valid/Ready Handshake
    • AXI-Lite
    • AXI-Stream
    • Bus Integration
    • Synchronous FIFO
    • Register-Map and CSR RTL
    • Memory Reliability
    • Peripheral RTL Integration
    • Full AXI4: RTL Roadmap
  • Advanced RTL28
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

‹SRAM and ROM
  1. Topics
  2. RTL Design
  3. Memory & Buses
  4. SRAM and ROM
  5. ROM Inference

Part 7 · Memory & Buses · Intermediate

ROM Inference

Fixed table contents from a function-filled array or a case statement, checked against a model.

On this page

    Why this matters

    A table written as an array maps to a block ROM only if its read is synchronous. A combinational read turns it into logic, and an ASIC ROM is a library macro, not the RTL array.

    After this lesson you can: write a ROM in array style and in case style, and choose between them by table size and read timing.

    Two shapes

    A ROM holds fixed contents with no write port. The array style suits large tables and the case style suits small ones.

    The full listing sits under Testbench below.

    Style

    Yosys result

    Use for

    rom_sine: array filled in an initial block, synchronous read

    1 memory cell, no write port, initialised contents: a ROM

    Tables larger than a few dozen words: sine, CRC, font, microcode

    rom_case: case on the address

    3 comparators and a pmux: pure logic, no memory cell

    Tiny tables (up to ~16 entries) where a LUT is cheaper than a block

    The two ROM shapes: rom_sine is one memory cell with initial contents and a clocked read; rom_case is constants selected by comparators and a pmux

    Read the left half as one block with its contents loaded at configuration, and the right half as plain gates. The table above gives the Yosys result for each.

    • Left: the array has no write port, and its clocked read lets it map to a block ROM: worth it for tables larger than a few dozen words.

    • Right: a 4-entry case costs a few comparators and a mux, and reads in the same cycle. A ROM block would waste most of its size.

    For FPGAs, the initial block (or $readmemh) becomes the memory's initial contents in the bitstream. For ASICs, a ROM macro is a separate library cell with fixed contents, and the RTL array is only a simulation model.

    Check which flow the project targets.

    Practical points

    • Use a function or a file to generate contents so the table can be regenerated; hand-typed numbers rot.

    • Prefer a synchronous read (as in rom_sine) so the table maps to a block ROM. A combinational read turns it into logic.

    • If the table is read in only a few places, a case may be smaller than a memory block with its control overhead.

    • Size limits: a function with $sin is evaluated at elaboration time; keep it deterministic and check that your synthesis tool accepts the system functions.

    Tip

    Repeating the generator expression checks storage, addressing and latency, but shares any numerical mistake in that expression. Add independently derived anchors and shape properties; an independently generated high-precision table is needed for full numerical validation.

    Common pitfall

    $readmemh paths and file formats differ between simulators and synthesis tools. Keep the file in the project, use a relative path, and check the first and last words after loading.

    Check yourself Why is a 4-entry lookup table better written as a case than as a ROM array?

    A block RAM has a large fixed minimum size and timing overhead; four entries cost just a few gates as logic. The case also reads combinationally with no extra cycle.

    Testbench

    The testbench compares all 64 sine entries with the generator, adds independent quarter-wave anchors and monotonicity and symmetry checks with a one-code tolerance for truncation, and checks all four case entries.

    rom_sine.sv
    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
    // A 64-entry sine ROM (unsigned, 8 bit): contents defined by a function, read synchronously.
    module rom_sine (
      input  logic       clk,
      input  logic [5:0] addr,
      output logic [7:0] dout
    );
      logic [7:0] mem [64];
    
      // Contents are fixed at elaboration time. Tools turn this into a ROM (block RAM with an init file, or LUTs).
      initial begin
        for (int i = 0; i < 64; i++)
          mem[i] = 8'($rtoi(127.5 + 127.5 * $sin(6.283185307179586 * i / 64.0)));
      end
    
      always_ff @(posedge clk) dout <= mem[addr];       // synchronous read, no write port
    endmodule
    
    // Same idea with a case statement: good for small tables, tools infer a LUT/ROM, no array needed.
    module rom_case (
      input  logic [1:0] sel,
      output logic [7:0] dout
    );
      always_comb begin
        unique case (sel)
          2'd0:    dout = 8'h3C;
          2'd1:    dout = 8'h5A;
          2'd2:    dout = 8'hA5;
          2'd3:    dout = 8'hC3;
          default: dout = 8'h00;
        endcase
      end
    endmodule
    

    Remember

    • An array with a synchronous read maps to a block ROM. A combinational read becomes logic.

    • A tiny table, such as four entries, is cheaper as a case than as a memory block with its overhead.

    • On an FPGA the initial contents go into the bitstream. On an ASIC the ROM macro is a separate cell.

    Rule of thumb: generate the table from a function or a file, because hand-typed numbers rot.

    How this was checked

    Source checker, run with Icarus Verilog 13.0 (2026-10-16): prints PASS. It compares all 64 sine entries with the generator, adds independent quarter-wave anchors, monotonicity and symmetry checks with a one-code tolerance for truncation, and checks all four case entries. Target ROM mapping is not checked, and these finite properties do not prove every numerical value.

    Keep going

    • Practice: ROM module with pre-initialised contents

    • Code lab: ROM lookup 16-entry

    • Interview: run a mock interview on this topic

    Related topics

    • Next: Register files

    Previous

    194 / 350

    Next: Register Files

    Up next

    Up nextRegister Files

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Inference Starts from Coding Style
    • Single-Port Synchronous RAM Template
    • Read-First vs Write-First