Instantiating a hardened module in a design#
Hardened modules allow predictable timing, efficient placement, and scalable hierarchical ASIC design.
This tutorial demonstrates how to use Silicon Compiler to harden a Verilog module as a reusable macro, and then instantiate it multiple times in another module by:
Setup design
Synthesizing, placing, and routing mod_and using Silicon Compiler
Packaging the resulting layout and timing models from mod_and as a hard macro
Running the ASIC flow on module top
All these steps are contained in the python script.
To run this script from its containing folder:
smake build_top
or to use docker:
docker run --rm -v "$(pwd):/sc_work" \
ghcr.io/siliconcompiler/sc_runner:latest \
python3 make.py
The image output will appear in: ./build/top/job0/write.gds/0/outputs/top.png
Environment Setup#
Import the modules to be used:
from siliconcompiler import Design, ASIC, StdCellLibrary
from siliconcompiler.targets import skywater130_demo
Files used#
Child Module (and.v)#
module mod_and (
input wire clk,
input wire [7:0] a,
input wire [7:0] b,
output reg [7:0] y
);
always @(posedge clk) begin
y <= a & b;
end
endmodule
Parent Module (top.v)#
module top (
input wire clk,
input wire [7:0] a1,
input wire [7:0] b1,
input wire [7:0] a2,
input wire [7:0] b2,
output wire [7:0] y
);
wire [7:0] d1;
wire [7:0] d2;
mod_and D1 (
.clk(clk),
.a (a1),
.b (b1),
.y (d1)
);
mod_and D2 (
.clk(clk),
.a (a2),
.b (b2),
.y (d2)
);
mod_and Y (
.clk(clk),
.a (d1),
.b (d2),
.y (y)
);
endmodule
Step 1: Setup design#
The following code will instantiate the module: mod_and and top:
Child Module (and.v)#
class And(Design):
"""
Defines the child module (an AND gate).
Inheriting from Design allows us to encapsulate file paths and module names.
"""
def __init__(self):
super().__init__("and")
# Set the root directory for relative paths to this file's location
self.set_dataroot("local", __file__)
self.set_topmodule("mod_and", fileset="rtl")
self.add_file("and.v", fileset="rtl")
Parent Module (top.v)#
class Top(Design):
"""
Defines the parent module (Top) which contains instances of the And gate.
"""
def __init__(self):
super().__init__("top")
self.set_dataroot("local", __file__)
self.set_topmodule("top", fileset="rtl")
self.add_file("top.v", fileset="rtl")
# Logically link 'And' so the linter/elaborator knows definitions exist.
# Note: In the build_top() function, we will override this for physical implementation.
self.add_depfileset(And(), depfileset="rtl", fileset="rtl")
Dependency graph from top:
Dependency graph from Top().write_depgraph#
Step 2: Synthesize, Place & Route module mod_and#
The following code will build module mod_and with the default ASIC flow. This generates the physical layout required for the macro.
def build_and() -> StdCellLibrary:
"""
Hardens module 'And' and packages it as a library.
Returns:
StdCellLibrary: An object containing paths to the generated LEF, GDS, and LIB files.
"""
project = ASIC(And())
project.add_fileset("rtl")
skywater130_demo(project)
# Run the standard ASIC flow (Syn -> Floorplan -> Place -> Route -> Export)
project.run()
project.summary()
# Create a reproducible snapshot of the build
project.snapshot()
Step 3: Packaging the resulting layout and timing models from mod_and as a hard macro#
Once the flow is complete, we must package the results into a StdCellLibrary. This object tells the tools where to find the physical views (LEF/GDS) and timing views (LIB) required by the parent design. We gather the results from the previous build step and register them into the library object
# --- Packaging the Macro ---
# Create a new library object to represent this hardened block
library = StdCellLibrary("module_and")
# Copy PDK name from the build project
library.add_asic_pdk(project.get("asic", "pdk"))
# Register Physical Views (GDS and Abstract)
with library.active_fileset("models.physical"):
# LEF: Abstract view for placement and routing in the parent
library.add_file(project.find_result("lef", step="write.views"))
# GDS: Actual layout for final merge
library.add_file(project.find_result("gds", step="write.gds"))
# Include necessary setup files for APR tools (like OpenROAD)
library.add_asic_aprfileset()
# Register Timing Views (Liberty files) for all available corners
# This ensures the parent flow can perform timing analysis on the macro
for corner in ("slow", "typical", "fast"):
with library.active_fileset(f"models.timing.{corner}"):
library.add_file(project.find_result(f"{corner}.lib", step="write.views"))
library.add_asic_libcornerfileset(corner, "nldm")
return library
Step 4: Running the ASIC flow on module top#
Finally, we configure the flow for the top module. Critical steps here include:
Blackboxing (Alias): We use add_alias to prevent the tool from synthesizing the RTL of And. We want to use the pre-hardened version, not re-synthesize it.
Library Injection: We use add_asiclib to provide the LEF and LIB files we packaged in Step 3.
def build_top(size: int = 250, margin: int = 10):
"""
Builds the top-level module using the pre-built 'And' library.
"""
project = ASIC(Top())
project.add_fileset('rtl')
# --- Hierarchical Configuration ---
# 1. 'add_alias': Tells the tool "When you see module 'And', do not compile its RTL."
# This effectively turns 'And' into a blackbox during synthesis.
project.add_alias(And(), "rtl", None, None)
# 2. 'add_asiclib': Injects the pre-built library (LEF/LIB) we created in build_and().
# The APR tool will use the LEF for placement and the LIB for timing.
project.add_asiclib(build_and())
# Load target technology
skywater130_demo(project)
# --- Constraints ---
# Setting explicit die area is critical for macros.
# If the core is too small, the placer may fail to fit the child macros.
project.constraint.area.set_diearea_rectangle(size, size, coremargin=margin)
project.run()
project.summary()
project.snapshot()
Note
Setting the core and die area explicitly is crucial for macro placement. If the area is too small, the floorplan may fail to insert the macros.
project.constraint.area.set_diearea_rectangle(size, size, coremargin=margin)
Conclusion#
This tutorial demonstrates how to perform a basic modular hierarchical ASIC design flow in Silicon Compiler by:
Hardening a leaf module mod_and
Exporting the layout and timing views from mod_and as a custom library
module_andInstantiating
module_andin the parent module top
This approach enables scalable chip design with reusable hardened blocks.