A system's power controller sees that a DMA engine has been quiet for a while and gates its clock. But the engine was not finished: it had issued a read whose data was still on its way back. The data arrives at a block with no clock, the engine never sees it, and the transfer hangs. The controller's view of "idle" was wrong, and the only party that knew better was never asked.
How does a power controller ask a device to sleep without cutting it off in the middle of work?
The obvious design lets the controller decide alone and switch the clock or power off. The controller sees only the interfaces it watches; it cannot see a device's outstanding requests, its internal queues or work it is about to start. Only the device knows whether it can stop now. So entry into a low-power state must be a request the device can answer.
Request: the controller raises a request and holds it (a four-phase handshake, lesson 3.5).
Accept: if the device is idle and can stop, it finishes anything in progress, stops taking new work, and accepts. Only then may the controller gate its clock or remove power.
Deny: if the device is busy, it denies. The controller withdraws and tries later. Being able to deny is the point: the device can never be forced asleep mid-work.
Exit: the controller withdraws the request (or the device signals it needs to wake); the device leaves the state and resumes.
The device is busy in cycles 1 to 4. The controller asks in cycle 2 and is denied; it asks again in cycle 7, when the device is idle, and is accepted; it wakes the device in cycle 11.
// DCLink step 11: the low-power entry handshake. The power controller asks the
// device to sleep by raising slp_req and holding it. The device answers with
// slp_acc (it is idle: it stops taking work and sleeps) or slp_deny (it is busy).
// On a deny the controller drops its request and tries later; to wake an accepted
// device, it drops slp_req and the device drops slp_acc. Here the device is busy
// in cycles 1 to 4: the first request (cycle 2) is denied, the second (cycle 7) is
// accepted, and the controller wakes the device in cycle 11. The checker confirms
// the device never accepted while busy and never answered both ways at once.
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;
logic busy = 0, slp_req = 0, slp_acc = 0, slp_deny = 0;
always @(posedge clk) busy <= (cyc >= 0 && cyc <= 3); // outstanding work in cycles 1-4
// Power controller
always @(posedge clk) begin
if (!slp_req && !slp_acc && !slp_deny && (cyc == 2 || cyc == 7)) slp_req <= 1;
else if (slp_req && slp_deny) slp_req <= 0; // denied: give up for now
else if (slp_req && slp_acc && cyc == 11) slp_req <= 0; // wake the device
end
// Device
always @(posedge clk) begin
if (slp_req && !slp_acc && !slp_deny) begin
if (busy) slp_deny <= 1;
else slp_acc <= 1; // idle: accept and sleep
end
else if (!slp_req) begin slp_deny <= 0; slp_acc <= 0; end
end
int accepts = 0, denies = 0, bad = 0;
logic acc_q = 0, deny_q = 0;
always @(posedge clk) begin
acc_q <= slp_acc; deny_q <= slp_deny;
if (slp_acc && !acc_q) begin accepts++; if (busy) bad++; end
if (slp_deny && !deny_q) denies++;
if (slp_acc && slp_deny) bad++;
end
initial begin
#200;
if (accepts == 1 && denies == 1 && bad == 0 && !slp_acc)
$display("PASS: busy device denied the first request, accepted the second when idle, and woke on request");
else $display("FAIL: accepts %0d denies %0d bad %0d acc %0d", accepts, denies, bad, slp_acc);
$finish;
end
endmoduleHow to read it, slot by slot:
Slots 0 to 3: the device is busy. The controller raises slp_req in slot 2.
Slot 3: the device answers slp_deny. Slot 4: the controller drops its request; slot 5: the device drops the deny. The handshake is closed and nothing was turned off.
Slot 7: a new request, with the device idle. Slot 8: slp_acc: the device is asleep and takes no work.
Slot 11: the controller drops slp_req to wake it; slot 12: slp_acc falls and the device is awake.
Choice | Options |
|---|---|
Who starts | Controller requests (most common); or the device asks to sleep |
Can the device refuse | Accept or deny (safe, flexible); or accept only, after it finishes (simpler, but the controller may wait long) |
What it controls | Clock gating only; or several power states, named in the request |
Wake | Controller withdraws the request; and the device signals that it has new work |
Protocol | Low-power handshake |
|---|---|
AMBA Q-Channel | QREQn (request, active low), QACCEPTn and QDENY from the device, and QACTIVE to show the device has work: for clock gating and simple power control. |
AMBA P-Channel | PREQ with a requested PSTATE, answered by PACCEPT or PDENY, and PACTIVE hints: for choosing among several power states. |
PCI Express | Device power states are set by software through configuration space; link states are negotiated between the two ends of the link. |
DCLink | Step 11: |
The opening hang (illustrative) came from a clock-gating controller that watched only the DMA engine's request interface. Replacing it with a Q-Channel-style handshake, where the engine accepts only when its outstanding-read count is zero, removed the hang; the engine denied about one request in five, each time because a read was still in flight.
Finished or drained its work, including outstanding requests, and stopped accepting new work.
The device was busy and denied it; the controller withdrew, and no accept was ever given.
Only the device knows if it can stop; entry into low power must be a request it can answer.
The device accepts after finishing its work, or denies if busy; the controller acts only on an accept.
AMBA's Q-Channel and P-Channel are the standard examples; DCLink step 11 builds the same idea.