ASIC Flow
The ASIC-flow package materialises deterministic Yosys, OpenROAD, OpenSTA,
KLayout, Magic, and Netgen inputs for Sky130, GF180MCU, commercial-template,
and custom process configurations. It does not execute those external tools or
claim physical area, power, timing, DRC, LVS, or GDSII closure.
Architecture
The historical sc_neurocore.asic_flow.asic_flow import remains available, but
implementation ownership is split by responsibility:
| Module |
Responsibility |
pdk |
PDK presets, path resolution, installation checks, and validation |
design |
physical-design and stochastic-synthesis parameters |
decks |
Yosys, OpenROAD, SDC, and GDSII scripts |
signoff |
STA/DRC/LVS scripts, PVT corners, OCV, and summaries |
constraints |
CDC, IR-drop, IO-placement, and equivalence scripts |
estimation |
deterministic pre-synthesis screening estimates |
flow |
complete deck generation, bundle writes, and evidence manifests |
hierarchy |
per-block synthesis and hard-macro top integration |
readiness |
evidence-derived tape-out checklist state |
The modules form an acyclic import graph. All 38 historical definitions retain
their original qualified names and pickle paths. The package root intentionally
keeps the narrower one-command API:
Pythonfrom sc_neurocore.asic_flow import ASICFlowBundle, generate_asic_flow_bundle
Generate a bundle
Pythonfrom sc_neurocore.asic_flow.asic_flow import DesignParams, generate_asic_flow_bundle
bundle = generate_asic_flow_bundle(
"build/asic/sky130_demo",
pdk_type="sky130",
design=DesignParams(
top_module="sc_neurocore_top",
rtl_files=["rtl/top.sv"],
),
pdk_root="/opt/pdks",
require_pdk_files=True,
n_neurons=32,
n_synapses=512,
bitstream_width=256,
n_aer_ports=8,
formal_evidence_artifacts=[
"formal/sc_neurocore_top.sby",
"formal/report.json",
],
)
print(bundle.manifest_path)
print(bundle.pdk_resolution.usable_for_synthesis)
The bundle contains nine generated flow files and
asic_flow_manifest.json. When require_pdk_files=True, missing Liberty,
cell-LEF, technology-LEF, setup, DRC, and LVS inputs are recorded rather than
hidden. Formal evidence is complete for a claim only when at least one proof
source (.sby, .sv, or .sva) and one report (.json, .txt, or .log)
are attached.
The manifest always reports external_eda_executed: false and
physical_ppa_claim_allowed: false. A downstream run must attach exact tool,
container, PDK revision, command, and signoff artefacts before physical claims
are made.
Polyglot boundary
Deck construction is typed text and filesystem orchestration, not a numerical
kernel. Earlier Rust, Go, Julia, and Mojo files named for asic_flow were
nonfunctional generated mirrors and have been removed. The maintained execution
boundary is the external EDA toolchain; no language-speed comparison is claimed
for removed code.
Verification and benchmark evidence
The focused suite contains 100 tests and covers all 580 statements and 66
branches across the 11 package files. It locks the historical facade, exclusive
symbol ownership, the real import DAG, pickle paths, package API, responsibility
size limits, removed false mirrors, and live benchmark hashes.
benchmarks/bench_asic_flow.py compares the pre-refactor source archive with
the modular candidate over 30 interleaved cold processes. Both variants emit
the same 10,465-byte canonical payload at SHA-256
ae901f9b10bdc61f0997964d6143568994625bdf89080f02cd58efbc83099653.
The committed capture used CPU affinity without exclusive isolation while the
workstation was under load, so its timing medians are local regression context,
not publishable throughput evidence.
API reference
sc_neurocore.asic_flow.asic_flow
Preserve the original ASIC-flow API over responsibility modules.
New code may import the focused modules directly. Historical imports and
pickle-qualified names remain stable through this facade.
CDCCheckGenerator
Generates clock-domain crossing lint scripts.
Source code in src/sc_neurocore/asic_flow/constraints.py
| Python |
|---|
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 | class CDCCheckGenerator:
"""Generates clock-domain crossing lint scripts."""
@staticmethod
def generate(design: DesignParams, clock_domains: Optional[List[str]] = None) -> str:
"""Render CDC checks for explicit domains or the design clock."""
if clock_domains is None:
clock_domains = [design.clock_name]
domain_defs = "\n".join(f"create_clock -name {c} [get_ports {c}]" for c in clock_domains)
return textwrap.dedent(f"""\
# SC-NeuroCore CDC Check
# Domains: {", ".join(clock_domains)}
{domain_defs}
# Report all clock-domain crossings
report_cdc -from [all_clocks] -to [all_clocks]
report_cdc -type async_reset
report_cdc -type reconvergence
# Check for missing synchronisers
check_cdc -severity error
""")
|
generate(design, clock_domains=None)
staticmethod
Render CDC checks for explicit domains or the design clock.
Source code in src/sc_neurocore/asic_flow/constraints.py
| Python |
|---|
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 | @staticmethod
def generate(design: DesignParams, clock_domains: Optional[List[str]] = None) -> str:
"""Render CDC checks for explicit domains or the design clock."""
if clock_domains is None:
clock_domains = [design.clock_name]
domain_defs = "\n".join(f"create_clock -name {c} [get_ports {c}]" for c in clock_domains)
return textwrap.dedent(f"""\
# SC-NeuroCore CDC Check
# Domains: {", ".join(clock_domains)}
{domain_defs}
# Report all clock-domain crossings
report_cdc -from [all_clocks] -to [all_clocks]
report_cdc -type async_reset
report_cdc -type reconvergence
# Check for missing synchronisers
check_cdc -severity error
""")
|
IOConstraintGenerator
dataclass
Generates IO placement constraint files.
Source code in src/sc_neurocore/asic_flow/constraints.py
| Python |
|---|
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108 | @dataclass
class IOConstraintGenerator:
"""Generates IO placement constraint files."""
@staticmethod
def generate(pins: List[IOPin], design: DesignParams) -> str:
"""Render one OpenROAD ``place_pin`` command per supplied IO pin."""
lines = [f"# IO Constraints for {design.top_module}"]
for pin in pins:
lines.append(
f"place_pin -pin_name {pin.name} -layer {pin.layer} "
f"-location {{{pin.offset_um} 0}} -side {pin.side}"
)
return "\n".join(lines) + "\n"
@staticmethod
def auto_assign(signal_names: List[str], sides: str = "NSEW") -> List[IOPin]:
"""Auto-assign pins to die edges round-robin."""
pins = []
for i, name in enumerate(signal_names):
side = sides[i % len(sides)]
pins.append(IOPin(name=name, direction="input", side=side, offset_um=float(i * 10)))
return pins
|
generate(pins, design)
staticmethod
Render one OpenROAD place_pin command per supplied IO pin.
Source code in src/sc_neurocore/asic_flow/constraints.py
| Python |
|---|
90
91
92
93
94
95
96
97
98
99 | @staticmethod
def generate(pins: List[IOPin], design: DesignParams) -> str:
"""Render one OpenROAD ``place_pin`` command per supplied IO pin."""
lines = [f"# IO Constraints for {design.top_module}"]
for pin in pins:
lines.append(
f"place_pin -pin_name {pin.name} -layer {pin.layer} "
f"-location {{{pin.offset_um} 0}} -side {pin.side}"
)
return "\n".join(lines) + "\n"
|
auto_assign(signal_names, sides='NSEW')
staticmethod
Auto-assign pins to die edges round-robin.
Source code in src/sc_neurocore/asic_flow/constraints.py
| Python |
|---|
101
102
103
104
105
106
107
108 | @staticmethod
def auto_assign(signal_names: List[str], sides: str = "NSEW") -> List[IOPin]:
"""Auto-assign pins to die edges round-robin."""
pins = []
for i, name in enumerate(signal_names):
side = sides[i % len(sides)]
pins.append(IOPin(name=name, direction="input", side=side, offset_um=float(i * 10)))
return pins
|
IOPin
dataclass
Specification for one IO pad.
Source code in src/sc_neurocore/asic_flow/constraints.py
| Python |
|---|
75
76
77
78
79
80
81
82
83 | @dataclass
class IOPin:
"""Specification for one IO pad."""
name: str
direction: str # "input", "output", "inout"
side: str = "N" # N, S, E, W
offset_um: float = 0.0
layer: str = "met3"
|
IRDropGenerator
Generates IR drop analysis scripts for OpenROAD.
Source code in src/sc_neurocore/asic_flow/constraints.py
| Python |
|---|
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72 | class IRDropGenerator:
"""Generates IR drop analysis scripts for OpenROAD."""
@staticmethod
def generate(pdk: PDKConfig, design: DesignParams, toggle_rate: float = 0.1) -> str:
"""Render OpenROAD power-grid analysis at an input toggle fraction."""
return textwrap.dedent(f"""\
# SC-NeuroCore IR Drop Analysis — OpenROAD
# Toggle rate: {toggle_rate:.2f}
# Read design
read_lef {pdk.tech_lef}
read_lef {pdk.lef_file}
read_def {design.top_module}_final.def
read_liberty {pdk.liberty_file}
read_sdc constraints_{design.top_module}.sdc
# Set activity
set_power_activity -input -activity {toggle_rate:.3f}
# Analyze IR drop
analyze_power_grid -net {design.power_nets[0]}
analyze_power_grid -net {design.power_nets[1]}
# Report
report_power_grid -net {design.power_nets[0]} -corner tt
""")
|
generate(pdk, design, toggle_rate=0.1)
staticmethod
Render OpenROAD power-grid analysis at an input toggle fraction.
Source code in src/sc_neurocore/asic_flow/constraints.py
| Python |
|---|
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72 | @staticmethod
def generate(pdk: PDKConfig, design: DesignParams, toggle_rate: float = 0.1) -> str:
"""Render OpenROAD power-grid analysis at an input toggle fraction."""
return textwrap.dedent(f"""\
# SC-NeuroCore IR Drop Analysis — OpenROAD
# Toggle rate: {toggle_rate:.2f}
# Read design
read_lef {pdk.tech_lef}
read_lef {pdk.lef_file}
read_def {design.top_module}_final.def
read_liberty {pdk.liberty_file}
read_sdc constraints_{design.top_module}.sdc
# Set activity
set_power_activity -input -activity {toggle_rate:.3f}
# Analyze IR drop
analyze_power_grid -net {design.power_nets[0]}
analyze_power_grid -net {design.power_nets[1]}
# Report
report_power_grid -net {design.power_nets[0]} -corner tt
""")
|
LECGenerator
Generates Logic Equivalence Checking scripts.
Source code in src/sc_neurocore/asic_flow/constraints.py
| Python |
|---|
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137 | class LECGenerator:
"""Generates Logic Equivalence Checking scripts."""
@staticmethod
def generate(design: DesignParams) -> str:
"""Render a Yosys equivalence proof between synthesis and routed RTL."""
return textwrap.dedent(f"""\
# SC-NeuroCore LEC — Yosys equivalence check
# Golden: synth_{design.top_module}.v
# Revised: {design.top_module}_final.v
read_verilog synth_{design.top_module}.v
prep -top {design.top_module}
design -stash golden
read_verilog {design.top_module}_final.v
prep -top {design.top_module}
design -stash revised
design -copy-from golden -as golden {design.top_module}
design -copy-from revised -as revised {design.top_module}
equiv_make golden revised equiv
equiv_simple
equiv_induct
equiv_status -assert
""")
|
generate(design)
staticmethod
Render a Yosys equivalence proof between synthesis and routed RTL.
Source code in src/sc_neurocore/asic_flow/constraints.py
| Python |
|---|
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137 | @staticmethod
def generate(design: DesignParams) -> str:
"""Render a Yosys equivalence proof between synthesis and routed RTL."""
return textwrap.dedent(f"""\
# SC-NeuroCore LEC — Yosys equivalence check
# Golden: synth_{design.top_module}.v
# Revised: {design.top_module}_final.v
read_verilog synth_{design.top_module}.v
prep -top {design.top_module}
design -stash golden
read_verilog {design.top_module}_final.v
prep -top {design.top_module}
design -stash revised
design -copy-from golden -as golden {design.top_module}
design -copy-from revised -as revised {design.top_module}
equiv_make golden revised equiv
equiv_simple
equiv_induct
equiv_status -assert
""")
|
FloorplanGenerator
Generates OpenROAD floorplan TCL scripts.
Source code in src/sc_neurocore/asic_flow/decks.py
| Python |
|---|
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131 | class FloorplanGenerator:
"""Generates OpenROAD floorplan TCL scripts."""
@staticmethod
def generate(pdk: PDKConfig, design: DesignParams) -> str:
"""Render the OpenROAD floorplan and optional two-net power grid."""
die = design.die_area_um
core = design.core_area_um
power = design.power_nets
power_ring = ""
if len(power) >= 2:
power_ring = textwrap.dedent(f"""\
# Power grid
add_global_connection -net {power[0]} -pin_pattern "VPWR|VDD|vdd" -power
add_global_connection -net {power[1]} -pin_pattern "VGND|VSS|vss" -ground
set_voltage_domain -name CORE -power {power[0]} -ground {power[1]}
define_pdn_grid -name core_grid -pins {{{power[0]} {power[1]}}} \\
-voltage_domains CORE
add_pdn_stripe -grid core_grid -layer met1 -width 0.48 -pitch 5.44 \\
-offset 0 -followpins
add_pdn_stripe -grid core_grid -layer met4 -width 1.6 -pitch 27.14 \\
-offset 13.57
add_pdn_stripe -grid core_grid -layer met5 -width 1.6 -pitch 27.2 \\
-offset 13.6
add_pdn_connect -grid core_grid -layers {{met1 met4}}
add_pdn_connect -grid core_grid -layers {{met4 met5}}
""")
return textwrap.dedent(f"""\
# SC-NeuroCore ASIC Floorplan — OpenROAD
# Die: {die[2] - die[0]}×{die[3] - die[1]} µm
# Read technology
read_lef {pdk.tech_lef}
read_lef {pdk.lef_file}
# Read synthesized netlist
read_verilog synth_{design.top_module}.v
link_design {design.top_module}
# Read timing constraints
read_sdc constraints_{design.top_module}.sdc
# Initialize floorplan
initialize_floorplan \\
-die_area {{{die[0]} {die[1]} {die[2]} {die[3]}}} \\
-core_area {{{core[0]} {core[1]} {core[2]} {core[3]}}} \\
-site unithd
# IO placement
place_pins -hor_layers met3 -ver_layers met2
{power_ring}
""")
|
generate(pdk, design)
staticmethod
Render the OpenROAD floorplan and optional two-net power grid.
Source code in src/sc_neurocore/asic_flow/decks.py
| Python |
|---|
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131 | @staticmethod
def generate(pdk: PDKConfig, design: DesignParams) -> str:
"""Render the OpenROAD floorplan and optional two-net power grid."""
die = design.die_area_um
core = design.core_area_um
power = design.power_nets
power_ring = ""
if len(power) >= 2:
power_ring = textwrap.dedent(f"""\
# Power grid
add_global_connection -net {power[0]} -pin_pattern "VPWR|VDD|vdd" -power
add_global_connection -net {power[1]} -pin_pattern "VGND|VSS|vss" -ground
set_voltage_domain -name CORE -power {power[0]} -ground {power[1]}
define_pdn_grid -name core_grid -pins {{{power[0]} {power[1]}}} \\
-voltage_domains CORE
add_pdn_stripe -grid core_grid -layer met1 -width 0.48 -pitch 5.44 \\
-offset 0 -followpins
add_pdn_stripe -grid core_grid -layer met4 -width 1.6 -pitch 27.14 \\
-offset 13.57
add_pdn_stripe -grid core_grid -layer met5 -width 1.6 -pitch 27.2 \\
-offset 13.6
add_pdn_connect -grid core_grid -layers {{met1 met4}}
add_pdn_connect -grid core_grid -layers {{met4 met5}}
""")
return textwrap.dedent(f"""\
# SC-NeuroCore ASIC Floorplan — OpenROAD
# Die: {die[2] - die[0]}×{die[3] - die[1]} µm
# Read technology
read_lef {pdk.tech_lef}
read_lef {pdk.lef_file}
# Read synthesized netlist
read_verilog synth_{design.top_module}.v
link_design {design.top_module}
# Read timing constraints
read_sdc constraints_{design.top_module}.sdc
# Initialize floorplan
initialize_floorplan \\
-die_area {{{die[0]} {die[1]} {die[2]} {die[3]}}} \\
-core_area {{{core[0]} {core[1]} {core[2]} {core[3]}}} \\
-site unithd
# IO placement
place_pins -hor_layers met3 -ver_layers met2
{power_ring}
""")
|
GDSIIExporter
Generates GDSII stream-out scripts.
Source code in src/sc_neurocore/asic_flow/decks.py
| Python |
|---|
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250 | class GDSIIExporter:
"""Generates GDSII stream-out scripts."""
@staticmethod
def generate(pdk: PDKConfig, design: DesignParams) -> str:
"""Render open-PDK stream-out commands or a vendor-tool boundary."""
if pdk.is_open_source:
return textwrap.dedent(f"""\
# SC-NeuroCore GDSII Export — KLayout/Magic
# Merge standard cell GDS with routed DEF
# Option 1: KLayout
klayout -zz -rd design_name={design.top_module} \\
-rd in_def={design.top_module}_final.def \\
-rd in_gds="$PDK_ROOT/{pdk.pdk_type.value}/libs.ref/*/gds/*.gds" \\
-rd seal_gds="" \\
-rd out_gds={design.top_module}.gds \\
-rm $OPENLANE_ROOT/scripts/klayout/def2gds.py
# Option 2: Magic
magic -dnull -noconsole << EOF
lef read {pdk.tech_lef}
lef read {pdk.lef_file}
def read {design.top_module}_final.def
load {design.top_module}
select top cell
expand
gds write {design.top_module}.gds
quit
EOF
""")
return f"# GDSII export for {pdk.pdk_type.value}: use vendor stream-out\n"
|
generate(pdk, design)
staticmethod
Render open-PDK stream-out commands or a vendor-tool boundary.
Source code in src/sc_neurocore/asic_flow/decks.py
| Python |
|---|
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250 | @staticmethod
def generate(pdk: PDKConfig, design: DesignParams) -> str:
"""Render open-PDK stream-out commands or a vendor-tool boundary."""
if pdk.is_open_source:
return textwrap.dedent(f"""\
# SC-NeuroCore GDSII Export — KLayout/Magic
# Merge standard cell GDS with routed DEF
# Option 1: KLayout
klayout -zz -rd design_name={design.top_module} \\
-rd in_def={design.top_module}_final.def \\
-rd in_gds="$PDK_ROOT/{pdk.pdk_type.value}/libs.ref/*/gds/*.gds" \\
-rd seal_gds="" \\
-rd out_gds={design.top_module}.gds \\
-rm $OPENLANE_ROOT/scripts/klayout/def2gds.py
# Option 2: Magic
magic -dnull -noconsole << EOF
lef read {pdk.tech_lef}
lef read {pdk.lef_file}
def read {design.top_module}_final.def
load {design.top_module}
select top cell
expand
gds write {design.top_module}.gds
quit
EOF
""")
return f"# GDSII export for {pdk.pdk_type.value}: use vendor stream-out\n"
|
PlaceRouteGenerator
Generates OpenROAD place-and-route TCL scripts.
Source code in src/sc_neurocore/asic_flow/decks.py
| Python |
|---|
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176 | class PlaceRouteGenerator:
"""Generates OpenROAD place-and-route TCL scripts."""
@staticmethod
def generate(pdk: PDKConfig, design: DesignParams) -> str:
"""Render the OpenROAD placement, clock-tree, and routing script."""
return textwrap.dedent(f"""\
# SC-NeuroCore ASIC Place & Route — OpenROAD
# Target: {design.top_module}
# Global placement
global_placement -density {design.utilisation:.2f} -pad_left 2 -pad_right 2
# Clock tree synthesis
clock_tree_synthesis -root_buf {pdk.cell_prefix}buf_2 \\
-buf_list {{{pdk.cell_prefix}buf_4 {pdk.cell_prefix}buf_8}} \\
-wire_unit 10
# Repair hold violations
estimate_parasitics -placement
repair_timing -hold
# Detailed placement
detailed_placement
# Check placement
check_placement
# Filler cell insertion
filler_placement {pdk.cell_prefix}fill_1 {pdk.cell_prefix}fill_2
# Global routing
global_route -guide_file route_{design.top_module}.guide \\
-congestion_iterations 30
# Detailed routing
detailed_route -output_drc route_drc_{design.top_module}.rpt \\
-output_maze route_maze_{design.top_module}.log
# Write outputs
write_def {design.top_module}_final.def
write_verilog {design.top_module}_final.v
""")
|
generate(pdk, design)
staticmethod
Render the OpenROAD placement, clock-tree, and routing script.
Source code in src/sc_neurocore/asic_flow/decks.py
| Python |
|---|
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176 | @staticmethod
def generate(pdk: PDKConfig, design: DesignParams) -> str:
"""Render the OpenROAD placement, clock-tree, and routing script."""
return textwrap.dedent(f"""\
# SC-NeuroCore ASIC Place & Route — OpenROAD
# Target: {design.top_module}
# Global placement
global_placement -density {design.utilisation:.2f} -pad_left 2 -pad_right 2
# Clock tree synthesis
clock_tree_synthesis -root_buf {pdk.cell_prefix}buf_2 \\
-buf_list {{{pdk.cell_prefix}buf_4 {pdk.cell_prefix}buf_8}} \\
-wire_unit 10
# Repair hold violations
estimate_parasitics -placement
repair_timing -hold
# Detailed placement
detailed_placement
# Check placement
check_placement
# Filler cell insertion
filler_placement {pdk.cell_prefix}fill_1 {pdk.cell_prefix}fill_2
# Global routing
global_route -guide_file route_{design.top_module}.guide \\
-congestion_iterations 30
# Detailed routing
detailed_route -output_drc route_drc_{design.top_module}.rpt \\
-output_maze route_maze_{design.top_module}.log
# Write outputs
write_def {design.top_module}_final.def
write_verilog {design.top_module}_final.v
""")
|
SDCGenerator
Generates Synopsys Design Constraints (SDC) for STA.
Source code in src/sc_neurocore/asic_flow/decks.py
| Python |
|---|
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216 | class SDCGenerator:
"""Generates Synopsys Design Constraints (SDC) for STA."""
@staticmethod
def generate(pdk: PDKConfig, design: DesignParams) -> str:
"""Render clock, IO-delay, reset, fanout, and load constraints."""
period = design.clock_period_ns
return textwrap.dedent(f"""\
# SC-NeuroCore ASIC Constraints — SDC
# Clock: {design.clock_name} @ {design.target_frequency_mhz} MHz
create_clock [get_ports {design.clock_name}] \\
-name {design.clock_name} \\
-period {period:.3f}
# Clock uncertainty (10% of period)
set_clock_uncertainty {period * 0.1:.3f} [get_clocks {design.clock_name}]
# Input/output delays (25% of period)
set_input_delay {period * 0.25:.3f} -clock {design.clock_name} [all_inputs]
set_output_delay {period * 0.25:.3f} -clock {design.clock_name} [all_outputs]
# Reset is constant during operation
set_false_path -from [get_ports {design.reset_name}]
# Don't touch clock/reset nets
set_dont_touch_network [get_ports {design.clock_name}]
# Max transition / fanout
set_max_transition {period * 0.15:.3f} [current_design]
set_max_fanout {design.sc_optimisation.max_fanout} [current_design]
# Driving cell
set_driving_cell -lib_cell {pdk.cell_prefix}buf_2 [all_inputs]
# Load
set_load 0.05 [all_outputs]
""")
|
generate(pdk, design)
staticmethod
Render clock, IO-delay, reset, fanout, and load constraints.
Source code in src/sc_neurocore/asic_flow/decks.py
| Python |
|---|
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216 | @staticmethod
def generate(pdk: PDKConfig, design: DesignParams) -> str:
"""Render clock, IO-delay, reset, fanout, and load constraints."""
period = design.clock_period_ns
return textwrap.dedent(f"""\
# SC-NeuroCore ASIC Constraints — SDC
# Clock: {design.clock_name} @ {design.target_frequency_mhz} MHz
create_clock [get_ports {design.clock_name}] \\
-name {design.clock_name} \\
-period {period:.3f}
# Clock uncertainty (10% of period)
set_clock_uncertainty {period * 0.1:.3f} [get_clocks {design.clock_name}]
# Input/output delays (25% of period)
set_input_delay {period * 0.25:.3f} -clock {design.clock_name} [all_inputs]
set_output_delay {period * 0.25:.3f} -clock {design.clock_name} [all_outputs]
# Reset is constant during operation
set_false_path -from [get_ports {design.reset_name}]
# Don't touch clock/reset nets
set_dont_touch_network [get_ports {design.clock_name}]
# Max transition / fanout
set_max_transition {period * 0.15:.3f} [current_design]
set_max_fanout {design.sc_optimisation.max_fanout} [current_design]
# Driving cell
set_driving_cell -lib_cell {pdk.cell_prefix}buf_2 [all_inputs]
# Load
set_load 0.05 [all_outputs]
""")
|
SynthesisGenerator
Generates Yosys synthesis TCL scripts.
Source code in src/sc_neurocore/asic_flow/decks.py
| Python |
|---|
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 | class SynthesisGenerator:
"""Generates Yosys synthesis TCL scripts."""
@staticmethod
def generate(pdk: PDKConfig, design: DesignParams) -> str:
"""Render the Yosys synthesis script for ``design`` and ``pdk``."""
rtl_reads = "\n".join(f"read_verilog {f}" for f in design.rtl_files)
if not rtl_reads:
rtl_reads = f"read_verilog {design.top_module}.v"
sc_passes = "\n".join(design.sc_optimisation.yosys_passes())
if design.sc_optimisation.preserve_lfsr_hierarchy:
sc_passes = (
"# Preserve deterministic SC seed generators for gate-level debug\n"
"setattr -mod -pattern *lfsr* keep_hierarchy 1\n"
f"{sc_passes}"
)
abc_delay_ps = design.clock_period_ns * 1000.0 * design.sc_optimisation.abc_delay_margin
return textwrap.dedent(f"""\
# SC-NeuroCore ASIC Synthesis — Yosys Script
# PDK: {pdk.pdk_type.value}
# Target: {design.top_module} @ {design.target_frequency_mhz} MHz
# Read RTL
{rtl_reads}
# Hierarchy check
hierarchy -check -top {design.top_module}
# High-level synthesis
proc; opt; fsm; opt; memory; opt
# Technology mapping
synth -top {design.top_module}
# SC-aware optimisation
{sc_passes}
# Map to standard cells
dfflibmap -liberty {pdk.liberty_file}
abc -liberty {pdk.liberty_file} -D {abc_delay_ps:.0f}
# Clean up
opt_clean -purge
# Write outputs
write_verilog -noattr synth_{design.top_module}.v
write_json synth_{design.top_module}.json
# Statistics
stat -liberty {pdk.liberty_file}
""")
|
generate(pdk, design)
staticmethod
Render the Yosys synthesis script for design and pdk.
Source code in src/sc_neurocore/asic_flow/decks.py
| Python |
|---|
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 | @staticmethod
def generate(pdk: PDKConfig, design: DesignParams) -> str:
"""Render the Yosys synthesis script for ``design`` and ``pdk``."""
rtl_reads = "\n".join(f"read_verilog {f}" for f in design.rtl_files)
if not rtl_reads:
rtl_reads = f"read_verilog {design.top_module}.v"
sc_passes = "\n".join(design.sc_optimisation.yosys_passes())
if design.sc_optimisation.preserve_lfsr_hierarchy:
sc_passes = (
"# Preserve deterministic SC seed generators for gate-level debug\n"
"setattr -mod -pattern *lfsr* keep_hierarchy 1\n"
f"{sc_passes}"
)
abc_delay_ps = design.clock_period_ns * 1000.0 * design.sc_optimisation.abc_delay_margin
return textwrap.dedent(f"""\
# SC-NeuroCore ASIC Synthesis — Yosys Script
# PDK: {pdk.pdk_type.value}
# Target: {design.top_module} @ {design.target_frequency_mhz} MHz
# Read RTL
{rtl_reads}
# Hierarchy check
hierarchy -check -top {design.top_module}
# High-level synthesis
proc; opt; fsm; opt; memory; opt
# Technology mapping
synth -top {design.top_module}
# SC-aware optimisation
{sc_passes}
# Map to standard cells
dfflibmap -liberty {pdk.liberty_file}
abc -liberty {pdk.liberty_file} -D {abc_delay_ps:.0f}
# Clean up
opt_clean -purge
# Write outputs
write_verilog -noattr synth_{design.top_module}.v
write_json synth_{design.top_module}.json
# Statistics
stat -liberty {pdk.liberty_file}
""")
|
DesignParams
dataclass
ASIC design parameters.
Source code in src/sc_neurocore/asic_flow/design.py
| Python |
|---|
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76 | @dataclass
class DesignParams:
"""ASIC design parameters."""
top_module: str = "sc_neurocore_top"
clock_name: str = "clk"
reset_name: str = "rst_n"
reset_active_low: bool = True
target_frequency_mhz: float = 100.0
die_area_um: Tuple[float, float, float, float] = (0, 0, 500, 500)
core_area_um: Tuple[float, float, float, float] = (20, 20, 480, 480)
utilisation: float = 0.5
aspect_ratio: float = 1.0
io_margin_um: float = 20.0
power_nets: List[str] = field(default_factory=lambda: ["VDD", "VSS"])
rtl_files: List[str] = field(default_factory=list)
sc_optimisation: SCASICOptimisationConfig = field(default_factory=SCASICOptimisationConfig)
@property
def clock_period_ns(self) -> float:
"""Return the target clock period in nanoseconds."""
return 1000.0 / self.target_frequency_mhz
@property
def die_width_um(self) -> float:
"""Return the die width in micrometres."""
return self.die_area_um[2] - self.die_area_um[0]
@property
def die_height_um(self) -> float:
"""Return the die height in micrometres."""
return self.die_area_um[3] - self.die_area_um[1]
@property
def core_area_mm2(self) -> float:
"""Return the rectangular core area in square millimetres."""
w = self.core_area_um[2] - self.core_area_um[0]
h = self.core_area_um[3] - self.core_area_um[1]
return (w * h) / 1e6
|
clock_period_ns
property
Return the target clock period in nanoseconds.
die_width_um
property
Return the die width in micrometres.
die_height_um
property
Return the die height in micrometres.
core_area_mm2
property
Return the rectangular core area in square millimetres.
SCASICOptimisationConfig
dataclass
SC-specific synthesis settings for stochastic neuromorphic datapaths.
Source code in src/sc_neurocore/asic_flow/design.py
| Python |
|---|
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 | @dataclass(frozen=True)
class SCASICOptimisationConfig:
"""SC-specific synthesis settings for stochastic neuromorphic datapaths."""
share_stochastic_counters: bool = True
reduce_constant_widths: bool = True
preserve_lfsr_hierarchy: bool = True
max_fanout: int = 16
abc_delay_margin: float = 0.90
def yosys_passes(self) -> List[str]:
"""Return the ordered Yosys passes selected for the SC datapath."""
passes: List[str] = []
if self.reduce_constant_widths:
passes.append("wreduce")
if self.share_stochastic_counters:
passes.extend(["share", "opt_share"])
passes.append("opt_clean -purge")
return passes
|
yosys_passes()
Return the ordered Yosys passes selected for the SC datapath.
Source code in src/sc_neurocore/asic_flow/design.py
| Python |
|---|
27
28
29
30
31
32
33
34
35 | def yosys_passes(self) -> List[str]:
"""Return the ordered Yosys passes selected for the SC datapath."""
passes: List[str] = []
if self.reduce_constant_widths:
passes.append("wreduce")
if self.share_stochastic_counters:
passes.extend(["share", "opt_share"])
passes.append("opt_clean -purge")
return passes
|
DesignEstimate
dataclass
Uncalibrated pre-synthesis screening estimate for an SC module.
Source code in src/sc_neurocore/asic_flow/estimation.py
| Python |
|---|
18
19
20
21
22
23
24
25
26
27
28 | @dataclass
class DesignEstimate:
"""Uncalibrated pre-synthesis screening estimate for an SC module."""
module_name: str
gate_count: int
area_um2: float
dynamic_power_mw: float
leakage_power_mw: float
critical_path_ns: float
max_frequency_mhz: float
|
PreSynthEstimator
Compute deterministic screening values before synthesis.
The legacy coefficients are architectural scaling assumptions, not
foundry-characterised PPA models:
- Bitstream ops: ~10 gates/bit
- LIF neuron: ~500 gates
- STDP synapse: ~200 gates
- AER router: ~100 gates/port
Outputs support relative design screening only. They are not physical
evidence and must not be presented as post-synthesis or signoff results.
Source code in src/sc_neurocore/asic_flow/estimation.py
| Python |
|---|
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90 | class PreSynthEstimator:
"""Compute deterministic screening values before synthesis.
The legacy coefficients are architectural scaling assumptions, not
foundry-characterised PPA models:
- Bitstream ops: ~10 gates/bit
- LIF neuron: ~500 gates
- STDP synapse: ~200 gates
- AER router: ~100 gates/port
Outputs support relative design screening only. They are not physical
evidence and must not be presented as post-synthesis or signoff results.
"""
GATES_PER_BIT = 10
GATES_PER_LIF = 500
GATES_PER_SYNAPSE = 200
GATES_PER_AER_PORT = 100
@classmethod
def estimate(
cls,
n_neurons: int,
n_synapses: int,
bitstream_width: int,
n_aer_ports: int,
pdk: PDKConfig,
) -> DesignEstimate:
"""Estimate design metrics from architectural parameters."""
gates = (
n_neurons * cls.GATES_PER_LIF
+ n_synapses * cls.GATES_PER_SYNAPSE
+ bitstream_width * cls.GATES_PER_BIT
+ n_aer_ports * cls.GATES_PER_AER_PORT
)
# Legacy screening scale: 1 µm²/gate at 130 nm, quadratic by feature size.
scale = (pdk.min_feature_nm / 130.0) ** 2
area = gates * 1.0 * scale
# Legacy screening scales: 1 µW/gate dynamic and 0.01 µW/gate leakage.
freq_scale = 100.0 / (1000.0 / pdk.clock_period_ns)
v_scale = (pdk.voltage_v / 1.8) ** 2
dynamic = gates * 1e-3 * freq_scale * v_scale # mW
leakage = gates * 1e-5 * scale # mW
# Legacy critical-path screening scale, normalised to the 130 nm preset.
cp = max(1.0, 10 + 0.01 * n_neurons) * (pdk.min_feature_nm / 130.0)
max_freq = 1000.0 / cp
return DesignEstimate(
module_name="sc_neurocore_top",
gate_count=gates,
area_um2=area,
dynamic_power_mw=dynamic,
leakage_power_mw=leakage,
critical_path_ns=cp,
max_frequency_mhz=max_freq,
)
|
estimate(n_neurons, n_synapses, bitstream_width, n_aer_ports, pdk)
classmethod
Estimate design metrics from architectural parameters.
Source code in src/sc_neurocore/asic_flow/estimation.py
| Python |
|---|
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90 | @classmethod
def estimate(
cls,
n_neurons: int,
n_synapses: int,
bitstream_width: int,
n_aer_ports: int,
pdk: PDKConfig,
) -> DesignEstimate:
"""Estimate design metrics from architectural parameters."""
gates = (
n_neurons * cls.GATES_PER_LIF
+ n_synapses * cls.GATES_PER_SYNAPSE
+ bitstream_width * cls.GATES_PER_BIT
+ n_aer_ports * cls.GATES_PER_AER_PORT
)
# Legacy screening scale: 1 µm²/gate at 130 nm, quadratic by feature size.
scale = (pdk.min_feature_nm / 130.0) ** 2
area = gates * 1.0 * scale
# Legacy screening scales: 1 µW/gate dynamic and 0.01 µW/gate leakage.
freq_scale = 100.0 / (1000.0 / pdk.clock_period_ns)
v_scale = (pdk.voltage_v / 1.8) ** 2
dynamic = gates * 1e-3 * freq_scale * v_scale # mW
leakage = gates * 1e-5 * scale # mW
# Legacy critical-path screening scale, normalised to the 130 nm preset.
cp = max(1.0, 10 + 0.01 * n_neurons) * (pdk.min_feature_nm / 130.0)
max_freq = 1000.0 / cp
return DesignEstimate(
module_name="sc_neurocore_top",
gate_count=gates,
area_um2=area,
dynamic_power_mw=dynamic,
leakage_power_mw=leakage,
critical_path_ns=cp,
max_frequency_mhz=max_freq,
)
|
ASICFlowBundle
dataclass
Generated ASIC flow files plus the evidence manifest path.
Source code in src/sc_neurocore/asic_flow/flow.py
| Python |
|---|
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167 | @dataclass(frozen=True)
class ASICFlowBundle:
"""Generated ASIC flow files plus the evidence manifest path."""
output_dir: str
manifest_path: str
file_paths: Dict[str, str]
pdk_resolution: PDKResolution
estimate: DesignEstimate
def to_dict(self) -> Dict[str, Any]:
"""Serialise bundle paths, PDK resolution, and screening estimate."""
return {
"output_dir": self.output_dir,
"manifest_path": self.manifest_path,
"file_paths": dict(self.file_paths),
"pdk_resolution": {
"pdk": _pdk_to_manifest(self.pdk_resolution.pdk),
"files": asdict(self.pdk_resolution.files),
"missing_required": list(self.pdk_resolution.missing_required),
"missing_optional": list(self.pdk_resolution.missing_optional),
"usable_for_synthesis": self.pdk_resolution.usable_for_synthesis,
"usable_for_signoff": self.pdk_resolution.usable_for_signoff,
},
"estimate": asdict(self.estimate),
}
|
to_dict()
Serialise bundle paths, PDK resolution, and screening estimate.
Source code in src/sc_neurocore/asic_flow/flow.py
| Python |
|---|
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167 | def to_dict(self) -> Dict[str, Any]:
"""Serialise bundle paths, PDK resolution, and screening estimate."""
return {
"output_dir": self.output_dir,
"manifest_path": self.manifest_path,
"file_paths": dict(self.file_paths),
"pdk_resolution": {
"pdk": _pdk_to_manifest(self.pdk_resolution.pdk),
"files": asdict(self.pdk_resolution.files),
"missing_required": list(self.pdk_resolution.missing_required),
"missing_optional": list(self.pdk_resolution.missing_optional),
"usable_for_synthesis": self.pdk_resolution.usable_for_synthesis,
"usable_for_signoff": self.pdk_resolution.usable_for_signoff,
},
"estimate": asdict(self.estimate),
}
|
ASICFlowGenerator
Top-level generator for the complete ASIC tape-out pipeline.
Source code in src/sc_neurocore/asic_flow/flow.py
| Python |
|---|
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139 | class ASICFlowGenerator:
"""Top-level generator for the complete ASIC tape-out pipeline."""
def generate(
self,
pdk: PDKConfig,
design: DesignParams,
) -> ASICFlowOutput:
"""Generate all deterministic decks for one PDK/design pair.
Parameters
----------
pdk:
Resolved process configuration used by every deck.
design:
Logical and physical design parameters.
Returns
-------
ASICFlowOutput
Nine generated files without executing external EDA tools.
"""
synth = SynthesisGenerator.generate(pdk, design)
sdc = SDCGenerator.generate(pdk, design)
fp = FloorplanGenerator.generate(pdk, design)
pnr = PlaceRouteGenerator.generate(pdk, design)
sta = SignoffGenerator.generate_sta_script(pdk, design)
drc = SignoffGenerator.generate_drc_script(pdk, design)
lvs = SignoffGenerator.generate_lvs_script(pdk, design)
gdsii = GDSIIExporter.generate(pdk, design)
makefile = self._generate_makefile(design)
filelist = list(
ASICFlowOutput(synth, sdc, fp, pnr, sta, drc, lvs, gdsii, makefile, []).to_dict().keys()
)
return ASICFlowOutput(synth, sdc, fp, pnr, sta, drc, lvs, gdsii, makefile, filelist)
def _generate_makefile(self, design: DesignParams) -> str:
return textwrap.dedent(f"""\
# SC-NeuroCore ASIC Flow — Makefile
# Usage: make all
TOP = {design.top_module}
.PHONY: all synth floorplan pnr sta drc lvs gdsii clean
all: synth floorplan pnr sta drc lvs gdsii
synth:
\tyosys -c synth.tcl 2>&1 | tee logs/synth.log
floorplan: synth
\topenroad -exit floorplan.tcl 2>&1 | tee logs/floorplan.log
pnr: floorplan
\topenroad -exit pnr.tcl 2>&1 | tee logs/pnr.log
sta: pnr
\tsta sta.tcl 2>&1 | tee logs/sta.log
drc: gdsii
\tpython3 drc_check.py 2>&1 | tee logs/drc.log
lvs: pnr
\tbash lvs_check.sh 2>&1 | tee logs/lvs.log
gdsii: pnr
\tbash gdsii_export.sh 2>&1 | tee logs/gdsii.log
clean:
\trm -rf synth_$(TOP).* $(TOP)_final.* $(TOP).gds logs/
""")
|
generate(pdk, design)
Generate all deterministic decks for one PDK/design pair.
Parameters
pdk:
Resolved process configuration used by every deck.
design:
Logical and physical design parameters.
Returns
ASICFlowOutput
Nine generated files without executing external EDA tools.
Source code in src/sc_neurocore/asic_flow/flow.py
| Python |
|---|
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103 | def generate(
self,
pdk: PDKConfig,
design: DesignParams,
) -> ASICFlowOutput:
"""Generate all deterministic decks for one PDK/design pair.
Parameters
----------
pdk:
Resolved process configuration used by every deck.
design:
Logical and physical design parameters.
Returns
-------
ASICFlowOutput
Nine generated files without executing external EDA tools.
"""
synth = SynthesisGenerator.generate(pdk, design)
sdc = SDCGenerator.generate(pdk, design)
fp = FloorplanGenerator.generate(pdk, design)
pnr = PlaceRouteGenerator.generate(pdk, design)
sta = SignoffGenerator.generate_sta_script(pdk, design)
drc = SignoffGenerator.generate_drc_script(pdk, design)
lvs = SignoffGenerator.generate_lvs_script(pdk, design)
gdsii = GDSIIExporter.generate(pdk, design)
makefile = self._generate_makefile(design)
filelist = list(
ASICFlowOutput(synth, sdc, fp, pnr, sta, drc, lvs, gdsii, makefile, []).to_dict().keys()
)
return ASICFlowOutput(synth, sdc, fp, pnr, sta, drc, lvs, gdsii, makefile, filelist)
|
ASICFlowOutput
dataclass
Complete output of the ASIC tape-out flow.
Source code in src/sc_neurocore/asic_flow/flow.py
| Python |
|---|
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64 | @dataclass
class ASICFlowOutput:
"""Complete output of the ASIC tape-out flow."""
synth_tcl: str
sdc: str
floorplan_tcl: str
pnr_tcl: str
sta_tcl: str
drc_script: str
lvs_script: str
gdsii_script: str
makefile: str
filelist: List[str]
def to_dict(self) -> Dict[str, str]:
"""Map canonical bundle filenames to their generated contents."""
return {
"synth.tcl": self.synth_tcl,
"constraints.sdc": self.sdc,
"floorplan.tcl": self.floorplan_tcl,
"pnr.tcl": self.pnr_tcl,
"sta.tcl": self.sta_tcl,
"drc_check.py": self.drc_script,
"lvs_check.sh": self.lvs_script,
"gdsii_export.sh": self.gdsii_script,
"Makefile": self.makefile,
}
|
to_dict()
Map canonical bundle filenames to their generated contents.
Source code in src/sc_neurocore/asic_flow/flow.py
| Python |
|---|
52
53
54
55
56
57
58
59
60
61
62
63
64 | def to_dict(self) -> Dict[str, str]:
"""Map canonical bundle filenames to their generated contents."""
return {
"synth.tcl": self.synth_tcl,
"constraints.sdc": self.sdc,
"floorplan.tcl": self.floorplan_tcl,
"pnr.tcl": self.pnr_tcl,
"sta.tcl": self.sta_tcl,
"drc_check.py": self.drc_script,
"lvs_check.sh": self.lvs_script,
"gdsii_export.sh": self.gdsii_script,
"Makefile": self.makefile,
}
|
BlockConfig
dataclass
One block in a hierarchical ASIC flow.
Source code in src/sc_neurocore/asic_flow/hierarchy.py
| Python |
|---|
| @dataclass
class BlockConfig:
"""One block in a hierarchical ASIC flow."""
name: str
design: DesignParams
is_hard_macro: bool = False
abstract_lef: str = ""
|
HierarchicalFlow
dataclass
Multi-block ASIC flow with per-block synthesis + top integration.
Source code in src/sc_neurocore/asic_flow/hierarchy.py
| Python |
|---|
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63 | @dataclass
class HierarchicalFlow:
"""Multi-block ASIC flow with per-block synthesis + top integration."""
top_design: DesignParams
blocks: List[BlockConfig] = field(default_factory=list)
def add_block(self, block: BlockConfig) -> None:
"""Append one logical or hard-macro block to the flow."""
self.blocks.append(block)
def block_names(self) -> List[str]:
"""Return block names in deterministic insertion order."""
return [b.name for b in self.blocks]
def generate_block_scripts(self, pdk: PDKConfig) -> Dict[str, str]:
"""Generate one Yosys synthesis script per configured block."""
gen = ASICFlowGenerator()
result = {}
for block in self.blocks:
output = gen.generate(pdk, block.design)
result[block.name] = output.synth_tcl
return result
def generate_top_integration(self, pdk: PDKConfig) -> str:
"""Render top-level netlist linkage and hard-macro LEF reads."""
lines = [f"# Hierarchical integration for {self.top_design.top_module}"]
for block in self.blocks:
if block.is_hard_macro and block.abstract_lef:
lines.append(f"read_lef {block.abstract_lef} ;# macro: {block.name}")
lines.append(f"read_verilog synth_{self.top_design.top_module}.v")
lines.append(f"link_design {self.top_design.top_module}")
return "\n".join(lines) + "\n"
|
add_block(block)
Append one logical or hard-macro block to the flow.
Source code in src/sc_neurocore/asic_flow/hierarchy.py
| Python |
|---|
| def add_block(self, block: BlockConfig) -> None:
"""Append one logical or hard-macro block to the flow."""
self.blocks.append(block)
|
block_names()
Return block names in deterministic insertion order.
Source code in src/sc_neurocore/asic_flow/hierarchy.py
| Python |
|---|
| def block_names(self) -> List[str]:
"""Return block names in deterministic insertion order."""
return [b.name for b in self.blocks]
|
generate_block_scripts(pdk)
Generate one Yosys synthesis script per configured block.
Source code in src/sc_neurocore/asic_flow/hierarchy.py
| Python |
|---|
| def generate_block_scripts(self, pdk: PDKConfig) -> Dict[str, str]:
"""Generate one Yosys synthesis script per configured block."""
gen = ASICFlowGenerator()
result = {}
for block in self.blocks:
output = gen.generate(pdk, block.design)
result[block.name] = output.synth_tcl
return result
|
generate_top_integration(pdk)
Render top-level netlist linkage and hard-macro LEF reads.
Source code in src/sc_neurocore/asic_flow/hierarchy.py
| Python |
|---|
55
56
57
58
59
60
61
62
63 | def generate_top_integration(self, pdk: PDKConfig) -> str:
"""Render top-level netlist linkage and hard-macro LEF reads."""
lines = [f"# Hierarchical integration for {self.top_design.top_module}"]
for block in self.blocks:
if block.is_hard_macro and block.abstract_lef:
lines.append(f"read_lef {block.abstract_lef} ;# macro: {block.name}")
lines.append(f"read_verilog synth_{self.top_design.top_module}.v")
lines.append(f"link_design {self.top_design.top_module}")
return "\n".join(lines) + "\n"
|
OpenSourcePDKResolver
Resolve Sky130/GF180 file locations without requiring OpenLane at import time.
Source code in src/sc_neurocore/asic_flow/pdk.py
| Python |
|---|
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256 | class OpenSourcePDKResolver:
"""Resolve Sky130/GF180 file locations without requiring OpenLane at import time."""
@staticmethod
def resolve(
pdk: PDKConfig,
pdk_root: Optional[str] = None,
require_existing: bool = False,
) -> PDKResolution:
"""Bind ``$PDK_ROOT`` and report missing PDK artefacts.
Parameters
----------
pdk:
PDK preset or custom configuration.
pdk_root:
Explicit PDK root. If absent, ``PDK_ROOT`` then ``PDKPATH`` are used.
require_existing:
When true, missing required files are reported as blockers. When false,
paths are still resolved so generated flow decks are deterministic.
"""
root = pdk_root or os.environ.get("PDK_ROOT") or os.environ.get("PDKPATH") or "$PDK_ROOT"
resolved_pdk = pdk.with_pdk_root(root)
files = OpenSourcePDKResolver._file_manifest(resolved_pdk)
missing_required: Tuple[str, ...] = ()
missing_optional: Tuple[str, ...] = ()
if require_existing:
missing_required = tuple(
name for name, path in files.required_paths().items() if not Path(path).exists()
)
missing_optional = tuple(
name
for name, path in files.optional_paths().items()
if path and not Path(path).exists()
)
return PDKResolution(resolved_pdk, files, missing_required, missing_optional)
@staticmethod
def _file_manifest(pdk: PDKConfig) -> ResolvedPDKFiles:
if pdk.pdk_type == PDKType.SKY130:
root = OpenSourcePDKResolver._pdk_root_from_path(pdk.liberty_file, "sky130A")
return ResolvedPDKFiles(
liberty_file=pdk.liberty_file,
lef_file=pdk.lef_file,
tech_lef=pdk.tech_lef,
setup_tcl=f"{root}/sky130A/libs.tech/netgen/sky130A_setup.tcl",
drc_deck=f"{root}/sky130A/libs.tech/klayout/drc/sky130.lydrc",
lvs_setup=f"{root}/sky130A/libs.tech/netgen/sky130A_setup.tcl",
)
if pdk.pdk_type == PDKType.GF180MCU:
root = OpenSourcePDKResolver._pdk_root_from_path(pdk.liberty_file, "gf180mcuD")
return ResolvedPDKFiles(
liberty_file=pdk.liberty_file,
lef_file=pdk.lef_file,
tech_lef=pdk.tech_lef,
setup_tcl=f"{root}/gf180mcuD/libs.tech/netgen/gf180mcuD_setup.tcl",
drc_deck=f"{root}/gf180mcuD/libs.tech/klayout/drc/gf180mcu.drc",
lvs_setup=f"{root}/gf180mcuD/libs.tech/netgen/gf180mcuD_setup.tcl",
)
return ResolvedPDKFiles(
liberty_file=pdk.liberty_file,
lef_file=pdk.lef_file,
tech_lef=pdk.tech_lef,
)
@staticmethod
def _pdk_root_from_path(path: str, marker: str) -> str:
before, separator, _after = path.partition(f"/{marker}/")
return before if separator else "$PDK_ROOT"
|
resolve(pdk, pdk_root=None, require_existing=False)
staticmethod
Bind $PDK_ROOT and report missing PDK artefacts.
Parameters
pdk:
PDK preset or custom configuration.
pdk_root:
Explicit PDK root. If absent, PDK_ROOT then PDKPATH are used.
require_existing:
When true, missing required files are reported as blockers. When false,
paths are still resolved so generated flow decks are deterministic.
Source code in src/sc_neurocore/asic_flow/pdk.py
| Python |
|---|
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223 | @staticmethod
def resolve(
pdk: PDKConfig,
pdk_root: Optional[str] = None,
require_existing: bool = False,
) -> PDKResolution:
"""Bind ``$PDK_ROOT`` and report missing PDK artefacts.
Parameters
----------
pdk:
PDK preset or custom configuration.
pdk_root:
Explicit PDK root. If absent, ``PDK_ROOT`` then ``PDKPATH`` are used.
require_existing:
When true, missing required files are reported as blockers. When false,
paths are still resolved so generated flow decks are deterministic.
"""
root = pdk_root or os.environ.get("PDK_ROOT") or os.environ.get("PDKPATH") or "$PDK_ROOT"
resolved_pdk = pdk.with_pdk_root(root)
files = OpenSourcePDKResolver._file_manifest(resolved_pdk)
missing_required: Tuple[str, ...] = ()
missing_optional: Tuple[str, ...] = ()
if require_existing:
missing_required = tuple(
name for name, path in files.required_paths().items() if not Path(path).exists()
)
missing_optional = tuple(
name
for name, path in files.optional_paths().items()
if path and not Path(path).exists()
)
return PDKResolution(resolved_pdk, files, missing_required, missing_optional)
|
PDKConfig
dataclass
Process Design Kit configuration.
Source code in src/sc_neurocore/asic_flow/pdk.py
| Python |
|---|
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135 | @dataclass
class PDKConfig:
"""Process Design Kit configuration."""
pdk_type: PDKType = PDKType.SKY130
liberty_file: str = ""
lef_file: str = ""
tech_lef: str = ""
cell_prefix: str = "sky130_fd_sc_hd__"
clock_period_ns: float = 10.0
voltage_v: float = 1.8
temperature_c: float = 25.0
corner: str = "tt"
metal_layers: int = 5
min_feature_nm: int = 130
@classmethod
def from_pdk_type(cls, pdk: PDKType) -> PDKConfig:
"""Construct the maintained preset for a process family.
Parameters
----------
pdk:
Process family whose nominal files, voltage, and geometry are
required.
Returns
-------
PDKConfig
Configuration containing deterministic ``$PDK_ROOT`` templates.
"""
presets: Dict[PDKType, Dict[str, Any]] = {
PDKType.SKY130: dict(
liberty_file="$PDK_ROOT/sky130A/libs.ref/sky130_fd_sc_hd/lib/sky130_fd_sc_hd__tt_025C_1v80.lib",
lef_file="$PDK_ROOT/sky130A/libs.ref/sky130_fd_sc_hd/lef/sky130_fd_sc_hd.lef",
tech_lef="$PDK_ROOT/sky130A/libs.ref/sky130_fd_sc_hd/techlef/sky130_fd_sc_hd__nom.tlef",
cell_prefix="sky130_fd_sc_hd__",
clock_period_ns=10.0,
voltage_v=1.8,
metal_layers=5,
min_feature_nm=130,
),
PDKType.GF180MCU: dict(
liberty_file="$PDK_ROOT/gf180mcuD/libs.ref/gf180mcu_fd_sc_mcu7t5v0/lib/gf180mcu_fd_sc_mcu7t5v0__tt_025C_3v30.lib",
lef_file="$PDK_ROOT/gf180mcuD/libs.ref/gf180mcu_fd_sc_mcu7t5v0/lef/gf180mcu_fd_sc_mcu7t5v0.lef",
tech_lef="$PDK_ROOT/gf180mcuD/libs.ref/gf180mcu_fd_sc_mcu7t5v0/techlef/gf180mcu_fd_sc_mcu7t5v0__nom.tlef",
cell_prefix="gf180mcu_fd_sc_mcu7t5v0__",
clock_period_ns=15.0,
voltage_v=3.3,
metal_layers=6,
min_feature_nm=180,
),
PDKType.TSMC28: dict(
liberty_file="$PDK_ROOT/tsmc28/tcbn28hpcplusbwp7t30p140_110a/TSMCHOME/digital/Front_End/timing_power_noise/NLDM/tcbn28hpcplusbwp7t30p140ssgnp0p81v125c.lib",
lef_file="$PDK_ROOT/tsmc28/lef/tcbn28hpcplusbwp7t30p140.lef",
tech_lef="$PDK_ROOT/tsmc28/lef/HiPe_M10.tlef",
cell_prefix="TSMC_",
clock_period_ns=2.0,
voltage_v=0.9,
metal_layers=10,
min_feature_nm=28,
),
PDKType.INTEL16: dict(
liberty_file="$PDK_ROOT/intel16/lib/intel16_sc.lib",
lef_file="$PDK_ROOT/intel16/lef/intel16_sc.lef",
tech_lef="$PDK_ROOT/intel16/lef/intel16.tlef",
cell_prefix="INTEL16_",
clock_period_ns=1.5,
voltage_v=0.8,
metal_layers=12,
min_feature_nm=16,
),
PDKType.CUSTOM: dict(
liberty_file="",
lef_file="",
tech_lef="",
cell_prefix="",
clock_period_ns=10.0,
voltage_v=1.8,
metal_layers=5,
min_feature_nm=130,
),
}
return cls(pdk_type=pdk, **presets[pdk])
@property
def is_open_source(self) -> bool:
"""Return whether the process has a maintained open-source file map."""
return self.pdk_type in (PDKType.SKY130, PDKType.GF180MCU)
def with_pdk_root(self, pdk_root: str) -> PDKConfig:
"""Return a copy with ``$PDK_ROOT`` variables bound to ``pdk_root``."""
root = str(Path(pdk_root).expanduser())
return PDKConfig(
pdk_type=self.pdk_type,
liberty_file=self.liberty_file.replace("$PDK_ROOT", root),
lef_file=self.lef_file.replace("$PDK_ROOT", root),
tech_lef=self.tech_lef.replace("$PDK_ROOT", root),
cell_prefix=self.cell_prefix,
clock_period_ns=self.clock_period_ns,
voltage_v=self.voltage_v,
temperature_c=self.temperature_c,
corner=self.corner,
metal_layers=self.metal_layers,
min_feature_nm=self.min_feature_nm,
)
|
is_open_source
property
Return whether the process has a maintained open-source file map.
from_pdk_type(pdk)
classmethod
Construct the maintained preset for a process family.
Parameters
pdk:
Process family whose nominal files, voltage, and geometry are
required.
Returns
PDKConfig
Configuration containing deterministic $PDK_ROOT templates.
Source code in src/sc_neurocore/asic_flow/pdk.py
| Python |
|---|
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113 | @classmethod
def from_pdk_type(cls, pdk: PDKType) -> PDKConfig:
"""Construct the maintained preset for a process family.
Parameters
----------
pdk:
Process family whose nominal files, voltage, and geometry are
required.
Returns
-------
PDKConfig
Configuration containing deterministic ``$PDK_ROOT`` templates.
"""
presets: Dict[PDKType, Dict[str, Any]] = {
PDKType.SKY130: dict(
liberty_file="$PDK_ROOT/sky130A/libs.ref/sky130_fd_sc_hd/lib/sky130_fd_sc_hd__tt_025C_1v80.lib",
lef_file="$PDK_ROOT/sky130A/libs.ref/sky130_fd_sc_hd/lef/sky130_fd_sc_hd.lef",
tech_lef="$PDK_ROOT/sky130A/libs.ref/sky130_fd_sc_hd/techlef/sky130_fd_sc_hd__nom.tlef",
cell_prefix="sky130_fd_sc_hd__",
clock_period_ns=10.0,
voltage_v=1.8,
metal_layers=5,
min_feature_nm=130,
),
PDKType.GF180MCU: dict(
liberty_file="$PDK_ROOT/gf180mcuD/libs.ref/gf180mcu_fd_sc_mcu7t5v0/lib/gf180mcu_fd_sc_mcu7t5v0__tt_025C_3v30.lib",
lef_file="$PDK_ROOT/gf180mcuD/libs.ref/gf180mcu_fd_sc_mcu7t5v0/lef/gf180mcu_fd_sc_mcu7t5v0.lef",
tech_lef="$PDK_ROOT/gf180mcuD/libs.ref/gf180mcu_fd_sc_mcu7t5v0/techlef/gf180mcu_fd_sc_mcu7t5v0__nom.tlef",
cell_prefix="gf180mcu_fd_sc_mcu7t5v0__",
clock_period_ns=15.0,
voltage_v=3.3,
metal_layers=6,
min_feature_nm=180,
),
PDKType.TSMC28: dict(
liberty_file="$PDK_ROOT/tsmc28/tcbn28hpcplusbwp7t30p140_110a/TSMCHOME/digital/Front_End/timing_power_noise/NLDM/tcbn28hpcplusbwp7t30p140ssgnp0p81v125c.lib",
lef_file="$PDK_ROOT/tsmc28/lef/tcbn28hpcplusbwp7t30p140.lef",
tech_lef="$PDK_ROOT/tsmc28/lef/HiPe_M10.tlef",
cell_prefix="TSMC_",
clock_period_ns=2.0,
voltage_v=0.9,
metal_layers=10,
min_feature_nm=28,
),
PDKType.INTEL16: dict(
liberty_file="$PDK_ROOT/intel16/lib/intel16_sc.lib",
lef_file="$PDK_ROOT/intel16/lef/intel16_sc.lef",
tech_lef="$PDK_ROOT/intel16/lef/intel16.tlef",
cell_prefix="INTEL16_",
clock_period_ns=1.5,
voltage_v=0.8,
metal_layers=12,
min_feature_nm=16,
),
PDKType.CUSTOM: dict(
liberty_file="",
lef_file="",
tech_lef="",
cell_prefix="",
clock_period_ns=10.0,
voltage_v=1.8,
metal_layers=5,
min_feature_nm=130,
),
}
return cls(pdk_type=pdk, **presets[pdk])
|
with_pdk_root(pdk_root)
Return a copy with $PDK_ROOT variables bound to pdk_root.
Source code in src/sc_neurocore/asic_flow/pdk.py
| Python |
|---|
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135 | def with_pdk_root(self, pdk_root: str) -> PDKConfig:
"""Return a copy with ``$PDK_ROOT`` variables bound to ``pdk_root``."""
root = str(Path(pdk_root).expanduser())
return PDKConfig(
pdk_type=self.pdk_type,
liberty_file=self.liberty_file.replace("$PDK_ROOT", root),
lef_file=self.lef_file.replace("$PDK_ROOT", root),
tech_lef=self.tech_lef.replace("$PDK_ROOT", root),
cell_prefix=self.cell_prefix,
clock_period_ns=self.clock_period_ns,
voltage_v=self.voltage_v,
temperature_c=self.temperature_c,
corner=self.corner,
metal_layers=self.metal_layers,
min_feature_nm=self.min_feature_nm,
)
|
PDKResolution
dataclass
Outcome of resolving a PDK against the local filesystem.
Source code in src/sc_neurocore/asic_flow/pdk.py
| Python |
|---|
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183 | @dataclass(frozen=True)
class PDKResolution:
"""Outcome of resolving a PDK against the local filesystem."""
pdk: PDKConfig
files: ResolvedPDKFiles
missing_required: Tuple[str, ...] = ()
missing_optional: Tuple[str, ...] = ()
@property
def usable_for_synthesis(self) -> bool:
"""Return whether every synthesis-required PDK file was found."""
return not self.missing_required
@property
def usable_for_signoff(self) -> bool:
"""Return whether required and optional signoff files were found."""
return self.usable_for_synthesis and not self.missing_optional
|
usable_for_synthesis
property
Return whether every synthesis-required PDK file was found.
usable_for_signoff
property
Return whether required and optional signoff files were found.
PDKType
Bases: Enum
Process-design-kit families supported by the deck templates.
Source code in src/sc_neurocore/asic_flow/pdk.py
| Python |
|---|
| class PDKType(Enum):
"""Process-design-kit families supported by the deck templates."""
SKY130 = "sky130"
GF180MCU = "gf180mcu"
TSMC28 = "tsmc28"
INTEL16 = "intel16"
CUSTOM = "custom"
|
PDKValidationResult
dataclass
Result of PDK sanity check.
Source code in src/sc_neurocore/asic_flow/pdk.py
| Python |
|---|
259
260
261
262
263
264
265 | @dataclass
class PDKValidationResult:
"""Result of PDK sanity check."""
valid: bool
errors: List[str] = field(default_factory=list)
warnings: List[str] = field(default_factory=list)
|
ResolvedPDKFiles
dataclass
Resolved file paths required by the open-source ASIC flow.
Source code in src/sc_neurocore/asic_flow/pdk.py
| Python |
|---|
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163 | @dataclass(frozen=True)
class ResolvedPDKFiles:
"""Resolved file paths required by the open-source ASIC flow."""
liberty_file: str
lef_file: str
tech_lef: str
setup_tcl: str = ""
drc_deck: str = ""
lvs_setup: str = ""
def required_paths(self) -> Dict[str, str]:
"""Return the Liberty, cell-LEF, and technology-LEF paths."""
return {
"liberty_file": self.liberty_file,
"lef_file": self.lef_file,
"tech_lef": self.tech_lef,
}
def optional_paths(self) -> Dict[str, str]:
"""Return optional setup, DRC-deck, and LVS-setup paths."""
return {
"setup_tcl": self.setup_tcl,
"drc_deck": self.drc_deck,
"lvs_setup": self.lvs_setup,
}
|
required_paths()
Return the Liberty, cell-LEF, and technology-LEF paths.
Source code in src/sc_neurocore/asic_flow/pdk.py
| Python |
|---|
149
150
151
152
153
154
155 | def required_paths(self) -> Dict[str, str]:
"""Return the Liberty, cell-LEF, and technology-LEF paths."""
return {
"liberty_file": self.liberty_file,
"lef_file": self.lef_file,
"tech_lef": self.tech_lef,
}
|
optional_paths()
Return optional setup, DRC-deck, and LVS-setup paths.
Source code in src/sc_neurocore/asic_flow/pdk.py
| Python |
|---|
157
158
159
160
161
162
163 | def optional_paths(self) -> Dict[str, str]:
"""Return optional setup, DRC-deck, and LVS-setup paths."""
return {
"setup_tcl": self.setup_tcl,
"drc_deck": self.drc_deck,
"lvs_setup": self.lvs_setup,
}
|
TapeOutChecklist
dataclass
Go/no-go checklist for ASIC tape-out.
Source code in src/sc_neurocore/asic_flow/readiness.py
| Python |
|---|
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78 | @dataclass
class TapeOutChecklist:
"""Go/no-go checklist for ASIC tape-out."""
synthesis_clean: bool = False
timing_met: bool = False
power_within_budget: bool = False
area_within_limit: bool = False
drc_clean: bool = False
lvs_clean: bool = False
formal_equiv_pass: bool = False
cdc_clean: bool = False
ir_drop_ok: bool = False
esd_reviewed: bool = False
@property
def readiness_score(self) -> float:
"""Return the fraction of the ten required checks that passed."""
checks = [
self.synthesis_clean,
self.timing_met,
self.power_within_budget,
self.area_within_limit,
self.drc_clean,
self.lvs_clean,
self.formal_equiv_pass,
self.cdc_clean,
self.ir_drop_ok,
self.esd_reviewed,
]
return sum(1 for c in checks if c) / len(checks)
@property
def is_tape_out_ready(self) -> bool:
"""Return whether all ten readiness checks passed."""
return self.readiness_score == 1.0
def failing_checks(self) -> List[str]:
"""Return stable field names for every incomplete readiness check."""
names = [
"synthesis_clean",
"timing_met",
"power_within_budget",
"area_within_limit",
"drc_clean",
"lvs_clean",
"formal_equiv_pass",
"cdc_clean",
"ir_drop_ok",
"esd_reviewed",
]
return [n for n in names if not getattr(self, n)]
def from_signoff(self, summary: SignoffSummary) -> None:
"""Populate from a signoff summary."""
self.timing_met = summary.timing.passed
self.power_within_budget = summary.power.passed
self.area_within_limit = summary.area.passed
self.drc_clean = summary.drc_clean
self.lvs_clean = summary.lvs_match
|
readiness_score
property
Return the fraction of the ten required checks that passed.
is_tape_out_ready
property
Return whether all ten readiness checks passed.
failing_checks()
Return stable field names for every incomplete readiness check.
Source code in src/sc_neurocore/asic_flow/readiness.py
| Python |
|---|
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 | def failing_checks(self) -> List[str]:
"""Return stable field names for every incomplete readiness check."""
names = [
"synthesis_clean",
"timing_met",
"power_within_budget",
"area_within_limit",
"drc_clean",
"lvs_clean",
"formal_equiv_pass",
"cdc_clean",
"ir_drop_ok",
"esd_reviewed",
]
return [n for n in names if not getattr(self, n)]
|
from_signoff(summary)
Populate from a signoff summary.
Source code in src/sc_neurocore/asic_flow/readiness.py
| Python |
|---|
| def from_signoff(self, summary: SignoffSummary) -> None:
"""Populate from a signoff summary."""
self.timing_met = summary.timing.passed
self.power_within_budget = summary.power.passed
self.area_within_limit = summary.area.passed
self.drc_clean = summary.drc_clean
self.lvs_clean = summary.lvs_match
|
CornerType
Bases: Enum
Process-corner combinations used by multi-corner timing analysis.
Source code in src/sc_neurocore/asic_flow/signoff.py
| Python |
|---|
112
113
114
115
116
117
118
119 | class CornerType(Enum):
"""Process-corner combinations used by multi-corner timing analysis."""
TT = "tt" # typical
FF = "ff" # fast-fast
SS = "ss" # slow-slow
SF = "sf" # slow-fast
FS = "fs"
|
DRCViolation
dataclass
One DRC rule violation.
Source code in src/sc_neurocore/asic_flow/signoff.py
| Python |
|---|
219
220
221
222
223
224
225 | @dataclass
class DRCViolation:
"""One DRC rule violation."""
rule_name: str
count: int
severity: str = "error"
|
MultiCornerAnalysis
dataclass
Generates multi-corner STA scripts for all PVT corners.
Source code in src/sc_neurocore/asic_flow/signoff.py
| Python |
|---|
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182 | @dataclass
class MultiCornerAnalysis:
"""Generates multi-corner STA scripts for all PVT corners."""
@staticmethod
def generate(
pdk: PDKConfig, design: DesignParams, corners: Optional[List[PVTCorner]] = None
) -> str:
"""Render one OpenSTA analysis section per selected PVT corner."""
if corners is None:
corners = DEFAULT_CORNERS
lines = [f"# Multi-Corner STA for {design.top_module}"]
for c in corners:
lib = (
pdk.liberty_file.replace("_tt_025C_1v80", c.liberty_suffix)
if c.liberty_suffix
else pdk.liberty_file
)
lines.append(f"\n# Corner: {c.label}")
lines.append(f"read_liberty {lib}")
lines.append(f"read_verilog {design.top_module}_final.v")
lines.append(f"link_design {design.top_module}")
lines.append(f"read_sdc constraints_{design.top_module}.sdc")
lines.append("set_operating_conditions -analysis_type on_chip_variation")
lines.append("report_checks -path_delay min_max -digits 4")
lines.append("report_tns")
lines.append("report_wns")
return "\n".join(lines) + "\n"
@staticmethod
def worst_slack(per_corner_wns: Dict[str, float]) -> Tuple[str, float]:
"""Return the corner with the smallest worst negative slack."""
if not per_corner_wns:
return ("none", 0.0)
worst = min(per_corner_wns.items(), key=lambda kv: kv[1])
return worst
|
generate(pdk, design, corners=None)
staticmethod
Render one OpenSTA analysis section per selected PVT corner.
Source code in src/sc_neurocore/asic_flow/signoff.py
| Python |
|---|
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174 | @staticmethod
def generate(
pdk: PDKConfig, design: DesignParams, corners: Optional[List[PVTCorner]] = None
) -> str:
"""Render one OpenSTA analysis section per selected PVT corner."""
if corners is None:
corners = DEFAULT_CORNERS
lines = [f"# Multi-Corner STA for {design.top_module}"]
for c in corners:
lib = (
pdk.liberty_file.replace("_tt_025C_1v80", c.liberty_suffix)
if c.liberty_suffix
else pdk.liberty_file
)
lines.append(f"\n# Corner: {c.label}")
lines.append(f"read_liberty {lib}")
lines.append(f"read_verilog {design.top_module}_final.v")
lines.append(f"link_design {design.top_module}")
lines.append(f"read_sdc constraints_{design.top_module}.sdc")
lines.append("set_operating_conditions -analysis_type on_chip_variation")
lines.append("report_checks -path_delay min_max -digits 4")
lines.append("report_tns")
lines.append("report_wns")
return "\n".join(lines) + "\n"
|
worst_slack(per_corner_wns)
staticmethod
Return the corner with the smallest worst negative slack.
Source code in src/sc_neurocore/asic_flow/signoff.py
| Python |
|---|
176
177
178
179
180
181
182 | @staticmethod
def worst_slack(per_corner_wns: Dict[str, float]) -> Tuple[str, float]:
"""Return the corner with the smallest worst negative slack."""
if not per_corner_wns:
return ("none", 0.0)
worst = min(per_corner_wns.items(), key=lambda kv: kv[1])
return worst
|
OCVConfig
dataclass
On-Chip Variation derating factors.
Source code in src/sc_neurocore/asic_flow/signoff.py
| Python |
|---|
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216 | @dataclass
class OCVConfig:
"""On-Chip Variation derating factors."""
data_cell_early: float = 0.95
data_cell_late: float = 1.05
data_net_early: float = 0.95
data_net_late: float = 1.05
clock_cell_early: float = 0.97
clock_cell_late: float = 1.03
def generate_sdc_fragment(self) -> str:
"""Render early and late cell/net derates as an SDC fragment."""
return textwrap.dedent(f"""\
# OCV Derating
set_timing_derate -early {self.data_cell_early:.3f} -cell_delay [all_inputs]
set_timing_derate -late {self.data_cell_late:.3f} -cell_delay [all_inputs]
set_timing_derate -early {self.data_net_early:.3f} -net_delay [all_inputs]
set_timing_derate -late {self.data_net_late:.3f} -net_delay [all_inputs]
""")
@classmethod
def conservative(cls) -> OCVConfig:
"""Return wider early/late derates for screening runs."""
return cls(
data_cell_early=0.93,
data_cell_late=1.07,
data_net_early=0.93,
data_net_late=1.07,
clock_cell_early=0.95,
clock_cell_late=1.05,
)
|
generate_sdc_fragment()
Render early and late cell/net derates as an SDC fragment.
Source code in src/sc_neurocore/asic_flow/signoff.py
| Python |
|---|
196
197
198
199
200
201
202
203
204 | def generate_sdc_fragment(self) -> str:
"""Render early and late cell/net derates as an SDC fragment."""
return textwrap.dedent(f"""\
# OCV Derating
set_timing_derate -early {self.data_cell_early:.3f} -cell_delay [all_inputs]
set_timing_derate -late {self.data_cell_late:.3f} -cell_delay [all_inputs]
set_timing_derate -early {self.data_net_early:.3f} -net_delay [all_inputs]
set_timing_derate -late {self.data_net_late:.3f} -net_delay [all_inputs]
""")
|
conservative()
classmethod
Return wider early/late derates for screening runs.
Source code in src/sc_neurocore/asic_flow/signoff.py
| Python |
|---|
206
207
208
209
210
211
212
213
214
215
216 | @classmethod
def conservative(cls) -> OCVConfig:
"""Return wider early/late derates for screening runs."""
return cls(
data_cell_early=0.93,
data_cell_late=1.07,
data_net_early=0.93,
data_net_late=1.07,
clock_cell_early=0.95,
clock_cell_late=1.05,
)
|
PVTCorner
dataclass
Process-Voltage-Temperature corner definition.
Source code in src/sc_neurocore/asic_flow/signoff.py
| Python |
|---|
122
123
124
125
126
127
128
129
130
131
132
133
134
135 | @dataclass
class PVTCorner:
"""Process-Voltage-Temperature corner definition."""
corner: CornerType
temperature_c: float
voltage_v: float
liberty_suffix: str = ""
is_signoff: bool = True
@property
def label(self) -> str:
"""Return a stable corner-temperature-voltage label."""
return f"{self.corner.value}_{self.temperature_c:.0f}C_{self.voltage_v:.2f}V"
|
label
property
Return a stable corner-temperature-voltage label.
SignoffCheckResult
dataclass
Result of one signoff check.
Source code in src/sc_neurocore/asic_flow/signoff.py
| Python |
|---|
| @dataclass
class SignoffCheckResult:
"""Result of one signoff check."""
check_name: str
passed: bool
details: str = ""
metric: float = 0.0
|
SignoffGenerator
Generates signoff scripts and evaluates results.
Source code in src/sc_neurocore/asic_flow/signoff.py
| Python |
|---|
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109 | class SignoffGenerator:
"""Generates signoff scripts and evaluates results."""
@staticmethod
def generate_sta_script(pdk: PDKConfig, design: DesignParams) -> str:
"""Generate OpenSTA timing analysis script."""
return textwrap.dedent(f"""\
# SC-NeuroCore STA Signoff — OpenSTA
read_liberty {pdk.liberty_file}
read_verilog {design.top_module}_final.v
link_design {design.top_module}
read_sdc constraints_{design.top_module}.sdc
report_checks -path_delay min_max -format full_clock_expanded \\
-fields {{slew cap input_pins nets}} \\
-digits 4
report_tns
report_wns
report_power
""")
@staticmethod
def generate_drc_script(pdk: PDKConfig, design: DesignParams) -> str:
"""Generate DRC check script (KLayout-based for open PDKs)."""
if pdk.is_open_source:
return textwrap.dedent(f"""\
# SC-NeuroCore DRC — KLayout (open-source PDK)
import klayout.db as db
import klayout.rdb as rdb
layout = db.Layout()
layout.read("{design.top_module}.gds")
# Run DRC deck for {pdk.pdk_type.value}
# drc_deck = "$PDK_ROOT/{pdk.pdk_type.value}/libs.tech/klayout/drc/{pdk.pdk_type.value}.lydrc"
""")
return f"# DRC for {pdk.pdk_type.value}: use vendor-specific tool\n"
@staticmethod
def generate_lvs_script(pdk: PDKConfig, design: DesignParams) -> str:
"""Generate LVS check script."""
if pdk.is_open_source:
return textwrap.dedent(f"""\
# SC-NeuroCore LVS — Netgen (open-source PDK)
netgen -batch lvs \\
"{design.top_module}.spice {design.top_module}" \\
"{design.top_module}_final.v {design.top_module}" \\
$PDK_ROOT/{pdk.pdk_type.value}/libs.tech/netgen/{pdk.pdk_type.value}_setup.tcl \\
lvs_{design.top_module}.log
""")
return f"# LVS for {pdk.pdk_type.value}: use vendor-specific tool\n"
@staticmethod
def evaluate_timing(wns: float, tns: float, clock_period_ns: float) -> SignoffCheckResult:
"""Evaluate timing signoff from worst/total negative slack."""
passed = wns >= 0.0
details = f"WNS={wns:.3f}ns TNS={tns:.3f}ns period={clock_period_ns:.3f}ns"
return SignoffCheckResult("STA", passed, details, wns)
@staticmethod
def evaluate_power(
dynamic_mw: float, leakage_mw: float, budget_mw: float
) -> SignoffCheckResult:
"""Compare dynamic plus leakage power against a milliwatt budget."""
total = dynamic_mw + leakage_mw
passed = total <= budget_mw
details = f"dynamic={dynamic_mw:.3f}mW leakage={leakage_mw:.3f}mW total={total:.3f}mW budget={budget_mw:.3f}mW"
return SignoffCheckResult("Power", passed, details, total)
@staticmethod
def evaluate_area(
cell_count: int, used_area_um2: float, die_area_um2: float
) -> SignoffCheckResult:
"""Compare placed-cell area with the 85 percent utilisation limit."""
util = used_area_um2 / die_area_um2 if die_area_um2 > 0 else 0
passed = util <= 0.85
details = f"cells={cell_count} util={util:.1%} used={used_area_um2:.0f}µm² die={die_area_um2:.0f}µm²"
return SignoffCheckResult("Area", passed, details, util)
|
generate_sta_script(pdk, design)
staticmethod
Generate OpenSTA timing analysis script.
Source code in src/sc_neurocore/asic_flow/signoff.py
| Python |
|---|
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52 | @staticmethod
def generate_sta_script(pdk: PDKConfig, design: DesignParams) -> str:
"""Generate OpenSTA timing analysis script."""
return textwrap.dedent(f"""\
# SC-NeuroCore STA Signoff — OpenSTA
read_liberty {pdk.liberty_file}
read_verilog {design.top_module}_final.v
link_design {design.top_module}
read_sdc constraints_{design.top_module}.sdc
report_checks -path_delay min_max -format full_clock_expanded \\
-fields {{slew cap input_pins nets}} \\
-digits 4
report_tns
report_wns
report_power
""")
|
generate_drc_script(pdk, design)
staticmethod
Generate DRC check script (KLayout-based for open PDKs).
Source code in src/sc_neurocore/asic_flow/signoff.py
| Python |
|---|
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68 | @staticmethod
def generate_drc_script(pdk: PDKConfig, design: DesignParams) -> str:
"""Generate DRC check script (KLayout-based for open PDKs)."""
if pdk.is_open_source:
return textwrap.dedent(f"""\
# SC-NeuroCore DRC — KLayout (open-source PDK)
import klayout.db as db
import klayout.rdb as rdb
layout = db.Layout()
layout.read("{design.top_module}.gds")
# Run DRC deck for {pdk.pdk_type.value}
# drc_deck = "$PDK_ROOT/{pdk.pdk_type.value}/libs.tech/klayout/drc/{pdk.pdk_type.value}.lydrc"
""")
return f"# DRC for {pdk.pdk_type.value}: use vendor-specific tool\n"
|
generate_lvs_script(pdk, design)
staticmethod
Generate LVS check script.
Source code in src/sc_neurocore/asic_flow/signoff.py
| Python |
|---|
70
71
72
73
74
75
76
77
78
79
80
81
82 | @staticmethod
def generate_lvs_script(pdk: PDKConfig, design: DesignParams) -> str:
"""Generate LVS check script."""
if pdk.is_open_source:
return textwrap.dedent(f"""\
# SC-NeuroCore LVS — Netgen (open-source PDK)
netgen -batch lvs \\
"{design.top_module}.spice {design.top_module}" \\
"{design.top_module}_final.v {design.top_module}" \\
$PDK_ROOT/{pdk.pdk_type.value}/libs.tech/netgen/{pdk.pdk_type.value}_setup.tcl \\
lvs_{design.top_module}.log
""")
return f"# LVS for {pdk.pdk_type.value}: use vendor-specific tool\n"
|
evaluate_timing(wns, tns, clock_period_ns)
staticmethod
Evaluate timing signoff from worst/total negative slack.
Source code in src/sc_neurocore/asic_flow/signoff.py
| Python |
|---|
| @staticmethod
def evaluate_timing(wns: float, tns: float, clock_period_ns: float) -> SignoffCheckResult:
"""Evaluate timing signoff from worst/total negative slack."""
passed = wns >= 0.0
details = f"WNS={wns:.3f}ns TNS={tns:.3f}ns period={clock_period_ns:.3f}ns"
return SignoffCheckResult("STA", passed, details, wns)
|
evaluate_power(dynamic_mw, leakage_mw, budget_mw)
staticmethod
Compare dynamic plus leakage power against a milliwatt budget.
Source code in src/sc_neurocore/asic_flow/signoff.py
| Python |
|---|
91
92
93
94
95
96
97
98
99 | @staticmethod
def evaluate_power(
dynamic_mw: float, leakage_mw: float, budget_mw: float
) -> SignoffCheckResult:
"""Compare dynamic plus leakage power against a milliwatt budget."""
total = dynamic_mw + leakage_mw
passed = total <= budget_mw
details = f"dynamic={dynamic_mw:.3f}mW leakage={leakage_mw:.3f}mW total={total:.3f}mW budget={budget_mw:.3f}mW"
return SignoffCheckResult("Power", passed, details, total)
|
evaluate_area(cell_count, used_area_um2, die_area_um2)
staticmethod
Compare placed-cell area with the 85 percent utilisation limit.
Source code in src/sc_neurocore/asic_flow/signoff.py
| Python |
|---|
101
102
103
104
105
106
107
108
109 | @staticmethod
def evaluate_area(
cell_count: int, used_area_um2: float, die_area_um2: float
) -> SignoffCheckResult:
"""Compare placed-cell area with the 85 percent utilisation limit."""
util = used_area_um2 / die_area_um2 if die_area_um2 > 0 else 0
passed = util <= 0.85
details = f"cells={cell_count} util={util:.1%} used={used_area_um2:.0f}µm² die={die_area_um2:.0f}µm²"
return SignoffCheckResult("Area", passed, details, util)
|
SignoffSummary
dataclass
Structured signoff summary with pass/fail per check.
Source code in src/sc_neurocore/asic_flow/signoff.py
| Python |
|---|
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266 | @dataclass
class SignoffSummary:
"""Structured signoff summary with pass/fail per check."""
timing: SignoffCheckResult
power: SignoffCheckResult
area: SignoffCheckResult
drc_violations: List[DRCViolation] = field(default_factory=list)
lvs_match: bool = False
@property
def drc_clean(self) -> bool:
"""Return whether no counted error-severity DRC violation exists."""
return not any(v.severity == "error" and v.count > 0 for v in self.drc_violations)
@property
def all_pass(self) -> bool:
"""Return whether timing, power, area, DRC, and LVS all pass."""
return (
self.timing.passed
and self.power.passed
and self.area.passed
and self.drc_clean
and self.lvs_match
)
def to_dict(self) -> Dict[str, Any]:
"""Serialise the signoff decision and counted DRC violations."""
return {
"timing": {"passed": self.timing.passed, "details": self.timing.details},
"power": {"passed": self.power.passed, "details": self.power.details},
"area": {"passed": self.area.passed, "details": self.area.details},
"drc_clean": self.drc_clean,
"drc_violations": [
{"rule": v.rule_name, "count": v.count} for v in self.drc_violations
],
"lvs_match": self.lvs_match,
"all_pass": self.all_pass,
}
|
drc_clean
property
Return whether no counted error-severity DRC violation exists.
all_pass
property
Return whether timing, power, area, DRC, and LVS all pass.
to_dict()
Serialise the signoff decision and counted DRC violations.
Source code in src/sc_neurocore/asic_flow/signoff.py
| Python |
|---|
254
255
256
257
258
259
260
261
262
263
264
265
266 | def to_dict(self) -> Dict[str, Any]:
"""Serialise the signoff decision and counted DRC violations."""
return {
"timing": {"passed": self.timing.passed, "details": self.timing.details},
"power": {"passed": self.power.passed, "details": self.power.details},
"area": {"passed": self.area.passed, "details": self.area.details},
"drc_clean": self.drc_clean,
"drc_violations": [
{"rule": v.rule_name, "count": v.count} for v in self.drc_violations
],
"lvs_match": self.lvs_match,
"all_pass": self.all_pass,
}
|
generate_asic_flow_bundle(output_dir, *, pdk_type=PDKType.SKY130, design=None, pdk_root=None, require_pdk_files=False, n_neurons=16, n_synapses=256, bitstream_width=256, n_aer_ports=4, formal_evidence_artifacts=None)
Write a complete ASIC flow deck and evidence manifest in one call.
The helper deliberately does not run Yosys/OpenROAD. It materialises the
scripts, resolves the requested PDK paths, records missing artefacts, and
adds a pre-synthesis estimate so Python API users can inspect the bundle
before launching external EDA tools.
Source code in src/sc_neurocore/asic_flow/flow.py
| Python |
|---|
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235 | def generate_asic_flow_bundle(
output_dir: str | Path,
*,
pdk_type: PDKType | str = PDKType.SKY130,
design: Optional[DesignParams] = None,
pdk_root: Optional[str] = None,
require_pdk_files: bool = False,
n_neurons: int = 16,
n_synapses: int = 256,
bitstream_width: int = 256,
n_aer_ports: int = 4,
formal_evidence_artifacts: Optional[List[str]] = None,
) -> ASICFlowBundle:
"""Write a complete ASIC flow deck and evidence manifest in one call.
The helper deliberately does not run Yosys/OpenROAD. It materialises the
scripts, resolves the requested PDK paths, records missing artefacts, and
adds a pre-synthesis estimate so Python API users can inspect the bundle
before launching external EDA tools.
"""
pdk_enum = _normalise_pdk_type(pdk_type)
design = design or DesignParams()
out = Path(output_dir).expanduser()
out.mkdir(parents=True, exist_ok=True)
pdk = PDKConfig.from_pdk_type(pdk_enum)
resolution = OpenSourcePDKResolver.resolve(
pdk,
pdk_root=pdk_root,
require_existing=require_pdk_files,
)
flow = ASICFlowGenerator().generate(resolution.pdk, design)
file_paths: Dict[str, str] = {}
for name, content in flow.to_dict().items():
path = out / name
path.write_text(content, encoding="utf-8")
file_paths[name] = str(path)
estimate = PreSynthEstimator.estimate(
n_neurons=n_neurons,
n_synapses=n_synapses,
bitstream_width=bitstream_width,
n_aer_ports=n_aer_ports,
pdk=resolution.pdk,
)
manifest = _build_asic_flow_manifest(
design=design,
pdk_resolution=resolution,
estimate=estimate,
file_paths=file_paths,
require_pdk_files=require_pdk_files,
formal_evidence_artifacts=formal_evidence_artifacts or [],
)
manifest_path = out / "asic_flow_manifest.json"
manifest_path.write_text(
json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8"
)
return ASICFlowBundle(
output_dir=str(out),
manifest_path=str(manifest_path),
file_paths=file_paths,
pdk_resolution=resolution,
estimate=estimate,
)
|
validate_pdk(pdk)
Check PDK configuration for obvious errors.
Source code in src/sc_neurocore/asic_flow/pdk.py
| Python |
|---|
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288 | def validate_pdk(pdk: PDKConfig) -> PDKValidationResult:
"""Check PDK configuration for obvious errors."""
errors = []
warnings = []
if pdk.pdk_type != PDKType.CUSTOM:
if not pdk.liberty_file:
errors.append("liberty_file is empty")
if not pdk.lef_file:
errors.append("lef_file is empty")
if not pdk.tech_lef:
errors.append("tech_lef is empty")
if pdk.clock_period_ns <= 0:
errors.append(f"clock_period_ns must be positive, got {pdk.clock_period_ns}")
if pdk.voltage_v <= 0:
errors.append(f"voltage_v must be positive, got {pdk.voltage_v}")
if pdk.metal_layers < 3:
warnings.append(f"only {pdk.metal_layers} metal layers — may limit routing")
return PDKValidationResult(valid=len(errors) == 0, errors=errors, warnings=warnings)
|
validate_pdk_installation(pdk, pdk_root=None, require_signoff=False)
Check whether the resolved open-source PDK files are present locally.
Source code in src/sc_neurocore/asic_flow/pdk.py
| Python |
|---|
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313 | def validate_pdk_installation(
pdk: PDKConfig,
pdk_root: Optional[str] = None,
require_signoff: bool = False,
) -> PDKValidationResult:
"""Check whether the resolved open-source PDK files are present locally."""
base = validate_pdk(pdk)
errors = list(base.errors)
warnings = list(base.warnings)
resolution = OpenSourcePDKResolver.resolve(pdk, pdk_root=pdk_root, require_existing=True)
for name in resolution.missing_required:
path = resolution.files.required_paths()[name]
errors.append(f"{name} not found: {path}")
for name in resolution.missing_optional:
path = resolution.files.optional_paths()[name]
message = f"{name} not found: {path}"
if require_signoff:
errors.append(message)
else:
warnings.append(message)
return PDKValidationResult(valid=len(errors) == 0, errors=errors, warnings=warnings)
|