[topgen] Add scanmode to ip port

If IP uses its own sw controlled reset/clock, it needs scanmode input to
improve DFT coverage. Now topgen reads `scan` field from ip hjson and
add `scanmode_i` port accordingly.

See the example of spi_device
diff --git a/hw/ip/spi_device/dv/tb/tb.sv b/hw/ip/spi_device/dv/tb/tb.sv
index 74601dd..a3ed23f 100644
--- a/hw/ip/spi_device/dv/tb/tb.sv
+++ b/hw/ip/spi_device/dv/tb/tb.sv
@@ -53,7 +53,8 @@
     .intr_rxf_o     (intr_rxf  ),
     .intr_rxlvl_o   (intr_rxlvl),
     .intr_txlvl_o   (intr_txlvl),
-    .intr_rxerr_o   (intr_rxerr)
+    .intr_rxerr_o   (intr_rxerr),
+    .scanmode_i     (1'b0      )
   );
 
   assign sck          = spi_if.sck;
diff --git a/hw/ip/spi_device/rtl/spi_device.sv b/hw/ip/spi_device/rtl/spi_device.sv
index 78fdd5f..8b7a64e 100644
--- a/hw/ip/spi_device/rtl/spi_device.sv
+++ b/hw/ip/spi_device/rtl/spi_device.sv
@@ -30,12 +30,10 @@
   output logic intr_txlvl_o,       // TX FIFO below level
   output logic intr_rxerr_o,       // RX Frame error
   output logic intr_rxoverflow_o,  // RX Async FIFO Overflow
-  output logic intr_txunderflow_o  // TX Async FIFO Underflow
-);
+  output logic intr_txunderflow_o, // TX Async FIFO Underflow
 
-  // TODO: make a way to connect scanmode from the top in topgen.py
-  logic scanmode_i;
-  assign scanmode_i = 1'b0;
+  input scanmode_i
+);
 
   import spi_device_pkg::*;
   import spi_device_reg_pkg::*;
diff --git a/hw/top_earlgrey/doc/top_earlgrey.gen.hjson b/hw/top_earlgrey/doc/top_earlgrey.gen.hjson
index 6a1a682..c3d73c6 100644
--- a/hw/top_earlgrey/doc/top_earlgrey.gen.hjson
+++ b/hw/top_earlgrey/doc/top_earlgrey.gen.hjson
@@ -78,6 +78,7 @@
           width: 1
         }
       ]
+      scan: "false"
     }
     {
       name: gpio
@@ -104,6 +105,7 @@
           width: 32
         }
       ]
+      scan: "false"
     }
     {
       name: spi_device
@@ -164,6 +166,7 @@
           width: 1
         }
       ]
+      scan: "true"
     }
     {
       name: flash_ctrl
@@ -204,6 +207,7 @@
           width: 1
         }
       ]
+      scan: "false"
     }
     {
       name: rv_timer
@@ -224,6 +228,7 @@
           width: 1
         }
       ]
+      scan: "false"
     }
     {
       name: hmac
@@ -248,6 +253,7 @@
           width: 1
         }
       ]
+      scan: "false"
     }
     {
       name: rv_plic
@@ -267,6 +273,7 @@
       available_output_list: []
       available_inout_list: []
       interrupt_list: []
+      scan: "false"
     }
   ]
   memory:
diff --git a/hw/top_earlgrey/doc/top_earlgrey.tpl.sv b/hw/top_earlgrey/doc/top_earlgrey.tpl.sv
index 92d564e..4bb4ec5 100644
--- a/hw/top_earlgrey/doc/top_earlgrey.tpl.sv
+++ b/hw/top_earlgrey/doc/top_earlgrey.tpl.sv
@@ -389,6 +389,9 @@
       .irq_id_o   (irq_id),
       .msip_o     (msip),
     % endif
+    % if m["scan"] == "true":
+      .scanmode_i   (scanmode_i),
+    % endif
       .clk_i(${"clk_i" if m["clock"] == "main" else "clk_"+ m["clock"] + "_i"}),
       .rst_ni(${"ndmreset_n" if m["clock"] == "main" else "rst_" + m["clock"] + "_ni"})
   );
diff --git a/hw/top_earlgrey/rtl/top_earlgrey.sv b/hw/top_earlgrey/rtl/top_earlgrey.sv
index 11574bd..d18e26f 100644
--- a/hw/top_earlgrey/rtl/top_earlgrey.sv
+++ b/hw/top_earlgrey/rtl/top_earlgrey.sv
@@ -372,6 +372,7 @@
       .intr_rxerr_o (intr_spi_device_rxerr),
       .intr_rxoverflow_o (intr_spi_device_rxoverflow),
       .intr_txunderflow_o (intr_spi_device_txunderflow),
+      .scanmode_i   (scanmode_i),
       .clk_i(clk_i),
       .rst_ni(ndmreset_n)
   );
diff --git a/util/topgen/merge.py b/util/topgen/merge.py
index fcfc3e8..154e336 100644
--- a/util/topgen/merge.py
+++ b/util/topgen/merge.py
@@ -139,6 +139,12 @@
 
     # (TBD) alert_list
 
+    # scan
+    if "scan" in ip:
+        ip_module["scan"] = ip["scan"]
+    else:
+        ip_module["scan"] = "false"
+
 
 # TODO: Replace this part to be configurable from hjson or template
 predefined_modules = {
@@ -174,7 +180,9 @@
             host] if host in predefined_modules else ""
         obj[0]["pipeline"] = obj[0]["pipeline"] if "pipeline" in obj[
             0] else "true"
-        obj[0]["pipeline_byp"] = obj[0]["pipeline_byp"] if obj[0]["pipeline"] == "true" and "pipeline_byp" in obj[0] else "true"
+        obj[0]["pipeline_byp"] = obj[0]["pipeline_byp"] if obj[0][
+            "pipeline"] == "true" and "pipeline_byp" in obj[0] else "true"
+
 
 def process_pipeline_var(node):
     """Add device nodes pipeline / pipeline_byp information
@@ -182,7 +190,9 @@
     - Supply a default of true / true if not defined by xbar
     """
     node["pipeline"] = node["pipeline"] if "pipeline" in node else "true"
-    node["pipeline_byp"] = node["pipeline_byp"] if "pipeline_byp" in node else "true"
+    node["pipeline_byp"] = node[
+        "pipeline_byp"] if "pipeline_byp" in node else "true"
+
 
 def xbar_adddevice(top, xbar, device):
     """Add device nodes information