Generic Types

The types we saw in the earlier chapter on types were fixed. For example, we can use structs or enums to group related signals together into a meaningful package. Enums in particular can be used to bundle a valid signal with its validated data:

enum MaybeUint32 {
    Invalid,
    Valid{value: Type used to represent unsigned integers. The generic parameter (<N>) specReturns the value of the first branch if the condition is true, otherwise the second branch.
Note that unlike software languages, Spade does not have conditional execution. You do not conditionally assign values inside if expressiosn, you compute a value and return it.
ies the number of bits
} }

However, we will likely want more data with valid signals, and not all of them will be 32 bit integers. This is the problem that generics solve, you can define a type that can contain any other type. Generic types are defined by writing <...> after the name of the type, and giving names to the contained types inside the angle brackets. For example, a general purpose valid-data pair can be defined as

enum Maybe<T> {
    Invalid,
    Valid{value: T}
}

where T is used as a placeholder for an actual type to be filled in later. Using this type, we can define a function which takes a valid-data pair of uint<32> and returns a new valid-data pair of bool:

Functions together with Entities together with fn and Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. are the basic building blocks of Spade circuits. Unlike fn, entity can contain registers and therefore have state and unlike Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline., they do not have a statically known latency or Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. structure. and Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. are the basic building blocks of Spade circuits. Unlike entities and pipelines, functions have no internal state, they are combinational (pure). example(input: Maybe<Type used to represent unsigned integers. The generic parameter (<N>) specReturns the value of the first branch if the condition is true, otherwise the second branch.
Note that unlike software languages, Spade does not have conditional execution. You do not conditionally assign values inside if expressiosn, you compute a value and return it.
ies the number of bits
>) -> Maybe<Type used to represent 1 bit values> { }

This Maybe type representing a valid-data pair is a very common construct, so much so that it is included in the standard library. However, in order to remain similar to Rust and other software languages, it is called Option and is defined as

enum Option<T> {
    None,
    Some{val: T}
}

i.e. it is either None or Some(T) if a value is present. The None and Some variants are imported by default, so while you would normally have to write Option::Some(5) or Option::None, with these you can simply write Some(5) and None.

Generic types behave just like their non-generic counterparts when used, see the types chapter for details on how to use them if you need a refresher.

Generic Functions

In addition to generic types, units can also be generic to support working with any type. For example, we can write a generic multiplexer like this:

Functions together with Entities together with fn and Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. are the basic building blocks of Spade circuits. Unlike fn, entity can contain registers and therefore have state and unlike Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline., they do not have a statically known latency or Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. structure. and Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. are the basic building blocks of Spade circuits. Unlike entities and pipelines, functions have no internal state, they are combinational (pure). mux<T>(sel: Type used to represent 1 bit values, on_true: T, on_false: T) -> T {
    Returns the value of the first branch if the condition is true, otherwise the second branch.
Note that unlike software languages, Spade does not have conditional execution. You do not conditionally assign values inside if expressiosn, you compute a value and return it.
sel { on_true } else { on_false } }

For this to work, the type of both branches and the output of course has to match, which is ensured by the fact that T has one unique replacement per instantiation. I.e. calling it as:

mux(sel, 5u6, 6)

will result in a type error.

Generic Values

Even if you may not have thought much about it while reading the book so far, we have seen plenty of examples of generic types in one specific case: integer sizes. Even in the first example in this section, we used uint<32> to specify a type that is an unsigned integer with 32 bits. It uses the same syntax as the types we saw later, but the difference is that 32 is not a type, there is no value whose type is just 32

Instead, 32 is a type level constant, and the uint type is generic over a constant representing the number of bits. Type level constants are written as #meta-type Name, where meta-type is usually uint for a type level unsigned integer.

This means that the definition of the int<N> and uint<N> types look roughly1 like this:

struct Type used to represent unsigned integers. The generic parameter (<N>) specReturns the value of the first branch if the condition is true, otherwise the second branch.
Note that unlike software languages, Spade does not have conditional execution. You do not conditionally assign values inside if expressiosn, you compute a value and return it.
ies the number of bits
{ /* implementation details */ } struct Type used to represent signed integers. The generic parameter (<N>) specReturns the value of the first branch if the condition is true, otherwise the second branch.
Note that unlike software languages, Spade does not have conditional execution. You do not conditionally assign values inside if expressiosn, you compute a value and return it.
ies the number of bits
{ /* implementation details */ }

All generic types can be used “recursively”, i.e. you can define a fixed point number like this:

struct Fp<#uint Size, #uint FractionalBits> {
    value: Type used to represent unsigned integers. The generic parameter (<N>) specReturns the value of the first branch if the condition is true, otherwise the second branch.
Note that unlike software languages, Spade does not have conditional execution. You do not conditionally assign values inside if expressiosn, you compute a value and return it.
ies the number of bits
}

Or counter that is generic over the number of bits like this:

Entities together with Functions together with entity and Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. are the basic building blocks of Spade circuits. Unlike entities and pipelines, functions have no internal state, they are combinational (pure). and Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. are the basic building blocks of Spade circuits. Unlike Functions together with entity and Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. are the basic building blocks of Spade circuits. Unlike entities and pipelines, functions have no internal state, they are combinational (pure)., entity can contain registers and therefore have state and unlike Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline., they do not have a statically known latency or Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. structure. counter<#uint N>(clk: Type used to represent clock signals., rst: Type used to represent 1 bit values) -> Type used to represent unsigned integers. The generic parameter (<N>) specReturns the value of the first branch if the condition is true, otherwise the second branch.
Note that unlike software languages, Spade does not have conditional execution. You do not conditionally assign values inside if expressiosn, you compute a value and return it.
ies the number of bits
{ reg(clk) is used to define registers which maintain the state of your circuit.
All registers have a Type used to represent clock signals. (clk), a name and a new value after the = which is given as a function of the current value. Registers can also have a Defines the reset value of a register as (trigger: value). When trigger ist rue, the reset is synchronously reset to valuetrigger: value) wich means that the reset is synchronously set to value when trigger is true.
In pipelines, you can also define registers with reg; is used in a Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. to separate stages. When you refer to a variable defined above a reg; statement below a reg; statement you refer to a registered version of the original value. Registers defined with an explicit Type used to represent clock signals. (reg(clk)) are used for state registers both inside and outside pipelines. which are used to separate stages.
value Defines the reset value of a register as (trigger: value). When trigger ist rue, the reset is synchronously reset to valuerst: 0) = value Add two values, producing a value of the same size while wrapping Returns the value of the first branch if the condition is true, otherwise the second branch.
Note that unlike software languages, Spade does not have conditional execution. You do not conditionally assign values inside if expressiosn, you compute a value and return it.
the result overflows. This is unlike + where the addition results in one more bit than the input type, guaranteeing no overflows.
1; value }

The other meta-types currently available in Spade in addition to #uint are

  • #int
  • #bool
  • #str

Though the latter is only used when instantiating external Verilog.

Type Expressions

This counter will count from 0 to 2𝑁1, but what if you wanted to have a counter that counts from 0 to some compile-time known value? You may write that as:

Entities together with Functions together with entity and Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. are the basic building blocks of Spade circuits. Unlike entities and pipelines, functions have no internal state, they are combinational (pure). and Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. are the basic building blocks of Spade circuits. Unlike Functions together with entity and Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. are the basic building blocks of Spade circuits. Unlike entities and pipelines, functions have no internal state, they are combinational (pure)., entity can contain registers and therefore have state and unlike Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline., they do not have a statically known latency or Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. structure. counter<#uint Count>(self, clk: Type used to represent clock signals., rst: Type used to represent 1 bit values)
    -> Type used to represent unsigned integers. The generic parameter (<N>) specReturns the value of the first branch if the condition is true, otherwise the second branch.
Note that unlike software languages, Spade does not have conditional execution. You do not conditionally assign values inside if expressiosn, you compute a value and return it.
ies the number of bits
{ reg(clk) is used to define registers which maintain the state of your circuit.
All registers have a Type used to represent clock signals. (clk), a name and a new value after the = which is given as a function of the current value. Registers can also have a Defines the reset value of a register as (trigger: value). When trigger ist rue, the reset is synchronously reset to valuetrigger: value) wich means that the reset is synchronously set to value when trigger is true.
In pipelines, you can also define registers with reg; is used in a Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. to separate stages. When you refer to a variable defined above a reg; statement below a reg; statement you refer to a registered version of the original value. Registers defined with an explicit Type used to represent clock signals. (reg(clk)) are used for state registers both inside and outside pipelines. which are used to separate stages.
value Defines the reset value of a register as (trigger: value). When trigger ist rue, the reset is synchronously reset to valuerst: 0) = Returns the value of the first branch if the condition is true, otherwise the second branch.
Note that unlike software languages, Spade does not have conditional execution. You do not conditionally assign values inside if expressiosn, you compute a value and return it.
value == Count {value Add two values, producing a value of the same size while wrapping Returns the value of the first branch if the condition is true, otherwise the second branch.
Note that unlike software languages, Spade does not have conditional execution. You do not conditionally assign values inside if expressiosn, you compute a value and return it.
the result overflows. This is unlike + where the addition results in one more bit than the input type, guaranteeing no overflows.
1} else {0}; value }

but there is a glaring issue there: what is the size of the output? Luckily, this can be fixed by using type expressions; expressions on type variables. These are written as {expression}, for example uint<{N+1}>, or in this case uint<{uint::bits_for(Count)}>:

Entities together with Functions together with entity and Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. are the basic building blocks of Spade circuits. Unlike entities and pipelines, functions have no internal state, they are combinational (pure). and Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. are the basic building blocks of Spade circuits. Unlike Functions together with entity and Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. are the basic building blocks of Spade circuits. Unlike entities and pipelines, functions have no internal state, they are combinational (pure)., entity can contain registers and therefore have state and unlike Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline., they do not have a statically known latency or Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. structure. counter<#uint Count>(clk: Type used to represent clock signals., rst: Type used to represent 1 bit values)
    -> Type used to represent unsigned integers. The generic parameter (<N>) specReturns the value of the first branch if the condition is true, otherwise the second branch.
Note that unlike software languages, Spade does not have conditional execution. You do not conditionally assign values inside if expressiosn, you compute a value and return it.
ies the number of bits
{ reg(clk) is used to define registers which maintain the state of your circuit.
All registers have a Type used to represent clock signals. (clk), a name and a new value after the = which is given as a function of the current value. Registers can also have a Defines the reset value of a register as (trigger: value). When trigger ist rue, the reset is synchronously reset to valuetrigger: value) wich means that the reset is synchronously set to value when trigger is true.
In pipelines, you can also define registers with reg; is used in a Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. to separate stages. When you refer to a variable defined above a reg; statement below a reg; statement you refer to a registered version of the original value. Registers defined with an explicit Type used to represent clock signals. (reg(clk)) are used for state registers both inside and outside pipelines. which are used to separate stages.
value Defines the reset value of a register as (trigger: value). When trigger ist rue, the reset is synchronously reset to valuerst: 0) = Returns the value of the first branch if the condition is true, otherwise the second branch.
Note that unlike software languages, Spade does not have conditional execution. You do not conditionally assign values inside if expressiosn, you compute a value and return it.
value == Count {value Add two values, producing a value of the same size while wrapping Returns the value of the first branch if the condition is true, otherwise the second branch.
Note that unlike software languages, Spade does not have conditional execution. You do not conditionally assign values inside if expressiosn, you compute a value and return it.
the result overflows. This is unlike + where the addition results in one more bit than the input type, guaranteeing no overflows.
1} else {0}; value }

Defining Methods on Generic Types

To define methods on a generic type, the impl block becomes generic. For example:

struct Generic<#uint N> {
    value: Type used to represent unsigned integers. The generic parameter (<N>) specReturns the value of the first branch if the condition is true, otherwise the second branch.
Note that unlike software languages, Spade does not have conditional execution. You do not conditionally assign values inside if expressiosn, you compute a value and return it.
ies the number of bits
} impl<#uint N> Generic<N> { Functions together with Entities together with fn and Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. are the basic building blocks of Spade circuits. Unlike fn, entity can contain registers and therefore have state and unlike Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline., they do not have a statically known latency or Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. structure. and Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. are the basic building blocks of Spade circuits. Unlike entities and pipelines, functions have no internal state, they are combinational (pure). some_method(self) {} }

While at a glance, this may seem strange; why do you have to define the generic parameter and then use it separately, it adds some extra expressive power to the language, and plays well with traits as we shall see later. For now, one happy consequence of this is that you can implement methods on a subset of the generic parameters. For example, the standard library defines methods for converting integers with a size that is a multiple of 8 into bytes, but does not do so for other integers:

impl Type used to represent unsigned integers. The generic parameter (<N>) specReturns the value of the first branch if the condition is true, otherwise the second branch.
Note that unlike software languages, Spade does not have conditional execution. You do not conditionally assign values inside if expressiosn, you compute a value and return it.
ies the number of bits
{ /// Converts `self` into an array of bytes in big-endian order. 0x1234 /// becomes [0x12, 0x34]. pub Functions together with Entities together with fn and Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. are the basic building blocks of Spade circuits. Unlike fn, entity can contain registers and therefore have state and unlike Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline., they do not have a statically known latency or Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. structure. and Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. are the basic building blocks of Spade circuits. Unlike entities and pipelines, functions have no internal state, they are combinational (pure). to_be_bytes(self) -> [Type used to represent unsigned integers. The generic parameter (<N>) specReturns the value of the first branch if the condition is true, otherwise the second branch.
Note that unlike software languages, Spade does not have conditional execution. You do not conditionally assign values inside if expressiosn, you compute a value and return it.
ies the number of bits
; 2] { // ... } /// Converts `self` into an array of bytes in little-endian order. 0x1234 /// becomes [0x34, 0x12]. pub Functions together with Entities together with fn and Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. are the basic building blocks of Spade circuits. Unlike fn, entity can contain registers and therefore have state and unlike Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline., they do not have a statically known latency or Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. structure. and Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. are the basic building blocks of Spade circuits. Unlike entities and pipelines, functions have no internal state, they are combinational (pure). to_le_bytes(self) -> [Type used to represent unsigned integers. The generic parameter (<N>) specReturns the value of the first branch if the condition is true, otherwise the second branch.
Note that unlike software languages, Spade does not have conditional execution. You do not conditionally assign values inside if expressiosn, you compute a value and return it.
ies the number of bits
; 2] { // ... } }
Here, the implementation is only for uint<16> and the standard library includes a similar block for 24 and 32 bit uints too, as the type system is not quite powerful enough to express that the size has to be a multiple of 8 and that the output size is then size/8.

  1. In reality, they are not defined in Spade itself, but are instead primitive types in the compiler. ↩︎