summaryrefslogtreecommitdiff
path: root/docs/src/explanations/functional-module-creation.md
blob: 407edb1de206b23ac9408870a4912f73ac69dc7a (plain)
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
---
layout: docs
title:  "Functional Module Creation"
section: "chisel3"
---

# Functional Module Creation

Objects in Scala have a pre-existing creation function (method) called `apply`.
When an object is used as value in an expression (which basically means that the constructor was called), this method determines the returned value.
When dealing with hardware modules, one would expect the module output to be representative of the hardware module's functionality.
Therefore, we would sometimes like the module output to be the value returned when using the object as a value in an expression.
Since hardware modules are represented as Scala objects, this can be done by defining the object's `apply` method to return the module's output.
This can be referred to as creating a functional interface for module construction.
If we apply this on the standard mux2 example, we would to return the mux2 output ports when we used mux2 in an expression.
Implementing this requires building a constructor that takes multiplexer inputs as parameters and returns the multiplexer output:

```scala mdoc:silent
import chisel3._

class Mux2 extends Module {
  val io = IO(new Bundle {
    val sel = Input(Bool())
    val in0 = Input(UInt())
    val in1 = Input(UInt())
    val out = Output(UInt())
  })
  io.out := Mux(io.sel, io.in0, io.in1)
}

object Mux2 {
  def apply(sel: UInt, in0: UInt, in1: UInt) = {
    val m = Module(new Mux2)
    m.io.in0 := in0
    m.io.in1 := in1
    m.io.sel := sel
    m.io.out
  }
}
```

As we can see in the code example, we defined the `apply` method to take the Mux2 inputs as the method parameters, and return the Mux2 output as the function's return value.
By defining modules in this way, it is easier to later implement larger and more complex version of this regular module.
For example, we previously implemented Mux4 like this:

```scala mdoc:silent
class Mux4 extends Module {
  val io = IO(new Bundle {
    val in0 = Input(UInt(1.W))
    val in1 = Input(UInt(1.W))
    val in2 = Input(UInt(1.W))
    val in3 = Input(UInt(1.W))
    val sel = Input(UInt(2.W))
    val out = Output(UInt(1.W))
  })
  val m0 = Module(new Mux2)
  m0.io.sel := io.sel(0)
  m0.io.in0 := io.in0
  m0.io.in1 := io.in1

  val m1 = Module(new Mux2)
  m1.io.sel := io.sel(0)
  m1.io.in0 := io.in2
  m1.io.in1 := io.in3

  val m3 = Module(new Mux2)
  m3.io.sel := io.sel(1)
  m3.io.in0 := m0.io.out
  m3.io.in1 := m1.io.out

  io.out := m3.io.out
}
```

However, by using the creation function we redefined for Mux2, we can now use the Mux2 outputs as values of the modules themselves
when writing the Mux4 output expression:

```scala mdoc:invisible:reset
// We need to re-do this to allow us to `reset`
// and then re-define Mux4
import chisel3._

class Mux2 extends Module {
  val io = IO(new Bundle {
    val sel = Input(Bool())
    val in0 = Input(UInt())
    val in1 = Input(UInt())
    val out = Output(UInt())
  })
  io.out := Mux(io.sel, io.in0, io.in1)
}

object Mux2 {
  def apply(sel: UInt, in0: UInt, in1: UInt) = {
    val m = Module(new Mux2)
    m.io.in0 := in0
    m.io.in1 := in1
    m.io.sel := sel
    m.io.out
  }
}
```

```scala mdoc:silent
class Mux4 extends Module {
  val io = IO(new Bundle {
    val in0 = Input(UInt(1.W))
    val in1 = Input(UInt(1.W))
    val in2 = Input(UInt(1.W))
    val in3 = Input(UInt(1.W))
    val sel = Input(UInt(2.W))
    val out = Output(UInt(1.W))
  })
  io.out := Mux2(io.sel(1),
                 Mux2(io.sel(0), io.in0, io.in1),
                 Mux2(io.sel(0), io.in2, io.in3))
}
```

This allows us to write more intuitively readable hardware connection descriptions, which are similar to software expression evaluation.