Interfacing with Verilog

It is often desirable to interface with existing Verilog, either instantiating a Verilog module inside a Spade project, or including a Spade module as a component of a larger Verilog project. Both are quite easy to do as long as you have no generics on the Spade side, and no parameters on the Verilog side. Generics and parameters may be supported in the future.

Instantiating a Verilog module

If you have a Verilog module that you want to instantiate from Spade, you need to add a stub for it in your Spade project. This is done by defining a function, entity or pipeline1 but using __builtin__ instead of the body of the unit. For example,

struct Output {
    valid: bool,
    value: int<16>
}
entity external_module(clk: clock, x: int<8>) -> Output __builtin__

While this works, Spade will "mangle" names to avoid namespace collisions and collisions with keywords, so this would in practice look for a module like

module \your_project::your_file::external_module(
    input clk_i,
    input[7:0] x_i,
    output[16:0] output__
);

Changing your module to follow this signature would work, but is not very convenient, the more convenient thing is to add #[no_mangle] to both the module and its inputs in order to use the raw names instead of the mangled names:

#[no_mangle]
entity external_module(
    #[no_mangle] clk: clock,
    #[no_mangle] x: int<8>
) -> Output __builtin__

Now, the resulting Verilog signature is

module external_module(
    input clk_i,
    input[7:0] x_i,
    output[16:0] output__
);

As you can see it still has a single output__ which is both inconvenient if you can't change the signature, and annoying since you need to know how Spade packs structs in order to generate the correct signals. Spade currently does not even define the packing of the structs, so we need to do something about this. The solution is to use mutable wires to generate Verilog ouputs

Changing our module to

#[no_mangle]
entity external_module(
    #[no_mangle] clk: clock,
    #[no_mangle] x: int<8>
    #[mo_mangle] output_valid: &mut bool,
    #[no_mangle] output_value: int<16>,
) __builtin__

results in

module external_module(
    input clk_i,
    input[7:0] x_i,
    output output_valid,
    output[15:0] output_value
);

which is a normal looking Verilog signature.

One downside of this however, is that the interface to this module isn't very Spadey, so typically you will want to define a wrapper around the external module that provides a more Spade-like interface

use std::ports::new_mut_wire;
use std::ports::read_mut_wire;
// Put the wrapper inside a `mod` to allow defining a spade-native unit of the same name.
mod extern {
    #[no_mangle]
    entity external_module(
        #[no_mangle] clk: clock,
        #[no_mangle] x: int<8>
        #[mo_mangle] output_valid: &mut bool,
        #[no_mangle] output_value: int<16>,
    ) __builtin__
}

struct Output {
    valid: bool,
    value: int<16>
}

entity external_module(clk: clock, x: int<8>) -> Output {
    let valid = inst new_mut_wire();
    let value = inst new_mut_wire();
    let _ = inst extern::external_module$(clk, x, output_valid: valid, output_value: value);
    Output {
        valid: inst read_mut_wire(valid),
        value: inst read_mut_wire(value),
    }
}

With this, we have the best of both worlds. A canonical spade-entity on the Spade side, and a canonical Verilog module on the other.

Finally, to use the Verilog module in a Spade project, the Verilog file containing the implementation must be specified in swim.toml under extra_verilog at the root, or extra_verilog in the synthesis section. This takes a list of globs that get synthesized with the rest of the project.

1

See the documentation for units for more details. Most of the time, you probably want to use entity for external Verilog.

Instantiating Spade in a Verilog project

Instantiating Spade in a larger Verilog project is similar to going the other way around as just described. Mark the Spade unit you want to expose as #[no_mangle] on both the unit itself and its inputs. Prefer using &mut instead of returning output values, as that results in a more Verilog-friendly interface.

To get the Verilog code, run swim build, which will generate build/spade.sv which contains all the Verilog code for the Spade project, including your exposed module.