xref: /XiangShan/src/main/scala/xiangshan/XSCore.scala (revision 3a6db8a39a25f02047d1fb2b257c89be0b2c36dc)
1/***************************************************************************************
2* Copyright (c) 2020-2021 Institute of Computing Technology, Chinese Academy of Sciences
3* Copyright (c) 2020-2021 Peng Cheng Laboratory
4*
5* XiangShan is licensed under Mulan PSL v2.
6* You can use this software according to the terms and conditions of the Mulan PSL v2.
7* You may obtain a copy of Mulan PSL v2 at:
8*          http://license.coscl.org.cn/MulanPSL2
9*
10* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
11* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
12* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
13*
14* See the Mulan PSL v2 for more details.
15***************************************************************************************/
16
17package xiangshan
18
19import chipsalliance.rocketchip.config
20import chipsalliance.rocketchip.config.Parameters
21import chisel3._
22import chisel3.util._
23import freechips.rocketchip.diplomacy.{BundleBridgeSource, LazyModule, LazyModuleImp}
24import freechips.rocketchip.interrupts.{IntSinkNode, IntSinkPortSimple}
25import freechips.rocketchip.tile.HasFPUParameters
26import system.HasSoCParameter
27import utils._
28import xiangshan.backend._
29import xiangshan.backend.exu.{ExuConfig, Wb2Ctrl, WbArbiterWrapper}
30import xiangshan.cache.mmu._
31import xiangshan.frontend._
32
33import scala.collection.mutable.ListBuffer
34
35abstract class XSModule(implicit val p: Parameters) extends MultiIOModule
36  with HasXSParameter
37  with HasFPUParameters {
38  def io: Record
39}
40
41//remove this trait after impl module logic
42trait NeedImpl {
43  this: RawModule =>
44  override protected def IO[T <: Data](iodef: T): T = {
45    println(s"[Warn]: (${this.name}) please reomve 'NeedImpl' after implement this module")
46    val io = chisel3.experimental.IO(iodef)
47    io <> DontCare
48    io
49  }
50}
51
52class WritebackSourceParams(
53  var exuConfigs: Seq[Seq[ExuConfig]] = Seq()
54 ) {
55  def length: Int = exuConfigs.length
56  def ++(that: WritebackSourceParams): WritebackSourceParams = {
57    new WritebackSourceParams(exuConfigs ++ that.exuConfigs)
58  }
59}
60
61trait HasWritebackSource {
62  val writebackSourceParams: Seq[WritebackSourceParams]
63  final def writebackSource(sourceMod: HasWritebackSourceImp): Seq[Seq[Valid[ExuOutput]]] = {
64    require(sourceMod.writebackSource.isDefined, "should not use Valid[ExuOutput]")
65    val source = sourceMod.writebackSource.get
66    require(source.length == writebackSourceParams.length, "length mismatch between sources")
67    for ((s, p) <- source.zip(writebackSourceParams)) {
68      require(s.length == p.length, "params do not match with the exuOutput")
69    }
70    source
71  }
72  final def writebackSource1(sourceMod: HasWritebackSourceImp): Seq[Seq[DecoupledIO[ExuOutput]]] = {
73    require(sourceMod.writebackSource1.isDefined, "should not use DecoupledIO[ExuOutput]")
74    val source = sourceMod.writebackSource1.get
75    require(source.length == writebackSourceParams.length, "length mismatch between sources")
76    for ((s, p) <- source.zip(writebackSourceParams)) {
77      require(s.length == p.length, "params do not match with the exuOutput")
78    }
79    source
80  }
81  val writebackSourceImp: HasWritebackSourceImp
82}
83
84trait HasWritebackSourceImp {
85  def writebackSource: Option[Seq[Seq[Valid[ExuOutput]]]] = None
86  def writebackSource1: Option[Seq[Seq[DecoupledIO[ExuOutput]]]] = None
87}
88
89trait HasWritebackSink {
90  // Caches all sources. The selected source will be the one with smallest length.
91  var writebackSinks = ListBuffer.empty[(Seq[HasWritebackSource], Seq[Int])]
92  def addWritebackSink(source: Seq[HasWritebackSource], index: Option[Seq[Int]] = None): HasWritebackSink = {
93    val realIndex = if (index.isDefined) index.get else Seq.fill(source.length)(0)
94    writebackSinks += ((source, realIndex))
95    this
96  }
97
98  def writebackSinksParams: Seq[WritebackSourceParams] = {
99    writebackSinks.map{ case (s, i) => s.zip(i).map(x => x._1.writebackSourceParams(x._2)).reduce(_ ++ _) }
100  }
101  final def writebackSinksMod(
102     thisMod: Option[HasWritebackSource] = None,
103     thisModImp: Option[HasWritebackSourceImp] = None
104   ): Seq[Seq[HasWritebackSourceImp]] = {
105    require(thisMod.isDefined == thisModImp.isDefined)
106    writebackSinks.map(_._1.map(source =>
107      if (thisMod.isDefined && source == thisMod.get) thisModImp.get else source.writebackSourceImp)
108    )
109  }
110  final def writebackSinksImp(
111    thisMod: Option[HasWritebackSource] = None,
112    thisModImp: Option[HasWritebackSourceImp] = None
113  ): Seq[Seq[ValidIO[ExuOutput]]] = {
114    val sourceMod = writebackSinksMod(thisMod, thisModImp)
115    writebackSinks.zip(sourceMod).map{ case ((s, i), m) =>
116      s.zip(i).zip(m).flatMap(x => x._1._1.writebackSource(x._2)(x._1._2))
117    }
118  }
119  def selWritebackSinks(func: WritebackSourceParams => Int): Int = {
120    writebackSinksParams.zipWithIndex.minBy(params => func(params._1))._2
121  }
122  def generateWritebackIO(
123    thisMod: Option[HasWritebackSource] = None,
124    thisModImp: Option[HasWritebackSourceImp] = None
125   ): Unit
126}
127
128abstract class XSBundle(implicit val p: Parameters) extends Bundle
129  with HasXSParameter
130
131abstract class XSCoreBase()(implicit p: config.Parameters) extends LazyModule
132  with HasXSParameter with HasExuWbHelper
133{
134  // interrupt sinks
135  val clint_int_sink = IntSinkNode(IntSinkPortSimple(1, 2))
136  val debug_int_sink = IntSinkNode(IntSinkPortSimple(1, 1))
137  val plic_int_sink = IntSinkNode(IntSinkPortSimple(2, 1))
138  // outer facing nodes
139  val frontend = LazyModule(new Frontend())
140  val ptw = LazyModule(new PTWWrapper())
141  val csrOut = BundleBridgeSource(Some(() => new DistributedCSRIO()))
142
143  val wbArbiter = LazyModule(new WbArbiterWrapper(exuConfigs, NRIntWritePorts, NRFpWritePorts))
144  val intWbPorts = wbArbiter.intWbPorts
145  val fpWbPorts = wbArbiter.fpWbPorts
146
147  // TODO: better RS organization
148  // generate rs according to number of function units
149  require(exuParameters.JmpCnt == 1)
150  require(exuParameters.MduCnt <= exuParameters.AluCnt && exuParameters.MduCnt > 0)
151  require(exuParameters.FmiscCnt <= exuParameters.FmacCnt && exuParameters.FmiscCnt > 0)
152  require(exuParameters.LduCnt == 2 && exuParameters.StuCnt == 2)
153
154  // one RS every 2 MDUs
155  val schedulePorts = Seq(
156    // exuCfg, numDeq, intFastWakeupTarget, fpFastWakeupTarget
157    Seq(
158      (AluExeUnitCfg, exuParameters.AluCnt, Seq(AluExeUnitCfg, MulDivExeUnitCfg, JumpCSRExeUnitCfg, LdExeUnitCfg, StaExeUnitCfg), Seq()),
159      (MulDivExeUnitCfg, exuParameters.MduCnt, Seq(AluExeUnitCfg, MulDivExeUnitCfg), Seq()),
160      (JumpCSRExeUnitCfg, 1, Seq(), Seq()),
161      (LdExeUnitCfg, exuParameters.LduCnt, Seq(AluExeUnitCfg, LdExeUnitCfg), Seq()),
162      (StaExeUnitCfg, exuParameters.StuCnt, Seq(), Seq()),
163      (StdExeUnitCfg, exuParameters.StuCnt, Seq(), Seq())
164    ),
165    Seq(
166      (FmacExeUnitCfg, exuParameters.FmacCnt, Seq(), Seq(FmacExeUnitCfg, FmiscExeUnitCfg)),
167      (FmiscExeUnitCfg, exuParameters.FmiscCnt, Seq(), Seq())
168    )
169  )
170
171  // should do outer fast wakeup ports here
172  val otherFastPorts = schedulePorts.zipWithIndex.map { case (sche, i) =>
173    val otherCfg = schedulePorts.zipWithIndex.filter(_._2 != i).map(_._1).reduce(_ ++ _)
174    val outerPorts = sche.map(cfg => {
175      // exe units from this scheduler need fastUops from exeunits
176      val outerWakeupInSche = sche.filter(_._1.wakeupFromExu)
177      val intraIntScheOuter = outerWakeupInSche.filter(_._3.contains(cfg._1)).map(_._1)
178      val intraFpScheOuter = outerWakeupInSche.filter(_._4.contains(cfg._1)).map(_._1)
179      // exe units from other schedulers need fastUop from outside
180      val otherIntSource = otherCfg.filter(_._3.contains(cfg._1)).map(_._1)
181      val otherFpSource = otherCfg.filter(_._4.contains(cfg._1)).map(_._1)
182      val intSource = findInWbPorts(intWbPorts, intraIntScheOuter ++ otherIntSource)
183      val fpSource = findInWbPorts(fpWbPorts, intraFpScheOuter ++ otherFpSource)
184      getFastWakeupIndex(cfg._1, intSource, fpSource, intWbPorts.length).sorted
185    })
186    println(s"inter-scheduler wakeup sources for $i: $outerPorts")
187    outerPorts
188  }
189
190  // allow mdu and fmisc to have 2*numDeq enqueue ports
191  val intDpPorts = (0 until exuParameters.AluCnt).map(i => {
192    if (i < exuParameters.JmpCnt) Seq((0, i), (1, i), (2, i))
193    else if (i < 2 * exuParameters.MduCnt) Seq((0, i), (1, i))
194    else Seq((0, i))
195  })
196  val lsDpPorts = Seq(
197    Seq((3, 0)),
198    Seq((3, 1)),
199    Seq((4, 0)),
200    Seq((4, 1))
201  ) ++ (0 until exuParameters.StuCnt).map(i => Seq((5, i)))
202  val fpDpPorts = (0 until exuParameters.FmacCnt).map(i => {
203    if (i < 2 * exuParameters.FmiscCnt) Seq((0, i), (1, i))
204    else Seq((0, i))
205  })
206
207  val dispatchPorts = Seq(intDpPorts ++ lsDpPorts, fpDpPorts)
208
209  val outIntRfReadPorts = Seq(0, 0)
210  val outFpRfReadPorts = Seq(0, 2)
211  val hasIntRf = Seq(true, false)
212  val hasFpRf = Seq(false, true)
213  val exuBlocks = schedulePorts.zip(dispatchPorts).zip(otherFastPorts).zipWithIndex.map {
214    case (((sche, disp), other), i) =>
215      LazyModule(new ExuBlock(sche, disp, intWbPorts, fpWbPorts, other, outIntRfReadPorts(i), outFpRfReadPorts(i), hasIntRf(i), hasFpRf(i)))
216  }
217
218  val memBlock = LazyModule(new MemBlock()(p.alter((site, here, up) => {
219    case XSCoreParamsKey => up(XSCoreParamsKey).copy(
220      IssQueSize = exuBlocks.head.scheduler.memRsEntries.max
221    )
222  })))
223
224  val wb2Ctrl = LazyModule(new Wb2Ctrl(exuConfigs))
225  wb2Ctrl.addWritebackSink(exuBlocks :+ memBlock)
226  val ctrlBlock = LazyModule(new CtrlBlock)
227  val writebackSources = Seq(Seq(wb2Ctrl), Seq(wbArbiter))
228  writebackSources.foreach(s => ctrlBlock.addWritebackSink(s))
229}
230
231class XSCore()(implicit p: config.Parameters) extends XSCoreBase
232  with HasXSDts
233{
234  lazy val module = new XSCoreImp(this)
235}
236
237class XSCoreImp(outer: XSCoreBase) extends LazyModuleImp(outer)
238  with HasXSParameter
239  with HasSoCParameter {
240  val io = IO(new Bundle {
241    val hartId = Input(UInt(64.W))
242    val l2_pf_enable = Output(Bool())
243    val perfEvents = Input(Vec(numPCntHc * coreParams.L2NBanks, new PerfEvent))
244    val beu_errors = Output(new XSL1BusErrors())
245  })
246
247  println(s"FPGAPlatform:${env.FPGAPlatform} EnableDebug:${env.EnableDebug}")
248
249  val frontend = outer.frontend.module
250  val ctrlBlock = outer.ctrlBlock.module
251  val wb2Ctrl = outer.wb2Ctrl.module
252  val memBlock = outer.memBlock.module
253  val ptw = outer.ptw.module
254  val exuBlocks = outer.exuBlocks.map(_.module)
255
256  ctrlBlock.io.hartId := io.hartId
257  exuBlocks.foreach(_.io.hartId := io.hartId)
258  memBlock.io.hartId := io.hartId
259  outer.wbArbiter.module.io.hartId := io.hartId
260
261  outer.wbArbiter.module.io.redirect <> ctrlBlock.io.redirect
262  val allWriteback = exuBlocks.flatMap(_.io.fuWriteback) ++ memBlock.io.writeback
263  require(exuConfigs.length == allWriteback.length, s"${exuConfigs.length} != ${allWriteback.length}")
264  outer.wbArbiter.module.io.in <> allWriteback
265  val rfWriteback = outer.wbArbiter.module.io.out
266
267  wb2Ctrl.io.redirect <> ctrlBlock.io.redirect
268  outer.wb2Ctrl.generateWritebackIO()
269
270  io.beu_errors.icache <> frontend.io.error
271  io.beu_errors.dcache <> memBlock.io.error
272
273  require(exuBlocks.count(_.fuConfigs.map(_._1).contains(JumpCSRExeUnitCfg)) == 1)
274  val csrFenceMod = exuBlocks.filter(_.fuConfigs.map(_._1).contains(JumpCSRExeUnitCfg)).head
275  val csrioIn = csrFenceMod.io.fuExtra.csrio.get
276  val fenceio = csrFenceMod.io.fuExtra.fenceio.get
277
278  frontend.io.backend <> ctrlBlock.io.frontend
279  frontend.io.sfence <> fenceio.sfence
280  frontend.io.tlbCsr <> csrioIn.tlb
281  frontend.io.csrCtrl <> csrioIn.customCtrl
282  frontend.io.fencei := fenceio.fencei
283
284  ctrlBlock.io.csrCtrl <> csrioIn.customCtrl
285  val redirectBlocks = exuBlocks.reverse.filter(_.fuConfigs.map(_._1).map(_.hasRedirect).reduce(_ || _))
286  ctrlBlock.io.exuRedirect <> redirectBlocks.flatMap(_.io.fuExtra.exuRedirect)
287  ctrlBlock.io.stIn <> memBlock.io.stIn
288  ctrlBlock.io.memoryViolation <> memBlock.io.memoryViolation
289  exuBlocks.head.io.scheExtra.enqLsq.get <> memBlock.io.enqLsq
290  exuBlocks.foreach(b => {
291    b.io.scheExtra.lcommit := ctrlBlock.io.robio.lsq.lcommit
292    b.io.scheExtra.scommit := memBlock.io.sqDeq
293    b.io.scheExtra.lqCancelCnt := memBlock.io.lqCancelCnt
294    b.io.scheExtra.sqCancelCnt := memBlock.io.sqCancelCnt
295  })
296  val sourceModules = outer.writebackSources.map(_.map(_.module.asInstanceOf[HasWritebackSourceImp]))
297  outer.ctrlBlock.generateWritebackIO()
298
299  val allFastUop = exuBlocks.flatMap(b => b.io.fastUopOut.dropRight(b.numOutFu)) ++ memBlock.io.otherFastWakeup
300  require(allFastUop.length == exuConfigs.length, s"${allFastUop.length} != ${exuConfigs.length}")
301  val intFastUop = allFastUop.zip(exuConfigs).filter(_._2.writeIntRf).map(_._1)
302  val fpFastUop = allFastUop.zip(exuConfigs).filter(_._2.writeFpRf).map(_._1)
303  val intFastUop1 = outer.wbArbiter.intConnections.map(c => intFastUop(c.head))
304  val fpFastUop1 = outer.wbArbiter.fpConnections.map(c => fpFastUop(c.head))
305  val allFastUop1 = intFastUop1 ++ fpFastUop1
306
307  ctrlBlock.io.dispatch <> exuBlocks.flatMap(_.io.in)
308
309  exuBlocks(0).io.scheExtra.fpRfReadIn.get <> exuBlocks(1).io.scheExtra.fpRfReadOut.get
310  exuBlocks(0).io.scheExtra.fpStateReadIn.get <> exuBlocks(1).io.scheExtra.fpStateReadOut.get
311
312  memBlock.io.issue <> exuBlocks(0).io.issue.get
313  // By default, instructions do not have exceptions when they enter the function units.
314  memBlock.io.issue.map(_.bits.uop.clearExceptions())
315  exuBlocks(0).io.scheExtra.loadFastMatch.get <> memBlock.io.loadFastMatch
316
317  val stdIssue = exuBlocks(0).io.issue.get.takeRight(exuParameters.StuCnt)
318  exuBlocks.map(_.io).foreach { exu =>
319    exu.redirect <> ctrlBlock.io.redirect
320    exu.allocPregs <> ctrlBlock.io.allocPregs
321    exu.rfWriteback <> rfWriteback
322    exu.fastUopIn <> allFastUop1
323    exu.scheExtra.jumpPc <> ctrlBlock.io.jumpPc
324    exu.scheExtra.jalr_target <> ctrlBlock.io.jalr_target
325    exu.scheExtra.stIssuePtr <> memBlock.io.stIssuePtr
326    exu.scheExtra.debug_fp_rat <> ctrlBlock.io.debug_fp_rat
327    exu.scheExtra.debug_int_rat <> ctrlBlock.io.debug_int_rat
328    exu.scheExtra.memWaitUpdateReq.staIssue.zip(memBlock.io.stIn).foreach{case (sink, src) => {
329      sink.bits := src.bits
330      sink.valid := src.valid
331    }}
332    exu.scheExtra.memWaitUpdateReq.stdIssue.zip(stdIssue).foreach{case (sink, src) => {
333      sink.valid := src.valid
334      sink.bits := src.bits
335    }}
336  }
337  XSPerfHistogram("fastIn_count", PopCount(allFastUop1.map(_.valid)), true.B, 0, allFastUop1.length, 1)
338  XSPerfHistogram("wakeup_count", PopCount(rfWriteback.map(_.valid)), true.B, 0, rfWriteback.length, 1)
339
340  ctrlBlock.perfinfo.perfEventsEu0 := exuBlocks(0).getPerf.dropRight(outer.exuBlocks(0).scheduler.numRs)
341  ctrlBlock.perfinfo.perfEventsEu1 := exuBlocks(1).getPerf.dropRight(outer.exuBlocks(1).scheduler.numRs)
342  memBlock.io.perfEventsPTW  := ptw.getPerf
343  ctrlBlock.perfinfo.perfEventsRs  := outer.exuBlocks.flatMap(b => b.module.getPerf.takeRight(b.scheduler.numRs))
344
345  csrioIn.hartId <> io.hartId
346  csrioIn.perf <> DontCare
347  csrioIn.perf.retiredInstr <> ctrlBlock.io.robio.toCSR.perfinfo.retiredInstr
348  csrioIn.perf.ctrlInfo <> ctrlBlock.io.perfInfo.ctrlInfo
349  csrioIn.perf.memInfo <> memBlock.io.memInfo
350  csrioIn.perf.frontendInfo <> frontend.io.frontendInfo
351
352  csrioIn.perf.perfEventsFrontend <> frontend.getPerf
353  csrioIn.perf.perfEventsCtrl     <> ctrlBlock.getPerf
354  csrioIn.perf.perfEventsLsu      <> memBlock.getPerf
355  csrioIn.perf.perfEventsHc       <> io.perfEvents
356
357  csrioIn.fpu.fflags <> ctrlBlock.io.robio.toCSR.fflags
358  csrioIn.fpu.isIllegal := false.B
359  csrioIn.fpu.dirty_fs <> ctrlBlock.io.robio.toCSR.dirty_fs
360  csrioIn.fpu.frm <> exuBlocks(1).io.fuExtra.frm.get
361  csrioIn.exception <> ctrlBlock.io.robio.exception
362  csrioIn.isXRet <> ctrlBlock.io.robio.toCSR.isXRet
363  csrioIn.trapTarget <> ctrlBlock.io.robio.toCSR.trapTarget
364  csrioIn.interrupt <> ctrlBlock.io.robio.toCSR.intrBitSet
365  csrioIn.memExceptionVAddr <> memBlock.io.lsqio.exceptionAddr.vaddr
366
367  csrioIn.externalInterrupt.msip := outer.clint_int_sink.in.head._1(0)
368  csrioIn.externalInterrupt.mtip := outer.clint_int_sink.in.head._1(1)
369  csrioIn.externalInterrupt.meip := outer.plic_int_sink.in.head._1(0)
370  csrioIn.externalInterrupt.seip := outer.plic_int_sink.in.last._1(0)
371  csrioIn.externalInterrupt.debug := outer.debug_int_sink.in.head._1(0)
372
373  csrioIn.distributedUpdate(0).w.valid := memBlock.io.csrUpdate.w.valid
374  csrioIn.distributedUpdate(0).w.bits := memBlock.io.csrUpdate.w.bits
375  csrioIn.distributedUpdate(1).w.valid := frontend.io.csrUpdate.w.valid
376  csrioIn.distributedUpdate(1).w.bits := frontend.io.csrUpdate.w.bits
377
378  fenceio.sfence <> memBlock.io.sfence
379  fenceio.sbuffer <> memBlock.io.fenceToSbuffer
380
381  memBlock.io.redirect <> ctrlBlock.io.redirect
382  memBlock.io.rsfeedback <> exuBlocks(0).io.scheExtra.feedback.get
383  memBlock.io.csrCtrl <> csrioIn.customCtrl
384  memBlock.io.tlbCsr <> csrioIn.tlb
385  memBlock.io.lsqio.rob <> ctrlBlock.io.robio.lsq
386  memBlock.io.lsqio.exceptionAddr.isStore := CommitType.lsInstIsStore(ctrlBlock.io.robio.exception.bits.uop.ctrl.commitType)
387
388  val itlbRepeater1 = PTWRepeater(frontend.io.ptw, fenceio.sfence, csrioIn.tlb)
389  val itlbRepeater2 = PTWRepeater(itlbRepeater1.io.ptw, ptw.io.tlb(0), fenceio.sfence, csrioIn.tlb)
390  val dtlbRepeater1  = PTWFilter(memBlock.io.ptw, fenceio.sfence, csrioIn.tlb, l2tlbParams.filterSize)
391  val dtlbRepeater2  = PTWRepeaterNB(passReady = false, dtlbRepeater1.io.ptw, ptw.io.tlb(1), fenceio.sfence, csrioIn.tlb)
392  ptw.io.sfence <> fenceio.sfence
393  ptw.io.csr.tlb <> csrioIn.tlb
394  ptw.io.csr.distribute_csr <> csrioIn.customCtrl.distribute_csr
395
396  // if l2 prefetcher use stream prefetch, it should be placed in XSCore
397  io.l2_pf_enable := csrioIn.customCtrl.l2_pf_enable
398
399  // Modules are reset one by one
400  // reset --> SYNC ----> SYNC ------> SYNC -----> SYNC -----> SYNC ---
401  //                  |          |            |           |           |
402  //                  v          v            v           v           v
403  //                 PTW  {MemBlock, dtlb}  ExuBlocks  CtrlBlock  {Frontend, itlb}
404  val resetChain = Seq(
405    Seq(memBlock, dtlbRepeater1, dtlbRepeater2),
406    Seq(exuBlocks.head),
407    // Note: arbiters don't actually have reset ports
408    exuBlocks.tail ++ Seq(outer.wbArbiter.module),
409    Seq(ctrlBlock),
410    Seq(ptw),
411    Seq(frontend, itlbRepeater1, itlbRepeater2)
412  )
413  ResetGen(resetChain, reset.asBool, !debugOpts.FPGAPlatform)
414}
415