十二 · 2021年11月22日

如何设计一个“价值百万”的总线互联IP(六)

系列终章,在本篇,看SpinalHDL软硬件结合的极致。

做事High Level

在前面的五篇文章中,对于Axi4总线互联的所有设计细节都已经做了介绍。那么剩下的就是集成工作了。再来回顾整个总线互联架构:

图片.png

对于完整的IP来讲,我们如果想要做成一个高度参数化的IP,就需要解决几个问题:

只有高度的参数化才能为逻辑设计增色,而SpinalHDL在这方面的优势可谓得天独厚。Axi4CrossbarFactory的设计思路不可谓不精彩。Axi4CrossbarFactory真个设计可谓是纯软件逻辑,通过软件的灵活性我们可以轻松定制总线互联IP。    

addSlave

对于slave端口,我们要做的就是向Axi4CrossbarFactory注册每个slave端口并附带上其地址段分区。Axi4CrossbarFactory提供了两个方法用于注册slave端口:

//注册单个slave端口
def addSlave(axi: Axi4Bus,mapping: SizeMapping) : this.type
//批量注册slave端口
def addSlaves(orders : (Axi4Bus,SizeMapping)*) : this.type

这里核心是addSlave函数:

图片.png   在注册slave端口时倘若我们传入的是axi4总线,那么会将axi4总线转换成Axi4ReadOnly和Axi4WriteOnly总线,同时将映射信息存放至axi4SlaveToReadWriteOnly映射表中,而slave端口的地址映射信息将会存放至slaveConfigs映射表。    

addConnection

addConnection用途在于建立mster端口与slave端口之间的映射关系。Axi4CrossbarFactory提供了三种方法:

def addConnection(axi: Axi4Bus,slaves: Seq[Axi4Bus]) : this.type 
def addConnection(order: (Axi4Bus,Seq[Axi4Bus])) : this.type
def addConnections(orders : (Axi4Bus,Seq[Axi4Bus])*) : this.type

这里着重点在于第一个函数:

def addConnection(axi: Axi4Bus,slaves: Seq[Axi4Bus]) : this.type = {
    val translatedSlaves = slaves.map(_ match{
      case that : Axi4 => axi4SlaveToReadWriteOnly(that)
      case that : Axi4Bus => that :: Nil
    }).flatten
    axi match {
      case axi : Axi4 => {
        addConnection(axi.toReadOnly().setCompositeName(axi, "readOnly", true),translatedSlaves.filter(!_.isInstanceOf[Axi4WriteOnly]))
        addConnection(axi.toWriteOnly().setCompositeName(axi, "writeOnly", true),translatedSlaves.filter(!_.isInstanceOf[Axi4ReadOnly]))
      }
      case axi : Axi4WriteOnly => {
        translatedSlaves.filter(!_.isInstanceOf[Axi4ReadOnly]).foreach(slavesConfigs(_).connections += Axi4CrossbarSlaveConnection(axi))
        masters += axi
      }
      case axi : Axi4ReadOnly => {
        translatedSlaves.filter(!_.isInstanceOf[Axi4WriteOnly]).foreach(slavesConfigs(_).connections += Axi4CrossbarSlaveConnection(axi))
        masters += axi
      }
      case axi : Axi4Shared => {
        translatedSlaves.foreach(slavesConfigs(_).connections += Axi4CrossbarSlaveConnection(axi))
        masters += axi
      }
    }
    this
  }

对于每个待连接的slave端口,这里转换成列表存放于translatedSlaves中。之所以将slave端口转换成列表的形式,在于当我们传入的slave端口是axi4时,在addSlave函数中将其转换为了Axi4ReadOnly和Axi4WriteOnly总线,其映射关系以列表形式存放在了 axi4SlaveToReadWriteOnly中。随后根据端口的类型将master与slave端口之间的连接关系存放在slave端口的slaveConfigs映射的connections中。而master端口则注册存放在一个Arrayuffer中。

addPipeLine

Axi4CrossbarFactory中提供了下面几个方法用于添加pipeline:

def addPipelining(axi : Axi4Shared)(bridger : (Axi4Shared,Axi4Shared) => Unit): this.type
def addPipelining(axi : Axi4ReadOnly)(bridger : (Axi4ReadOnly,Axi4ReadOnly) => Unit): this.type
def addPipelining(axi : Axi4WriteOnly)(bridger : (Axi4WriteOnly,Axi4WriteOnly) => Unit): this.type
def addPipelining(axi : Axi4)(ro : (Axi4ReadOnly,Axi4ReadOnly) => Unit)(wo : (Axi4WriteOnly,Axi4WriteOnly) => Unit): this.type

这里最后一个函数值得注意:

def addPipelining(axi : Axi4)(ro : (Axi4ReadOnly,Axi4ReadOnly) => Unit)(wo : (Axi4WriteOnly,Axi4WriteOnly) => Unit): this.type ={
    val b = axi4SlaveToReadWriteOnly(axi)
    val rAxi = b(0).asInstanceOf[Axi4ReadOnly]
    val wAxi = b(1).asInstanceOf[Axi4WriteOnly]
    addPipelining(rAxi)(ro)
    addPipelining(wAxi)(wo)
    this
  }

当传入的参数是Axi4总线时,其会查询axi4SlaveToReadWriteOnly找到映射的ReadOnly总线与WriteOnly总线,那这里就限制了其只能用在slave端口而不能用在master端口(axi4SlaveToReadWriteOnly注册是发生在addSlave端口中)。

这几个addPipelining方法只能用来添加decoder入口和arbiter出口的总线,而对于decoder出口和arbiter入口之间的拓扑互联则无能为力。

build

有了前面的注册,那么最后一步就是建立整个IP的生成和拓扑互联了。这里以ReadOnly的处理为例:

对于decoder部分:

图片.png

对于每个master端口,首先遍历所有的slave端口查询该master可访问的端口并保存在列表中,随后根据slaves列表信息声称该master端口对应的Axi4ReadOnlyDecoder IP,并将output端口与slaves端口建立一一映射关系存放在masterToDecodedSlave映射中。值得注意的是按照IP的默认参数在建立映射是会在aw,ar通路上插入一级pipeline,而w,b,r通路则没有,也就意味着在decoder和arbiter之间这些通路是直连的。随后若用户在master端口上若有调用addpipelining则会插入相应用户指定的逻辑。

对于arbiter部分:

图片.png    arbiter在处理上只有访问该slave端口的个数大于2时才会例化Axi4ReadOnlyArbiter,所有Arbiter输入端口通过前面的masterToDecodedSlave映射表来获取拓扑连接关系进行连接。随后若用户在slave端口上若有调用addpipelining则会插入相应用户指定的逻辑。

至此,完成了整个IP的例化。

example

这里给出一个example。假定IP有两个master端口和两个slave端口,每个slave端口占1G空间:

图片.png

写在最后

通过该系列,完整解析了通过SpinalHDL来实现Axi4总线互联的设计思路及代码技巧。整个代码设计实现不过百行,而其中所穿插的设计思路是值得所学习和思考的,目前也就只能基于SpinalHDL这类设计语言才能够达到如此精炼与高效。尤其是本节所述,其更是充分体现了软件定义硬件的高度参数化设计,读懂此系列,那么SpinalHDL你就可以很轻松的玩转了。  

☆ END ☆

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

推荐阅读

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