Load, clear and priority

Read the priority of a clocked block from its statement order, write a register whose clear, load and hold follow the brief, and see the priority in the netlist.

Lesson
R2.6
Time
20 min
Runs on
Your browser
Check
5 questions

A register had a load and a synchronous clear, and its brief said clear wins. The RTL had two plain if statements, clear first and load second. In the one cycle where both were high, the register loaded instead of clearing, because the last assignment in the block wins.

Clear and load in the same cycleEdges 2 to 6 of the lab bench, printed after each edge.
01234clkclearloadd5a33q5a3300
  1. Cycle 0

    Edge 2: load alone. q takes 5a.

  2. Cycle 2

    Edge 4: clear and load are both 1. The block schedules 0 for q, then schedules 33. The later one is the one that lands: q is 33.

  3. Cycle 4

    Edge 6: clear alone works, q is 0. The bug shows only in the cycle where both are high.

Measured on the lab bench. With clear first in an if-else chain, edge 4 gives 00.

The last assignment wins

In a clocked block, each nonblocking assignment schedules a new value for its variable. All of them land after the block has run, in the order they were made. So when two statements assign q in the same edge, the later one is what q holds. Statement order is priority.

That gives two correct ways to write a priority. An if ... else if chain puts the highest priority first, because the first true test wins and the rest are skipped. A sequence of plain if statements puts the highest priority last, because the last assignment wins. The hook mixed them: it wrote clear first, as in a chain, but with plain ifs.

Two plain ifs, clear first

else begin
  if (clear) q <= '0;
  if (load)  q <= d;   // last: wins
end

Load wins when both are 1. Edge 4 gives 33.

One chain, clear first

else if (clear) q <= '0;
else if (load)  q <= d;
// neither: q holds

Clear wins when both are 1. Edge 4 gives 00.

The priority chain as hardwareArrows carry data toward the flop. Each step can override the value from the step before it; the last word belongs to the highest priority.
The priority chain as hardwareArrows carry data toward the flop. Each step can override the value from the step before it; the last word belongs to the highest priority. register q (hold) the default: keep thevalue logic load ? d : ... overrides hold logic clear ? 0 : ... overrides load register flop q 8 flops with enable input rst_n asynchronous:overrides everything next qreset pin
  1. Hold

    With no input active, the next value is the old one. The synthesizer turns this into the flop's enable.

  2. Load

    Load replaces the held value with d.

  3. Clear

    Clear sits closest to the flop, so it replaces whatever load chose. That is the brief's priority.

  4. Reset

    The asynchronous reset does not wait for an edge. It is the first test in the block and the strongest input of the flop.

Read the diagram as text
  • q (hold): the default: keep the value
  • load ? d : ...: overrides hold
  • clear ? 0 : ...: overrides load
  • flop q: 8 flops with enable
  • rst_n: asynchronous: overrides everything
  • q (hold) → load ? d : ...
  • load ? d : ... → clear ? 0 : ...
  • clear ? 0 : ... → flop q: next q
  • rst_n → flop q: reset pin

Read the chain from the flop outward: the nearer a step, the higher its priority.

Default first, then override

Plain ifs in increasing priority are a common idiom, not a mistake. Write the default first, then let later statements override it for special cases. Counters and state machines use it all the time.

Default, then overrides, lowest priority first. Correct, and the same register as the chain.
always_ff @(posedge clk or negedge rst_n)
  if (!rst_n) q <= '0;
  else begin
    if (load)  q <= d;     // lower priority first
    if (clear) q <= '0;    // highest priority last: it wins
  end

Both idioms are correct. A block that uses both at once is the bug, because the reader cannot tell which rule applies. Pick the chain for registers with a few controls; it reads top to bottom in priority order.

PriorityInputIn a chainTypical reason
1resetfirst test, in the sensitivity list if asynchronousnothing may stop it
2clearfirst else ifa known state on command
3loadnext else ifnew data
4count enablenext else ifthe normal work
5holdno branch at allthe flop keeps its value

The common order. The brief states it; the code should make it visible in one structure.

What the netlist shows

Synthesize both versions and the cell counts match: 17 cells each, measured on this flow. Each has 8 flops with an asynchronous reset and an enable, and one OR gate making the enable from clear and load.

The difference is the 8 gates in front of the flops. With load winning they are AND gates, d & load: when the flop is enabled, it takes d if load is 1, else 0. With clear winning they are AND-NOT gates, d & ~clear: it takes 0 if clear is 1, else d. Same size, opposite priority, one gate type apart.

Design brief
PortDirectionWidthMeaning
clkinput1clock
rst_ninput1reset, asynchronous, active-low
clearinput1q becomes 0
loadinput1q becomes d
dinput[7:0]the value load writes
qoutput[7:0]clear: q becomes 0; load: q becomes d; neither: q holds
Latency
clear and load act at the edge; one cycle of latency
Corner cases
clear and load in the same cycle; clear alone; load right after a clear. Priority: reset, then clear, then load, then hold

The lab ships the two plain ifs. Run it and find edge 4: q=33. Rewrite the block as one chain with clear first and run again: q=00 at edge 4, held at edge 5, every other line the same. Synthesize both and compare the gate types.

Asked in interviews
  1. Two nonblocking assignments to the same register in one block: which wins?

    The last one executed. Both are scheduled in order and the later update lands last. That is why statement order inside a clocked block is priority, and why a default-then-override style works.

  2. How do you code a register with clear, load and enable with a defined priority?

    One if-else-if chain in the order of the brief: reset first, then clear, then load, then enable, with no final else so the register holds. Each later branch only runs when every earlier test was false.

  3. Where does the priority show in the netlist?

    In the data logic in front of the flops. The control with the last word sits closest to the flop input. Swapping the priority of clear and load here kept 17 cells and changed AND gates into AND-NOT gates.

Try it

Edge 4 is the only cycle with both inputs high, and q reads 33: the load, written last, won. Every other line matches the brief.

Rewrite the block as if (!rst_n) ... else if (clear) ... else if (load) ... and run again: edge 4 reads q=00, edge 5 holds it, and the other seven lines are unchanged.

RTLIcarus Verilog · in your browser · Lint: Verilator service · Synthesize: Yosys service
Edit the design, then Run, Lint or Synthesize
Expected: e0 clear=0 load=0 d=00 q=00 e1 clear=0 load=0 d=00 q=00 e2 clear=0 load=1 d=5a q=5a e3 clear=0 load=0 d=5a q=5a e4 clear=1 load=1 d=33 q=33 e5 clear=0 load=0 d=33 q=33 e6 clear=1 load=0 d=33 q=00 e7 clear=0 load=1 d=77 q=77 e8 clear=0 load=0 d=77 q=77

Original design · 17 cells · 8 flip-flops · 0 latches · 1 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       clear = 0, load = 0;
  logic [7:0] d = 0;
  logic [7:0] q;
  reg8 dut (.clk(clk), .rst_n(rst_n), .clear(clear), .load(load), .d(d), .q(q));
  // Advance to the next rising edge, wait for the design to settle, then print one line.
  task automatic tick();
    @(posedge clk); #1;
    $display("e%0d clear=%b load=%b d=%h q=%h", edge_n, clear, load, d, q);
  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
    load = 1; d = 8'h5a;             tick();   // load alone
    load = 0;                        tick();   // hold
    clear = 1; load = 1; d = 8'h33;  tick();   // both in the same cycle: the brief says clear wins
    clear = 0; load = 0;             tick();   // hold
    clear = 1;                       tick();   // clear alone
    clear = 0; load = 1; d = 8'h77;  tick();   // load alone
    load = 0;                        tick();   // hold
    $finish(0);                             // quiet: no "$finish called" line
  end
endmodule

Check yourself

Predict the outputThe register uses default-then-override with load first and clear second. The bench loads 5a, then sets load and clear together with d = 33. What does it print?
module tb;
  logic clk = 0, rst_n = 1, clear = 0, load = 0;
  logic [7:0] d = 8'h00, q;
  always_ff @(posedge clk or negedge rst_n)
    if (!rst_n) q <= '0;
    else begin
      if (load)  q <= d;
      if (clear) q <= '0;
    end
  initial begin
    #1 rst_n = 0;
    #1 rst_n = 1;
    load = 1; d = 8'h5a;            #1 clk = 1; #1 clk = 0;   // load alone
    load = 1; clear = 1; d = 8'h33; #1 clk = 1; #1 clk = 0;   // both
    $display("q=%h", q);
    $finish(0);
  end
endmodule
Spot the bugEach option is the body of the register. The bench loads, holds, sets clear and load together, loads, and clears, checking q after every edge. Which body fails?
ConceptInside one always_ff block, two nonblocking assignments to q run in the same edge. Which value does q hold afterwards?
ConceptWhy does the reset test sit at the top of the register's if-else chain?
Read the waveformThis is the lab bench with the two plain ifs, edges 2 to 8. Where does the register break its brief?
The register with two plain ifs
0123456clearloadd5a3377q5a330077

One value per edge, printed after the edge, from edge 2 on the left to edge 8 on the right.

Remember

  • In one clocked block the last nonblocking assignment to a variable is the one that lands, so statement order is priority.
  • An if-else-if chain puts the highest priority first; plain ifs put it last. Use one style per block.
  • Write reset first, then the brief's order, and leave out the final else so the register holds.
  • The netlist shows priority in the gates before the flop: here AND with load or AND-NOT with clear, 17 cells either way.