UPF externalizes power intent: domains, switches, isolation, retention, and state tables. RTL should remain clean while UPF captures implementation policy.
The RTL job is to expose the hooks: domain boundaries, isolation enables, and save and restore controls. The UPF job is to say how tools wire them.
Tools insert the cells, but RTL drives the sequence. For external context capture, save state before isolating its capture path, then allow power-down. On wake, keep isolation asserted until power, restore and reset/clock readiness complete; release it before resuming traffic.
The controller below emits one save pulse, holds isolation through the off state, waits for power-good on wake, emits one restore pulse, and keeps isolation asserted until restore and domain readiness are acknowledged. Status inputs must already be synchronized into the always-on clock domain. pwr_ack_o indicates permission to remain powered off, not measured rail status; the power-switch controller returns pwr_good_i. This example assumes traffic was quiesced before req_off_i, and context capture completes within the save cycle.
Each slot is one clock cycle, numbered from 0, and shows the values the next rising edge will see. restore_done_i and domain_ready_i are not drawn; both rise in slot 9.
Slots 1 and 2: req_off_i rises and the controller emits a one-cycle save_o. Isolation is still off, so the capture path sees real data.
Slots 3 and 4: iso_en_o rises first. Only then does pwr_good_i fall in slot 4, so no unclamped output is ever exposed to the dying rail.
Slots 5 to 7: the wake request ends req_off_i, but iso_en_o stays high while the controller waits for pwr_good_i, which returns in slot 7.
Slots 8 to 10: one restore_o pulse in slot 8, then isolation is released last, in slot 10, after the restore and readiness inputs have arrived.
The sketch below is conceptual; tool syntax varies. Each line maps to an RTL duty in the table that follows.
# Conceptual UPF sketch (tool syntax varies)
create_power_domain PD_AON -elements {top/aon_ctrl}
create_power_domain PD_ACCEL -elements {top/accel}
create_supply_net VDD_AON
create_supply_net VDD_SW
set_isolation ISO_ACCEL_OUT -domain PD_ACCEL -applies_to outputs -isolation_signal top/aon_ctrl/iso_en -clamp_value 0
set_retention RET_ACCEL_CTX -domain PD_ACCEL -retention_power_net VDD_AON -save_signal top/aon_ctrl/save_ctx -restore_signal top/aon_ctrl/restore_ctxUPF line | Meaning | RTL duty |
|---|---|---|
| Which instances die together | Hierarchy that matches the power regions |
| Which crossings clamp and to what | A stable |
| Which state survives collapse | Save and restore controls plus the retained register list |
Supply and switch rules | Which rail feeds each domain | Separate always-on and switchable resets and clocks |
No. UPF can select the register directly; duplicate RTL bookkeeping is not a language requirement. Confirm the element resolves in the elaborated hierarchy, belongs to the intended strategy, maps to a supported retention cell with live supplies, and obeys save/restore timing. Missing or incorrect UPF selection is the real failure to diagnose.
No. External capture through an isolated output would see the clamp, whereas an internal retention cell saves local state. The required ordering depends on that topology; the controller here assumes external context capture before isolation.
An always-on controller with the save, isolate, and restore order, plus the testbench that checks each pulse.
`timescale 1ns/1ps
module aon_ctrl (
input logic clk_aon, rst_aon_n, req_off_i,
input logic pwr_good_i, restore_done_i, domain_ready_i,
output logic save_o, iso_en_o, restore_o, pwr_ack_o
);
localparam logic [2:0] ST_ON = 3'd0, ST_SAVE = 3'd1, ST_OFF = 3'd2,
ST_POWERUP = 3'd3, ST_RESTORE = 3'd4, ST_WAIT_READY = 3'd5;
logic [2:0] st, nxt;
always_ff @(posedge clk_aon or negedge rst_aon_n) begin
if (!rst_aon_n) st <= ST_ON;
else st <= nxt;
end
always_comb begin
nxt = st; save_o = 1'b0; iso_en_o = 1'b0; restore_o = 1'b0; pwr_ack_o = 1'b0;
case (st)
ST_ON: if (req_off_i) nxt = ST_SAVE;
ST_SAVE: begin save_o = 1'b1; nxt = ST_OFF; end
ST_OFF: begin iso_en_o = 1'b1; pwr_ack_o = 1'b1; if (!req_off_i) nxt = ST_POWERUP; end
ST_POWERUP: begin iso_en_o = 1'b1; if (pwr_good_i) nxt = ST_RESTORE; end
ST_RESTORE: begin iso_en_o = 1'b1; restore_o = 1'b1; nxt = ST_WAIT_READY; end
ST_WAIT_READY: begin
iso_en_o = 1'b1;
if (pwr_good_i && restore_done_i && domain_ready_i) nxt = ST_ON;
end
default: begin iso_en_o = 1'b1; nxt = ST_POWERUP; end
endcase
end
endmodule