[pinmux] Move JTAG muxing into pinmux strap sample logic

Signed-off-by: Michael Schaffner <msf@opentitan.org>
diff --git a/hw/ip/pinmux/data/pinmux.hjson b/hw/ip/pinmux/data/pinmux.hjson
index d448948..35d9087 100644
--- a/hw/ip/pinmux/data/pinmux.hjson
+++ b/hw/ip/pinmux/data/pinmux.hjson
@@ -212,6 +212,17 @@
       default: "0",
       local: "true"
     },
+    // Since the target-specific top-levels often have slightly
+    // different debug signal positions, we need a way to pass
+    // this info from the target specific top-level into the pinmux
+    // logic. The parameter struct below serves this purpose.
+   { name: "TargetCfg",
+      desc:    "Target specific pinmux configuration.",
+      type:    "pinmux_pkg::target_cfg_t",
+      default: "pinmux_pkg::DefaultTargetCfg",
+      local:   "false",
+      expose:  "true"
+    },
   ],
   registers: [
 //////////////////////////
diff --git a/hw/ip/pinmux/data/pinmux.hjson.tpl b/hw/ip/pinmux/data/pinmux.hjson.tpl
index fbf3ee6..08a210c 100644
--- a/hw/ip/pinmux/data/pinmux.hjson.tpl
+++ b/hw/ip/pinmux/data/pinmux.hjson.tpl
@@ -224,6 +224,17 @@
       default: "${usb_dn_pull_sel}",
       local: "true"
     },
+    // Since the target-specific top-levels often have slightly
+    // different debug signal positions, we need a way to pass
+    // this info from the target specific top-level into the pinmux
+    // logic. The parameter struct below serves this purpose.
+   { name: "TargetCfg",
+      desc:    "Target specific pinmux configuration.",
+      type:    "pinmux_pkg::target_cfg_t",
+      default: "pinmux_pkg::DefaultTargetCfg",
+      local:   "false",
+      expose:  "true"
+    },
   ],
   registers: [
 //////////////////////////
diff --git a/hw/ip/pinmux/rtl/pinmux.sv b/hw/ip/pinmux/rtl/pinmux.sv
index c3d5d04..78aa55f 100644
--- a/hw/ip/pinmux/rtl/pinmux.sv
+++ b/hw/ip/pinmux/rtl/pinmux.sv
@@ -10,7 +10,11 @@
 module pinmux
   import pinmux_pkg::*;
   import pinmux_reg_pkg::*;
-(
+#(
+  // Taget-specific pinmux configuration passed down from the
+  // target-specific top-level.
+  parameter target_cfg_t TargetCfg = DefaultTargetCfg
+) (
   input                            clk_i,
   input                            rst_ni,
   // Slow always-on clock
@@ -131,6 +135,44 @@
   end
 
 
+  //////////////////////////
+  // Strap Sampling Logic //
+  //////////////////////////
+
+  // Local versions of the input signals
+  logic [NMioPads-1:0] mio_out, mio_oe, mio_in;
+  logic [NDioPads-1:0] dio_out, dio_oe, dio_in;
+
+  // This module contains the strap sampling and JTAG mux.
+  // Affected inputs are intercepted/tapped before they go to the pinmux
+  // matrix. Likewise, affected outputs are intercepted/tapped after the
+  // retention registers.
+  pinmux_strap_sampling #(
+    .TargetCfg (TargetCfg)
+  ) u_pinmux_strap_sampling (
+    .clk_i,
+    .rst_ni,
+    // To padring side
+    .out_padring_o ( {dio_out_o, mio_out_o} ),
+    .oe_padring_o  ( {dio_oe_o , mio_oe_o } ),
+    .in_padring_i  ( {dio_in_i , mio_in_i } ),
+    // To core side
+    .out_core_i    ( {dio_out, mio_out} ),
+    .oe_core_i     ( {dio_oe,  mio_oe}  ),
+    .in_core_o     ( {dio_in,  mio_in}  ),
+    // Strap and JTAG signals
+    .strap_en_i,
+    .lc_dft_en_i,
+    .lc_hw_debug_en_i,
+    .dft_strap_test_o,
+    .lc_jtag_o,
+    .lc_jtag_i,
+    .rv_jtag_o,
+    .rv_jtag_i,
+    .dft_jtag_o,
+    .dft_jtag_i
+  );
+
   ///////////////////////////////////////
   // USB wake detect module connection //
   ///////////////////////////////////////
@@ -209,14 +251,14 @@
   localparam int AlignedMuxSize = (NMioPads + 2 > NDioPads) ? 2**$clog2(NMioPads + 2) :
                                                               2**$clog2(NDioPads);
 
-  // stack input and default signals for convenient indexing below possible defaults: constant 0 or
-  // 1. make sure mux is aligned to a power of 2 to avoid Xes.
-  logic [AlignedMuxSize-1:0] mio_data_mux;
-  assign mio_data_mux = AlignedMuxSize'({mio_in_i, 1'b1, 1'b0});
+  // stack input and default signals for convenient indexing below possible defaults:
+  // constant 0 or 1. make sure mux is aligned to a power of 2 to avoid Xes.
+  logic [AlignedMuxSize-1:0] mio_mux;
+  assign mio_mux = AlignedMuxSize'({mio_in, 1'b1, 1'b0});
 
   for (genvar k = 0; k < NMioPeriphIn; k++) begin : gen_mio_periph_in
     // index using configured insel
-    assign mio_to_periph_o[k] = mio_data_mux[reg2hw.mio_periph_insel[k].q];
+    assign mio_to_periph_o[k] = mio_mux[reg2hw.mio_periph_insel[k].q];
   end
 
   //////////////////////
@@ -231,13 +273,13 @@
 
   for (genvar k = 0; k < NMioPads; k++) begin : gen_mio_out
     // Check individual sleep enable status bits
-    assign mio_out_o[k] = reg2hw.mio_pad_sleep_status[k].q ?
-                          mio_out_retreg_q[k]              :
-                          periph_data_mux[reg2hw.mio_outsel[k].q];
+    assign mio_out[k] = reg2hw.mio_pad_sleep_status[k].q ?
+                        mio_out_retreg_q[k]              :
+                        periph_data_mux[reg2hw.mio_outsel[k].q];
 
-    assign mio_oe_o[k]  = reg2hw.mio_pad_sleep_status[k].q ?
-                          mio_oe_retreg_q[k]               :
-                          periph_oe_mux[reg2hw.mio_outsel[k].q];
+    assign mio_oe[k]  = reg2hw.mio_pad_sleep_status[k].q ?
+                        mio_oe_retreg_q[k]               :
+                        periph_oe_mux[reg2hw.mio_outsel[k].q];
 
     // latch state when going to sleep
     // 0: drive low
@@ -246,11 +288,11 @@
     // 3: previous value
     assign mio_out_retreg_d[k] = (reg2hw.mio_pad_sleep_mode[k].q == 0) ? 1'b0 :
                                  (reg2hw.mio_pad_sleep_mode[k].q == 1) ? 1'b1 :
-                                 (reg2hw.mio_pad_sleep_mode[k].q == 2) ? 1'b0 : mio_out_o[k];
+                                 (reg2hw.mio_pad_sleep_mode[k].q == 2) ? 1'b0 : mio_out[k];
 
     assign mio_oe_retreg_d[k] = (reg2hw.mio_pad_sleep_mode[k].q == 0) ? 1'b1 :
                                 (reg2hw.mio_pad_sleep_mode[k].q == 1) ? 1'b1 :
-                                (reg2hw.mio_pad_sleep_mode[k].q == 2) ? 1'b0 : mio_oe_o[k];
+                                (reg2hw.mio_pad_sleep_mode[k].q == 2) ? 1'b0 : mio_oe[k];
 
     // Activate sleep behavior only if it has been enabled
     assign mio_sleep_trig[k] = reg2hw.mio_pad_sleep_en[k].q & sleep_trig;
@@ -263,17 +305,17 @@
   /////////////////////
 
   // Inputs are just fed through
-  assign dio_to_periph_o = dio_in_i;
+  assign dio_to_periph_o = dio_in;
 
   for (genvar k = 0; k < NDioPads; k++) begin : gen_dio_out
     // Check individual sleep enable status bits
-    assign dio_out_o[k] = reg2hw.dio_pad_sleep_status[k].q ?
-                          dio_out_retreg_q[k]              :
-                          periph_to_dio_i[k];
+    assign dio_out[k] = reg2hw.dio_pad_sleep_status[k].q ?
+                        dio_out_retreg_q[k]              :
+                        periph_to_dio_i[k];
 
-    assign dio_oe_o[k]  = reg2hw.dio_pad_sleep_status[k].q ?
-                          dio_oe_retreg_q[k]               :
-                          periph_to_dio_oe_i[k];
+    assign dio_oe[k]  = reg2hw.dio_pad_sleep_status[k].q ?
+                        dio_oe_retreg_q[k]               :
+                        periph_to_dio_oe_i[k];
 
     // latch state when going to sleep
     // 0: drive low
@@ -282,11 +324,11 @@
     // 3: previous value
     assign dio_out_retreg_d[k] = (reg2hw.dio_pad_sleep_mode[k].q == 0) ? 1'b0 :
                                  (reg2hw.dio_pad_sleep_mode[k].q == 1) ? 1'b1 :
-                                 (reg2hw.dio_pad_sleep_mode[k].q == 2) ? 1'b0 : dio_out_o[k];
+                                 (reg2hw.dio_pad_sleep_mode[k].q == 2) ? 1'b0 : dio_out[k];
 
     assign dio_oe_retreg_d[k] = (reg2hw.dio_pad_sleep_mode[k].q == 0) ? 1'b1 :
                                 (reg2hw.dio_pad_sleep_mode[k].q == 1) ? 1'b1 :
-                                (reg2hw.dio_pad_sleep_mode[k].q == 2) ? 1'b0 : dio_oe_o[k];
+                                (reg2hw.dio_pad_sleep_mode[k].q == 2) ? 1'b0 : dio_oe[k];
 
     // Activate sleep behavior only if it has been enabled
     assign dio_sleep_trig[k] = reg2hw.dio_pad_sleep_en[k].q & sleep_trig;
@@ -298,15 +340,20 @@
   // Wakeup detectors //
   //////////////////////
 
-  logic [AlignedMuxSize-1:0] dio_data_mux;
-  assign dio_data_mux = AlignedMuxSize'(dio_in_i);
+  // Wakeup detector taps are not affected by JTAG/strap
+  // selection mux. I.e., we always sample the unmuxed inputs
+  // that come directly from the pads.
+  logic [AlignedMuxSize-1:0] dio_wkup_mux;
+  logic [AlignedMuxSize-1:0] mio_wkup_mux;
+  assign dio_wkup_mux = AlignedMuxSize'(dio_in_i);
+  assign mio_wkup_mux = AlignedMuxSize'(mio_in_i);
 
   logic [NWkupDetect-1:0] aon_wkup_req;
   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]];
+                       dio_wkup_mux[reg2hw.wkup_detector_padsel[k]] :
+                       mio_wkup_mux[reg2hw.wkup_detector_padsel[k]];
 
     pinmux_wkup u_pinmux_wkup (
       .clk_i,
@@ -331,26 +378,6 @@
   // OR' together all wakeup requests
   assign aon_wkup_req_o = |aon_wkup_req;
 
-  //////////////////////////
-  // Strap Sampling Logic //
-  //////////////////////////
-
-  pinmux_strap_sampling u_pinmux_strap_sampling (
-    .clk_i,
-    .rst_ni,
-    .mio_in_i,
-    .strap_en_i,
-    .lc_dft_en_i,
-    .lc_hw_debug_en_i,
-    .dft_strap_test_o,
-    .lc_jtag_o,
-    .lc_jtag_i,
-    .rv_jtag_o,
-    .rv_jtag_i,
-    .dft_jtag_o,
-    .dft_jtag_i
-  );
-
   ////////////////
   // Assertions //
   ////////////////
@@ -374,9 +401,18 @@
   `ASSERT_KNOWN(MioKnownO_A, mio_attr_o)
   `ASSERT_KNOWN(DioKnownO_A, dio_attr_o)
 
-  `ASSERT_KNOWN(LcJtagKnown_A, lc_jtag_o)
-  `ASSERT_KNOWN(RvJtagKnown_A, rv_jtag_o)
-  `ASSERT_KNOWN(DftJtagKnown_A, dft_jtag_o)
+  `ASSERT_KNOWN(LcJtagTckKnown_A, lc_jtag_o.tck)
+  `ASSERT_KNOWN(LcJtagTrstKnown_A, lc_jtag_o.trst_n)
+  `ASSERT_KNOWN(LcJtagTmsKnown_A, lc_jtag_o.tms)
+
+  `ASSERT_KNOWN(RvJtagTckKnown_A, rv_jtag_o.tck)
+  `ASSERT_KNOWN(RvJtagTrstKnown_A, rv_jtag_o.trst_n)
+  `ASSERT_KNOWN(RvJtagTmsKnown_A, rv_jtag_o.tms)
+
+  `ASSERT_KNOWN(DftJtagTckKnown_A, dft_jtag_o.tck)
+  `ASSERT_KNOWN(DftJtagTrstKnown_A, dft_jtag_o.trst_n)
+  `ASSERT_KNOWN(DftJtagTmsKnown_A, dft_jtag_o.tms)
+
   `ASSERT_KNOWN(DftStrapsKnown_A, dft_strap_test_o)
 
   // running on slow AON clock
diff --git a/hw/ip/pinmux/rtl/pinmux_jtag_buf.sv b/hw/ip/pinmux/rtl/pinmux_jtag_buf.sv
index 516e572..d3b6dfa 100644
--- a/hw/ip/pinmux/rtl/pinmux_jtag_buf.sv
+++ b/hw/ip/pinmux/rtl/pinmux_jtag_buf.sv
@@ -13,9 +13,9 @@
     .clk_i(req_i.tck),
     .clk_o(req_o.tck)
   );
-  prim_clock_buf prim_clock_buf_trst_n (
-    .clk_i(req_i.trst_n),
-    .clk_o(req_o.trst_n)
+  prim_buf prim_buf_trst_n (
+    .in_i(req_i.trst_n),
+    .out_o(req_o.trst_n)
   );
   prim_buf prim_buf_tms (
     .in_i(req_i.tms),
diff --git a/hw/ip/pinmux/rtl/pinmux_pkg.sv b/hw/ip/pinmux/rtl/pinmux_pkg.sv
index b36d66a..2c16eac 100644
--- a/hw/ip/pinmux/rtl/pinmux_pkg.sv
+++ b/hw/ip/pinmux/rtl/pinmux_pkg.sv
@@ -4,6 +4,44 @@
 
 package pinmux_pkg;
 
+  import pinmux_reg_pkg::*;
+
+  parameter int NumIOs     = NMioPads + NDioPads;
+  parameter int NDFTStraps = 2;
+  parameter int NTapStraps = 2;
+
+  // Since the target-specific top-levels often have slightly different debug signal positions, we
+  // need a way to pass this info from the target specific top-level into the pinmux logic. The
+  // datastructure below serves this purpose. Note that all the indices below are with respect to
+  // the concatenated {DIO, MIO} packed array.
+  typedef struct packed {
+    bit                const_sampling; // TODO: check whether this can be eliminated.
+    logic [NumIOs-1:0] tie_offs;       // TODO: check whether this can be eliminated.
+    int                tck_idx;
+    int                tms_idx;
+    int                trst_idx;
+    int                tdi_idx;
+    int                tdo_idx;
+    int                tap_strap0_idx;
+    int                tap_strap1_idx;
+    int                dft_strap0_idx;
+    int                dft_strap1_idx;
+  } target_cfg_t;
+
+  parameter target_cfg_t DefaultTargetCfg = '{
+    const_sampling: 1'b0,
+    tie_offs:       '0,
+    tck_idx:        0,
+    tms_idx:        0,
+    trst_idx:       0,
+    tdi_idx:        0,
+    tdo_idx:        0,
+    tap_strap0_idx: 0,
+    tap_strap1_idx: 0,
+    dft_strap0_idx: 0,
+    dft_strap1_idx: 0
+  };
+
   // Wakeup Detector Modes
   typedef enum logic [2:0] {
     Posedge   = 3'b000,
@@ -13,20 +51,17 @@
     LowTimed  = 3'b100
   } wkup_mode_e;
 
-  // DFT Test Mode straps
-  parameter int NDFTStraps  = 2;
-  // Strap sampling is only supported on MIOs at the moment
-  parameter int DftStrapPos [NDFTStraps] = '{3, 2};
-
   // Interface with LC controller
   typedef struct packed {
     logic                  valid;
     logic [NDFTStraps-1:0] straps;
   } dft_strap_test_req_t;
 
-  // Life cycle DFT straps for TAP select
-  parameter int NTapStraps  = 2;
-  // Strap sampling is only supported on MIOs at the moment
-  parameter int TapStrapPos [NTapStraps] = '{1, 0};
+  typedef enum logic [NTapStraps-1:0] {
+    FuncSel   = 2'b00,
+    LcTapSel  = 2'b01,
+    RvTapSel  = 2'b10,
+    DftTapSel = 2'b11
+  } tap_strap_t;
 
 endpackage : pinmux_pkg
diff --git a/hw/ip/pinmux/rtl/pinmux_strap_sampling.sv b/hw/ip/pinmux/rtl/pinmux_strap_sampling.sv
index e9e1664..2fcc9b3 100644
--- a/hw/ip/pinmux/rtl/pinmux_strap_sampling.sv
+++ b/hw/ip/pinmux/rtl/pinmux_strap_sampling.sv
@@ -5,12 +5,21 @@
 module pinmux_strap_sampling
   import pinmux_pkg::*;
   import pinmux_reg_pkg::*;
-(
+#(
+  // Taget-specific pinmux configuration passed down from the
+  // target-specific top-level.
+  parameter target_cfg_t TargetCfg = DefaultTargetCfg
+) (
   input                            clk_i,
   input                            rst_ni,
-  // MIO inputs.
-  // TODO(#5221): need tapped IOs for JTAG mux.
-  input  logic [NMioPads-1:0]      mio_in_i,
+  // To padring side
+  output logic [NumIOs-1:0]        out_padring_o,
+  output logic [NumIOs-1:0]        oe_padring_o,
+  input  logic [NumIOs-1:0]        in_padring_i,
+  // To core side
+  input  logic [NumIOs-1:0]        out_core_i,
+  input  logic [NumIOs-1:0]        oe_core_i,
+  output logic [NumIOs-1:0]        in_core_o,
   // Used for TAP qualification
   input  logic                     strap_en_i,
   input  lc_ctrl_pkg::lc_tx_t      lc_dft_en_i,
@@ -53,42 +62,29 @@
   // Strap Sampling Logic //
   //////////////////////////
 
-  typedef enum logic [NTapStraps-1:0] {
-    FuncSel   = 2'b00,
-    LcTapSel  = 2'b01,
-    RvTapSel  = 2'b10,
-    DftTapSel = 2'b11
-  } tap_strap_t;
-
   logic strap_en_q;
   logic dft_strap_valid_d, dft_strap_valid_q;
   logic lc_strap_sample_en, rv_strap_sample_en, dft_strap_sample_en;
-  logic [1:0] tap_strap_sample_en;
   logic [NTapStraps-1:0] tap_strap_d, tap_strap_q;
   logic [NDFTStraps-1:0] dft_strap_d, dft_strap_q;
   lc_ctrl_pkg::lc_tx_e continue_sampling_d, continue_sampling_q;
 
-  // Not all MIOs are used.
-  logic unused_mio_in;
-  assign unused_mio_in = ^mio_in_i;
-
   // The LC strap at index 0 has a slightly different
   // enable condition than the DFT strap at index 1.
-  for (genvar k = 0; k < NTapStraps; k++) begin : gen_lc_strap_taps
-    assign tap_strap_d[k] = (tap_strap_sample_en[k]) ? mio_in_i[TapStrapPos[k]] : tap_strap_q[k];
-  end
+  assign tap_strap_d[0] = (lc_strap_sample_en) ? in_padring_i[TargetCfg.tap_strap0_idx] :
+                                                 tap_strap_q[0];
+  assign tap_strap_d[1] = (rv_strap_sample_en) ? in_padring_i[TargetCfg.tap_strap1_idx] :
+                                                 tap_strap_q[1];
 
   // We're always using the DFT strap sample enable for the DFT straps.
-  for (genvar k = 0; k < NDFTStraps; k++) begin : gen_dft_strap_taps
-    assign dft_strap_d[k] = (dft_strap_sample_en) ? mio_in_i[DftStrapPos[k]] : dft_strap_q[k];
-  end
+  assign dft_strap_d = (dft_strap_sample_en) ? {in_padring_i[TargetCfg.dft_strap1_idx],
+                                                in_padring_i[TargetCfg.dft_strap0_idx]} :
+                                               dft_strap_q;
 
   assign dft_strap_valid_d = dft_strap_sample_en | dft_strap_valid_q;
   assign dft_strap_test_o.valid  = dft_strap_valid_q;
   assign dft_strap_test_o.straps = dft_strap_q;
 
-  assign tap_strap_sample_en = {rv_strap_sample_en,
-                                lc_strap_sample_en};
 
   always_comb begin : p_strap_sampling
     lc_strap_sample_en = 1'b0;
@@ -119,6 +115,11 @@
         continue_sampling_d = lc_ctrl_pkg::On;
       end
     end
+    // TODO: this can currently be overridden with a parameter for legacy reasons.
+    // This parameter will be removed in the future.
+    if (TargetCfg.const_sampling) begin
+      continue_sampling_d = lc_ctrl_pkg::On;
+    end
   end
 
   always_ff @(posedge clk_i or negedge rst_ni) begin : p_strap_sample
@@ -137,50 +138,49 @@
     end
   end
 
-  ////////////////////
-  // TAP Selection  //
-  ////////////////////
+  ///////////////////////
+  // TAP Selection Mux //
+  ///////////////////////
 
+  logic jtag_en;
+  tap_strap_t tap_strap;
   jtag_pkg::jtag_req_t jtag_req, lc_jtag_req, rv_jtag_req, dft_jtag_req;
   jtag_pkg::jtag_rsp_t jtag_rsp, lc_jtag_rsp, rv_jtag_rsp, dft_jtag_rsp;
 
-  // TODO(#5221): need to mux this with the correct MIOs.
-  // But first, the jtag_mux from the chip level hierarchy needs
-  // to be pulled into pinmux, and the FPGA emulation and the simulation
-  // environments need to be adapted such that this does not break our
-  // regressions.
-  logic unused_jtag_rsp;
-  assign jtag_req = '0;
-  assign unused_jtag_rsp = ^jtag_rsp;
-
   // This muxes the JTAG signals to the correct TAP, based on the
   // sampled straps. Further, the individual JTAG signals are gated
   // using the corresponding life cycle signal.
-  tap_strap_t tap_strap;
   assign tap_strap = tap_strap_t'(tap_strap_q);
   `ASSERT_KNOWN(TapStrapKnown_A, tap_strap)
 
   always_comb begin : p_tap_mux
     jtag_rsp     = '0;
+    // Note that this holds the JTAGs in reset
+    // when they are not selected.
     lc_jtag_req  = '0;
     rv_jtag_req  = '0;
     dft_jtag_req = '0;
+    // This activates the TDO override further below.
+    jtag_en      = 1'b0;
 
     unique case (tap_strap)
       LcTapSel: begin
         lc_jtag_req = jtag_req;
-        jtag_rsp = lc_jtag_rsp;
+        jtag_rsp    = lc_jtag_rsp;
+        jtag_en     = 1'b1;
       end
       RvTapSel: begin
         if (lc_hw_debug_en[1] == lc_ctrl_pkg::On) begin
           rv_jtag_req = jtag_req;
-          jtag_rsp = rv_jtag_rsp;
+          jtag_rsp    = rv_jtag_rsp;
+          jtag_en     = 1'b1;
         end
       end
       DftTapSel: begin
         if (lc_dft_en[1] == lc_ctrl_pkg::On) begin
           dft_jtag_req = jtag_req;
-          jtag_rsp = dft_jtag_rsp;
+          jtag_rsp     = dft_jtag_rsp;
+          jtag_en      = 1'b1;
         end
       end
       default: ;
@@ -208,15 +208,64 @@
     .rsp_o(dft_jtag_rsp)
   );
 
+  //////////////////////
+  // TAP Input Muxes  //
+  //////////////////////
+
+  // Inputs connections
+  assign jtag_req.tck    = in_padring_i[TargetCfg.tck_idx];
+  assign jtag_req.tms    = in_padring_i[TargetCfg.tms_idx];
+  assign jtag_req.trst_n = in_padring_i[TargetCfg.trst_idx];
+  assign jtag_req.tdi    = in_padring_i[TargetCfg.tdi_idx];
+
+  // Input tie-off muxes
+  for (genvar k = 0; k < NumIOs; k++) begin : gen_input_tie_off
+    if (k == TargetCfg.tck_idx  ||
+        k == TargetCfg.tms_idx  ||
+        k == TargetCfg.trst_idx ||
+        k == TargetCfg.tdi_idx  ||
+        k == TargetCfg.tdo_idx) begin : gen_jtag_signal
+      assign in_core_o[k] = (jtag_en) ? TargetCfg.tie_offs[k] : in_padring_i[k];
+    end else begin : gen_other_inputs
+      assign in_core_o[k] = in_padring_i[k];
+    end
+  end
+
+  // Override TDO output
+  for (genvar k = 0; k < NumIOs; k++) begin : gen_output_mux
+    if (k == TargetCfg.tdo_idx) begin : gen_tdo
+      assign out_padring_o[k] = (jtag_en) ? jtag_rsp.tdo    : out_core_i[k];
+      assign oe_padring_o[k]  = (jtag_en) ? jtag_rsp.tdo_oe : oe_core_i[k];
+    end else begin : gen_other_outputs
+      assign out_padring_o[k] = out_core_i[k];
+      assign oe_padring_o[k]  = oe_core_i[k];
+    end
+  end
+
   ////////////////
   // Assertions //
   ////////////////
 
+  `ASSERT_INIT(tck_idxRange_A,  TargetCfg.tck_idx  >= 0 && TargetCfg.tck_idx  < NumIOs)
+  `ASSERT_INIT(tms_idxRange_A,  TargetCfg.tms_idx  >= 0 && TargetCfg.tms_idx  < NumIOs)
+  `ASSERT_INIT(trst_idxRange_A, TargetCfg.trst_idx >= 0 && TargetCfg.trst_idx < NumIOs)
+  `ASSERT_INIT(tdi_idxRange_A,  TargetCfg.tdi_idx  >= 0 && TargetCfg.tdi_idx  < NumIOs)
+  `ASSERT_INIT(tdo_idxRange_A,  TargetCfg.tdo_idx  >= 0 && TargetCfg.tdo_idx  < NumIOs)
+
+  `ASSERT_INIT(tap_strap0_idxRange_A, TargetCfg.tap_strap0_idx >= 0 &&
+                                      TargetCfg.tap_strap0_idx < NumIOs)
+  `ASSERT_INIT(tap_strap1_idxRange_A, TargetCfg.tap_strap1_idx >= 0 &&
+                                      TargetCfg.tap_strap1_idx < NumIOs)
+  `ASSERT_INIT(dft_strap0_idxRange_A, TargetCfg.dft_strap0_idx >= 0 &&
+                                      TargetCfg.dft_strap0_idx < NumIOs)
+  `ASSERT_INIT(dft_strap1_idxRange_A, TargetCfg.dft_strap1_idx >= 0 &&
+                                      TargetCfg.dft_strap1_idx < NumIOs)
+
   // The strap sampling enable input shall be pulsed high exactly once after cold boot.
   `ASSERT(PwrMgrStrapSampleOnce_A, strap_en_i |=> ##0 !strap_en_i [*])
 
-  `ASSERT(RvTapOff0_A, lc_hw_debug_en_i == lc_ctrl_pkg::Off |-> ##2 rv_jtag_o == '0)
-  `ASSERT(RvTapOff1_A, lc_hw_debug_en_i == lc_ctrl_pkg::Off |-> ##2 rv_jtag_i == '0)
+  `ASSERT(RvTapOff0_A,  lc_hw_debug_en_i == lc_ctrl_pkg::Off |-> ##2 rv_jtag_o == '0)
+  `ASSERT(RvTapOff1_A,  lc_hw_debug_en_i == lc_ctrl_pkg::Off |-> ##2 rv_jtag_i == '0)
   `ASSERT(DftTapOff0_A, lc_dft_en_i == lc_ctrl_pkg::Off |-> ##2 dft_jtag_o == '0)
   `ASSERT(DftTapOff1_A, lc_dft_en_i == lc_ctrl_pkg::Off |-> ##2 dft_jtag_i == '0)