1730cfbc0SXuan Hupackage xiangshan.backend.issue 2730cfbc0SXuan Hu 3730cfbc0SXuan Huimport chipsalliance.rocketchip.config.Parameters 4730cfbc0SXuan Huimport chisel3._ 5730cfbc0SXuan Huimport chisel3.util._ 6730cfbc0SXuan Huimport freechips.rocketchip.diplomacy.{LazyModule, LazyModuleImp} 7730cfbc0SXuan Huimport xiangshan._ 8730cfbc0SXuan Huimport xiangshan.backend.Bundles 9730cfbc0SXuan Huimport xiangshan.backend.datapath.DataConfig.VAddrData 10730cfbc0SXuan Huimport xiangshan.backend.regfile.RfWritePortWithConfig 11730cfbc0SXuan Huimport xiangshan.backend.rename.BusyTable 12730cfbc0SXuan Huimport xiangshan.mem.{LsqEnqCtrl, LsqEnqIO, MemWaitUpdateReq, SqPtr} 13730cfbc0SXuan Huimport xiangshan.backend.Bundles.{DynInst, IssueQueueWakeUpBundle} 14730cfbc0SXuan Hu 15730cfbc0SXuan Husealed trait SchedulerType 16730cfbc0SXuan Hu 17730cfbc0SXuan Hucase class IntScheduler() extends SchedulerType 18730cfbc0SXuan Hucase class MemScheduler() extends SchedulerType 19730cfbc0SXuan Hucase class VfScheduler() extends SchedulerType 20730cfbc0SXuan Hucase class NoScheduler() extends SchedulerType 21730cfbc0SXuan Hu 22730cfbc0SXuan Huclass Scheduler(val params: SchdBlockParams)(implicit p: Parameters) extends LazyModule with HasXSParameter { 23730cfbc0SXuan Hu val numIntStateWrite = backendParams.numIntWb 24730cfbc0SXuan Hu val numVfStateWrite = backendParams.numVfWb 25730cfbc0SXuan Hu 26730cfbc0SXuan Hu val dispatch2Iq = LazyModule(new Dispatch2Iq(params)) 27730cfbc0SXuan Hu val issueQueue = params.issueBlockParams.map(x => LazyModule(new IssueQueue(x).suggestName(x.getIQName))) 28730cfbc0SXuan Hu 29730cfbc0SXuan Hu lazy val module = params.schdType match { 30730cfbc0SXuan Hu case IntScheduler() => new SchedulerArithImp(this)(params, p) 31730cfbc0SXuan Hu case MemScheduler() => new SchedulerMemImp(this)(params, p) 32730cfbc0SXuan Hu case VfScheduler() => new SchedulerArithImp(this)(params, p) 33730cfbc0SXuan Hu case _ => null 34730cfbc0SXuan Hu } 35730cfbc0SXuan Hu} 36730cfbc0SXuan Hu 37730cfbc0SXuan Huclass SchedulerIO()(implicit params: SchdBlockParams, p: Parameters) extends XSBundle { 38*68d13085SXuan Hu // params alias 39*68d13085SXuan Hu private val LoadQueueSize = VirtualLoadQueueSize 40*68d13085SXuan Hu 41730cfbc0SXuan Hu val fromTop = new Bundle { 42730cfbc0SXuan Hu val hartId = Input(UInt(8.W)) 43730cfbc0SXuan Hu } 44730cfbc0SXuan Hu val fromCtrlBlock = new Bundle { 45730cfbc0SXuan Hu val pcVec = Input(Vec(params.numPcReadPort, UInt(VAddrData().dataWidth.W))) 46730cfbc0SXuan Hu val targetVec = Input(Vec(params.numPcReadPort, UInt(VAddrData().dataWidth.W))) 47730cfbc0SXuan Hu val flush = Flipped(ValidIO(new Redirect)) 48730cfbc0SXuan Hu } 49730cfbc0SXuan Hu val fromDispatch = new Bundle { 50730cfbc0SXuan Hu val allocPregs = Vec(RenameWidth, Input(new ResetPregStateReq)) 51730cfbc0SXuan Hu val uops = Vec(params.numUopIn, Flipped(DecoupledIO(new DynInst))) 52730cfbc0SXuan Hu } 53730cfbc0SXuan Hu val intWriteBack = MixedVec(Vec(backendParams.intPregParams.numWrite, 54730cfbc0SXuan Hu new RfWritePortWithConfig(backendParams.intPregParams.dataCfg, backendParams.intPregParams.addrWidth))) 55730cfbc0SXuan Hu val vfWriteBack = MixedVec(Vec(backendParams.vfPregParams.numWrite, 56730cfbc0SXuan Hu new RfWritePortWithConfig(backendParams.vfPregParams.dataCfg, backendParams.vfPregParams.addrWidth))) 57730cfbc0SXuan Hu val toDataPath: MixedVec[MixedVec[DecoupledIO[Bundles.IssueQueueIssueBundle]]] = MixedVec(params.issueBlockParams.map(_.genIssueDecoupledBundle)) 58730cfbc0SXuan Hu val fromDataPath: MixedVec[MixedVec[Bundles.OGRespBundle]] = MixedVec(params.issueBlockParams.map(x => Flipped(x.genOGRespBundle))) 59730cfbc0SXuan Hu 60730cfbc0SXuan Hu val memIO = if (params.isMemSchd) Some(new Bundle { 61730cfbc0SXuan Hu val feedbackIO = Flipped(Vec(params.StaCnt, new MemRSFeedbackIO)) 62730cfbc0SXuan Hu val lsqEnqIO = Flipped(new LsqEnqIO) 63730cfbc0SXuan Hu }) else None 64730cfbc0SXuan Hu val fromMem = if (params.isMemSchd) Some(new Bundle { 65730cfbc0SXuan Hu val stIssuePtr = Input(new SqPtr()) 66730cfbc0SXuan Hu val lcommit = Input(UInt(log2Up(CommitWidth + 1).W)) 67730cfbc0SXuan Hu val scommit = Input(UInt(log2Ceil(EnsbufferWidth + 1).W)) // connected to `memBlock.io.sqDeq` instead of ROB 68730cfbc0SXuan Hu // from lsq 69730cfbc0SXuan Hu val lqCancelCnt = Input(UInt(log2Up(LoadQueueSize + 1).W)) 70730cfbc0SXuan Hu val sqCancelCnt = Input(UInt(log2Up(StoreQueueSize + 1).W)) 71730cfbc0SXuan Hu val memWaitUpdateReq = Flipped(new MemWaitUpdateReq) 72730cfbc0SXuan Hu }) else None 73730cfbc0SXuan Hu val toMem = if (params.isMemSchd) Some(new Bundle { 74730cfbc0SXuan Hu val loadFastMatch = Output(Vec(params.LduCnt, new IssueQueueLoadBundle)) 75730cfbc0SXuan Hu }) else None 76730cfbc0SXuan Hu} 77730cfbc0SXuan Hu 78730cfbc0SXuan Huabstract class SchedulerImpBase(wrapper: Scheduler)(implicit params: SchdBlockParams, p: Parameters) 79730cfbc0SXuan Hu extends LazyModuleImp(wrapper) 80730cfbc0SXuan Hu with HasXSParameter 81730cfbc0SXuan Hu{ 82730cfbc0SXuan Hu val io = IO(new SchedulerIO()) 83730cfbc0SXuan Hu 84730cfbc0SXuan Hu // alias 85730cfbc0SXuan Hu private val schdType = params.schdType 86730cfbc0SXuan Hu private val (numRfRead, numRfWrite) = params.numRfReadWrite.getOrElse((0, 0)) 87730cfbc0SXuan Hu private val numPregs = params.numPregs 88730cfbc0SXuan Hu 89730cfbc0SXuan Hu // Modules 90730cfbc0SXuan Hu val dispatch2Iq: Dispatch2IqImp = wrapper.dispatch2Iq.module 91730cfbc0SXuan Hu val issueQueues: Seq[IssueQueueImp] = wrapper.issueQueue.map(_.module) 92730cfbc0SXuan Hu 93730cfbc0SXuan Hu // BusyTable Modules 94730cfbc0SXuan Hu val intBusyTable = schdType match { 95730cfbc0SXuan Hu case IntScheduler() | MemScheduler() => Some(Module(new BusyTable(dispatch2Iq.numIntStateRead, wrapper.numIntStateWrite))) 96730cfbc0SXuan Hu case _ => None 97730cfbc0SXuan Hu } 98730cfbc0SXuan Hu 99730cfbc0SXuan Hu val vfBusyTable = schdType match { 100730cfbc0SXuan Hu case VfScheduler() | MemScheduler() => Some(Module(new BusyTable(dispatch2Iq.numVfStateRead, wrapper.numVfStateWrite))) 101730cfbc0SXuan Hu case _ => None 102730cfbc0SXuan Hu } 103730cfbc0SXuan Hu 104730cfbc0SXuan Hu dispatch2Iq.io match { case dp2iq => 105730cfbc0SXuan Hu dp2iq.redirect <> io.fromCtrlBlock.flush 106730cfbc0SXuan Hu dp2iq.in <> io.fromDispatch.uops 107730cfbc0SXuan Hu dp2iq.readIntState.foreach(_ <> intBusyTable.get.io.read) 108730cfbc0SXuan Hu dp2iq.readVfState.foreach(_ <> vfBusyTable.get.io.read) 109730cfbc0SXuan Hu } 110730cfbc0SXuan Hu 111730cfbc0SXuan Hu intBusyTable match { 112730cfbc0SXuan Hu case Some(bt) => 113730cfbc0SXuan Hu bt.io.allocPregs.zip(io.fromDispatch.allocPregs).foreach { case (btAllocPregs, dpAllocPregs) => 114730cfbc0SXuan Hu btAllocPregs.valid := dpAllocPregs.isInt 115730cfbc0SXuan Hu btAllocPregs.bits := dpAllocPregs.preg 116730cfbc0SXuan Hu } 117730cfbc0SXuan Hu bt.io.wbPregs.zipWithIndex.foreach { case (wb, i) => 118730cfbc0SXuan Hu wb.valid := io.intWriteBack(i).wen && io.intWriteBack(i).intWen 119730cfbc0SXuan Hu wb.bits := io.intWriteBack(i).addr 120730cfbc0SXuan Hu } 121730cfbc0SXuan Hu case None => 122730cfbc0SXuan Hu } 123730cfbc0SXuan Hu 124730cfbc0SXuan Hu vfBusyTable match { 125730cfbc0SXuan Hu case Some(bt) => 126730cfbc0SXuan Hu bt.io.allocPregs.zip(io.fromDispatch.allocPregs).foreach { case (btAllocPregs, dpAllocPregs) => 127730cfbc0SXuan Hu btAllocPregs.valid := dpAllocPregs.isFp 128730cfbc0SXuan Hu btAllocPregs.bits := dpAllocPregs.preg 129730cfbc0SXuan Hu } 130730cfbc0SXuan Hu bt.io.wbPregs.zipWithIndex.foreach { case (wb, i) => 131730cfbc0SXuan Hu wb.valid := io.vfWriteBack(i).wen && (io.vfWriteBack(i).fpWen || io.vfWriteBack(i).vecWen) 132730cfbc0SXuan Hu wb.bits := io.vfWriteBack(i).addr 133730cfbc0SXuan Hu } 134730cfbc0SXuan Hu case None => 135730cfbc0SXuan Hu } 136730cfbc0SXuan Hu 137730cfbc0SXuan Hu val wakeupFromWBVec = Wire(Vec(params.numWakeupFromWB, ValidIO(new IssueQueueWakeUpBundle(params.pregIdxWidth)))) 138730cfbc0SXuan Hu val writeback = params.schdType match { 139730cfbc0SXuan Hu case IntScheduler() => io.intWriteBack 140730cfbc0SXuan Hu case MemScheduler() => io.intWriteBack ++ io.vfWriteBack 141730cfbc0SXuan Hu case VfScheduler() => io.vfWriteBack 142730cfbc0SXuan Hu case _ => Seq() 143730cfbc0SXuan Hu } 144730cfbc0SXuan Hu wakeupFromWBVec.zip(writeback).foreach { case (sink, source) => 145730cfbc0SXuan Hu sink.valid := source.wen 146730cfbc0SXuan Hu sink.bits.rfWen := source.intWen 147730cfbc0SXuan Hu sink.bits.fpWen := source.fpWen 148730cfbc0SXuan Hu sink.bits.vecWen := source.vecWen 149730cfbc0SXuan Hu sink.bits.pdest := source.addr 150730cfbc0SXuan Hu } 151730cfbc0SXuan Hu 152730cfbc0SXuan Hu io.toDataPath.zipWithIndex.foreach { case (toDp, i) => 153730cfbc0SXuan Hu toDp <> issueQueues(i).io.deq 154730cfbc0SXuan Hu } 155730cfbc0SXuan Hu} 156730cfbc0SXuan Hu 157730cfbc0SXuan Huclass SchedulerArithImp(override val wrapper: Scheduler)(implicit params: SchdBlockParams, p: Parameters) 158730cfbc0SXuan Hu extends SchedulerImpBase(wrapper) 159730cfbc0SXuan Hu with HasXSParameter 160730cfbc0SXuan Hu{ 161730cfbc0SXuan Hu println(s"[SchedulerArithImp] " + 162730cfbc0SXuan Hu s"has intBusyTable: ${intBusyTable.nonEmpty}, " + 163730cfbc0SXuan Hu s"has vfBusyTable: ${vfBusyTable.nonEmpty}") 164730cfbc0SXuan Hu 165730cfbc0SXuan Hu issueQueues.zipWithIndex.foreach { case (iq, i) => 166730cfbc0SXuan Hu iq.io.flush <> io.fromCtrlBlock.flush 167730cfbc0SXuan Hu iq.io.enq <> dispatch2Iq.io.out(i) 168730cfbc0SXuan Hu iq.io.wakeup := wakeupFromWBVec 169730cfbc0SXuan Hu iq.io.deqResp.zipWithIndex.foreach { case (deqResp, j) => 170ea0f92d8Sczw deqResp.valid := iq.io.deq(j).valid && io.toDataPath(i)(j).ready 171730cfbc0SXuan Hu deqResp.bits.success := false.B 172ea0f92d8Sczw deqResp.bits.respType := RSFeedbackType.issueSuccess 173730cfbc0SXuan Hu deqResp.bits.addrOH := iq.io.deq(j).bits.addrOH 174730cfbc0SXuan Hu } 175730cfbc0SXuan Hu iq.io.og0Resp.zipWithIndex.foreach { case (og0Resp, j) => 176730cfbc0SXuan Hu og0Resp.valid := io.fromDataPath(i)(j).og0resp.valid 177730cfbc0SXuan Hu og0Resp.bits.success := false.B // Todo: remove it 178730cfbc0SXuan Hu og0Resp.bits.respType := io.fromDataPath(i)(j).og0resp.bits.respType 179730cfbc0SXuan Hu og0Resp.bits.addrOH := io.fromDataPath(i)(j).og0resp.bits.addrOH 180730cfbc0SXuan Hu } 181730cfbc0SXuan Hu iq.io.og1Resp.zipWithIndex.foreach { case (og1Resp, j) => 182730cfbc0SXuan Hu og1Resp.valid := io.fromDataPath(i)(j).og1resp.valid 183730cfbc0SXuan Hu og1Resp.bits.success := false.B 184730cfbc0SXuan Hu og1Resp.bits.respType := io.fromDataPath(i)(j).og1resp.bits.respType 185730cfbc0SXuan Hu og1Resp.bits.addrOH := io.fromDataPath(i)(j).og1resp.bits.addrOH 186730cfbc0SXuan Hu } 187730cfbc0SXuan Hu } 188730cfbc0SXuan Hu 189730cfbc0SXuan Hu val iqJumpBundleVec: Seq[IssueQueueJumpBundle] = issueQueues.map { 190730cfbc0SXuan Hu case imp: IssueQueueIntImp => imp.io.enqJmp 191730cfbc0SXuan Hu case _ => None 192730cfbc0SXuan Hu }.filter(_.nonEmpty).flatMap(_.get) 193730cfbc0SXuan Hu println(s"[Scheduler] iqJumpBundleVec: ${iqJumpBundleVec}") 194730cfbc0SXuan Hu 195730cfbc0SXuan Hu iqJumpBundleVec.zip(io.fromCtrlBlock.pcVec zip io.fromCtrlBlock.targetVec).foreach { case (iqJmp, (pc, target)) => 196730cfbc0SXuan Hu iqJmp.pc := pc 197730cfbc0SXuan Hu iqJmp.target := target 198730cfbc0SXuan Hu } 199730cfbc0SXuan Hu} 200730cfbc0SXuan Hu 201730cfbc0SXuan Huclass SchedulerMemImp(override val wrapper: Scheduler)(implicit params: SchdBlockParams, p: Parameters) 202730cfbc0SXuan Hu extends SchedulerImpBase(wrapper) 203730cfbc0SXuan Hu with HasXSParameter 204730cfbc0SXuan Hu{ 205730cfbc0SXuan Hu println(s"[SchedulerMemImp] " + 206730cfbc0SXuan Hu s"has intBusyTable: ${intBusyTable.nonEmpty}, " + 207730cfbc0SXuan Hu s"has vfBusyTable: ${vfBusyTable.nonEmpty}") 208730cfbc0SXuan Hu 209730cfbc0SXuan Hu val memAddrIQs = issueQueues.filter(iq => iq.params.StdCnt == 0) 210730cfbc0SXuan Hu val stAddrIQs = issueQueues.filter(iq => iq.params.StaCnt > 0) // included in memAddrIQs 211730cfbc0SXuan Hu val stDataIQs = issueQueues.filter(iq => iq.params.StdCnt > 0) 212730cfbc0SXuan Hu require(memAddrIQs.nonEmpty && stDataIQs.nonEmpty) 213730cfbc0SXuan Hu 214730cfbc0SXuan Hu issueQueues.zipWithIndex.foreach { case (iq, i) => 215730cfbc0SXuan Hu iq.io.deqResp.zipWithIndex.foreach { case (deqResp, j) => 216ea0f92d8Sczw deqResp.valid := iq.io.deq(j).valid && io.toDataPath(i)(j).ready 217730cfbc0SXuan Hu deqResp.bits.success := false.B 218ea0f92d8Sczw deqResp.bits.respType := RSFeedbackType.issueSuccess 219730cfbc0SXuan Hu deqResp.bits.addrOH := iq.io.deq(j).bits.addrOH 220730cfbc0SXuan Hu } 221730cfbc0SXuan Hu iq.io.og0Resp.zipWithIndex.foreach { case (og0Resp, j) => 222730cfbc0SXuan Hu og0Resp.valid := io.fromDataPath(i)(j).og0resp.valid 223730cfbc0SXuan Hu og0Resp.bits.success := false.B // Todo: remove it 224730cfbc0SXuan Hu og0Resp.bits.respType := io.fromDataPath(i)(j).og0resp.bits.respType 225730cfbc0SXuan Hu og0Resp.bits.addrOH := io.fromDataPath(i)(j).og0resp.bits.addrOH 226730cfbc0SXuan Hu } 227730cfbc0SXuan Hu iq.io.og1Resp.zipWithIndex.foreach { case (og1Resp, j) => 228730cfbc0SXuan Hu og1Resp.valid := io.fromDataPath(i)(j).og1resp.valid 229730cfbc0SXuan Hu og1Resp.bits.success := false.B 230730cfbc0SXuan Hu og1Resp.bits.respType := io.fromDataPath(i)(j).og1resp.bits.respType 231730cfbc0SXuan Hu og1Resp.bits.addrOH := io.fromDataPath(i)(j).og1resp.bits.addrOH 232730cfbc0SXuan Hu } 233730cfbc0SXuan Hu } 234730cfbc0SXuan Hu 235730cfbc0SXuan Hu memAddrIQs.zipWithIndex.foreach { case (iq, i) => 236730cfbc0SXuan Hu iq.io.flush <> io.fromCtrlBlock.flush 237730cfbc0SXuan Hu iq.io.enq <> dispatch2Iq.io.out(i) 238730cfbc0SXuan Hu iq.io.wakeup := wakeupFromWBVec 239730cfbc0SXuan Hu } 240730cfbc0SXuan Hu 241730cfbc0SXuan Hu 242730cfbc0SXuan Hu dispatch2Iq.io.out(1).zip(stAddrIQs(0).io.enq).zip(stDataIQs(0).io.enq).foreach{ case((di, staIQ), stdIQ) => 243730cfbc0SXuan Hu val isAllReady = staIQ.ready && stdIQ.ready 244730cfbc0SXuan Hu di.ready := isAllReady 245730cfbc0SXuan Hu staIQ.valid := di.valid && isAllReady 246730cfbc0SXuan Hu stdIQ.valid := di.valid && isAllReady 247730cfbc0SXuan Hu } 248730cfbc0SXuan Hu 249730cfbc0SXuan Hu require(stAddrIQs.size == stDataIQs.size, s"number of store address IQs(${stAddrIQs.size}) " + 250730cfbc0SXuan Hu s"should be equal to number of data IQs(${stDataIQs})") 251730cfbc0SXuan Hu stDataIQs.zip(stAddrIQs).zipWithIndex.foreach { case ((stdIQ, staIQ), i) => 252730cfbc0SXuan Hu stdIQ.io.flush <> io.fromCtrlBlock.flush 253730cfbc0SXuan Hu 254730cfbc0SXuan Hu stdIQ.io.enq.zip(staIQ.io.enq).foreach { case (stdIQEnq, staIQEnq) => 255730cfbc0SXuan Hu stdIQEnq.bits := staIQEnq.bits 256730cfbc0SXuan Hu // Store data reuses store addr src(1) in dispatch2iq 257730cfbc0SXuan Hu // [dispatch2iq] --src*------src*(0)--> [staIQ] 258730cfbc0SXuan Hu // \ 259730cfbc0SXuan Hu // ---src*(1)--> [stdIQ] 260730cfbc0SXuan Hu // Since the src(1) of sta is easier to get, stdIQEnq.bits.src*(0) is assigned to staIQEnq.bits.src*(1) 261730cfbc0SXuan Hu // instead of dispatch2Iq.io.out(x).bits.src*(1) 262730cfbc0SXuan Hu stdIQEnq.bits.srcState(0) := staIQEnq.bits.srcState(1) 263730cfbc0SXuan Hu stdIQEnq.bits.srcType(0) := staIQEnq.bits.srcType(1) 264730cfbc0SXuan Hu stdIQEnq.bits.psrc(0) := staIQEnq.bits.psrc(1) 265730cfbc0SXuan Hu stdIQEnq.bits.sqIdx := staIQEnq.bits.sqIdx 266730cfbc0SXuan Hu } 267730cfbc0SXuan Hu stdIQ.io.wakeup := wakeupFromWBVec 268730cfbc0SXuan Hu } 269730cfbc0SXuan Hu 270730cfbc0SXuan Hu val iqMemBundleVec = stAddrIQs.map { 271730cfbc0SXuan Hu case imp: IssueQueueMemAddrImp => imp.io.memIO 272730cfbc0SXuan Hu case _ => None 273730cfbc0SXuan Hu }.filter(_.nonEmpty).map(_.get) 274730cfbc0SXuan Hu println(s"[Scheduler] iqMemBundleVec: ${iqMemBundleVec}") 275730cfbc0SXuan Hu 276730cfbc0SXuan Hu val lsqEnqCtrl = Module(new LsqEnqCtrl) 277730cfbc0SXuan Hu 278730cfbc0SXuan Hu lsqEnqCtrl.io.redirect <> io.fromCtrlBlock.flush 279730cfbc0SXuan Hu lsqEnqCtrl.io.enq <> dispatch2Iq.io.enqLsqIO.get 280730cfbc0SXuan Hu lsqEnqCtrl.io.lcommit := io.fromMem.get.lcommit 281730cfbc0SXuan Hu lsqEnqCtrl.io.scommit := io.fromMem.get.scommit 282730cfbc0SXuan Hu lsqEnqCtrl.io.lqCancelCnt := io.fromMem.get.lqCancelCnt 283730cfbc0SXuan Hu lsqEnqCtrl.io.sqCancelCnt := io.fromMem.get.sqCancelCnt 284730cfbc0SXuan Hu io.memIO.get.lsqEnqIO <> lsqEnqCtrl.io.enqLsq 285730cfbc0SXuan Hu require(io.memIO.get.feedbackIO.size == iqMemBundleVec.map(_.feedbackIO.size).sum, 286730cfbc0SXuan Hu s"[SchedulerMemImp] io.memIO.feedbackIO.size(${io.memIO.get.feedbackIO.size}) " + 287730cfbc0SXuan Hu s"should be equal to sum of memIQ.io.feedbackIO.size(${iqMemBundleVec.map(_.feedbackIO.size).sum})") 288730cfbc0SXuan Hu 289730cfbc0SXuan Hu val memIQFeedbackIO: Seq[MemRSFeedbackIO] = iqMemBundleVec.flatMap(_.feedbackIO) 290730cfbc0SXuan Hu io.memIO.get.feedbackIO <> memIQFeedbackIO 291730cfbc0SXuan Hu} 292