天天看點

chisel常用的硬體原語(更新)

主體内容摘自:https://blog.csdn.net/qq_34291505/article/details/87862433

前兩章介紹了基本的資料類型和硬體類型,已經足夠編寫基本的小規模電路。至于要如何生成Verilog,會在後續章節講解。如果要編寫大型電路,當然也可以一磚一瓦地搭建,但是費時費力,完全展現不出軟體語言的優勢。Chisel在語言庫裡定義了很多常用的硬體原語,讀者可以直接導入相應的包來使用。讓編譯器多幹活,讓程式員少費力。

一、多路選擇器

因為多路選擇器是一個很常用的電路子產品,是以Chisel内建了幾種多路選擇器。

  • 第一種形式是二輸入多路選擇器“

    Mux(sel, in1, in2)

    ”。sel是Bool類型,in1和in2的類型相同,都是Data的任意子類型。當sel為true.B時,傳回in1,否則傳回in2。Mux在chisel3包裡。
  • 因為Mux僅僅是把一個輸入傳回,是以Mux可以内嵌Mux,構成n輸入多路選擇器,類似于嵌套的三元操作符。其形式為“

    Mux(c1, a, Mux(c2, b, Mux(..., default)))

    ”。第二種就是針對上述n輸入多路選擇器的簡便寫法,形式為“

    MuxCase(default, Array(c1 -> a, c2 -> b, ...))

    ”,它的展開與嵌套的Mux是一樣的。第一個參數是預設情況下傳回的結果,第二個參數是一個數組,數組的元素是對偶“(成立條件(

    Bool類型

    ),被選擇的輸入)”。MuxCase在chisel3.util包裡。
  • 第三種是MuxCase的變體,它相當于把MuxCase的成立條件依次換成從0開始的索引值,就好像一個查找表,其形式為“

    MuxLookup(idx, default, Array(0.U -> a, 1.U -> b, ...))

    ”。它的展開相當于“

    MuxCase(default, Array((idx === 0.U) -> a, (idx === 1.U) -> b, ...))

    ”。MuxLookup也在chisel3.util包裡。
  • 第四種多路選擇器是Mux1H ,是chisel3.util包裡的獨熱碼多路選擇器,它的選擇信号是一個獨熱碼。如果零個或多個選擇信号有效,則行為未定義。它有以下幾種常用的定義形式:
val hotValue = Mux1H(io.selector,Seq(2.U,4.U,8.U,11.U))

val hotValue = Mux1H(Seq(io.selector(0),io.selector(1),io.selector(2),io.selector(3)),Seq(2.U,4.U,8.U,11.U))

val hotValue = Mux1H(Seq(
    io.selector(0) -> 2.U,
    io.selector(1) -> 4.U,
    io.selector(2) -> 8.U,
    io.selector(3) -> 11.U
))
           

以上三種形式是等價的,io.selector是一個UInt類型的資料,并且位寬不能小于待選擇資料的個數。在第一種形式中,Mux1H會從低到高依次将io.selector的每一位作為一個選擇信号,并和提供的被選擇資料一一對應。

  • 第五種多路選擇器是chisel3.util包裡的優先級選擇器PriorityMux,當多個選擇信号有效時,按照定義時的順序,傳回更靠前的被選資料。有以下三種定義形式:
val priorityValue = PriorityMux(io.selector,Seq(2.U,4.U,8.U,11.U))

val priorityValue = PriorityMux(Seq(io.selector(0),io.selector(1),
									io.selector(2),io.selector(3)),Seq(2.U,4.U,8.U,11.U))

val priorityValue = PriorityMux(Seq(
    io.selector(0) -> 2.U,
    io.selector(1) -> 4.U,
    io.selector(2) -> 8.U,
    io.selector(3) -> 11.U,
  ))
           

以上三種形式是等價的,io.selector是一個Bits類型的資料,位寬不小于待選擇資料的個數。

内建的多路選擇器會轉換成Verilog的三元操作符“? :”,這對于建構組合邏輯而言是完全足夠的,而且更推薦這種做法,是以when語句常用于給寄存器指派,而很少用來給線網指派。

讀者可能習慣用always語句塊來編寫電路,但這存在一些問題:

  • 首先,always既可以綜合出時序邏輯又能綜合出組合邏輯,導緻reg變量存在二義性,常常使得新手誤解reg就是寄存器;
  • 其次,if…else 不能傳播控制變量的未知态x(某些EDA工具可以),使得仿真階段無法發現一些錯誤,但是assign語句會在控制變量為x時也輸出x。

注1:

對這句話的了解是,verilog中的if是不能寫成

if(a==x) b=0

這種形式的,即使你在testbench裡面将a指派為x,這條分支也不成立,總之這樣寫是不對的。正是因為這樣,在仿真的時候,由于分支

if(a==x)

不成立,那麼b就不會為0,那麼你就錯誤的認為a不等于x,是以a的未知态就沒有傳播出去。

工業級的Verilog,都是用assign語句來建構電路。時序邏輯也是通過例化觸發器子產品來完成的,相應的端口都是由assign來驅動,而且觸發器會使用SystemVerilog的斷言來尋找always語句裡的x和z。整個設計應該盡量避免使用always語句。

二、優先編碼器

Chisel内建了兩種優先編碼器,它的作用是對多個輸入信号中優先級最高的一個信号進行編碼。

  • 第一種優先編碼器是

    PriorityEncoder

    ,它有兩種定義形式:
PriorityEncoder("b1010".U)

PriorityEncoder(Seq(true.B, false.B, true.B, false.B))
           

以上兩種形式是等價的,傳回值類型都是UInt,值為1.U。

  • 第二種優先編碼器是

    PriorityEncoderOH

    ,它也有兩種定義形式:
PriorityEncoderOH("b1010".U)
PriorityEncoderOH(Seq(false.B, true.B, true.B, false.B))
           

它和第一種編碼器的差別在于該編碼器會把編碼結果轉換成獨熱碼。第一種形式傳回一個UInt的資料2.U,第二種形式傳回一個Seq:Seq(false.B, true.B, false.B, false.B)。

三、ROM

可以通過

apply

方法“

VecInit[T <: Data](elt0: T, elts: T*)”或“VecInit[T <: Data](elts: Seq[T])

”來建立一個隻讀存儲器,參數就是ROM裡的常量數值,對應的Verilog代碼就是給讀取ROM的線網或寄存器賦予常量值(注意了解這句話)。例如:

// rom.scala
package test
 
import chisel3._
 
class ROM extends Module {
  val io = IO(new Bundle {
    val sel = Input(UInt(2.W))
    val out = Output(UInt(8.W))  
  })
 
  val rom = VecInit(1.U, 2.U, 3.U, 4.U)
 
  io.out := rom(io.sel)
}
           

對應的Verilog為:

// ROM.v
module ROM(
  input        clock,
  input        reset,
  input  [1:0] io_sel,
  output [7:0] io_out
);
  wire [2:0] _GEN_1; 
  wire [2:0] _GEN_2; 
  wire [2:0] _GEN_3; 
  assign _GEN_1 = 2'h1 == io_sel ? 3'h2 : 3'h1; 
  assign _GEN_2 = 2'h2 == io_sel ? 3'h3 : _GEN_1; 
  assign _GEN_3 = 2'h3 == io_sel ? 3'h4 : _GEN_2; 
  assign io_out = {{5'd0}, _GEN_3};
endmodule
           

在這個例子裡需要提的一點是,Vec[T]類的apply方法不僅可以接收Int類型的索引值,另一個重載版本還能接收UInt類型的索引值。是以對于承擔位址、計數器等功能的部件,可以直接作為由Vec[T]構造的元素的索引參數,比如這個例子中根據sel端口的值來選擇相應位址的ROM值。

注2:

個人認為,并沒有真正的ROM概念,隻要能夠存儲資料并且可以将資料取出就行,是以不一定使用

vecinit,像vec、mixedvec、mixedvecinit以及reg

等都是可以的。如下面的例子:
//或者val rom = Reg(Vec(4, UInt(8.W)))
 val rom = Wire(Vec(4, UInt(8.W)))
  
 rom(0) := 1.U(8.W)
 rom(1) := 2.U(8.W)
 rom(2) := 3.U(8.W)
 rom(3) := 4.U(8.W)
 io.out := rom(io.sel)
           

生成的verilog代碼是:

module ROM(
  input        clock,
  input        reset,
  input  [1:0] io_sel,
  output [7:0] io_out
);
  wire [7:0] _GEN_1 = 2'h1 == io_sel ? 8'h2 : 8'h1; // @[rom.scala 18:10 rom.scala 18:10]
  wire [7:0] _GEN_2 = 2'h2 == io_sel ? 8'h3 : _GEN_1; // @[rom.scala 18:10 rom.scala 18:10]
  assign io_out = 2'h3 == io_sel ? 8'h4 : _GEN_2; // @[rom.scala 18:10 rom.scala 18:10]
endmodule
           

四、RAM

Chisel支援兩種類型的RAM,都在chisel3包裡。

  • 第一種RAM是同步(時序)寫,異步(組合邏輯)讀,通過

    apply

    方法“

    Mem[T <: Data](size: Int, t: T)

    ”來建構。例如:

由于現代的FPGA和ASIC技術已經不再支援異步讀RAM,是以這種RAM會被綜合成寄存器陣列。

  • 第二種RAM則是同步(時序)讀、寫,通過

    apply

    方法“

    SyncReadMem[T <: Data](size: Int, t: T)

    ”來建構,這種RAM會被綜合成實際的SRAM。在Verilog代碼上,這兩種RAM都是由reg類型的變量來表示的,差別在于第二種RAM的讀位址會被位址寄存器寄存一次。例如:

寫RAM的文法是:

when(wr_en) {
     mem.write(address, dataIn) 
     out := DontCare
}
           

其中DontCare告訴Chisel的未連接配接線網檢測機制,寫入RAM時讀端口的行為無需關心。

讀RAM的文法是:

讀、寫使能信号都可以省略。

不使用write和read方法,而是使用指派語句,也可以實作讀寫,寫法如下例所示:

import chisel3._
class RWSmem extends Module {
  val width: Int = 32
  val io = IO(new Bundle {
    val enable = Input(Bool())
    val write = Input(Bool())
    val addr = Input(UInt(10.W))
    val dataIn = Input(UInt(width.W))
    val dataOut = Output(UInt(width.W))
  })

  val mem = SyncReadMem(1024, UInt(width.W))
  io.dataOut := DontCare
  when(io.enable) {
    val rdwrPort = mem(io.addr)
    when (io.write) { rdwrPort := io.dataIn }
      .otherwise    { io.dataOut := rdwrPort }
  }
}
           

注3: 關于單雙端口RAM

  • 當讀寫操作有效的條件,分别是同一條件的兩種互斥的情況時,會被推斷為單端口RAM。如:
val mem = SyncReadMem(1024, UInt(width.W))
  io.dataOut := DontCare
  when(io.enable) {
    when (io.write) { mem.write(io.addr, io.dataIn) }
      .otherwise    { io.dataOut := mem.read(io.addr) }
  }
           
  • 反之,如果讀寫操作有效的條件不互斥,那麼會被推斷為雙口RAM。如下面這種寫法:
val mem = SyncReadMem(1024, UInt(width.W))
  // Create one write port and one read port
  mem.write(io.addr, io.dataIn)
  io.dataOut := mem.read(io.addr, io.enable)
           

要綜合出實際的SRAM,讀者最好了解自己的綜合器是如何推斷的,按照綜合器的推斷規則來編寫子產品的端口定義、時鐘域劃分、讀寫使能的行為等等,否則就可能綜合出寄存器陣列而不是SRAM。以Vivado 2018.3為例,下面的單端口SRAM代碼經過綜合後會映射到FPGA上實際的BRAM資源,而不是寄存器:

// ram.scala
package test
 
import chisel3._
class SinglePortRAM extends Module {
  val io = IO(new Bundle {
    val addr = Input(UInt(10.W))
    val dataIn = Input(UInt(32.W))
    val en = Input(Bool())
    val we = Input(Bool())
    val dataOut = Output(UInt(32.W))  
  })
  val syncRAM = SyncReadMem(1024, UInt(32.W))
  when(io.en) {
    when(io.we) {
      syncRAM.write(io.addr, io.dataIn)
      io.dataOut := DontCare
    } .otherwise {
      io.dataOut := syncRAM.read(io.addr)
    }
  } .otherwise {
    io.dataOut := DontCare
  }
}
           
chisel常用的硬體原語(更新)

Vivado的BRAM最多支援真·雙端口,按照對應的Verilog模闆逆向編寫Chisel,然後用編譯器把Chisel轉換成Verilog。但此時編譯器生成的Verilog代碼并不能被Vivado的綜合器識别出來。原因在于SyncReadMem生成的Verilog代碼是用一級寄存器儲存輸入的讀位址,然後用讀位址寄存器去異步讀取RAM的資料,而Vivado的綜合器識别不出這種模式的RAM。讀者必須手動修改成用一級寄存器儲存異步讀取的資料而不是讀位址,然後把讀資料寄存器的内容用assign語句指派給讀資料端口,這樣才能被識别成真·雙端口BRAM。

尚不清楚其它綜合器是否有這個問題。經過咨詢SiFive的從業人員,對方答複因為目前轉換的代碼把延遲放在位址一側,是以流水線的節拍設計也是根據這個來的。考慮到貿然修改SyncReadMem的行為,可能會潛在地影響其它使用者對流水線的設計,故而沒有修改計劃。如果确實需要自定義的、對綜合器友好的Verilog代碼,可以使用黑盒功能替代,或者給Firrtl編譯器傳入參數,改用自定義腳本來編譯Chisel。

五、帶寫掩模的RAM(更新)

RAM通常都具備按位元組寫入的功能,比如資料寫入端口的位寬是32bit,那麼就應該有4bit的寫掩模信号,隻有當寫掩模比特有效時,對應的位元組才會寫入。Chisel也具備建構帶寫掩模的RAM的功能。

當建構RAM的資料類型為Vec[T]時,就會推斷出該RAM具有寫掩模。此時,需要定義一個Seq[Bool]類型的寫掩模信号,序列的元素個數為資料寫入端口的位寬除以位元組寬度。而write方法有一個重載版本,就是第三個參數是接收寫掩模信号的。當下标為0的寫掩模比特是true.B時,最低的那個位元組會被寫入,依次類推。下面是一個帶寫掩模的單端口RAM:

// maskram.scala
package test
 
import chisel3._
import chisel3.util._
class MaskRAM extends Module {
  val io = IO(new Bundle {
    val addr = Input(UInt(10.W))
    val dataIn = Input(UInt(32.W))
    val en = Input(Bool())
    val we = Input(Bool())
    val mask = Input(Vec(4, Bool()))
    val dataOut = Output(UInt(32.W))  
  })
  val dataIn_temp = Wire(Vec(4, UInt(8.W)))
  val dataOut_temp = Wire(Vec(4, UInt(8.W)))
  val syncRAM = SyncReadMem(1024, Vec(4, UInt(8.W)))
  dataOut_temp := DontCare
  when(io.en) {
    when(io.we){
      syncRAM.write(io.addr, dataIn_temp, io.mask)
    }.otherwise{
      dataOut_temp := syncRAM.read(io.addr)
    }
  } .otherwise {
    dataOut_temp := DontCare
  } 
  for(i <- 0 until 4) {
    dataIn_temp(i) := io.dataIn(8*i+7, 8*i)
    io.dataOut := Cat(dataOut_temp(3), dataOut_temp(2), dataOut_temp(1), dataOut_temp(0))
  }
}
           

讀、寫端口和寫掩模可以不用定義成一個UInt,也可以是Vec[UInt],這樣定義隻是為了讓子產品對外隻有一個讀端口、一個寫端口和一個寫掩模端口。

注意,編譯器會把Vec[T]的元素逐個展開,而不是合并成壓縮數組的形式。也正是如此,上述代碼對應的Verilog中,把RAM主體定義成了

“reg [7:0] syncRAM_0 [0:1023]”、“reg [7:0] syncRAM_1 [0:1023]”、“reg [7:0] syncRAM_2 [0:1023]”和“reg [7:0] syncRAM_3 [0:1023]”,而不是一個“reg [31:0] syncRAM [0:1023]”

。這樣,Vivado綜合出來的電路是四小塊BRAM,而不是一大塊BRAM。

讀寫的時候也可以不使用write和read方法,而是采用指派的形式,如下例這種寫法,同樣可以實作帶掩膜的寫操作:

import chisel3._

class MaskedRWSmem extends Module {
  val width: Int = 32
  val io = IO(new Bundle {
    val enable = Input(Bool())
    val write = Input(Bool())
    val mask = Input(Vec(2, Bool()))
    val addr = Input(UInt(10.W))
    val dataIn = Input(Vec(2, UInt(width.W)))
    val dataOut = Output(Vec(2, UInt(width.W)))
  })

  val mem = SyncReadMem(1024, Vec(2, UInt(width.W)))
  io.dataOut := DontCare
  when(io.enable) {
    val rdwrPort = mem(io.addr)
    when (io.write) {
      when(io.mask(0)) {
        rdwrPort(0) := io.dataIn(0)
      }
      when(io.mask(1)) {
        rdwrPort(1) := io.dataIn(1)
      }
    }.otherwise { io.dataOut := rdwrPort }
  }
}
           

注4:

val syncRAM = SyncReadMem(1024, Vec(4, UInt(8.W)))

這種寫法隻是為了讓編譯器推斷出該RAM具有寫掩模,而和第三部分不帶掩膜的RAM的定義方式:

val syncRAM = SyncReadMem(1024, UInt(32.W))

相比,這兩種寫法沒有必然的聯系。
我一開始以為難道因為4*8=32,這兩種寫法就等價嗎。其實并不是,由上面的分析已經可知,RAM從1塊變成了4塊。而且,既然定義成了這樣,那麼讀寫資料也要類型一緻,如

val dataIn_temp = Wire(Vec(4, UInt(8.W))),val dataOut_temp = Wire(Vec(4, UInt(8.W)))

六、從檔案讀取資料到RAM

chisel3.util.experimental包裡有一個單例對象loadMemoryFromFile

,它的apply方法可以在Chisel層面上從txt檔案讀取資料到RAM裡。其定義如下所示:

def apply[T <: Data](memory: MemBase[T], fileName: String,
 			hexOrBinary: FileType = MemoryLoadFileType.Hex): Unit
           
  • 第一個參數是MemBase[T]類型的,也就是Mem[T]和SyncReadMem[T]的超類,該參數接收一個自定義的RAM對象。
  • 第二個參數是檔案的名字及路徑,用字元串表示。
  • 第三個參數表示讀取的方式為十六進制或二進制,預設是MemoryLoadFileType.Hex,也可以改成MemoryLoadFileType.Binary。注意,沒有十進制和八進制。

該方法其實就是調用Verilog的系統函數

“$readmemh”和“$readmemb”

,是以要注意檔案路徑的書寫和資料的格式都要按照Verilog的要求書寫,比如下面這種格式:

7

d

15

舉例:

// loadmem.scala
package test
 
import chisel3._
import chisel3.util.experimental.{loadMemoryFromFile,loadMemoryFromFileInline}
 
class LoadMem extends Module {
  val io = IO(new Bundle {
    val address = Input(UInt(3.W))
    val value   = Output(UInt(8.W))
  })
  val memory = Mem(8, UInt(8.W))
  io.value := memory.read(io.address)
  loadMemoryFromFile(memory, "/home/alex/Desktop/chisel-examples/mem.txt")
}
           

那麼就會得到兩個Verilog檔案:

// LoadMem.v
module LoadMem(
  input        clock,
  input        reset,
  input  [2:0] io_address,
  output [7:0] io_value
);
`ifdef RANDOMIZE_MEM_INIT
  reg [31:0] _RAND_0;
`endif // RANDOMIZE_MEM_INIT
  reg [7:0] memory [0:7]; // @[LoadMemTester.scala 12:19]
  wire [7:0] memory_MPORT_data; // @[LoadMemTester.scala 12:19]
  wire [2:0] memory_MPORT_addr; // @[LoadMemTester.scala 12:19]
  assign memory_MPORT_addr = io_address;
  assign memory_MPORT_data = memory[memory_MPORT_addr]; // @[LoadMemTester.scala 12:19]
  assign io_value = memory_MPORT_data; // @[LoadMemTester.scala 13:12]
endmodule
 
// LoadMem.LoadMem.memory.v
module BindsTo_0_LoadMem(
  input        clock,
  input        reset,
  input  [2:0] io_address,
  output [7:0] io_value
);

initial begin
  $readmemh("/home/alex/Desktop/chisel-examples/mem.txt", LoadMem.memory);
end
endmodule

bind LoadMem BindsTo_0_LoadMem BindsTo_0_LoadMem_Inst(.*);
           

在用Verilator仿真時,它會識别這個Chisel代碼,從檔案讀取資料。

注5:

在chisel3.4.3版本(目前最新),還引入了一個單例對象

loadMemoryFromFileInline

,它的使用方法和

loadMemoryFromFile

一模一樣,隻不過不再單獨生成一個用來讀取檔案資料到mem的BindsTo_0_LoadMem子產品,而是将讀取檔案資料的代碼直接嵌入到LoadMem子產品中,如下所示:
module LoadMem(
  input        clock,
  input        reset,
  input  [2:0] io_address,
  output [7:0] io_value
);
  reg [7:0] memory [0:7]; // @[LoadMemTester.scala 12:19]
  wire [7:0] memory_MPORT_data; // @[LoadMemTester.scala 12:19]
  wire [2:0] memory_MPORT_addr; // @[LoadMemTester.scala 12:19]
  assign memory_MPORT_addr = io_address;
  assign memory_MPORT_data = memory[memory_MPORT_addr]; // @[LoadMemTester.scala 12:19]
  assign io_value = memory_MPORT_data; // @[LoadMemTester.scala 13:12]
// 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
  integer initvar;
`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
  `endif // RANDOMIZE
  $readmemh("/home/alex/Desktop/chisel-examples/mem.txt", memory);
end // initial
`ifdef FIRRTL_AFTER_INITIAL
`FIRRTL_AFTER_INITIAL
`endif
`endif // SYNTHESIS
endmodule

           

七、計數器(更新)

計數器也是一個常用的硬體電路。在Chisel.util包裡定義了一個自增計數器原語Counter,它的

apply

方法是:

apply(cond: Bool, n: Int): (UInt, Bool)

,接收兩個參數:

  • 第一個參數是Bool類型的使能信号,為true.B時計數器從0開始每個時鐘上升沿加1自增,為false.B時則計數器保持不變;
  • 第二個參數需要一個Int類型的具體正數,當計數到n時歸零。

該方法傳回一個二進制組:

  • 其第一個元素是計數器的計數值,
  • 第二個元素是判斷計數值是否等于n的結果。

apply

方法還有兩個重載版本:

  • def apply(r: Range, enable: Bool = true.B, reset: Bool = false.B): (UInt, Bool)

    ,該版本的傳回值和上述一樣,隻不過入參有所不同:
  • r:是一個

    scala.collection.immutable.Range

    類型的參數,用來提供一個計數的範圍。
  • enable:有效時表示該clk使能計數器計數。
  • reset:有效時表示該clk使計數器複位到初始值,這裡的初始值是提供的計數範圍的起始值。如果不提供,那麼預設跟随隐式複位信号,如果提供,那麼就會有多個複位信号。
  • apply(n: Int): Counter

    ,和上述兩個方法不同,該方法傳回的是一個Counter對象,有如下的屬性和方法可以使用:

def inc(): Bool

        随時鐘使計數器+1,傳回值表示計數器是否在下一個時鐘周期結束

def n: Int

        無參函數,傳回計數器的計數最大值n

def range: Range

        無參函數,傳回計數器的計數範圍,類型為Range

def reset(): Unit

        使計數器複位到初始值0

val value: UInt

        表示計數器此時的計數值,因為是val類型,是以隻能讀取

傳回一個對象之後,就需要根據所需邏輯,手動的使能、複位計數,或者擷取此時的計數器狀态。

假設我們想從0計數到233,那麼分别使用上述三種版本的apply方法的寫法如下:

  • apply(cond: Bool, n: Int): (UInt, Bool)

// counter.scala
package test
 
import chisel3._
import chisel3.util._
 
class MyCounter extends Module {
  val io = IO(new Bundle {
    val en = Input(Bool())
    val out = Output(UInt(8.W))
    val valid = Output(Bool())  
  })
 
  val (a, b) = Counter(io.en, 233)
  io.out := a
  io.valid := b
}
           

生成的verilog代碼如下:

module MyCounter(
  input        clock,
  input        reset,
  input        io_en,
  output [7:0] io_out,
  output       io_valid
);
`ifdef RANDOMIZE_REG_INIT
  reg [31:0] _RAND_0;
`endif // RANDOMIZE_REG_INIT
  reg [7:0] a; // @[Counter.scala 60:40]
  wire  wrap_wrap = a == 8'he8; // @[Counter.scala 72:24]
  wire [7:0] _wrap_value_T_1 = a + 8'h1; // @[Counter.scala 76:24]
  assign io_out = a; // @[MyCounter.scala 15:10]
  assign io_valid = io_en & wrap_wrap; // @[Counter.scala 118:17 Counter.scala 118:24]
  always @(posedge clock) begin
    if (reset) begin // @[Counter.scala 60:40]
      a <= 8'h0; // @[Counter.scala 60:40]
    end else if (io_en) begin // @[Counter.scala 118:17]
      if (wrap_wrap) begin // @[Counter.scala 86:20]
        a <= 8'h0; // @[Counter.scala 86:28]
      end else begin
        a <= _wrap_value_T_1; // @[Counter.scala 76:15]
      end
    end
  end
endmodule
           
  • def apply(r: Range, enable: Bool = true.B, reset: Bool = false.B): (UInt, Bool)

import chisel3._
import chisel3.util._
import scala.collection.immutable.Range

class MyCounter extends Module {
  val io = IO(new Bundle {
    val en = Input(Bool())
    val out = Output(UInt(8.W))
    val valid = Output(Bool())
  })

  val (a, b) = Counter(Range(0,233),io.en)
  io.out := a
  io.valid := b
}
           

生成的verilog代碼如下:

module MyCounter(
  input        clock,
  input        reset,
  input        io_en,
  output [7:0] io_out,
  output       io_valid
);
`ifdef RANDOMIZE_REG_INIT
  reg [31:0] _RAND_0;
`endif // RANDOMIZE_REG_INIT
  reg [7:0] a; // @[Counter.scala 60:40]
  wire  wrap_wrap = a == 8'he8; // @[Counter.scala 72:24]
  wire [7:0] _wrap_value_T_1 = a + 8'h1; // @[Counter.scala 76:24]
  assign io_out = a; // @[MyCounter.scala 17:10]
  assign io_valid = io_en & wrap_wrap; // @[Counter.scala 137:24 Counter.scala 138:12]
  always @(posedge clock) begin
    if (reset) begin // @[Counter.scala 60:40]
      a <= 8'h0; // @[Counter.scala 60:40]
    end else if (io_en) begin // @[Counter.scala 137:24]
      if (wrap_wrap) begin // @[Counter.scala 86:20]
        a <= 8'h0; // @[Counter.scala 86:28]
      end else begin
        a <= _wrap_value_T_1; // @[Counter.scala 76:15]
      end
    end
  end
endmodule
           
  • apply(n: Int): Counter

import chisel3._
import chisel3.util._

class MyCounter extends Module {
  val io = IO(new Bundle {
    val en = Input(Bool())
    val out = Output(UInt(8.W))
    val valid = Output(Bool())
  })
  val cnt = Counter(233)

  when(io.en){
    cnt.inc()
  }

  val a = cnt.value
  val b = cnt.value === cnt.n.U

  io.out := a
  io.valid := b
}
           
module MyCounter(
  input        clock,
  input        reset,
  input        io_en,
  output [7:0] io_out,
  output       io_valid
);
`ifdef RANDOMIZE_REG_INIT
  reg [31:0] _RAND_0;
`endif // RANDOMIZE_REG_INIT
  reg [7:0] value; // @[Counter.scala 60:40]
  wire  wrap = value == 8'he8; // @[Counter.scala 72:24]
  wire [7:0] _value_T_1 = value + 8'h1; // @[Counter.scala 76:24]
  assign io_out = value; // @[MyCounter.scala 22:10]
  assign io_valid = value == 8'he9; // @[MyCounter.scala 20:21]
  always @(posedge clock) begin
    if (reset) begin // @[Counter.scala 60:40]
      value <= 8'h0; // @[Counter.scala 60:40]
    end else if (io_en) begin // @[MyCounter.scala 15:14]
      if (wrap) begin // @[Counter.scala 86:20]
        value <= 8'h0; // @[Counter.scala 86:28]
      end else begin
        value <= _value_T_1; // @[Counter.scala 76:15]
      end
    end
  end
endmodule
           

八、線性回報移位寄存器(更新)

如果要産生

僞随機數

,可以使用chisel3.util.random包裡的線性回報移位寄存器原語

LFSR

def apply(width: Int, increment: Bool = true.B, seed: Option[BigInt] = Some(1)): UInt
           
  • 第一個參數是寄存器的位寬。
  • 第二個參數是一個

    Bool類型

    的使能信号,用于控制寄存器是否移位,預設值為true.B。
  • 第三個參數是一個随機種子,是可選值類型。

它傳回一個UInt(width.W)類型的結果。例如:

// lfsr.scala
package test
 
import chisel3._
import chisel3.util._
 
class LFSR16 extends Module {
  val io = IO(new Bundle {
    val en = Input(Bool())
    val out = Output(UInt(16.W))
  })

  io.out := LFSR(16,io.en,Some(1))
}
           

它生成的主要Verilog代碼為:

// LFSR.v
module LFSR16(
  input   clock,
  input   reset,
  input   io_increment,
  output  io_out_0,
  output  io_out_1,
  output  io_out_2,
  output  io_out_3,
  output  io_out_4,
  output  io_out_5,
  output  io_out_6,
  output  io_out_7,
  output  io_out_8,
  output  io_out_9,
  output  io_out_10,
  output  io_out_11,
  output  io_out_12,
  output  io_out_13,
  output  io_out_14,
  output  io_out_15
);
`ifdef RANDOMIZE_REG_INIT
  reg [31:0] _RAND_0;
  reg [31:0] _RAND_1;
  reg [31:0] _RAND_2;
  reg [31:0] _RAND_3;
  reg [31:0] _RAND_4;
  reg [31:0] _RAND_5;
  reg [31:0] _RAND_6;
  reg [31:0] _RAND_7;
  reg [31:0] _RAND_8;
  reg [31:0] _RAND_9;
  reg [31:0] _RAND_10;
  reg [31:0] _RAND_11;
  reg [31:0] _RAND_12;
  reg [31:0] _RAND_13;
  reg [31:0] _RAND_14;
  reg [31:0] _RAND_15;
`endif // RANDOMIZE_REG_INIT
  reg  state_0; // @[PRNG.scala 47:50]
  reg  state_1; // @[PRNG.scala 47:50]
  reg  state_2; // @[PRNG.scala 47:50]
  reg  state_3; // @[PRNG.scala 47:50]
  reg  state_4; // @[PRNG.scala 47:50]
  reg  state_5; // @[PRNG.scala 47:50]
  reg  state_6; // @[PRNG.scala 47:50]
  reg  state_7; // @[PRNG.scala 47:50]
  reg  state_8; // @[PRNG.scala 47:50]
  reg  state_9; // @[PRNG.scala 47:50]
  reg  state_10; // @[PRNG.scala 47:50]
  reg  state_11; // @[PRNG.scala 47:50]
  reg  state_12; // @[PRNG.scala 47:50]
  reg  state_13; // @[PRNG.scala 47:50]
  reg  state_14; // @[PRNG.scala 47:50]
  reg  state_15; // @[PRNG.scala 47:50]
  wire  _T_2 = state_15 ^ state_13 ^ state_12 ^ state_10; // @[LFSR.scala 15:41]
  wire  _GEN_0 = io_increment ? _T_2 : state_0; // @[PRNG.scala 61:23 PRNG.scala 62:11 PRNG.scala 47:50]
  assign io_out_0 = state_0; // @[PRNG.scala 69:10]
  assign io_out_1 = state_1; // @[PRNG.scala 69:10]
  assign io_out_2 = state_2; // @[PRNG.scala 69:10]
  assign io_out_3 = state_3; // @[PRNG.scala 69:10]
  assign io_out_4 = state_4; // @[PRNG.scala 69:10]
  assign io_out_5 = state_5; // @[PRNG.scala 69:10]
  assign io_out_6 = state_6; // @[PRNG.scala 69:10]
  assign io_out_7 = state_7; // @[PRNG.scala 69:10]
  assign io_out_8 = state_8; // @[PRNG.scala 69:10]
  assign io_out_9 = state_9; // @[PRNG.scala 69:10]
  assign io_out_10 = state_10; // @[PRNG.scala 69:10]
  assign io_out_11 = state_11; // @[PRNG.scala 69:10]
  assign io_out_12 = state_12; // @[PRNG.scala 69:10]
  assign io_out_13 = state_13; // @[PRNG.scala 69:10]
  assign io_out_14 = state_14; // @[PRNG.scala 69:10]
  assign io_out_15 = state_15; // @[PRNG.scala 69:10]
  always @(posedge clock) begin
    state_0 <= reset | _GEN_0; // @[PRNG.scala 47:50 PRNG.scala 47:50]
    if (reset) begin // @[PRNG.scala 47:50]
      state_1 <= 1'h0; // @[PRNG.scala 47:50]
    end else if (io_increment) begin // @[PRNG.scala 61:23]
      state_1 <= state_0; // @[PRNG.scala 62:11]
    end
    if (reset) begin // @[PRNG.scala 47:50]
      state_2 <= 1'h0; // @[PRNG.scala 47:50]
    end else if (io_increment) begin // @[PRNG.scala 61:23]
      state_2 <= state_1; // @[PRNG.scala 62:11]
    end
    if (reset) begin // @[PRNG.scala 47:50]
      state_3 <= 1'h0; // @[PRNG.scala 47:50]
    end else if (io_increment) begin // @[PRNG.scala 61:23]
      state_3 <= state_2; // @[PRNG.scala 62:11]
    end
    if (reset) begin // @[PRNG.scala 47:50]
      state_4 <= 1'h0; // @[PRNG.scala 47:50]
    end else if (io_increment) begin // @[PRNG.scala 61:23]
      state_4 <= state_3; // @[PRNG.scala 62:11]
    end
    if (reset) begin // @[PRNG.scala 47:50]
      state_5 <= 1'h0; // @[PRNG.scala 47:50]
    end else if (io_increment) begin // @[PRNG.scala 61:23]
      state_5 <= state_4; // @[PRNG.scala 62:11]
    end
    if (reset) begin // @[PRNG.scala 47:50]
      state_6 <= 1'h0; // @[PRNG.scala 47:50]
    end else if (io_increment) begin // @[PRNG.scala 61:23]
      state_6 <= state_5; // @[PRNG.scala 62:11]
    end
    if (reset) begin // @[PRNG.scala 47:50]
      state_7 <= 1'h0; // @[PRNG.scala 47:50]
    end else if (io_increment) begin // @[PRNG.scala 61:23]
      state_7 <= state_6; // @[PRNG.scala 62:11]
    end
    if (reset) begin // @[PRNG.scala 47:50]
      state_8 <= 1'h0; // @[PRNG.scala 47:50]
    end else if (io_increment) begin // @[PRNG.scala 61:23]
      state_8 <= state_7; // @[PRNG.scala 62:11]
    end
    if (reset) begin // @[PRNG.scala 47:50]
      state_9 <= 1'h0; // @[PRNG.scala 47:50]
    end else if (io_increment) begin // @[PRNG.scala 61:23]
      state_9 <= state_8; // @[PRNG.scala 62:11]
    end
    if (reset) begin // @[PRNG.scala 47:50]
      state_10 <= 1'h0; // @[PRNG.scala 47:50]
    end else if (io_increment) begin // @[PRNG.scala 61:23]
      state_10 <= state_9; // @[PRNG.scala 62:11]
    end
    if (reset) begin // @[PRNG.scala 47:50]
      state_11 <= 1'h0; // @[PRNG.scala 47:50]
    end else if (io_increment) begin // @[PRNG.scala 61:23]
      state_11 <= state_10; // @[PRNG.scala 62:11]
    end
    if (reset) begin // @[PRNG.scala 47:50]
      state_12 <= 1'h0; // @[PRNG.scala 47:50]
    end else if (io_increment) begin // @[PRNG.scala 61:23]
      state_12 <= state_11; // @[PRNG.scala 62:11]
    end
    if (reset) begin // @[PRNG.scala 47:50]
      state_13 <= 1'h0; // @[PRNG.scala 47:50]
    end else if (io_increment) begin // @[PRNG.scala 61:23]
      state_13 <= state_12; // @[PRNG.scala 62:11]
    end
    if (reset) begin // @[PRNG.scala 47:50]
      state_14 <= 1'h0; // @[PRNG.scala 47:50]
    end else if (io_increment) begin // @[PRNG.scala 61:23]
      state_14 <= state_13; // @[PRNG.scala 62:11]
    end
    if (reset) begin // @[PRNG.scala 47:50]
      state_15 <= 1'h0; // @[PRNG.scala 47:50]
    end else if (io_increment) begin // @[PRNG.scala 61:23]
      state_15 <= state_14; // @[PRNG.scala 62:11]
    end
  end
endmodule
           

九、XOR和XNOR

chisel3.util.random

下面還有兩個原語,

異或XOR和同或XNOR

,使用非常簡單,入參是兩個Bool類型的資料,傳回值也是Bool類型的資料。

chisel常用的硬體原語(更新)
chisel常用的硬體原語(更新)

十、總結

本章介紹了Chisel内建的常用原語,使用原語可以幫助我們極大的簡化代碼設計,避免重複造輪子!!!

繼續閱讀