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

  • How to Use This Course
  • Reading Paths
  • Concept Map
  • Meet DCRouter
  • Glossary
  • Part 0: What Verification Is8
  • Part 1: What Can Go Wrong11
  • Part 2: Specifications as Checkable Rules8
    • Intent and the Oracle
    • From Prose to Rules
    • Reference Models
    • Tables and Executable Specifications
    • Specification Review and Ambiguity
    • Requirements Traceability
    • What Not to Check
    • Drills: Specifications as Checkable Rules
  • Part 3: Observability and Controllability7
  • Part 4: Stimulus11
  • Part 5: Checking11
  • Part 6: Coverage10
  • Part 7: Formal and Simulation10
  • Part 8: Verification Levels and Reuse8
  • Part 9: Planning and Risk9
  • Part 10: Emulation and Prototyping8
  • Part 11: Post-Silicon Validation8
  • Part 12: Bug Economics8
  • Part 13: Debug7
  • Part 14: Sign-Off7
  • Part 15: Verification in Context8
  • Part 16: Case Studies5
  • Part 17: Capstones5
  • Part 18: Interview Preparation6
Loading lesson

‹Part 2: Specifications as Checkable Rules
  1. Topics
  2. First Principles of Verification Strategy
  3. Part 2: Specifications as Checkable Rules
  4. Reference Models

Part 2 · Specifications as Checkable Rules · Senior (S)

Reference Models

A reference model computes what the design should produce, so a checker can compare. Transaction-level versus cycle-level models, what each can and cannot predict, and DCRouter step 3: a Python model that predicts every packet's output, error flag and the counters, validated against a published CRC check value.

On this page

    Note

    Level: Senior (S) · Answers: What exactly are we checking against?

    Before this: From Prose to Rules

    Four inputs send DCRouter random packets of random lengths to random outputs, some with corrupted CRCs, some with illegal lengths. Thousands of flits leave the outputs, interleaved in an order decided cycle by cycle by four arbiters. What should have come out? Writing the answer by hand is impossible for one test, let alone a regression. A program has to compute it, from the specification, for any input.

    What should a reference model predict, and how precise does it need to be?

    The obvious answer, and why it breaks

    The obvious reference model copies the design's behaviour cycle by cycle, so its outputs can be compared flit for flit. Such a model is nearly a second RTL implementation: as hard to write, as likely to contain bugs, and bound to one micro-architecture, so every legal design change breaks it. It also predicts things the specification never promised, such as exactly which input wins an arbitration in which cycle.

    The idea: predict what the specification promises

    Model level

    Predicts

    Cost

    Use when

    Transaction level

    What leaves where, with which content and flags, in which order per flow

    Small, fast, close to the specification

    The specification promises results and per-flow order, not timing

    Cycle level

    Exact values on every signal in every cycle

    Large, slow, tied to one implementation

    Timing itself is specified (fixed latency pipelines)

    Hybrid

    Transaction results plus bounds on timing

    Moderate

    Results plus latency or throughput limits

    DCRouter's specification promises that each packet leaves on its output whole, in its input's order, with out_err set by rule, and that the counters follow rules R9 and R10. It does not promise how packets from different inputs interleave. So DCRouter's model is transaction level, organised by flow: one ordered list per (input, output) pair.

    Build it: DCRouter step 3

    The model builds packets exactly as the specification describes, checks its own CRC against the catalogued CRC-8/SMBUS check value (fact M6), then predicts a mixed workload of 24 packets with two corrupted CRCs and one zero-length header.

    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
    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
    """DCRouter step 3: a transaction-level reference model.
    
    Given the packets offered on each input, the model predicts what the specification
    promises at the outputs (rules R3, R5, R8, R9, R10):
      - which output each packet leaves on (its dst), whole and in its input's order
      - out_err on its tail when len is 0 or the CRC is wrong
      - ERR_CNT per input (saturating at 255) and PKT_CNT per output
    It deliberately does not predict how packets from different inputs interleave at an
    output: the specification leaves arbitration order to the design, so a transaction-level
    model must not pin it down. First the model's CRC is checked against the catalogued
    CRC-8/SMBUS check value, then it predicts a small mixed workload.
    """
    import random
    
    def crc8_byte(c, b):
        c ^= b
        for _ in range(8):
            c = ((c << 1) ^ 0x07) & 0xFF if c & 0x80 else (c << 1) & 0xFF
        return c
    
    def crc8_flits(flits):                     # each 16-bit flit, high byte first (DCRouter spec)
        c = 0
        for f in flits:
            c = crc8_byte(crc8_byte(c, f >> 8), f & 0xFF)
        return c
    
    def make_packet(src, seq, dst, prio, length, payload, bad_crc=False):
        header = (dst << 14) | (prio << 13) | (length << 9) | (src << 7) | seq
        flits = [header] + payload
        c = crc8_flits(flits)
        return flits + [(c ^ 0xFF) if bad_crc else c]
    
    def predict(inputs):
        """inputs: list of 4 lists of packets (lists of flits). Returns per-output streams and counters."""
        per_flow = {}                           # (src, dst) -> list of (flits, err) in order
        err_cnt = [0] * 4
        pkt_cnt = [0] * 4
        for src, packets in enumerate(inputs):
            for p in packets:
                header, tail = p[0], p[-1]
                dst, length = header >> 14, (header >> 9) & 0xF
                err = length == 0 or (tail & 0xFF) != crc8_flits(p[:-1])
                per_flow.setdefault((src, dst), []).append((p, err))
                pkt_cnt[dst] += 1
                if err:
                    err_cnt[src] = min(255, err_cnt[src] + 1)
        return per_flow, err_cnt, pkt_cnt
    
    check = crc8_flits([0x3132, 0x3334, 0x3536, 0x3738]) 
    check = crc8_byte(check, 0x39)
    print(f"CRC-8 of \"123456789\": 0x{check:02X} (catalogued check value 0xF4)")
    
    rng = random.Random(9)
    inputs = [[], [], [], []]
    for src in range(4):
        for seq in range(6):
            length = rng.choice([0, 1, 15]) if seq == 0 else rng.randint(1, 15)
            inputs[src].append(make_packet(src, seq, rng.randrange(4), rng.randrange(2), length,
                                           [rng.randrange(1 << 16) for _ in range(length)],
                                           bad_crc=(src == 1 and seq in (2, 4))))
    flows, err_cnt, pkt_cnt = predict(inputs)
    for (src, dst), pkts in sorted(flows.items()):
        print(f"input {src} -> output {dst}: {len(pkts)} packet{'s' if len(pkts) != 1 else ' '}, seq " +
              " ".join(f"{p[0] & 0x7F}{'!' if e else ''}" for p, e in pkts))
    print(f"predicted ERR_CNT {err_cnt}   PKT_CNT {pkt_cnt}")
    
    assert check == 0xF4
    assert sum(pkt_cnt) == 24 and sum(len(v) for v in flows.values()) == 24
    assert err_cnt[1] >= 2
    print("PASS: the model's CRC matches CRC-8/SMBUS, and it accounts for all 24 packets, their outputs, errors and counters")

    It prints:

    Diagram
    CRC-8 of "123456789": 0xF4 (catalogued check value 0xF4)
    input 0 -> output 0: 3 packets, seq 1 2 3
    input 0 -> output 2: 2 packets, seq 0 4
    input 0 -> output 3: 1 packet , seq 5
    input 1 -> output 0: 1 packet , seq 3
    input 1 -> output 1: 2 packets, seq 1 2!
    input 1 -> output 2: 2 packets, seq 4! 5
    input 1 -> output 3: 1 packet , seq 0
    input 2 -> output 0: 2 packets, seq 1 3
    input 2 -> output 1: 2 packets, seq 0 2
    input 2 -> output 3: 2 packets, seq 4 5
    input 3 -> output 0: 4 packets, seq 0! 1 3 5
    input 3 -> output 1: 1 packet , seq 2
    input 3 -> output 2: 1 packet , seq 4
    predicted ERR_CNT [0, 2, 0, 1]   PKT_CNT [10, 5, 5, 4]
    PASS: the model's CRC matches CRC-8/SMBUS, and it accounts for all 24 packets, their outputs, errors and counters

    How to read it: the first line validates the oracle itself, because a model with a wrong CRC would flag every good packet. Each flow line lists the packets that must leave that output from that input, in order, with ! marking those that must carry out_err (corrupted CRC or length 0). The last line gives the counters the registers must show. The model says nothing about how flows interleave at each output; a scoreboard built on it (Part 5) matches packets per flow and so accepts every legal arbitration order.

    Keeping a model trustworthy

    • Validate it independently: against published check values, hand-worked examples, or a second team's numbers.

    • Write it from the specification, not from the RTL, and by someone other than the designer.

    • Predict only what is promised: anything extra becomes a false failure when the design legally differs (lesson 2.7).

    • Keep it simple enough to review: a model as complex as the design is just another design.

    How real teams build reference models

    Practice

    Purpose

    Transaction-level models in Python, C++ or SystemVerilog classes

    Fast to write, independent of the micro-architecture

    Models connected through DPI or monitors

    Fed the same transactions the design sees

    Algorithm teams' golden models for numerical blocks

    An independent source for arithmetic results

    What breaks

    A router team (illustrative) first wrote a cycle-accurate model that predicted exactly which input each arbiter would grant. When designers improved the arbiter's timing, legally, thousands of tests failed overnight with mismatches that were not bugs. Rewriting the model at transaction level, matching per flow, removed every false failure and still caught the real ones, which were packets lost, corrupted or misordered within a flow.

    Why this matters

    Senior lens. Choose the model's level from the specification, not from the design: model results, per-flow order and counters if that is all the specification promises, and add timing only where it is specified. The model is part of the verification plan's risk, so validate it, review it and keep it independent.

    Interview

    Asked: "When would you build a cycle-accurate reference model?"

    Weak answer: Always, so you can compare every cycle.

    Strong answer: Only when the specification promises exact timing, such as a fixed-latency pipeline or a cycle-specified protocol. Otherwise a cycle-accurate model is effectively a second implementation: expensive, buggy in its own way, and tied to one micro-architecture, so legal changes cause false failures. Most models should be transaction level: predict what leaves where, with which content and flags, and in what order per flow, plus counters, and let the scoreboard match per flow so any legal interleaving passes. Timing promises become separate bounds.

    Follow-ups: (1) How do you validate the model itself? (2) What does matching per flow mean for a router?

    Check yourself Why does DCRouter's model organise predictions by (input, output) flow rather than by output?

    The specification fixes the order within each flow but not how flows interleave at an output, so per-flow lists predict exactly what is promised.

    Check yourself Why check the model's CRC against a published check value first?

    A wrong CRC in the model would flag every correct packet as an error, so the oracle itself must be validated before it judges the design.

    Key takeaways

    • A reference model computes the expected outputs for any input: the oracle for data and results.

    • Model at the level the specification promises: usually transactions and per-flow order, not cycles.

    • Validate the model independently and keep it simple enough to review.

    Keep going

    Next: Lesson 2.4, Tables and Executable Specifications. Register maps and state tables as checkable data.

    Related topics

    • Part 2 overview: Specifications as Checkable Rules

    • VIP and compliance course: Transaction Modelling

    Previous

    27 / 160

    Next: Tables and Executable Specifications

    Up next

    Up nextTables and Executable Specifications

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Intent and the Oracle
    • Specification Review and Ambiguity
    • Requirements Traceability