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.

One sentence, two buffersArrows carry beats. The consumer is stalled, so every beat the producer sends must be held somewhere.
One sentence, two buffersArrows carry beats. The consumer is stalled, so every beat the producer sends must be held somewhere. source Producer reading A queue Buffer 8 entries sink Consumer stalled source Producer reading B queue Buffer 8 entries component Output register one more beat sink Consumer stalled beatsbeatsbeatsbeatsbeats
  1. 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
    1. data beat 1: Buffer → Output register
  2. Beat 9, reading A

    The buffer is full. The producer stalls on the ninth beat.

    Follow the calls, values and waits
    1. Waiting: Producer — stalled
  3. 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
    1. 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.

  1. Ports

    Every signal, with its direction, width and meaning.

  2. Clock and reset

    Which clock edge, which reset style, and the value of every register in reset.

  3. Function

    How outputs follow from inputs, with the exact rules for rounding, wrap and priority.

  4. Timing

    Latency, the clock edges from an input to its output. Throughput, the items per cycle. And what happens on a stall.

  5. Parameters

    Names, legal ranges and defaults of everything the block lets a user set.

  6. 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.

The brief of this lesson's lab: a 4-bit up/down counter with load.
PortDirectionWidthMeaning
clkinput1clock
rst_ninput1reset, asynchronous, active low
loadinput1sets count to d
dinput[3:0]the value load sets
upinput1adds 1 when alone
downinput1subtracts 1 when alone
countoutput[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_n asynchronous, 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
load with up or down: load wins; up at 15 wraps to 0; down at 0 wraps to 15; up and down together 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.

The counter as a register and a cloudArrows carry values. The register holds count between edges; the cloud computes the next count from the inputs and the old count.
The counter as a register and a cloudArrows carry values. The register holds count between edges; the cloud computes the next count from the inputs and the old count. input load, d inputs input up, down inputs logic next count logic: load, +1, -1 orhold input clk, rst_n edge and reset register count register 4 flops, 0 in reset output count output inputsinputsnext valueold countedgecount
  1. What is remembered

    Count must survive from one edge to the next, so it is a register: four flops, 0 in reset.

  2. 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
    1. data old count: count register → next count
  3. 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
    1. data count + 1: next count → count register
    2. 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.

The counter as shipped, against its briefOne value per edge, from edge 0. Inputs are the values present at the edge; count is the value after it.
012345678910clkrst_nloadd09updowncount01230150910
  1. Cycle 1

    Reset holds count at 0, as the brief says. rst_n rises just after this edge.

  2. Cycle 3

    up alone: count goes 1, then 2. One edge of latency.

  3. Cycle 4

    up and down together. The brief says hold at 2; the code reaches the up line first and counts to 3.

  4. Cycle 5

    load and up together: load wins and count is d, 0. This corner is right.

  5. Cycle 7

    Down from 0 wraps to 15, up from 15 wraps to 0: both as the brief says.

  6. Cycle 9

    up and down together 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
  1. 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.

  2. 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.

  3. 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.

RTLIcarus Verilog · in your browser · Lint: Verilator service · Synthesize: Yosys service
Edit the design, then Run, Lint or Synthesize
Expected: 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 brief

Original 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

Predict the outputThis counter was written from a brief that never said whether load or up wins. After the first step count is 5. What does the program print?
module ctr (input logic clk, rst_n, load, up, input logic [3:0] d, output logic [3:0] count);
  always_ff @(posedge clk or negedge rst_n)
    if (!rst_n)    count <= 4'd0;
    else if (up)   count <= count + 4'd1;
    else if (load) count <= d;
endmodule
module tb;
  logic clk = 0, rst_n = 0, load = 0, up = 0;
  logic [3:0] d = 0, count;
  ctr dut (.clk(clk), .rst_n(rst_n), .load(load), .up(up), .d(d), .count(count));
  task automatic step(); #1 clk = 1; #1 clk = 0; endtask
  initial begin
    #1 rst_n = 1;
    load = 1; d = 4'd5; step();        // count = 5
    load = 1; d = 4'd12; up = 1; step();
    $display("count=%0d", count);
    $finish(0);
  end
endmodule
Spot the bugEach option is the body of the counter after reset. The bench checks load, up, down, up with down, and load with both. Which body breaks the brief of this lesson?
ConceptWhich part of a brief would have settled the ninth beat in the hook?
ConceptIn the sketch, count is a box: a register. What is a register in the RTL?
Read the waveformThis is the bench run of the lab. At which edge does the counter first break its brief?
The counter as shipped
012345678910loadupdowncount01230150910

One value per edge from edge 0; count is the value after the edge. Reset is released after edge 1.

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.