昨晚看SpinalHDL的Issues,其中有一个关于性能提升的case 吸引到了我,尝试实验到深夜,测试下在SpinalHDL以及cocotb下的性能优化手段。
	SpinalHDL Simulation性能提升测试
	   无论是SpinalHDL还是cocotb,其在仿真方面所采用的思路是一样的。在SpinalHDL的那个Issue里,Dolu主要想做的是尽可能避免在信号的赋值和读取上的冗余代码。以toInt为例,其会调用getInt函数:
	
private defgetInt(bt: BaseType):Int = { if(bt.getBitsWidth == 0) return0 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 fromcocotb_bus.drivers import BusDriver fromcocotb.clock import Clock fromcocotb.triggers import ClockCycles,RisingEdge,Timer,ReadOnly fromcocotb.handle import * @cocotb.test(skip=False) asyncdef 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 awaitClockCycles(dut.clk,10) dut.reset.value=0 awaitClockCycles(dut.clk,10) sum=0 forindex inrange(1000000): dut.data_out_valid.value awaitClockCycles(dut.clk,10) @cocotb.test(skip=False) asyncdef 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 awaitClockCycles(dut.clk,10) dut.reset.value=0 awaitClockCycles(dut.clk,10) sum=0 forindex inrange(1000000): targetDataSignal.get_signal_val_binstr() awaitClockCycles(dut.clk,10) @cocotb.test(skip=False) asyncdef 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 awaitClockCycles(dut.clk,10) dut.reset.value=0 awaitClockCycles(dut.clk,10) sum=0 targetSignal=dut.data_out._handle forindex inrange(100000): awaitRisingEdge(dut.clk) ifint(dut.data_out_valid.value) == 1: sum+= dut.data_out.value dut.data_in_valid.value= 1 dut.data_in.value= index awaitClockCycles(dut.clk,100000) @cocotb.test(skip=False) asyncdef 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 awaitClockCycles(dut.clk,10) dut.reset.value=0 awaitClockCycles(dut.clk,10) sum=0 targetDataSignal=dut.data_out._handle targetValueSignal=dut.data_out_valid._handle forindex inrange(100000): awaitRisingEdge(dut.clk) iftargetValueSignal.get_signal_val_long()==1: sum+= int(targetDataSignal.get_signal_val_binstr(),2) dut.data_in_valid.value= 1 dut.data_in.value= index awaitClockCycles(dut.clk,10) @cocotb.test(skip=False) asyncdef 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 awaitClockCycles(dut.clk,10) dut.reset.value=0 awaitClockCycles(dut.clk,10) sum=0 targetDataSignal=dut.data_out._handle targetValueSignal=dut.data_out_valid._handle forindex inrange(100000): awaitRisingEdge(dut.clk) iftargetValueSignal.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) awaitClockCycles(dut.clk,10) @cocotb.test(skip=False) asyncdef 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 awaitClockCycles(dut.clk,10) dut.reset.value=0 awaitClockCycles(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 forindex inrange(100000): awaitRisingEdge(dut.clk) iftargetValueSignal.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) awaitClockCycles(dut.clk,10) asyncdef generateClk(clk): clk._handle.set_signal_val_int(1,0) whileTrue: awaitTimer(5, units="ns") clk._handle.set_signal_val_int(0,0) awaitTimer(5, units="ns") clk._handle.set_signal_val_int(0,1) @cocotb.test(skip=False) asyncdef 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 awaitClockCycles(dut.clk,10) dut.reset.value=0 awaitClockCycles(dut.clk,10) sum=0 forindex inrange(100000): awaitRisingEdge(dut.clk) iftargetValueSignal.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) awaitClockCycles(dut.clk,10) @cocotb.test(skip=False) asyncdef testCase5(dut): cocotb.start_soon(generateClk(dut.clk)) dut.reset.value=1 dut.data_in.value= 0 dut.data_in_valid.value= 0 awaitClockCycles(dut.clk,10) dut.reset.value=0 awaitClockCycles(dut.clk,10) sum=0 targetSignal=dut.data_out._handle forindex inrange(100000): awaitRisingEdge(dut.clk) ifint(dut.data_out_valid.value) == 1: sum+= dut.data_out.value dut.data_in_valid.value= 1 dut.data_in.value= index awaitClockCycles(dut.clk,100000)审核编辑:汤梓红
- 
                                仿真
                                +关注关注 50文章 4057浏览量 133492
- 
                                信号
                                +关注关注 11文章 2785浏览量 76699
- 
                                代码
                                +关注关注 30文章 4765浏览量 68450
- 
                                Simulation
                                +关注关注 0文章 13浏览量 8156
原文标题:给仿真加点速
文章出处:【微信号:Spinal FPGA,微信公众号:Spinal FPGA】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
如何实现SpinalHDL 环境搭建
 
    
spinalhdl转Verilog可读性 SpinalHDL开发流程
 
    
如何提升EMC性能?
关于SpinalHDL中的验证覆盖率收集简单说明
就SpinalHDL的测试平台搭建进行说明
SpinalHDL是如何让仿真跑起来的
如何在SpinalHDL里启动一个仿真
SpinalHDL设计错误总结相关资料分享
基于Windows系统的SpinalHDL开发环境搭建步骤
SpinalHDL里如何实现Sobel边缘检测
SpinalHDL BlackBox时钟与复位
 
    
SOLIDWORKS Simulation 2024的10大新功能
 
    
SOLIDWORKS教育版——SIMULATION
 
    
 
           
        
 
         SpinalHDL Simulation性能提升测试
SpinalHDL Simulation性能提升测试 
  
            
             
             
                 
             工商网监
工商网监
        
评论