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