Generator Details¶
The SVGenerator class is the core component that transforms
Zuspec IR (Intermediate Representation) into SystemVerilog RTL.
Overview¶
The generator performs these key transformations:
Component → Module: Zuspec Components become SystemVerilog modules
Fields → Ports/Signals: Input/output fields become ports, internal fields become signals
@sync → always: Clocked processes become always blocks
@process → initial: Async processes become initial blocks
Bindings → Connections: Component bindings become wire connections or port maps
Exports → Interfaces: Export fields become SystemVerilog interfaces with tasks
Constructor¶
SVGenerator(output_dir: Path, debug_annotations: bool = False)
Parameters:
output_dir: Directory where generated.svfiles will be writtendebug_annotations: If True, includes source location comments in generated code
Example:
from pathlib import Path
from zuspec.be.sv import SVGenerator
generator = SVGenerator(
output_dir=Path("rtl_output"),
debug_annotations=True
)
Main API¶
generate()¶
def generate(self, ctxt: ir.Context) -> List[Path]:
"""Generate SystemVerilog code for all components in context.
Args:
ctxt: IR Context containing components to generate
Returns:
List of paths to generated .sv files
"""
This is the main entry point. It:
Iterates through all components in the context
Generates a
.svfile for each componentReturns list of generated file paths
Example:
import zuspec.dataclasses as zdc
from zuspec.be.sv import SVGenerator
from pathlib import Path
@zdc.dataclass
class MyComponent(zdc.Component):
clock : zdc.bit = zdc.input()
# ... component definition
factory = zdc.DataModelFactory()
ctxt = factory.build(MyComponent)
generator = SVGenerator(Path("output"))
files = generator.generate(ctxt)
for f in files:
print(f"Generated: {f}")
Internal Methods¶
The generator uses several internal methods for different aspects of code generation:
Component Generation¶
_generate_component(): Main component-to-module transformation_generate_component_instances(): Creates module instantiations_generate_extern_instances(): Instantiates external modules
Process Generation¶
_generate_sync_process(): Converts @sync methods to always blocks_generate_async_process(): Converts @process methods to initial blocks_generate_stmt(): Generates individual statements (if, assign, match)_generate_expr(): Generates expressions (field refs, operators, constants)
Interface Generation¶
_generate_export_interfaces(): Creates SystemVerilog interfaces for exports_generate_interface_task(): Converts bound methods to interface tasks_generate_task_body(): Generates task body with timing controls
Type Conversion¶
_get_sv_type(): Converts IR types to SystemVerilog types_get_sv_parameterized_type(): Generates parameterized types_eval_width_lambda_to_sv(): Evaluates width expressions for parameters
Name Handling¶
_sanitize_sv_name(): Converts Python names to valid SystemVerilog identifiersReplaces dots with double underscores
Handles angle brackets and special characters
Example:
test_smoke.<locals>.Counter→test_smoke__locals__Counter
Bundle Handling¶
_resolve_bundle_type(): Resolves bundle field references to struct types_get_flattened_bundle_fields(): Flattens bundle into individual signals_infer_bundle_signal_type(): Determines signal types for bundle fields
Operator Conversion¶
_get_sv_binop(): Binary operators (+, -, *, /, <<, >>, |, ^, &)_get_sv_cmpop(): Comparison operators (==, !=, <, <=, >, >=)_get_sv_boolop(): Boolean operators (&&, ||)_get_sv_unaryop(): Unary operators (!, ~, +, -)
Generation Patterns¶
Module Structure¶
Generated modules follow this pattern:
module ModuleName #(
parameter int PARAM1 = default_val
)(
input logic port1,
output logic [WIDTH-1:0] port2
);
// Internal signal declarations
logic internal_sig;
// Component instantiations
SubModule inst(...);
// Always blocks (from @sync)
always @(posedge clk) begin
// ...
end
// Initial blocks (from @process)
initial begin
// ...
end
// Interface instantiations (from exports)
ModuleName_if my_if();
assign my_if.signal = internal_signal;
endmodule
Interface Structure¶
Generated interfaces for export fields:
interface ModuleName_export_name;
// Local signals
logic signal1;
logic [31:0] signal2 = 0;
// Tasks for bound methods
task method_name(
input logic [31:0] arg1,
output logic [31:0] __ret);
// Task body with timing controls
@(posedge clock);
__ret = signal1 + arg1;
endtask
endinterface
Port Connection Patterns¶
The generator handles several connection patterns:
Direct Port Connection:
# Zuspec
self.bind(self.sub.port, self.my_port)
// SystemVerilog
SubModule sub(
.port(my_port)
);
Bundle Flattening:
# Zuspec
self.bind(self.sub.io, self.my_io) # io is a bundle
// SystemVerilog - flattened
SubModule sub(
.io_ready(my_io_ready),
.io_valid(my_io_valid),
.io_data(my_io_data)
);
Internal Signal Connection:
# Zuspec
self.bind(self.sub1.out, self.sub2.in)
// SystemVerilog
logic sub1_out; // Internal wire
SubModule1 sub1(.out(sub1_out));
SubModule2 sub2(.in(sub1_out));
Limitations and Notes¶
Current Limitations¶
State Machines: Match/case statements are converted, but FSM optimization is not performed
Type Inference: Some complex expressions may need explicit type hints
Bundle Recursion: Nested bundles are not fully supported
Extern Types: Limited introspection of external component ports
Best Practices¶
Use Type Annotations: Explicit types help with correct width inference
Keep Bundles Flat: Avoid deeply nested bundle structures
Name Signals Clearly: Signal names influence type inference
Test Generated Code: Always verify generated SystemVerilog with simulation
Enable Debug Mode: Use
debug_annotations=Trueduring development