A chip-to-chip link draws a few watts while it runs at full speed, and most of the time it carries nothing: a burst of traffic, then milliseconds of silence. Keeping the transmitters, receivers and clock recovery running through that silence burns power for no work at all. Turning them off is easy. Knowing when to turn them off, without losing a request or making the next one wait too long, is the problem.
When should an interface decide it is idle, and what does that decision cost?
The obvious rule is to sleep the moment nothing is being sent. On real traffic that fails twice. Gaps of a few cycles are everywhere, between packets of the same burst, so the link would sleep and wake constantly, paying the cost of each transition (lesson 12.3) and adding wake latency to almost every packet. And "nothing being sent" is not the same as idle: a read may be outstanding, with its response on the way back, or credits may still need to be returned.
Part of the decision | What it means |
|---|---|
What counts as idle | No packets to send, no outstanding requests awaiting responses, no credits or acknowledgements owed to the partner, nothing queued above the link |
How long | Idle continuously for a timeout (the idle limit); any activity resets the count |
What happens on new work | A request that arrives while asleep is held, the link wakes, and the request goes when it is ready: never dropped |
What it costs | Each sleep and wake takes energy and time; the wake time is added to the first request after a sleep |
The link serves requests in cycles 1 and 2, then sees nothing. After 3 idle cycles in a row it enters low power (lp). A request in cycle 9 finds it asleep: the link wakes for 2 cycles, then serves it.
// Idle detection with a timeout. The link serves requests in cycles 1 and 2, then
// sees none. After IDLE_LIMIT idle cycles in a row it enters a low-power state (lp).
// A request arriving in low power must wait: the link wakes for WAKE cycles first,
// then serves it. The checker confirms every request was served (none lost while
// asleep), that low power was entered once before the late request, and that it paid the
// wake latency.
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;
localparam int IDLE_LIMIT = 3, WAKE = 2;
wire req = (cyc == 1 || cyc == 2 || cyc == 9);
logic [2:0] idle_cnt = 0;
logic lp = 0, waking = 0, pend = 0, served = 0;
logic [1:0] wake_cnt = 0;
always @(posedge clk) begin
served <= 0;
if (lp) begin
if (req) begin lp <= 0; waking <= 1; wake_cnt <= 0; pend <= 1; end // wake on demand
end
else if (waking) begin
if (wake_cnt == WAKE - 1) begin waking <= 0; served <= pend; pend <= 0; idle_cnt <= 0; end
else wake_cnt <= wake_cnt + 1;
end
else if (req) begin served <= 1; idle_cnt <= 0; end
else if (idle_cnt == IDLE_LIMIT - 1) begin lp <= 1; idle_cnt <= 0; end // idle long enough
else idle_cnt <= idle_cnt + 1;
end
int n_served = 0, n_lp = 0, late_req_cyc = 0, late_srv_cyc = 0;
logic lp_q = 0;
always @(posedge clk) begin
lp_q <= lp;
if (served) begin n_served++; late_srv_cyc = cyc; end
if (lp && !lp_q && cyc < 9) n_lp++; // sleeps before the late request
if (req) late_req_cyc = cyc;
end
initial begin
#200;
if (n_served == 3 && n_lp == 1 && late_srv_cyc - late_req_cyc == WAKE + 1)
$display("PASS: three requests served, low power entered once before the late request, which waited %0d cycles to wake", late_srv_cyc - late_req_cyc);
else $display("FAIL: served %0d lp entries %0d wait %0d", n_served, n_lp, late_srv_cyc - late_req_cyc);
$finish;
end
endmoduleHow to read it, slot by slot:
Slots 0 to 2: two requests (req) are served (served in slots 1 and 2); each resets idle_cnt to 0.
Slots 3 to 4: no requests; idle_cnt counts 1, 2.
Slot 5: the third idle cycle completes the timeout: lp rises.
Slot 8: a request arrives while asleep. It is held.
Slots 9 to 10: the link wakes (waking); slot 11: the held request is served, three cycles after it arrived. With the link awake it would have been served in one.
Too short: the link sleeps in gaps inside bursts, waking constantly; energy and latency both suffer.
Too long: the link stays awake through idle periods it could have slept through.
About right: around the break-even time of the low-power state (lesson 12.3), adjusted for how much wake latency the traffic tolerates.
Adaptive: some controllers lengthen the timeout when wakes come quickly after sleeps, and shorten it when idle periods are long.
Protocol | Idle detection |
|---|---|
PCI Express ASPM | Hardware enters L0s or L1 after an idle period, while the device stays in D0; software enables it only where the exit latency is acceptable (fact P34). |
USB 3.x | Inactivity timers move the link from U0 to U1 or U2 (fact S19). |
Energy-Efficient Ethernet | An idle link enters Low Power Idle and wakes on demand (fact S18). |
DCLink | Step 11: the power controller decides idleness and asks the device to sleep (lesson 12.4). |
A storage link (illustrative) used an idle timeout shorter than the gap between commands in its most common workload. It entered low power between almost every pair of commands; measured power went up, and every command paid the wake latency. Raising the timeout above the typical command gap cut power by a third and removed the latency penalty.
Responses to outstanding requests may still be coming, and credits or acknowledgements may still be owed to the partner.
Three cycles: two for the wake time and one to be served, instead of one cycle if the link had been awake.
Idle means nothing pending in either direction, continuously, for longer than a timeout.
A request arriving while asleep is held and served after the wake time, never dropped.
Choose the timeout from the break-even time and the measured idle gaps; too short can raise power.