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