Skip to content

HDL Export API

The bitlogic.hdl submodule turns a trained nn.Sequential — optionally starting with a Thermometer / DistributiveThermometer encoder plus nn.Flatten, followed by one or more LogicDense layers and one GroupSum head — into a single SystemVerilog module, and optionally drives Yosys against the Nangate 45 nm Open Cell Library to extract a NAND2-equivalent gate count — the primary hardware-cost metric for bitlogic experiments.

When the encoder is present, the emitted module takes a packed x_int8 input (one unsigned 8-bit byte per input slot) and emits the thermometer comparators inline as combinational logic. With no encoder, the legacy x_bin port is kept for backwards-compatibility.

Sequential behavior is controlled via HDLExportOptions. Each of the three stages (encoder, logic layers, GroupSum) independently selects between replication ("parallel", fully combinational) and iterative decomposition ("iterative", time-multiplexed with a shared unit), and each can have a flip-flop stage inserted at its output boundary (pipeline_encoder / pipeline_logic_layers / pipeline_groupsum). All defaults reproduce the pre-v2 pure-combinational emission so existing callers see no change.

See the HDL Export guide for an end-to-end walkthrough.

Top-level helpers

emit_systemverilog

emit_systemverilog(model: Module, *, top_name: str = 'logic_net', options: HDLExportOptions | None = None) -> str

Emit a SystemVerilog module for a trained bitlogic model.

The input model must be an nn.Sequential that optionally starts with a :class:Thermometer / :class:DistributiveThermometer (plus optional :class:nn.Flatten), then one or more :class:LogicDense layers, then exactly one :class:GroupSum head. The model is put into eval mode so learnable routing is frozen (via argmax) before LUT extraction.

Parameters:

Name Type Description Default
model Module

Trained module to export.

required
top_name str

Name of the emitted SystemVerilog module. Also the Yosys top-level.

'logic_net'
options HDLExportOptions | None

Sequential-behavior controls. See :class:HDLExportOptions. Defaults to fully-combinational parallel emission.

None

Returns:

Type Description
str

A string containing the SystemVerilog source. Write it to a .sv

str

file before handing to Yosys.

Raises:

Type Description
TypeError

If the model's submodule layout is unsupported, or an encoder is present but its configuration is outside the supported v2 scope.

ValueError

If the last logic layer's out_dim is not divisible by the head's k.

HDLExportOptions dataclass

HDLExportOptions(encoder_style: Literal['parallel', 'iterative'] = 'parallel', groupsum_style: Literal['parallel', 'iterative'] = 'parallel', pipeline_encoder: bool = False, pipeline_logic_layers: bool = False, pipeline_groupsum: bool = False, io_shim: bool = False)

Knobs controlling the sequential behavior of the emitted module.

All defaults reproduce the pre-v2 pure-combinational emission so existing callers see no change.

Attributes:

Name Type Description
encoder_style Literal['parallel', 'iterative']

"parallel" replicates one 8-bit comparator per (channel, threshold-bit). "iterative" shares a single comparator across channels with a counter + mux and an output shift register.

groupsum_style Literal['parallel', 'iterative']

Applies to whichever head the model uses. "parallel" instantiates one per-class reduction (popcount adder tree, signed MAC row) plus a combinational argmax. "iterative" walks classes sequentially through a single shared reduction unit, updating a running best_val / best_id register and latching class_id after the final cycle. Supported for both heads — the per-cycle reduction is head-specific (popcount for :class:GroupSum, signed k-term MAC for :class:GroupedDSP).

pipeline_encoder bool

Insert a flip-flop on x_bin (the encoder output feeding the first :class:LogicDense). No effect when encoder_style == "iterative" (already registered).

pipeline_logic_layers bool

Insert a flip-flop after every :class:LogicDense layer. Pipeline depth grows by the number of layers.

pipeline_groupsum bool

Insert a flip-flop on the class_id output. No effect when groupsum_style == "iterative" (output is already registered).

io_shim bool

Wrap the generated core module in a thin BRAM-backed register shim so place+route can succeed on FPGAs that don't have enough physical pins to expose the wide x_int8 / x_bin input directly. When enabled, the top-level SV file contains two modules: {top_name}_core (the usual network) and {top_name} (the shim). The shim has a narrow pin footprint (clk, rst, class_id) and feeds the core from an internal ROM of pseudo-random samples registered at each clock. This adds one flop-to-flop stage at the core's output for timing analysis; the static :class:CycleBudget is adjusted accordingly.

encoder_style class-attribute instance-attribute

encoder_style: Literal['parallel', 'iterative'] = 'parallel'

groupsum_style class-attribute instance-attribute

groupsum_style: Literal['parallel', 'iterative'] = 'parallel'

pipeline_encoder class-attribute instance-attribute

pipeline_encoder: bool = False

pipeline_logic_layers class-attribute instance-attribute

pipeline_logic_layers: bool = False

pipeline_groupsum class-attribute instance-attribute

pipeline_groupsum: bool = False

io_shim class-attribute instance-attribute

io_shim: bool = False

needs_clock

needs_clock() -> bool

True iff the emitted CORE module requires a clk input port.

resolve_liberty

resolve_liberty(explicit: str | Path | None = None, *, verify_sha: bool = True) -> Path

Return a path to a usable Nangate Liberty file.

Parameters:

Name Type Description Default
explicit str | Path | None

User-provided path. If given, it is returned as-is (after existence check) with no download or checksum verification.

None
verify_sha bool

If True (default) and we are using the cached copy, re-check the SHA-256 against :data:_LIB_SHA256. A mismatch triggers a re-download.

True

Returns:

Type Description
Path

Absolute path to the Liberty file.

Raises:

Type Description
LibertyError

If no usable file can be resolved (network failure + no cache, bad explicit path, etc.).

synthesize

Yosys driver for bitlogic HDL export.

Writes a flow script, invokes yosys, and parses the resulting stat report into a NAND2-equivalent gate count. Total chip area is divided by the NAND2_X1 area (parsed out of the Liberty file in :mod:.liberty) to obtain the nand2_ge metric.

The flow is intentionally a thin shell around a known-good Yosys incantation; no custom passes, no liberty-specific tuning. That keeps the metric reproducible and portable.

YosysError

Bases: RuntimeError

Raised when the Yosys subprocess fails or its output is unparseable.

SynthMetrics dataclass

SynthMetrics(total_area_um2: float, nand2_area_um2: float, nand2_ge: float, cell_counts: dict[str, int] = dict(), top_module: str = 'logic_net', liberty_path: str = '')

Primary hardware-cost metrics extracted from a Yosys run.

synthesize

synthesize(sv_path: str | Path, *, out_dir: str | Path, liberty_path: str | Path, top: str = 'logic_net', keep_tmp: bool = False, abc_fast: bool = False) -> SynthMetrics

Run Yosys on an emitted SystemVerilog file and extract NAND2-eq GE.

Parameters:

Name Type Description Default
sv_path str | Path

Path to the .sv file produced by :func:emit.emit_systemverilog.

required
out_dir str | Path

Directory to write flow.ys, mapped.v, area.rpt, yosys.log, and metrics.json. Created if missing.

required
liberty_path str | Path

Path to NangateOpenCellLibrary_typical.lib (resolved by :func:.liberty.resolve_liberty).

required
top str

Top-level module name (must match :func:emit_systemverilog's top_name).

'logic_net'
keep_tmp bool

If False, the generated flow.ys is kept (it is tiny and useful for reproduction). Retained for future pruning of larger intermediates.

False
abc_fast bool

If True, invoke abc -fast -liberty ... instead of the default abc -liberty .... ABC's fast script is ~3–10× quicker on large flat netlists at a 5–15% area cost — useful for ranking seeds, not for final paper numbers.

False

Returns:

Name Type Description
A SynthMetrics

class:SynthMetrics populated with the parsed values.

Raises:

Type Description
YosysError

If yosys is not on PATH, if the subprocess exits non-zero, or if the area report cannot be parsed.

LibertyError

If NAND2_X1 cannot be found in the Liberty file.

Metrics + exceptions

SynthMetrics dataclass

SynthMetrics(total_area_um2: float, nand2_area_um2: float, nand2_ge: float, cell_counts: dict[str, int] = dict(), top_module: str = 'logic_net', liberty_path: str = '')

Primary hardware-cost metrics extracted from a Yosys run.

total_area_um2 instance-attribute

total_area_um2: float

nand2_area_um2 instance-attribute

nand2_area_um2: float

nand2_ge instance-attribute

nand2_ge: float

cell_counts class-attribute instance-attribute

cell_counts: dict[str, int] = field(default_factory=dict)

top_module class-attribute instance-attribute

top_module: str = 'logic_net'

liberty_path class-attribute instance-attribute

liberty_path: str = ''

to_json

to_json() -> str

LibertyError

Bases: RuntimeError

Raised when the Nangate Liberty file cannot be resolved.

YosysError

Bases: RuntimeError

Raised when the Yosys subprocess fails or its output is unparseable.

Low-level surfaces

nand2_area_um2

nand2_area_um2(lib_path: str | Path) -> float

Parse the NAND2_X1 cell area (μm²) out of a Liberty file.

Parameters:

Name Type Description Default
lib_path str | Path

Path to a Nangate 45 nm Liberty .lib file.

required

Returns:

Type Description
float

Area of the NAND2_X1 cell in square micrometers.

Raises:

Type Description
LibertyError

If the file cannot be read or the cell / area field is not found.

ExportModel dataclass

ExportModel(input_width: int, layers: tuple[_LayerSpec, ...], num_classes: int, group_width: int, head: _HeadSpec = None, encoder: _EncoderSpec | None = None)

Metadata for an exported model — intermediate between walk and emit.