Connectivity
DFHDL wires designs together with two kinds of operators:
- The connection operator
<>. - The assignment operators
:=(blocking) and:==(non-blocking).
This section focuses on the <> connection operator and how it relates to the assignment operators.
Key Differences Between <> and :=/:==
| Criteria | <> Connection |
:=/:== Assignment |
|---|---|---|
| Directionality & Commutativity |
Commutative: a <> b is equivalent to b <> a. One side is the producer and the other the consumer; the dataflow direction is inferred from the operands and the context in which the operator is applied. |
Non-commutative: a := b makes b the producer, transferring data to the consumer a. |
| Mutation | Each consumer bit can be connected at most once. A consumer may still receive several connections, as long as they target disjoint bit ranges (e.g. y(3, 0) <> a and y(7, 4) <> b). |
A consumer bit can be assigned any number of times; the last assignment in program order wins. |
| Statement Order | Connection statements can be placed in any order. | Assignment statements are order-sensitive. |
Connection <> Rules
Port Direct Connections
The connection operator <> is generally used to connect parent designs to their child designs (components) and to connect between sibling designs (children of the same parent). Unlike VHDL/Verilog, there is no need to go through intermediate 'signals' to connect sibling design ports, e.g.:
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Port Via Connections
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
Runnable example
import dfhdl.*
class Plus1 extends DFDesign:
val x = UInt(8) <> IN
val y = UInt(8) <> OUT
y <> x + 1
class Plus2 extends DFDesign:
val x = UInt(8) <> IN
val y = UInt(8) <> OUT
val p1A = Plus1()
val p1B = Plus1()
p1A.x <> x
p1A.y <> p1B.x
y <> p1B.y
given options.CompilerOptions.PrintBackendCode = false
given options.CompilerOptions.PrintDFHDLCode = true
Connectable Value Connections
At least one side of a connection must be a connectable DFHDL value — a variable (VAR) or a port (IN/OUT/INOUT). Connecting two immutable values (e.g. two constants or two read-only aliases) is not allowed, e.g.:
1 2 3 4 5 6 | |
Partial Selection Connections
A partial selection of a connectable value is itself connectable. This includes bit selection and bit-range selection of Bits/UInt/SInt values, as well as struct field and tuple element selection. This lets you drive a single port or variable from several sources, as long as each bit is driven exactly once (see Multiple Connections):
1 2 3 4 5 6 | |
Input Port Assignment := Rule
An input port cannot be assigned to. A connection must be used to drive data into an input port, e.g.:
1 2 3 4 5 6 7 8 9 10 | |
Immutable Value Connections
When connecting a port to an immutable value (such as a constant), the port must be the consumer. This means the connection is done internally to an output port or externally to an input port, e.g.:
1 2 3 4 5 6 7 8 9 10 | |
Different Type Connections
Connecting between different types requires the types to match, possibly through an explicit cast. Different widths are considered different types and require an explicit cast/resize. A casted/converted dataflow value is immutable for the purposes of the connection (see above), so it can only be used as a producer. Here are some examples:
1 2 3 4 5 6 7 8 9 10 | |
In contrast to type casts, bit and bit-range selections of a port (e.g. o(3, 0)) are connectable, as shown in Partial Selection Connections.
Multiple Connections
A given consumer bit can be connected at most once. A single producer, however, can be connected to more than one consumer. Connecting two producers to the same consumer bit is an error:
1 2 3 4 5 6 7 8 | |
Because a partial selection is connectable, the same consumer may be driven by several connections that cover disjoint bit ranges:
1 2 3 4 5 6 7 | |
Mixing Assignments and Connections
The same consumer bit cannot be both assigned to (:=/:==) and connected to (<>), e.g.:
1 2 3 4 5 6 7 8 9 10 11 12 | |
Different bits of the same value may be split between an assignment and a connection, as long as the bit ranges are disjoint:
1 2 3 4 5 | |
Connection Statement Order
The order of <> connection statements does not matter.
Open (Unconnected) Ports
Ports have two connection sides: a consumer side and a producer side. Typically both sides are connected, except for top-level ports. When either side is unconnected, the port is open, with the following behavior:
-
When the port consumer side is open, the port produces its initial value. An uninitialized open-consumer port produces a bubble (undefined) value.
-
When the port producer side is open (unless it is a top-level output port), the port is considered unused and is pruned during compilation, along with any logic used only to drive it.
To explicitly mark a port as unconnected, use the OPEN keyword with the <> connection operator:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Note
OPEN can only be used with the <> connection operator. Using it with := assignment results in a compile error.
Valid Connection and Assignment Examples
1 2 3 4 | |
1 2 3 4 5 6 | |
1 2 3 4 5 6 7 | |
1 2 3 4 5 6 | |
1 2 3 4 5 6 7 8 | |
1 2 3 4 5 6 7 | |
1 2 3 4 5 6 7 | |
Magnet Port Connections
Future Work
- In the future
<>will be used to connect multi-port interfaces. - Connecting between any ancestor which is not a direct parent and child. Currently not fully supported.