RegIf

寄存器接口搭建器

  • 自动寻址、字段分配和冲突检测

  • 28种寄存器访问类型(涵盖UVM标准定义的25种类型)

  • 自动生成文档

自动分配

自动地址分配

class RegBankExample extends Component {
  val io = new Bundle {
    apb = slave(Apb3(Apb3Config(16,32)))
  }
  val busif = Apb3BusInterface(io.apb,(0x0000, 100 Byte))
  val M_REG0  = busif.newReg(doc="REG0")
  val M_REG1  = busif.newReg(doc="REG1")
  val M_REG2  = busif.newReg(doc="REG2")

  val M_REGn  = busif.newRegAt(address=0x40, doc="REGn")
  val M_REGn1 = busif.newReg(doc="REGn1")

  busif.accept(HtmlGenerator("regif", "AP"))
  // busif.accept(CHeaderGenerator("header", "AP"))
  // busif.accept(JsonGenerator("regif"))
  // busif.accept(RalfGenerator("regbank"))
  // busif.accept(SystemRdlGenerator("regif", "AP"))
}
../../_images/reg-auto-allocate.gif

Automatic filed allocation

val M_REG0  = busif.newReg(doc="REG1")
val fd0 = M_REG0.field(Bits(2 bit), RW, doc= "fields 0")
M_REG0.reserved(5 bits)
val fd1 = M_REG0.field(Bits(3 bit), RW, doc= "fields 0")
val fd2 = M_REG0.field(Bits(3 bit), RW, doc= "fields 0")
// auto reserved 2 bits
val fd3 = M_REG0.fieldAt(pos=16, Bits(4 bit), doc= "fields 3")
// auto reserved 12 bits
../../_images/field-auto-allocate.gif

冲突检测

val M_REG1  = busif.newReg(doc="REG1")
val r1fd0 = M_REG1.field(Bits(16 bits), RW, doc="fields 1")
val r1fd2 = M_REG1.field(Bits(18 bits), RW, doc="fields 1")
  ...
cause Exception
val M_REG1  = busif.newReg(doc="REG1")
val r1fd0 = M_REG1.field(Bits(16 bits), RW, doc="fields 1")
val r1fd2 = M_REG1.fieldAt(pos=10, Bits(2 bits), RW, doc="fields 1")
  ...
cause Exception

28种访问类型

其中大部分来自UVM规范

访问类型

描述

来源

RO

w:无影响,r:无影响

UVM

RW

w:保持原样,r:无影响

UVM

RC

w:无影响,r:清除所有比特

UVM

RS

w:无影响,r:置位所有比特

UVM

WRC

w:保持原样,r:清除所有比特

UVM

WRS

w:保持原样,r:置位所有比特

UVM

WC

w:清除所有比特,r:无影响

UVM

WS

w:置位所有比特,r:无影响

UVM

WSRC

w:置位所有比特,r:清除所有比特

UVM

WCRS

w:清除所有比特,r:置位所有比特

UVM

W1C

w:1/0 对匹配位清除/无影响,r:无影响

UVM

W1S

w:1/0 对匹配位置位/无影响,r:无影响

UVM

W1T

w:1/0 对匹配位翻转/无影响,r:无影响

UVM

W0C

w:1/0 对匹配位清除/无影响,r:无影响

UVM

W0S

w:1/0 对匹配位置位/无影响,r:无影响

UVM

W0T

w:1/0 对匹配位翻转/无影响,r:无影响

UVM

W1SRC

w:1/0 对匹配位置位/无影响,r:清除所有比特

UVM

W1CRS

w:1/0 对匹配位清除/无影响,r:置位所有比特

UVM

W0SRC

w:1/0 对匹配位置位/无影响,r:清除所有比特

UVM

W0CRS

w:1/0 对匹配位清除/无影响,r:置位所有比特

UVM

WO

w:保持原样,r:错误

UVM

WOC

w:清除所有比特,r:错误

UVM

WOS

w:置位所有比特,r:错误

UVM

W1

w:硬复位后第一个w保持原样,其他w无影响,r:无影响

UVM

WO1

w:硬复位后第一个w保持原样,其他w无影响,r:错误

UVM

NA

w:保留,r:保留

新的

W1P

w:1/0 对匹配位脉冲(pulse)/无影响,r:无影响

新的

W0P

w:0/1 对匹配位脉冲(pulse)/无影响,r:无影响

新的

HSRW

w:硬件置位,软件RW

新的

RWHS

w:软件RW、硬件置位

新的

ROV

w:只读值,用于硬件版本

新的

CSTM

w:用户自定义类型,用于文档

新的

自动生成文档

文档类型

文档类型

用法

状态

HTML

busif.accept(HtmlGenerator("regif", title = "XXX register file"))

Y

CHeader

busif.accept(CHeaderGenerator("header", "AP"))

Y

JSON

busif.accept(JsonGenerator("regif"))

Y

RALF(UVM)

busif.accept(RalfGenerator("header"))

Y

SystemRDL

busif.accept(SystemRdlGenerator("regif", "addrmap_name", Some("name"), Some("desc")))

Y

Latex(pdf)

N

docx

N

HTM 自动文档现已完成,源代码示例:

生成的 HTML 文档:

../../_images/regif-html.png

特殊访问用途

案例1: RO 用法

RO 与其他类型不同。它不创建寄存器,需要外部信号来驱动它,注意,请不要忘记驱动它。

val io = new Bundle {
  val cnt = in UInt(8 bit)
}

val counter = M_REG0.field(UInt(8 bit), RO, 0, "counter")
counter :=  io.cnt
val xxstate = M_REG0.field(UInt(8 bit), RO, 0, "xx-ctrl state").asInput
val overflow = M_REG0.field(Bits(32 bit), RO, 0, "xx-ip parameter")
val ovfreg = Reg(32 bit)
overflow := ovfreg
val inc    = in Bool()
val counter = M_REG0.field(UInt(8 bit), RO, 0, "counter")
val cnt = Counter(100,  inc)
counter := cnt

案例2: ROV 用法

ASIC设计常常需要一些固化的版本信息。与 RO 不同,它不会产生有线信号

旧方法:

val version = M_REG0.field(Bits(32 bit), RO, 0, "xx-device version")
version := BigInt("F000A801", 16)

新方法:

M_REG0.field(Bits(32 bit), ROV, BigInt("F000A801", 16), "xx-device version")(Symbol("Version"))

案例3: HSRW/RWHS 在某些情况下的硬件设置类型,此类寄存器不仅可以由软件配置,还可以由硬件信号设置

val io = new Bundle {
  val xxx_set = in Bool()
  val xxx_set_val = in Bits(32 bit)
}

val reg0 = M_REG0.fieldHSRW(io.xxx_set, io.xxx_set_val, 0, "xx-device version")  // 0x0000
val reg1 = M_REG1.fieldRWHS(io.xxx_set, io.xxx_set_val, 0, "xx-device version")  // 0x0004
always @(posedge clk or negedge rstn)
  if(!rstn) begin
     reg0  <= '0;
     reg0  <= '0;
  end else begin
     if(hit_0x0000) begin
        reg0 <= wdata ;
     end
     if(io.xxx_set) begin      // HW have High priority than SW
        reg0 <= io.xxx_set_val ;
     end

     if(io.xxx_set) begin
        reg1 <= io.xxx_set_val ;
     end
     if(hit_0x0004) begin      // SW have High priority than HW
        reg1 <= wdata ;
     end
  end

案例4: CSTM 虽然SpinalHDL包含25种寄存器类型和6种扩展类型,但在实际应用中仍然对私有寄存器类型有各种需求。因此,我们保留CSTM类型以实现可扩展性。 CSTM仅用于生成软件接口,不生成实际电路

val reg = Reg(Bits(16 bit)) init 0
REG.registerAtOnlyReadLogic(0, reg, CSTM("BMRW"), resetValue = 0, "custom field")

when(busif.dowrite) {
   reg :=  reg & ~busif.writeData(31 downto 16) |  busif.writeData(15 downto 0) & busif.writeData(31 downto 16)
}

案例5: parasiteField

这用于软件在多个地址上共享同一寄存器,而不是生成多个寄存器实体

示例1:时钟门软件使能

val M_CG_ENS_SET = busif.newReg(doc="Clock Gate Enables")  // x00000
val M_CG_ENS_CLR = busif.newReg(doc="Clock Gate Enables")  // 0x0004
val M_CG_ENS_RO  = busif.newReg(doc="Clock Gate Enables")  // 0x0008

val xx_sys_cg_en = M_CG_ENS_SET.field(Bits(4 bit), W1S, 0, "clock gate enables, write 1 set" )
                   M_CG_ENS_CLR.parasiteField(xx_sys_cg_en, W1C, 0, "clock gate enables, write 1 clear" )
                   M_CG_ENS_RO.parasiteField(xx_sys_cg_en, RO, 0, "clock gate enables, read only"

example2: interrupt raw reg with force interface for software

val RAW    = this.newRegAt(offset,"Interrupt Raw status Register\n set when event \n clear raw when write 1")
val FORCE  = this.newReg("Interrupt Force  Register\n for SW debug use \n write 1 set raw")

val raw    = RAW.field(Bool(), AccessType.W1C,    resetValue = 0, doc = s"raw, default 0" )
             FORCE.parasiteField(raw, AccessType.W1S,   resetValue = 0, doc = s"force, write 1 set, debug use" )

案例6: SpinalEnum

当字段类型为 SpinalEnum 时,resetValue 指定枚举元素的索引。

object UartCtrlTxState extends SpinalEnum(defaultEncoding = binaryOneHot) {
   val sIdle, sStart, sData, sParity, sStop = newElement()
}

val raw = M_REG2.field(UartCtrlTxState(), AccessType.RW, resetValue = 2, doc="state")
// raw will be init to sData

字节掩码

withStrb

典型例子

批量创建REG-Address和字段寄存器

import spinal.lib.bus.regif._

class RegBank extends Component {
  val io = new Bundle {
    val apb = slave(Apb3(Apb3Config(16, 32)))
    val stats = in Vec(Bits(16 bit), 10)
    val IQ  = out Vec(Bits(16 bit), 10)
  }
  val busif = Apb3BusInterface(io.apb, (0x000, 100 Byte), regPre = "AP")

  (0 to 9).map { i =>
    // here use setName give REG uniq name for Docs usage
    val REG = busif.newReg(doc = s"Register${i}").setName(s"REG${i}")
    val real = REG.field(SInt(8 bit), AccessType.RW, 0, "Complex real")
    val imag = REG.field(SInt(8 bit), AccessType.RW, 0, "Complex imag")
    val stat = REG.field(Bits(16 bit), AccessType.RO, 0, "Accelerator status")
    io.IQ(i)( 7 downto 0) := real.asBits
    io.IQ(i)(15 downto 8) := imag.asBits
    stat := io.stats(i)
  }

  def genDocs() = {
    busif.accept(CHeaderGenerator("regbank", "AP"))
    busif.accept(HtmlGenerator("regbank", "Interupt Example"))
    busif.accept(JsonGenerator("regbank"))
    busif.accept(RalfGenerator("regbank"))
    busif.accept(SystemRdlGenerator("regbank", "AP"))
  }

  this.genDocs()
}

SpinalVerilog(new RegBank())

中断生成器

手动写中断

class cpInterruptExample extends Component {
   val io = new Bundle {
     val tx_done, rx_done, frame_end = in Bool()
     val interrupt = out Bool()
     val apb = slave(Apb3(Apb3Config(16, 32)))
   }
   val busif = Apb3BusInterface(io.apb, (0x000, 100 Byte), regPre = "AP")
   val M_CP_INT_RAW   = busif.newReg(doc="cp int raw register")
   val tx_int_raw      = M_CP_INT_RAW.field(Bool(), W1C, doc="tx interrupt enable register")
   val rx_int_raw      = M_CP_INT_RAW.field(Bool(), W1C, doc="rx interrupt enable register")
   val frame_int_raw   = M_CP_INT_RAW.field(Bool(), W1C, doc="frame interrupt enable register")

   val M_CP_INT_FORCE = busif.newReg(doc="cp int force register\n for debug use")
   val tx_int_force     = M_CP_INT_FORCE.field(Bool(), RW, doc="tx interrupt enable register")
   val rx_int_force     = M_CP_INT_FORCE.field(Bool(), RW, doc="rx interrupt enable register")
   val frame_int_force  = M_CP_INT_FORCE.field(Bool(), RW, doc="frame interrupt enable register")

   val M_CP_INT_MASK    = busif.newReg(doc="cp int mask register")
   val tx_int_mask      = M_CP_INT_MASK.field(Bool(), RW, doc="tx interrupt mask register")
   val rx_int_mask      = M_CP_INT_MASK.field(Bool(), RW, doc="rx interrupt mask register")
   val frame_int_mask   = M_CP_INT_MASK.field(Bool(), RW, doc="frame interrupt mask register")

   val M_CP_INT_STATUS   = busif.newReg(doc="cp int state register")
   val tx_int_status      = M_CP_INT_STATUS.field(Bool(), RO, doc="tx interrupt state register")
   val rx_int_status      = M_CP_INT_STATUS.field(Bool(), RO, doc="rx interrupt state register")
   val frame_int_status   = M_CP_INT_STATUS.field(Bool(), RO, doc="frame interrupt state register")

   rx_int_raw.setWhen(io.rx_done)
   tx_int_raw.setWhen(io.tx_done)
   frame_int_raw.setWhen(io.frame_end)

   rx_int_status := (rx_int_raw || rx_int_force) && (!rx_int_mask)
   tx_int_status := (tx_int_raw || rx_int_force) && (!rx_int_mask)
   frame_int_status := (frame_int_raw || frame_int_force) && (!frame_int_mask)

   io.interrupt := rx_int_status || tx_int_status || frame_int_status

}

这是一项非常繁琐且重复的工作,更好的方法是使用“生成器(factory)”范例来自动生成每个信号的文档。

现在InterruptFactory可以做到这一点。

创建中断的简单方法:

class EasyInterrupt extends Component {
  val io = new Bundle {
    val apb = slave(Apb3(Apb3Config(16,32)))
    val a, b, c, d, e = in Bool()
  }

  val busif = BusInterface(io.apb,(0x000,1 KiB), 0, regPre = "AP")

  busif.interruptFactory("T", io.a, io.b, io.c, io.d, io.e)

  busif.accept(CHeaderGenerator("intrreg","AP"))
  busif.accept(HtmlGenerator("intrreg", "Interupt Example"))
  busif.accept(JsonGenerator("intrreg"))
  busif.accept(RalfGenerator("intrreg"))
  busif.accept(SystemRdlGenerator("intrreg", "AP"))
}
../../_images/easy-intr.png

IP级中断生成器

寄存器

访问类型

描述

RAW

W1C

中断原始状态(int raw)寄存器,由int事件设置,总线写1时清零

FORCE

RW

中断强制寄存器,用于软件调试

MASK

RW

int mask register, 1: off; 0: open; default 1 int off

STATUS

RO

中断状态,只读, status = raw && ! mask

../../_images/RFMS.svg

Spinal用法:

busif.interruptFactory("T", io.a, io.b, io.c, io.d, io.e)

SYS级中断合并

寄存器

访问类型

描述

MASK

RW

int mask register, 1: off; 0: open; default 1 int off

STATUS

RO

中断状态,RO, status = int_level && ! mask

../../_images/MS.svg

Spinal用法:

busif.interruptLevelFactory("T", sys_int0, sys_int1)

Spinal的生成器

总线接口方法

描述

InterruptFactory(regNamePre: String, triggers: Bool*)

为脉冲事件创建RAW/FORCE/MASK/STATUS

InterruptFactoryNoForce(regNamePre: String, triggers: Bool*)

为脉冲事件创建RAW/MASK/STATUS

InterruptLevelFactory(regNamePre: String, triggers: Bool*)

为level_int合并创建MASK/STATUS

InterruptFactoryAt(addrOffset: Int, regNamePre: String, triggers: Bool*)

在addrOffset处为脉冲事件创建RAW/FORCE/MASK/STATUS

InterruptFactoryNoForceAt(addrOffset: Int, regNamePre: String, triggers: Bool*)

在addrOffset处为脉冲事件创建 RAW/MASK/STATUS

InterruptFactoryAt(addrOffset: Int, regNamePre: String, triggers: Bool*)

在addrOffset处为level_int合并创建MASK/STATUS

interrupt_W1SCmask_FactoryAt(addrOffset: BigInt, regNamePre: String, triggers: Bool*)

create RAW/FORCE/MASK(SET/CLR)/STATUS for pulse event at addrOffset

interruptLevel_W1SCmask_FactoryAt(addrOffset: BigInt, regNamePre: String, levels: Bool*)

create RAW/FORCE/MASK(SET/CLR)/STATUS for level event at addrOffset

示例

class RegFileIntrExample extends Component {
   val io = new Bundle {
     val apb = slave(Apb3(Apb3Config(16,32)))
     val int_pulse0, int_pulse1, int_pulse2, int_pulse3 = in Bool()
     val int_level0, int_level1, int_level2 = in Bool()
     val sys_int = out Bool()
     val gpio_int = out Bool()
   }

   val busif = BusInterface(io.apb,  (0x000,1 KiB), 0, regPre = "AP")
   io.sys_int  := busif.interruptFactory("SYS",io.int_pulse0, io.int_pulse1, io.int_pulse2, io.int_pulse3)
   io.gpio_int := busif.interruptLevelFactory("GPIO",io.int_level0, io.int_level1, io.int_level2, io.sys_int)

   def genDoc() = {
     busif.accept(CHeaderGenerator("intrreg","Intr"))
     busif.accept(HtmlGenerator("intrreg", "Interrupt Example"))
     busif.accept(JsonGenerator("intrreg"))
     busif.accept(RalfGenerator("intrreg"))
     busif.accept(SystemRdlGenerator("intrreg", "Intr"))
     this
   }

   this.genDoc()
 }
../../_images/intc.jpeg

默认读取值

当软件读取保留地址时,当前的策略是正常返回,readerror=0。为了方便软件调试,可以配置回读值,默认为0

busif.setReservedAddressReadValue(0x0000EF00)
default: begin
   busif_rdata  <= 32'h0000EF00 ;
   busif_rderr  <= 1'b0         ;
end

开发者区域

You can add your document Type by extending the BusIfVisitor Trait

case class Latex(fileName : String) extends BusIfVisitor{ ... }

BusIfVisitor give access BusIf.RegInsts to do what you want

// lib/src/main/scala/spinal/lib/bus/regif/BusIfBase.scala

trait BusIfVisitor {
  def begin(busDataWidth : Int) : Unit
  def visit(descr : FifoDescr)  : Unit
  def visit(descr : RegDescr)   : Unit
  def end()                     : Unit
}