Methods

A feature that is almost universal among modern programming languages is methods, and Spade is no exception. While many people associate methods with full object oriented programming, they are really an independent concept that turns out to be very useful both with and without OOP.

Like many languages, methods in Spade are called using . notation:

x.method();

Just like Spade separates fn, entity, and pipeline units, it separates them for methods. To call entity and pipeline methods, the same inst keyword is used, but after the ., i.e.

x.inst is required when instantiating entities, units with state, as opposed to 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). which are combinational, or pure in software terms. entity_method();

x.inst is required when instantiating entities, units with state, as opposed to 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). which are combinational, or pure in software terms.(5) pipeline_method(clk);

Defining Methods

In most mainstream languages that include methods, the methods a type has are defined along with the type, for example:

class SomeClass {
  public:
    void method() {}
  private:
    // ...
}

However, in Spade methods are added in separate blocks from their definition, so called impl blocks:

struct SomeStruct {}

impl SomeStruct {
    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). method(self) {}
}

The self parameter is special. It must be the first argument of a method and contains the value of the signal the method is called on, analogous to this in C++ and Java. Unlike those languages however, you have to explicitly write self.field to refer to struct members.

As an example, we can define a struct containing memory addresses used in a processor, and give it a method to return an address that is aligned to a 4 byte boundary:

struct Addr {
    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 Addr { 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). aligned(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
{ self.value % 4 } }

Remember that Spade has no mutable variables! In the same way that you cannot modify a parameter to a function, you cannot change the value of the .value field in a method. You can only produce a new value.

Naturally, methods can also take parameters in addition to self:

impl Addr {
    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). offset(self, offset: 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
) -> Addr { Self(self.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.
offset) } }

But What Is a Method?

At this point, it is worth reminding ourselves that we are describing hardware, not software, and think about what a method actually means. A method instance is nothing more than a unit instance with different syntax. Just like non-method unit instances, every time you instantiate a method, the hardware described by the method will be instantiated in the final circuit.

You may then wonder why methods exist in the first place, if they do the same thing as free-standing functions. In short, they allow writing more concise and readable code, especially if the code transforms a value in some way. For example, consider the aligned and offset functions above. Using them, we can neatly define a transformation from an address to an offset address aligned to a value as:

addr
    .offset(10)
    .aligned()

The same code, written with free-standing functions would require prefixing the functions with the type they operate on, since offset and align are operations that could be applied to many different types. In addition, the functions need to be imported if they are defined in a different namespace:

use addr_impl::offset_addr;
use addr_impl::align_addr;
use address_struct_example::Addr;

When using the functions, the transformation has to be read inside-out:

align_addr(offset_addr(addr, 10))

whereas the methods allow reading the transformation as a sequence of steps, which in many cases is more natural.

Defining Methods on Foreign Types

An advantage of impl blocks being standalone from the types they define methods for is that you can add methods to existing types. For example, if you define a library that implements an efficient integer division unit, you can add a divide method to the int type in the standard library even though you do not have access to the definition of int.

Warning

It is worth noting that you should wield this power carefully. If multiple impl blocks define the same method, you will get an error. This is true even if the method is implemented in another library. As an example, say you implement a divide method for integers in your library. Meanwhile someone else implements divide in a different way in another library. If a user depends on both of these libraries, they will now get a compiler error complaining about conflicting methods.

Rust has a rule to prevent this called the Orphan rule, which essentially says that you can only add methods to types defined in your library without defining a new trait. Currently, Spade does not have an orphan rule, so you should be careful when defining methods on foreign types, especially if you are developing a library that others may depend on.