✎ 编 者 按
在做逻辑实现及后期调试维护里,在维护文档中的寄存器列表和真实设计中的唯一性往往是一件令人“痛苦”的事情(一个字,懒)。在SpinalHDL的设计里,slaveFactory是寄存器实现的一个不错的方法(还有一个是RegIf库),虽然两者都能够打印寄存器列表,但终究是分散的。本篇以slaveFactory库为例看看如何一键生成整个设计的寄存器文档。
slaveFactory中的dataModel
slaveFactory中提供了printDataModel方法可以用于打印寄存器列表,如下面的例子所示:
在生成RTL时控制台会打印寄存器列表:
不妨看下printDataModel背后的逻辑:
def dataModelString(): String = {
val builder = new StringBuilder()
for ((address, tasks) <- elementsPerAddress.toList.sortBy(_._1.lowerBound)) {
builder ++= s"$address :\n"
for (task <- tasks) task match {
case task: BusSlaveFactoryRead => builder ++= s" R[${task.bitOffset + widthOf(task.that) - 1}:${task.bitOffset}] ${task.that.getName()} ${if(task.documentation != null) s"- ${task.documentation}" else ""} \n"
case task: BusSlaveFactoryWrite => builder ++= s" W[${task.bitOffset + widthOf(task.that) - 1}:${task.bitOffset}] ${task.that.getName()} ${if(task.documentation != null) s"- ${task.documentation}" else ""} \n"
case _ =>
}
}
builder.toString
}
def printDataModel(): Unit = print(dataModelString())
上面的代码来自BusSlaveFactory.scala文件,在slaveFactory中我们注册声明的寄存器列表均存储在 elementPerAddress(LinkedHashMap)中,而printDataModel中则是将elementPerAddress转换成字符串打印出来而已,那么基于elementPerAddress,我们可以一键生成寄存器文档,所要解决的只是如何将各分散的slaveFactory如何“集中”到一起。
隐式转换
Scala的隐式转换可以方便的对类进行方法扩展(关于隐式转换可以参考《Scala编程》),如下例所示:
object regFileTestApp extends App{
implicit class IntMth(data:Int){
def multi():Int={
data*2
}
}
val a=3
println(a.multi())
}
上面的代码里为Int隐式扩展了multi方法,执行程序将会打印6,借助隐式扩展功能,我们可以为BusSlaveFactoryDelayed扩展addDataModel方法用于注册每个slaveFactory的dataModel。
addPrePopTask
字如其名,在SpinalHDL中可以为每个模块添加addPrePopTask方法,在编译生成RTL时每当处理完成一个模块后就会执行改方法,如下所示:
基于该方法,我们可以在顶层注册genRegFileByMarkdown()方法用于生成Markdown形式的寄存器文档。
实现
贴出一个比较粗糙的实现逻辑:
import spinal.core._
import spinal.core._
import spinal.lib.bus.misc._
import java.io._
object regFileGen {
val regContent=new StringBuilder()
def genRegFileByMarkdown()={
val regFileHdl = new PrintWriter(new File("regFile.md"))
regFileHdl.println("|module name|base addr|offset|reg name|bit filed|attribute|description|")
regFileHdl.println("|---------|----------|----------|----------|----------|----------|----------|")
regFileHdl.println(regContent.toString())
regFileHdl.close()
}
implicit class regInsert(busFactory:BusSlaveFactoryDelayed) {
def addDataModel(moduleName:String,baseAddr:Long): Unit ={
var moduleFirst=true
for ((address, tasks) <- busFactory.elementsPerAddress.toList.sortBy(_._1.lowerBound)) {
for (task <- tasks) task match {
case task: BusSlaveFactoryRead =>{
if(moduleFirst){
regContent++=f"$moduleName|0x$baseAddr%x|0x${address.lowerBound}%x|${task.that.getName("not named")}|[${task.bitOffset + widthOf(task.that) - 1}:${task.bitOffset}]|R|${if(task.documentation != null) s" ${task.documentation}" else "no description"}|\n"
moduleFirst=false
}else{
regContent++=f"^|^|0x${address.lowerBound}%x|${task.that.getName("not named")}|[${task.bitOffset + widthOf(task.that) - 1}:${task.bitOffset}]|R|${if(task.documentation != null) s" ${task.documentation}" else "no description"}|\n"
}
}
case task: BusSlaveFactoryWrite =>{
if(moduleFirst){
regContent++=f"$moduleName|0x${address.lowerBound}%x|0x${address.lowerBound}%x|${task.that.getName("not named")}|[${task.bitOffset + widthOf(task.that) - 1}:${task.bitOffset}]|W|${if(task.documentation != null) s" ${task.documentation}" else "no description"}|\n"
moduleFirst=false
}else{
regContent++=f"^|^|0x${address.lowerBound}%x|${task.that.getName("not named")}|[${task.bitOffset + widthOf(task.that) - 1}:${task.bitOffset}]|W|${if(task.documentation != null) s" ${task.documentation}" else "no description"}|\n"
}
}
case _ =>
}
}
}
}
}
这里为BusSlaveFactoryDelayed隐式扩展了一个addDataModel方法,用于向regContent中注册寄存器列表,genRegFileByMarkdown()则用于生成Markdown文档(有关Markdown相关使用可以参照公众号中Markdown文章)。
example
使用方式如下:
import spinal.core._
import spinal.lib._
import spinal.lib.bus.amba4.axilite._
import regFileGen._
case class regFileTest() extends Component{
val io=new Bundle{
val alite0=slave (AxiLite4(32,32))
val alite1=slave (AxiLite4(32,32))
}
val regSeg0=regSeg("port0")
val regSeg1=regSeg("port1")
io.alite0<>regSeg0.io.alite
io.alite1<>regSeg1.io.alite
addPrePopTask(()=>genRegFileByMarkdown())
}
case class regSeg(moduleName:String) extends Component{
val io=new Bundle{
val alite=slave(AxiLite4(32,32))
}
val factory=new AxiLite4SlaveFactory(io.alite)
val cnt=UInt(8 bits) setAsReg()
val cnt1=UInt(8 bits) setAsReg()
factory.readAndWrite(cnt,0,0,"counter")
factory.readAndWrite(cnt1,4,0,"counter1")
factory.addDataModel(moduleName,0x0)
}
object regFileTestApp extends App{
SpinalSystemVerilog(regFileTest())
}
最终执行编译时会生成一个regFile.md文件,包含了各个模块的寄存器列表。效果如下:
☆ END ☆
作者:玉骐
原文链接:https://mp.weixin.qq.com/s/ctTDDiW73BhJQtjeT1CQ2A
微信公众号:
推荐阅读
更多SpinalHDL技术干货请关注Spinal FPGA专栏。