RegIf
Register Interface Builder
Automatic address, fields allocation and conflict detection
28 Register Access types (Covering the 25 types defined by the UVM standard)
Automatic documentation generation
Automatic allocation
Automatic address allocation
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"))
}
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
conflict detection
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 Access Types
Most of these come from UVM specification
AccessType |
Description |
From |
---|---|---|
RO |
w: no effect, r: no effect |
UVM |
RW |
w: as-is, r: no effect |
UVM |
RC |
w: no effect, r: clears all bits |
UVM |
RS |
w: no effect, r: sets all bits |
UVM |
WRC |
w: as-is, r: clears all bits |
UVM |
WRS |
w: as-is, r: sets all bits |
UVM |
WC |
w: clears all bits, r: no effect |
UVM |
WS |
w: sets all bits, r: no effect |
UVM |
WSRC |
w: sets all bits, r: clears all bits |
UVM |
WCRS |
w: clears all bits, r: sets all bits |
UVM |
W1C |
w: 1/0 clears/no effect on matching bit, r: no effect |
UVM |
W1S |
w: 1/0 sets/no effect on matching bit, r: no effect |
UVM |
W1T |
w: 1/0 toggles/no effect on matching bit, r: no effect |
UVM |
W0C |
w: 1/0 no effect on/clears matching bit, r: no effect |
UVM |
W0S |
w: 1/0 no effect on/sets matching bit, r: no effect |
UVM |
W0T |
w: 1/0 no effect on/toggles matching bit, r: no effect |
UVM |
W1SRC |
w: 1/0 sets/no effect on matching bit, r: clears all bits |
UVM |
W1CRS |
w: 1/0 clears/no effect on matching bit, r: sets all bits |
UVM |
W0SRC |
w: 1/0 no effect on/sets matching bit, r: clears all bits |
UVM |
W0CRS |
w: 1/0 no effect on/clears matching bit, r: sets all bits |
UVM |
WO |
w: as-is, r: error |
UVM |
WOC |
w: clears all bits, r: error |
UVM |
WOS |
w: sets all bits, r: error |
UVM |
W1 |
w: first one after hard reset is as-is, other w have no effects, r: no effect |
UVM |
WO1 |
w: first one after hard reset is as-is, other w have no effects, r: error |
UVM |
NA |
w: reserved, r: reserved |
New |
W1P |
w: 1/0 pulse/no effect on matching bit, r: no effect |
New |
W0P |
w: 0/1 pulse/no effect on matching bit, r: no effect |
New |
HSRW |
w: Hardware Set, SoftWare RW |
New |
RWHS |
w: SoftWare RW, Hardware Set |
New |
ROV |
w: ReadOnly Value, used for hardware version |
New |
CSTM |
w: user custom Type, used for document |
New |
Automatic documentation generation
Document Type
Document |
Usage |
Status |
---|---|---|
HTML |
|
Y |
CHeader |
|
Y |
JSON |
|
Y |
RALF(UVM) |
|
Y |
SystemRDL |
|
Y |
Latex(pdf) |
N |
|
docx |
N |
HTML auto-doc is now complete, Example source Code:
generated HTML document:
Special Access Usage
CASE1: RO
usage
RO
is different from other types. It does not create registers and requires an external signal to drive it,
Attention, please don’t forget to drive it.
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
CASE2: ROV
usage
ASIC design often requires some solidified version information. Unlike RO, it is not expected to generate wire signals
old way:
val version = M_REG0.field(Bits(32 bit), RO, 0, "xx-device version")
version := BigInt("F000A801", 16)
new way:
M_REG0.field(Bits(32 bit), ROV, BigInt("F000A801", 16), "xx-device version")(Symbol("Version"))
CASE3: HSRW/RWHS
hardware set type
In some cases, such registers are not only configured by software, but also set by hardware signals
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
CASE4: CSTM
Although SpinalHDL includes 25 register types and 6 extension types,
there are still various demands for private register types in practical application.
Therefore, we reserve CSTM types for scalability.
CSTM is only used to generate software interfaces, and does not generate actual circuits
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)
}
CASE5: parasiteField
This is used for software to share the same register on multiple address instead of generating multiple register entities
example1: clock gate software enable
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" )
CASE6: SpinalEnum
When the field type is SpinalEnum, the resetValue specifies the index of the enum elements.
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
Byte Mask
withStrb
Typical Example
Batch create REG-Address and fields register
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())
Interrupt Factory
Manual writing interruption
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
}
this is a very tedious and repetitive work, a better way is to use the “factory” paradigm to auto-generate the documentation for each signal.
now the InterruptFactory can do that.
Easy Way create interruption:
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"))
}
IP level interrupt Factory
Register |
AccessType |
Description |
---|---|---|
RAW |
W1C |
int raw register, set by int event, clear when bus write 1 |
FORCE |
RW |
int force register, for SW debug use |
MASK |
RW |
int mask register, 1: off; 0: open; default 1 int off |
STATUS |
RO |
int status, Read Only, |
SpinalUsage:
busif.interruptFactory("T", io.a, io.b, io.c, io.d, io.e)
SYS level interrupt merge
Register |
AccessType |
Description |
---|---|---|
MASK |
RW |
int mask register, 1: off; 0: open; default 1 int off |
STATUS |
RO |
int status, RO, |
SpinalUsage:
busif.interruptLevelFactory("T", sys_int0, sys_int1)
Spinal Factory
BusInterface method |
Description |
---|---|
|
create RAW/FORCE/MASK/STATUS for pulse event |
|
create RAW/MASK/STATUS for pulse event |
|
create MASK/STATUS for level_int merge |
|
create RAW/FORCE/MASK/STATUS for pulse event at addrOffset |
|
create RAW/MASK/STATUS for pulse event at addrOffset |
|
create MASK/STATUS for level_int merge at addrOffset |
|
create RAW/FORCE/MASK(SET/CLR)/STATUS for pulse event at addrOffset |
|
create RAW/FORCE/MASK(SET/CLR)/STATUS for level event at addrOffset |
Example
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()
}
DefaultReadValue
When the software reads a reserved address, the current policy is to return normally, readerror=0. In order to facilitate software debugging, the read back value can be configured, which is 0 by default
busif.setReservedAddressReadValue(0x0000EF00)
default: begin
busif_rdata <= 32'h0000EF00 ;
busif_rderr <= 1'b0 ;
end
Developers Area
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
}