[pinmux] Update pinmux implementation and add sleep/strap sampling features Signed-off-by: Michael Schaffner <msf@opentitan.org>
diff --git a/hw/ip/pinmux/pinmux.core b/hw/ip/pinmux/pinmux.core index 3e7d7e5..e32e3b8 100644 --- a/hw/ip/pinmux/pinmux.core +++ b/hw/ip/pinmux/pinmux.core
@@ -11,7 +11,6 @@ - lowrisc:ip:pinmux_reg - lowrisc:ip:pinmux_component - parameters: SYNTHESIS: datatype: bool
diff --git a/hw/ip/pinmux/pinmux_component.core b/hw/ip/pinmux/pinmux_component.core index e405669..f130a7f 100644 --- a/hw/ip/pinmux/pinmux_component.core +++ b/hw/ip/pinmux/pinmux_component.core
@@ -11,6 +11,8 @@ - lowrisc:ip:tlul - lowrisc:prim:all files: + - rtl/pinmux_pkg.sv + - rtl/pinmux_wkup.sv - rtl/pinmux.sv file_type: systemVerilogSource
diff --git a/hw/ip/pinmux/rtl/pinmux.sv b/hw/ip/pinmux/rtl/pinmux.sv index bd6993d..bf79015 100644 --- a/hw/ip/pinmux/rtl/pinmux.sv +++ b/hw/ip/pinmux/rtl/pinmux.sv
@@ -7,27 +7,59 @@ `include "prim_assert.sv" -module pinmux ( - input clk_i, - input rst_ni, +module pinmux import pinmux_pkg::*; import pinmux_reg_pkg::*; ( + input clk_i, + input rst_ni, + // Slow always-on clock + input clk_aon_i, + input rst_aon_ni, + // Wakeup request, running on clk_aon_i + output logic aon_wkup_req_o, + // Sleep enable, running on clk_i + input sleep_en_i, + // Strap sample request + input lc_strap_req_t lc_pinmux_strap_i, + output lc_strap_rsp_t lc_pinmux_strap_o, // Bus Interface (device) - input tlul_pkg::tl_h2d_t tl_i, - output tlul_pkg::tl_d2h_t tl_o, - // Peripheral side - input [pinmux_reg_pkg::NPeriphOut-1:0] periph_to_mio_i, - input [pinmux_reg_pkg::NPeriphOut-1:0] periph_to_mio_oe_i, - output logic [pinmux_reg_pkg::NPeriphIn-1:0] mio_to_periph_o, + input tlul_pkg::tl_h2d_t tl_i, + output tlul_pkg::tl_d2h_t tl_o, + // Muxed Peripheral side + input [NMioPeriphOut-1:0] periph_to_mio_i, + input [NMioPeriphOut-1:0] periph_to_mio_oe_i, + output logic [NMioPeriphIn-1:0] mio_to_periph_o, + // Dedicated Peripheral side + input [NDioPads-1:0] periph_to_dio_i, + input [NDioPads-1:0] periph_to_dio_oe_i, + output logic [NDioPads-1:0] dio_to_periph_o, // Pad side - output logic [pinmux_reg_pkg::NMioPads-1:0] mio_out_o, - output logic [pinmux_reg_pkg::NMioPads-1:0] mio_oe_o, - input [pinmux_reg_pkg::NMioPads-1:0] mio_in_i + // MIOs + output logic [NMioPads-1:0] mio_out_o, + output logic [NMioPads-1:0] mio_oe_o, + input [NMioPads-1:0] mio_in_i, + // DIOs + output logic [NDioPads-1:0] dio_out_o, + output logic [NDioPads-1:0] dio_oe_o, + input [NDioPads-1:0] dio_in_i ); + //////////////////////////// + // Parameters / Constants // + //////////////////////////// + + // TODO: these need to be parameterizable via topgen at some point. + // They have been placed here such that they do not generate + // warnings in the C header generation step, since logic is not supported + // as a data type yet. + localparam logic [pinmux_reg_pkg::NMioPeriphOut-1:0] MioPeriphHasSleepMode = '1; + localparam logic [pinmux_reg_pkg::NDioPads-1:0] DioPeriphHasSleepMode = '1; + localparam logic [pinmux_reg_pkg::NDioPads-1:0] DioPeriphHasWkup = '1; + ////////////////////////////////// // Regfile Breakout and Mapping // ////////////////////////////////// - pinmux_reg_pkg::pinmux_reg2hw_t reg2hw; + pinmux_reg2hw_t reg2hw; + pinmux_hw2reg_t hw2reg; pinmux_reg_top i_reg_top ( .clk_i , @@ -35,15 +67,91 @@ .tl_i , .tl_o , .reg2hw , + .hw2reg , .devmode_i(1'b1) ); + ///////////////////// + // Sleep registers // + ///////////////////// + + logic sleep_en_q; + logic [NMioPads-1:0] mio_out_sleep_d, mio_oe_sleep_d; + logic [NMioPads-1:0] mio_out_sleep_q, mio_oe_sleep_q; + logic [NDioPads-1:0] dio_out_sleep_d, dio_oe_sleep_d; + logic [NDioPads-1:0] dio_out_sleep_q, dio_oe_sleep_q; + // these are external due to their WARL behavior + logic [NDioPads-1:0][1:0] dio_out_sleep_val_d, dio_out_sleep_val_q; + + // latch MIO/DIO state when going to sleep + // 0: drive low + // 1: drive high + // 2: high-z + // 3: previous value + for (genvar k = 0; k < NMioPads; k++) begin : gen_mio_sleep + assign mio_out_sleep_d[k] = (reg2hw.mio_out_sleep_val[k].q == 0) ? 1'b0 : + (reg2hw.mio_out_sleep_val[k].q == 1) ? 1'b1 : + (reg2hw.mio_out_sleep_val[k].q == 2) ? 1'b0 : mio_out_o[k]; + + assign mio_oe_sleep_d[k] = (reg2hw.mio_out_sleep_val[k].q == 0) ? 1'b1 : + (reg2hw.mio_out_sleep_val[k].q == 1) ? 1'b1 : + (reg2hw.mio_out_sleep_val[k].q == 2) ? 1'b0 : mio_oe_o[k]; + end + + // since DIO pads are permanently mapped to a specific peripheral, + // we only need to support retention regs on non-always on peripherals, + // outputs / inouts. + for (genvar k = 0; k < NDioPads; k++) begin : gen_dio_sleep + if (DioPeriphHasSleepMode[k]) begin : gen_warl_connect + assign hw2reg.dio_out_sleep_val[k].d = dio_out_sleep_val_q[k]; + + assign dio_out_sleep_val_d[k] = (reg2hw.dio_out_sleep_val[k].qe) ? + reg2hw.dio_out_sleep_val[k].q : + dio_out_sleep_val_q[k]; + + assign dio_out_sleep_d[k] = (dio_out_sleep_val_q[k] == 0) ? 1'b0 : + (dio_out_sleep_val_q[k] == 1) ? 1'b1 : + (dio_out_sleep_val_q[k] == 2) ? 1'b0 : dio_out_o[k]; + + assign dio_oe_sleep_d[k] = (dio_out_sleep_val_q[k] == 0) ? 1'b1 : + (dio_out_sleep_val_q[k] == 1) ? 1'b1 : + (dio_out_sleep_val_q[k] == 2) ? 1'b0 : dio_oe_o[k]; + end else begin : gen_warl_tie0 + // these signals will be unused + assign hw2reg.dio_out_sleep_val[k].d = 2'b10; // default value defined in hjson + assign dio_out_sleep_val_d[k] = 2'b10; // default value defined in hjson + assign dio_out_sleep_d[k] = '0; + assign dio_oe_sleep_d[k] = '0; + end + end + + always_ff @(posedge clk_i or negedge rst_ni) begin : p_sleep + if (!rst_ni) begin + sleep_en_q <= 1'b0; + dio_out_sleep_val_q <= {NDioPads{2'b10}}; // default value defined in hjson + mio_out_sleep_q <= '0; + mio_oe_sleep_q <= '0; + dio_out_sleep_q <= '0; + dio_oe_sleep_q <= '0; + end else begin + sleep_en_q <= sleep_en_i; + dio_out_sleep_val_q <= dio_out_sleep_val_d; + + if (sleep_en_i & !sleep_en_q) begin + mio_out_sleep_q <= mio_out_sleep_d; + mio_oe_sleep_q <= mio_oe_sleep_d; + dio_out_sleep_q <= dio_out_sleep_d; + dio_oe_sleep_q <= dio_oe_sleep_d; + end + end + end + /////////////// // Input Mux // /////////////// - for (genvar k = 0; k < pinmux_reg_pkg::NPeriphIn; k++) begin : gen_periph_in - logic [2**$clog2(pinmux_reg_pkg::NMioPads+2)-1:0] data_mux; + for (genvar k = 0; k < NMioPeriphIn; k++) begin : gen_mio_periph_in + logic [2**$clog2(NMioPads+2)-1:0] data_mux; // stack input and default signals for convenient indexing below // possible defaults: constant 0 or 1 assign data_mux = $bits(data_mux)'({mio_in_i, 1'b1, 1'b0}); @@ -55,15 +163,110 @@ // Output Mux // //////////////// - for (genvar k = 0; k < pinmux_reg_pkg::NMioPads; k++) begin : gen_mio_out - logic [2**$clog2(pinmux_reg_pkg::NPeriphOut+3)-1:0] data_mux, oe_mux; + for (genvar k = 0; k < NMioPads; k++) begin : gen_mio_out + logic sleep_en; + logic [2**$clog2(NMioPeriphOut+3)-1:0] data_mux, oe_mux, sleep_mux; // stack output data/enable and default signals for convenient indexing below // possible defaults: 0, 1 or 2 (high-Z) - assign data_mux = $bits(data_mux)'({periph_to_mio_i, 1'b0, 1'b1, 1'b0}); - assign oe_mux = $bits(oe_mux)'({periph_to_mio_oe_i, 1'b0, 1'b1, 1'b1}); + assign data_mux = $bits(data_mux)'({periph_to_mio_i, 1'b0, 1'b1, 1'b0}); + assign oe_mux = $bits(oe_mux)'({periph_to_mio_oe_i, 1'b0, 1'b1, 1'b1}); + assign sleep_mux = $bits(sleep_mux)'({MioPeriphHasSleepMode, 1'b1, 1'b1, 1'b1}); + + // check whether this peripheral can actually go to sleep + assign sleep_en = sleep_mux[reg2hw.mio_outsel[k].q] & sleep_en_q; // index using configured outsel - assign mio_out_o[k] = data_mux[reg2hw.mio_outsel[k].q]; - assign mio_oe_o[k] = oe_mux[reg2hw.mio_outsel[k].q]; + assign mio_out_o[k] = (sleep_en) ? mio_out_sleep_q[k] : data_mux[reg2hw.mio_outsel[k].q]; + assign mio_oe_o[k] = (sleep_en) ? mio_oe_sleep_q[k] : oe_mux[reg2hw.mio_outsel[k].q]; + end + + ///////////////////// + // DIO connections // + ///////////////////// + + // Inputs are just fed through + assign dio_to_periph_o = dio_in_i; + + for (genvar k = 0; k < NDioPads; k++) begin : gen_dio_out + // Since this is a DIO, this can be determined at design time + if (DioPeriphHasSleepMode[k]) begin : gen_sleep + assign dio_out_o[k] = (sleep_en_q) ? dio_out_sleep_q[k] : periph_to_dio_i[k]; + assign dio_oe_o[k] = (sleep_en_q) ? dio_oe_sleep_q[k] : periph_to_dio_oe_i[k]; + end else begin : gen_nosleep + assign dio_out_o[k] = periph_to_dio_i[k]; + assign dio_oe_o[k] = periph_to_dio_oe_i[k]; + end + end + + ////////////////////// + // Wakeup detectors // + ////////////////////// + + localparam int AlignedMuxSize = (NMioPads + 2 > NDioPads) ? 2**$clog2(NMioPads + 2) : + 2**$clog2(NDioPads); + logic [NWkupDetect-1:0] aon_wkup_req; + logic [AlignedMuxSize-1:0] dio_data_mux, mio_data_mux; + assign mio_data_mux = AlignedMuxSize'({mio_in_i, 1'b0, 1'b0}); + + // Only connect DIOs that are not excempt + for (genvar k = 0; k < AlignedMuxSize; k++) begin : gen_dio_wkup + if (k < NDioPads && DioPeriphHasWkup[k]) begin : gen_dio_wkup_connect + assign dio_data_mux[k] = dio_in_i[k]; + end else begin : gen_dio_tie_off + assign dio_data_mux[k] = 1'b0; + end + end + + for (genvar k = 0; k < NWkupDetect; k++) begin : gen_wkup_detect + logic pin_value; + assign pin_value = (reg2hw.wkup_detector[k].miodio.q) ? + dio_data_mux[reg2hw.wkup_detector_padsel[k]] : + mio_data_mux[reg2hw.wkup_detector_padsel[k]]; + + pinmux_wkup i_pinmux_wkup ( + .clk_i, + .rst_ni, + .clk_aon_i, + .rst_aon_ni, + // config signals. these are synched to clk_aon internally + .wkup_en_i ( reg2hw.wkup_detector_en[k].q ), + .filter_en_i ( reg2hw.wkup_detector[k].filter.q ), + .wkup_mode_i ( reg2hw.wkup_detector[k].mode.q ), + .wkup_cnt_th_i ( reg2hw.wkup_detector_cnt_th[k].q ), + .pin_value_i ( pin_value ), + // cause reg signals. these are synched from/to clk_aon internally + .wkup_cause_valid_i ( reg2hw.wkup_cause[k].qe ), + .wkup_cause_data_i ( reg2hw.wkup_cause[k].q ), + .wkup_cause_data_o ( hw2reg.wkup_cause[k].d ), + // wakeup request signals on clk_aon (level encoded) + .aon_wkup_req_o ( aon_wkup_req[k] ) + ); + end + + // OR' together all wakeup requests + assign aon_wkup_req_o = |aon_wkup_req; + + //////////////////// + // Strap Sampling // + //////////////////// + + logic [NStraps-1:0] lc_strap_taps; + lc_strap_rsp_t lc_strap_d, lc_strap_q; + + for (genvar k = 0; k < NStraps; k++) begin : gen_strap_taps + assign lc_strap_taps[k] = mio_in_i[MioStrapPos[k]]; + end + + assign lc_pinmux_strap_o = lc_strap_q; + assign lc_strap_d = (lc_pinmux_strap_i.sample_pulse) ? + '{valid: 1'b1, straps: lc_strap_taps} : + lc_strap_q; + + always_ff @(posedge clk_i or negedge rst_ni) begin : p_strap_sample + if (!rst_ni) begin + lc_strap_q <= '0; + end else begin + lc_strap_q <= lc_strap_d; + end end //////////////// @@ -72,8 +275,22 @@ `ASSERT_KNOWN(TlDValidKnownO_A, tl_o.d_valid) `ASSERT_KNOWN(TlAReadyKnownO_A, tl_o.a_ready) - `ASSERT_KNOWN(MioToPeriphKnownO_A, mio_to_periph_o) - `ASSERT_KNOWN(MioOutKnownO_A, mio_out_o) + // `ASSERT_KNOWN(MioToPeriphKnownO_A, mio_to_periph_o) `ASSERT_KNOWN(MioOeKnownO_A, mio_oe_o) + // `ASSERT_KNOWN(DioToPeriphKnownO_A, dio_to_periph_o) + `ASSERT_KNOWN(DioOeKnownO_A, dio_oe_o) + `ASSERT_KNOWN(LcPinmuxStrapKnownO_A, lc_pinmux_strap_o) + + // TODO: need to check why some outputs are not valid (e.g. SPI device MISO) + // for (genvar k = 0; k < NMioPads; k++) begin : gen_mio_known_if + // `ASSERT_KNOWN_IF(MioOutKnownO_A, mio_out_o[k], mio_oe_o[k]) + // end + + // for (genvar k = 0; k < NDioPads; k++) begin : gen_dio_known_if + // `ASSERT_KNOWN_IF(DioOutKnownO_A, dio_out_o[k], dio_oe_o[k]) + // end + + // running on slow AON clock + `ASSERT_KNOWN(AonWkupReqKnownO_A, aon_wkup_req_o, !rst_aon_ni, clk_aon_i) endmodule : pinmux
diff --git a/hw/ip/pinmux/rtl/pinmux_pkg.sv b/hw/ip/pinmux/rtl/pinmux_pkg.sv new file mode 100644 index 0000000..fe73341 --- /dev/null +++ b/hw/ip/pinmux/rtl/pinmux_pkg.sv
@@ -0,0 +1,35 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +package pinmux_pkg; + + // Wakeup Detector Modes + typedef enum logic [2:0] { + Disabled = 3'b000, + Negedge = 3'b001, + Posedge = 3'b010, + Edge = 3'b011, + LowTimed = 3'b100, + HighTimed = 3'b101 + } wkup_mode_e; + + // Interface with LC controller + parameter int NStraps = 2; + // Strap sampling is only supported on MIOs at the moment + parameter int MioStrapPos [0:NStraps-1] = '{1, 0}; + + typedef struct packed { + logic sample_pulse; + } lc_strap_req_t; + + parameter lc_strap_req_t LC_PINMUX_STRAP_REQ_DEFAULT = '{ + sample_pulse: 1'b0 + }; + + typedef struct packed { + logic valid; + logic [NStraps-1:0] straps; + } lc_strap_rsp_t; + +endpackage : pinmux_pkg
diff --git a/hw/ip/pinmux/rtl/pinmux_wkup.sv b/hw/ip/pinmux/rtl/pinmux_wkup.sv new file mode 100644 index 0000000..58475e2 --- /dev/null +++ b/hw/ip/pinmux/rtl/pinmux_wkup.sv
@@ -0,0 +1,198 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +module pinmux_wkup import pinmux_pkg::*; import pinmux_reg_pkg::*; #( + parameter Cycles = 4 +) ( + input clk_i, + input rst_ni, + // Always on clock / reset + input clk_aon_i, + input rst_aon_ni, + // These signals get synchronized to the + // slow AON clock within this module. + // Note that wkup_en_i is assumed to be level encoded. + input wkup_en_i, + input filter_en_i, + input wkup_mode_e wkup_mode_i, + input [WkupCntWidth-1:0] wkup_cnt_th_i, + input pin_value_i, + // Signals to/from cause register. + // They are synched to/from the AON clock internally + input wkup_cause_valid_i, + input wkup_cause_data_i, + output wkup_cause_data_o, + // This signal is running on the AON clock + // and is held high as long as the cause register + // has not been cleared. + output logic aon_wkup_req_o +); + + /////////////////////////// + // Input Synchronization // + /////////////////////////// + + // Synchronize configuration to slow clock + wkup_mode_e aon_wkup_mode_q; + logic aon_filter_en_q; + logic aon_wkup_en_d, aon_wkup_en_q; + logic [WkupCntWidth-1:0] aon_wkup_cnt_th_q; + + prim_flop_2sync #( + .Width(1) + ) i_prim_flop_2sync_config ( + .clk_i ( clk_aon_i ), + .rst_ni ( rst_aon_ni ), + .d ( wkup_en_i ), + .q ( aon_wkup_en_d ) + ); + + always_ff @(posedge clk_aon_i or negedge rst_aon_ni) begin : p_sync + if (!rst_aon_ni) begin + aon_wkup_en_q <= 1'b0; + aon_wkup_mode_q <= Disabled; + aon_filter_en_q <= 1'b0; + aon_wkup_cnt_th_q <= '0; + end else begin + aon_wkup_en_q <= aon_wkup_en_d; + // latch these when going into sleep. note that these + // config signals should be stable at this point, since + // SW has configured them many cycles ago. hence no + // explicit multibit consistency check is performed. + if (aon_wkup_en_d & !aon_wkup_en_q) begin + aon_wkup_mode_q <= wkup_mode_i; + aon_filter_en_q <= filter_en_i; + aon_wkup_cnt_th_q <= wkup_cnt_th_i; + end + end + end + + //////////////////////////// + // Optional Signal Filter // + //////////////////////////// + + // This uses a lower value for filtering than GPIO since + // the always-on clock is slower. This can be disabled, + // in which case the signal is just combinationally bypassed. + logic aon_filter_out, aon_filter_out_d, aon_filter_out_q; + prim_filter #( + .Cycles(Cycles) + ) i_prim_filter ( + .clk_i ( clk_aon_i ), + .rst_ni ( rst_aon_ni ), + .enable_i ( aon_filter_en_q ), + .filter_i ( pin_value_i ), + .filter_o ( aon_filter_out ) + ); + + // Run this through a 2 stage synchronizer to + // prevent metastability. + prim_flop_2sync #( + .Width(1) + ) i_prim_flop_2sync_filter ( + .clk_i ( clk_aon_i ), + .rst_ni ( rst_aon_ni ), + .d ( aon_filter_out ), + .q ( aon_filter_out_d ) + ); + + ////////////////////// + // Pattern Matching // + ////////////////////// + + logic aon_rising, aon_falling; + assign aon_falling = ~aon_filter_out_d & aon_filter_out_q; + assign aon_rising = aon_filter_out_d & ~aon_filter_out_q; + + logic aon_cnt_en, aon_cnt_eq_th; + logic [WkupCntWidth-1:0] aon_cnt_d, aon_cnt_q; + assign aon_cnt_d = (aon_cnt_eq_th) ? '0 : + (aon_cnt_en) ? aon_cnt_q + 1'b1 : '0; + + assign aon_cnt_eq_th = aon_cnt_q == aon_wkup_cnt_th_q; + + logic aon_wkup_pulse; + always_comb begin : p_mode + aon_wkup_pulse = 1'b0; + aon_cnt_en = 1'b0; + if (aon_wkup_en_q) begin + unique case (aon_wkup_mode_q) + Negedge: aon_wkup_pulse = aon_falling; + Posedge: aon_wkup_pulse = aon_rising; + Edge: aon_wkup_pulse = aon_rising | aon_falling; + LowTimed: begin + aon_cnt_en = ~aon_filter_out_d; + aon_wkup_pulse = aon_cnt_eq_th; + end + HighTimed: begin + aon_cnt_en = aon_filter_out_d; + aon_wkup_pulse = aon_cnt_eq_th; + end + default: ; // also covers "Disabled" + endcase + end + end + + always_ff @(posedge clk_aon_i or negedge rst_aon_ni) begin : p_aon_pattern + if (!rst_aon_ni) begin + aon_filter_out_q <= 1'b0; + aon_cnt_q <= '0; + end else begin + aon_filter_out_q <= aon_filter_out_d; + aon_cnt_q <= aon_cnt_d; + end + end + + //////////////////// + // Cause register // + //////////////////// + + // to AON domain + logic aon_wkup_cause_valid, aon_wkup_cause_data; + logic aon_wkup_cause_d, aon_wkup_cause_q; + + prim_flop_2sync #( + .Width(1) + ) i_prim_flop_2sync_cause_in ( + .clk_i ( clk_aon_i ), + .rst_ni ( rst_aon_ni ), + .d ( wkup_cause_data_i ), + .q ( aon_wkup_cause_data ) + ); + + prim_pulse_sync i_prim_pulse_sync_cause ( + .clk_src_i ( clk_i ), + .rst_src_ni ( rst_ni ), + .src_pulse_i ( wkup_cause_valid_i ), + .clk_dst_i ( clk_aon_i ), + .rst_dst_ni ( rst_aon_ni ), + .dst_pulse_o ( aon_wkup_cause_valid ) + ); + + // note that aon_wkup_pulse will not be asserted when not in sleep mode + assign aon_wkup_cause_d = (aon_wkup_cause_valid) ? aon_wkup_cause_q & aon_wkup_cause_data : + aon_wkup_cause_q | aon_wkup_pulse; + + // output to power manager + assign aon_wkup_req_o = aon_wkup_cause_q; + + // output to CSR + prim_flop_2sync #( + .Width(1) + ) i_prim_flop_2sync_cause_out ( + .clk_i, + .rst_ni, + .d ( aon_wkup_cause_q ), + .q ( wkup_cause_data_o ) + ); + + always_ff @(posedge clk_aon_i or negedge rst_aon_ni) begin : p_aon_cause + if (!rst_aon_ni) begin + aon_wkup_cause_q <= 1'b0; + end else begin + aon_wkup_cause_q <= aon_wkup_cause_d; + end + end + +endmodule : pinmux_wkup