blob: 9a7dbee66da2f1f9bad1aed37d27977603fcd013 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Power Manager Slow FSM
//
`include "prim_assert.sv"
module pwrmgr_slow_fsm import pwrmgr_pkg::*; (
input clk_i,
input rst_ni,
// sync'ed requests from peripherals
input wakeup_i,
input reset_req_i,
// interface with fast fsm
output logic req_pwrup_o,
output logic pwrup_cause_toggle_o,
output pwrup_cause_e pwrup_cause_o,
input ack_pwrup_i,
input req_pwrdn_i,
output logic ack_pwrdn_o,
// low power entry configuration
input main_pd_ni,
input io_clk_en_i,
input core_clk_en_i,
input usb_clk_en_lp_i,
input usb_clk_en_active_i,
// AST interface
input pwr_ast_rsp_t ast_i,
output pwr_ast_req_t ast_o
);
slow_pwr_state_e state_q, state_d;
// All signals crossing over to other domain must be flopped
pwrup_cause_e cause_q, cause_d;
logic cause_toggle_q, cause_toggle_d;
logic req_pwrup_q, req_pwrup_d;
logic ack_pwrdn_q, ack_pwrdn_d;
// All power signals and signals going to analog logic are flopped to avoid transitional glitches
logic pd_nq, pd_nd;
logic pwr_clamp_q, pwr_clamp_d;
logic pwr_clamp_env_q, pwr_clamp_env_d;
logic core_clk_en_q, core_clk_en_d;
logic io_clk_en_q, io_clk_en_d;
logic usb_clk_en_q, usb_clk_en_d;
logic all_clks_valid;
logic all_clks_invalid;
// all clocks sources are valid
// if clocks (usb) not configured to be active, then just bypass check
assign all_clks_valid = ast_i.core_clk_val &
ast_i.io_clk_val &
(~usb_clk_en_active_i | ast_i.usb_clk_val);
// if clocks were configured to turn off, make sure val is invalid
// if clocks were not configured to turn off, just bypass the check
assign all_clks_invalid = (core_clk_en_i | ~ast_i.core_clk_val) &
(io_clk_en_i | ~ast_i.io_clk_val) &
(usb_clk_en_lp_i | ~ast_i.usb_clk_val);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
state_q <= SlowPwrStateReset;
cause_q <= Por;
cause_toggle_q <= 1'b0;
// pwrmgr resets assuming main power domain is already ready
pd_nq <= 1'b1;
pwr_clamp_q <= 1'b0;
pwr_clamp_env_q <= 1'b0;
core_clk_en_q <= 1'b0;
io_clk_en_q <= 1'b0;
usb_clk_en_q <= 1'b0;
req_pwrup_q <= 1'b0;
ack_pwrdn_q <= 1'b0;
end else begin
state_q <= state_d;
cause_q <= cause_d;
cause_toggle_q <= cause_toggle_d;
pd_nq <= pd_nd;
pwr_clamp_q <= pwr_clamp_d;
pwr_clamp_env_q <= pwr_clamp_env_d;
core_clk_en_q <= core_clk_en_d;
io_clk_en_q <= io_clk_en_d;
usb_clk_en_q <= usb_clk_en_d;
req_pwrup_q <= req_pwrup_d;
ack_pwrdn_q <= ack_pwrdn_d;
end
end
always_comb begin
state_d = state_q;
cause_d = cause_q;
pd_nd = pd_nq;
cause_toggle_d = cause_toggle_q;
pwr_clamp_d = pwr_clamp_q;
pwr_clamp_env_d = pwr_clamp_env_q;
core_clk_en_d = core_clk_en_q;
io_clk_en_d = io_clk_en_q;
usb_clk_en_d = usb_clk_en_q;
req_pwrup_d = req_pwrup_q;
ack_pwrdn_d = ack_pwrdn_q;
unique case(state_q)
SlowPwrStateReset: begin
state_d = SlowPwrStateMainPowerOn;
cause_d = Por;
end
SlowPwrStateLowPower: begin
// reset request behaves identically to a wakeup, other than the power-up cause being
// different
if (wakeup_i || reset_req_i) begin
state_d = SlowPwrStateMainPowerOn;
cause_toggle_d = ~cause_toggle_q;
cause_d = reset_req_i ? Reset : Wake;
end
end
SlowPwrStateMainPowerOn: begin
pd_nd = 1'b1;
if (ast_i.main_pok) begin
pwr_clamp_env_d = 1'b0;
state_d = SlowPwrStatePwrClampOff;
end
end
SlowPwrStatePwrClampOff: begin
pwr_clamp_d = 1'b0;
state_d = SlowPwrStateClocksOn;
end
SlowPwrStateClocksOn: begin
core_clk_en_d = 1'b1;
io_clk_en_d = 1'b1;
usb_clk_en_d = usb_clk_en_active_i;
if (all_clks_valid) begin
state_d = SlowPwrStateReqPwrUp;
end
end
SlowPwrStateReqPwrUp: begin
req_pwrup_d = 1'b1;
// req_pwrdn_i should be 0 here to indicate
// the request from the previous round has definitely completed
if (ack_pwrup_i && !req_pwrdn_i) begin
req_pwrup_d = 1'b0;
state_d = SlowPwrStateIdle;
end
end
SlowPwrStateIdle: begin
// ack_pwrup_i should be 0 here to indicate
// the ack from the previous round has definitively completed
usb_clk_en_d = usb_clk_en_active_i;
if (req_pwrdn_i && !ack_pwrup_i) begin
state_d = SlowPwrStateAckPwrDn;
end
end
SlowPwrStateAckPwrDn: begin
ack_pwrdn_d = 1'b1;
if (!req_pwrdn_i) begin
ack_pwrdn_d = 1'b0;
state_d = SlowPwrStateClocksOff;
end
end
SlowPwrStateClocksOff: begin
core_clk_en_d = core_clk_en_i;
io_clk_en_d = io_clk_en_i;
usb_clk_en_d = usb_clk_en_lp_i;
if (all_clks_invalid) begin
// if main power is turned off, assert early clamp ahead
pwr_clamp_env_d = ~main_pd_ni;
state_d = SlowPwrStatePwrClampOn;
end
end
SlowPwrStatePwrClampOn: begin
// if main power is turned off, assert clamp ahead
pwr_clamp_d = pwr_clamp_env_q;
state_d = SlowPwrStateMainPowerOff;
end
SlowPwrStateMainPowerOff: begin
pd_nd = main_pd_ni;
// if power is never turned off, proceed directly to low power state
if (!ast_i.main_pok | main_pd_ni) begin
state_d = SlowPwrStateLowPower;
end
end
// Very terminal state, kill everything
default: begin
pd_nd = 1'b0;
pwr_clamp_d = 1'b1;
core_clk_en_d = 1'b0;
io_clk_en_d = 1'b0;
usb_clk_en_d = 1'b0;
end
endcase // unique case (state_q)
end // always_comb
assign pwrup_cause_o = cause_q;
assign pwrup_cause_toggle_o = cause_toggle_q;
assign req_pwrup_o = req_pwrup_q;
assign ack_pwrdn_o = ack_pwrdn_q;
assign ast_o.core_clk_en = core_clk_en_q;
assign ast_o.io_clk_en = io_clk_en_q;
assign ast_o.usb_clk_en = usb_clk_en_q;
assign ast_o.main_pd_n = pd_nq;
assign ast_o.pwr_clamp_env = pwr_clamp_env_q;
assign ast_o.pwr_clamp = pwr_clamp_q;
// This is hardwired to 1 all the time
assign ast_o.slow_clk_en = 1'b1;
////////////////////////////
/// Unused
////////////////////////////
logic unused_slow_clk_val;
assign unused_slow_clk_val = ast_i.slow_clk_val;
endmodule