十二 · 2021年11月15日

一键生成寄存器文档也容易

✎ 编 者 按 
 在做逻辑实现及后期调试维护里,在维护文档中的寄存器列表和真实设计中的唯一性往往是一件令人“痛苦”的事情(一个字,懒)。在SpinalHDL的设计里,slaveFactory是寄存器实现的一个不错的方法(还有一个是RegIf库),虽然两者都能够打印寄存器列表,但终究是分散的。本篇以slaveFactory库为例看看如何一键生成整个设计的寄存器文档。

slaveFactory中的dataModel

slaveFactory中提供了printDataModel方法可以用于打印寄存器列表,如下面的例子所示:

36ddce83162cf86fd2b700628acc1609.png

在生成RTL时控制台会打印寄存器列表:

b79f8a3a86da8061268d8aa4dee07465.png

不妨看下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时每当处理完成一个模块后就会执行改方法,如下所示:

d87b0a462fe9eb85277f574f088521ce.png

基于该方法,我们可以在顶层注册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文件,包含了各个模块的寄存器列表。效果如下:

a641122df7279dfd00b6e2f09eff97f8.png

☆ END ☆

作者:玉骐
原文链接:https://mp.weixin.qq.com/s/ctTDDiW73BhJQtjeT1CQ2A
微信公众号:
 title=

推荐阅读

更多SpinalHDL技术干货请关注Spinal FPGA专栏。
推荐阅读
关注数
1581
内容数
133
用SpinalHDL提升生产力
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息