logo Spade Blog

Spade 0.8.0

Posted 2024-05-14 by The Spade Developers

Today we're releasing v0.8.0 of Spade. This release adds a bunch of new stuff to the standard library, fixes a bunch of papercuts around test and adds inout<T> to work with inout ports. We also had 2 new contributors in this release which is very fun to see, thanks @0xC01DC0FFEE and @phire

Add reduce_* functions (first contribution)🔗

@0xC01DC0FFEE added three new functions to std::ops:

  • fn reduce_and<#N>(x: uint<N>) -> bool
  • fn reduce_or<#N>(x: uint<N>) -> bool
  • fn reduce_xor<#N>(x: uint<N>) -> bool

These compute the and, or and xor of all bits in a number. reduce_xor is especially useful for computing parity.

stage improvements (first contribution)🔗

Scott Mansell (@phire) has fixed several issues this release. The most important fix resolves a long-standing issue where referencing pipeline variables using stage inside blocks would cause a panic. With this change, you can now write

    let x = if condition {stage(+1).y} else {stage(+2).y}
  reg;
    let y = /* ... */;
  reg;

Higher level memory primitives in std::mem🔗

std::mem::dp_bram provides a more structured primitive for working with dual port memories. Unlike the raw std::mem::clocked_memory primitive which can be used to model any memory but is quite clunky to use in the common case, the dp_bram provides a memory with one read port and one write port that can belong to different clock domains.

Writing to the memory is done using the std::Mem::WritePort struct

entity writer(
    clk: clock,
    wport: WritePort<10, uint<16>>,
) {
    let (addr0, write_val0) = /* ... */;

    set wport.addr = addr0;
    set wport.write = write_val0;
}

And reads are done on the ReadPort struct, preferably using std::mem::read_read_port

pipeline(1) reader(
    clk: clock,
    rport: ReadPort<10, uint<16>>,
) -> [[uint<16>; 3]; 3] {
      let addr0 = /* ... */;
      let addr1 = /* ... */;

      let out0 = inst(1) read_read_port(clk, addr0, rport)
      // Since the memory read unit is a pipeline, we can't accidentally forget that the memory
      // has read latency
  reg;
      // Do something fun with the read values!
}

The dp_bram module returns two ports which you can then pass along to the reader and writer

entity top(clk: clock) {
  let (write, read) = inst dp_bram::<1024, uint<16>, 10>$(write_clk: clk, read_clk: clk);

  let _ = inst writer(clk, write);
  let _ = inst reader(clk, read);
}

One big advantage of this is that since the memory ports are struct port, the compiler will ensure that there aren't multiple users of the ports. For example, the following code results in a compilation error

entity top(clk: clock) {
  let (write, read) = inst dp_bram::<1024, uint<16>, 10>$(write_clk: clk, read_clk: clk);

  let _ = inst writer(clk, write);
  let _ = inst reader(clk, read);
                        // ^^^^ First use here
  let _ = inst reader(clk, read);
                        // ^^^^
                        // Use of consumed resource
}

Clock domain crossing primitives🔗

The second addition to the stdlib is the new std::cdc module which contains primitives for crossing clock domains. The primary additions are std::cdc::sync2_bool, std::cdc::sync_wide and std::cdc::handshake which provide primitives for a few different crossing scenarios. All of these are extremely danger if used incorrectly, so make sure to read the documentation for the modules to make sure that you satisfy the constraints they have to behave correctly.

Test improvements🔗

We have made several improvements to the testing system in Spade

A minor but important change is that tests now support modules that return void. In addition, the verilator wrapper now works on modules with &mut inputs.

For verilator tests, we have also added .spade_repr to fields which returns a human-readable Spade representation of the field. This is useful if you need to, for example, print a result.

Using these changes, it is possible to use the simulation speed of Verilator to write "interactive" test benches, here is an example of a testbench that visualizes the memory access pattern of a camera project I'm working on:

Screenshot of a window that visualizes the memory access pattern of a camera output

Latch-Up presentation🔗

Two weeks ago, Frans presented Spade at Latch-Up 2024 in Boston. You can see a recording of the talk here: