blob: 606c8694dbe9927614099d6d620a5a2bf2f60a78 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 vta.util
import chisel3._
import chisel3.util._
import vta.util.config._
//! Queue with SRAM one port or 1r1W
class SyncQueue[T <: Data](
gen: T,
val entries: Int,
pipe: Boolean = false,
flow: Boolean = false)
extends Module() {
val genType = gen
val forceSimpleQueue = false // Force usage of Queue
val io = IO(new QueueIO(genType, entries))
require (!pipe, "-F- Not supported")
require (!flow, "-F- Not supported")
if (forceSimpleQueue) {
val queue = Module(new Queue(genType.asUInt, entries))
io <> queue.io
} else {
val queue = Module(new SyncQueue2PortMem(genType.asUInt, entries))
io <> queue.io
}
}
// Implement a Queue on a single-port memory
// pipe/flow not supported
// combine DoubleQueue with a 3-entry Queue
// Queue is required to buffer DoubleQueue latency
class SyncQueue1PortMem[T <: Data](
gen: T,
val entries: Int,
pipe: Boolean = false,
flow: Boolean = false)
extends Module() {
val genType = gen
val io = IO(new QueueIO(genType, entries))
require (!pipe, "-F- Not supported")
require (!flow, "-F- Not supported")
if (entries < 4 ) {
val queue = Module(new Queue(genType.asUInt, entries))
io <> queue.io
} else {
val queue = Module(new SyncQueue1PortMemImpl(genType.asUInt, entries))
io <> queue.io
}
}
class SyncQueue1PortMemImpl[T <: Data](
gen: T,
val entries: Int,
pipe: Boolean = false,
flow: Boolean = false)
extends Module() {
require (!pipe, "-F- Not supported")
require (!flow, "-F- Not supported")
val genType = gen
val io = IO(new QueueIO(genType, entries))
require (entries > 3, "-F- TODO: small queue implemetation")
val doubleQueue = Module(new DoubleQueue(genType.asUInt, entries))
val buffer = Module(new Queue(genType.asUInt, 3))
val doubleQueueHasValues = doubleQueue.io.count =/= 0.U
val bufferInValid = Mux(doubleQueueHasValues, doubleQueue.io.deq.valid, io.enq.valid)
val bufferInBits = Mux(doubleQueueHasValues, doubleQueue.io.deq.bits, io.enq.bits)
buffer.io.enq.valid := bufferInValid
buffer.io.enq.bits := bufferInBits
io.deq <> buffer.io.deq
doubleQueue.io.enq.bits := io.enq.bits
doubleQueue.io.enq.valid := io.enq.fire && (!buffer.io.enq.ready || doubleQueueHasValues)
doubleQueue.io.deq.ready := buffer.io.enq.ready
val count = Wire(UInt(log2Up(entries + 1).W))
val countNext = RegEnable(
next = count,
init = 0.U,
enable = io.enq.fire || io.deq.fire)
when (io.enq.fire && !io.deq.fire) {
assert(countNext < entries.U)
count := countNext + 1.U
}.elsewhen (!io.enq.fire && io.deq.fire) {
assert(countNext > 0.U)
count := countNext - 1.U
}.otherwise {
count := countNext
}
io.count := countNext
io.enq.ready := countNext =/= entries.U
io.deq.valid := countNext =/= 0.U
assert(io.deq.valid === buffer.io.deq.valid)
assert(io.enq.ready === buffer.io.enq.ready || doubleQueue.io.enq.ready)
}
class SyncQueue2PortMem[T <: Data](
gen: T,
val entries: Int,
pipe: Boolean = false,
flow: Boolean = false)
extends Module() {
val genType = gen
val io = IO(new QueueIO(genType, entries))
require (!pipe, "-F- Not supported")
require (!flow, "-F- Not supported")
if (entries < 4 ) {
val queue = Module(new Queue(genType.asUInt, entries))
io <> queue.io
} else {
val queue = Module(new SyncQueue2PortMemImpl(genType.asUInt, entries))
io <> queue.io
}
}
class SyncQueue2PortMemImpl[T <: Data](
gen: T,
val entries: Int,
pipe: Boolean = false,
flow: Boolean = false)
extends Module() {
require (!pipe, "-F- Not supported")
require (!flow, "-F- Not supported")
val genType = gen
val io = IO(new QueueIO(genType, entries))
require (entries > 3, "-F- TODO: small queue implemetation")
val memoryQueue = Module(new OneCycleQueue(genType.asUInt, entries, ""))
val buffer = Module(new Queue(genType.asUInt, 3))
val memoryQueueHasValues = memoryQueue.io.count =/= 0.U
val bufferInValid = Mux(memoryQueueHasValues, memoryQueue.io.deq.valid, io.enq.valid)
val bufferInBits = Mux(memoryQueueHasValues, memoryQueue.io.deq.bits, io.enq.bits)
buffer.io.enq.valid := bufferInValid
buffer.io.enq.bits := bufferInBits
io.deq <> buffer.io.deq
memoryQueue.io.enq.bits := io.enq.bits
memoryQueue.io.enq.valid := io.enq.fire && (!buffer.io.enq.ready || memoryQueueHasValues)
memoryQueue.io.deq.ready := buffer.io.enq.ready
val count = Wire(UInt(log2Up(entries + 1).W))
val countNext = RegEnable(
next = count,
init = 0.U,
enable = io.enq.fire || io.deq.fire)
when (io.enq.fire && !io.deq.fire) {
assert(countNext < entries.U)
count := countNext + 1.U
}.elsewhen (!io.enq.fire && io.deq.fire) {
assert(countNext > 0.U)
count := countNext - 1.U
}.otherwise {
count := countNext
}
io.count := countNext
io.enq.ready := countNext =/= entries.U
io.deq.valid := countNext =/= 0.U
assert(io.deq.valid === buffer.io.deq.valid)
assert(io.enq.ready === buffer.io.enq.ready || memoryQueue.io.enq.ready)
}
//combines two TwoCycle one-port memory queues into a queue
// with a latency 3
class DoubleQueue[T <: Data](
gen: T,
val entries: Int)
extends Module() {
val genType = gen
val io = IO(new QueueIO(genType, entries))
require(entries > 1, "Zero size not tested")
val entriesRam0 = entries/2
val entriesRam1 = entries - entriesRam0
val queue0 = Module(new TwoCycleQueue(genType.asUInt, entriesRam0, "q0"))
val queue1 = Module(new TwoCycleQueue(genType.asUInt, entriesRam1, "q1"))
val enqRR = Wire(Bool())
enqRR := RegEnable(
next = ~enqRR,
init = 1.U,
enable = io.enq.fire)
val deqRR = Wire(Bool())
deqRR := RegEnable(
next = ~deqRR,
init = 1.U,
enable = io.deq.fire)
val do_enq0 = WireInit(io.enq.fire && enqRR)
val do_enq1 = WireInit(io.enq.fire && ~enqRR)
val deq0 = WireInit(io.deq.fire && deqRR)
val deq1 = WireInit(io.deq.fire && ~deqRR)
val do_deq0_next = RegNext(deq0 && do_enq0)
val do_deq1_next = RegNext(deq1 && do_enq1)
val do_deq0 = (deq0 && ~do_enq0) || do_deq0_next
val do_deq1 = (deq1 && ~do_enq1) || do_deq1_next
val do_deq = WireInit(io.deq.fire)
val full = !queue0.io.enq.ready && !queue1.io.enq.ready
val empty = !queue0.io.deq.valid && !queue1.io.deq.valid
queue0.io.enq.bits := io.enq.bits
queue0.io.enq.valid := do_enq0
queue1.io.enq.bits := io.enq.bits
queue1.io.enq.valid := do_enq1
queue0.io.deq.ready := do_deq0
queue1.io.deq.ready := do_deq1
io.deq.valid := !empty
io.enq.ready := !full
when(do_deq0) {
assert(queue0.io.deq.valid, "-F- Deq empty queue 0")
}
when(do_deq1) {
assert(queue1.io.deq.valid, "-F- Deq empty queue 1")
}
when(do_enq0) {
assert(queue0.io.enq.ready, "-F- Enq full queue 0")
}
when(do_enq1) {
assert(queue1.io.enq.ready, "-F- Enq full queue 1")
}
io.deq.bits := Mux(deqRR, queue0.io.deq.bits, queue1.io.deq.bits)
io.count := queue0.io.count +& queue1.io.count
}
// one-port memory queue
// enq and deq should not overlap
// two subsequent enq should be cycle separated
// two subsequent deq can be next cycle
class TwoCycleQueue[T <: Data](
gen: T,
val entries: Int,
val qname: String)
extends Module() {
val genType = gen
val io = IO(new QueueIO(genType, entries))
val ram0 = Module(new OnePortMem(genType.asUInt, entries, qname))
val enq_ptr = Counter(entries)
val deq_ptr = Counter(entries)
val maybe_full = RegInit(false.B)
val ptr_match = enq_ptr.value === deq_ptr.value
val empty = ptr_match && !maybe_full
val full = ptr_match && maybe_full
val do_enq = WireInit(io.enq.fire)
val do_deq = WireInit(io.deq.fire)
// check protocol
val enq_next = RegNext(do_enq)
assert(!(enq_next && do_enq), "-F- Expecting two cycle behavior on enq")
assert(!do_enq || !do_deq, "-F- No simultaneous R/W")
when(do_deq) {
deq_ptr.inc()
}
when(do_enq =/= do_deq) {
maybe_full := do_enq
}
val firstRead = RegEnable(next = do_enq && io.count === 0.U, init = false.B, enable = true.B)
io.deq.valid := !empty && !firstRead
io.enq.ready := !full
when (do_enq) {
enq_ptr.inc()
}
val memAddr = Wire(chiselTypeOf(enq_ptr.value))
memAddr := enq_ptr.value
when(!do_enq) {
when(firstRead) {// output the 1st written data
memAddr := deq_ptr.value
}.elsewhen (do_deq) {
val wrap = deq_ptr.value === (entries - 1).U
when (wrap) {
memAddr := 0.U // initiate read of the next entry
}.otherwise {
memAddr := (deq_ptr.value + 1.U) // initiate read of the next entry
}
}.otherwise {
memAddr := deq_ptr.value
}
}
ram0.io.wr_en := do_enq
ram0.io.wr_data := io.enq.bits.asUInt
ram0.io.ch_en := do_deq || firstRead || do_enq
io.deq.bits := ram0.io.rd_data
ram0.io.addr := memAddr
val ptr_diff = enq_ptr.value - deq_ptr.value
if (isPow2(entries)) {
io.count := Mux(maybe_full && ptr_match, entries.U, 0.U) | ptr_diff
} else {
io.count := Mux(
ptr_match,
Mux(
maybe_full,
entries.asUInt, 0.U),
Mux(
deq_ptr.value > enq_ptr.value,
entries.asUInt + ptr_diff, ptr_diff))
}
}
class OneCycleQueue[T <: Data](
gen: T,
val entries: Int,
val qname: String)
extends Module() {
val genType = gen
val io = IO(new QueueIO(genType, entries))
val ram0 = Module(new TwoPortMem(genType.asUInt, entries, qname))
val enq_ptr = Counter(entries)
val deq_ptr = Counter(entries)
val maybe_full = RegInit(false.B)
val ptr_match = enq_ptr.value === deq_ptr.value
val empty = ptr_match && !maybe_full
val full = ptr_match && maybe_full
val do_enq = WireInit(io.enq.fire)
val do_deq = WireInit(io.deq.fire)
when(do_deq) {
deq_ptr.inc()
}
when(do_enq =/= do_deq) {
maybe_full := do_enq
}
when (do_enq) {
enq_ptr.inc()
}
val firstRead = RegEnable(next = do_enq && io.count === 0.U, init = false.B, enable = true.B)
io.deq.valid := !empty && !firstRead
io.enq.ready := !full
assert(!firstRead || !do_deq, "-F- Cannot have deq with first read as queue output is not valid yet")
val rdAddr = Wire(chiselTypeOf(enq_ptr.value))
when(firstRead) {// output the 1st written data
rdAddr := deq_ptr.value
}.elsewhen (do_deq) {
val wrap = deq_ptr.value === (entries - 1).U
when (wrap) {
rdAddr := 0.U // initiate read of the next entry
}.otherwise {
rdAddr := (deq_ptr.value + 1.U) // initiate read of the next entry
}
}.otherwise {
rdAddr := deq_ptr.value
}
ram0.io.wr_en := do_enq
ram0.io.wr_data := io.enq.bits.asUInt
ram0.io.wr_addr := enq_ptr.value
ram0.io.rd_en := do_deq || firstRead
ram0.io.rd_addr := rdAddr
io.deq.bits := ram0.io.rd_data
val ptr_diff = enq_ptr.value - deq_ptr.value
if (isPow2(entries)) {
io.count := Mux(maybe_full && ptr_match, entries.U, 0.U) | ptr_diff
} else {
io.count := Mux(
ptr_match,
Mux(
maybe_full,
entries.asUInt, 0.U),
Mux(
deq_ptr.value > enq_ptr.value,
entries.asUInt + ptr_diff, ptr_diff))
}
}
// one-port memory implementation
class MemIO[T <: Data](gen: T, entries: Int) extends Bundle
{
val wr_en = Input(Bool())
val wr_data = Input(gen)
val ch_en = Input(Bool())
val rd_data = Output(gen)
val addr = Input(UInt(16.W)) // i dont care
}
class OnePortMem[T <: Data](
gen: T,
val entries: Int,
val qname: String)
extends Module() {
val genType = gen
val io = IO(new MemIO(genType, entries))
val mem = SyncReadMem(entries, genType.asUInt)
// detected as one-port sync mem interface
io.rd_data := DontCare
when(io.ch_en) {
val rdwrPort = mem(io.addr)
when (io.wr_en) { rdwrPort := io.wr_data }
.otherwise { io.rd_data := rdwrPort }
}
}
// two-port memory implementation
class MemIO2P[T <: Data](gen: T, entries: Int) extends Bundle
{
val wr_en = Input(Bool())
val wr_addr = Input(UInt(16.W)) // i dont care
val wr_data = Input(gen)
val rd_en = Input(Bool())
val rd_addr = Input(UInt(16.W)) // i dont care
val rd_data = Output(gen)
}
class TwoPortMem[T <: Data](
gen: T,
val entries: Int,
val qname: String)
extends Module() {
val genType = gen
val io = IO(new MemIO2P(genType, entries))
val mem = SyncReadMem(entries, genType.asUInt)
when (io.wr_en ) {
mem.write(io.wr_addr, io.wr_data.asUInt)
}
io.rd_data := DontCare
when (io.rd_en) {
io.rd_data := mem.read(io.rd_addr, io.rd_en)
}
}