blob: d97cb89113790526c09cb91bde505a29fbdf14ff [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
//
`include "prim_assert.sv"
module pwrmgr import pwrmgr_pkg::*; import pwrmgr_reg_pkg::*;
(
// Clocks and resets
input clk_slow_i,
input clk_i,
input rst_slow_ni,
input rst_ni,
// Bus Interface
input tlul_pkg::tl_h2d_t tl_i,
output tlul_pkg::tl_d2h_t tl_o,
// AST interface
input pwr_ast_rsp_t pwr_ast_i,
output pwr_ast_req_t pwr_ast_o,
// rstmgr interface
input pwr_rst_rsp_t pwr_rst_i,
output pwr_rst_req_t pwr_rst_o,
// clkmgr interface
output pwr_clk_req_t pwr_clk_o,
input pwr_clk_rsp_t pwr_clk_i,
// otp interface
input pwr_otp_rsp_t pwr_otp_i,
output pwr_otp_req_t pwr_otp_o,
// life cycle interface
input pwr_lc_rsp_t pwr_lc_i,
output pwr_lc_req_t pwr_lc_o,
// flash interface
output pwr_flash_req_t pwr_flash_o,
input pwr_flash_rsp_t pwr_flash_i,
// processor interface
input pwr_cpu_t pwr_cpu_i,
// peripherals wakeup and reset requests
input [NumWkups-1:0] wakeups_i,
input [HwRstReqs-1:0] rstreqs_i,
output intr_wakeup_o
);
////////////////////////////
/// async declarations
////////////////////////////
pwr_peri_t peri_reqs_raw;
assign peri_reqs_raw.wakeups = wakeups_i;
assign peri_reqs_raw.rstreqs = rstreqs_i;
////////////////////////////
/// clk_i domain declarations
////////////////////////////
pwrmgr_reg2hw_t reg2hw;
pwrmgr_hw2reg_t hw2reg;
pwr_peri_t peri_reqs_masked;
logic req_pwrup;
logic ack_pwrup;
logic req_pwrdn;
logic ack_pwrdn;
pwrup_cause_e pwrup_cause;
logic capture_en_pulse; // begin capture wakeup causes
logic low_power_fall_through;
logic low_power_abort;
pwr_flash_rsp_t flash_rsp;
////////////////////////////
/// clk_slow_i domain declarations
////////////////////////////
// Captured signals
// These signals, though on clk_i domain, are safe for clk_slow_i to use
logic [NumWkups-1:0] slow_wakeup_en;
pwrmgr_reg2hw_reset_en_reg_t slow_reset_en;
pwr_ast_rsp_t slow_ast;
pwr_peri_t slow_peri_reqs, slow_peri_reqs_masked;
pwrup_cause_e slow_pwrup_cause;
logic slow_pwrup_cause_toggle;
logic slow_req_pwrup;
logic slow_ack_pwrup;
logic slow_req_pwrdn;
logic slow_ack_pwrdn;
logic slow_main_pd_n;
logic slow_io_clk_en;
logic slow_core_clk_en;
////////////////////////////
/// Register module
////////////////////////////
logic low_power_hint;
logic lowpwr_cfg_wen;
logic clr_hint;
logic wkup;
logic clr_cfg_lock;
pwrmgr_reg_top u_reg (
.clk_i,
.rst_ni,
.tl_i,
.tl_o,
.reg2hw,
.hw2reg,
.devmode_i (1'b1)
);
// whenever low power entry begins, wipe the hint
assign hw2reg.control.low_power_hint.d = 1'b0;
assign hw2reg.control.low_power_hint.de = clr_hint;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
lowpwr_cfg_wen <= 1'b1;
end else if (!lowpwr_cfg_wen && (clr_cfg_lock || wkup)) begin
lowpwr_cfg_wen <= 1'b1;
end else if (low_power_hint) begin
lowpwr_cfg_wen <= 1'b0;
end
end
assign hw2reg.ctrl_cfg_regwen.d = lowpwr_cfg_wen;
////////////////////////////
/// cdc handling
////////////////////////////
pwrmgr_cdc i_cdc (
.clk_i,
.rst_ni,
.clk_slow_i,
.rst_slow_ni,
// slow domain signals
.slow_req_pwrup_i(slow_req_pwrup),
.slow_ack_pwrdn_i(slow_ack_pwrdn),
.slow_pwrup_cause_toggle_i(slow_pwrup_cause_toggle),
.slow_pwrup_cause_i(slow_pwrup_cause),
.slow_wakeup_en_o(slow_wakeup_en),
.slow_reset_en_o(slow_reset_en),
.slow_main_pd_no(slow_main_pd_n),
.slow_io_clk_en_o(slow_io_clk_en),
.slow_core_clk_en_o(slow_core_clk_en),
.slow_req_pwrdn_o(slow_req_pwrdn),
.slow_ack_pwrup_o(slow_ack_pwrup),
.slow_ast_o(slow_ast),
.slow_peri_reqs_o(slow_peri_reqs),
.slow_peri_reqs_masked_i(slow_peri_reqs_masked),
// fast domain signals
.req_pwrdn_i(req_pwrdn),
.ack_pwrup_i(ack_pwrup),
.cfg_cdc_sync_i(reg2hw.cfg_cdc_sync.qe & reg2hw.cfg_cdc_sync.q),
.cdc_sync_done_o(hw2reg.cfg_cdc_sync.de),
.wakeup_en_i(reg2hw.wakeup_en),
.reset_en_i(reg2hw.reset_en.q),
.main_pd_ni(reg2hw.control.main_pd_n.q),
.io_clk_en_i(reg2hw.control.io_clk_en.q),
.core_clk_en_i(reg2hw.control.core_clk_en.q),
.ack_pwrdn_o(ack_pwrdn),
.req_pwrup_o(req_pwrup),
.pwrup_cause_o(pwrup_cause),
.peri_reqs_o(peri_reqs_masked),
// AST signals
.ast_i(pwr_ast_i),
// peripheral signals
.peri_i(peri_reqs_raw),
// flash handshake
.flash_i(pwr_flash_i),
.flash_o(flash_rsp)
);
assign hw2reg.cfg_cdc_sync.d = 1'b0;
////////////////////////////
/// Wakup and reset capture
////////////////////////////
// reset and wakeup requests are captured into the slow clock domain and then
// fanned out to other domains as necessary. This ensures there is not a huge
// time gap between when the slow clk domain sees the signal vs when the fast
// clock domains see it. This creates redundant syncing but keeps the time
// scale approximately the same across all domains.
//
// This also implies that these signals must be at least 1 clk_slow pulse long
//
// Since resets are not latched inside pwrmgr, there exists a corner case where
// non-always-on reset requests may get wiped out by a graceful low power entry
// It's not clear if this is really an issue at the moment, but something to keep
// in mind if future changes are needed.
//
// Latching the reset requests is not difficult, but the bigger question is who
// should clear it and when that should happen. If the clearing does not work
// correctly, it is possible for the device to end up in a permanent reset loop,
// and that would be very undesirable.
assign slow_peri_reqs_masked.wakeups = slow_peri_reqs.wakeups & slow_wakeup_en;
assign slow_peri_reqs_masked.rstreqs = slow_peri_reqs.rstreqs & slow_reset_en;
////////////////////////////
/// clk_slow FSM
////////////////////////////
pwrmgr_slow_fsm i_slow_fsm (
.clk_i (clk_slow_i),
.rst_ni (rst_slow_ni),
.wakeup_i (|slow_peri_reqs_masked.wakeups),
.reset_req_i (|slow_peri_reqs_masked.rstreqs),
.ast_i (slow_ast),
.req_pwrup_o (slow_req_pwrup),
.pwrup_cause_o (slow_pwrup_cause),
.pwrup_cause_toggle_o (slow_pwrup_cause_toggle),
.ack_pwrup_i (slow_ack_pwrup),
.req_pwrdn_i (slow_req_pwrdn),
.ack_pwrdn_o (slow_ack_pwrdn),
.main_pd_ni (slow_main_pd_n),
.io_clk_en_i (slow_io_clk_en),
.core_clk_en_i (slow_core_clk_en),
// outputs to AST - These are on the slow clock domain
// TBD - need to check this with partners
.ast_o (pwr_ast_o)
);
////////////////////////////
/// clk FSM
////////////////////////////
assign low_power_hint = reg2hw.control.low_power_hint.q == LowPower;
pwrmgr_fsm i_fsm (
.clk_i,
.rst_ni,
// interface with slow_fsm
.req_pwrup_i (req_pwrup),
.pwrup_cause_i (pwrup_cause), // por, wake or reset request
.ack_pwrup_o (ack_pwrup),
.req_pwrdn_o (req_pwrdn),
.ack_pwrdn_i (ack_pwrdn),
.low_power_entry_i (pwr_cpu_i.core_sleeping & low_power_hint),
.reset_req_i (|peri_reqs_masked.rstreqs),
// cfg
.main_pd_ni (reg2hw.control.main_pd_n.q),
// consumed in pwrmgr
.wkup_record_o (capture_en_pulse),
.wkup_o (wkup),
.clr_cfg_lock_o (clr_cfg_lock),
.fall_through_o (low_power_fall_through),
.abort_o (low_power_abort),
.clr_hint_o (clr_hint),
// rstmgr
.pwr_rst_o (pwr_rst_o),
.pwr_rst_i (pwr_rst_i),
// clkmgr
.ips_clk_en_o (pwr_clk_o.ip_clk_en),
.clk_en_status_i (pwr_clk_i.clk_status),
// otp
.otp_init_o (pwr_otp_o.otp_init),
.otp_done_i (pwr_otp_i.otp_done),
.otp_idle_i (pwr_otp_i.otp_idle),
// lc
.lc_init_o (pwr_lc_o.lc_init),
.lc_done_i (pwr_lc_i.lc_done),
.lc_idle_i (pwr_lc_i.lc_idle),
// flash
.flash_init_o (pwr_flash_o.flash_init),
.flash_done_i (flash_rsp.flash_done),
.flash_idle_i (flash_rsp.flash_idle)
);
////////////////////////////
/// Wakeup Info Capture
////////////////////////////
logic wake_info_wen;
logic [TotalWakeWidth-1:0] wake_info_data;
assign wake_info_wen = reg2hw.wake_info.abort.qe |
reg2hw.wake_info.fall_through.qe |
reg2hw.wake_info.reasons.qe;
assign wake_info_data = {reg2hw.wake_info.abort.q,
reg2hw.wake_info.fall_through.q,
reg2hw.wake_info.reasons.q};
pwrmgr_wake_info i_wake_info (
.clk_i,
.rst_ni,
.wr_i (wake_info_wen),
.data_i (wake_info_data),
.start_capture_i (capture_en_pulse),
.record_dis_i (reg2hw.wake_info_capture_dis.q),
.wakeups_i (peri_reqs_masked.wakeups),
.fall_through_i (low_power_fall_through),
.abort_i (low_power_abort),
.info_o (hw2reg.wake_info)
);
////////////////////////////
/// Interrupts
////////////////////////////
// This interrupt is asserted whenever the fast FSM transitions
// into active state. However, it does not assert during POR
prim_intr_hw #(.Width(1)) intr_wakeup (
.clk_i,
.rst_ni,
.event_intr_i (wkup),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.q),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.qe),
.reg2hw_intr_state_q_i (reg2hw.intr_state.q),
.hw2reg_intr_state_de_o (hw2reg.intr_state.de),
.hw2reg_intr_state_d_o (hw2reg.intr_state.d),
.intr_o (intr_wakeup_o)
);
////////////////////////////
/// Assertions
////////////////////////////
endmodule // pwrmgr