前言
没有状态机的逻辑电路是不完整的,没有状态机的逻辑电路是不完整的,没有状态机的逻辑电路是不完整的,重要的事情说三遍。
这一节我们将要学习Chisel中的时序逻辑。在最后我们会实现一个移位器。
这里你需要知道的是,前面的这几节实际上都是对Chisel的基础语法的学习,它并不会给你带来很大的“惊喜”。Chisel真正的作用是把电路设计模块化。而我们在前几节的讲解都只是让Chisel做了Verilog也能做到的事。不必失望,我们只是还在了解语法。
寄存器(Reg
)
寄存器(Reg
)是Chisel中基础状态变量。在下一个时钟沿到来之前,Reg
会维持现有的输出值,直到时钟上升/下降,Reg
会接受来自上一级的输入值。Chisel默认给每一个Module声明一个给寄存器用的内置时钟。这样就不用像在verilog里面一样每一个都去声明一次了。
实例
在这里例子中,声明了一个module获取输入值后+1然后连接到了寄存器上,寄存器存储得到的值,在下一个时钟上升沿赋给输出。【注意看这里的Chisel代码中并没有对时钟声明,但是verilog中有时钟的定义,这就是Chisel中对每个Module时钟的内置定义。】
class RegisterModule extends Module {
val io = IO(new Bundle {
val in = Input(UInt(12.W))
val out = Output(UInt(12.W))
})
val register = Reg(UInt(12.W))
register := io.in + 1.U
io.out := register
}
println(getVerilog(new RegisterModule))
test(new RegisterModule) { c =>
for (i <- 0 until 100) {
c.io.in.poke(i.U)
c.clock.step(1)
c.io.out.expect((i + 1).U)
}
}
println("SUCCESS!!")
生成的verilog代码:
module RegisterModule(
input clock,
input reset,
input [11:0] io_in,
output [11:0] io_out
);
`ifdef RANDOMIZE_REG_INIT
reg [31:0] _RAND_0;
`endif // RANDOMIZE_REG_INIT
reg [11:0] register; // @[cmd3.sc 7:21]
assign io_out = register; // @[cmd3.sc 9:10]
always @(posedge clock) begin
register <= io_in + 12'h1; // @[cmd3.sc 8:21]
end
// Register and memory initialization
`ifdef RANDOMIZE_GARBAGE_ASSIGN
`define RANDOMIZE
`endif
`ifdef RANDOMIZE_INVALID_ASSIGN
`define RANDOMIZE
`endif
`ifdef RANDOMIZE_REG_INIT
`define RANDOMIZE
`endif
`ifdef RANDOMIZE_MEM_INIT
`define RANDOMIZE
`endif
`ifndef RANDOM
`define RANDOM $random
`endif
`ifdef RANDOMIZE_MEM_INIT
integer initvar;
`endif
`ifndef SYNTHESIS
`ifdef FIRRTL_BEFORE_INITIAL
`FIRRTL_BEFORE_INITIAL
`endif
initial begin
`ifdef RANDOMIZE
`ifdef INIT_RANDOM
`INIT_RANDOM
`endif
`ifndef VERILATOR
`ifdef RANDOMIZE_DELAY
#`RANDOMIZE_DELAY begin end
`else
#0.002 begin end
`endif
`endif
`ifdef RANDOMIZE_REG_INIT
_RAND_0 = {1{`RANDOM}};
register = _RAND_0[11:0];
`endif // RANDOMIZE_REG_INIT
`endif // RANDOMIZE
end // initial
`ifdef FIRRTL_AFTER_INITIAL
`FIRRTL_AFTER_INITIAL
`endif
`endif // SYNTHESIS
endmodule
寄存器由Reg(tpe)
语句声明,tpe
为寄存器内存储的数据类型。在这个例子中,我们用了UInt(12,W)
,代表我们寄存器内存储的是12-bit的无符号数。
在测试语句中我们观察到,和以往相比,在poke()后面多出了一句step()。因为在poke()语句中,输入会立马传输到组合逻辑中再传给输出。加了step()过后,代表了这是一个时序逻辑,输出会在时钟的边沿被赋值。
这里需要注意的是:Chisel对“类”(UInt)和“数值”(2.U)有着明确的区分。比如:
val myReg = Reg(UInt(2,W)) 是合法的 val myReg = Reg(2.W) 是不合法的,这里2.W并不是tpe类型
Chisel的状态函数
实例2 RegNext
class RegNextModule extends Module {
val io = IO(new Bundle {
val in = Input(UInt(12.W))
val out = Output(UInt(12.W))
})
// register bitwidth is inferred from io.out
io.out := RegNext(io.in + 1.U)
}
println(getVerilog(new RegNextModule))
test(new RegNextModule) { c =>
for (i <- 0 until 100) {
c.io.in.poke(i.U)
c.clock.step(1)
c.io.out.expect((i + 1).U)
}
}
println("SUCCESS!!")
对应的verilog代码(部分):
module RegNextModule(
input clock,
input reset,
input [11:0] io_in,
output [11:0] io_out
);
`ifdef RANDOMIZE_REG_INIT
reg [31:0] _RAND_0;
`endif // RANDOMIZE_REG_INIT
reg [11:0] REG; // @[cmd6.sc 8:20]//RegNext语句自动给io_out生成了一个寄存器保存输入值
assign io_out = REG; // @[cmd6.sc 8:10]
always @(posedge clock) begin
REG <= io_in + 12'h1; // @[cmd6.sc 8:27]
end
/* initialization
.... 省略了一些寄存器和存储的初始化操作
*/
endmodule
这里可以看到,在RegNext函数的作用下,verilog代码中自动给io.out声明了一个寄存器来存储输入的值,直到下一个时钟上升沿把寄存器内的值赋给输出。
实例3 RegInit
RegisterModule
中的寄存器被初始化为随机数据以进行仿真。 除非特别指定,否则寄存器没有复位值。 创建具有复位值的寄存器需要使用RegInit
。
例如,下面创建初始值为零的12位寄存器。 以下两句都是有效的,并执行相同的操作:
**val** myReg **=** **RegInit**(**UInt**(12.W), 0.U)
**val** myReg **=** **RegInit**(0.U(12.W))
第一句有两个参数。 第一个参数指定数据类型及其宽度。 第二个参数是一个硬件节点,用来指定复位值,在本例中为0。
第二句有一个参数。 它是指定复位值的硬件节点,但通常为“0.U”。
生成的verilog代码(部分):
module RegInitModule(
input clock,
input reset,
input [11:0] io_in,
output [11:0] io_out
);
`ifdef RANDOMIZE_REG_INIT
reg [31:0] _RAND_0;
`endif // RANDOMIZE_REG_INIT
reg [11:0] register; // @[cmd7.sc 7:25]
wire [11:0] _T_1 = io_in + 12'h1; // @[cmd7.sc 8:21]
assign io_out = register; // @[cmd7.sc 9:10]
always @(posedge clock) begin
if (reset) begin // @[cmd7.sc 7:25]
register <= 12'h0; // @[cmd7.sc 7:25]
end else begin
register <= _T_1; // @[cmd7.sc 8:12]
end
end
// Register and memory initialization
/* initialization
.... 省略了一些寄存器和存储的初始化操作
*/
endmodule
请注意,生成的verilog现在有一个代码块,用于检查reset
以便将寄存器复位为0。 另外还需要注意的是,这是在always @(posedge clock)
块中。 Chisel的内置的reset = 1
是在时钟的上升沿,并且是同步的。 在真正重置之前,寄存器里仍然是随机值。 PeekPokeTesters
在运行测试之前会调用reset,但您也可以使用reset(n)
函数来手动reset,其中n
表示reset信号会持续为n
个周期。
移位器
class MyShiftRegister(val init: Int = 1) extends Module {
val io = IO(new Bundle {
val in = Input(Bool())
val out = Output(UInt(4.W))
})
val state = RegInit(UInt(4.W), init.U)
state := Cat(state,io.in)
io.out := state
}
println(getVerilog(new MyShiftRegister))
test(new MyShiftRegister()) { c =>
var state = c.init
for (i <- 0 until 10) {
// poke in LSB of i (i % 2)
c.io.in.poke(((i % 2) != 0).B)
// update expected state
state = ((state * 2) + (i % 2)) & 0xf
c.clock.step(1)
c.io.out.expect(state.U)
}
}
println("SUCCESS!!")
附录 显式的时钟信号和复位信号
Chisel模块具有默认的时钟和复位信号,在模块中创建的每个寄存器都隐式使用它们。 有时您可能不想要使用这种默认的信号;也许您有一个黑盒可以生成时钟或复位信号,或者您有一个多时钟设计。
Chisel提供了处理这种情况的结构。 时钟和复位信号可以通过使用withClock(){}
,withReset(){}
以及withClockAndReset(){}
,分别或一起被override。 下面的代码块将给出使用这些函数的示例。
需要注意的一点是reset
(至少在本教程编写时)始终是同步的并且类型为Bool
。 时钟信号在Chisel(Clock
)中有自己的类型,应该这样声明。 *Bool
*可以通过调用 asClock()
来转换为Clock
类型,但是您应该小心谨慎。另外要注意的是,chisel-testers
目前还没有完全支持多时钟设计。
// 我们需要导入多时钟的功能
import chisel3.experimental.{withClock, withReset, withClockAndReset}
class ClockExamples extends Module {
val io = IO(new Bundle {
val in = Input(UInt(10.W))
val alternateReset = Input(Bool())
val alternateClock = Input(Clock())
val outImplicit = Output(UInt())
val outAlternateReset = Output(UInt())
val outAlternateClock = Output(UInt())
val outAlternateBoth = Output(UInt())
})
val imp = RegInit(0.U(10.W))
imp := io.in
io.outImplicit := imp
withReset(io.alternateReset) {
//此范围内都使用 alternateReset 作为复位信号
val altRst = RegInit(0.U(10.W))
altRst := io.in
io.outAlternateReset := altRst
}
withClock(io.alternateClock) {
val altClk = RegInit(0.U(10.W))
altClk := io.in
io.outAlternateClock := altClk
}
withClockAndReset(io.alternateClock, io.alternateReset) {
val alt = RegInit(0.U(10.W))
alt := io.in
io.outAlternateBoth := alt
}
}
println(getVerilog(new ClockExamples))
生成的verilog代码:
module cmd10HelperClockExamples(
input clock,
input reset,
input [9:0] io_in,
input io_alternateReset,
input io_alternateClock,
output [9:0] io_outImplicit,
output [9:0] io_outAlternateReset,
output [9:0] io_outAlternateClock,
output [9:0] io_outAlternateBoth
);
reg [9:0] imp; // @[cmd10.sc 14:20]
reg [31:0] _RAND_0;
reg [9:0] _T; // @[cmd10.sc 20:25]
reg [31:0] _RAND_1;
reg [9:0] _T_1; // @[cmd10.sc 26:25]
reg [31:0] _RAND_2;
reg [9:0] _T_2; // @[cmd10.sc 32:22]
reg [31:0] _RAND_3;
assign io_outImplicit = imp; // @[cmd10.sc 16:18]
assign io_outAlternateReset = _T; // @[cmd10.sc 22:26]
assign io_outAlternateClock = _T_1; // @[cmd10.sc 28:26]
assign io_outAlternateBoth = _T_2; // @[cmd10.sc 34:25]
`ifdef RANDOMIZE_GARBAGE_ASSIGN
`define RANDOMIZE
`endif
`ifdef RANDOMIZE_INVALID_ASSIGN
`define RANDOMIZE
`endif
`ifdef RANDOMIZE_REG_INIT
`define RANDOMIZE
`endif
`ifdef RANDOMIZE_MEM_INIT
`define RANDOMIZE
`endif
`ifndef RANDOM
`define RANDOM $random
`endif
`ifdef RANDOMIZE_MEM_INIT
integer initvar;
`endif
initial begin
`ifdef RANDOMIZE
`ifdef INIT_RANDOM
`INIT_RANDOM
`endif
`ifndef VERILATOR
`ifdef RANDOMIZE_DELAY
#`RANDOMIZE_DELAY begin end
`else
#0.002 begin end
`endif
`endif
`ifdef RANDOMIZE_REG_INIT
_RAND_0 = {1{`RANDOM}};
imp = _RAND_0[9:0];
`endif // RANDOMIZE_REG_INIT
`ifdef RANDOMIZE_REG_INIT
_RAND_1 = {1{`RANDOM}};
_T = _RAND_1[9:0];
`endif // RANDOMIZE_REG_INIT
`ifdef RANDOMIZE_REG_INIT
_RAND_2 = {1{`RANDOM}};
_T_1 = _RAND_2[9:0];
`endif // RANDOMIZE_REG_INIT
`ifdef RANDOMIZE_REG_INIT
_RAND_3 = {1{`RANDOM}};
_T_2 = _RAND_3[9:0];
`endif // RANDOMIZE_REG_INIT
`endif // RANDOMIZE
end
always @(posedge clock) begin
if (reset) begin
imp <= 10'h0;
end else begin
imp <= io_in;
end
if (io_alternateReset) begin
_T <= 10'h0;
end else begin
_T <= io_in;
end
end
always @(posedge io_alternateClock) begin
if (reset) begin
_T_1 <= 10'h0;
end else begin
_T_1 <= io_in;
end
if (io_alternateReset) begin
_T_2 <= 10'h0;
end else begin
_T_2 <= io_in;
end
end
endmodule