0%

3.1 Chisel Standard Library

3.1 Chisel Standard Library

Chisel cheatsheet

Chisel3 cheatsheet

Decoupled

DecoupledIO provides a ready-valid interface for transferring data. Any Chisel data can be wrapped in a DecoupledIO (used as the bits field).

  • (master)The source drives the bits signal with the data to be transferred and the valid signal when there is data to be transferred.
    (slave)The sink drives the ready signal when it is ready to accept data, and data is considered transferred when both ready and valid are asserted on a cycle.

  • ready and valid should not be combinationally coupled, otherwise this may result in unsynthesizable combinational loops.
    ready should only be dependent on whether the sink is able to receive data, and valid should only be dependent on whether the source has data. Only after the transaction (on the next clock cycle) should the values update.

  • Output: Decoupled(UInt(8.W)) Bundle with fields:

    1
    2
    3
    4
    5
    val out = new Bundle {
    val valid = Output(Bool())
    val ready = Input(Bool())
    val bits = Output(UInt(8.W))
    }

    Input: Flipped(Decoupled(UInt(8.W)))Bundle with fields:

    1
    2
    3
    4
    5
    val in = new Bundle {
    val valid = Input(Bool())
    val ready = Output(Bool())
    val bits = Input(UInt(8.W))
    }

Arbiter

  • Arbiters routes data from n DecoupledIO sources to one DecoupledIO sink, given a prioritization. There are two types included in Chisel:
    • Arbiter: prioritizes lower-index producers
    • RRArbiter: runs in round-robin order
  • Arbiter routing is implemented in combinational logic.
1
2
3
4
5
6
7
8
9
10
11
class Mod extends Module {
// Example circuit using a priority arbiter
val io = IO(new Bundle {
val in = Flipped(Vec(2, Decoupled(UInt(8.W))))
val out = Decoupled(UInt(8.W))
})
// Arbiter doesn't have a convenience constructor, so it's built like any Module
val arbiter = Module(new Arbiter(UInt(8.W), 2)) // 2 to 1 Priority Arbiter
arbiter.io.in <> io.in
io.out <> arbiter.io.out
}

Queue

Queue creates a FIFO (first-in, first-out) queue with Decoupled interfaces on both sides, allowing backpressure. Both the data type and number of elements are configurable.

1
2
3
4
5
6
7
8
class Queue extends Module {
val io = IO(new Bundle {
val in = Flipped(Decoupled(UInt(8.W)))
val out = Decoupled(UInt(8.W))
})
val queue = Queue(io.in, 2) // 2-element queue
io.out <> queue
}

Bitwise Utilities

PopCount

PopCount returns the number of high (1) bits in the input as a UInt.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
test(new Module {
// Example circuit using PopCount
val io = IO(new Bundle {
val in = Input(UInt(8.W))
val out = Output(UInt(8.W))
})
io.out := PopCount(io.in)
}) { c =>
// Integer.parseInt is used create an Integer from a binary specification
c.io.in.poke(Integer.parseInt("00000000", 2).U)
println(s"in=0b${c.io.in.peek().litValue.toInt.toBinaryString}, out=${c.io.out.peek().litValue}")

c.io.in.poke(Integer.parseInt("11001010", 2).U)
println(s"in=0b${c.io.in.peek().litValue.toInt.toBinaryString}, out=${c.io.out.peek().litValue}")
}

Reverse

Reverse returns the bit-reversed sequence input. NOT ~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
test(new Module {
// Example circuit using Reverse
val io = IO(new Bundle {
val in = Input(UInt(8.W))
val out = Output(UInt(8.W))
})
io.out := Reverse(io.in)
}) { c =>
// Integer.parseInt is used create an Integer from a binary specification
c.io.in.poke(Integer.parseInt("00001111", 2).U)
println(s"in=0b${c.io.in.peek().litValue.toInt.toBinaryString}, out=0b${c.io.out.peek().litValue.toInt.toBinaryString}")

c.io.in.poke(Integer.parseInt("11001010", 2).U)
println(s"in=0b${c.io.in.peek().litValue.toInt.toBinaryString}, out=0b${c.io.out.peek().litValue.toInt.toBinaryString}")
}

OneHot encoding utilities

OneHot is an encoding of integers where there is one wire for each value, and exactly one wire is high.

The below two functions provide conversion between binary (UInt) and OneHot encodings, and are inverses of each other:

  • UInt to OneHot: UIntToOH
  • OneHot to UInt: OHToUInt
1
2
3
4
5
6
7
8
// UIntToOH
val in = Input(UInt(4.W))
val out = Output(UInt(16.W))
io.out := UIntToOH(io.in)
// OHToUInt
val in = Input(UInt(16.W))
val out = Output(UInt(4.W))
io.out := OHToUInt(io.in)

Mux

  • These muxes take in a list of values with select signals, and output the value associated with the lowest-index select signal.
  • These can either take a list of (select: Bool, value: Data) tuples, or corresponding lists of selects and values as arguments.

Priority Mux

A PriorityMux outputs the value associated with the lowest-index asserted select signal.

1
2
PriorityMux(io.in_sels, io.in_bits)
PriorityMux(List[Bool, Bits])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
test(new Module {
// Example circuit using PriorityMux
val io = IO(new Bundle {
val in_sels = Input(Vec(2, Bool()))
val in_bits = Input(Vec(2, UInt(8.W)))
val out = Output(UInt(8.W))
})
io.out := PriorityMux(io.in_sels, io.in_bits)
}) { c =>
c.io.in_bits(0).poke(10.U)
c.io.in_bits(1).poke(20.U)

c.io.in_sels(0).poke(false.B)
c.io.in_sels(1).poke(true.B) // Select higher index only

c.io.in_sels(0).poke(true.B) // Select both - arbitration needed
c.io.in_sels(1).poke(true.B)

c.io.in_sels(0).poke(true.B) // Select lower index only
c.io.in_sels(1).poke(false.B)
}

OneHot Mux

An Mux1H provides an efficient implementation when it is guaranteed that exactly one of the select signals will be high. Behavior is undefined if the assumption is not true.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
test(new Module {
// Example circuit using Mux1H
val io = IO(new Bundle {
val in_sels = Input(Vec(2, Bool()))
val in_bits = Input(Vec(2, UInt(8.W)))
val out = Output(UInt(8.W))
})
io.out := Mux1H(io.in_sels, io.in_bits)
}) { c =>
c.io.in_bits(0).poke(10.U)
c.io.in_bits(1).poke(20.U)

c.io.in_sels(0).poke(false.B)
c.io.in_sels(1).poke(true.B) // Select index 1

c.io.in_sels(0).poke(true.B) // Select index 0
c.io.in_sels(1).poke(false.B)

c.io.in_sels(0).poke(false.B) // Select none (invalid)
c.io.in_sels(1).poke(false.B)

c.io.in_sels(0).poke(true.B) // Select both (invalid)
c.io.in_sels(1).poke(true.B)
}

Counter

Counter is a counter that can be incremented once every cycle, up to some specified limit, at which point it overflows. Note that it is not a Module, and its value is accessible.

1
2
3
4
5
6
7
8
9
10
11
12
class Cnt extends Module {
// Example circuit using Mux1H
val io = IO(new Bundle {
val count = Input(Bool())
val out = Output(UInt(2.W))
})
val counter = Counter(3) // 3-count Counter (outputs range [0...2])
when(io.count) {
counter.inc() // increasement
}
io.out := counter.value
}