chisel-tutorial代码学习 - 03
1. 寄存器的定义
在chisel中,定义一个寄存器变量,就会综合成一个上升沿触发的寄存器。
回忆第1篇笔记中,声明了2个寄存器x
和y
,并在when
语句块中对这两个寄存器进行赋值,
val x = Reg(UInt())
val y = Reg(UInt())
when (io.load) {
x := io.a; y := io.b
} .otherwise {
when (x > y) {
x := x - y
} .elsewhen (x <= y) {
y := y - x
}
}
2. chisel的多种寄存器声明
可以参考大佬写的帖子,第十八章 Chisel基础——模块与硬件类型
2.1 Reg
使用Reg
可以定义一个无条件更新的寄存器,
class RegTest extends Module {
val io = IO(new Bundle{
val in = Input(UInt(4.W))
val out = Output(UInt(4.W))
})
val x = Reg(UInt(1.W))
x := io.in.orR()
io.out := x
}
上述代码生成的verilog如下,我这里手动删除了生成的verilog代码中宏定义无法综合的部分,
module RegTest(
input clock,
input reset,
input [3:0] io_in,
input io_load,
output [3:0] io_out
);
reg x; // @[RegTest.scala 12:14]
assign io_out = {{3'd0}, x}; // @[RegTest.scala 17:10]
always @(posedge clock) begin
x <= |io_in;
end
endmodule
如果想给Reg
增加条件更新的功能,那就在when
代码块里更新Reg
值,代码如下,
package Test
import chisel3._
class RegTest extends Module {
val io = IO(new Bundle{
val in = Input(UInt(4.W))
val load = Input(Bool())
val out = Output(UInt(4.W))
})
val x = Reg(UInt(1.W))
when (io.load) {
x := io.in.orR()
}
io.out := x
}
生成的verilog代码如下,就是在always
块中增加了条件赋值语句,实现寄存器的条件更新,
module RegTest(
input clock,
input reset,
input [3:0] io_in,
input io_load,
output [3:0] io_out
);
reg x; // @[RegTest.scala 12:14]
wire _T = |io_in; // @[RegTest.scala 14:19]
assign io_out = {{3'd0}, x}; // @[RegTest.scala 17:10]
always @(posedge clock) begin
if (io_load) begin
x <= _T;
end
end
endmodule
2.2 RegNext
RegNext
与Reg
十分类似,使用Reg
时要先定义这个寄存器,然后再给寄存器赋值,而如果使用RegNext
,直接把被寄存的值放在RegNext
内即可,那么RegNext
的输出就是输入延迟1拍。RegNext
有2个apply
方法。
2.2.1 def apply[T <: Data](next: T)
这个apply
方法只接收1个参数,功能和Reg
是一样的,不带有复位功能,代码如下,
package Test
import chisel3._
class RegNextTest extends Module {
val io = IO(new Bundle() {
val in = Input(UInt(4.W))
val out = Output(UInt(4.W))
})
val x = RegNext(io.in)
io.out := x
}
上述代码实现的功能就是将输入打一拍后输出。编译产生的verilog如下,
module RegNextTest(
input clock,
input reset,
input [3:0] io_in,
output [3:0] io_out
);
reg [3:0] x; // @[RegNextTest.scala 11:18]
assign io_out = x; // @[RegNextTest.scala 12:10]
always @(posedge clock) begin
x <= io_in;
end
endmodule
2.2.2 def apply[T <: Data](next: T, init: T)
RegNext
的第二种apply
方法,除了接收一个被寄存的值之外,还接收一个寄存器复位值,例如,
package Test
import chisel3._
class RegNextTest extends Module {
val io = IO(new Bundle() {
val in = Input(UInt(4.W))
val out = Output(UInt(4.W))
})
val x = RegNext(io.in, 5.U)
io.out := x
}
和刚才的代码唯一的不同就是,我给x
这个寄存器设置了复位值为5。编译产生的verilog如下,
module RegNextTest(
input clock,
input reset,
input [3:0] io_in,
output [3:0] io_out
);
reg [3:0] x; // @[RegNextTest.scala 11:18]
assign io_out = x; // @[RegNextTest.scala 12:10]
always @(posedge clock) begin
if (reset) begin
x <= 4'h5;
end else begin
x <= io_in;
end
end
endmodule
从生成的verilog看到,寄存器x
增加了复位逻辑,高电平复位,复位值为5。
2.3 RegInit
RegInit
就是定义寄存器时,给该寄存器设置复位值。RegInit
有2个apply
方法,一个只接收寄存器复位值,另一个除了接收寄存器复位值之外还接收寄存器要保存的变量。
2.3.1 def apply[T <: Data](init: T)
第1种apply
方法,代码如下,
package Test
import chisel3._
class RegInitTest extends Module {
val io = IO(new Bundle() {
val in = Input(UInt(4.W))
val out = Output(UInt(4.W))
})
val x = RegInit(5.U)
x := io.in
io.out := x
}
编译生成的verilog如下,注意和RegNext(io.in, 5.U)
生成的verilog是一样的,
module RegInitTest(
input clock,
input reset,
input [3:0] io_in,
output [3:0] io_out
);
reg [3:0] x; // @[RegInitTest.scala 11:18]
assign io_out = x; // @[RegInitTest.scala 13:10]
always @(posedge clock) begin
if (reset) begin
x <= 4'h5;
end else begin
x <= io_in;
end
end
endmodule
2.3.2 def apply[T <: Data](t: T, init: T)
这种apply
方法为RegNext
指定了要存储的数据类型,例如,
package Test
import chisel3._
class RegInitTest extends Module {
val io = IO(new Bundle() {
val in = Input(UInt(4.W))
val out = Output(UInt(4.W))
})
val x = RegInit(chiselTypeOf(io.in), 5.U)
x := io.in
io.out := x
}
生成的verilog与第一个apply
方法生成的verilog完全一样,这个apply
方法可以chisel
层面保证寄存器存储的数据类型不出错。
module RegInitTest(
input clock,
input reset,
input [3:0] io_in,
output [3:0] io_out
);
reg [3:0] x; // @[RegInitTest.scala 11:18]
assign io_out = x; // @[RegInitTest.scala 13:10]
always @(posedge clock) begin
if (reset) begin
x <= 4'h5;
end else begin
x <= io_in;
end
end
endmodule
2.4 RegEnable
RegEnable
定义的寄存器带有条件更新的功能,使用RegEnable
需要引入chisel.util
包。有2个apply
方法,其中一个可以指定寄存器复位值。
2.4.1 def apply[T <: Data](next: T, enable: Bool)
使用这个apply
方法编译产生的寄存器,会在时钟上升沿判断enable
条件是否满足,如果满足,寄存器就更新数据,否则不更新,代码如下,
package Test
import chisel3._
import chisel3.util._
class RegEnableTest extends Module {
val io = IO(new Bundle{
val in = Input(UInt(4.W))
val load = Input(Bool())
val out = Output(UInt(4.W))
})
val x = RegEnable(io.in, io.load)
io.out := x
}
编译产生的verilog如下,
module RegEnableTest(
input clock,
input reset,
input [3:0] io_in,
input io_load,
output [3:0] io_out
);
reg [3:0] x; // @[Reg.scala 15:16]
assign io_out = x; // @[RegEnableTest.scala 13:10]
always @(posedge clock) begin
if (io_load) begin
x <= io_in;
end
end
endmodule
2.4.2 def apply[T <: Data](next: T, init: T, enable: Bool)
这个apply
方法就是可以指定复位值,代码如下,
package Test
import chisel3._
import chisel3.util._
class RegEnableTest extends Module {
val io = IO(new Bundle{
val in = Input(UInt(4.W))
val load = Input(Bool())
val out = Output(UInt(4.W))
})
val x = RegEnable(io.in, 5.U, io.load)
io.out := x
}
编译产生的verilog如下,
module RegEnableTest(
input clock,
input reset,
input [3:0] io_in,
input io_load,
output [3:0] io_out
);
reg [3:0] x; // @[Reg.scala 27:20]
assign io_out = x; // @[RegEnableTest.scala 13:10]
always @(posedge clock) begin
if (reset) begin
x <= 4'h5;
end else if (io_load) begin
x <= io_in;
end
end
endmodule
2.5 ShiftRegister
ShiftRegister
就是一个简单的打拍延迟,有两个apply
方法,一个可以指定复位值,另一个不可以。
2.5.1 def apply[T <: Data](in: T, n: Int, en: Bool = true.B)
注意第2个参数是scala的Int
类型,指定将输入延迟多少拍再输出。举个例子,
package Test
import chisel3._
import chisel3.util._
class ShiftRegisterTest extends Module {
val io = IO(new Bundle{
val in = Input(UInt(4.W))
val load = Input(Bool())
val out = Output(UInt(4.W))
})
val x = ShiftRegister(io.in, 3, io.load)
io.out := x
}
编译产生的verilog如下,这个模块就是将输入延迟3拍之后输出,并受到io_load
控制,
module ShiftRegisterTest(
input clock,
input reset,
input [3:0] io_in,
input io_load,
output [3:0] io_out
);
reg [3:0] _T; // @[Reg.scala 15:16]
reg [3:0] _T_1; // @[Reg.scala 15:16]
reg [3:0] x; // @[Reg.scala 15:16]
assign io_out = x; // @[ShiftRegisterTest.scala 14:10]
always @(posedge clock) begin
if (io_load) begin
_T <= io_in;
end
if (io_load) begin
_T_1 <= _T;
end
if (io_load) begin
x <= _T_1;
end
end
endmodule
2.5.2 def apply[T <: Data](in: T, n: Int, resetData: T, en: Bool)
这个apply
方法就是可以给ShiftRegister
指定复位值,其他和第一种apply
方法一致。举个例子,与上面的代码唯一的不同就是指定了复位值,
package Test
import chisel3._
import chisel3.util._
class ShiftRegisterTest extends Module {
val io = IO(new Bundle{
val in = Input(UInt(4.W))
val load = Input(Bool())
val out = Output(UInt(4.W))
})
val x = ShiftRegister(io.in, 3, 5.U, io.load)
io.out := x
}
编译产生的verilog如下,注意在always
块内,寄存器设置了复位逻辑,其他与第一种apply
方法生成的verilog一样的,
module ShiftRegisterTest(
input clock,
input reset,
input [3:0] io_in,
input io_load,
output [3:0] io_out
);
reg [3:0] _T; // @[Reg.scala 27:20]
reg [3:0] _T_1; // @[Reg.scala 27:20]
reg [3:0] x; // @[Reg.scala 27:20]
assign io_out = x; // @[ShiftRegisterTest.scala 14:10]
always @(posedge clock) begin
if (reset) begin
_T <= 4'h5;
end else if (io_load) begin
_T <= io_in;
end
if (reset) begin
_T_1 <= 4'h5;
end else if (io_load) begin
_T_1 <= _T;
end
if (reset) begin
x <= 4'h5;
end else if (io_load) begin
x <= _T_1;
end
end
endmodule
使用寄存器实现一个累加器Accumulator
累加器Accumulator实现是chisel-tutorial中的problem的第一个问题,给定一个1-bit的输入,每个时钟上升沿将该输入累加到输出端口上进行输出,输出是8-bit的。那么直接给代码,
// See LICENSE.txt for license details.
package problems
import chisel3._
// Problem:
//
// Count incoming trues
// (increase counter every clock if 'in' is asserted)
//
class Accumulator extends Module {
val io = IO(new Bundle {
val in = Input(UInt(1.W))
val out = Output(UInt(8.W))
})
// Implement below ----------
val accu = Reg(chiselTypeOf(io.out))
accu := accu + io.in
io.out := accu
// Implement above ----------
}
我定义了一个寄存器accu
,用于存储每个周期的累加值,使用chiselTypeOf
提取io.out
的类型,并定义accu
存储的就是这种类型的数据。
我们来看一眼测试用例,
// See LICENSE.txt for license details.
package problems
import chisel3.iotesters.PeekPokeTester
class AccumulatorTests(c: Accumulator) extends PeekPokeTester(c) {
var tot = 0
for (t <- 0 until 16) {
val in = rnd.nextInt(2)
poke(c.io.in, in)
step(1)
if (in == 1) tot += 1
expect(c.io.out, tot)
}
}
测试用例中,使用rnd.nextInt(2)
产生一个伪随机数,随机范围是0(包括)到2(不包括)的Int
类型数据,也就是0和1,将这个随机数赋值给in
。