diff --git a/.github/workflows/quick_test.sh b/.github/workflows/quick_test.sh
index d6d92dc..b3f3a05 100755
--- a/.github/workflows/quick_test.sh
+++ b/.github/workflows/quick_test.sh
@@ -12,6 +12,9 @@ set -e
# - Run FPGA tasks to validate netlist generations
python3 SCRIPT/repo_setup.py --openfpga_root_path ./OpenFPGA
+# Post processing netlist to use custom cells
+python3 HDL/common/custom_cell_mux_primitive_generator.py --template_netlist HDL/k4_N8_reset_softadder_caravel_io_FPGA_12x12_customhd_cc/SRC/sub_module/mux_primitives.v --output_verilog HDL/k4_N8_reset_softadder_caravel_io_FPGA_12x12_customhd_cc/SRC/sub_module/mux_primitives_hd.v
+
##############################################
# Generate wrapper HDL codes to bridge Caravel I/Os and FPGA I/Os
python3 HDL/common/wrapper_lines_generator.py --template_netlist HDL/common/caravel_fpga_wrapper_hd_template.v --pin_assignment_file HDL/common/caravel_wrapper_pin_assignment_v1.0.json --output_verilog HDL/common/caravel_fpga_wrapper_hd_v1.0.v
@@ -30,3 +33,4 @@ python3 TESTBENCH/common/post_pnr_wrapper_testbench_converter.py --post_pnr_test
# Generate wrapper testbenches from template tesbenches for scan chain tests
python3 TESTBENCH/common/post_pnr_wrapper_testbench_converter.py --post_pnr_testbench TESTBENCH/common/scff_test_post_pnr_v1.0.v --pin_assignment_file HDL/common/caravel_wrapper_pin_assignment_v1.0.json --wrapper_testbench TESTBENCH/k4_N8_caravel_io_FPGA_12x12_fdhd_cc/postpnr/verilog_testbench/scff_test_post_pnr_wrapper.v
python3 TESTBENCH/common/post_pnr_wrapper_testbench_converter.py --post_pnr_testbench TESTBENCH/common/scff_test_post_pnr_v1.1.v --pin_assignment_file HDL/common/caravel_wrapper_pin_assignment_v1.1.json --wrapper_testbench TESTBENCH/k4_N8_reset_softadder_caravel_io_FPGA_12x12_fdhd_cc/postpnr/verilog_testbench/scff_test_post_pnr_wrapper.v
+
diff --git a/ARCH/openfpga_arch_template/k4_frac_N8_reset_softadder_register_scan_chain_caravel_io_skywater130nm_customhd_cc_openfpga.xml b/ARCH/openfpga_arch_template/k4_frac_N8_reset_softadder_register_scan_chain_caravel_io_skywater130nm_customhd_cc_openfpga.xml
index b427d66..479c05b 100644
--- a/ARCH/openfpga_arch_template/k4_frac_N8_reset_softadder_register_scan_chain_caravel_io_skywater130nm_customhd_cc_openfpga.xml
+++ b/ARCH/openfpga_arch_template/k4_frac_N8_reset_softadder_register_scan_chain_caravel_io_skywater130nm_customhd_cc_openfpga.xml
@@ -43,6 +43,18 @@
10e-12
+
+
+
+
+
+
+ 10e-12
+
+
+ 10e-12
+
+
@@ -160,7 +172,7 @@
-
+
@@ -169,7 +181,7 @@
-
+
diff --git a/HDL/common/custom_cell_mux_primitive_generator.py b/HDL/common/custom_cell_mux_primitive_generator.py
new file mode 100644
index 0000000..45b8c88
--- /dev/null
+++ b/HDL/common/custom_cell_mux_primitive_generator.py
@@ -0,0 +1,179 @@
+#####################################################################
+# Python script generate Verilog codes for the primitive modules
+# that is used to build routing multiplexers
+# The Verilog codes will exploit the custom cells built for MUX primitives
+# including:
+# - 2-input MUX
+# - 3-input MUX
+# - Skywater MUX2 standard cell
+#####################################################################
+
+import os
+from os.path import dirname, abspath, isfile
+import shutil
+import re
+import argparse
+import logging
+import json
+
+#####################################################################
+# Initialize logger
+#####################################################################
+logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.DEBUG)
+
+#####################################################################
+# Parse the options
+# - OpenFPGA root path is a manadatory option
+#####################################################################
+parser = argparse.ArgumentParser(
+ description='Generator for custom cells of routing multiplexer primitives')
+parser.add_argument('--template_netlist', required=True,
+ help='Specify template verilog netlist')
+parser.add_argument('--output_verilog', required=True,
+ help='Specify output verilog file path')
+args = parser.parse_args()
+
+#####################################################################
+# Check options:
+# - Input file must be valid
+# Otherwise, error out
+# - Remove any output file if already exist
+# TODO: give a warning when remove files
+#####################################################################
+if not isfile(args.template_netlist):
+ logging.error("Invalid template netlist: " + args.template_netlist + "\nFile does not exist!\n")
+ exit(1)
+if isfile(args.output_verilog):
+ logging.warn("Remove existing output netlist: " + args.output_verilog + "!\n")
+ os.remove(args.output_verilog)
+
+#####################################################################
+# Open the template Verilog netlist and start modification
+#####################################################################
+logging.info("Converting template netlist:"+ args.template_netlist)
+logging.info(" To custom cell netlist:"+ args.output_verilog)
+# Create output file handler
+custom_nlist = open(args.output_verilog, "w")
+
+#######################################################################
+# A function to generate Verilog codes for a MUX3 custom cell
+# Given an input index
+def generate_verilog_codes_custom_cell_mux3(first_input_index, instance_index):
+ lines = []
+
+ lines.append("\tscs8hd_muxinv3_1 scs8hd_muxinv3_1_" + str(instance_index) + "(")
+ lines.append("\t .Q1(in[" + str(first_input_index) + "]),")
+ lines.append("\t .Q2(in[" + str(first_input_index + 1) + "]),")
+ lines.append("\t .Q3(in[" + str(first_input_index + 2) + "]),")
+ lines.append("\t .S0(mem[" + str(first_input_index) + "]),")
+ lines.append("\t .S0B(mem_inv[" + str(first_input_index) + "]),")
+ lines.append("\t .S1(mem[" + str(first_input_index + 1) + "]),")
+ lines.append("\t .S1B(mem_inv[" + str(first_input_index + 1) + "]),")
+ lines.append("\t .S2(mem[" + str(first_input_index + 2) + "]),")
+ lines.append("\t .S2B(mem_inv[" + str(first_input_index + 2) + "]),")
+ lines.append("\t .Z(out[0])")
+ lines.append("\t );")
+
+ return lines
+
+#######################################################################
+# A function to generate Verilog codes for a MUX3 custom cell
+# Given an input index
+def generate_verilog_codes_custom_cell_mux2(first_input_index, instance_index):
+ lines = []
+
+ lines.append("\tscs8hd_muxinv2_1 scs8hd_muxinv2_1_" + str(instance_index) + "(")
+ lines.append("\t .Q1(in[" + str(first_input_index) + "]),")
+ lines.append("\t .Q2(in[" + str(first_input_index + 1) + "]),")
+ lines.append("\t .S0(mem[" + str(first_input_index) + "]),")
+ lines.append("\t .S0B(mem_inv[" + str(first_input_index) + "]),")
+ lines.append("\t .S1(mem[" + str(first_input_index + 1) + "]),")
+ lines.append("\t .S1B(mem_inv[" + str(first_input_index + 1) + "]),")
+ lines.append("\t .Z(out[0])")
+ lines.append("\t );")
+
+ return lines
+
+
+#######################################################################
+# A function to output custom cells of multiplexing structure to a file
+# based on the input size and memory size
+# - If the memory size is 1, the input size should be 2
+# In this case, an standard cell will be outputted
+# - If the memory size is larger than 1, the input size should be the same
+# as memory size. In this case, we will output custom cells
+def write_custom_mux_cells_to_file(custom_nlist, input_size, mem_size):
+ lines = []
+ if (1 == mem_size):
+ assert(2 == input_size)
+ # Output a standard cell, currently we support HD cell MUX2
+ lines.append("\tsky130_fd_sc_hd_mux2_1 sky130_fd_sc_hd_mux2_1_0(")
+ lines.append("\t .A1(in[0]),")
+ lines.append("\t .A0(in[1]),")
+ lines.append("\t .S(mem[0]),")
+ lines.append("\t .X(out[0])")
+ lines.append("\t );")
+ else:
+ assert(1 < mem_size)
+ assert(mem_size == input_size)
+ # Currently we support MUX2 and MUX3 custom cells
+ # - If the input size is an odd number, we will use
+ # - 1 MUX3 cell
+ # - a few MUX2 cells
+ if (1 == input_size % 2):
+ assert(3 <= input_size)
+ for line in generate_verilog_codes_custom_cell_mux3(0, 0):
+ lines.append(line)
+ for mux2_inst in range(int((input_size - 3) / 2)):
+ for line in generate_verilog_codes_custom_cell_mux2(3 + 2 * mux2_inst, mux2_inst):
+ lines.append(line)
+ # - If the input size is an even number, we will use
+ # - a few MUX2 cells
+ else:
+ assert (0 == input_size % 2)
+ for mux2_inst in range(int(input_size / 2)):
+ for line in generate_verilog_codes_custom_cell_mux2(2 * mux2_inst, mux2_inst):
+ lines.append(line)
+
+ # Output lines to file
+ for line in lines:
+ custom_nlist.write(line + "\n")
+
+# Read line by line from template netlist
+with open(args.template_netlist, "r") as wp:
+ template_nlist = wp.readlines()
+ # A flag for write the current line or skip
+ output_action = "copy"
+ input_size = 0
+ mem_size = 0
+ for line_num, curr_line in enumerate(template_nlist):
+ # If the current line satisfy the following conditions
+ # It should be modified and outputted to custom netlist
+ # Other lines can be directly copied to custom netlist
+ line2output = curr_line
+ # Once current line starts with a module definition
+ # Find the input size and memory size
+ if (curr_line.startswith("module ")):
+ input_size = int(re.findall("input(\d+)_mem(\d+)\(", curr_line)[0][0])
+ mem_size = int(re.findall("input(\d+)_mem(\d+)\(", curr_line)[0][1])
+ assert(input_size > 0)
+ assert(mem_size > 0)
+ # Change status indicating that we are now inside a module
+ output_action = "copy"
+
+ # If a line contains the keyword TGATE
+ # we will bypass all the lines until reach the endmodule line
+ if (curr_line.startswith("\tTGATE TGATE")):
+ output_action = "skip"
+
+ # Reaching the end of the current module
+ # Now output the custom cell instanciation
+ if (curr_line.startswith("endmodule")):
+ write_custom_mux_cells_to_file(custom_nlist, input_size, mem_size)
+ output_action = "copy"
+
+ if ("skip" != output_action):
+ custom_nlist.write(line2output)
+
+custom_nlist.close()
+logging.info("Done")