chisel线网(wire)和寄存器(reg)详解(更新)
主體內容摘自:https://blog.csdn.net/qq_34291505/article/details/87714172
在Verilog里,模塊內部主要有“線網(wire)”和“四態變量(reg)”兩種硬件類型,它們用于描述數字電路的組合邏輯和時序邏輯。在Chisel里,也按這個思路定義了一些硬件類型,包括基本的線網和寄存器。
在verilog中我們經常會對線網和寄存器賦值,以完成信號的傳遞或者電路的連接。在chisel中也是,線網和寄存器最常見的操作都是被賦值,因為只有這樣我們才能實現各種我們想要的電路邏輯,所以,下面也會對在chisel中如何對線網和寄存器賦值做一下說明!!!
一、Chisel是如何賦值的
我們可以使用用賦值操作來進行信號的傳遞或者電路的連接。只有硬件賦值才有意義,單純的數據類型的對象進行賦值并不會被編譯器轉換成實際的電路,因為在Verilog里也是對wire、reg類型的硬件進行賦值。那么,賦值操作需要什么樣的操作符來完成呢?
在Chisel里,所有對象都應該由val類型的變量來引用,因為硬件電路的不可變性。因此,一個變量一旦初始化時綁定了一個對象,就不能再發生更改。但是,引用的對象很可能需要被重新賦值。例如,輸出端口在定義時使用了“=”與端口變量名進行了綁定,那等到驅動該端口時,就需要通過變量名來進行賦值操作,更新數據。很顯然,此時“=”已經不可用了,因為變量在聲明的時候不是var類型。即使是var類型,這也只是讓變量引用新的對象,而不是直接更新原來的可變對象。
為了解決這個問題,幾乎所有的Chisel類都定義了方法“:=”,作為等號賦值的代替。所以首次創建變量時用等號初始化,如果變量引用的對象不能立即確定狀態或本身就是可變對象,則在后續更新狀態時應該用“:=”。從前面講的操作符優先級來判斷,該操作符以等號結尾,而且不是四種邏輯比較符號之一,所以優先級與等號一致,是最低的。例如:
val x = Wire(UInt(4.W))val y = Wire(UInt(4.W))x := "b1010".U // 向4bit的線網x賦予了無符號數10y := ~x // 把x按位取反,傳遞給y二、線網(類似于verilog中組合邏輯用的wire)
Chisel把線網作為電路的節點,通過工廠方法“Wire[T <: Data](t: T)”來定義??梢詫€網進行賦值,也可以連接到其他電路節點,這是組成組合邏輯的基本硬件類型。例如:
- 可以自動推斷位寬:
- 也可以指定位寬:
注1:
- 里面包裹Vec或者MixedVec也是可以的(注意,不能包裹Vecinit或者MixedVecinit,因為它們傳入的是硬件類型,返回的也是硬件類型,而Wire必須傳入數據類型):
但需要注意的是,一旦被Wire包裹,必須提供初始值,也即必須被驅動,至少要賦個DontCare,否則會報錯。不過這個檢查機制是可以關閉的!!!
注2: 賦值覆蓋性理解
因為Scala作為軟件語言是順序執行的,定義具有覆蓋性,所以如果對同一個線網多次賦值,則只有最后一次有效。例如下面的代碼與上面的例子是等效的:
val myNode = Wire(UInt(8.W))myNode := 10.UmyNode := 0.U需要注意的是,如果存在多條賦值語句,那么要考慮覆蓋性;如果只有一條賦值語句,那么該條語句一定是有效的,不能認為軟件語言是順序執行的就覺得該條語句執行過一次之后就沒有了。因為對線網類型的賦值操作,在生成的verilog代碼中一定會將其變成assign語句,那么該語句是一直有效的,你也可以認為一直在執行,只要右邊的驅動源發生變化,左邊的目標信號就一定會改變!!!下面舉一個例子:
這是一個0-1上升沿檢測的狀態機,可以看到在switch里面,并沒有對io.risingEdge進行false的賦值。這是因為,當需要進行true賦值的時候,它會覆蓋一開始的false賦值語句;但是當條件不滿足,不進行true賦值的時候,最上面的false賦值語句就會起作用,所以不在switch里面進行false賦值也是可以的??傊?#xff0c;總會有一個賦值語句起作用,否則就有可能出現未驅動的情況!!!
注3: 未驅動的線網
Chisel的Invalidate API支持檢測未驅動的輸出型IO以及定義不完整的Wire定義,在編譯成firrtl時會產生“not fully initialized”錯誤。換句話說,就是組合邏輯的真值表不完整,不能綜合出完整的電路。如果確實需要不被驅動的線網,則可以賦給一個DontCare對象,這會告訴Firrtl編譯器,該線網故意不被驅動。轉換成的Verilog會賦予該信號全0值,甚至把邏輯全部優化掉,所以謹慎使用。例如:
val io = IO(new Bundle {val outs = Output(Vec(10, Bool())) }) io.outs <> DontCare檢查機制是由CompileOptions.explicitInvalidate控制的,如果把它設置成true就是嚴格模式(執行檢查),設置成false就是不嚴格模式(不執行檢查)。
- 開關方法有兩種,第一種是定義一個抽象的模塊類,由抽象類設置,其余模塊都繼承自這個抽象類。例如:
- 第二種方法是在每個模塊里重寫compileOptions字段,由該字段設置編譯選項。例如:
三、寄存器(類似于verilog中時序邏輯中用的reg)
寄存器是時序邏輯的基本硬件類型,它們都是由當前時鐘域的時鐘上升沿觸發的。如果模塊里沒有多時鐘域的語句塊,那么寄存器都是由隱式的全局時鐘來控制。對于有復位信號的寄存器,如果不在多時鐘域語句塊里,則由隱式的全局復位來控制,并且高有效。目前Chisel所有的復位都是同步復位,異步復位功能還在開發中。如果需要異步復位寄存器,則需要通過黑盒引入。
有五種內建的寄存器:
- 第一種是跟隨寄存器“RegNext[T <: Data](next: T)”,在每個時鐘上升沿,它都會采樣一次傳入的參數(跟隨),并且沒有復位信號;它的另一個版本的apply工廠方法是“RegNext[T <: Data](next: T, init: T)”,也就是由復位信號控制,當復位信號有效時,復位到指定值,否則就跟隨。
注4:
注意,在定義時傳入next,后面可以無需再顯式的對寄存器賦值,它會自動跟隨next;但也可以在后面再對其進行無條件或者有條件賦值,這樣會覆蓋掉之前的定義或者賦值,然后開始跟隨新的變量。這里的覆蓋,比如RegNext[T <: Data](next: T, init: T),一開始如果給了init參數,那么是會有復位信號的, 但如果被后面的無條件或者有條件賦值給覆蓋,那么復位信號的有無就要取決了后面的賦值邏輯!!!
- 第二種是復位到指定值的寄存器“RegInit[T <: Data](init: T)”或者“RegInit[T <: Data](t: T, init: T)”,可以進行條件賦值,也可以無條件賦值;默認有復位信號;參數t的意思是提供一個數據類型模板,也即指定數據的類型,不用給確切的值。
定義之后可以不顯式賦值,但是會被當做一個常量,常量的值取決于你定義時給的init參數的值。
- 第三種是普通的寄存器“Reg[T <: Data](t: T)”,可以進行條件賦值,也可以進行無條件賦值,默認沒有復位信號;參數t的意思是提供一個數據類型模板,也即指定數據的類型,不用給確切的值。
注5:
定義之后必須顯式賦值,否則報錯!!!
- 第四種是util包里的帶一個使能端的寄存器“RegEnable[T <: Data](next: T, init: T, enable: Bool)”,如果不需要復位信號,則第二個參數需要省略給出;有使能信號。
注6:
注意,在定義時傳入next,后面可以無需再顯式的對寄存器賦值,它會自動跟隨next;但也可以在后面再對其進行無條件或者有條件賦值,這樣會覆蓋掉之前的定義或者賦值,然后開始跟隨新的變量。一開始如果給了init參數,那么是會有復位信號的,也有使能信號; 但如果被后面的無條件或者有條件賦值給覆蓋,那么復位信號和使能信號的有無就都要取決了后面的賦值邏輯了!!!
- 第五種是util包里的移位寄存器“ShiftRegister[T <: Data](in: T, n: Int, resetData: T, en: Bool)”,其中第一個參數in是待移位的數據;第二個參數n是需要延遲的周期數;第三個參數resetData是指定的復位值,可以省略,省略之后無復位信號;第四個參數en是使能移位的信號,默認為true.B。
從以上內容可以總結出:
- 如果寄存器有next參數,那么就可以無任何顯式賦值操作,寄存器會跟隨next參數,如RegNext、RegEnable;如果無next參數,而是t參數,那么就必須顯式的賦值,如RegInit、Reg;ShiftRegister因為有in參數,也可以不用顯式賦值。
- 如果有init或者resetData參數,那么就不需要顯式的將reset信號作為when的條件進行賦值,這樣也會有復位信號,當然也可以通過省略init參數使得生成的verilog代碼中沒有復位信號(RegInit除外);如果沒有init參數,就只有通過將reset信號作為when的條件進行賦值才會有復位信號。
- 無條件賦值就是直接賦值,有條件就是使用when語句。
- 如果寄存器被賦值為常量,那么在生成的verilog中就不會出現該寄存器變量;如果被賦值為變量,那么才會在跟隨該變量,跟隨就是每個時鐘上升沿時對變量進行采樣。
- 除了Reg,其余寄存器傳入的參數都必須是硬件類型,而Reg傳入的參數是數據類型。
- 關于t參數的理解:提供一個參數模板的意思就是指定該寄存器的數據類型,不能指定為確切值,并且在以后賦值的時候所提供的值必須是該類型,如下所示:
假如有如下代碼:
// reg.scala package testimport chisel3._ import chisel3.util._ class REG extends Module {val io = IO(new Bundle {val a = Input(UInt(8.W))val en = Input(Bool())val c = Output(UInt(1.W))})val reg0 = RegNext(io.a)val reg1 = RegNext(io.a, 0.U)val reg2 = RegInit(0.U(8.W))val reg3 = Reg(UInt(8.W))val reg4 = Reg(UInt(8.W))val reg5 = RegEnable(io.a + 1.U, 0.U, io.en)val reg6 = RegEnable(io.a - 1.U, io.en)val reg7 = ShiftRegister(io.a, 3, 0.U, io.en)val reg8 = ShiftRegister(io.a, 3, io.en)reg2 := io.a.andRreg3 := io.a.orRwhen(reset.asBool) {reg4 := 0.U} .otherwise {reg4 := 1.U}io.c := reg0(0) & reg1(0) & reg2(0) & reg3(0) & reg4(0) & reg5(0) & reg6(0) & reg7(0) & reg8(0) }對應生成的主要Verilog代碼為:
// REG.v module REG(input clock,input reset,input [7:0] io_a,input io_en,output io_c );reg [7:0] reg0; reg [7:0] reg1; reg [7:0] reg2; reg [7:0] reg3; reg [7:0] reg4; wire [7:0] _T_1; reg [7:0] reg5; wire [8:0] _T_2; wire [8:0] _T_3; wire [7:0] _T_4; reg [7:0] reg6; reg [7:0] _T_5; reg [7:0] _T_6; reg [7:0] reg7; reg [7:0] _T_7; reg [7:0] _T_8; reg [7:0] reg8; wire [7:0] _T_9; wire _T_10; wire _T_11; wire _GEN_8; wire _T_13; wire _T_14; wire _T_15; wire _T_16; wire _T_17; wire _T_18; wire _T_19; wire _T_20; wire _T_21; wire _T_22; wire _T_23; wire _T_24; wire _T_25; wire _T_26; wire _T_27; wire _T_28; assign _T_1 = io_a + 8'h1; assign _T_2 = io_a - 8'h1; assign _T_3 = $unsigned(_T_2); assign _T_4 = _T_3[7:0]; assign _T_9 = ~ io_a; assign _T_10 = _T_9 == 8'h0; assign _T_11 = io_a != 8'h0; assign _GEN_8 = reset ? 1'h0 : 1'h1; assign _T_13 = reg0[0]; assign _T_14 = reg1[0]; assign _T_15 = _T_13 & _T_14; assign _T_16 = reg2[0]; assign _T_17 = _T_15 & _T_16; assign _T_18 = reg3[0]; assign _T_19 = _T_17 & _T_18; assign _T_20 = reg4[0]; assign _T_21 = _T_19 & _T_20; assign _T_22 = reg5[0]; assign _T_23 = _T_21 & _T_22; assign _T_24 = reg6[0]; assign _T_25 = _T_23 & _T_24; assign _T_26 = reg7[0]; assign _T_27 = _T_25 & _T_26; assign _T_28 = reg8[0]; assign io_c = _T_27 & _T_28; always @(posedge clock) beginreg0 <= io_a;if (reset) beginreg1 <= 8'h0;end else beginreg1 <= io_a;endif (reset) beginreg2 <= 8'h0;end else beginreg2 <= {{7'd0}, _T_10};endreg3 <= {{7'd0}, _T_11};reg4 <= {{7'd0}, _GEN_8};if (reset) beginreg5 <= 8'h0;end else beginif (io_en) beginreg5 <= _T_1;endendif (io_en) beginreg6 <= _T_4;endif (reset) begin_T_5 <= 8'h0;end else beginif (io_en) begin_T_5 <= io_a;endendif (reset) begin_T_6 <= 8'h0;end else beginif (io_en) begin_T_6 <= _T_5;endendif (reset) beginreg7 <= 8'h0;end else beginif (io_en) beginreg7 <= _T_6;endendif (io_en) begin_T_7 <= io_a;endif (io_en) begin_T_8 <= _T_7;endif (io_en) beginreg8 <= _T_8;endend endmodule四、寄存器組(可以一次性生成多個reg)
上述構造寄存器的工廠方法,它們的參數可以是任何Data的子類型。如果把子類型Vec[T]作為參數傳遞進去,就會生成多個位寬相同、行為相同、名字前綴相同的寄存器。當然如果Vec[T]或者Vecinit[T]中的參數不一樣,那么生成的多個reg也不一樣,其實它們本質上是沒啥必然聯系的,具體咋用取決于你想怎么操作它們!!!。同樣,寄存器組在Chisel代碼里可以通過下標索引。例如:
// reg2.scala package testimport chisel3._ import chisel3.util._ class REG2 extends Module {val io = IO(new Bundle {val a = Input(UInt(8.W))val en = Input(Bool())val c = Output(UInt(1.W))})val reg0 = RegNext(VecInit(io.a, io.a))val reg1 = RegNext(VecInit(io.a, io.a), VecInit(0.U, 0.U))val reg2 = RegInit(VecInit(0.U(8.W), 0.U(8.W)))val reg3 = Reg(Vec(2, UInt(8.W)))val reg4 = Reg(Vec(2, UInt(8.W)))val reg5 = RegEnable(VecInit(io.a + 1.U, io.a + 1.U), VecInit(0.U(8.W), 0.U(8.W)), io.en)val reg6 = RegEnable(VecInit(io.a - 1.U, io.a - 1.U), io.en)val reg7 = ShiftRegister(VecInit(io.a, io.a), 3, VecInit(0.U(8.W), 0.U(8.W)), io.en)val reg8 = ShiftRegister(VecInit(io.a, io.a), 3, io.en)reg2(0) := io.a.andRreg2(1) := io.a.andRreg3(0) := io.a.orRreg3(1) := io.a.orRwhen(reset.asBool) {reg4(0) := 0.Ureg4(1) := 0.U} .otherwise {reg4(0) := 1.Ureg4(1) := 1.U}io.c := reg0(0)(0) & reg1(0)(0) & reg2(0)(0) & reg3(0)(0) & reg4(0)(0) & reg5(0)(0) & reg6(0)(0) & reg7(0)(0) & reg8(0)(0) ®0(1)(0) & reg1(1)(0) & reg2(1)(0) & reg3(1)(0) & reg4(1)(0) & reg5(1)(0) & reg6(1)(0) & reg7(1)(0) & reg8(1)(0) }五、用when給線網或者寄存器進行條件賦值
在Verilog里,可以使用“if…else if…else”這樣的條件選擇語句來方便地構建電路的邏輯。由于Scala已經占用了“if…else if…else”語法,所以相應的Chisel控制結構改成了when語句,其語法如下:
when (condition 1) { definition 1 } .elsewhen (condition 2) { definition 2 } ... .elsewhen (condition N) { definition N } .otherwise { default behavior }注意,“.elsewhen”和“.otherwise”的開頭有兩個句點。所有的判斷條件都是返回Bool類型的傳名參數,不要和Scala的Boolean類型混淆,也不存在Boolean和Bool之間的相互轉換。對于UInt、SInt,甚至是Clock和Reset類型,可以用方法asBool轉換成Bool類型來作為判斷條件。
when語句不僅可以給線網賦值,還可以給寄存器賦值,但是要注意構建組合邏輯時不能缺失“.otherwise”分支。 下面是一個簡單的尋找三個數中的最大值的模塊:
class Max3 extends Module {val io = IO(new Bundle {val in1 = Input(UInt(16.W))val in2 = Input(UInt(16.W))val in3 = Input(UInt(16.W))val out = Output(UInt(16.W))})when(io.in1 >= io.in2 && io.in1 >= io.in3) {io.out := io.in1 }.elsewhen(io.in2 >= io.in3) {io.out := io.in2 }.otherwise {io.out := io.in3} }when有很多場合可以使用:
- 比如使用when給帶使能信號的寄存器更新數據;
- 還可用于狀態機的狀態轉換;
除了when結構,util包里還有一個與之對偶的結構“unless”,如果unless的判定條件為false.B則一直執行,否則不執行:
import chisel3.util._unless (condition) { definition }總結
以上是生活随笔為你收集整理的chisel线网(wire)和寄存器(reg)详解(更新)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: maxmemory-policy
- 下一篇: Admob的注意事项