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:

  1. Component → Module: Zuspec Components become SystemVerilog modules

  2. Fields → Ports/Signals: Input/output fields become ports, internal fields become signals

  3. @sync → always: Clocked processes become always blocks

  4. @process → initial: Async processes become initial blocks

  5. Bindings → Connections: Component bindings become wire connections or port maps

  6. Exports → Interfaces: Export fields become SystemVerilog interfaces with tasks

Constructor

SVGenerator(output_dir: Path, debug_annotations: bool = False)

Parameters:

  • output_dir: Directory where generated .sv files will be written

  • debug_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:

  1. Iterates through all components in the context

  2. Generates a .sv file for each component

  3. Returns 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 identifiers

    • Replaces dots with double underscores

    • Handles angle brackets and special characters

    • Example: test_smoke.<locals>.Countertest_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

  1. State Machines: Match/case statements are converted, but FSM optimization is not performed

  2. Type Inference: Some complex expressions may need explicit type hints

  3. Bundle Recursion: Nested bundles are not fully supported

  4. Extern Types: Limited introspection of external component ports

Best Practices

  1. Use Type Annotations: Explicit types help with correct width inference

  2. Keep Bundles Flat: Avoid deeply nested bundle structures

  3. Name Signals Clearly: Signal names influence type inference

  4. Test Generated Code: Always verify generated SystemVerilog with simulation

  5. Enable Debug Mode: Use debug_annotations=True during development