Domain handling
Spades handling of clocks and resets is currently pretty poor. Clocks and resets are passed as special parameters to the reg construct, and arguments to entities and pipelines. Normally I believe the clock is either going to be implicit because there is only 1, or you have multiple clocks in which case you want the compiler to catch accidental clock domain crossing and enforce proper handling of clocks. When I say clocks, I think I am also talking about resets, which would usually be bundled into a domain.
A single implicit domain
Tackling the simple problem first, if we ignore multiple domains, we can infer the domain used for registers and entities, meaning that we could simplify the reg expression to
reg <name> reset (to_value) = ...
When instantiating entities and pipelines, the clock could also be infered, i.e.
entity X(clk: clk, rst: bool, args...) ...
can be instantiated with
inst X(args...)
This would probably mean that we also want to handle clk
and rst
separately, probably removing them from the argument list, at least in the
initial phases of the compiler
This works well until we try to instantiate a spade module in verilog, at which point we do want clk and rst exposed. A simple solution there would be to expose them in the output module, but never in spade.
Multiple domains
In some cases, a module will need to interact with multiple clock domains, at which point one needs to be explicit about them. In this case, I propose a syntax similar to the lifetime syntax of rust, where clock domains are part of the types of modules and signals.
For example, an entity with 2 domains ('a
and 'b
) would look like
entity<'a, 'b> name(x: 'a int<8>, y: 'b int<8>) -> 'b int<8> {
// ...
}
inside the body, one would be able to write code normally with implicit domains
until they become ambiguous. I.e. a register reg ... = <expr>;
where all
signals in <expr>
belong to 'a
uses the 'a
clock and reset.
If domains are mixed, i.e. x + y
, an error would be emitted, and the user
would need to insert some synchronisation. I'm not sure exactly how this
synchronisation would be performed, right now I'm tempted to leave that to the
user in the form of a unsafe
or allow domain crossing
block.
Implementation details
In practice, one should probably not make a special case for single clock domains, instead,
by default, an entity without any domain arguments is given an implicit 'x
domain, so the
entity X
from before would be compiled to
entity<'a> X(arg1: 'a ...) ...
This will never be visible to the user, unless they instantiate the X
module and pass
missmatched domains to the arguments.
This again, is similar to how rust handles lifetimes.
Domain kinds
Not all domains are created equal (probably). For example, there has to be a
domain with no clock attached, for external signals. This could be denoted by
'extern
or something, i.e. a top module would probably look like
entity top(a: 'extern bool, b: 'extern ...) {}
this domain is special however, as there is no way to create a register for a
domain with no associated clock. reg ... = a
should result in a compiler error.
A related problem might present itself for different types of reset. For example, one might want to express a unit which requires an async reset. Or, one might want to build a unit that is reset agnostic. However, that needs to change the codegen of the instanciated module depending on what kind of reset is used.
For now, I suppose we can prototype things by ignoring all of those problems by
continuing to assume async active high resets, and probably only handle the
extern
domain separately as that is something that does come up in practice.