blob: 1bbbe64635e4428110da3555852a11a33f418ac8 [file] [log] [blame] [edit]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// This module is the overall reset manager wrapper
// TODO: This module is only a draft implementation that covers most of the rstmgr
// functoinality but is incomplete
`include "prim_assert.sv"
// This top level controller is fairly hardcoded right now, but will be switched to a template
module rstmgr
import rstmgr_pkg::*;
import rstmgr_reg_pkg::*;
(
// Primary module clocks
input clk_i,
input rst_ni, // this is currently connected to top level reset, but will change once ast is in
input clk_aon_i,
input clk_io_div4_i,
input clk_main_i,
input clk_io_i,
input clk_io_div2_i,
input clk_usb_i,
// Bus Interface
input tlul_pkg::tl_h2d_t tl_i,
output tlul_pkg::tl_d2h_t tl_o,
// pwrmgr interface
input pwrmgr_pkg::pwr_rst_req_t pwr_i,
output pwrmgr_pkg::pwr_rst_rsp_t pwr_o,
// ast interface
input rstmgr_ast_t ast_i,
// cpu related inputs
input rstmgr_cpu_t cpu_i,
// Interface to alert handler
input alert_pkg::alert_crashdump_t alert_dump_i,
// dft bypass
input scan_rst_ni,
input scanmode_i,
// reset outputs
output rstmgr_ast_out_t resets_ast_o,
output rstmgr_out_t resets_o
);
// receive POR and stretch
// The por is at first stretched and synced on clk_aon
// The rst_ni and pok_i input will be changed once AST is integrated
logic [PowerDomains-1:0] rst_por_aon_n;
for (genvar i = 0; i < PowerDomains; i++) begin : gen_rst_por_aon
if (i == DomainAonSel) begin : gen_rst_por_aon_normal
rstmgr_por u_rst_por_aon (
.clk_i(clk_aon_i),
.rst_ni(ast_i.aon_pok),
.scan_rst_ni,
.scanmode_i,
.rst_no(rst_por_aon_n[i])
);
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_rst_por_aon_n_mux (
.clk0_i(rst_por_aon_n[i]),
.clk1_i(scan_rst_ni),
.sel_i(scanmode_i),
.clk_o(resets_o.rst_por_aon_n[i])
);
end else begin : gen_rst_por_aon_tieoff
assign rst_por_aon_n[i] = 1'b0;
assign resets_o.rst_por_aon_n[i] = rst_por_aon_n[i];
end
end
////////////////////////////////////////////////////
// Register Interface //
////////////////////////////////////////////////////
// local_rst_n is the reset used by the rstmgr for its internal logic
logic local_rst_n;
assign local_rst_n = resets_o.rst_por_io_div2_n[DomainAonSel];
rstmgr_reg_pkg::rstmgr_reg2hw_t reg2hw;
rstmgr_reg_pkg::rstmgr_hw2reg_t hw2reg;
rstmgr_reg_top u_reg (
.clk_i,
.rst_ni(local_rst_n),
.tl_i,
.tl_o,
.reg2hw,
.hw2reg,
.devmode_i(1'b1)
);
////////////////////////////////////////////////////
// Input handling //
////////////////////////////////////////////////////
logic ndmreset_req_q;
logic ndm_req_valid;
prim_flop_2sync #(
.Width(1),
.ResetValue('0)
) u_sync (
.clk_i,
.rst_ni(local_rst_n),
.d_i(cpu_i.ndmreset_req),
.q_o(ndmreset_req_q)
);
assign ndm_req_valid = ndmreset_req_q & (pwr_i.reset_cause == pwrmgr_pkg::ResetNone);
////////////////////////////////////////////////////
// Source resets in the system //
// These are hardcoded and not directly used. //
// Instead they act as async reset roots. //
////////////////////////////////////////////////////
// The two source reset modules are chained together. The output of one is fed into the
// the second. This ensures that if upstream resets for any reason, the associated downstream
// reset will also reset.
logic [PowerDomains-1:0] rst_lc_src_n;
logic [PowerDomains-1:0] rst_sys_src_n;
// lc reset sources
rstmgr_ctrl u_lc_src (
.clk_i,
.rst_ni(local_rst_n),
.rst_req_i(pwr_i.rst_lc_req),
.rst_parent_ni({PowerDomains{1'b1}}),
.rst_no(rst_lc_src_n)
);
// sys reset sources
rstmgr_ctrl u_sys_src (
.clk_i,
.rst_ni(local_rst_n),
.rst_req_i(pwr_i.rst_sys_req | {PowerDomains{ndm_req_valid}}),
.rst_parent_ni(rst_lc_src_n),
.rst_no(rst_sys_src_n)
);
assign pwr_o.rst_lc_src_n = rst_lc_src_n;
assign pwr_o.rst_sys_src_n = rst_sys_src_n;
////////////////////////////////////////////////////
// Software reset controls external reg //
////////////////////////////////////////////////////
logic [NumSwResets-1:0] sw_rst_ctrl_n;
for (genvar i=0; i < NumSwResets; i++) begin : gen_sw_rst_ext_regs
prim_subreg #(
.DW(1),
.SwAccess(prim_subreg_pkg::SwAccessRW),
.RESVAL(1)
) u_rst_sw_ctrl_reg (
.clk_i,
.rst_ni(local_rst_n),
.we(reg2hw.sw_rst_ctrl_n[i].qe & reg2hw.sw_rst_regen[i]),
.wd(reg2hw.sw_rst_ctrl_n[i].q),
.de('0),
.d('0),
.qe(),
.q(sw_rst_ctrl_n[i]),
.qs(hw2reg.sw_rst_ctrl_n[i].d)
);
end
////////////////////////////////////////////////////
// leaf reset in the system //
// These should all be generated //
////////////////////////////////////////////////////
// To simplify generation, each reset generates all associated power domain outputs.
// If a reset does not support a particular power domain, that reset is always hard-wired to 0.
logic [PowerDomains-1:0] rst_por_n;
prim_flop_2sync #(
.Width(1),
.ResetValue('0)
) u_aon_por (
.clk_i(clk_main_i),
.rst_ni(rst_por_aon_n[DomainAonSel]),
.d_i(1'b1),
.q_o(rst_por_n[DomainAonSel])
);
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_aon_por_mux (
.clk0_i(rst_por_n[DomainAonSel]),
.clk1_i(scan_rst_ni),
.sel_i(scanmode_i),
.clk_o(resets_o.rst_por_n[DomainAonSel])
);
assign rst_por_n[Domain0Sel] = 1'b0;
assign resets_o.rst_por_n[Domain0Sel] = rst_por_n[Domain0Sel];
logic [PowerDomains-1:0] rst_por_io_n;
prim_flop_2sync #(
.Width(1),
.ResetValue('0)
) u_aon_por_io (
.clk_i(clk_io_i),
.rst_ni(rst_por_aon_n[DomainAonSel]),
.d_i(1'b1),
.q_o(rst_por_io_n[DomainAonSel])
);
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_aon_por_io_mux (
.clk0_i(rst_por_io_n[DomainAonSel]),
.clk1_i(scan_rst_ni),
.sel_i(scanmode_i),
.clk_o(resets_o.rst_por_io_n[DomainAonSel])
);
assign rst_por_io_n[Domain0Sel] = 1'b0;
assign resets_o.rst_por_io_n[Domain0Sel] = rst_por_io_n[Domain0Sel];
logic [PowerDomains-1:0] rst_por_io_div2_n;
prim_flop_2sync #(
.Width(1),
.ResetValue('0)
) u_aon_por_io_div2 (
.clk_i(clk_io_div2_i),
.rst_ni(rst_por_aon_n[DomainAonSel]),
.d_i(1'b1),
.q_o(rst_por_io_div2_n[DomainAonSel])
);
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_aon_por_io_div2_mux (
.clk0_i(rst_por_io_div2_n[DomainAonSel]),
.clk1_i(scan_rst_ni),
.sel_i(scanmode_i),
.clk_o(resets_o.rst_por_io_div2_n[DomainAonSel])
);
assign rst_por_io_div2_n[Domain0Sel] = 1'b0;
assign resets_o.rst_por_io_div2_n[Domain0Sel] = rst_por_io_div2_n[Domain0Sel];
logic [PowerDomains-1:0] rst_por_io_div4_n;
prim_flop_2sync #(
.Width(1),
.ResetValue('0)
) u_aon_por_io_div4 (
.clk_i(clk_io_div4_i),
.rst_ni(rst_por_aon_n[DomainAonSel]),
.d_i(1'b1),
.q_o(rst_por_io_div4_n[DomainAonSel])
);
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_aon_por_io_div4_mux (
.clk0_i(rst_por_io_div4_n[DomainAonSel]),
.clk1_i(scan_rst_ni),
.sel_i(scanmode_i),
.clk_o(resets_o.rst_por_io_div4_n[DomainAonSel])
);
assign rst_por_io_div4_n[Domain0Sel] = 1'b0;
assign resets_o.rst_por_io_div4_n[Domain0Sel] = rst_por_io_div4_n[Domain0Sel];
logic [PowerDomains-1:0] rst_por_usb_n;
prim_flop_2sync #(
.Width(1),
.ResetValue('0)
) u_aon_por_usb (
.clk_i(clk_usb_i),
.rst_ni(rst_por_aon_n[DomainAonSel]),
.d_i(1'b1),
.q_o(rst_por_usb_n[DomainAonSel])
);
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_aon_por_usb_mux (
.clk0_i(rst_por_usb_n[DomainAonSel]),
.clk1_i(scan_rst_ni),
.sel_i(scanmode_i),
.clk_o(resets_o.rst_por_usb_n[DomainAonSel])
);
assign rst_por_usb_n[Domain0Sel] = 1'b0;
assign resets_o.rst_por_usb_n[Domain0Sel] = rst_por_usb_n[Domain0Sel];
logic [PowerDomains-1:0] rst_lc_n;
assign rst_lc_n[DomainAonSel] = 1'b0;
assign resets_o.rst_lc_n[DomainAonSel] = rst_lc_n[DomainAonSel];
prim_flop_2sync #(
.Width(1),
.ResetValue('0)
) u_0_lc (
.clk_i(clk_main_i),
.rst_ni(rst_lc_src_n[Domain0Sel]),
.d_i(1'b1),
.q_o(rst_lc_n[Domain0Sel])
);
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_0_lc_mux (
.clk0_i(rst_lc_n[Domain0Sel]),
.clk1_i(scan_rst_ni),
.sel_i(scanmode_i),
.clk_o(resets_o.rst_lc_n[Domain0Sel])
);
logic [PowerDomains-1:0] rst_lc_io_div4_n;
assign rst_lc_io_div4_n[DomainAonSel] = 1'b0;
assign resets_o.rst_lc_io_div4_n[DomainAonSel] = rst_lc_io_div4_n[DomainAonSel];
prim_flop_2sync #(
.Width(1),
.ResetValue('0)
) u_0_lc_io_div4 (
.clk_i(clk_io_div4_i),
.rst_ni(rst_lc_src_n[Domain0Sel]),
.d_i(1'b1),
.q_o(rst_lc_io_div4_n[Domain0Sel])
);
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_0_lc_io_div4_mux (
.clk0_i(rst_lc_io_div4_n[Domain0Sel]),
.clk1_i(scan_rst_ni),
.sel_i(scanmode_i),
.clk_o(resets_o.rst_lc_io_div4_n[Domain0Sel])
);
logic [PowerDomains-1:0] rst_sys_n;
prim_flop_2sync #(
.Width(1),
.ResetValue('0)
) u_aon_sys (
.clk_i(clk_main_i),
.rst_ni(rst_sys_src_n[DomainAonSel]),
.d_i(1'b1),
.q_o(rst_sys_n[DomainAonSel])
);
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_aon_sys_mux (
.clk0_i(rst_sys_n[DomainAonSel]),
.clk1_i(scan_rst_ni),
.sel_i(scanmode_i),
.clk_o(resets_o.rst_sys_n[DomainAonSel])
);
prim_flop_2sync #(
.Width(1),
.ResetValue('0)
) u_0_sys (
.clk_i(clk_main_i),
.rst_ni(rst_sys_src_n[Domain0Sel]),
.d_i(1'b1),
.q_o(rst_sys_n[Domain0Sel])
);
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_0_sys_mux (
.clk0_i(rst_sys_n[Domain0Sel]),
.clk1_i(scan_rst_ni),
.sel_i(scanmode_i),
.clk_o(resets_o.rst_sys_n[Domain0Sel])
);
logic [PowerDomains-1:0] rst_sys_io_n;
assign rst_sys_io_n[DomainAonSel] = 1'b0;
assign resets_o.rst_sys_io_n[DomainAonSel] = rst_sys_io_n[DomainAonSel];
prim_flop_2sync #(
.Width(1),
.ResetValue('0)
) u_0_sys_io (
.clk_i(clk_io_div2_i),
.rst_ni(rst_sys_src_n[Domain0Sel]),
.d_i(1'b1),
.q_o(rst_sys_io_n[Domain0Sel])
);
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_0_sys_io_mux (
.clk0_i(rst_sys_io_n[Domain0Sel]),
.clk1_i(scan_rst_ni),
.sel_i(scanmode_i),
.clk_o(resets_o.rst_sys_io_n[Domain0Sel])
);
logic [PowerDomains-1:0] rst_sys_io_div4_n;
prim_flop_2sync #(
.Width(1),
.ResetValue('0)
) u_aon_sys_io_div4 (
.clk_i(clk_io_div4_i),
.rst_ni(rst_sys_src_n[DomainAonSel]),
.d_i(1'b1),
.q_o(rst_sys_io_div4_n[DomainAonSel])
);
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_aon_sys_io_div4_mux (
.clk0_i(rst_sys_io_div4_n[DomainAonSel]),
.clk1_i(scan_rst_ni),
.sel_i(scanmode_i),
.clk_o(resets_o.rst_sys_io_div4_n[DomainAonSel])
);
prim_flop_2sync #(
.Width(1),
.ResetValue('0)
) u_0_sys_io_div4 (
.clk_i(clk_io_div4_i),
.rst_ni(rst_sys_src_n[Domain0Sel]),
.d_i(1'b1),
.q_o(rst_sys_io_div4_n[Domain0Sel])
);
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_0_sys_io_div4_mux (
.clk0_i(rst_sys_io_div4_n[Domain0Sel]),
.clk1_i(scan_rst_ni),
.sel_i(scanmode_i),
.clk_o(resets_o.rst_sys_io_div4_n[Domain0Sel])
);
logic [PowerDomains-1:0] rst_sys_aon_n;
prim_flop_2sync #(
.Width(1),
.ResetValue('0)
) u_aon_sys_aon (
.clk_i(clk_aon_i),
.rst_ni(rst_sys_src_n[DomainAonSel]),
.d_i(1'b1),
.q_o(rst_sys_aon_n[DomainAonSel])
);
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_aon_sys_aon_mux (
.clk0_i(rst_sys_aon_n[DomainAonSel]),
.clk1_i(scan_rst_ni),
.sel_i(scanmode_i),
.clk_o(resets_o.rst_sys_aon_n[DomainAonSel])
);
assign rst_sys_aon_n[Domain0Sel] = 1'b0;
assign resets_o.rst_sys_aon_n[Domain0Sel] = rst_sys_aon_n[Domain0Sel];
logic [PowerDomains-1:0] rst_spi_device_n;
assign rst_spi_device_n[DomainAonSel] = 1'b0;
assign resets_o.rst_spi_device_n[DomainAonSel] = rst_spi_device_n[DomainAonSel];
prim_flop_2sync #(
.Width(1),
.ResetValue('0)
) u_0_spi_device (
.clk_i(clk_io_div2_i),
.rst_ni(rst_sys_src_n[Domain0Sel]),
.d_i(sw_rst_ctrl_n[SPI_DEVICE]),
.q_o(rst_spi_device_n[Domain0Sel])
);
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_0_spi_device_mux (
.clk0_i(rst_spi_device_n[Domain0Sel]),
.clk1_i(scan_rst_ni),
.sel_i(scanmode_i),
.clk_o(resets_o.rst_spi_device_n[Domain0Sel])
);
logic [PowerDomains-1:0] rst_usb_n;
prim_flop_2sync #(
.Width(1),
.ResetValue('0)
) u_aon_usb (
.clk_i(clk_usb_i),
.rst_ni(rst_sys_src_n[DomainAonSel]),
.d_i(sw_rst_ctrl_n[USB]),
.q_o(rst_usb_n[DomainAonSel])
);
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_aon_usb_mux (
.clk0_i(rst_usb_n[DomainAonSel]),
.clk1_i(scan_rst_ni),
.sel_i(scanmode_i),
.clk_o(resets_o.rst_usb_n[DomainAonSel])
);
assign rst_usb_n[Domain0Sel] = 1'b0;
assign resets_o.rst_usb_n[Domain0Sel] = rst_usb_n[Domain0Sel];
////////////////////////////////////////////////////
// Reset info construction //
////////////////////////////////////////////////////
logic rst_hw_req;
logic rst_low_power;
logic rst_ndm;
logic rst_cpu_nq;
logic first_reset;
// The qualification of first reset below could technically be POR as well.
// However, that would enforce software to clear POR upon cold power up. While that is
// the most likely outcome anyways, hardware should not require that.
assign rst_hw_req = ~first_reset & pwr_i.reset_cause == pwrmgr_pkg::HwReq;
assign rst_ndm = ~first_reset & ndm_req_valid;
assign rst_low_power = ~first_reset & pwr_i.reset_cause == pwrmgr_pkg::LowPwrEntry;
prim_flop_2sync #(
.Width(1),
.ResetValue('0)
) u_cpu_reset_synced (
.clk_i,
.rst_ni(local_rst_n),
.d_i(cpu_i.rst_cpu_n),
.q_o(rst_cpu_nq)
);
// first reset is a flag that blocks reset recording until first de-assertion
always_ff @(posedge clk_i or negedge local_rst_n) begin
if (!local_rst_n) begin
first_reset <= 1'b1;
end else if (rst_cpu_nq) begin
first_reset <= 1'b0;
end
end
// Only sw is allowed to clear a reset reason, hw is only allowed to set it.
assign hw2reg.reset_info.low_power_exit.d = 1'b1;
assign hw2reg.reset_info.low_power_exit.de = rst_low_power;
assign hw2reg.reset_info.ndm_reset.d = 1'b1;
assign hw2reg.reset_info.ndm_reset.de = rst_ndm;
// HW reset requests most likely will be multi-bit, so OR in whatever reasons
// that are already set.
assign hw2reg.reset_info.hw_req.d = pwr_i.rstreqs | reg2hw.reset_info.hw_req.q;
assign hw2reg.reset_info.hw_req.de = rst_hw_req;
////////////////////////////////////////////////////
// Exported resets //
////////////////////////////////////////////////////
assign resets_ast_o.rst_ast_usbdev_sys_io_div4_n = resets_o.rst_sys_io_div4_n;
assign resets_ast_o.rst_ast_usbdev_usb_n = resets_o.rst_usb_n;
assign resets_ast_o.rst_ast_sensor_ctrl_sys_io_div4_n = resets_o.rst_sys_io_div4_n;
////////////////////////////////////////////////////
// Crash info capture //
////////////////////////////////////////////////////
localparam int CrashRemainder = $bits(alert_pkg::alert_crashdump_t) % RdWidth > 0 ? 1 : 0;
localparam int CrashStoreSlot = $bits(alert_pkg::alert_crashdump_t) / RdWidth +
CrashRemainder;
localparam int TotalWidth = CrashStoreSlot * RdWidth;
localparam int SlotCntWidth = $clog2(CrashStoreSlot);
logic dump_capture;
logic [2**SlotCntWidth-1:0][RdWidth-1:0] slots;
logic [CrashStoreSlot-1:0][RdWidth-1:0] slots_q;
// capture on any legal reset request
assign dump_capture = reg2hw.alert_info_ctrl.en.q &
(rst_hw_req | rst_ndm | rst_low_power);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
slots_q <= '0;
end else if (dump_capture) begin
slots_q <= TotalWidth'(alert_dump_i);
end
end
always_comb begin
slots = '0;
slots[CrashStoreSlot-1:0] = slots_q;
end
// once dump is captured, no more information is captured until
// re-eanbled by software.
assign hw2reg.alert_info_ctrl.en.d = 1'b0;
assign hw2reg.alert_info_ctrl.en.de = dump_capture;
// number of segments to read
assign hw2reg.alert_info_attr.d = CrashStoreSlot;
// the actual dump data
assign hw2reg.alert_info.d = slots[reg2hw.alert_info_ctrl.index.q[SlotCntWidth-1:0]];
if (SlotCntWidth < IdxWidth) begin : gen_tieoffs
logic [IdxWidth-SlotCntWidth-1:0] unused_idx;
assign unused_idx = reg2hw.alert_info_ctrl.index.q[IdxWidth-1:SlotCntWidth];
end
////////////////////////////////////////////////////
// Assertions //
////////////////////////////////////////////////////
// Make sure the crash dump isn't excessively large
`ASSERT_INIT(CntWidth_A, SlotCntWidth <= IdxWidth)
// when upstream resets, downstream must also reset
// output known asserts
`ASSERT_KNOWN(TlDValidKnownO_A, tl_o.d_valid )
`ASSERT_KNOWN(TlAReadyKnownO_A, tl_o.a_ready )
`ASSERT_KNOWN(PwrKnownO_A, pwr_o )
`ASSERT_KNOWN(ResetsKnownO_A, resets_o )
`ASSERT_KNOWN(AstResetsKnownO_A, resets_ast_o )
endmodule // rstmgr