Examples for the SUS Language
Explore the various examples demonstrating the features and capabilities of SUS
Latency Counting - automatic insertion of registers
Simply add the reg keyword to any critical path and any paths running parallel to it will get latency added to compensate.
This is accomplished by adding a 'latency' field to every path.
module pow17 {
interface pow17 : int i -> int o
int i2 = i * i
reg int i4 = i2 * i2
int i8 = i4 * i4
reg int i16 = i8 * i8
o = i16 * i
}
Compile-Time Generation of Look-Up Tables
This generative code constructs a lookup table to efficiently solve the Fizz-Buzz problem. Instead of evaluating conditions at runtime, the table is fully computed during code generation.
FIZZ (888) represents numbers divisible by 3.
BUZZ (555) represents numbers divisible by 5.
FIZZ_BUZZ (888555) is used for numbers divisible by both 3 and 5.
TABLE_SIZE (256) defines the number of precomputed entries in the lookup table.
The array lut stores a precomputed value for each i from 0 to TABLE_SIZE - 1.
If i is divisible by both 3 and 5, FIZZ_BUZZ (888555) is stored.
If only by 3, FIZZ (888) is stored.
If only by 5, BUZZ (555) is stored.
Otherwise, i itself is stored.
After generation, lut contains all results.
At runtime, fb = lut[v] provides an instant lookup, avoiding conditionals and divisions.
module fizz_buzz_gen {
interface fizz_buzz_gen : int v -> int fb
gen int FIZZ = 888
gen int BUZZ = 555
gen int FIZZ_BUZZ = 888555
gen int TABLE_SIZE = 256
gen int[TABLE_SIZE] lut
for int i in 0..TABLE_SIZE {
gen bool fizz = i % 3 == 0
gen bool buzz = i % 5 == 0
gen int tbl_fb
if fizz & buzz {
tbl_fb = FIZZ_BUZZ
} else if fizz {
tbl_fb = FIZZ
} else if buzz {
tbl_fb = BUZZ
} else {
tbl_fb = i
}
lut[i] = tbl_fb
}
fb = lut[v]
}
Multiple Interfaces and Clock Domain Separation
This FIFO module is structured around multiple interfaces that define distinct operational domains.
Interfaces serve as the only means to transfer data between latency and clock domains while maintaining synchronization.
Interfaces in the FIFO Module
Push Interface – Handles incoming data and writes it to memory.
Pop Interface – Reads data from memory when available.
Each interface consists of input and output ports and operates within its own clock and latency domain.
Wires belonging to the same latency counting group are placed in the same interface, ensuring that they experience delays consistently.
Clock Domain Separation and Cross Mechanism
Since the push and pop domains operate independently, they cannot directly share wires.
Instead, domain crossing primitives (CrossDomain) are used to safely transfer data while preserving synchronization:
write_to_pop transfers the write pointer to the pop domain.
read_to_push transfers the read pointer to the push domain.
mem_to_pop allows the pop domain to access memory contents.
To maintain signal synchronization across interfaces, multiple wires can be transferred together using the cross keyword:
cross wire_a, wire_b, wire_c;
This ensures that all three signals arrive with their relative latencies preserved.
However, separate cross statements do not maintain synchronization between signals.
How Interfaces and Domains Work Together in the FIFO
The push interface writes data into the FIFO and updates the write address.
The pop interface reads data and checks the write pointer from the push domain.
Cross-domain mechanisms synchronize read and write pointers across interfaces.
Wires never directly cross domains without explicit synchronization.
Flow control logic ensures safe data transfer by preventing overflows and underflows.
By using explicitly defined interfaces and strict clock domain separation,
this FIFO design guarantees reliable data exchange between independent timing domains while maintaining latency correctness.
module FIFO #(
T,
int DEPTH,
// The FIFO may still receive data for several cycles after ready is de-asserted
int READY_SLACK
) {
state T[DEPTH] mem
state int read_addr
state int write_addr
initial read_addr = 0
initial write_addr = 0
domain push
output bool ready'0
interface push : bool push'READY_SLACK, T data_in'READY_SLACK
domain pop
interface pop : bool pop -> bool data_valid, T data_out
CrossDomain write_to_pop
write_to_pop.in = write_addr
CrossDomain read_to_push
read_to_push.in = read_addr
CrossDomain mem_to_pop
mem_to_pop.in = mem
if pop {
data_valid = read_addr != write_to_pop.out
if data_valid {
// Add a pipelining register, because it can usually be fitted to the
reg data_out = mem_to_pop.out[read_addr]
read_addr = (read_addr + 1) % DEPTH
}
}
if push {
mem[write_addr] = data_in
write_addr = (write_addr + 1) % DEPTH
}
// Wrapping subtract
int space_remaining = (read_to_push.out - write_addr) % DEPTH
gen int ALMOST_FULL_TRESHOLD = READY_SLACK + 1 // +1 for the latency reg we introduce here
reg bool r = space_remaining > ALMOST_FULL_TRESHOLD
ready = LatencyOffset #(OFFSET: -ALMOST_FULL_TRESHOLD)(r)
}