Spade: Een Moderne Hardwarebeschrijvings taal

  • Sterk en expressief typesysteem om bugs vroegtijdig te detecteren.
  • First-class pipelines voor betere timing-controle.
  • Zero-cost abstracties voor hardware.
  • Hulpvaardige compiler met heldere foutmeldingen.
  • Een schattige mascotte.

Voorbeeld: Dot Product

Hieronder staat een voorbeeld van het berekenen van het dot product van twee arrays van willekeurige grootte.

// Bereken het dot product van twee arrays van elke gewenste grootte
pipeline(3) dot_product<#uint N>(
    clk: clock,
    rst: bool,
    a: [int<16>; N],
    b: [int<16>; N],
) -> int<32> {
    let products = a
        .zip(b)
        .map(fn |(a, b)| a * b)
        reg * 3; // Creëert N pipeline-registers zonder berekening ertussen

    // Retourneer de som van de producten
    products.sum()
}

Timing beheren met Pipelines

Pipelines zijn een centraal onderdeel van Spade. Door latentie direct in de taal te coderen, worden re-timing en re-pipelining triviaal.

Pipeline-fasen worden gescheiden door het reg-commando. De compiler voegt de benodigde tussenregisters toe om alle signalen synchroon te houden. Bij geneste pipelines waarschuwt de compiler wanneer waarden te vroeg worden aangeroepen, zodat de synchronisatie gewaarborgd blijft.

Voorbeeld: Polynoom berekening

Dit voorbeeld berekent $x^3 \cdot c[0] + x^2 \cdot c[1] + x \cdot c[2] + c[3]$, met één klokcyclus latentie per multiplier en adder.

// Bereken x^3*c[0] + x^2*c[1] + x*c[2] + c[3]
pipeline(4) polynomial(clk: clock, x: int<18>, c: [int<18>; 4]) -> int<74> {
    let x_squared = x * x;
    reg;

    let x_cubed = x_squared * x;
    reg;

    let p3 = c[0] * x_cubed;
    let p2 = c[1] * x_squared;
    let p1 = c[2] * x;
    let p0 = c[3] * 1;
    reg;

    // 'sext' is sign extension. Spade voert nooit impliciete casts
    // van integer-breedtes uit om verrassingen te voorkomen.
    let result = (p3 + sext(p2)) + sext(p1 + p0);
    reg;

    result
}

Krachtig Typesysteem

Het typesysteem van Spade lijkt op dat van Rust, Haskell en Scala. Hierdoor kunnen aannames en invarianten direct in de taal worden vastgelegd, wat de integratie van modules vereenvoudigt en refactoring veiliger maakt.

  • Enums: Hiermee kunnen Finite State Machines (FSM's), commando's en instructies worden gemodelleerd. De compiler garandeert dat er nooit ongeldige data wordt gelezen.
  • Ports: Gerelateerde input- en outputsignalen kunnen worden gegroepeerd in één unit. De compiler controleert of alle signalen exact één keer worden geschreven.
  • Generics: Types in Spade kunnen generiek zijn over elk type, niet alleen geparametriseerd door integers.

Voorbeeld: Geheugenoperaties

// Commando voor geheugen: lezen of schrijven.
// Generiek over type T.
enum MemoryCommand<T> {
    Read { addr: uint<16> },
    Write { addr: uint<16>, data: T },
}

// Een geheugenpoort voor lees- en schrijfoperaties
struct MemoryPort<T> {
    addr: inv uint<16>,
    // Option<T> representeert een waarde van type T samen met een 'valid' signaal.
    write: inv Option<T>,
    read_value: T,
}

impl MemoryPort<T> {
    // Methode die de poort aanstuurt op basis van het opgegeven commando.
    pipeline(1) with_command(self, clk: clock, cmd: Option<MemoryCommand>) -> Option<T> {
        let (is_read, addr, write) = match cmd {
            None => (false, 0, None),
            Some(MemoryCommand::Read { addr }) => (true, addr, None),
            Some(MemoryCommand::Write { addr, data }) => (false, addr, Some(data)),
        };
        set self.addr = addr;
        set self.write = write;
        reg;
        if is_read { Some(self.read_value) } else { None }
    }
}

Angstloze Refactoring voor Agile Design

Dankzij het typesysteem en de pipelines kunnen grootschalige wijzigingen worden doorgevoerd zonder bestaande code te breken. Timing kan worden verbeterd zonder de functionaliteit te beïnvloeden.

Van combinatorische functie naar pipeline

Stel dat we een functie hebben die twee streams mengt:

fn mix_streams(clk: clock, s: [int<18>; 2], gains: [int<18>; 2]) -> int<37> {
    (s[0] * gains[0]) + (s[1] * gains[1])
}

Als we ontdekken dat de multipliers op het kritieke pad liggen, kunnen we pipelining toevoegen. Wanneer we reg toevoegen aan een fn, zal de compiler een foutmelding geven omdat functies geen registers mogen bevatten. We moeten de fn omzetten naar een pipeline.

Wanneer we mix_streams omzetten naar een pipeline, zal de compiler vervolgens waarschuwen bij alle plekken waar deze pipeline wordt gebruikt (zoals in een processing pipeline), omdat de latentie is veranderd. Door de aanwijzingen van de compiler te volgen (bijvoorbeeld door extra reg statements toe te voegen), ontstaat er een nieuw, sneller circuit met exact dezelfde output.

Uitbreidbaar met eigen Abstracties

Het typesysteem is krachtig genoeg om domeinspecifieke abstracties te bouwen. De Option-type in de standaardbibliotheek kan bijvoorbeeld worden gebruikt voor Ready/Valid handshaking, wat essentieel is voor het bouwen van Ethernet-bibliotheken of logische analyzers.

Voorspelbare en Performante Resultaten

Spade is geen High Level Synthesis (HLS). De onderliggende abstractie is nog steeds RTL, wat betekent dat je volledige controle houdt over de gegenereerde hardware. De abstracties van Spade hebben weinig tot geen performance-overhead.

RTL Mapping

Het dot product voorbeeld uit de introductie wordt door de compiler vertaald naar een structuur die een één-op-één mapping heeft met de gegenereerde Verilog:

entity dot_product(...) {
    reg(clk) products = [a[0] * b[0], a[1] * b[1], ...]
    reg(clk) products_s2 = products;
    reg(clk) products_s3 = products_s2;
    products_s3[0] + products_s3[1] ...
}

Speciaal Ontwikkelde Tooling

Spade wordt geleverd met een complete set tools:

  • Swim: De build tool die projecten en afhankelijkheden beheert.
  • LSP: Integratie met tekst-editors via het Language Server Protocol.
  • Surfer: Een waveform viewer, specifiek gebouwd voor Spade om debugging met complexe types te vereenvoudigen.

Eenvoudige Verilog Interoperabiliteit

Je hoeft niet direct volledig over te stappen. Bestaande Verilog-code kan worden hergebruikt in Spade, en omdat de compiler Verilog genereert, kan Spade-code worden geïnstantieerd vanuit bestaande Verilog-projecten.

#[no_mangle(all)]
extern entity recFNToFN <
    #uint expWidth,
    #uint sigWidth
>(
    in: uint<{expWidth + sigWidth + 1}>,
    out: inv uint<{expWidth + sigWidth}>
);

Maak kennis met Phoebe

Phoebe (zij/haar) is een Spadefish. Zij is het logo en de mascotte van de taal en de reden waarom alle tools rondom Spade een oceaan-thema hebben.

Publicaties

  • Skarman, F., Sörnäs, G., Gustafsson, O. (2026). Spade – A Modern Hardware Description Language. ACM Trans. Reconfig. Technol. Syst.
  • Skarman, F. (2025). Improved Tooling for Digital Hardware Development: Spade, Surfer, and more. PhD Thesis.
  • Skarman, F., Klemmer, L., Gustafsson, O., Große, D. (2023). Enhancing Compiler-Driven HDL Design with Automatic Waveform Analysis. Forum on specification & Design Languages (FDL).
  • Skarman, F., Gustafsson, O. (2023). Spade: An Expression-Based HDL With Pipelines. 3rd Workshop on Open-Source Design Automation (OSDA).
  • Skarman, F., Gustafsson, O. (2023). Abstraction in the Spade Hardware Description Language. 3rd Workshop on Languages, Tools, and Techniques for Accelerator Design (LATTE).
  • Skarman, F., Gustafsson, O. (2022). Spade: An HDL Inspired by Modern Software Languages. 32nd International Conference on Field-Programmable Logic and Applications (FPL).

Ontwikkeling

Spade is een open-source project met significante bijdragen van de community. Het project wordt momenteel onderhouden in de AEMY-groep aan de Munich University of Applied Sciences en heeft financiering ontvangen van NGI Zero Core via NLNet. De ontwikkeling startte oorspronkelijk in de ELDA-groep aan de Linköping University.

Het project is dual-licensed onder MIT en Apache.