Ports and wires

If you prefer documentation in video form there is a talk available on this topic.

Most types in Spade represent values. However, in digital design it is sometimes beneficial to also reason about connections between modules, ports. Spade separates values from ports because the semantics are a bit different.

The basic building block of ports are immutable wires, denoted by &T, and mutable wires: &mut T.

Because they represent connections rather than values, ports are not delayed by reg statements in pipelines, and cannot be stored in registers.

Immutable wires

An immutable wire can be read using the dereference operator *. Apart from that, they behave very similar to normal values.

Mutable wires

Mutable wires are used to communicate values in the opposite direction than what is usually done. Entities usually receive values from its inputs and produce values from its outputs. On the other hand, an entity which receives a mutable wire can set the value of that wire, and an entity that returns a mutable wire will be able to read values from that wire.

The primary use case for mutable wires is to group related signals together. As an example consider a memory module which takes two addresses and returns the content of the memory at those addresses. Without mutable wires, It looks like this:

entity memory(addr0: Addr, addr1: Addr) -> (Data, Data) {...}

as you can see, there is nothing here that says which address is associated with which output value.

Similar problems exist in the module which uses the memory value, it returns an address and receives data as one of its inputs, but the link between them is not at all obvious:

entity consumer(data: Data) -> Addr {...}

Additionally, if the addres and data are used deep within a module hierarchy, one has to propagate the address and return value manually via the return value of everything in the hierarchy.

With wires, the memory and consumer would instead look like this:

entity memory() -> ((&mut Addr, &Data), (&mut Addr, &Data)) {...}

entity consumer(memory_port: (&mut Addr, &Data));

the whole memory port, here modelled as a tuple, is passed around as a single thing.

Mutable wires must be set

One important property of mutable wires is that once you have access to one, you must make sure that it receives a value. You can either do that by setting the value using the set-statement or by passing the mutable value along to another unit which is then responsible for setting the value. You also are not allowed to set a mutable wire more than once. Doing so would cause ambiguity in which value should actually be connected, since it is essentially a physical wire.

Luckily, the compiler checks both of these properties.

In cases where you want to conditionally assign a value to a wire, you cannot use multiple set statements inside if-expressions, instead you should compute the wire to output using an if- or match-expression, and assign that resulting value to the wire.

struct ports

In the example above, we used tuples to group related wires together, but often you also want to give names to each wires. Then, the struct port construct comes in handy. It behaves like a struct, but contains only wires instead of only values.

For example, the memory port shown in the previous example can be written as a struct port

struct port MemoryPort {
    addr: &mut Addr,
    data: &Data
}

entity memory() -> (MemoryPort, MemoryPort) {...}

entity consumer(memory_port: MemoryPort);