0%

2.2 Combinational Logic

2.2 Combinational Logic

Operator

  • Scala and Chisel Operators Look the Same
1
2
3
4
5
6
7
8
// scala
val two = 1 + 1 // Scala: Int + Int
println(two) // 2
// chisel: hardware node
val utwo = 1.U + 1.U // Chisel: Uint + Uint
println(utwo) // UInt<1>(OpResult in MyModule)
// error
val ertwo = 1 + 1.U
  • Width
1
2
3
4
5
io.out_add    := 1.U + 4.U        // max(w(x),w(y))
io.out_add1 := 1.U +& 4.U // max(w(x),w(y))+1
io.out_sub := 2.U - 1.U // max(w(x),w(y))
io.out_sub1 := 1.U -& 4.U // max(w(x),w(y))+1
io.out_mul := 4.U * 2.U // w(x)+w(y)
  • Mux & Cat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MyOperatorsTwo extends Module {
val io = IO(new Bundle {
val in = Input(Bool())
val out_mux = Output(UInt(4.W))
val out_cat = Output(UInt(4.W))
})

// Generate Verilog containing constants instead of actual mux or concatenation logic
// FIRRTL transformations have simplified the circuit, eliminating obvious logic.
val s = true.B
io.out_mux := Mux(s, 3.U, 0.U)
// assign io_out_mux = 4'h3;

// actual mux
io.out_mux := Mux(io.in, 3.U, 0.U)
// wire [1:0] _T = io_in ? 2'h3 : 2'h0;
// assign io_out_mux = {{2'd0}, _T};

// concatenates 2 (b10) with 1 (b1) to give 5 (101)
io.out_cat := Cat(2.U, 1.U)
// assign io_out_cat = 4'h5
}
  • More operators

Example

Arbiter

The following circuit arbitrates data coming from a FIFO into two parallel processing units. The FIFO and processing elements (PEs) communicate with ready-valid interfaces. Construct the arbiter to send data to whichever PE is ready to receive data, prioritizing PE0 if both are ready to receive data. Remember that the arbiter should tell the FIFO that it’s ready to receive data when at least one of the PEs can receive data. Also, wait for a PE to assert that it’s ready before asserting that the data are valid. You will likely need binary operators to complete this exercise.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Arbiter extends Module {
val io = IO(new Bundle {
// FIFO
val fifo_valid = Input(Bool())
val fifo_ready = Output(Bool())
val fifo_data = Input(UInt(16.W))

// PE0
val pe0_valid = Output(Bool())
val pe0_ready = Input(Bool())
val pe0_data = Output(UInt(16.W))

// PE1
val pe1_valid = Output(Bool())
val pe1_ready = Input(Bool())
val pe1_data = Output(UInt(16.W))
})

io.fifo_ready := io.pe0_ready || io.pe1_ready
io.pe0_valid := io.fifo_valid && io.pe0_ready
io.pe1_valid := io.fifo_valid && io.pe1_ready && !io.pe0_ready
io.pe0_data := io.fifo_data
io.pe1_data := io.fifo_data
}

Paramiterized Adder

This optional exercise exposes you to one of the most powerful features of Chisel, it’s parameterization capabilities. To demonstrate this, we’ll construct a parameterized adder that can either saturate the output when overflow occurs, or truncate the results (i.e. wrap around).

First, look at the Module below. The parameter we pass into it is called saturate and has type Scala Boolean. This is not a Chisel Bool. So, we’re not creating a single hardware adder that can either saturate or truncate, but rather we’re creating a generator that produces either a saturating hardware adder or a truncating hardware adder. The decision is made at compile time.

Next, notice the inputs and outputs are all 4-bit UInts. Chisel has built-in width inferencing, and if you look at the cheatsheet, you’ll see that the bitwidth of a normal summation is equal to the maximum bitwidth of the two inputs. This means that

1
val sum = io.in_a + io.in_b

will make sum a 4-bit wire, and the value will be the truncated result for 4-bit inputs. To check if the summation should saturate, you need to place the result in a 5-bit wire. This can be done with the +& summation, as seen on the cheatsheet.

1
val sum = io.in_a +& io.in_b

Finally, note that connecting a 4-bit UInt wire to a 5-bit UInt wire will truncate the MSB by default. You can use this to easily truncate the 5-bit sum for the non-saturating adder.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ParameterizedAdder(saturate: Boolean) extends Module {
val io = IO(new Bundle {
val in_a = Input(UInt(4.W))
val in_b = Input(UInt(4.W))
val out = Output(UInt(4.W))
})

val sum = io.in_a +& io.in_b
if (saturate) {
io.out := Mux(sum > 15.U, 15.U, sum)
} else {
io.out := sum
}
}
1
2
3
4
5
6
7
8
9
10
11
module ParameterizedAdder(
input clock,
input reset,
input [3:0] io_in_a,
input [3:0] io_in_b,
output [3:0] io_out
);
wire [4:0] sum = io_in_a + io_in_b; // @[cmd9.sc 8:21]
wire [4:0] _T_1 = sum > 5'hf ? 5'hf : sum; // @[cmd9.sc 10:18]
assign io_out = _T_1[3:0]; // @[cmd9.sc 10:12]
endmodule
1
2
3
4
5
6
7
8
9
10
module ParameterizedAdder(
input clock,
input reset,
input [3:0] io_in_a,
input [3:0] io_in_b,
output [3:0] io_out
);
wire [4:0] sum = io_in_a + io_in_b; // @[cmd10.sc 8:21]
assign io_out = sum[3:0]; // @[cmd10.sc 12:12]
endmodule