Hazard3/hdl/hazard3_power_ctrl.v

174 lines
5.7 KiB
Verilog

/*****************************************************************************\
| 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_clkgate,
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_clkgate) && (sleeping_on_wfi || allow_sleep_on_block)) begin
if (frontend_pwrdown_ok) begin
pwrup_req <= !allow_power_down;
clk_en <= !allow_clkgate;
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_clkgate);
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