【Chisel】2.4 时序逻辑

前言

没有状态机的逻辑电路是不完整的,没有状态机的逻辑电路是不完整的,没有状态机的逻辑电路是不完整的,重要的事情说三遍。

这一节我们将要学习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的状态函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ICeCSDxi-1647403065819)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/05518793-4db4-4bdc-849a-56c158780393/Untitled.png)]

实例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个周期。

移位器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VO1pELL4-1647403065820)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/773cc730-995e-487a-9cf9-c84adec5edd3/Untitled.png)]

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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值