-
Notifications
You must be signed in to change notification settings - Fork 21
Comparison to Verilog
A few quick examples of Verilog code, and then the equivalent in Chisel. Please do not spend too much time here, especially not in the first pass, rather use this page as a reference as you go along the Learning Journey.
In Verilog, a module is created as follows:
module adder(a, b, sum, cout);
...
endmodule
whereas, to achieve the same purpose, in Chisel we define a Scala class:
class Adder(val w: Int) extends Module {
...
}
Arguments passed to the module created in Verilog represent the module ports. It only remains to provide them direction:
module adder(a, b, sum, cout);
input a, b;
output sum, cout;
...
endmodule
However, the parameters passed to the Scala class are not the interfaces, they are utilized for parameterization - a later topic, so those will be omitted here. Interfaces are specified within an instance of a Bundle
, as follows:
class Adder(...) extends Module {
val io = IO(new Bundle {
val a = Input(UInt(1.W))
val b = Input(UInt(1.W))
val sum = Output(UInt(1.W))
val cout = Output(UInt(1.W))
})
...
}
A complete implementation of a one-bit adder represents an example of boolean logic, with named wires feeding values in and a named wire as output, given first in Verilog:
module adder(a, b, sum, cout);
input a, b;
output sum, cout;
assign sum = a ^ b; // xor
assign cout = a & b; // and
endmodule
and then also in Chisel:
class Adder(...) extends Module {
val io = IO(new Bundle {
val a = Input(UInt(1.W))
val b = Input(UInt(1.W))
val sum = Output(UInt(1.W))
val cout = Output(UInt(1.W))
})
io.sum := io.a ^ io.b // xor
io.cout := io.a & io.b // and
}
However, parameterization brings advantage to Chisel, which is demonstrated by the Adder tutorial, where the operator :=
for re-assignment is also explained.
To specify conditional behavior in Verilog we use the always
statement with the list of sensitivity:
...
always @ (posedge clk, a, b);
...
In Chisel, a when
block is executed if its condition evaluates to true:
when (io.a | io.b) {
...
}
As an example, Verilog implementation of a shift register is provided:
module ShiftRegister(input clk, input reset,
input io_in,
output io_out);
reg[0:0] r3;
reg[0:0] r2;
reg[0:0] r1;
reg[0:0] r0;
assign io_out = r3;
always @(posedge clk) begin
r3 <= r2;
r2 <= r1;
r1 <= r0;
r0 <= io_in;
end
endmodule
Each of the internal one-bit registers takes the value of the preceding register at the positive edge of clk
, and this is specified by the always
statement. In Chisel, however, both clock and reset are implicitly created by the following implementation of the shift register:
class ShiftRegister extends Module {
val io = IO(new Bundle {
val in = Input(UInt(1.W))
val out = Output(UInt(1.W))
})
val r0 = RegNext(io.in)
val r1 = RegNext(r0)
val r2 = RegNext(r1)
val r3 = RegNext(r2)
io.out := r3
}
As a matter of fact, the Verilog code of the shift register has been generated from this Chisel representation - meaning that this is an example a wire that feeds a value into a register, where register is triggered at every rising clock edge.
Verilog code of a multiplexer with named wires feeding in and a named wire accepting output:
module mux2(in0, in1, sel, out);
input in0, in1, sel;
output out;
assign out = in0 & ~sel | in1 & sel;
endmodule
Chisel equivalent:
class Mux2 extends Module {
val io = IO(new Bundle {
val sel = Input(UInt(1.W))
val in0 = Input(UInt(1.W))
val in1 = Input(UInt(1.W))
val out = Output(UInt(1.W))
})
io.out := (io.sel & io.in1) | (~io.sel & io.in0)
}
A 4-to-1 multiplexer can be created using three 2-to-1 multiplexers. In Verilog, modules are instantiated in the following way:
Mux2 m0 (
.io_sel(m0_io_sel),
.io_in0(m0_io_in0),
.io_in1(m0_io_in1),
.io_out(m0_io_out)
);
Chisel equivalent:
val m0 = Module(new Mux2()) // the first instance of the 2-to-1 multiplexer
m0.io.sel := io.sel(0)
m0.io.in0 := io.in0
m0.io.in1 := io.in1
Once you've scrolled through these, not more than a couple of minutes, please proceed to the next step of the Learning Journey.