The design brief
Write the one-screen brief that fixes what a block must do, sketch its registers and logic from it, and only then write the RTL.
- Lesson
- R1.2
- Time
- 25 min
- Runs on
- Your browser
- Check
- 5 questions
Two engineers read one line of a spec: buffer eight beats between the producer and the consumer. One built a buffer that held eight beats and stalled the producer on the ninth. The other added an output register, held nine beats, and never stalled at nine. Integration failed on the ninth beat, because nobody had written down the stall rule.
- Beats 1 to 8
Both buffers take eight beats. In reading B the first beat moves on into the output register.
Follow the calls, values and waits
- data beat 1: Buffer → Output register
- Beat 9, reading A
The buffer is full. The producer stalls on the ninth beat.
Follow the calls, values and waits
- Waiting: Producer — stalled
- Beat 9, reading B
One entry is still free, because beat 1 sits in the register. The ninth beat goes in and the producer does not stall.
Follow the calls, values and waits
- data beat 9: Producer → Buffer
Read the diagram as text
- Producer: reading A
- Buffer: 8 entries
- Consumer: stalled
- Producer: reading B
- Buffer: 8 entries
- Output register: one more beat
- Consumer: stalled
- Producer → Buffer: beats
- Buffer → Consumer: beats
- Producer → Buffer: beats
- Buffer → Output register: beats
- Output register → Consumer: beats
Both blocks match the sentence. A producer built for one of them fails against the other.
Six parts that settle a block
RTL starts before the first line of code, with a brief: one screen that fixes what the block does. A bench can be built from it, a reviewer can read it, and two engineers cannot read it two ways. A brief has six parts.
Ports
Every signal, with its direction, width and meaning.
Clock and reset
Which clock edge, which reset style, and the value of every register in reset.
Function
How outputs follow from inputs, with the exact rules for rounding, wrap and priority.
Timing
Latency, the clock edges from an input to its output. Throughput, the items per cycle. And what happens on a stall.
Parameters
Names, legal ranges and defaults of everything the block lets a user set.
Corner cases
The inputs that decide between two readings: a ninth beat, a load and a count in one cycle, a read from an empty buffer.
| Port | Direction | Width | Meaning |
|---|---|---|---|
| clk | input | 1 | clock |
| rst_n | input | 1 | reset, asynchronous, active low |
| load | input | 1 | sets count to d |
| d | input | [3:0] | the value load sets |
| up | input | 1 | adds 1 when alone |
| down | input | 1 | subtracts 1 when alone |
| count | output | [3:0] | load sets count to d; otherwise up alone adds 1, down alone subtracts 1; both or neither hold |
- Clock
- rising edge of
clk - Reset
rst_nasynchronous, active low; count is 0 in reset- Latency
- one edge of latency: the inputs at an edge decide count after that edge
- Throughput
- one update per cycle
- Parameters
- none: 4 bits wide
- Corner cases
loadwithupordown: load wins; up at 15 wraps to 0; down at 0 wraps to 15;upanddowntogether hold
Which part settles the eight beats? Latency and throughput: they say when the producer must stop. One more line there would have given both engineers the same block.
From the brief to a sketch
From the brief comes the microarchitecture: which values the block must remember and which it computes. A remembered value becomes a register, a group of flip-flops that change only on a clock edge. A computed value becomes combinational logic, gates whose outputs follow their inputs. Draw registers as boxes, logic as clouds, and mark the clock edges.
- What is remembered
Count must survive from one edge to the next, so it is a register: four flops, 0 in reset.
- What is computed
The next count depends on the inputs and the old count, so it is logic: a cloud with no memory.
Follow the calls, values and waits
- data old count: count register → next count
- Where the edge falls
At the edge the register takes the cloud's value. Count changes one edge after its inputs: the latency in the brief.
Follow the calls, values and waits
- data count + 1: next count → count register
- data count + 1: count register → count
Read the diagram as text
- load, d: inputs
- up, down: inputs
- next count: logic: load, +1, -1 or hold
- clk, rst_n: edge and reset
- count register: 4 flops, 0 in reset
- count: output
- load, d → next count: inputs
- up, down → next count: inputs
- next count → count register: next value
- count register → next count: old count
- clk, rst_n → count register: edge
- count register → count: count
Each box becomes an always_ff block. Each cloud becomes an always_comb block, an assign, or the right-hand sides inside the always_ff.
The sketch settles latency before any code. Only then comes the RTL: one module with the brief's ports, one clocked block per register group, one block or assignment per cloud.
The corner the code left out
The lab's counter follows the sketch with four lines of priority. It counts, loads and wraps as the brief says. It has no line for up and down together, so the up line wins there and count moves. The bench carries the brief as a reference and marks every edge where count disagrees.
- Cycle 1
Reset holds count at 0, as the brief says.
rst_nrises just after this edge. - Cycle 3
upalone: count goes 1, then 2. One edge of latency. - Cycle 4
upanddowntogether. The brief says hold at 2; the code reaches theupline first and counts to 3. - Cycle 5
loadanduptogether: load wins and count is d, 0. This corner is right. - Cycle 7
Down from 0 wraps to 15, up from 15 wraps to 0: both as the brief says.
- Cycle 9
upanddowntogether again: 10, where the brief says 9. The same missing corner, the second marked edge.
Measured on the bench. Edges 4 and 9 break the brief; the other nine edges match it.
As shipped
else if (load) count <= d;
else if (up) count <= count + 4'd1;
else if (down) count <= count - 4'd1;
Up and down together count up. 24 cells, 4 flops, 7 levels of logic on this flow.
With the corner from the brief
else if (load) count <= d;
else if (up && down) count <= count;
else if (up) count <= count + 4'd1;
else if (down) count <= count - 4'd1;
Up and down together hold. Also 24 cells, 4 flops, 7 levels of logic.
Yosys 0.70 builds both counters from 24 cells, and Lint is clean on both. Neither tool has read the brief, so neither can tell the right counter from the wrong one. Only a bench built from the brief can. That is why every problem on this site opens with a brief.
Run the lab and find the two marked edges. Add the missing line above the up line and run again: no edge breaks the brief. Synthesize still reports 24 cells, 4 flops and 7 levels of logic.
Asked in interviews
What do you write down before coding a block?
Ports, clock and reset with the reset values, and the function with its priority and wrap rules. Then latency, throughput and the stall rule, parameters with legal ranges, and the corner cases that decide between two readings.
How do you decide what becomes a register?
A value that must survive from one clock edge to the next is a register. A value computed from the current inputs and registers is logic. Sketch both before coding; the sketch fixes the latency.
Why does the testbench team want the brief and not the RTL?
A bench built from the RTL checks that the code does what the code does. A bench built from the brief can find a corner the RTL got wrong, such as up and down in the same cycle.
Try it
Edges 0 and 1 are in reset. From edge 2 the bench walks every corner of the brief: counting, load against up and against down, both wraps, and up with down. Edges 4 and 9 are marked: count moved where the brief says hold.
Add else if (up && down) count <= count; after the load line. Edge 4 then shows 2, edge 9 shows 9, and the last line reads 0 edges broke the brief. The Gates tab does not change: 24 cells, 4 flops, 7 levels.
Synthesize the design to measure its cells and inspect the mapped gates.
e0 load=0 d=0 up=0 down=0 count=0 e1 load=0 d=0 up=0 down=0 count=0 e2 load=0 d=0 up=1 down=0 count=1 e3 load=0 d=0 up=1 down=0 count=2 e4 load=0 d=0 up=1 down=1 count=3 brief says 2 e5 load=1 d=0 up=1 down=0 count=0 e6 load=0 d=0 up=0 down=1 count=15 e7 load=0 d=0 up=1 down=0 count=0 e8 load=1 d=9 up=0 down=1 count=9 e9 load=0 d=9 up=1 down=1 count=10 brief says 9 e10 load=0 d=9 up=0 down=0 count=10 2 edges broke the briefOriginal design · 24 cells · 4 flip-flops · 0 latches · 7 logic levels. Synthesis uses Yosys; counts depend on the toolchain.
Read included file: tb.sv
`timescale 1ns/1ps
module tb;
logic clk = 0;
always #5 clk = ~clk; // rising edges at 5, 15, 25, ...
logic rst_n = 1; // falls at 1 ns: a real edge, so an asynchronous reset acts before any clock
int edge_n = -1;
always @(posedge clk) edge_n = edge_n + 1;
logic load = 0, up = 0, down = 0;
logic [3:0] d = 0;
logic [3:0] count;
logic [3:0] want; // what the brief says count must be after this edge
int broken = 0;
always @(posedge clk) // reads the old count: the design updates it later in the step
if (!rst_n) want = 4'd0;
else if (load) want = d;
else if (up && down) want = count;
else if (up) want = count + 4'd1;
else if (down) want = count - 4'd1;
else want = count;
updown4 dut (.clk(clk), .rst_n(rst_n), .load(load), .d(d), .up(up), .down(down), .count(count));
// Advance to the next rising edge, wait for the design to settle, then print one line.
task automatic tick();
@(posedge clk); #1;
if (count !== want) broken++;
if (count === want) $display("e%0d load=%b d=%0d up=%b down=%b count=%0d", edge_n, load, d, up, down, count);
else $display("e%0d load=%b d=%0d up=%b down=%b count=%0d brief says %0d", edge_n, load, d, up, down, count, want);
endtask
initial begin
#1 rst_n = 0; tick(); tick(); rst_n = 1; // asserted at 1 ns, edges 0 and 1 in reset, released 1 ns after edge 1
up = 1; tick(); // count up
tick();
down = 1; tick(); // up and down together: hold
load = 1; d = 0; down = 0; tick(); // load and up together: load wins
load = 0; up = 0; down = 1; tick(); // down from 0: wrap to 15
down = 0; up = 1; tick(); // up from 15: wrap to 0
load = 1; d = 9; up = 0; down = 1; tick(); // load and down together: load wins
load = 0; up = 1; tick(); // up and down together again
up = 0; down = 0; tick(); // idle: hold
$display("%0d edges broke the brief", broken);
$finish(0); // quiet: no "$finish called" line
end
endmodule
Check yourself
Remember
- A brief fixes ports, clock and reset, function, timing, parameters and corner cases on one screen, before any RTL.
- From the brief, sketch the microarchitecture: remembered values are registers, computed values are logic, and the sketch fixes the latency.
- Synthesis and lint cannot tell a counter that holds from one that counts up; only a bench built from the brief can.
- Write the corner cases into the brief; each one becomes a line of RTL and a line of the bench.