| // Copyright 2024 Google LLC | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package common | |
| import chisel3._ | |
| import chisel3.util._ | |
| /** An interface which encapsulates up-to n DecoupledIO interfaces. The | |
| * convention is that the first nValid interfaces are considered valid. | |
| */ | |
| class DecoupledVectorIO[T <: Data](gen: T, n: Int) extends Bundle { | |
| val nReady = Input(UInt(log2Up(n+1).W)) | |
| val nValid = Output(UInt(log2Up(n+1).W)) | |
| val bits = Output(Vec(n, gen)) | |
| } | |
| object DecoupledVectorIO { | |
| def apply[T <: Data](gen: T, n: Int): DecoupledVectorIO[T] = new DecoupledVectorIO(gen, n) | |
| } | |
| /** Returns the one-hot vec with only the least significant valid bit set, or | |
| * all false if all bits are false. | |
| * @param in A sequences of booleans | |
| * @return The priority selected boolean. | |
| */ | |
| object PrioritySelect { | |
| def apply(in: Seq[Bool]): Vec[Bool] = { | |
| val seenValid = in.scan(false.B)(_ || _).take(in.length) | |
| VecInit((in zip seenValid).map({case (x, seen) => x && !seen})) | |
| } | |
| } | |
| /** A FIFO queue that enqueues/dequeues multiple elements a cycle, but also | |
| * contains a bypass interface that allows dequeuing elements out of order and | |
| * optional flush. | |
| * The general structure of this module looks as follows: | |
| * | |
| * Out | |
| * ^ | |
| * | | |
| * +-----------------------------+ | |
| * | | | | |
| * | | | | |
| * | | | | |
| * | | | | |
| * | | New Buffer | | |
| * | | | ^ | | |
| * | | V | | | |
| * | Buffer | | | |
| * | | | | | |
| * | +------> Align -----|-> feedOut | |
| * | ^ | | |
| * | | | | |
| * feedIn -|-------------------+ | | |
| * | | | |
| * +-----------------------------+ | |
| * | |
| * We can compose InstructionBufferSlice's together to create a bigger window | |
| * to extract elements from. This looks like: | |
| * | |
| * Out | |
| * ^ | |
| * | | |
| * +--------------+------------+ | |
| * | Concat | | |
| * +--------------+------------+ | |
| * ^ ^ | |
| * | | | |
| * +------------------------+ +------------------------+ | |
| * feedIn -| InstructionBufferSlice | -> | InstructionBufferSlice | -> feedOut | |
| * +------------------------+ +------------------------+ | |
| */ | |
| class InstructionBufferSlice[T <: Data]( | |
| val gen: T, val n: Int, val hasFlush: Boolean = false) extends Module { | |
| val io = IO(new Bundle { | |
| val feedIn = Flipped(DecoupledVectorIO(gen, n)) | |
| val feedOut = DecoupledVectorIO(gen, n) | |
| val out = Vec(n, Decoupled(gen)) | |
| val flush = if (hasFlush) { Some(Input(Bool())) } else { None } | |
| }) | |
| val buffer = RegInit(VecInit.fill(n)(MakeValid(false.B, 0.U.asTypeOf(gen)))) | |
| // Withdraw elements from buffer | |
| val remainderValid = Wire(Vec(n, Bool())) | |
| for (i <- 0 until n) { | |
| if (hasFlush) { | |
| io.out(i).valid := buffer(i).valid && !io.flush.get | |
| } else { | |
| io.out(i).valid := buffer(i).valid | |
| } | |
| io.out(i).bits := buffer(i).bits | |
| remainderValid(i) := buffer(i).valid && !io.out(i).ready | |
| } | |
| // Sort remainder buffer | |
| val sortedRemainderBuffer = Wire(Vec(n, gen)) | |
| var prevValids = remainderValid | |
| for (i <- 0 until n) { | |
| val selectHot = PrioritySelect(prevValids) | |
| sortedRemainderBuffer(i) := MuxCase( | |
| 0.U.asTypeOf(gen), (0 until n).map(x => selectHot(x) -> buffer(x).bits)) | |
| prevValids = VecInit((prevValids zip selectHot).map( | |
| {case (p, s) => p && !s})) | |
| } | |
| // Request buffers from feedIn | |
| val nRemaining = PopCount(remainderValid) | |
| val nRequesting = io.feedOut.nReady +& n.U - nRemaining | |
| val satRequesting = Mux(nRequesting > n.U, n.U, nRequesting) | |
| io.feedIn.nReady := satRequesting | |
| assert(io.feedIn.nValid <= satRequesting) | |
| // Sort available elements | |
| val nAvailable = io.feedIn.nValid +& nRemaining | |
| val available = Wire(Vec(2*n, gen)) | |
| for (i <- 0 until 2*n) { | |
| available(i) := MuxCase( | |
| /* i.U < nRemaining */ sortedRemainderBuffer(i.U), | |
| Array( | |
| (i.U >= nAvailable) -> 0.U.asTypeOf(gen), | |
| (i.U >= nRemaining) -> io.feedIn.bits(i.U - nRemaining), | |
| )) | |
| } | |
| // Populate feedOut | |
| val nFeedOut = Mux( | |
| nAvailable > io.feedOut.nReady, io.feedOut.nReady, nAvailable) | |
| io.feedOut.nValid := nFeedOut | |
| for (i <- 0 until n) { | |
| io.feedOut.bits(i) := Mux(i.U < nFeedOut, available(i), 0.U.asTypeOf(gen)) | |
| } | |
| // Populate new buffer | |
| val nextBuffer = Wire(Vec(n, Valid(gen))) | |
| for (i <- 0 until n) { | |
| val idx = i.U +& nFeedOut | |
| val valid = idx < nAvailable | |
| nextBuffer(i).valid := valid | |
| nextBuffer(i).bits := Mux(valid, available(idx), 0.U.asTypeOf(gen)) | |
| } | |
| if (hasFlush) { | |
| buffer := Mux(io.flush.get, | |
| VecInit.fill(n)(MakeValid(false.B, 0.U.asTypeOf(gen))), | |
| nextBuffer) | |
| } else { | |
| buffer := nextBuffer | |
| } | |
| } | |
| /** A data structure where elements are inserted in order, but can be removed | |
| * in an arbitrary order. This can be used to implement the instruction window | |
| * of a processor. | |
| * | |
| * There are a few of notable limitations for this module: | |
| * 1) It is expected that up to "n" elements can be removed each cycle. | |
| * Downstream consumers of the out interface should be sure to only set up | |
| * to "n" ready signals. | |
| * 2) The window parameter must be a multiple of n. | |
| */ | |
| class InstructionBuffer[T <: Data](val gen: T, | |
| val n: Int, | |
| val window: Int, | |
| val hasFlush: Boolean = false) extends Module { | |
| val slices: Int = window / n | |
| assert(window % n == 0) | |
| assert(slices > 0) | |
| val io = IO(new Bundle { | |
| val feedIn = Flipped(DecoupledVectorIO(gen, n)) | |
| val out = Vec(window, Decoupled(gen)) | |
| val flush = if (hasFlush) { Some(Input(Bool())) } else { None } | |
| }) | |
| // Compose InstructionBufferSlices | |
| var feedIn = io.feedIn | |
| var outputs: Seq[DecoupledIO[T]] = Seq() | |
| for (s <- 0 until slices) { | |
| val slice = Module(new InstructionBufferSlice(gen, n, hasFlush)) | |
| if (hasFlush) { | |
| slice.io.flush.get := io.flush.get | |
| } | |
| slice.io.feedIn <> feedIn | |
| feedIn = slice.io.feedOut | |
| outputs = slice.io.out ++ outputs | |
| } | |
| // Terminate | |
| feedIn.nReady := 0.U | |
| io.out <> VecInit(outputs) | |
| } |