Clocks, edges and sampling
Say when a value on a bus is driven and when it is read, keep every change away from the sampling edge, read an active-low signal correctly, and know what the bus may carry during reset.
- Lesson
- P1.2
- Time
- 25 min
- Runs on
- Your browser
- Check
- 5 questions
A scoreboard reported a duplicated beat and a missing one, and the bus looked clean in the waveform. The monitor sampled at the falling edge "to be safe". The stimulus changed data at the falling edge too, a habit from another project. Whenever both acted at the same edge, the monitor logged whichever value happened to be there first, and the target, sampling at the rising edge, saw something else.
The upper pill is what the target takes; the lower pill is what the monitor logs.
- Cycle 1
Beat 5a. The target takes it at the rising edge, the monitor logs it at the falling edge. They agree.
- Cycle 2
Beat 3c. The driver and the monitor both act at the falling edge. This time the monitor ran first and logged 5a again.
- Cycle 3
Beat ff. Now the monitor ran second and saw the new value. One log, two orders, and the bus itself was never wrong.
Which process runs first at a shared edge is not fixed by the standard. The cure is a rule about edges, not a faster monitor.
Driven after the edge, sampled at the next
A synchronous bus shares one clock, and every signal on it follows one rule: it is changed just after a rising edge and read at the next one. In between, the value has the whole period to travel and settle. On a 100 MHz clock that is 10 ns, minus the small setup time a flip-flop needs before the edge.
q shows the value from the next cycle on.The pill is the value in flight during the cycle; q is what the target holds after the edge.
- Cycle 1
Just after edge 1 the source drives
validwith 5a. Nothing reads it until the edge that ends this cycle. - Cycle 2
That edge loaded 5a into
q. The source already drives the next value, 3c, for the next edge. - Cycle 3
qnow holds 3c. Every value arrives one period after it was driven, by design and on every synchronous bus.
In a testbench the same rule reads: drive one time unit after the rising edge, or from a clocked block with nonblocking assignments.
The one-period lag is not a cost to be removed. It is the margin that lets a value cross the chip and settle before anyone reads it. Every rule in this module counts in edges because of it.
Stable before the edge
Silicon needs a value to be stable for the setup time before the edge and the hold time after it. Simulation has no picoseconds, so the rule shrinks to one line: never change a signal at the edge that samples it. A change at the sampling edge gives old or new, and which one depends on the order the simulator chose for its processes.
That is what the scoreboard in the opening story met. The driver and the monitor shared the falling edge, so the monitor read old or new at random. The fix was not a cleverer monitor. The driver moved to one time unit after the rising edge, and the monitor moved to the rising edge, where the target reads.
Driver and monitor on the same edge
// driver: changes data at the falling edge
always @(negedge clk) data = next;
// monitor: reads data at the falling edge
always @(negedge clk) log(data); // old or new
Two processes act at one edge. The log depends on their order.
Driver after the edge, monitor at the edge
// driver: changes data just after the rising edge
always @(posedge clk) #1 data = next;
// monitor: reads where the target reads
always @(posedge clk) log(data); // the value the target took
The monitor sees exactly what the target sampled.
A monitor samples where the design samples. Half a cycle early can work while every source behaves, as the lab below shows, and it stops working the day one source changes a value mid-cycle.
What asserted means
A signal is asserted when it is at its active level, whatever that level is. Most bus signals are active-high, so asserted means 1. Resets, chip selects and interrupt lines are often active-low, marked with a trailing n or _n. For them, asserted means 0. "Reset is asserted" and "rst_n is 0" say the same thing.
| Signal | Active level | Asserted means | Deasserted means |
|---|---|---|---|
valid, ready | High | 1 | 0 |
rst_n | Low | 0: reset is active | 1: the block runs |
cs_n (chip select) | Low | 0: this chip is addressed | 1: ignore the bus |
irq_n (interrupt) | Low | 0: an interrupt is pending | 1: nothing pending |
Read the suffix before the level. if (rst_n) means "when reset is released".
Nothing on the bus is valid during reset
Reset is the one asynchronous signal on a synchronous bus. It is asserted without waiting for a clock, so a block can be forced quiet even if its clock has stopped. It is released at an edge, so every block leaves reset in the same cycle. While it is asserted, every valid on the bus is 0 and every FIFO is empty. The first offer may rise only after an edge that sampled reset as released.
rst_n is active-low: 0 is asserted. The first valid waits one full cycle after the release.A ringed block is in reset. The first pill flies one cycle after the release.
- Cycle 0
rst_nis 0: reset is asserted. Both blocks are held,validis 0, the FIFO is empty. - Cycle 2
Reset was released after edge 1. The edge that ends this cycle is the first to sample it released, so nothing may be offered yet.
- Cycle 3
The first offer, a full cycle after the release.
readyis already 1, so the beat moves at the end of this cycle. - Cycle 4
A second beat in flight. The sink holds 5a; the rule cost one cycle and no block ever saw a half-reset bus.
The bench checker names this rule reset when valid is 1 while rst_n is 0.
A block that raises valid during reset breaks more than one rule. The sink may not be running yet, so the beat is lost, and the checker reports a transfer that never happened.
Where you will meet it
AXI samples every signal on the rising edge of its clock and its reset is active-low, released at a rising edge. APB and AHB share that clock rule. Avalon and Wishbone name the active-low reset differently and keep the same meaning. A testbench that drives one time unit after the rising edge, and samples at it, works with all of them.
Asked in interviews: clocks and sampling
Why are buses synchronous?
One clock gives every block the same moment to read, so a value only has to be stable at the edge. Without it each signal would need its own handshake just to be read safely.
What happens to a signal that changes at the sampling edge?
The reader gets the old value or the new one, and the standard does not say which. Change signals after the edge, and sample them at it.
What does asserted mean for an active-low signal?
It is at its active level, which is 0. "rst_n is asserted" means reset is in progress; "deasserted" means the block runs.
Why is reset asserted asynchronously but released synchronously?
Asserting without a clock forces a block quiet even when its clock has stopped. Releasing at an edge lets every block start in the same cycle.
At which edge should a monitor sample?
At the edge the design samples, normally the rising edge. Any other edge only matches while every source happens to behave.
Try it
The monitor watches the rising edge, like the sink, and its log names the same edges as the tasks. Run it, then set FALLING(1) and run again: the monitor now reports each beat half a cycle before the target takes it, and still counts three.
The logs agree only because the tasks drive one time unit after the rising edge. Replace the third send with @(negedge clk) valid = 1; data = 8'hc3; and the falling-edge monitor shares an edge with the driver: its log now depends on process order.
monitor (rising edge): beat a1 at edge 3 edge 3: beat a1 taken monitor (rising edge): beat b2 at edge 4 edge 4: beat b2 taken monitor (rising edge): beat c3 at edge 8 edge 8: beat c3 taken monitor counted 3 beats, the checker counted 3 transfers checker: 3 transfers, 3 stalled edges, 0 rules brokenRead included file: link_sink.sv
// link_sink.sv: the target of the generic link (read-only in this lesson).
// A DEPTH-entry FIFO behind valid/ready. It takes a beat at every rising edge where valid and
// ready are both 1, and hands beats on to its consumer at every edge where drain and out_valid
// are both 1. ready is 1 while the FIFO has room: it never waits for valid.
module link_sink #(parameter int DEPTH = 4, parameter int WIDTH = 8) (
input logic clk,
input logic rst_n,
input logic valid,
input logic [WIDTH-1:0] data,
input logic last,
output logic ready,
input logic drain, // the consumer behind the FIFO takes a beat while 1
output logic out_valid,
output logic [WIDTH-1:0] out_data,
output logic out_last,
output int count // beats held right now
);
logic [WIDTH:0] mem [DEPTH]; // {last, data}
int wr = 0, rd = 0;
assign ready = (count < DEPTH);
assign out_valid = (count > 0);
assign {out_last, out_data} = mem[rd];
wire push = valid && ready;
wire pop = drain && out_valid;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wr <= 0; rd <= 0; count <= 0;
end else begin
if (push) begin mem[wr] <= {last, data}; wr <= (wr + 1) % DEPTH; end
if (pop) rd <= (rd + 1) % DEPTH;
count <= count + (push ? 1 : 0) - (pop ? 1 : 0);
end
end
endmodule
Read included file: link_checker.sv
// link_checker.sv: the rules of the generic link, checked at every rising edge (read-only).
// Each broken rule is named with the edge where it was seen:
// reset valid is 0 while reset is active;
// hold once valid is 1 and ready is 0, valid is still 1 at the next edge (an offer is not withdrawn);
// stable while an offer waits, data and last do not change;
// known data is not x while valid is 1.
// The one rule no checker can see on the wires, "valid must not wait for ready", is the stimulus's
// own responsibility (lesson P1.3).
// It also counts transfers and stalled edges, and prints one summary line at the end of the run.
module link_checker #(parameter int WIDTH = 8) (
input logic clk,
input logic rst_n,
input logic valid,
input logic ready,
input logic [WIDTH-1:0] data,
input logic last
);
int cycle = -1, transfers = 0, stalls = 0, broken = 0;
logic pv = 0, pr = 0, pl = 0;
logic [WIDTH-1:0] pd = 'x;
always @(posedge clk) begin
cycle++;
if (!rst_n) begin
if (valid === 1) report("reset", "valid is 1 during reset");
end else begin
if (pv && !pr && valid !== 1) report("hold", "valid dropped before ready: the offer was withdrawn");
if (pv && !pr && valid === 1 && data !== pd) report("stable", "data changed while the offer waited");
if (pv && !pr && valid === 1 && last !== pl) report("stable", "last changed while the offer waited");
if (valid === 1 && $isunknown(data)) report("known", "data is x while valid is 1");
if (valid === 1 && ready === 1) transfers++;
if (valid === 1 && ready !== 1) stalls++;
end
pv = (valid === 1); pr = (ready === 1); pd = data; pl = last;
end
function void report(input string rule, input string what);
broken++;
$display("RULE %s broken at edge %0d: %s", rule, cycle, what);
endfunction
final $display("checker: %0d transfers, %0d stalled edges, %0d rules broken", transfers, stalls, broken);
endmodule
Read included file: link_tasks.svh
// link_tasks.svh: the source side of the generic link, as tasks for the stimulus (read-only).
// Every task returns one time unit after the rising edge where its last beat transferred, so the
// next call starts driving in the following cycle. `cycle` counts rising edges from 0.
int cycle = -1;
always @(posedge clk) cycle = cycle + 1;
// Offer one beat and hold it until the sink takes it.
task automatic send(input logic [7:0] d, input logic is_last = 0);
valid = 1; data = d; last = is_last;
do @(posedge clk); while (!ready);
#1 $display("edge %0d: beat %02h taken", cycle, d);
valid = 0; data = 'x; last = 0;
endtask
// A burst of n beats with consecutive values; last marks the final beat.
task automatic send_burst(input logic [7:0] first, input int n);
for (int i = 0; i < n; i++) begin
valid = 1; data = first + i[7:0]; last = (i == n - 1);
do @(posedge clk); while (!ready);
#1 $display("edge %0d: beat %02h taken%s", cycle, first + i[7:0], last ? " (last)" : "");
end
valid = 0; data = 'x; last = 0;
endtask
// Drive nothing for n cycles.
task automatic idle(input int n);
repeat (n) @(posedge clk);
#1;
endtask
Read included file: edge_monitor.sv
`timescale 1ns/1ps
// edge_monitor.sv: a monitor that counts beats at the edge of your choice (read-only).
// FALLING = 0 samples at the rising edge, where the sink samples.
// FALLING = 1 samples at the falling edge, half a cycle earlier.
// Edge numbers follow the tasks: rising edge k is at 5 + 10k ns, the falling edge after it at 10 + 10k.
module edge_monitor #(parameter bit FALLING = 0) (
input logic clk,
input logic rst_n,
input logic valid,
input logic ready,
input logic [7:0] data
);
int beats = 0;
generate
if (FALLING) begin : fall
always @(negedge clk) if (rst_n && valid && ready) begin
beats++;
$display("monitor (falling edge): beat %02h between edges %0d and %0d", data, ($time - 10) / 10, ($time - 10) / 10 + 1);
end
end else begin : rise
always @(posedge clk) if (rst_n && valid && ready) begin
beats++;
$display("monitor (rising edge): beat %02h at edge %0d", data, ($time - 5) / 10);
end
end
endgenerate
endmodule
Check yourself
Remember
- On a synchronous bus a value is driven just after a rising edge and read at the next one. It arrives one period later, by design.
- Never change a signal at the edge that samples it. A shared edge gives old or new, and the standard does not say which.
- A monitor samples where the design samples. Sampling elsewhere matches only while every source behaves.
- Asserted means at the active level: 1 for most signals, 0 for an active-low signal such as
rst_norcs_n. - Reset is asserted asynchronously and released at an edge. Every
validis 0 while it is asserted, and the first offer waits one cycle after the release.