A new block exposes a signal called data_valid. One engineer captures the data bus in the same cycle data_valid is high. Another, reading the same block's documentation, captures it one cycle later, because the text says data is "presented after" data_valid. Both read the word "valid" in the name and assumed they knew what it meant. Every second word in the system is off by one.
If a name does not tell you what a signal means, what does a specification have to tell you?
The tempting answer is that good names make a specification obvious: call it valid, ready, addr, last, and everyone knows. Names help people remember; they do not define anything. valid in one protocol means "the data is here now" and in another "data follows next cycle". last might mark the last byte, the last beat of a burst, or the last packet of a message. A name is a hint to a human, and two humans can take the same hint two ways.
Every protocol specification, whatever its style, has to define three layers:
Layer | What it defines | Questions it must answer |
|---|---|---|
Signals | The wires | Name, width, direction, who drives it, value in reset, electrical form (for off-chip links) |
Meaning | What each value stands for | Encodings (what |
Timing | When a value counts | Which clock edge, how long a value must stay stable, how signals relate in time, what may follow what |
The third layer includes the sequencing rules: what may happen next given what just happened. "A new transfer may start only after the previous one completed" is timing in this sense. Most of the bugs this course discusses are timing-layer misunderstandings, because that layer is the hardest to write down and the easiest to assume.
The same three questions, asked of three very different protocols, using facts from their specifications:
APB (register bus) | UART (serial line) | AXI-Stream (data stream) | |
|---|---|---|---|
Signals | PSEL, PENABLE, PWRITE, PADDR, PWDATA, PRDATA, PREADY | One data line per direction; no clock | TVALID, TREADY, TDATA, TLAST, TKEEP |
Meaning | PWRITE selects write or read; PADDR selects the register | Idle high; start bit low; data bits least significant first; stop bit high | TLAST marks the last transfer of a packet; a byte with TKEEP low is a null byte |
Timing | One-cycle setup phase, then an access phase until PREADY is high; address and data stay constant meanwhile | Each bit lasts one bit time; the receiver samples near the middle of each bit | A transfer happens on an edge where TVALID and TREADY are both high; payload stable while stalled |
Read across a row and you see why one protocol cannot be learned by analogy with another. Read down a column and you have most of what that protocol's specification is about.
Specifications differ in style, but the useful ones contain the same parts. When you open a new one, look for these, roughly in this order:
Overview and scope: what the interface is for, and what it deliberately does not cover.
Signal list: every signal with width, direction, driver and reset value.
Transfer descriptions with timing diagrams: one diagram per kind of transfer, including stalls and back-to-back cases.
Rules: numbered requirements ("must", "must not", "may"). These are the contract in its sharpest form.
State machines: for protocols with phases (setup, access) or link states.
Ordering and concurrency: what may overtake what (Parts 6 and 7).
Errors and exceptional cases: error responses, reset, low power.
Compliance or checks: what an implementation must demonstrate.
When a specification lacks one of these, that gap is where two teams will disagree. Lesson 16.1 turns this list into a full checklist for reading any specification.
Style | Strength | Weakness |
|---|---|---|
Prose ("the receiver samples after...") | Easy to write | Ambiguous; the source of most disagreements |
Timing diagrams | Concrete and visual | Show examples, not rules; a diagram cannot show every case |
Numbered rules (must, must not, may) | Precise and checkable | Hard to read as a whole |
State machines and tables | Complete for phased behaviour | Can hide the meaning behind the mechanics |
Formal properties (assertions) | Executable and unambiguous | Rarely the primary form; needs a tool to read |
Good specifications combine them: diagrams to build intuition, numbered rules to settle arguments, and, increasingly, assertions shipped alongside so implementations can check themselves.
The opening off-by-one is the classic timing-layer bug (illustrative). The block registered its data output, so data appeared one cycle after the internal event; its data_valid was combinational, so it rose in the same cycle as the event. The documentation said data was "presented after data_valid", which one reader took as "a cycle later" and another as "while". The fix was to register data_valid too, so both arrived together, and to replace the sentence with a timing diagram and a rule: "data is valid in every cycle in which data_valid is high".
Symptom: every second captured word wrong, but only with back-to-back traffic.
Evidence: on the waveform, data changes one cycle after data_valid rises.
Root cause: the timing layer was written in prose, and the block's own outputs disagreed with each other.
Fix: align the signals in the RTL and state the timing as a rule, not a sentence.
What happens during a stall (must the data stay stable?), whether transfers can be back-to-back, and what reset looks like. A diagram shows one example; only rules cover every case.
Timing, specifically sequencing: it says what may follow what. It is not about what any value means.
Meaning: it defines how a sequence of line levels maps to a data value.
Every specification defines signals, meaning and timing; a name is only a hint about the first.
The timing layer, including what may follow what, is the hardest to write and the source of most integration bugs.
Precise specifications state rules (must, must not, may) and use diagrams for intuition, not as the definition.