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. Logo

 
          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)
}