Design Hierarchy
DFHDL supports composable design hierarchies through design class instantiation and port connections.
Terminology
- design - A Scala class extending
XXDesign
, whereXX
can beDF
,RT
, orED
, corresponding to the desired design domain. - design member - Any DFHDL object instantiated within a design (the design contains or owns all its members).
- child design/component - A design instance that is owned by another design.
- top design - The highest-level design in the hierarchy (not contained by any other design), also known as the top-level design.
- top-app design - A
@top
annotated top design that generates a main entry point with the default application.
Design Declaration
Syntax
A DFHDL design declaration follows standard Scala class syntax, with specialized handling by the DFHDL Scala compiler plugin.
/** _documentation_ */
@top(genMain) //required only for top-level designs
[_modifiers_] class _name_(_params_) extends XXDesign:
_contents_
end _name_ //optional `end` marker
-
_name_
- The Scala class name for the design. The DFHDL compiler preserves this name and uses it in error messages and generated artifacts (e.g., Verilog modules or VHDL entities). See the naming section for details. -
(_params_)
- An optional parameter block. This can include either Scala parameters that are inlined during design elaboration, or DFHDL design parameters that are preserved through elaboration and compilation. If no parameters are needed, Scala syntax accepts either empty parentheses()
or no parentheses. See Parameter Block Syntax for details. -
_XXDesign_
- The base class to extend, whereXX
specifies the design domain:DF
for dataflow,RT
for register-transfer, orED
for event-driven. -
_contents_
- The design interface (ports/interfaces/domains) and functionality (variables, functions, child designs, processes, etc.), based on the selected design domain's semantics. -
@top(genMain)
- A required annotation for top-level designs (designs not instantiated within another design). The annotation has an optionalval genMain: Boolean = true
parameter:- When
genMain = true
, the design becomes a top-app design where all parameters must have default values, and a main Scala entry point namedtop__name_
is generated - When
genMain = false
, the annotation only provides a default top-level context for the design
- When
-
_documentation_
- Design documentation in Scaladoc format. This documentation is preserved throughout compilation and included in the generated backend code. -
_modifiers_
- Optional Scala modifiers. See Design Class Modifier Rules for details.
LeftShift2
example
Basic top-app design example: a two-bits left shifter
The DFHDL code below implements a two-bit left shifter design named LeftShift2
. The design:
- Uses register-transfer (RT) domain semantics by extending
RTDesign
- Has an 8-bit input port and an 8-bit output port
- Performs a fixed 2-bit left shift operation on the input
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Since this design is annotated with @top
, it is a top-app design that generates an executable Scala program. This program compiles the design and generates backend code (Verilog or VHDL). The backend can be configured through:
- Command-line arguments when running the program
- Implicit backend settings in the code (as shown in this example)
The @top
annotation captures any implicit/given options within its scope and provides them as defaults when no CLI arguments are specified.
Looking at the generated Verilog code, we can observe several key differences from the DFHDL source:
-
Module Interface: DFHDL's Scala-style port declarations (
<> IN/OUT
) are translated to traditional Verilog port declarations (input wire
/output logic
) -
Documentation: Scaladoc comments are preserved and converted to Verilog-style comments (
/* */
) -
Default Settings: The compiler adds standard Verilog settings like
`default_nettype none
and`timescale
-
Include Files: The compiler adds necessary include files for backend-specific definitions
-
Assignment Syntax: DFHDL's
:=
assignments are translated to Verilog'sassign
statements
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
The generated VHDL code shows similar transformations from the DFHDL source:
-
Entity Interface: DFHDL's port declarations are translated to VHDL's
in
/out
mode declarations with explicit signal types -
Documentation: Scaladoc comments are preserved as VHDL comments (
--
) -
Library/Package Usage: The compiler adds necessary library and package references (
ieee
,std_logic_1164
, etc.) -
Signal Types: DFHDL's
Bits
type is translated to VHDL'sstd_logic_vector
with appropriate widths -
Assignment Syntax: While both DFHDL and VHDL use
:=
, the semantics differ - DFHDL represents high-level connections while VHDL represents signal assignments
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Runnable example
import dfhdl.*
//optionally set the default backend configuration option
//(can be overridden by the top-app CLI)
given options.CompilerOptions.Backend = backends.verilog
/** A two-bits left shifter */
@top class LeftShift2 extends RTDesign:
/** bits input */
val iBits = Bits(8) <> IN
/** bits output */
val oBits = Bits(8) <> OUT
oBits := iBits << 2
Parameter Block Syntax
The DFHDL design parameter block follows standard Scala syntax, accepting a comma-separated list of parameter declarations:
([_access_] _name_: _type_ [= _default_], ...)
-
_type_
- Either a pure Scala type or a DFHDL parameter type (DFType <> CONST
):- Pure Scala parameters are inlined during elaboration
- DFHDL parameters are preserved in the generated backend code
- See Design Parameter Type Rules for details
-
_name_
- The parameter name. For DFHDL parameters, this name is:- Preserved throughout compilation
- Used in the generated backend code
- Available through the CLI for top-app designs
-
_default_
- Optional default value. Required for all parameters in top-app designs. See Design Parameter Default Value Rules for details. -
_access_
- Optional Scala access modifier. Usuallyval
to make the parameter public. See Design Parameter Access Rules for details.
LeftShiftBasic
example
Scala-parameterized top-app design example: a basic left shifter
The DFHDL code below implements a basic left shifter design named LeftShiftBasic
. This design is similar to the earlier example of LeftShift2
except here the design has the shift value as an input, and its input and output port widths are set according to the Scala parameter width
.
1 2 3 4 5 6 7 8 9 10 11 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
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.*
given options.CompilerOptions.Backend = backends.verilog
/** A basic left shifter */
@top class LeftShiftBasic(
val width: Int = 8
) extends RTDesign:
/** bits input */
val iBits = Bits(width) <> IN
/** requested shift */
val shift = UInt.until(width) <> IN
/** bits output */
val oBits = Bits(width) <> OUT
oBits := iBits << shift
The basic code shifter above did not generate the width
parameter in the Verilog and VHDL backend code. The following example shows how to preserve the width
parameter:
LeftShiftGen
example
DFHDL-parameterized top-app design example: a generic left shifter
The DFHDL code below implements a generic left shifter design named LeftShiftGen
. This design is similar to the earlier example of LeftShiftBasic
, except here the width
parameter is now a DFHDL parameter, as indicated by its Int <> CONST
type. This enables the DFHDL compiler to preserve the parameter name and directly use it in the generated backend code where applicable.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
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 |
|
Runnable example
import dfhdl.*
given options.CompilerOptions.Backend = backends.verilog
/** A generic left shifter
*
* @param width
* the width of the input and output bits
*/
@top class LeftShiftGen(
val width: Int <> CONST = 8,
) extends RTDesign:
/** bits input */
val iBits = Bits(width) <> IN
/** requested shift */
val shift = UInt.until(width) <> IN
/** bits output */
val oBits = Bits(width) <> OUT
oBits := iBits << shift
Design Parameter Type Rules
- Any pure Scala parameter or DFHDL parameter types are acceptable.
- Top-app design parameters that can be modified from the CLI must be one of:
- Pure Scala Types:
String
,Boolean
,Int
, andDouble
- DFHDL Types:
Int <> CONST
,Bit <> CONST
, andBoolean <> CONST
- Pure Scala Types:
Top-app design with accepted and ignored CLI arguments example
In this example, the top-app supported parameters pureIntArg
and dfhdlIntArg
are preserved to be modifiable from the CLI, while ignored
and dfhdlIgnored
keep their default values.
DFHDL code | |
---|---|
1 2 3 4 5 6 7 8 |
|
CLI help mode output, when running via sbt (truncated) | |
---|---|
1 2 3 4 5 |
|
Runnable example
import dfhdl.*
//this option forces the top-app
//to run help mode by default
given options.AppOptions.DefaultMode =
options.AppOptions.DefaultMode.help
class CustomArg
@top class Foo(
val pureIntArg: Int = 5,
val dfhdlIntArg: Int <> CONST = 7,
val ignored: CustomArg = CustomArg(),
val dfhdlIgnored: Bits[8] <> CONST = all(0)
) extends DFDesign
Design Parameter Default Value Rules
For top-app designs, all parameters must have default values.
@top
annotation and default parameter value requirement example
This example shows FooErr
is missing a default value and throws an error. There are three ways to resolve this, shown in FooOK1
, FooOK2
, and FooOK3
.
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 |
|
Good design practice - avoid default parameter value overuse
Overusing default parameter values is considered bad design practice. In general, default values should be used sparingly and only to define "sensible defaults" for parameters that are rarely changed. A good rule of thumb is to avoid default values that affect a design's interface (e.g., the width of a port).
Design Parameter Access Rules
Without any Scala access modifier, a Scala class parameter access is declared as private val
. This default access leads to an error if that parameter affects the type of non-private class member (e.g., a width
parameter affecting the bits width of a port). To resolve this error, the parameter can be declared as public val
, as protected val
, or even private[scope] val
with a scope access qualifier.
Parameter access example
This example shows an access error in FooErr
, where a private parameter width
affects the type of a non-private member v
. There are four ways to resolve this error, shown in FooOK1
, FooOK2
, FooOK3
, and FooOK4
. FooOK5
demonstrates that private parameters can be accessed by public members as long as they don't affect the type.
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 |
|
Good design practice - how to choose the right parameter/member access?
- For Simple Development - During initial development, you can declare all parameters and named design members as public
val
for simplicity. - Protection for Shared Code - When sharing your design with others in DFHDL (Scala) form, follow good design practices to maintain source and binary compatibility of your Scala library artifacts. Remember: You can always remove protection without breaking code, but adding protection later will cause breakage.
- Public Interface, Protected Implementation - Keep the design interface (ports, domains, interfaces, and their type-affecting parameters) public. All other design class members should have private or protected access modifiers.
- Private vs. Protected Access - Use private access for members that should only be accessible within the design class itself. Use protected access for members that should also be accessible by subclasses.
- Scoped Protection for Testing - For verification code that needs access to internal design members, use package-private access with a scope qualifier (e.g.
private[mylib]
) instead of making members fully public. This restricts access to just your library's test code.
In this example, the Foo
class demonstrates good design practices for parameter and member access.
Good design practice example | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Design Class Modifier Rules
A DFHDL design class cannot be declared as final class
or case class
. Attempting to do so produces an error:
DFHDL design class modifier limitation example | |
---|---|
1 2 3 4 |
|
All other Scala class modifiers have no special effect or limitation from a DFHDL compiler perspective. Nonetheless, these modifiers can be relevant when defining a more complex design API, as part of the DFHDL meta-programming capabilities through the Scala language (e.g., changing class access to protected
).
LRShiftFlat
example
Generic left-right shifter, flat design example
The DFHDL code below implements a generic left-right shifter flat design named LRShiftFlat
. This design expands on LeftShiftGen
by adding a dir
enum port value that specifies the shift direction and a shift operation multiplexer through a match
statement.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
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 |
|
1 2 3 4 5 6 7 8 |
|
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 |
|
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 51 |
|
Runnable example
import dfhdl.*
given options.CompilerOptions.Backend = backends.verilog
enum ShiftDir extends Encode:
case Left, Right
/** A left-right bits shifter (flat version)
*
* @param width
* the width of the input and output bits
*/
@top class LRShiftFlat(
val width: Int <> CONST = 8
) extends RTDesign:
/** bits input */
val iBits = Bits(width) <> IN
/** requested shift */
val shift = UInt.until(width) <> IN
/** direction of shift */
val dir = ShiftDir <> IN
/** bits output */
val oBits = Bits(width) <> OUT
oBits := dir match
case ShiftDir.Left => iBits << shift
case ShiftDir.Right => iBits >> shift
Design Class Inheritance
DFHDL leverages Scala inheritance to enable sharing functionality between design classes.
ShiftGen
example
Generic left and right shifters, design class inheritance example
The DFHDL code below demonstrates how to implement both left and right generic shifters efficiently by using a common abstract class
named ShiftGen
. The width
parameter is declared as an abstract class field (without an assigned value) inside the ShiftGen
class body. By extending ShiftGen
, both LeftShiftGen
and RightShiftGen
can utilize the IOs already declared in ShiftGen
. They only need to explicitly declare the width
parameter and implement the shift functionality in their respective class bodies.
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 |
|
Design Composition & Instantiation
DFHDL supports three mechanisms to form a design hierarchy through design instantiation and composition:
-
Direct Connection Composition - The recommended mechanism for complex design hierarchies with multiple inputs and outputs. Design instantiation and port connection can be done separately, allowing child design ports to be referenced without intermediate variables.
-
Via Connection Composition - A legacy mechanism that connects ports only within a design instantiation. This exists for compatibility with Verilog module instancing and VHDL component instancing. The DFHDL compiler automatically transforms direct connections into via connections.
-
Functional Composition - A method-call mechanism for dataflow designs, primarily used for arithmetic/logic functionality with a single output port. The DFHDL compiler automatically transforms functional composition into direct design composition.
The following sections explore these composition mechanisms using our running example of a bit shifter:
Direct Connection Composition
Direct connection composition is the recommended approach for building hierarchical designs in DFHDL. It offers several key advantages:
- Separate instantiation and connection - Create child design instances first, then connect their ports later
- Direct port references - Access child design ports without intermediate variables
- Flexible connectivity - Connect ports in any order and across multiple statements
Syntax
The syntax for direct composition follows standard Scala class instantiation, with port connections made via the <>
operator:
//instantiate a child design
val _childDesignName_ = _designClass_(_params_)
//port connection (repeat for each child port)
_childDesignName_._childPort_ <> _connectedValue_
Where:
-
_childDesignName_
- The instance name for the child design. This name is preserved by the DFHDL compiler and used in error messages and generated artifacts. See the naming section for details. -
_designClass_
- The design class to instantiate. -
_params_
- Parameters for the child design. Empty parentheses()
are required even if no parameters are needed. Parameters can be specified:- As ordered values (e.g.,
Counter(8, Up)
) - As named parameters (e.g.,
Counter(width = 8, dir = Up)
) - Parameters with default values can be omitted
- As ordered values (e.g.,
-
_childPort_
- The port of the child design to connect. -
_connectedValue_
- The value to connect to the child port. Can be:- A constant
- A variable
- A port of the parent design
- A port of another child design instance
The <>
connection operator has no explicit directionality - it automatically infers producer/consumer relationships based on the connected value types and scope. See the connectivity section for details.
LRShiftDirect
example
Generic left-right shifter using direct connection composition
The code below implements a generic left-right shifter named LRShiftDirect
. This design provides the same functionality as LRShiftFlat
, but uses composition to split left and right shift operations into separate designs. The implementation leverages the design class inheritance shown in the ShiftGen
example.
Note
While this example demonstrates direct composition, a flat approach is often preferable for simpler designs. For complex designs, however, splitting functionality into sub-components promotes code reuse, simplifies verification, and follows good design practices.
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 51 52 53 54 |
|
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 51 52 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
1 2 3 4 5 6 7 8 |
|
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 51 52 53 54 55 56 57 58 59 60 |
|
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 |
|
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 |
|
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 51 |
|
Runnable example
import dfhdl.*
given options.CompilerOptions.Backend = backends.verilog
/** A generic abstract shifter with only IOs */
abstract class ShiftGen extends RTDesign:
/** the width of the input and output bits */
val width: Int <> CONST //abstract
/** bits input */
val iBits = Bits(width) <> IN
/** requested shift */
val shift = UInt.until(width) <> IN
/** bits output */
val oBits = Bits(width) <> OUT
/** A generic left shifter
*
* @param width
* the width of the input and output bits
*/
class LeftShiftGen(
val width: Int <> CONST
) extends ShiftGen:
oBits := iBits << shift
/** A generic right shifter
*
* @param width
* the width of the input and output bits
*/
class RightShiftGen(
val width: Int <> CONST
) extends ShiftGen:
oBits := iBits >> shift
enum ShiftDir extends Encode:
case Left, Right
/** A left-right bits shifter, direct composition
*
* @param width
* the width of the input and output bits
*/
@top class LRShiftDirect(
val width: Int <> CONST = 8
) extends ShiftGen:
/** direction of shift */
val dir = ShiftDir <> IN
val lshifter = LeftShiftGen(width)
val rshifter = RightShiftGen(width)
lshifter.iBits <> iBits
lshifter.shift <> shift
rshifter.iBits <> iBits
rshifter.shift <> shift
oBits := dir match
case ShiftDir.Left => lshifter.oBits
case ShiftDir.Right => rshifter.oBits
end LRShiftDirect
Via Connection Composition
Via connection composition is a legacy mechanism that connects child design ports within the child design instantiation. It exists for compatibility with Verilog module instantiation and VHDL component instantiation. The DFHDL compiler automatically transforms direct connections into via connections.
Syntax
The syntax for via composition uses Scala anonymous class instantiation, with port connections made inside the instantiation block:
//instantiate a child design
val _childDesignName_ = new _designClass_(_params_):
//port connection (repeat for each child port)
_childPort_ <> _connectedValue_
The new
keyword and colon :
syntax creates an anonymous class instance. Port connections must be made within this instantiation block, similar to Verilog module and VHDL component instantiation. This means connected values must be declared before they are used in the connection operation.
Handling port name collisions between parent and child designs
When connecting ports with the same name in parent and child designs, Scala's name shadowing rules will favor the child port name. For example, when connecting the iBits
port of LeftShiftGen
to the iBits
port of LRShiftVia
, we need a way to reference the parent's iBits
port from within the child design.
To solve this, we use Scala's class self reference feature and name it parent
, as shown in the LRShiftVia
example below.
LRShiftVia
example
Generic left-right shifter, via composition example
The DFHDL code below implements the same generic left-right shifter composition seen in the LRShiftDirect
example, but uses via composition instead of direct composition. We define a parent
self reference for the LRShiftVia
design to refer to the LRShiftVia
design within the lshifter: LeftShiftGen
and rshifter: RightShiftGen
child designs. We also use intermediate variables for the oBits
ports of the lshifter
and rshifter
child designs and apply the multiplexer logic to select between them.
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 |
|
Another interesting example is the automatic transformation of a direct composition design into a via composition design:
Direct-to-via compilation transformation example
The code below shows how the DFHDL compiler transforms the LRShiftDirect
design into via composition form. For each child design port, the compiler:
- Creates an intermediate variable with the same type
- Connects the child port to this variable inside the child's instantiation block
- Connects the variable to the appropriate value in the parent design
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 |
|
/*<--*/
and /*-->*/
) to indicate the direction of data flow in the child design port connections.
The following runnable example is the same as the LRShiftDirect
example, except for the default compiler options, which we altered to print the compiled design in DFHDL code format rather than the backend code format.
Runnable example
import dfhdl.*
given options.CompilerOptions.Backend = backends.verilog
//disable the default backend code print (in scastie)
given options.CompilerOptions.PrintBackendCode = false
//enable the DFHDL code print after compilation
given options.CompilerOptions.PrintDFHDLCode = true
/** A generic abstract shifter with only IOs */
abstract class ShiftGen extends RTDesign:
/** the width of the input and output bits */
val width: Int <> CONST //abstract
/** bits input */
val iBits = Bits(width) <> IN
/** requested shift */
val shift = UInt.until(width) <> IN
/** bits output */
val oBits = Bits(width) <> OUT
/** A generic left shifter
*
* @param width
* the width of the input and output bits
*/
class LeftShiftGen(
val width: Int <> CONST
) extends ShiftGen:
oBits := iBits << shift
/** A generic right shifter
*
* @param width
* the width of the input and output bits
*/
class RightShiftGen(
val width: Int <> CONST
) extends ShiftGen:
oBits := iBits >> shift
enum ShiftDir extends Encode:
case Left, Right
/** A left-right bits shifter, direct composition
*
* @param width
* the width of the input and output bits
*/
@top class LRShiftDirect(
val width: Int <> CONST = 8
) extends ShiftGen:
/** direction of shift */
val dir = ShiftDir <> IN
val lshifter = LeftShiftGen(width)
val rshifter = RightShiftGen(width)
lshifter.iBits <> iBits
lshifter.shift <> shift
rshifter.iBits <> iBits
rshifter.shift <> shift
oBits := dir match
case ShiftDir.Left => lshifter.oBits
case ShiftDir.Right => rshifter.oBits
end LRShiftDirect
Functional Composition
Functional composition is a method-call based mechanism primarily used for dataflow designs with a single output. It's particularly useful for arithmetic and logic operations. The DFHDL compiler automatically transforms functional composition into direct design composition.
Syntax
The syntax for functional composition follows standard Scala method declaration and invocation to declare and invoke design definitions (aka design methods or design functions):
//declare a design definition
def _designName_(
_param1_: _type1_ <> VAL,
_param2_: _type2_ <> VAL,
...,
_paramN_: _typeN_ <> VAL
): _returnType_ <> DFRET = _expression_
//invoke the design definition
_designName_(_param1_, _param2_, ..., _paramN_)
Where:
_designName_
- The name of the design definition_paramN_
- Input parameters that act as design ports_typeN_
- DFHDL types for the parameters_returnType_
- DFHDL type for the output port<> VAL
- Marks parameters as design input ports<> DFRET
- Marks the return type as a design output port_expression_
- The design functionality
LRShiftFunc
example
Generic left-right shifter using functional composition
The code below implements the same generic left-right shifter functionality seen in previous examples, but uses functional composition. The implementation is split into three function designs:
LeftShiftGen
- Performs left shift operationRightShiftGen
- Performs right shift operationLRShiftFunc
- Top-level function that selects between left/right shift
Since functions can't be top-level designs in DFHDL, we wrap LRShiftFunc
in a top-level design class LRShiftFuncWrapper
.
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 |
|
Advantages of functional composition
Functional composition offers several benefits:
- Concise syntax - No need for explicit port declarations and connections
- Natural expression - Operations can be written in a more natural mathematical style
- Easy reuse - Functions can be composed and reused easily
- Type safety - Scala's type system ensures correct port connections