SpinalHDL Simulation性能提升测试

描述

昨晚看SpinalHDL的Issues,其中有一个关于性能提升的case 吸引到了我,尝试实验到深夜,测试下在SpinalHDL以及cocotb下的性能优化手段。

SpinalHDL Simulation性能提升测试


      无论是SpinalHDL还是cocotb,其在仿真方面所采用的思路是一样的。在SpinalHDL的那个Issue里,Dolu主要想做的是尽可能避免在信号的赋值和读取上的冗余代码。以toInt为例,其会调用getInt函数:

 

private def getInt(bt: BaseType): Int = {
    if(bt.getBitsWidth == 0) return 0
    val manager = SimManagerContext.current.manager
    val signal = btToSignal(manager, bt)
    manager.getInt(signal)
  }

 

   
 而Dolu的思路则是没有必要每次都重新寻找manager、signal这些信息,毕竟对于一个信号而言这两个值是不变的。而是提前准备好对于这种频繁使用的接口则能够尽可能降低不必要的开销。

这里做了个测试,testSimNormal采用普通的API调用形式访问信号,

testSimSpeed则采用加速后的方式进行访问一个512bit信号位宽。两者测试访问1亿次信号值所消耗的时间,测试结果如下:

testSimNormal 26998ms
testSimSpeed 24462ms

      可以看出,还是能够加速仿真速度的。

      考虑到在仿真过程中无非是信号的驱动和读取,那么这里应该是都适用的,遂以相同的DUT相同的Case尝试做了如下测试:  


testSim1:采用SpinalHDL原生API进行仿真测试
 

testSim2:将信号的读取和赋值均改为优化后的方式
 

testSim3:在testSim2的基础上将时钟,复位驱动也改为优化后的方式
 

testSim4:在testSim2的基础上将waitSampling修改为优化后的方式
 

testSim5:在testSim3的基础上将时钟,将waitSampling修改为优化后的方式
 

    测试结果如下:

testSim1 6469.675 ms
testSim2 6196.007 ms
testSim3 6196.007 ms
testSim4 6066.035 ms
testSim5 6076.121 ms

   
 每个测试里面都是跑了500000周期,可以看到,对于降低延迟还是有效果的。对于更大的case,也许会有更有效的效果。     

附上完整的测试代码(由于电脑较差,诸君可自行测试):

import spinal.core._
import spinal.lib._
import spinal.sim.{Signal, SimManager, SimManagerContext}

import scala.collection.mutable.ArrayBuffer

case class dut() extends Component {
  val io = new Bundle {
    val data_in = slave Flow (UInt(512 bits))
    val data_out = master Flow (UInt(512 bits))
  }
  noIoPrefix()
  io.data_out << io.data_in.translateWith(io.data_in.payload + 1).stage()
}
import spinal.core.sim._

object testSimNormal extends App {
  SimConfig.withFstWave.compile(dut()).doSim { dut =>
    dut.io.data_in.valid #= false
    dut.clockDomain.forkStimulus(10)
    dut.clockDomain.waitSampling(10)
    val startTime = System.currentTimeMillis()
    for (index <- 0 until 100000000) {
      dut.io.data_out.payload.toBigInt
    }
    val endTime = System.currentTimeMillis()
    val totalTime = endTime - startTime
    println("代码运行时间:" + totalTime + "毫秒")
  }
}

object testSimSpeed extends App {
  implicit class SimBitVectorPimper(bt: BaseType) {
    class SimProxy(bt: BaseType) {
      val manager = SimManagerContext.current.manager
      val signal = manager.raw.userData.asInstanceOf[ArrayBuffer[Signal]](bt.algoInt)
      val alwaysZero = bt.getBitsWidth == 0

      def getLong = manager.getLong(signal)

      def getBoolean = manager.getLong(signal) != 0

      def getBigInt = manager.getBigInt(signal)

      def assignBoolean(value: Boolean) = manager.setLong(signal, value.toInt)

      def setLong(value: Long) = manager.setLong(signal, value)

      def assignBigInt(value: BigInt) = manager.setBigInt(signal, value)
    }

    def simProxy() = new SimProxy(bt)
  }

  SimConfig.withFstWave.compile(dut()).doSim { dut =>
    dut.io.data_in.valid #= false
    dut.clockDomain.forkStimulus(10)
    dut.clockDomain.waitSampling(10)
    val dataOutHdl = dut.io.data_out.payload.simProxy()
    val startTime = System.currentTimeMillis()
    for (index <- 0 until 100000000) {
      dataOutHdl.getBigInt
    }
    val endTime = System.currentTimeMillis()
    val totalTime = endTime - startTime
    println("代码运行时间:" + totalTime + "毫秒")
  }
}

object SimExtend {
  implicit class SimBitVectorPimper(bt: BaseType) {
    class SimProxy(bt: BaseType) {
      val manager = SimManagerContext.current.manager
      val signal = manager.raw.userData.asInstanceOf[ArrayBuffer[Signal]](bt.algoInt)
      val alwaysZero = bt.getBitsWidth == 0

      def getLong = manager.getLong(signal)

      def getBoolean = manager.getLong(signal) != 0

      def getBigInt = manager.getBigInt(signal)

      def assignBoolean(value: Boolean) = manager.setLong(signal, value.toInt)

      def setLong(value: Long) = manager.setLong(signal, value)

      def assignBigInt(value: BigInt) = manager.setBigInt(signal, value)
    }

    def simProxy() = new SimProxy(bt)
  }

  def getBool(manager: SimManager, who: Bool): Bool = {
    val component = who.component
    if ((who.isInput || who.isOutput) && component != null && component.parent == null) {
      who
    } else {
      manager.userData.asInstanceOf[Component].pulledDataCache.getOrElse(who, null).asInstanceOf[Bool]
    }
  }
}

object testSim extends App {
  val dutCompiled = SimConfig.withFstWave.compile(dut())

  /** *****************************************************************************************
   * testSim1
   * ***************************************************************************************** */
  dutCompiled.doSim { dut =>
    dut.io.data_in.valid #= false
    dut.clockDomain.forkStimulus(10)
    dut.clockDomain.waitSampling(10)
    var sum = BigInt(0)
    for (index <- 0 until 500000) {
      dut.clockDomain.waitSampling()
      if (dut.io.data_out.valid.toBoolean) {
        sum = sum + dut.io.data_out.payload.toBigInt
      }
      dut.io.data_in.valid #= true
      dut.io.data_in.payload #= BigInt(index)
    }
  }

  /** *****************************************************************************************
   * testSim2
   * ***************************************************************************************** */
  dutCompiled.doSim { dut =>
    import SimExtend._
    val dataInValidHdl = dut.io.data_in.valid.simProxy()
    val dataInDataHdl = dut.io.data_in.payload.simProxy()
    val dataOutValidHdl = dut.io.data_out.valid.simProxy()
    val dataOutDataHdl = dut.io.data_out.payload.simProxy()
    dataInValidHdl.assignBoolean(false)
    dut.clockDomain.forkStimulus(10)
    dut.clockDomain.waitSampling(10)
    var sum = BigInt(0)
    for (index <- 0 until 500000) {
      dut.clockDomain.waitSampling()
      if (dataOutValidHdl.getBoolean) {
        sum = sum + dataOutDataHdl.getBigInt
      }
      dataInValidHdl.assignBoolean(true)
      dataInDataHdl.assignBigInt(index)
    }
  }

  /** *****************************************************************************************
   * testSim3
   * ***************************************************************************************** */
  dutCompiled.doSim { dut =>
    import SimExtend._
    val dataInValidHdl = dut.io.data_in.valid.simProxy()
    val dataInDataHdl = dut.io.data_in.payload.simProxy()
    val dataOutValidHdl = dut.io.data_out.valid.simProxy()
    val dataOutDataHdl = dut.io.data_out.payload.simProxy()
    val clock = getBool(SimManagerContext.current.manager, dut.clockDomain.clock).simProxy()
    val reset = getBool(SimManagerContext.current.manager, dut.clockDomain.reset).simProxy()
    dataInValidHdl.assignBoolean(false)
    //clock generation
    clock.assignBoolean(false)
    reset.assignBoolean(true)
    sleep(10 * 16)
    reset.assignBoolean(false)
    fork {
      var value = false

      def t: Unit = {
        value = !value
        clock.assignBoolean(value)
        delayed(5)(t)
      }

      t
    }
    dut.clockDomain.waitSampling(10)
    var sum = BigInt(0)
    for (index <- 0 until 500000) {
      dut.clockDomain.waitSampling()
      if (dataOutValidHdl.getBoolean) {
        sum = sum + dataOutDataHdl.getBigInt
      }
      dataInValidHdl.assignBoolean(true)
      dataInDataHdl.assignBigInt(index)
    }
  }

  /** *****************************************************************************************
   * testSim4
   * ***************************************************************************************** */
  dutCompiled.doSim { dut =>
    import SimExtend._
    val dataInValidHdl = dut.io.data_in.valid.simProxy()
    val dataInDataHdl = dut.io.data_in.payload.simProxy()
    val dataOutValidHdl = dut.io.data_out.valid.simProxy()
    val dataOutDataHdl = dut.io.data_out.payload.simProxy()
    val clock = getBool(SimManagerContext.current.manager, dut.clockDomain.clock).simProxy()
    val reset = getBool(SimManagerContext.current.manager, dut.clockDomain.reset).simProxy()
    var rising = false
    var last = false
    dataInValidHdl.assignBoolean(false)
    //clock generation
    dut.clockDomain.forkStimulus(10)
    dut.clockDomain.waitSampling(10)
    var sum = BigInt(0)
    for (index <- 0 until 500000) {
      waitUntil {
        rising = false
        val current = clock.getBoolean
        if ((!last) && current) {
          rising = true
        }
        last = current
        rising
      }
      if (dataOutValidHdl.getBoolean) {
        sum = sum + dataOutDataHdl.getBigInt
      }
      dataInValidHdl.assignBoolean(true)
      dataInDataHdl.assignBigInt(index)
    }
  }

  /** *****************************************************************************************
   * testSim5
   * ***************************************************************************************** */
  dutCompiled.doSim { dut =>
    import SimExtend._
    val dataInValidHdl = dut.io.data_in.valid.simProxy()
    val dataInDataHdl = dut.io.data_in.payload.simProxy()
    val dataOutValidHdl = dut.io.data_out.valid.simProxy()
    val dataOutDataHdl = dut.io.data_out.payload.simProxy()
    val clock = getBool(SimManagerContext.current.manager, dut.clockDomain.clock).simProxy()
    val reset = getBool(SimManagerContext.current.manager, dut.clockDomain.reset).simProxy()
    var rising = false
    var last = false
    dataInValidHdl.assignBoolean(false)
    //clock generation
    clock.assignBoolean(false)
    reset.assignBoolean(true)
    sleep(10 * 16)
    reset.assignBoolean(false)
    fork {
      var value = false

      def t: Unit = {
        value = !value
        clock.assignBoolean(value)
        delayed(5)(t)
      }

      t
    }
    dut.clockDomain.waitSampling(10)
    var sum = BigInt(0)
    for (index <- 0 until 500000) {
      waitUntil {
        rising = false
        val current = clock.getBoolean
        if ((!last) && current) {
          rising = true
        }
        last = current
        rising
      }
      if (dataOutValidHdl.getBoolean) {
        sum = sum + dataOutDataHdl.getBigInt
      }
      dataInValidHdl.assignBoolean(true)
      dataInDataHdl.assignBigInt(index)
    }
  }
}
cocotb性能优化

 

 
  cocotb的仿真速度一直我是持保留意见的。 在SpinalHDL里面做完尝试,最近工作里用到的cocotb较多,就尝试看下能否应用到cocotb中。看了下cocotb中的信号读写封装背后的调用,其做了太多的封装和调用。遂采用了相同的DUT做了同样的测试。首先是做优化前后的一百万次的方式测试(跑一亿次真的太久了)

testSimNormal 3.58s
testSimSpeed 1.09s

  
  可以看到,这里有明显的性能提升。

    
再来构建下面的六个case:


testCase0 :采用cocotb提供的API接口进行数据读写访问


testCase1: 仅将信号读更改为底层接口直接调用形式进行访问


testCase2:将信号读,信号写均改为底层接口直接调用形式进行访问


testCase3:在testCase2的基础上将信号接口提前生成好而不是使用时例化


testCase4:在testCase4的基础上将时钟生成修改为底层接口直接调用形式


testCase5: 在testCase0基础上,仅将时钟生成修改为底层接口直接调用的形式

   

 测试结果如下:

信号

 
   每个Case中均做100000次周期测试。可以看到,与原生Case仿真相比,testCase5能提升1.7倍多,而testCase4则有4.8倍的性能提升。由此可见,cocotb中对于信号读写的封装由于做了太多安全和边界的处理导致这种在仿真中经常使用的函数带来挺大的开销。   
  由于Verilator好像不支持时钟下沉,如果将时钟的驱动给放到Verilog里面,也许还会有进一步的性能提升。    
 本人对于底层的东西不甚了解,单纯从仿真速度上,cocotb相较于SpinalHDL还是有较大的差距(《既生瑜何生亮——SpinalHDL VS Cocotb》),有一点有意思的额是在SpinalHDL里面修改时钟生成的方式并未有太大的性能提升,而在cocotb里确有明显改善,诸君有兴趣可以自行研究。     
附上源码,感兴趣的小伙伴可以自行测试:     
DUT:

// Generator : SpinalHDL v1.8.0b git head : 761a30e521263983ddf14de3592f7a9f38bf0589
// Component : simSpeedUpTest

`timescale 1ns/1ps

module dut (
  input data_in_valid,
  output reg data_out_valid,
  input [511:0] data_in,
  output reg [511:0] data_out,
  input clk,
  input reset
);
 always @(posedge clk ) begin
    if(reset) begin
      data_out <= 'd0;
      data_out_valid<='d0;
    end else begin
      data_out <= data_in+1;
      data_out_valid<= data_in_valid;
    end
  end
endmodule

 

    TestBench:

 

import cocotb
from cocotb_bus.drivers import BusDriver
from cocotb.clock import Clock
from cocotb.triggers import ClockCycles,RisingEdge,Timer,ReadOnly
from cocotb.handle import *

@cocotb.test(skip=False)
async def testCaseNormal(dut):
    targetDataSignal=dut.data_out._handle
    targetValueSignal=dut.data_out_valid._handle
    dataInvalidSignal=dut.data_in_valid._handle
    dataInDataSignal=dut.data_in._handle
    cocotb.start_soon(generateClk(dut.clk))
    dataInDataSignal.set_signal_val_binstr(0,bin(0)[2:])
    dataInvalidSignal.set_signal_val_int(0,0)
    dut.reset.value=1
    await ClockCycles(dut.clk,10)
    dut.reset.value=0
    await ClockCycles(dut.clk,10)
    sum=0
    for index in range(1000000):
        dut.data_out_valid.value
    await ClockCycles(dut.clk,10)

@cocotb.test(skip=False)
async def testCaseSpeed(dut):
    targetDataSignal=dut.data_out._handle
    targetValueSignal=dut.data_out_valid._handle
    dataInvalidSignal=dut.data_in_valid._handle
    dataInDataSignal=dut.data_in._handle
    cocotb.start_soon(generateClk(dut.clk))
    dataInDataSignal.set_signal_val_binstr(0,bin(0)[2:])
    dataInvalidSignal.set_signal_val_int(0,0)
    dut.reset.value=1
    await ClockCycles(dut.clk,10)
    dut.reset.value=0
    await ClockCycles(dut.clk,10)
    sum=0
    for index in range(1000000):
        targetDataSignal.get_signal_val_binstr()
    await ClockCycles(dut.clk,10)

@cocotb.test(skip=False)
async def testCase0(dut):
    cocotb.start_soon(Clock(dut.clk,10,'ns').start())
    dut.reset.value=1
    dut.data_in.value = 0
    dut.data_in_valid.value = 0
    await ClockCycles(dut.clk,10)
    dut.reset.value=0
    await ClockCycles(dut.clk,10)
    sum=0
    targetSignal=dut.data_out._handle
    for index in range(100000):
        await RisingEdge(dut.clk)
        if int(dut.data_out_valid.value) == 1:
            sum+= dut.data_out.value
        dut.data_in_valid.value = 1
        dut.data_in.value = index
    await ClockCycles(dut.clk,100000)

@cocotb.test(skip=False)
async def testCase1(dut):
    cocotb.start_soon(Clock(dut.clk,10,'ns').start())
    dut.data_in.value = 0
    dut.data_in_valid.value = 0
    dut.reset.value=1
    await ClockCycles(dut.clk,10)
    dut.reset.value=0
    await ClockCycles(dut.clk,10)
    sum=0
    targetDataSignal=dut.data_out._handle
    targetValueSignal=dut.data_out_valid._handle
    for index in range(100000):
        await RisingEdge(dut.clk)
        if targetValueSignal.get_signal_val_long()==1:
            sum+= int(targetDataSignal.get_signal_val_binstr(),2)
        dut.data_in_valid.value = 1
        dut.data_in.value = index
    await ClockCycles(dut.clk,10)


@cocotb.test(skip=False)
async def testCase2(dut):
    cocotb.start_soon(Clock(dut.clk,10,'ns').start())
    dut.data_in.value = 0
    dut.data_in_valid.value = 0
    dut.reset.value=1
    await ClockCycles(dut.clk,10)
    dut.reset.value=0
    await ClockCycles(dut.clk,10)
    sum=0
    targetDataSignal=dut.data_out._handle
    targetValueSignal=dut.data_out_valid._handle
    for index in range(100000):
        await RisingEdge(dut.clk)
        if targetValueSignal.get_signal_val_long()==1:
            sum+= int(targetDataSignal.get_signal_val_binstr(),2)
        dut.data_in._handle.set_signal_val_binstr(0,bin(index)[2:])
        dut.data_in_valid._handle.set_signal_val_int(0,1)
    await ClockCycles(dut.clk,10)

@cocotb.test(skip=False)
async def testCase3(dut):
    cocotb.start_soon(Clock(dut.clk,10,'ns').start())
    dut.data_in.value = 0
    dut.data_in_valid.value = 0
    dut.reset.value=1
    await ClockCycles(dut.clk,10)
    dut.reset.value=0
    await ClockCycles(dut.clk,10)
    sum=0
    targetDataSignal=dut.data_out._handle
    targetValueSignal=dut.data_out_valid._handle
    dataInvalidSignal=dut.data_in_valid._handle
    dataInDataSignal=dut.data_in._handle
    for index in range(100000):
        await RisingEdge(dut.clk)
        if targetValueSignal.get_signal_val_long()==1:
            sum+= int(targetDataSignal.get_signal_val_binstr(),2)
        dataInDataSignal.set_signal_val_binstr(0,bin(index)[2:])
        dataInvalidSignal.set_signal_val_int(0,1)
    await ClockCycles(dut.clk,10)

async def generateClk(clk):
    clk._handle.set_signal_val_int(1,0)
    while True:
        await Timer(5, units="ns")
        clk._handle.set_signal_val_int(0,0)
        await Timer(5, units="ns")
        clk._handle.set_signal_val_int(0,1)

@cocotb.test(skip=False)
async def testCase4(dut):
    targetDataSignal=dut.data_out._handle
    targetValueSignal=dut.data_out_valid._handle
    dataInvalidSignal=dut.data_in_valid._handle
    dataInDataSignal=dut.data_in._handle
    cocotb.start_soon(generateClk(dut.clk))
    dataInDataSignal.set_signal_val_binstr(0,bin(0)[2:])
    dataInvalidSignal.set_signal_val_int(0,0)
    dut.reset.value=1
    await ClockCycles(dut.clk,10)
    dut.reset.value=0
    await ClockCycles(dut.clk,10)
    sum=0
    for index in range(100000):
        await RisingEdge(dut.clk)
        if targetValueSignal.get_signal_val_long()==1:
            sum+= int(targetDataSignal.get_signal_val_binstr(),2)
        dataInDataSignal.set_signal_val_binstr(0,bin(index)[2:])
        dataInvalidSignal.set_signal_val_int(0,1)
    await ClockCycles(dut.clk,10)

@cocotb.test(skip=False)
async def testCase5(dut):
    cocotb.start_soon(generateClk(dut.clk))
    dut.reset.value=1
    dut.data_in.value = 0
    dut.data_in_valid.value = 0
    await ClockCycles(dut.clk,10)
    dut.reset.value=0
    await ClockCycles(dut.clk,10)
    sum=0
    targetSignal=dut.data_out._handle
    for index in range(100000):
        await RisingEdge(dut.clk)
        if int(dut.data_out_valid.value) == 1:
            sum+= dut.data_out.value
        dut.data_in_valid.value = 1
        dut.data_in.value = index
    await ClockCycles(dut.clk,100000)
  审核编辑:汤梓红
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分