174 lines
5.6 KiB
Verilog
174 lines
5.6 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_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
|