Source code for chia.vlsi.sram_cacti.liberty_gen

"""Generate minimal Liberty (.lib) files for SRAM macros from CACTI results.

Produces Liberty files with area, timing arcs, and pin definitions that match
the Chisel-generated _ext module port names. Uses scalar delay model (no NLDM
tables) — sufficient for synthesis area estimation and basic timing.

"""

import math
from dataclasses import dataclass
from chia.vlsi.sram_cacti.cacti_runner import SRAMSpec, CACTIResult


[docs] @dataclass class LibertyCorner: name: str # "ff", "ss", "tt" temperature: int # -40, 100, 25 voltage: float # 1.95, 1.60, 1.80 pvt_name: str # "PVT_1P95V_-40C", etc. lib_suffix: str # output key / filename suffix, e.g. "ff_n40C_1v95"
# Typical Sky130 corner; also the fallback when generate_liberty gets no corner. _SKY130_TT = LibertyCorner("tt", 25, 1.80, "PVT_1P8V_25C", "tt_025C_1v80") # Default corner set, matching the SRAM22 Sky130 characterization (ff/ss/tt). SKY130_CORNERS: tuple[LibertyCorner, ...] = ( LibertyCorner("ff", -40, 1.95, "PVT_1P95V_-40C", "ff_n40C_1v95"), LibertyCorner("ss", 100, 1.60, "PVT_1P6V_100C", "ss_100C_1v60"), _SKY130_TT, )
[docs] def generate_liberty( spec: SRAMSpec, result: CACTIResult, corner: LibertyCorner | None = None, cell_prefix: str = "cacti_", ) -> str: """Generate a Liberty .lib file for an SRAM macro at a specific corner. The cell name and pin names match the Chisel-generated _ext module so Genus can use this as a drop-in library cell. If corner is None, defaults to typical (tt_025C_1v80). cell_prefix defaults to ``cacti_`` so the Liberty cell name doesn't collide with the synflop wrapper named ``<spec.name>`` in chipyard's .top.mems.v (MacroCompiler then rewrites synflop refs to point at ``cacti_<name>``). Pass "" when the upstream Verilog references <spec.name> directly as an extmodule (e.g. firtool --repl-seq-mem) so the Liberty cell name lines up. """ if corner is None: corner = _SKY130_TT addr_bits = max(1, math.ceil(math.log2(max(spec.depth, 2)))) mask_bits = math.ceil(spec.width / spec.mask_gran) if spec.mask_gran else 0 setup_time = result.access_time_ns * 0.2 hold_time = result.access_time_ns * 0.05 clk_to_q = result.access_time_ns leakage_nw = result.leakage_power_mw * 1e6 cell_name = f"{cell_prefix}{spec.name}" lines = [] _a = lines.append # Library header _a(f'library ({cell_name}) {{') _a(f' delay_model : table_lookup;') _a(f' capacitive_load_unit (1,pf);') _a(f' current_unit : "1mA";') _a(f' leakage_power_unit : "1nW";') _a(f' time_unit : "1ns";') _a(f' voltage_unit : "1V";') # Delay/slew measurement thresholds. Required by STA readers (OpenSTA errors # "missing one or more thresholds" without them); standard NLDM defaults. _a(f' input_threshold_pct_rise : 50;') _a(f' input_threshold_pct_fall : 50;') _a(f' output_threshold_pct_rise : 50;') _a(f' output_threshold_pct_fall : 50;') _a(f' slew_lower_threshold_pct_rise : 20;') _a(f' slew_lower_threshold_pct_fall : 20;') _a(f' slew_upper_threshold_pct_rise : 80;') _a(f' slew_upper_threshold_pct_fall : 80;') _a(f' slew_derate_from_library : 1.0;') _a(f' nom_process : 1;') _a(f' nom_temperature : {corner.temperature};') _a(f' nom_voltage : {corner.voltage};') _a(f' voltage_map (VDD, {corner.voltage});') _a(f' voltage_map (VSS, 0);') _a(f' operating_conditions ({corner.pvt_name}) {{') _a(f' process : 1;') _a(f' temperature : {corner.temperature};') _a(f' voltage : {corner.voltage};') _a(f' }}') _a(f' default_operating_conditions : {corner.pvt_name};') _a(f' bus_naming_style : "%s[%d]";') _a(f'') # Bus type definitions bus_types = _get_bus_types(spec, addr_bits, mask_bits, cell_name) for bt in bus_types: _a(f' type ({bt["name"]}) {{') _a(f' base_type : array;') _a(f' data_type : bit;') _a(f' bit_width : {bt["width"]};') _a(f' bit_from : {bt["width"] - 1};') _a(f' bit_to : 0;') _a(f' downto : true;') _a(f' }}') _a(f'') # Cell definition _a(f' cell ({cell_name}) {{') _a(f' area : {result.area_um2:.3f};') _a(f' cell_leakage_power : {leakage_nw:.3f};') _a(f' dont_touch : true;') _a(f' interface_timing : true;') _a(f' is_macro_cell : true;') _a(f'') # Power pins _a(f' pg_pin (VDD) {{') _a(f' direction : inout;') _a(f' pg_type : primary_power;') _a(f' voltage_name : "VDD";') _a(f' }}') _a(f' pg_pin (VSS) {{') _a(f' direction : inout;') _a(f' pg_type : primary_ground;') _a(f' voltage_name : "VSS";') _a(f' }}') _a(f'') # Memory declaration _a(f' memory () {{') _a(f' address_width : {addr_bits};') _a(f' type : ram;') _a(f' word_width : {spec.width};') _a(f' }}') _a(f'') # Generate pins for each parsed port for prefix, masked in spec.parsed_ports: _gen_port_pins(lines, spec, prefix, masked, addr_bits, mask_bits, setup_time, hold_time, clk_to_q, cell_name) _a(f' }}') # end cell _a(f'}}') # end library _a(f'') return '\n'.join(lines)
def _get_bus_types(spec: SRAMSpec, addr_bits: int, mask_bits: int, cell_name: str = "") -> list[dict]: """Generate bus type definitions needed for this SRAM.""" types = [] for prefix, masked in spec.parsed_ports: is_rw = prefix.startswith("RW") is_reader = prefix.startswith("R") and not is_rw is_writer = prefix.startswith("W") types.append({"name": f"bus_{cell_name}_{prefix}_addr", "width": addr_bits}) if is_rw: types.append({"name": f"bus_{cell_name}_{prefix}_wdata", "width": spec.width}) types.append({"name": f"bus_{cell_name}_{prefix}_rdata", "width": spec.width}) if masked and mask_bits > 0: types.append({"name": f"bus_{cell_name}_{prefix}_wmask", "width": mask_bits}) elif is_reader: types.append({"name": f"bus_{cell_name}_{prefix}_data", "width": spec.width}) elif is_writer: types.append({"name": f"bus_{cell_name}_{prefix}_data", "width": spec.width}) if masked and mask_bits > 0: types.append({"name": f"bus_{cell_name}_{prefix}_mask", "width": mask_bits}) return types def _gen_clock_pin(lines, pin_name, setup_time, cap=0.005): """Generate a clock pin definition.""" lines.append(f' pin ({pin_name}) {{') lines.append(f' clock : true;') lines.append(f' direction : input;') lines.append(f' related_ground_pin : VSS;') lines.append(f' related_power_pin : VDD;') lines.append(f' capacitance : {cap};') lines.append(f' timing () {{') lines.append(f' timing_type : min_pulse_width;') lines.append(f' related_pin : "{pin_name}";') lines.append(f' rise_constraint (scalar) {{ values ("{setup_time:.4f}"); }}') lines.append(f' fall_constraint (scalar) {{ values ("{setup_time:.4f}"); }}') lines.append(f' }}') lines.append(f' }}') lines.append(f'') def _gen_input_pin(lines, pin_name, clk_name, setup_time, hold_time, cap=0.003): """Generate an input pin with setup/hold timing.""" lines.append(f' pin ({pin_name}) {{') lines.append(f' direction : input;') lines.append(f' related_ground_pin : VSS;') lines.append(f' related_power_pin : VDD;') lines.append(f' capacitance : {cap};') lines.append(f' timing () {{') lines.append(f' related_pin : "{clk_name}";') lines.append(f' timing_type : setup_rising;') lines.append(f' rise_constraint (scalar) {{ values ("{setup_time:.4f}"); }}') lines.append(f' fall_constraint (scalar) {{ values ("{setup_time:.4f}"); }}') lines.append(f' }}') lines.append(f' timing () {{') lines.append(f' related_pin : "{clk_name}";') lines.append(f' timing_type : hold_rising;') lines.append(f' rise_constraint (scalar) {{ values ("{hold_time:.4f}"); }}') lines.append(f' fall_constraint (scalar) {{ values ("{hold_time:.4f}"); }}') lines.append(f' }}') lines.append(f' }}') lines.append(f'') def _gen_input_bus(lines, bus_name, bus_type, clk_name, width, setup_time, hold_time): """Generate an input bus with per-pin setup/hold timing.""" lines.append(f' bus ({bus_name}) {{') lines.append(f' bus_type : {bus_type};') lines.append(f' direction : input;') for i in range(width - 1, -1, -1): _gen_input_pin(lines, f'{bus_name}[{i}]', clk_name, setup_time, hold_time) lines.append(f' }}') lines.append(f'') def _gen_output_bus(lines, bus_name, bus_type, clk_name, width, clk_to_q): """Generate an output bus with clock-to-Q timing.""" lines.append(f' bus ({bus_name}) {{') lines.append(f' bus_type : {bus_type};') lines.append(f' direction : output;') for i in range(width - 1, -1, -1): lines.append(f' pin ({bus_name}[{i}]) {{') lines.append(f' related_ground_pin : VSS;') lines.append(f' related_power_pin : VDD;') lines.append(f' max_capacitance : 0.5;') lines.append(f' timing () {{') lines.append(f' related_pin : "{clk_name}";') lines.append(f' timing_sense : non_unate;') lines.append(f' timing_type : rising_edge;') lines.append(f' cell_rise (scalar) {{ values ("{clk_to_q:.4f}"); }}') lines.append(f' cell_fall (scalar) {{ values ("{clk_to_q:.4f}"); }}') lines.append(f' rise_transition (scalar) {{ values ("0.05"); }}') lines.append(f' fall_transition (scalar) {{ values ("0.05"); }}') lines.append(f' }}') lines.append(f' }}') lines.append(f' }}') lines.append(f'') def _gen_port_pins(lines, spec, prefix, masked, addr_bits, mask_bits, setup_time, hold_time, clk_to_q, cell_name=""): """Generate Liberty pins for a single port (R, W, or RW) identified by prefix.""" is_rw = prefix.startswith("RW") is_reader = prefix.startswith("R") and not is_rw is_writer = prefix.startswith("W") clk = f"{prefix}_clk" _gen_clock_pin(lines, clk, setup_time) _gen_input_pin(lines, f"{prefix}_en", clk, setup_time, hold_time) if is_rw: _gen_input_pin(lines, f"{prefix}_wmode", clk, setup_time, hold_time) _gen_input_bus(lines, f"{prefix}_addr", f"bus_{cell_name}_{prefix}_addr", clk, addr_bits, setup_time, hold_time) _gen_input_bus(lines, f"{prefix}_wdata", f"bus_{cell_name}_{prefix}_wdata", clk, spec.width, setup_time, hold_time) _gen_output_bus(lines, f"{prefix}_rdata", f"bus_{cell_name}_{prefix}_rdata", clk, spec.width, clk_to_q) if masked and mask_bits > 0: _gen_input_bus(lines, f"{prefix}_wmask", f"bus_{cell_name}_{prefix}_wmask", clk, mask_bits, setup_time, hold_time) elif is_reader: _gen_input_bus(lines, f"{prefix}_addr", f"bus_{cell_name}_{prefix}_addr", clk, addr_bits, setup_time, hold_time) _gen_output_bus(lines, f"{prefix}_data", f"bus_{cell_name}_{prefix}_data", clk, spec.width, clk_to_q) elif is_writer: _gen_input_bus(lines, f"{prefix}_addr", f"bus_{cell_name}_{prefix}_addr", clk, addr_bits, setup_time, hold_time) _gen_input_bus(lines, f"{prefix}_data", f"bus_{cell_name}_{prefix}_data", clk, spec.width, setup_time, hold_time) if masked and mask_bits > 0: _gen_input_bus(lines, f"{prefix}_mask", f"bus_{cell_name}_{prefix}_mask", clk, mask_bits, setup_time, hold_time)