[padctrl/fpv] Initial FPV testbench for padctrl
diff --git a/hw/formal/formal.core b/hw/formal/formal.core
index 406bf7f..3208c92 100644
--- a/hw/formal/formal.core
+++ b/hw/formal/formal.core
@@ -23,6 +23,7 @@
       - lowrisc:tlul:socket_1n
       - lowrisc:tlul:socket_m1
       - lowrisc:fpv:pinmux_fpv
+      - lowrisc:fpv:padctrl_fpv
 
 targets:
   sim:
diff --git a/hw/formal/fpv_all b/hw/formal/fpv_all
index 43acd7b..af67f96 100755
--- a/hw/formal/fpv_all
+++ b/hw/formal/fpv_all
@@ -31,6 +31,7 @@
   "prim_alert_rxtx_async_tb"
   "prim_esc_rxtx_tb"
   "pinmux_tb"
+  "padctrl_tb"
 )
 
 #-------------------------------------------------------------------------
diff --git a/hw/ip/padctrl/fpv/padctrl_fpv.core b/hw/ip/padctrl/fpv/padctrl_fpv.core
new file mode 100644
index 0000000..875fc73
--- /dev/null
+++ b/hw/ip/padctrl/fpv/padctrl_fpv.core
@@ -0,0 +1,23 @@
+CAPI=2:
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+name: "lowrisc:fpv:padctrl_fpv:0.1"
+description: "PADCTRL FPV target"
+filesets:
+  files_fpv:
+    depend:
+      - lowrisc:ip:padctrl
+    files:
+      - tb/padctrl_tb.sv
+      - tb/padctrl_bind.sv
+      - vip/padring_assert.sv
+    file_type: systemVerilogSource
+
+targets:
+  default: &default_target
+    toplevel:
+      - tb/padctrl_tb.sv
+    filesets:
+      - files_fpv
+    default_tool: jg
diff --git a/hw/ip/padctrl/fpv/tb/padctrl_bind.sv b/hw/ip/padctrl/fpv/tb/padctrl_bind.sv
new file mode 100644
index 0000000..fe236ac
--- /dev/null
+++ b/hw/ip/padctrl/fpv/tb/padctrl_bind.sv
@@ -0,0 +1,16 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+
+module padctrl_bind;
+
+  bind padring padring_assert padring_assert (
+    // symbolic inputs for FPV
+    .mio_sel_i(padctrl_tb.mio_sel_i),
+    .dio_sel_i(padctrl_tb.dio_sel_i),
+    // normal connections to padring
+    .*
+  );
+
+endmodule : padctrl_bind
diff --git a/hw/ip/padctrl/fpv/tb/padctrl_tb.sv b/hw/ip/padctrl/fpv/tb/padctrl_tb.sv
new file mode 100644
index 0000000..d7658d1
--- /dev/null
+++ b/hw/ip/padctrl/fpv/tb/padctrl_tb.sv
@@ -0,0 +1,60 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Testbench module for padctrl. Intended to use with a formal tool.
+
+module padctrl_tb #(
+  parameter Impl = "generic"
+) (
+  input wire                                    clk_i,
+  input wire                                    rst_ni,
+  output logic                                  clk_o,
+  output logic                                  rst_no,
+  input  tlul_pkg::tl_h2d_t                     tl_i,
+  output tlul_pkg::tl_d2h_t                     tl_o,
+  inout  wire  [padctrl_reg_pkg::NMioPads-1:0]  mio_io,
+  inout  wire  [padctrl_reg_pkg::NDioPads-1:0]  dio_io,
+  input        [padctrl_reg_pkg::NMioPads-1:0]  mio_out_i,
+  input        [padctrl_reg_pkg::NMioPads-1:0]  mio_oe_i,
+  output logic [padctrl_reg_pkg::NMioPads-1:0]  mio_in_o,
+  input        [padctrl_reg_pkg::NDioPads-1:0]  dio_out_i,
+  input        [padctrl_reg_pkg::NDioPads-1:0]  dio_oe_i,
+  output logic [padctrl_reg_pkg::NDioPads-1:0]  dio_in_o,
+  // symbolic inputs for FPV
+  input [$clog2(padctrl_reg_pkg::NMioPads)-1:0] mio_sel_i,
+  input [$clog2(padctrl_reg_pkg::NDioPads)-1:0] dio_sel_i
+);
+
+  logic [padctrl_reg_pkg::NMioPads-1:0][padctrl_reg_pkg::AttrDw-1:0] mio_attr;
+  logic [padctrl_reg_pkg::NDioPads-1:0][padctrl_reg_pkg::AttrDw-1:0] dio_attr;
+
+  padctrl i_padctrl (
+    .clk_i     ,
+    .rst_ni    ,
+    .tl_i      ,
+    .tl_o      ,
+    .mio_attr_o(mio_attr),
+    .dio_attr_o(dio_attr)
+  );
+
+  padring #(
+    .Impl(Impl)
+  ) i_padring (
+    .clk_i     ,
+    .rst_ni    ,
+    .clk_o     ,
+    .rst_no    ,
+    .mio_io    ,
+    .dio_io    ,
+    .mio_out_i ,
+    .mio_oe_i  ,
+    .mio_in_o  ,
+    .dio_out_i ,
+    .dio_oe_i  ,
+    .dio_in_o  ,
+    .mio_attr_i(mio_attr),
+    .dio_attr_i(dio_attr)
+  );
+
+endmodule : padctrl_tb
diff --git a/hw/ip/padctrl/fpv/vip/padring_assert.sv b/hw/ip/padctrl/fpv/vip/padring_assert.sv
new file mode 100644
index 0000000..1ebc172
--- /dev/null
+++ b/hw/ip/padctrl/fpv/vip/padring_assert.sv
@@ -0,0 +1,103 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Assertions for padring. Intended to use with a formal tool.
+// Note that only the mandatory pad attributes are tested here.
+
+module padring_assert (
+  input                                 clk_i,
+  input                                 rst_ni,
+  input                                 clk_o,
+  input                                 rst_no,
+  input [padctrl_reg_pkg::NMioPads-1:0] mio_io,
+  input [padctrl_reg_pkg::NDioPads-1:0] dio_io,
+  input [padctrl_reg_pkg::NMioPads-1:0] mio_out_i,
+  input [padctrl_reg_pkg::NMioPads-1:0] mio_oe_i,
+  input [padctrl_reg_pkg::NMioPads-1:0] mio_in_o,
+  input [padctrl_reg_pkg::NDioPads-1:0] dio_out_i,
+  input [padctrl_reg_pkg::NDioPads-1:0] dio_oe_i,
+  input [padctrl_reg_pkg::NDioPads-1:0] dio_in_o,
+  input [padctrl_reg_pkg::NMioPads-1:0]
+        [padctrl_reg_pkg::AttrDw-1:0]   mio_attr_i,
+  input [padctrl_reg_pkg::NDioPads-1:0]
+        [padctrl_reg_pkg::AttrDw-1:0]   dio_attr_i,
+  // symbolic inputs for FPV
+  input [$clog2(padctrl_reg_pkg::NMioPads)-1:0] mio_sel_i,
+  input [$clog2(padctrl_reg_pkg::NDioPads)-1:0] dio_sel_i
+);
+
+  //////////////////////////////////////////////////////
+  // Check main connectivity of infrastructure signals
+  //////////////////////////////////////////////////////
+
+  `ASSERT(Clk_A, clk_i == clk_o, clk_i, !rst_ni)
+  `ASSERT(Rst_A, rst_ni == rst_no, clk_i, !rst_ni)
+
+  //////////////////////////////////////////////////////
+  // Check muxed IO pads
+  //////////////////////////////////////////////////////
+
+  `ASSUME(NMioRange_M, mio_sel_i < padctrl_reg_pkg::NMioPads, clk_i, !rst_ni)
+
+  // attribute 0 is the input/output inversion bit
+  logic mio_output_value;
+  assign mio_output_value = mio_out_i[mio_sel_i] ^ mio_attr_i[mio_sel_i][0];
+
+  `ASSERT(MioIn_A,  mio_in_o[mio_sel_i] == mio_io[mio_sel_i] ^ mio_attr_i[mio_sel_i][0],
+      clk_i, !rst_ni)
+
+  `ASSERT(MioOutNormal_A, mio_oe_i[mio_sel_i] && !mio_attr_i[mio_sel_i][1] |->
+      mio_output_value == mio_io[mio_sel_i], clk_i, !rst_ni)
+
+  `ASSERT(MioOutOd0_A, mio_oe_i[mio_sel_i] && mio_attr_i[mio_sel_i][1] && !mio_output_value |->
+      mio_io[mio_sel_i] == 1'b0, clk_i, !rst_ni)
+
+  // TODO: find a better way to test high-Z?
+  `ASSERT(MioOutOd1_A, mio_oe_i[mio_sel_i] && mio_attr_i[mio_sel_i][1] && mio_output_value |->
+      mio_io[mio_sel_i] === 1'b0 ||
+      mio_io[mio_sel_i] === 1'b1 ||
+      mio_io[mio_sel_i] === 1'bz ||
+      mio_io[mio_sel_i] === 1'bx, clk_i, !rst_ni)
+
+  // TODO: find a better way to test high-Z?
+  `ASSERT(MioOe_A, !mio_oe_i[mio_sel_i] |->
+      mio_io[mio_sel_i] === 1'b0 ||
+      mio_io[mio_sel_i] === 1'b1 ||
+      mio_io[mio_sel_i] === 1'bz ||
+      mio_io[mio_sel_i] === 1'bx, clk_i, !rst_ni)
+
+  //////////////////////////////////////////////////////
+  // Check dedicated IO pads
+  //////////////////////////////////////////////////////
+
+  `ASSUME(NDioRange_M, dio_sel_i < padctrl_reg_pkg::NDioPads, clk_i, !rst_ni)
+
+  // attribute 0 is the input/output inversion bit
+  logic dio_output_value;
+  assign dio_output_value = dio_out_i[dio_sel_i] ^ dio_attr_i[dio_sel_i][0];
+
+  `ASSERT(DioIn_A,  dio_in_o[dio_sel_i] == dio_io[dio_sel_i] ^ dio_attr_i[dio_sel_i][0],
+      clk_i, !rst_ni)
+
+  `ASSERT(DioOutNormal_A, dio_oe_i[dio_sel_i] && !dio_attr_i[dio_sel_i][1] |->
+      dio_output_value == dio_io[dio_sel_i], clk_i, !rst_ni)
+
+  `ASSERT(DioOutOd0_A, dio_oe_i[dio_sel_i] && dio_attr_i[dio_sel_i][1] && !dio_output_value |->
+      dio_io[dio_sel_i] == 1'b0, clk_i, !rst_ni)
+
+  // TODO: find a better way to test high-Z?
+  `ASSERT(DioOutOd1_A, dio_oe_i[dio_sel_i] && dio_attr_i[dio_sel_i][1] && dio_output_value |->
+      dio_io[dio_sel_i] === 1'b0 ||
+      dio_io[dio_sel_i] === 1'b1 ||
+      dio_io[dio_sel_i] === 1'bz ||
+      dio_io[dio_sel_i] === 1'bx, clk_i, !rst_ni)
+
+  // TODO: find a better way to test high-Z?
+  `ASSERT(DioOe_A, !dio_oe_i[dio_sel_i] |->
+      dio_io[dio_sel_i] === 1'b0 ||
+      dio_io[dio_sel_i] === 1'b1 ||
+      dio_io[dio_sel_i] === 1'bz ||
+      dio_io[dio_sel_i] === 1'bx, clk_i, !rst_ni)
+
+endmodule : padring_assert
diff --git a/hw/ip/padctrl/rtl/padring.sv b/hw/ip/padctrl/rtl/padring.sv
index 212f2c2..77290ce 100644
--- a/hw/ip/padctrl/rtl/padring.sv
+++ b/hw/ip/padctrl/rtl/padring.sv
@@ -10,8 +10,8 @@
   parameter Impl = "generic" // this determines the pad implementation
 ) (
   // pad input
-  inout wire                                   clk_i,
-  inout wire                                   rst_ni,
+  input wire                                   clk_i,
+  input wire                                   rst_ni,
   // to clocking/reset infrastructure
   output logic                                 clk_o,
   output logic                                 rst_no,