Type System
DFHDL is a Scala library and thus inherently supports type-safe and modern language constructs. This chapter covers the rules and API of this type system.
Check out the benefits of the DFHDL type system
-
Strongly-typed
Most type checks are performed statically, enforcing strict rules that help avoid ambiguity.
//8-bit unsigned input val u8 = UInt(8) <> IN //2-bit unsigned input val u2 = UInt(2) <> IN val y1 = u8 - u2 //ok // Error prevents ambiguous behavior // when a wider num is subtracted from // a narrow num. val y2 = u2 - u8 //error
-
Bit-accurate
Each DFHDL value has a defined bit-width, which is used to enforce rules that prevent data loss.
//8-bit unsigned input val u8 = UInt(8) <> IN //8-bit signed output val s8 = SInt(8) <> OUT // Error prevents data loss when u8 is // converted to a 9-bit signed to be // assigned to s8, which is only 8-bits // wide. s8 := u8 //error
-
Composable
Types can be composed through structs or tuples to form new, combined types.
//new Pixel type as a structure //of two unsigned 8-bit numbers case class Pixel( x: UInt[8] <> VAL, y: UInt[8] <> VAL ) extends Struct val pixel = Pixel <> VAR //select and assign fields pixel.x := pixel.y
-
Expandable
New types can be defined, and methods can be added for entirely new or existing types.
//new AESByte type of unsigned 8-bit num case class AESByte() extends Opaque(UInt(8)) //define addition between two AESByte //values as a xor operation extension (lhs: AESByte <> VAL) def +(rhs: AESByte <> VAL): AESByte <> DFRET = (lhs.actual ^ rhs.actual).as(AESByte) val x, y = AESByte <> VAR val z = x + y //actually XOR
DFHDL Values
Each DFHDL value is simply a Scala object that has two critical fields:
-
(Shape) Type, aka DFType
Determines the bit-width and bit-structure of the value. Currently the supported types are:
- DFHDL Bit/Boolean:
Bit
/Boolean
- DFHDL Bit Vector:
Bits
- DFHDL Integer:
UInt
/SInt
/Int
-
DFHDL Fix-Point (future work)
-
DFHDL Flt-Point (future work)
-
DFHDL String (future work)
- DFHDL Enumeration:
enum ... extends Encoded
- DFHDL Vector:
_CellType_ X _Dim_
- DFHDL Structure:
... extends Struct
- DFHDL Tuple:
(T1, T2, ..., Tn)
- DFHDL Opaque:
... extends Opaque
- DFHDL Double:
Double
- DFHDL Time/Freq:
Time
/Freq
- DFHDL Unit (Void):
Unit
- DFHDL Bit/Boolean:
-
(Access) Modifier
Determines what kind of access the user has on the value. User explicit modifiers:
- Variable:
VAR[.REG][.SHARED]
- Port:
IN
/OUT[.REG]
/INOUT
- Constant:
CONST
- Struct Field:
VAL
- Method Param:
VAL
- Method Return:
DFRET
/RTRET
/EDRET
Although this mechanism can be quite complex under the hood, the explicit modifiers available to the user are straightforward.
- Variable:
Internal Type-System Hierarchy (For Advanced Users)
DFHDL brings type-driven development concepts to hardware design, by creating an extensible type class hierarchy. Any DFHDL value is a Scala object instance of the class DFVal[T <: DFTypeAny, M <: ModifierAny]
, where T
is the type (shape) of value and M
is a modifier that sets additional characteristics of the DFHDL value, like if it's assignable, connectable, initializable, etc.
For example, the Scala value x
which references a port declared like val x = Boolean <> IN
has the type DFVal[DFBool, Modifier.Dcl]
.
Variable and Port Declarations
Ports are DFHDL values that define the inputs and outputs of a design. Variables are DFHDL values that represent internal design wiring, logic, or state.
Syntax
val _name_ = _dftype_ <> _modifier_ [init _const_]
_name_
is the Scala value name reference for the DFHDL port/variable you constructed. The DFHDL compiler preserves this name and uses it in error messages and the final generated artifacts (e.g., Verilog module or VHDL entity port names)._name_
can also be a series of names separated by commas to declare several equivalent ports/variables. More information is available under the naming section._dftype_
is set according to the shape type (DFType) of the DFHDL value. Each of the supported DFTypes have their own constructors. See relevant sections for the DFHDL DFType you wish to construct.- __<>
__ is the operator applied between a
dftypeand a
modifierto construct the Scala value that represents a DFHDL variable or port accordingly. Note: the same
<>operator is used as a language construct for declaring [connections][connection]. Thanks to Scala method overloading,
<>` can be shared for both use-cases with no issues (due to the Scala argument type difference). _modifier_
is set with one of the following:VAR
- to construct a variableIN
- to construct an input portOUT
- to construct an output portINOUT
- to construct a bidirectional input-output portVAR.REG
/OUT.REG
- to construct a registered variable or output port (available only in RT domains)VAR.SHARED
- to construct a shared variable that can be assigned in more than one domain (this feature is to be used scarcely, to model unique designs like True Dual-Port RAM)
init
is an optional construct to initialize the DFHDL variable/port declaration history with the applied_const_
value._const_
is the state history initialization value which must be a constant that is supported by the DFType_dftype_
. Under DF domain only,_const_
can also be represented by a Scala Tuple sequence of constant initialization values that are supported by the DFType_dftype_
.
Port/Variable declaration examples | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Transitioning from Verilog
TODO
Transitioning from VHDL
TODO
Rules
Scope
-
Variables can be declared in any DFHDL scope, except global scope, meaning within DFHDL designs, domains, interfaces, methods, processes, and conditional blocks.
1 2 3 4
//error: Port/Variable declarations cannot be global val x = Bit <> VAR class Foo extends DFDesign: val o = Bit <> OUT
-
Ports can only be declared at the scopes of DFHDL designs, domains, and interfaces. Other scopes are not allowed.
1 2 3 4 5 6
class Foo extends DFDesign: val i = Boolean <> IN if (i) //error: Ports can only be directly owned by a design, a domain or an interface. val o = Bit <> OUT o := 0
Naming
Ports and variables must always be named, and cannot be anonymous.
Anonymous declaration elaboration error example | |
---|---|
1 2 3 |
|
As you'll read later on, constants and other values can be anonymous.
Connectable
Ports and variables are connectable, meaning they can be the receiving (drain/consumer) end of a connection <>
operation.
For input ports this occurs outside their design scope, while connecting to an external value.
For output ports and variables this occurs only within their design scope, while connecting to an internal value.
1 2 3 4 |
|
Assignable (Mutable)
Output ports, input-output ports, and variables are assignable (mutable), when they can be the receiving (drain/consumer) end of an assignment :=
/:==
operation, which occurs only within their design scope. Input ports can never be assigned (are immutable). Registered ports and variables are assignable only when referencing their registers' input via .din
selection (referencing a register without .din
is always considered to be its output, which is immutable).
Assignment semantics are a key difference between the different design domains DFHDL has to offer. Here are some basic examples:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
|
Variability (Not Constant)
DFHDL ports and variables are never considered to be constant (even when connected/assigned only once and to a constant value) for elaboration. Later compilation stages can apply further constant propagation steps that reduce logic utilization.
1 2 3 4 5 6 |
|
INOUT
Port Limitation
INOUT
(bidirectional) ports are generally used to define IO pins of top-level device connectivity (e.g., protocols like I2C benefit from such ability). They are not meant for inter-device wiring reduction, and thus should be used scarcely within their intended purpose. Throughout the years they were also used to workaround HDL limitations like reading from output ports in VHDL'93, or lack of interfaces. Since DFHDL has none of these limitations, we encourage you to use INOUT
for their intended purpose only, as synthesis tools for FPGAs and even ASICs will not cooperate. Although, theoretically, in DF domain we can enable bidirectional communication that can later be compiled into two separate ports, there is no real value behind this.
1 2 3 |
|
Grouping
Ports can be grouped together in dedicated interfaces.
Transitioning
Transitioning from Verilog
TODO
Transitioning from VHDL
TODO
Differences from Scala parameters/fields
TODO: Data validity, Number of outputs
Constant/Literal Values
In DFHDL there are three methods to construct constant DFHDL values:
- Literal value generators: These language constructs directly generate constant DFHDL values. Currently, these are:
- Constant candidates: Various Scala values can become DFHDL values, as.
Constant declaration syntax
val _name_: _dftype_ <> CONST = _value_
- Constant value propagation: Cleaners
Syntax
Rules
Unconnectable
Constant values are not connectable, meaning they can never be the receiving (drain/consumer) end of a connection <>
operation.
Unassignable (Immutable)
Constant values are immutable and cannot be assigned, meaning they can never be the receiving (drain/consumer) end of an assignment :=
/:==
operation.
DFHDL Value Statement Order & Referencing
Any DFHDL value must be declared before it can be referenced in code. Other than this (pretty intuitive) limitation, no other limitations exist and ports, variables, constants, and other values may be freely distributed within their approved scope space. During the compilation process, you can notice that the compiler reorders the port declarations so that they always come second to constant declarations, and variables right after.
DFHDL Value Connections
After ([or during][via-connections]) a design instantiation, its ports need to be connected to other ports or values of the same DFType by applying the <>
operator. Variables can also be connected and used as intermediate wiring between ports. Output ports can be directly referenced (read) without being connected to an intermediate variable. For more rules about design and port connectivity, see the relevant section.
Successful port/variable connection example | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Failed port/variable connection example | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
DFHDL Value Assignment (Mutation)
Both output ports and variables are [mutable][mutability] and can be assigned with values of the same DFType and only within the scope of the design they belong to. Input ports cannot be directly assigned, and require an intermediate variable connected to them to modify their value. Generally assignments to DFHDL values are applied through the :=
operator. In processes under ED domains there are two kind of assignments: blocking assignments via :=
, and non-blocking assignments via :==
. Other domains support only blocking assignments via :=
. Read more on domain semantics in the [next section][domain-semantics].
See the connectivity section for more rules about mixing connections and assignments.
Successful port/variable connection example | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Don't use var
with DFHDL values/designs
Because the semantics may get confusing, we enforced a compiler warning if a DFHDL value/design is constructed and fed into a Scala var
reference. You can apply a Scala @nowarn
annotation to suppress this warning.
Warning when using a Scala `var` and suppression example | |
---|---|
1 2 3 4 5 6 7 8 9 10 |
|
Bit-Accurate Operations and Type Inference
DFHDL provides bit-accurate operations and strong type inference for bit-level manipulations. Here are the key features:
Bit Selection and Slicing
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Bit Operations
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Multiple Variable Assignment
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Width Inference and Resizing
1 2 3 4 5 6 7 |
|
Bubble Values
- RT and ED - Don't Care / Unknown
- DF - Stall
DFHDL Value Candidates
TODO: requires explanation The candidate produces a constant DFHDL value if the candidate argument is a constant.
Operation supported values for an argument of DFType T
Bits assignment and concatenation operation candidates example | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Bit
/Boolean
DFHDL Values
Bit
DFHDL values represent binary 1
or 0
values, whereas Boolean
DFHDL values represent true
or false
values, respectively. The Bit
and Boolean
DFHDL values are generally interchangeable, and automatically converted between one and the other.
Should I use Bit
or Boolean
DFTypes?
Although they are interchangeable, it's generally recommended to use Boolean
DFHDL values with conditional if
statements, guards, or expressions, and Bit
DFHDL values for everything else. There could be constant parameters that are better defined as a true
or false
Boolean
values rather than 0
or 1
Bit
values.
Why have both Bit
and Boolean
DFTypes?
The main reason to differentiate between Bit
and Boolean
is that VHDL has both std_logic
and boolean
types, respectively. Verilog has only a single logic
or wire
to represent both. Indeed VHDL'2008 has relaxed some of the type constraints, but not enough. And nevertheless, DFHDL aims to support various HDL dialects, and thus enables simple implicit or explicit conversion between these two DFType values.
DFType Constructors
Use the Bit
or Boolean
objects/types to construct Bit
or Boolean
DFHDL values, respectively.
1 2 3 4 |
|
Candidates
- DFHDL
Bit
values. - DFHDL
Boolean
values. - Scala
1
or0
literal values. A regular ScalaInt
is not accepted. This candidate always produces a constant DFHDL value. - Scala
Boolean
values. This candidate always produces a constant DFHDL value.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
Operations
Explicit Casting Operations
These operations propagate constant modifiers, meaning that if the casted argument is a constant, the returned value is also a constant.
Operation | Description | LHS Constraints | Returns |
---|---|---|---|
lhs.bool |
Cast to a DFHDL Boolean value |
Bit DFHDL value |
Boolean DFHDL value |
lhs.bit |
Cast to a DFHDL Bit value |
Boolean DFHDL value |
Bit DFHDL value |
1 2 3 4 5 6 7 8 9 10 |
|
Bit History Operations
Currently these operations are only supported under ED domains. However, in upcoming DFHDL updates, support will be added across all domain abstractions.
Operation | Description | LHS Constraints | Returns |
---|---|---|---|
lhs.rising |
True when a value changes from 0 to 1 |
Bit DFHDL value |
Boolean DFHDL value |
lhs.falling |
True when a value changes from 1 to 0 |
Bit DFHDL value |
Boolean DFHDL value |
1 2 3 4 5 6 7 8 9 10 11 |
|
Transitioning from Verilog
Under the ED domain, the x.rising
and x.falling
operations are equivalent to the Verilog posedge x
and negedge x
, respectively.
In future releases these operations will have an expanded functionality under the other design domains.
Transitioning from VHDL
Under the ED domain, the x.rising
and x.falling
operations are equivalent to the VHDL rising_edge(x)
and falling_edge(x)
, respectively.
In future releases these operations will have an expanded functionality under the other design domains.
For more information see either the design domains or processes sections.
Logical Operations
Logical operations' return type always match the LHS argument's type. These operations propagate constant modifiers, meaning that if all arguments are constant, the returned value is also a constant.
Operation | Description | LHS/RHS Constraints | Returns |
---|---|---|---|
lhs && rhs |
Logical AND | The LHS argument must be a Bit /Boolean DFHDL value. The RHS must be a Bit /Boolean candidate. |
LHS-Type DFHDL value |
lhs || rhs |
Logical OR | The LHS argument must be a Bit /Boolean DFHDL value. The RHS must be a Bit /Boolean candidate. |
LHS-Type DFHDL value |
lhs ^ rhs |
Logical XOR | The LHS argument must be a Bit /Boolean DFHDL value. The RHS must be a Bit /Boolean candidate. |
LHS-Type DFHDL value |
!lhs |
Logical NOT | The argument must be a Bit /Boolean DFHDL value. |
LHS-Type DFHDL value |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
Transitioning from Verilog
Under the ED domain, the following operations are equivalent:
DFHDL Operation | Verilog Operation |
---|---|
lhs && rhs |
lhs & rhs |
lhs || rhs |
lhs | rhs |
lhs ^ rhs |
lhs ^ rhs |
!lhs |
!lhs |
Transitioning from VHDL
Under the ED domain, the following operations are equivalent:
DFHDL Operation | VHDL Operation |
---|---|
lhs && rhs |
lhs and rhs |
lhs || rhs |
lhs or rhs |
lhs ^ rhs |
lhs xor rhs |
!lhs |
not lhs |
Constant Meta Operations
These operations are activated during the elaboration stage of the DFHDL compilation, and are only available for constant Bit
/Boolean
DFHDL values.
Their use case is for meta-programming purposes, to control the generated code without the knowledge of the DFHDL compiler (could be considered as pre-processing steps).
Operation | Description | LHS Constraints | Returns |
---|---|---|---|
lhs.toScalaBitNum |
Extracts the known elaboration Scala BitNum (1 | 0 ) value from a constant DFHDL Bit /Boolean value |
Constant Bit /Boolean DFHDL value |
Scala BitNum value |
lhs.toScalaBoolean |
Extracts the known elaboration Scala Boolean value from a constant DFHDL Bit /Boolean value |
Constant Bit /Boolean DFHDL value |
Scala Boolean value |
The following runnable example demonstrates how such meta operation affect the elaborated design.
The Boolean
argument arg
of a design Foo
is used twice within the design:
first, in an if
condition directly; and second, in an if
condition after a Scala value extraction.
When referenced directly, the if
is elaborated as-is, but when the if
is applied on the extracted Scala value,
the if
is completely removed and either the block inside the if
is elaborated when the argument is true or completely removed if false.
1 2 3 4 5 6 |
|
1 2 3 4 5 6 |
|
1 2 3 4 5 |
|
Runnable example
import dfhdl.*
@top(false) class Foo(
val arg: Boolean <> CONST
) extends DFDesign:
val o = Bit <> OUT
if (!arg) o := 1
if (arg.toScalaBoolean) o := 0
@main def main =
println("Foo(true) Elaboration:")
Foo(true).printCodeString
println("Foo(false) Elaboration:")
Foo(false).printCodeString
Bits
DFHDL Values
Bits
DFHDL values represent vectors of DFHDL Bit
values as elements.
The vector bits width (length) is a positive constant number (nilable [zero-width] vectors will be supported in the future).
Differences between DFHDL Bits
and DFHDL Vector of Bit
In addition to Bits
, DFHDL also supports generic vectors of any DFHDL values.
One could therefore construct a generic vector with Bit
as the element DFType.
This vector has a different type than Bits
, since Bits
is a special case, both internally
in their implementations and externally in their API. Where applicable, both Bits
and generic
vector of Bits
have overlapping equivalent APIs.
DFType Constructors
Constructor | Description | Arg Constraints | Returns |
---|---|---|---|
Bits(width) |
Construct a Bits DFType with the given width as number of bits. |
width is a positive Scala Int or constant DFHDL Int value. |
Bits[width.type] DFType |
Bits.until(sup) |
Construct a Bits DFType with the given sup supremum number the vector is expected to reach. The number of bits is set as clog2(sup) . |
sup is a Scala Int or constant DFHDL Int value larger than 1. |
Bits[CLog2[width.type]] DFType |
Bits.to(max) |
Construct a Bits DFType with the given max maximum number the vector is expected to reach. The number of bits is set as clog2(max+1) . |
max is a positive Scala Int or constant DFHDL Int value. |
Bits[CLog2[width.type+1]] DFType |
Bits[W] |
Construct a Bits DFType with the given W width as Scala type argument (for advanced users). |
width is a positive Scala Int or constant DFHDL Int Singleton type. |
Bits[W] DFType |
1 2 3 4 5 6 7 |
|
Transitioning from Verilog
- Specifying a width instead of an index range: In Verilog bit vectors are declared with an index range that enables outliers like non-zero index start, negative indexing or changing bit order. These use-cases are rare and they are better covered using different language constructs. Therefore, DFHDL simplifies things by only requiring a single width/length argument which yields a
[width-1:0]
sized vector (for generic vectors the element order the opposite). - Additional constructors: DFHDL provides additional constructs to simplify some common Verilog bit vector declaration. For example, instead of declaring
reg [$clog2(DEPTH)-1:0] addr
in Verilog, in DFHDL simply declareval addr = Bits.until(DEPTH) <> VAR
.
Transitioning from VHDL
- Specifying a width instead of an index range: In VHDL bit vectors are declared with an index range that enables outliers like non-zero index start, negative indexing or changing bit order. These use-cases are rare and they are better covered using different language constructs. Therefore, DFHDL simplifies things by only requiring a single width/length argument which yields a
(width-1 downto 0)
sized vector (for generic vectors the element order the opposite). - Additional constructors: DFHDL provides additional constructs to simplify some common VHDL bit vector declaration. For example, instead of declaring
signal addr: std_logic_vector(clog2(DEPTH)-1 downto 0)
in VHDL, in DFHDL simply declareval addr = Bits.until(DEPTH) <> VAR
.
Literal (Constant) Value Generation
Literal (constant) DFHDL Bits
value generation is carried out through binary and hexadecimal string interpolation, a core Scala feature that was customized for DFHDL's exact use-case. There are also bit-accurate decimal and signed decimal interpolations available that produce UInt
and SInt
DFHDL values. If needed, those values can be cast to Bits
. No octal interpolation is currently available or planned.
Binary Bits String-Interpolator
b"width'bin"
- bin is a sequence of
0
,1
, and?
characters, each representing a single bit.?
indicates a bit bubble. The leftest (first) character is the most-significant bit (MSB), and the rightest (last) character is the least-significant bit (LSB). - Separators
' '
(space) or_
(underscore) withinbin
are ignored. bin
can also contain interpolated ScalaString
arguments through${arg}
.- width, followed by a
'
(apostrophe), is optional and specifies the bit vector's width. If omitted, the minimal width is inferred from the sequence length. If specified, leading zeros are added at the left of the sequence or the sequence is truncated based on thewidth
. Truncation only occurs if the MSBits being removed are zeros; otherwise, it triggers a compilation error. width
can be an interpolated argument of either ScalaInt
or a Constant DFHDLInt
value.- Returns: A constant DFHDL
Bits
value with the inferred or set width.
Binary Bits string-interpolation examples | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Transitioning from Verilog
This interpolation covers the Verilog binary literal use-cases, but also adds the ability for parametric width
to be set. The high impedance (high-Z) use-cases will be supported in the future, likely using a different language construct.
Transitioning from VHDL
This interpolation covers the VHDL binary literal use-cases, but also adds the ability for parametric width
to be set. The high impedance (high-Z) use-cases will be supported in the future, likely using a different language construct.
Hexadecimal Bits String-Interpolator
h"width'hex"
- hex is a sequence of hexadecimal characters (
0
-9
,A
-F
,a
-f
, and?
) where?
indicates a 4-bit bubble. Each character represents a 4-bit nibble, encoded such that the leftest bit is the most-significant bit.
The leftest (first) character is the most-significant nibble, and the rightest (last) character is the least-significant nibble. - Separators
' '
(space) or_
(underscore) withinhex
are ignored. hex
can also contain interpolated ScalaString
arguments through${arg}
.- Binary sequences can be embedded within
{bin}
tags, allowing integration of binary bit sequences of any length, not necessarily divisible by 4, between hex nibbles. - width, followed by a
'
, is optional and specifies the bit vector's width. If omitted, the minimal width is inferred from the sequence length. If specified, leading zeros are added or the sequence is truncated based on thewidth
. Truncation only occurs if the most significant bits being removed are zeros or bubbles; otherwise, it triggers a compilation error. width
can be an interpolated argument of either ScalaInt
or a Constant DFHDLInt
value.- Returns: A constant DFHDL
Bits
value with the inferred or set width.
Hexadecimal Bits string-interpolation examples | |
---|---|
1 2 3 4 5 6 7 8 9 10 |
|
Transitioning from Verilog
This interpolation covers the Verilog hexadecimal literal use-cases, but also adds the ability for parametric width
to be set. The high impedance (high-Z) use-cases will be supported in the future, likely using a different language construct.
Transitioning from VHDL
This interpolation covers the VHDL hexadecimal literal use-cases, but also adds the ability for parametric width
to be set. The high impedance (high-Z) use-cases will be supported in the future, likely using a different language construct.
Candidates
- DFHDL
Bits
values - DFHDL
Bit
orBoolean
values. This candidate produces a single bitBits[1]
vector. - DFHDL
UInt
values - Scala
Tuple
combination of any DFHDL values and1
/0
literal values. This candidate performs bit concatenation of all values, according their order in the tuple, encoded from the most-significant value position down to the least-significant value position. - Application-only candidate - Same-Element Vector (
all(elem)
).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Concatenated Assignment
DFHDL supports a special-case assignment of concatenated DFHDL Bits variables, using a Scala Tuple
syntax on LHS of the assignment operator. Both LHS and RHS bits width must be the same. This assignment is just syntactic sugar for multiple separate assignments and carried out during the design elaboration. The assignment ordering is from the first value at most-significant position down to the last value at least-significant position.
1 2 3 4 5 6 |
|
1 2 3 4 5 6 7 8 |
|
Runnable example
import dfhdl.*
//print the code after elaboration
given options.ElaborationOptions.PrintDFHDLCode = true
//set mode to elaborate only
given options.AppOptions.DefaultMode = options.AppOptions.DefaultMode.elaborate
@top class Foo extends DFDesign:
val i4 = Bits(4) <> IN
val b2 = Bits(2) <> OUT
val b3 = Bits(3) <> OUT
val b5 = Bits(5) <> OUT
(b2, b5, b3) := (b"101", i4, b"111")
UInt
/SInt
/Int
DFHDL Values
DFHDL provides three decimal numeric types:
- UInt
- Unsigned integer values
- SInt
- Signed integer values
- Int
- Constant integer values (used mainly for parameters)
DFType Constructors
Constructor | Description | Arg Constraints | Returns |
---|---|---|---|
UInt(width) |
Construct an unsigned integer DFType with the given width as number of bits. |
width is a positive Scala Int or constant DFHDL Int value. |
UInt[width.type] DFType |
UInt.until(sup) |
Construct an unsigned integer DFType with the given sup supremum number the value is expected to reach. The number of bits is set as clog2(sup) . |
sup is a Scala Int or constant DFHDL Int value larger than 1. |
UInt[CLog2[width.type]] DFType |
UInt.to(max) |
Construct an unsigned integer DFType with the given max maximum number the value is expected to reach. The number of bits is set as clog2(max+1) . |
max is a positive Scala Int or constant DFHDL Int value. |
UInt[CLog2[width.type+1]] DFType |
SInt(width) |
Construct a signed integer DFType with the given width as number of bits. |
width is a positive Scala Int or constant DFHDL Int value. |
SInt[width.type] DFType |
Int |
Construct a constant integer DFType. Used mainly for parameters. | None | Int DFType |
Candidates
- DFHDL decimal values of the same type
- DFHDL
Bits
values (via.uint
or.sint
casting) - Scala numeric values (Int, Long, etc.) for constant values
- Decimal string interpolation values
Operations
Arithmetic Operations
These operations propagate constant modifiers and maintain proper bit widths:
Operation | Description | LHS/RHS Constraints | Returns |
---|---|---|---|
lhs + rhs |
Addition | Both decimal types | Result with appropriate width |
lhs - rhs |
Subtraction | Both decimal types | Result with appropriate width |
lhs * rhs |
Multiplication | Both decimal types | Result with width = lhs.width + rhs.width |
lhs / rhs |
Division | Both decimal types | Result with lhs width |
lhs % rhs |
Modulo | Both decimal types | Result with rhs width |
Comparison Operations
Return Boolean values:
Operation | Description | LHS/RHS Constraints | Returns |
---|---|---|---|
lhs < rhs |
Less than | Both decimal types | Boolean |
lhs <= rhs |
Less than or equal | Both decimal types | Boolean |
lhs > rhs |
Greater than | Both decimal types | Boolean |
lhs >= rhs |
Greater than or equal | Both decimal types | Boolean |
lhs == rhs |
Equal | Both decimal types | Boolean |
lhs != rhs |
Not equal | Both decimal types | Boolean |
Constant Generation
Decimal String-Interpolator
The decimal string interpolator d
creates unsigned/signed integer constants (UInt
) from decimal values.
d"width'dec"
- dec is a sequence of decimal characters ('0'-'9') with an optional prefix
-
for negative values - width followed by a
'
is optional and specifies the exact width of the integer's bit representation - Separators
_
(underscore) and,
(comma) withindec
are ignored - If width is omitted, it is inferred from the value's size
- If specified, the output is padded with zeros or extended for signed numbers using two's complement
- Returns an unsigned
UInt[W]
for natural numbers and signedSInt[W]
for negative numbers, whereW
is the width in bits - An error occurs if the specified width is less than required to represent the value
Examples:
1 2 3 4 5 6 |
|
Signed Decimal String-Interpolator
The signed decimal string interpolator sd
creates signed integer constants (SInt
) from decimal values.
sd"width'dec"
- dec is a sequence of decimal characters ('0'-'9') with an optional prefix
-
for negative values - width followed by a
'
is optional and specifies the exact width of the integer's bit representation - Separators
_
(underscore) and,
(comma) withindec
are ignored - Output is always a signed integer type
SInt[W]
, regardless of whether the value is negative or natural - Width is always at least 2 bits to accommodate the sign bit
- An error occurs if the specified width is less than required to represent the value including the sign bit
Examples:
1 2 3 4 5 |
|
Examples
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Enumeration DFHDL Values
DFHDL supports enumerated types through Scala's enum feature with special encoding traits. Enums provide a type-safe way to represent a fixed set of values.
Enum Type Definition
1 2 |
|
Encoding Types
DFHDL supports several encoding schemes for enums:
-
Binary Encoded (default)
1 2
enum MyEnum extends Encoded: case A, B, C, D // Encoded as 0,1,2,3
-
One-Hot Encoded
1 2
enum MyEnum extends Encoded.OneHot: case A, B, C // Encoded as 001,010,100
-
Gray Encoded
1 2
enum MyEnum extends Encoded.Grey: case A, B, C // Encoded as 00,01,11
-
Custom Start Value
1 2
enum MyEnum extends Encoded.StartAt(10): case A, B, C // Encoded as 10,11,12
-
Manual Encoding
1 2 3 4
enum MyEnum(val value: UInt[8] <> CONST) extends Encoded.Manual(8): case A extends MyEnum(200) case B extends MyEnum(100) case C extends MyEnum(50)
Operations
Operation | Description | LHS/RHS Constraints | Returns |
---|---|---|---|
lhs == rhs |
Equality comparison | Same enum type | Boolean |
lhs != rhs |
Inequality comparison | Same enum type | Boolean |
lhs.bits |
Get raw bits representation | Enum value | Bits |
lhs.uint |
Get unsigned int representation | Enum value | UInt |
Pattern Matching
Enums can be used in pattern matching expressions:
1 2 3 4 5 6 |
|
Examples
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Vector DFHDL Values
DFHDL vectors allow creating arrays of any DFHDL type. Unlike Bits
which is specialized for bit vectors, generic vectors can hold any DFHDL type and support multi-dimensional arrays.
Vector Type Construction
The vector type is constructed using the X
operator between a base type and dimension:
1 |
|
Examples:
1 2 3 |
|
Initialization
Vectors can be initialized in several ways:
1 2 3 4 5 6 7 8 |
|
Operations
Element Access
Access individual elements using array indexing:
1 2 |
|
Vector-wide Operations
Operation | Description | Returns |
---|---|---|
vec.elements |
Get all elements as Scala sequence | Seq[BaseType] |
vec.size |
Get vector dimension | Int |
vec.bits |
Get bits representation | Bits |
Multi-dimensional Vectors
Multi-dimensional vectors are created by chaining X
operators:
1 2 3 4 5 6 7 8 9 |
|
Memory/RAM Implementation
Vectors are commonly used to implement memories and RAMs:
1 2 3 4 5 6 7 8 9 |
|
File Initialization
Vectors support initialization from files in various formats:
1 2 3 4 5 |
|
Struct DFHDL Values
DFHDL structures allow creating composite types by combining multiple DFHDL values into a single type. Structs are defined using Scala case classes that extend the Struct
trait.
Struct Type Definition
1 2 3 4 5 |
|
Field Access and Assignment
Fields are accessed using dot notation and can be assigned individually:
1 2 3 4 |
|
Nested Structs
Structs can be nested to create more complex data structures:
1 2 3 4 5 6 |
|
Operations
Operation | Description | LHS/RHS Constraints | Returns |
---|---|---|---|
lhs == rhs |
Equality comparison | Same struct type | Boolean |
lhs != rhs |
Inequality comparison | Same struct type | Boolean |
lhs.bits |
Get raw bits representation | Struct value | Bits |
Pattern Matching
Structs support pattern matching for field extraction:
1 2 3 4 |
|
Examples
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Tuple DFHDL Values
DFHDL tuples provide a way to group multiple DFHDL values together without defining a named structure. They are similar to Scala tuples but operate on DFHDL values.
Tuple Type Construction
1 |
|
Examples
1 2 3 4 5 6 7 8 9 |
|
Element Access
Tuple elements can be accessed using ._N notation or pattern matching:
1 2 3 4 5 |
|
Operations
Operation | Description | Returns |
---|---|---|
tuple.bits |
Get bits representation | Bits |
tuple == other |
Equality comparison | Boolean |
tuple != other |
Inequality comparison | Boolean |
Opaque DFHDL Values
Opaque types allow creating new DFHDL types that wrap existing types while hiding their internal representation. This is useful for creating abstraction layers and type-safe interfaces.
Opaque Type Definition
1 2 3 4 5 6 7 8 |
|
Usage
1 2 3 |
|
Examples
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Double DFHDL Values
DFHDL Double values represent IEEE-754 double-precision floating-point numbers.
Type Construction
1 |
|
Operations
Supports standard arithmetic operations:
1 2 3 4 5 6 7 |
|
Conversion
1 2 |
|
Time/Freq DFHDL Values
DFHDL provides special types for representing time and frequency values in hardware designs through physical units. These types help ensure correct timing specifications and frequency calculations.
Time Values
Time values can be created using various unit suffixes:
1 2 3 4 5 6 7 8 9 |
|
Both integer and floating-point values can be used with time units:
1 2 |
|
Frequency Values
Frequency values can be specified using standard frequency units:
1 2 3 4 5 |
|
Like time values, both integer and floating-point values are supported:
1 2 |
|
Usage in RT Domains
Physical values are particularly useful when configuring RT domains and specifying clock frequencies:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Cycles in RT Domain
In RT domains, you can also specify cycle counts using the .cy
unit:
1 2 |
|
Note: The .cy
unit is only available within register-transfer (RT) domains.
Unit (Void) DFHDL Values
The Unit type in DFHDL represents a void or no-value type, similar to Scala's Unit type. It's typically used when an operation doesn't need to return a meaningful value.
Usage
1 2 3 4 5 6 7 8 |
|
Common Use Cases
- Side-effect operations
- Void method returns
- Assignment results
- Process bodies in event-driven designs
1 2 3 4 5 6 |
|