BOOMv3のIssue Unitの構成を読み解いて、自作CPUの性能向上の役に立てる。
いくつかのバリエーションがあるようだが正式に採用されているのはこのIssueUnit
で、内部にはIssueSlot
が複数定義されている。
//------------------------------------------------------------- // Issue Table val slots = for (i <- 0 until numIssueSlots) yield { val slot = Module(new IssueSlot(numWakeupPorts)); slot } val issue_slots = VecInit(slots.map(_.io)) for (i <- 0 until numIssueSlots) { issue_slots(i).wakeup_ports := io.wakeup_ports issue_slots(i).pred_wakeup_port := io.pred_wakeup_port issue_slots(i).spec_ld_wakeup := io.spec_ld_wakeup issue_slots(i).ldspec_miss := io.ld_miss issue_slots(i).brupdate := io.brupdate issue_slots(i).kill := io.flush_pipeline }
IssueSlotの内部には単純なステートマシンが入っていて、これで状態の管理と投機実行ミス時のロールバックなどをやっていると思われる。
/** * Single issue slot. Holds a uop within the issue queue * * @param numWakeupPorts number of wakeup ports */ class IssueSlot(val numWakeupPorts: Int)(implicit p: Parameters) extends BoomModule with IssueUnitConstants { val io = IO(new IssueSlotIO(numWakeupPorts)) // slot invalid? // slot is valid, holding 1 uop // slot is valid, holds 2 uops (like a store) def is_invalid = state === s_invalid def is_valid = state =/= s_invalid val next_state = Wire(UInt()) // the next state of this slot (which might then get moved to a new slot) val next_uopc = Wire(UInt()) // the next uopc of this slot (which might then get moved to a new slot) val next_lrs1_rtype = Wire(UInt()) // the next reg type of this slot (which might then get moved to a new slot) val next_lrs2_rtype = Wire(UInt()) // the next reg type of this slot (which might then get moved to a new slot)
エントリ数は、整数発行・メモリアクセス命令発行でそれぞれ決められているようだ。
issueParams = Seq( IssueParams(issueWidth=1, numEntries=12, iqType=IQT_MEM.litValue, dispatchWidth=2), IssueParams(issueWidth=2, numEntries=20, iqType=IQT_INT.litValue, dispatchWidth=2), IssueParams(issueWidth=1, numEntries=16, iqType=IQT_FP.litValue , dispatchWidth=2)),
デフォルトの発行ユニットの割り当ては、Orderedでポインタに沿って割り当てられているように見える。
issue-unit-age-ordered.scala
val uops = issue_slots.map(s=>s.out_uop) ++ dis_uops.map(s=>s) for (i <- 0 until numIssueSlots) { issue_slots(i).in_uop.valid := false.B issue_slots(i).in_uop.bits := uops(i+1) for (j <- 1 to maxShift by 1) { when (shamts_oh(i+j) === (1 << (j-1)).U) { issue_slots(i).in_uop.valid := will_be_valid(i+j) issue_slots(i).in_uop.bits := uops(i+j) } } issue_slots(i).clear := shamts_oh(i) =/= 0.U }
そうでないバリエーションもある。これはAge Matrixに基づいて、もっと若い空いているエントリに割り当てを行っているように見える。 たぶんこっちの方が性能がいい、というかエントリを効率的に使用できる。
issue-unit-unordered.scala
val entry_wen_oh = VecInit(Seq.fill(numIssueSlots){ Wire(Bits(dispatchWidth.W)) }) for (i <- 0 until numIssueSlots) { issue_slots(i).in_uop.valid := entry_wen_oh(i).orR issue_slots(i).in_uop.bits := Mux1H(entry_wen_oh(i), dis_uops) issue_slots(i).clear := false.B }