Type System🔗
DFiant is a Scala library, hence it inherently supports type safe and rich language constructs. DFiant brings type driven development concepts to hardware design, by creating an extensible dataflow class hierarchy, with the trait DFAny
at its head (similar concept to Scala's Unified Types hierarchy). DFAny
contains all fields that are common to every dataflow variable (e.g., .width
represents the number of bits contained by the variable). Fig. 1 illustrates a simplified inheritance diagram of DFiant's dataflow types. Further explanation is given in the next section.
Fig. 1: DFiant dataflow types: simplified inheritance diagram
Mutable Dataflow Variables and Immutable Dataflow Values🔗
DFiant supports dataflow variables mutability via the :=
operator. Do not confuse with Scala-level mutability which is enabled by using var
instead of val
. Each dataflow class has two variations: an immutable class, which inherits from DFAny.Val
and a mutable class, which inherits from DFAny.Var
and accepts :=
. The difference between the types enforces an immutable right-hand-side (RHS), where required, and a mutable variable creation.
Consider, for instance, the DFiant implementation of g
in Table \reftbl:StateExDefImpl
: a
is immutable because it is a RHS addition between the dataflow variable i
and a literal value 5
. Contrarily, c
is mutable, since it is a dataflow variable constructor (.init
constructs a new initialized variable, while preserving the mutability trait).
Fig. 1 demonstrates a dual class definition for every type (immutable and mutable). The naming convention helps to reason about the mutability. For example, DFBits
and DFBits.Var
are immutable and mutable classes, respectively. Constructing a new variable via DFBits
(e.g, val a = DFBits[5]
) returns the mutable DFBits.Var[5]
. Usually, we either receive or return an immutable type, hence we do not require annotating a type with its mutable variation. In cases where we want to return a mutable type, we annotate it as an output port (see Section~\refsec:io_ports
).
Don't use var
with dataflow values/variables
Because the semantics may get confusing, we enforced a compiler error if a dataflow variable is constructed and fed into a Scala var
reference. For example var a = DFUInt(8)
will generate a Scala compiler error.
Bit-Accurate Operations, Type Inference, and Data Structures🔗
All DFiant's dataflow types are bit-accurate and structurally static, with their bit-width set upon construction (e.g., DFBits[5]
is a 5-bit vector). Operations between dataflow variables produce a bit-accurate result with the proper type inference. For example, an addition between an unsigned 5-bit variable (DFUInt[5]
) and a signed 10-bit variable (DFSInt[10]
) produces an adder that can be implicitly converted to a 10-bit signed variable, if carry is not required, or an 11-bit signed variable by explicitly invoking .wc
from the addition.
DFiant also allows operations between dataflow types and their corresponding Scala numeric types, by treating the Scala numeric types as constants (e.g., addition between DFSInt
and Integer
variables). A constant in the dataflow graph is a node that can produce infinite tokens of the same value.
Bit Aliasing and Casting🔗
Aliasing in DFiant enables referencing a part of a dataflow variable, by invoking .bits(hiIdx, loIdx)
, which creates a bits vector alias that references the original variable at the given index parameters. Every change of a dataflow variable affects its alias and vice versa (similar to VHDL's signal aliasing). Since this function also casts the variable as DFBits
, this feature is used as a raw-data cast between different dataflow types. Aliasing of an alias is also possible, while maintaining relative bits indexing. Aliasing preserves the mutability trait: an alias of an immutable value is immutable, while an alias of a mutable variable is mutable.
Fig.~\reffig:Aliasing
demonstrates aliasing code and its effect on the contents of a dataflow variable (bits128
). Each line code does as follows:
- Constructs a new 128-bit vector,
bits128
, and clears it. - Creates a new alias,
alias64
, which references the most significant 64 bits ofbits128
. Sincebits128
is aDFBits
variable, there is no need to invoke.bits()
, and we can apply the required indexes directly. - Creates a new alias,
alias32
, which references the least significant 32 bits ofalias64
, which reference bits 64 to 95 ofbits128
. - Constructs a new double precision floating point dataflow variable,
dbl
, and initialize its value as1.0
(hexadecimal value of0x3FF00...0
). - Modifies the least significant byte of
dbl
. - Sets the most significant bit of
bits128
. - Assigns
dbl
to the least significant 64 bits ofbits128
through casting. All the bits ofdbl
are selected because.bits()
is invoked without index parameters. - Modifies a byte of
bits128
.
Basic Types🔗
DFiant pays