SpinalHDL basic

模块例化次数动态变化:
val cellArray = Array.fill(num)(new AdderCell)
Array和Seq都可以创建Vec,而Vec调用read方法可以避免RTL中case的啰嗦

引用环境变量:
addRTLPath(s"${sys.env("MY_PROJECT")}/myTopLevel.vhd")

数组相关概念:
wire [1:0][7:0] data0; //合并数组
wire [7:0] data1[1:0]; //非合并数组
合并数组则可以作为一个整体进行赋值,而非合并数组则不可以
流操作符进行相互转换,assign data1 = {>>{data2}};


相关函数:
Math.max()   widthOf()   isPow2()   log2Up()   getBitsWidth
whenIndexed(io.dataIn, io.sel)(io.dataOut:=io.dataIn)
(dut.io.dataIn, 1 to 8).zipped.foreach(_ #= _)
io.outPort:=Delay(io.inPort,3)
def initFrom(data0:Int,data1:Int):this.type ={
  this.data0:=data0
  this.data1:=data1
  this
}
io.outPort:=Delay(io.inPort,3,init = dataBus().initFrom(1,2))
printin(s"address=$address\t data=${getBigInt(dut.memInst,address)})

RTL生成时的复位配置:
SpinalConfig(
    defaultConfigForClockDomains = ClockDomainConfig(resetActiveLevel = HIGH, resetKind = SYNC)
).generateSystemVerilog(new addSpinal(8))

例化BlackBox时,包含parameter:
最前面添加,addGeneric("dataWidth",dataWidht)

monitor,添加检测模块:
assignAllByName,io.alite.assignAllByName(io.inPort)

重复多次赋值:
b:=B(a,3) // 不是B(a,3 bits)

生成名称管理:
避免io_前缀,添加noIoPrefix();   setDefinitionName();   Area不给名称,val _= Area {}.setName("")
在生成Verilog代码时,会为每个模块单独生成一个文件,SpinalConfig(oneFilePerComponent = true)
所有模块均有前缀名字"demo",SpinalConfig(globalPrefix = "demo")
setDefinitionName的优先级是高于globalPrefix的,我们在例化BlackBox时通过setDefinitionName指定模块的名字来避免在添加globalPrefix时带来的问题便可
bus.flatten.foreach((bt) => {
bt.setName(bt.getName( ).replace("payload",“”))if(bt.getName().startswith("io_")) bt.setName(bt.getName().replaceFirst("io",“”))
})

map(用于集合转换)示例:
subdivideIn(cfg.bitsPerGroup bits).map(_.xorR).asBits().asUInt
每组bit求和,subdivideIn(cfg.bitsPerGroup bits).map(~_).map(RegNext(_))).reduce(_+_)
加法溢出时做饱和处理,subdivideIn(cfg.bitsPerGroup bits).map(~_).map(RegNext(_))).reduce(_ +| _)
val sboxLut=Array(0xB8...).map(U(_,8 bits))   io.dataOut:=Vec(sboxLut).read(io.dataIn)

参数类示意:
case class myconfig(addr_width: Int, data_width: Int)
case class config(bitsPerGroup:Int, dataWidth:Int) {
  require(dataWidth%bitsPerGroup==0,"Invalid parameter")
  val sigOutWidth=dataWidth/bitsPerGroup
}
函数参数可以这样,input: Vec[Stream[UInt]]

interface相关定义:
case class myinterface extends Bundle with IMasterSlave {
    val data1 = Bool
    val data2 = Bool
    override def asMaster() = this.asOutput()
    override def asMaster() = {
      out(data1)
      in(data2)
    }
}
Bundle中可以用function定义逻辑,调用function时即逻辑例化
方向获得getDirection

bus slave factory使用:
read(that,address,bitOffset),write(that,address,bitOffset),readAndWrite(that,address,bitOffset),readMultiWord(that,address),writeMultiWord(that,address)
printDataModel函数用于打印寄存器列表,而且在生成RTL时,其会自动检测寄存器地址是否存在冲突

电路添加属性标签:
val mem = Mem(UInt(8bits), 256)
mem.addAttribute(name = "ram_style", value = "uram")
生成结果——(* ram_style = "uram" *) reg [7:0] mem [0:255];

上升沿或下降沿检测:
val incEnable=if(detectRise) io.sigIn.rise(False) else io.sigIn.fall(False)

switch for循环:
switch(io.dataIn) {
  for(index<-0 until sboxLut.length) {
    is(index) {
      io.dataOut:=sboxLut(index)
    }
  }
}

连线的最高阶,与类名同名的伴生对象结合apply方法:
object add2 {
  def apply(dataWidth: Int, port: sumPort) : Unit = {
    val addInst=new add2(dataWidth)
    addInst.io.sumport<>port
  }
}

随机的操作:
import scala.util.Random._

断言:
assert((data1+data2)==port.sum.payload.toInt,"data Mismathc")
更多消息打印,Seq("data0:",io.data0,"\tdata1:",io.data1)

测试框架:
基于Scala的测试框架,可以生成html的测试报告

x(offset, width)方法,width必须是一个固定值:
io.dataOut:=io.dataIn(io.sel,8 bits)
assign io_dataOut = io_dataIn[io_sel +: 8];

逻辑树的高效实现:
io.sum:=io.dataIn.reduceBalancedTree(_+_,(s,l)=>RegNext(s))
io.sum:=io.dataIn.reduceBalancedTree(_+_,(s,l)=>{if(l%2==0) s else RegNext(s)})

跨时钟处理:
BufferCC(input: T, init: T = null, bufferDepth: Int = 2),init必须为“硬件”数据类型,可以是U(0,8bits),不可以是0
GrayCounter(width: Int, enable: Bool)   fromGray()   toGray()
PulseCCByToggle(input: Bool,clockIn: ClockDomain,clockOut: ClockDomain): Bool
StreamCCByToggle(input: Stream[T], inputClock: ClockDomain, outputClock: ClockDomain): Stream[T]
FlowCCByToggle   StreamFifoCC

Stream相关处理:
insertHeader()   translateWith()
type Event = Stream[NoData]
SpinalHDL将功能实现和时序改善分开来做是一个非常好的实现方式,StreamFork中所有的端口的输出其实都是组合逻辑(AXILite可以用两个StreamFork配合)
StreamFork与StreamArbiterFactory呼应
arbitrationFrom,从Stream中提取Event,cmdEvent.arbitrationFrom(io.cmd),从Event中提取信号,io.alite4WriteOnly.aw.arbitrationFrom(awEvent)
translateWith使用(替换payload),val rdataStream = memInst.streamReadSync(io.axi4lite.ar.translateWith(io.axi4lite.ar.addr))
streamReadSync(cmd: Stream[UInt]): Stream[T]

stateMachine相关处理:
避免状态机名称加一些前缀,SpinalConfig(enumPrefixEnable = false)
屏蔽字符串相关逻辑,SpinalConfig().withoutEnumString()

插入额外操作:
在SpinalHDL中可以为每个模块添加addPrePopTask方法,在编译生成RTL时每当处理完成一个模块后就会执行该方法,addPrePopTask(()=>genRegFileByMarkdown())
获取状态机状态也需要这个函数,addPrePopTask(()=>io.currentState.assignFromBits(fsm.stateReg.asBits))

隐式转换:
很方便为已有的类扩展方法
implicit class IntMth(data:Int){
  def multi():Int={
    data*2
  }
}
val a=3
println(a.multi())

不在乎的值(switch时生成casez):
val itMatch = myBits === M"00--10--" // - don't care value

因为id位宽的改变将导致不能直接用“<>”方法进行端口的互联,而这里却依旧使用了 “<>”方法,只不过在最后又调用了removeAssignments方法将id的赋值给抹去,
重新对id字段进行赋值(位宽的匹配检查是在生成RTL代码的一步执行的)。
io.upPort<>io.downPort
io.downPort.aw.addr.removeAssignments()
io.downPort.ar.addr.removeAssignments()
io.downPort.aw.addr:=io.upPort.aw.addr&0xffff
io.downPort.ar.addr:=io.upPort.ar.addr&0xffff

复合类型的使用:
assignFromBits与asBits是相反方向,io.data.assignFromBits(io.dataBus.asBits)
两个总线可以直接比较,io.isSame := io.dataBus0 === io.dataBus1
统一赋值为0,val dataBusOut = master(port())   io.dataBusOut := port().getZero

获取同类型:val ret = cloneOf(requests)

toSeq作用:调用toSeq在于我们从Array[UInt]中选择所使用的索引类型是UInt而非Int

至于拼接的工作,交给initValue函数本身就好:
val memInst = Mem(memData(), 4)
memInst.init(
  Seq(memData().initValue(0x10, 0xff),
      memData().initValue(0x11, 0xff),
      memData().initValue(0x12, 0xff),
      memData().initValue(0x13, 0xff)))

blackBox的inline功能
setInlineVerilog(
    """module adder #(
      |    parameter WIDTH = 16
      |endmodule
      """.stripMargin
在SpinalHDL中可以通过SpinalConfig指定在生成RTL代码时直接将ROM初始化的设置放置在RTL文件中(inlineRom为true)

spinalHDL的fork和join
fork只负责发起一个协程,并返回该协程的句柄,而至于如果要阻塞等待协程执行完毕,则需调用该协程的join方法等待该协程执行完成。
val threadA = fork {}   threadA.join()
实现join_any场景
def joinAny(thread: SimThread*) = {
  while (!thread.map(_.done).reduce(_ | _)) {
    sleep(10)
  }
}
joinAny(threadA, threadB)

  • 10
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值