A bug report arrives: "After the first transfer, the device stops responding. Happens every time on the power-saving build, never on the performance build." The design below reproduces it. It has a requester, a device that takes two cycles per request, and a power controller using the request/accept/deny sleep handshake from Part 12. Everything looks reasonable. One request never completes.
Can you find the root cause of a hang from the waveform alone?
// Capstone: debug the hang. A requester sends two requests to a device over a
// valid/ready interface. A power controller puts the device to sleep with the
// step 11 handshake when the device reports no activity for two cycles, and wakes
// it when the device reports activity again. The first request is served; the
// second never is. Find out why from the waveform before reading the solution.
// The checker reports whether the hang reproduced.
module tb;
logic clk = 0;
always #5 clk = ~clk;
initial begin $dumpfile("wave.vcd"); $dumpvars(0, tb); end
int cyc = 0;
always @(posedge clk) cyc <= cyc + 1;
// Requester: request 1 in cycle 1, request 2 in cycle 8; holds vld until accepted
logic vld = 0;
logic rdy;
always @(posedge clk) begin
if ((cyc == 1 || cyc == 8) && !vld) vld <= 1;
else if (vld && rdy) vld <= 0;
end
// Device: busy for 2 cycles per request; cannot accept while asleep or busy
logic busy = 0, slp_acc = 0, slp_req = 0, active;
logic [1:0] work = 0;
always_comb begin
rdy = !slp_acc && !busy;
active = busy; // activity reported to the controller
end
always @(posedge clk) begin
if (vld && rdy) begin busy <= 1; work <= 1; end
else if (busy) begin if (work == 0) busy <= 0; else work <= work - 1; end
// sleep handshake (device side): accept when idle and nothing is arriving
if (slp_req && !slp_acc && !busy && !vld) slp_acc <= 1;
else if (!slp_req) slp_acc <= 0;
end
// Power controller: sleep after 2 idle cycles, wake on activity
logic [1:0] idle = 0;
always @(posedge clk) begin
idle <= active ? 0 : (idle == 3 ? 3 : idle + 1);
if (!slp_req && idle >= 2) slp_req <= 1;
else if (slp_req && slp_acc && active) slp_req <= 0;
end
int served = 0, stuck = 0;
always @(posedge clk) begin
if (vld && rdy) served++;
if (vld && !rdy) stuck++;
end
initial begin
#300;
if (served == 1 && stuck > 15 && slp_acc)
$display("PASS: hang reproduced: request 2 has waited %0d cycles while the device sleeps", stuck);
else $display("FAIL: served %0d stuck %0d asleep %0d", served, stuck, slp_acc);
$finish;
end
endmoduleWhen run, it prints a PASS line confirming the hang reproduced: request 2 waits for ever while the device sleeps.
Apply the method (lesson 15.10): find the stuck handshake, the missing condition, the state behind it, and the first cycle that state went wrong. Write your answer before opening the solution.
From slot 8, vld is high and rdy is low, for ever: request 2 is never accepted.
rdy = !slp_acc && !busy. busy is 0, so slp_acc is the cause: the device has been asleep since slot 5.
It wakes only when the controller drops slp_req, which happens only when active is high. active stays 0 throughout.
active = busy reports only work the device is doing, not work waiting at its input. A request that arrives after the device accepted sleep is invisible to the controller, so nobody wakes the device. Request 1 arrived before any sleep request and was served normally; the controller's request (slot 2) was accepted only in slot 5, once the device was idle, and from then on any new request is stranded.
Report pending input as activity: active = busy || vld (the role of AMBA's QACTIVE, fact A22). The controller then sees request 2, drops slp_req, the device wakes and serves it. Add an assertion: vld && slp_acc must lead to !slp_acc within a bounded number of cycles.
Part 3: the valid/ready handshake and what stalls it.
Part 9: a liveness failure that no safety check caught.
Part 12: the sleep handshake and the wake path (lessons 12.4, 12.6).
Part 15: the debug method and a bounded-liveness assertion.
Follow the wait: stuck handshake, missing condition, the state behind it, the first wrong cycle.
Here, activity reporting ignored pending input, so nothing could wake the sleeping device.
Every input that carries work must be part of the wake condition; assert bounded wake-up.