/*****************************************************************************\ | Copyright (C) 2022 Luke Wren | | SPDX-License-Identifier: Apache-2.0 | \*****************************************************************************/ `default_nettype none // Wake/sleep (power) state machine for Hazard3 module hazard3_power_ctrl #( `include "hazard3_config.vh" ) ( input wire clk_always_on, input wire rst_n, // 4-phase (Gray code) req/ack handshake for requesting and releasing // power+clock enable on non-processor hardware, e.g. the bus fabric. This // can also be used for an external controller to gate the processor's clk // input, rather than the clk_en signal below. output reg pwrup_req, input wire pwrup_ack, // Top-level clock enable for an optional clock gate on the processor's clk // input (but not clk_always_on, which clocks this module and the IRQ input // flops). This allows the processor to clock-gate when sleeping. It's // acceptable for the clock gate cell to have one cycle of delay when // clk_en changes. output reg clk_en, // Power state controls from CSRs input wire allow_sleep, input wire allow_power_down, input wire allow_sleep_on_block, // Signal from frontend that it has stalled against the WFI pipeline // stall, and we are now clear to enter a deep sleep state input wire frontend_pwrdown_ok, input wire sleeping_on_wfi, input wire wfi_wakeup_req, input wire sleeping_on_block, input wire block_wakeup_req_pulse, output reg stall_release ); // ---------------------------------------------------------------------------- // Wake/sleep state machine localparam W_STATE = 2; localparam S_AWAKE = 2'h0; localparam S_ENTER_ASLEEP = 2'h1; localparam S_ASLEEP = 2'h2; localparam S_ENTER_AWAKE = 2'h3; reg [W_STATE-1:0] state; reg block_wakeup_req; wire active_wake_req = (sleeping_on_block && (block_wakeup_req || wfi_wakeup_req)) || (sleeping_on_wfi && wfi_wakeup_req); // Note: we assert our power up request during reset, and *assume* that the // power up acknowledge is also high at reset. If this is a problem, extend // the core reset. always @ (posedge clk_always_on or negedge rst_n) begin if (!rst_n) begin state <= S_AWAKE; pwrup_req <= 1'b1; clk_en <= 1'b1; stall_release <= 1'b0; end else begin stall_release <= 1'b0; case (state) S_AWAKE: if (sleeping_on_wfi || sleeping_on_block) begin if (stall_release) begin // The last cycle of an ongoing which we have just released. Sit // tight, this instruction will move down the pipeline at the // end of this cycle. (There is an assertion that this doesn't // happen twice.) state <= S_AWAKE; end else if (active_wake_req) begin // Skip deep sleep if it would immediately fall through. stall_release <= 1'b1; end else if ((allow_power_down || allow_sleep) && (sleeping_on_wfi || allow_sleep_on_block)) begin if (frontend_pwrdown_ok) begin pwrup_req <= !allow_power_down; clk_en <= !allow_sleep; state <= allow_power_down ? S_ENTER_ASLEEP : S_ASLEEP; end else begin // Stay awake until it is safe to power down (i.e. until our // instruction fetch goes quiet). state <= S_AWAKE; end end else begin // No power state change. Just sit with the pipeline stalled. state <= S_AWAKE; end end S_ENTER_ASLEEP: if (!pwrup_ack) begin state <= S_ASLEEP; end S_ASLEEP: if (active_wake_req) begin pwrup_req <= 1'b1; clk_en <= 1'b1; // Still go through the enter state for non-power-down wakeup, in // case the clock gate cell has a 1 cycle delay. state <= S_ENTER_AWAKE; end S_ENTER_AWAKE: if (pwrup_ack || !allow_power_down) begin state <= S_AWAKE; stall_release <= 1'b1; end default: begin state <= S_AWAKE; end endcase end end `ifdef HAZARD3_ASSERTIONS // Regs are a workaround for the non-constant reset value issue with // $past() in yosys-smtbmc. reg past_sleeping; reg past_stall_release; always @ (posedge clk_always_on or negedge rst_n) begin if (!rst_n) begin past_sleeping <= 1'b0; past_stall_release <= 1'b0; end else begin past_sleeping <= sleeping_on_wfi || sleeping_on_block; past_stall_release <= stall_release; // These must always be mutually exclusive. assert(!(sleeping_on_wfi && sleeping_on_block)); if (stall_release) begin // Presumably there was a stall which we just released assert(past_sleeping); // Presumably we are still in that stall assert(sleeping_on_wfi|| sleeping_on_block); // It takes one cycle to do a release and enter a new sleep state, so a // double release should be impossible. assert(!past_stall_release); end if (state == S_ASLEEP) begin assert(allow_power_down || allow_sleep); end end end `endif // ---------------------------------------------------------------------------- // Pulse->level for block wakeup // Unblock signal is sticky: a prior unblock with no block since will cause // the next block to immediately fall through. always @ (posedge clk_always_on or negedge rst_n) begin if (!rst_n) begin block_wakeup_req <= 1'b0; end else begin // Note the OR takes precedence over the AND, so we don't miss a second // unblock that arrives at the instant we wake up. block_wakeup_req <= (block_wakeup_req && !( sleeping_on_block && stall_release )) || block_wakeup_req_pulse; end end endmodule `ifndef YOSYS `default_nettype wire `endif