blob: 5bb2a0f224c18fee412b784591761acbd6b8c882 [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 CDC handling
//
`include "prim_assert.sv"
module pwrmgr_cdc import pwrmgr_pkg::*; import pwrmgr_reg_pkg::*;
(
// Clocks and resets
input clk_slow_i,
input clk_i,
input rst_slow_ni,
input rst_ni,
// slow domain signals,
input slow_req_pwrup_i,
input slow_ack_pwrdn_i,
input slow_pwrup_cause_toggle_i,
input pwrup_cause_e slow_pwrup_cause_i,
output logic [NumWkups-1:0] slow_wakeup_en_o,
output logic [NumRstReqs-1:0] slow_reset_en_o,
output logic slow_main_pd_no,
output logic slow_io_clk_en_o,
output logic slow_core_clk_en_o,
output logic slow_usb_clk_en_lp_o,
output logic slow_usb_clk_en_active_o,
output logic slow_req_pwrdn_o,
output logic slow_ack_pwrup_o,
output pwr_ast_rsp_t slow_ast_o,
output pwr_peri_t slow_peri_reqs_o,
input pwr_peri_t slow_peri_reqs_masked_i,
// fast domain signals
input req_pwrdn_i,
input ack_pwrup_i,
input cfg_cdc_sync_i,
input [NumWkups-1:0] wakeup_en_i,
input logic [NumRstReqs-1:0] reset_en_i,
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,
output logic ack_pwrdn_o,
output logic req_pwrup_o,
output pwrup_cause_e pwrup_cause_o,
output pwr_peri_t peri_reqs_o,
output logic cdc_sync_done_o,
// peripheral inputs, mixed domains
input pwr_peri_t peri_i,
input pwr_flash_rsp_t flash_i,
output pwr_flash_rsp_t flash_o,
// otp interface
input pwr_otp_rsp_t otp_i,
output pwr_otp_rsp_t otp_o,
// AST inputs, unknown domain
input pwr_ast_rsp_t ast_i
);
////////////////////////////////
// Sync from clk_i to clk_slow_i
////////////////////////////////
logic slow_cdc_sync;
pwr_ast_rsp_t slow_ast_q, slow_ast_q2;
prim_flop_2sync # (
.Width(1)
) i_req_pwrdn_sync (
.clk_i(clk_slow_i),
.rst_ni(rst_slow_ni),
.d_i(req_pwrdn_i),
.q_o(slow_req_pwrdn_o)
);
prim_flop_2sync # (
.Width(1)
) i_ack_pwrup_sync (
.clk_i(clk_slow_i),
.rst_ni(rst_slow_ni),
.d_i(ack_pwrup_i),
.q_o(slow_ack_pwrup_o)
);
prim_pulse_sync i_slow_cdc_sync (
.clk_src_i(clk_i),
.rst_src_ni(rst_ni),
.src_pulse_i(cfg_cdc_sync_i),
.clk_dst_i(clk_slow_i),
.rst_dst_ni(rst_slow_ni),
.dst_pulse_o(slow_cdc_sync)
);
// Even though this is multi-bit, the bits are individual request lines.
// So there is no general concern about recombining as there is
// no intent to use them in a related manner.
prim_flop_2sync # (
.Width($bits(pwr_peri_t))
) i_slow_ext_req_sync (
.clk_i (clk_slow_i),
.rst_ni (rst_slow_ni),
.d_i (peri_i),
.q_o (slow_peri_reqs_o)
);
// Some of the AST signals are multi-bits themselves (such as clk_val)
// thus they need to be delayed one more stage to check for stability
prim_flop_2sync # (
.Width($bits(pwr_ast_rsp_t)),
.ResetValue(PWR_AST_RSP_SYNC_DEFAULT)
) i_ast_sync (
.clk_i (clk_slow_i),
.rst_ni (rst_slow_ni),
.d_i (ast_i),
.q_o (slow_ast_q)
);
always_ff @(posedge clk_slow_i or negedge rst_slow_ni) begin
if (!rst_slow_ni) begin
slow_ast_q2 <= PWR_AST_RSP_SYNC_DEFAULT;
end else begin
slow_ast_q2 <= slow_ast_q;
end
end
// if possible, we should simulate below with random delays through
// flop_2sync
always_ff @(posedge clk_slow_i or negedge rst_slow_ni) begin
if (!rst_slow_ni) begin
slow_ast_o <= PWR_AST_RSP_SYNC_DEFAULT;
end else if (slow_ast_q2 == slow_ast_q) begin
// Output only updates whenever sync and delayed outputs both agree.
// If there are delays in sync, this will result in a 1 cycle difference
// and the output will hold the previous value
slow_ast_o <= slow_ast_q2;
end
end
// only register configurations can be sync'd using slow_cdc_sync
always_ff @(posedge clk_slow_i or negedge rst_slow_ni) begin
if (!rst_slow_ni) begin
slow_wakeup_en_o <= '0;
slow_reset_en_o <= '0;
slow_main_pd_no <= '0;
slow_io_clk_en_o <= '0;
slow_core_clk_en_o <= '0;
slow_usb_clk_en_lp_o <= '0;
slow_usb_clk_en_active_o <= 1'b1;
end else if (slow_cdc_sync) begin
slow_wakeup_en_o <= wakeup_en_i;
slow_reset_en_o <= reset_en_i;
slow_main_pd_no <= main_pd_ni;
slow_io_clk_en_o <= io_clk_en_i;
slow_core_clk_en_o <= core_clk_en_i;
slow_usb_clk_en_lp_o <= usb_clk_en_lp_i;
slow_usb_clk_en_active_o <= usb_clk_en_active_i;
end
end
////////////////////////////////
// Sync from clk_slow_i to clk_i
////////////////////////////////
logic pwrup_cause_toggle_q, pwrup_cause_toggle_q2;
logic pwrup_cause_chg;
prim_flop_2sync # (
.Width(1)
) i_req_pwrup_sync (
.clk_i,
.rst_ni,
.d_i(slow_req_pwrup_i),
.q_o(req_pwrup_o)
);
prim_flop_2sync # (
.Width(1)
) i_ack_pwrdn_sync (
.clk_i,
.rst_ni,
.d_i(slow_ack_pwrdn_i),
.q_o(ack_pwrdn_o)
);
prim_flop_2sync # (
.Width(1)
) i_pwrup_chg_sync (
.clk_i,
.rst_ni,
.d_i(slow_pwrup_cause_toggle_i),
.q_o(pwrup_cause_toggle_q)
);
prim_pulse_sync i_scdc_sync (
.clk_src_i(clk_slow_i),
.rst_src_ni(rst_slow_ni),
.src_pulse_i(slow_cdc_sync),
.clk_dst_i(clk_i),
.rst_dst_ni(rst_ni),
.dst_pulse_o(cdc_sync_done_o)
);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
pwrup_cause_toggle_q2 <= 1'b0;
end else begin
pwrup_cause_toggle_q2 <= pwrup_cause_toggle_q;
end
end
assign pwrup_cause_chg = pwrup_cause_toggle_q2 ^ pwrup_cause_toggle_q;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
pwrup_cause_o <= Por;
end else if (pwrup_cause_chg) begin
pwrup_cause_o <= slow_pwrup_cause_i;
end
end
prim_flop_2sync #(
.Width($bits(pwr_peri_t))
) i_ext_req_sync (
.clk_i,
.rst_ni,
.d_i(slow_peri_reqs_masked_i),
.q_o(peri_reqs_o)
);
// synchronize inputs from flash
prim_flop_2sync #(
.Width(1),
.ResetValue(1'b0)
) u_sync_flash_done (
.clk_i,
.rst_ni,
.d_i(flash_i.flash_done),
.q_o(flash_o.flash_done)
);
prim_flop_2sync #(
.Width(1),
// TODO: Is a value of 1 correct here?
.ResetValue(1'b1)
) u_sync_flash_idle (
.clk_i,
.rst_ni,
.d_i(flash_i.flash_idle),
.q_o(flash_o.flash_idle)
);
prim_flop_2sync #(
.Width($bits(pwr_otp_rsp_t)),
.ResetValue('0)
) u_sync_otp (
.clk_i,
.rst_ni,
.d_i(otp_i),
.q_o(otp_o)
);
endmodule
// An alternative solution relying on finding slow clock edges
// Keep it around just in case
/*
// finds a clk_slow edge in clk domain to know when it is safe to sync over
// this signal is only safe to use within the pwrmgr module when the source
// and destination clock domains are both clear
logic cdc_safe;
// pwrup is synced directly as it acts as a start signal to the pulse module
prim_flop_2sync # (
.Width(1)
) i_pwrup_sync (
.clk_i,
.rst_ni,
.d_i(slow_req_pwrup),
.q_o(req_pwrup)
);
pwrmgr_cdc_pulse i_cdc_pulse (
.clk_slow_i,
.clk_i,
.rst_ni,
.start_i(req_pwrup),
.stop_i(req_pwrdn),
.pulse_o(cdc_safe)
);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
ack_pwrdn <= '0;
pwrup_cause <= Por;
end else if (cdc_safe) begin
ack_pwrdn <= slow_ack_pwrdn;
pwrup_cause <= slow_pwrup_cause;
end
end
////////////////////////////
/// cdc handling - clk_slow_i
////////////////////////////
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
slow_wakeup_en <= '0;
slow_reset_en <= '0;
slow_main_pdb <= '0;
slow_io_clk_en <= '0;
slow_core_clk_en <= '0;
slow_ack_pwrup <= '0;
slow_req_pwrdn <= '0;
end else if (cdc_safe) begin
slow_wakeup_en <= reg2hw.wakeup_en.q;
slow_reset_en <= reg2hw.reset_en.q;
slow_main_pdb <= reg2hw.control.main_pdb.q;
slow_io_clk_en <= reg2hw.control.io_clk_en.q;
slow_core_clk_en <= reg2hw.control.core_clk_en.q;
slow_ack_pwrup <= ack_pwrup;
slow_req_pwrdn <= req_pwrdn;
end
end
// TODO
// Need to vote on the differential signals to ensure they are stable
prim_flop_2sync # (
.Width($bits(pwr_ast_rsp_t))
) i_pok_sync (
.clk_i (clk_slow_i),
.rst_ni (rst_slow_ni),
.d_i (pwr_ast_i),
.q_o (slow_ast_q)
);
*/