Examples

This page provides complete examples of Zuspec components and their generated SystemVerilog.

Simple Counter

A basic up-counter with synchronous reset.

Zuspec Source

import zuspec.dataclasses as zdc

@zdc.dataclass
class Counter(zdc.Component):
    """Simple up-counter with reset."""
    clock : zdc.bit = zdc.input()
    reset : zdc.bit = zdc.input()
    count : zdc.bit32 = zdc.output()

    @zdc.sync(clock=lambda s:s.clock, reset=lambda s:s.reset)
    def _count(self):
        if self.reset:
            self.count = 0
        else:
            self.count += 1

Generated SystemVerilog

module Counter(
  input logic clock,
  input logic reset,
  output logic [31:0] count
);

  always @(posedge clock or posedge reset) begin
    if (reset) begin
      count <= 0;
    end else begin
      count <= count + 1;
    end
  end

endmodule

Parameterized FIFO

A FIFO with configurable width and depth.

Zuspec Source

@zdc.dataclass
class FIFO(zdc.Component):
    """Parameterized FIFO."""
    DATA_WIDTH : int = zdc.const(default=32)
    DEPTH : int = zdc.const(default=16)

    clock : zdc.bit = zdc.input()
    reset : zdc.bit = zdc.input()

    # Write interface
    wr_en : zdc.bit = zdc.input()
    wr_data : zdc.int = zdc.input(width=lambda s: s.DATA_WIDTH)
    full : zdc.bit = zdc.output()

    # Read interface
    rd_en : zdc.bit = zdc.input()
    rd_data : zdc.int = zdc.output(width=lambda s: s.DATA_WIDTH)
    empty : zdc.bit = zdc.output()

    # Internal state
    wr_ptr : zdc.int = zdc.field(bits=8)
    rd_ptr : zdc.int = zdc.field(bits=8)
    count : zdc.int = zdc.field(bits=8)

    @zdc.sync(clock=lambda s:s.clock, reset=lambda s:s.reset)
    def _fifo_ctrl(self):
        if self.reset:
            self.wr_ptr = 0
            self.rd_ptr = 0
            self.count = 0
            self.full = 0
            self.empty = 1
        else:
            # Update count
            if self.wr_en and not self.full:
                if not (self.rd_en and not self.empty):
                    self.count += 1
            elif self.rd_en and not self.empty:
                self.count -= 1

            # Update full/empty flags
            self.full = (self.count == self.DEPTH)
            self.empty = (self.count == 0)

            # Update pointers
            if self.wr_en and not self.full:
                self.wr_ptr += 1
            if self.rd_en and not self.empty:
                self.rd_ptr += 1

Generated SystemVerilog

module FIFO #(
  parameter int DATA_WIDTH = 32,
  parameter int DEPTH = 16
)(
  input logic clock,
  input logic reset,
  input logic wr_en,
  input logic [(DATA_WIDTH-1):0] wr_data,
  output logic full,
  input logic rd_en,
  output logic [(DATA_WIDTH-1):0] rd_data,
  output logic empty
);

  logic [7:0] wr_ptr;
  logic [7:0] rd_ptr;
  logic [7:0] count;

  always @(posedge clock or posedge reset) begin
    if (reset) begin
      wr_ptr <= 0;
      rd_ptr <= 0;
      count <= 0;
      full <= 0;
      empty <= 1;
    end else begin
      // Counter update logic
      if (wr_en && !full) begin
        if (!(rd_en && !empty)) begin
          count <= count + 1;
        end
      end else if (rd_en && !empty) begin
        count <= count - 1;
      end

      // Flag updates
      full <= (count == DEPTH);
      empty <= (count == 0);

      // Pointer updates
      if (wr_en && !full) begin
        wr_ptr <= wr_ptr + 1;
      end
      if (rd_en && !empty) begin
        rd_ptr <= rd_ptr + 1;
      end
    end
  end

endmodule

State Machine

A traffic light controller FSM.

Zuspec Source

@zdc.dataclass
class TrafficLight(zdc.Component):
    """Traffic light controller FSM."""
    clock : zdc.bit = zdc.input()
    reset : zdc.bit = zdc.input()

    red : zdc.bit = zdc.output()
    yellow : zdc.bit = zdc.output()
    green : zdc.bit = zdc.output()

    state : zdc.bit8 = zdc.field()
    timer : zdc.bit32 = zdc.field()

    # States
    STATE_RED = 0
    STATE_GREEN = 1
    STATE_YELLOW = 2

    @zdc.sync(clock=lambda s:s.clock, reset=lambda s:s.reset)
    def _fsm(self):
        if self.reset:
            self.state = self.STATE_RED
            self.timer = 0
            self.red = 1
            self.yellow = 0
            self.green = 0
        else:
            self.timer += 1

            match self.state:
                case self.STATE_RED:
                    self.red = 1
                    self.yellow = 0
                    self.green = 0
                    if self.timer >= 100:
                        self.state = self.STATE_GREEN
                        self.timer = 0

                case self.STATE_GREEN:
                    self.red = 0
                    self.yellow = 0
                    self.green = 1
                    if self.timer >= 80:
                        self.state = self.STATE_YELLOW
                        self.timer = 0

                case self.STATE_YELLOW:
                    self.red = 0
                    self.yellow = 1
                    self.green = 0
                    if self.timer >= 20:
                        self.state = self.STATE_RED
                        self.timer = 0

Generated SystemVerilog

module TrafficLight(
  input logic clock,
  input logic reset,
  output logic red,
  output logic yellow,
  output logic green
);

  logic [7:0] state;
  logic [31:0] timer;

  always @(posedge clock or posedge reset) begin
    if (reset) begin
      state <= 0;
      timer <= 0;
      red <= 1;
      yellow <= 0;
      green <= 0;
    end else begin
      timer <= timer + 1;

      case (state)
        0: begin  // STATE_RED
          red <= 1;
          yellow <= 0;
          green <= 0;
          if (timer >= 100) begin
            state <= 1;
            timer <= 0;
          end
        end

        1: begin  // STATE_GREEN
          red <= 0;
          yellow <= 0;
          green <= 1;
          if (timer >= 80) begin
            state <= 2;
            timer <= 0;
          end
        end

        2: begin  // STATE_YELLOW
          red <= 0;
          yellow <= 1;
          green <= 0;
          if (timer >= 20) begin
            state <= 0;
            timer <= 0;
          end
        end
      endcase
    end
  end

endmodule

Hierarchical System

A system with multiple component instances.

Zuspec Source

@zdc.dataclass
class Adder(zdc.Component):
    """32-bit adder."""
    a : zdc.bit32 = zdc.input()
    b : zdc.bit32 = zdc.input()
    sum : zdc.bit32 = zdc.output()

    @zdc.sync(clock=lambda s:s.clock)
    def _add(self):
        self.sum = self.a + self.b

@zdc.dataclass
class Accumulator(zdc.Component):
    """Accumulator using adder."""
    clock : zdc.bit = zdc.input()
    reset : zdc.bit = zdc.input()
    data_in : zdc.bit32 = zdc.input()
    sum_out : zdc.bit32 = zdc.output()

    adder : Adder = zdc.inst()
    accum : zdc.bit32 = zdc.field()

    def __bind__(self):
        return {
            self.adder.a : self.accum,
            self.adder.b : self.data_in,
            self.adder.sum : self.sum_out
        }

    @zdc.sync(clock=lambda s:s.clock, reset=lambda s:s.reset)
    def _accum(self):
        if self.reset:
            self.accum = 0
        else:
            self.accum = self.sum_out

Generated SystemVerilog

module Adder(
  input logic [31:0] a,
  input logic [31:0] b,
  output logic [31:0] sum
);

  always @(posedge clock) begin
    sum <= a + b;
  end

endmodule

module Accumulator(
  input logic clock,
  input logic reset,
  input logic [31:0] data_in,
  output logic [31:0] sum_out
);

  logic [31:0] accum;
  logic [31:0] adder_sum;

  Adder adder (
    .a(accum),
    .b(data_in),
    .sum(adder_sum)
  );

  assign sum_out = adder_sum;

  always @(posedge clock or posedge reset) begin
    if (reset) begin
      accum <= 0;
    end else begin
      accum <= sum_out;
    end
  end

endmodule

Export Interface Example

A transactor with export interface.

Zuspec Source

from typing import Protocol

class SendIF(Protocol):
    """Send interface protocol."""
    async def send(self, data: int) -> int: ...

@zdc.dataclass
class Transactor(zdc.Component):
    """Component with export interface."""
    clock : zdc.bit = zdc.input()
    reset : zdc.bit = zdc.input()

    send_if : SendIF = zdc.export()

    data_out : zdc.bit32 = zdc.output()
    valid : zdc.bit = zdc.output()

    def __bind__(self):
        return {
            self.send_if.send : self._send_impl
        }

    async def _send_impl(self, data: int) -> int:
        """Implementation of send method."""
        self.data_out = data
        self.valid = 1
        await self.posedge(self.clock)
        self.valid = 0
        return data * 2

Generated SystemVerilog

Interface:

interface Transactor_send_if;

  logic [31:0] data_out = 0;
  logic valid = 0;

  task send(
    input logic [31:0] data,
    output logic [31:0] __ret);

    $display("%0t: [send] Task started", $time);

    data_out = data;
    valid = 1;
    @(posedge clock);
    valid = 0;
    __ret = data * 2;

    $display("%0t: [send] Task completed", $time);

  endtask

endinterface

Module:

module Transactor(
  input logic clock,
  input logic reset,
  output logic [31:0] data_out,
  output logic valid
);

  // Instantiate interface
  Transactor_send_if send_if();

  // Connect module signals to interface
  assign data_out = send_if.data_out;
  assign valid = send_if.valid;

endmodule

Usage

The interface can be called from a testbench:

module tb;
  logic clock, reset;
  logic [31:0] data_out;
  logic valid;

  Transactor dut(
    .clock(clock),
    .reset(reset),
    .data_out(data_out),
    .valid(valid)
  );

  initial begin
    logic [31:0] result;

    reset = 1;
    #10ns reset = 0;

    // Call send through interface
    dut.send_if.send(42, result);
    $display("Result: %d", result);  // Should be 84

    $finish;
  end
endmodule