blob: f0e8fbf3d2e5c9b4df2a83ca48f07428214b55e1 [file]
// 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 kelvin
import chisel3._
import chisel3.util._
import bus._
import common._
class RWAxiAddress(p: Parameters) extends Bundle {
val addr = new AxiAddress(p.axi2AddrBits, p.axi2DataBits, p.axi2IdBits)
val write = Bool()
}
class AxiSlave(p: Parameters) extends Module {
val io = IO(new Bundle{
val axi = Flipped(new AxiMasterIO(p.axi2AddrBits, p.axi2DataBits, p.axi2IdBits))
val fabric = new FabricIO(p)
// Output indicating that a transaction is in progress
val txnInProgress = Output(Bool())
// Input indicating that the peripheral is busy -- do not accept AXI transactions
val periBusy = Input(Bool())
})
// Arbitrate Read/Write channels
val addrArbiter = Module(new KelvinRRArbiter(
new AxiAddress(p.axi2AddrBits, p.axi2DataBits, p.axi2IdBits), 2))
addrArbiter.io.in(0) <> Queue(io.axi.read.addr, 2)
addrArbiter.io.in(1) <> Queue(io.axi.write.addr, 2)
val axiAddr = Wire(Decoupled(new RWAxiAddress(p)))
axiAddr.valid := addrArbiter.io.out.valid
axiAddr.bits.addr := addrArbiter.io.out.bits
axiAddr.bits.write := (addrArbiter.io.chosen === 1.U)
addrArbiter.io.out.ready := axiAddr.ready
val axiAddrCmd = Queue(axiAddr, 1)
val writeActive = axiAddrCmd.valid && axiAddrCmd.bits.write
val readActive = axiAddrCmd.valid && !axiAddrCmd.bits.write
// This register tracks the address over multiple beats in a burst
val cmdAddr = RegInit(0.U(p.axi2AddrBits.W))
// Write
val writeData = Queue(io.axi.write.data, 2)
val writeResponse = Wire(Decoupled(new AxiWriteResponse(p.axi2IdBits)))
io.axi.write.resp <> Queue(writeResponse, 2)
/// Set fabric write command. Note that even if valid is asserted,
/// the write won't occur if io.periBusy is high
val maybeWriteData = writeActive && // Write must be active
writeData.valid && // Write data packet must be valid
writeResponse.ready // Make sure resp can be sent
io.fabric.writeDataAddr.valid := maybeWriteData
io.fabric.writeDataAddr.bits := cmdAddr
io.fabric.writeDataBits := writeData.bits.data
io.fabric.writeDataStrb := writeData.bits.strb
/// Check if ok to write
writeData.ready := maybeWriteData && !io.periBusy
/// Enqueue write response
writeResponse.valid := writeData.fire && writeData.bits.last
writeResponse.bits.id := axiAddrCmd.bits.addr.id
writeResponse.bits.resp := Mux(io.fabric.writeResp,
AxiResponseType.OKAY.asUInt, AxiResponseType.SLVERR.asUInt)
// Read
val readDataQueueSize = 2
val readDataQueue = Module(new Queue(
new AxiReadData(p.axi2DataBits, p.axi2IdBits), readDataQueueSize))
val readData = readDataQueue.io.enq
io.axi.read.data <> readDataQueue.io.deq
val readIssued = RegInit(false.B) // Tracks if a read was issued last cycle
val readsIssued = RegInit(0.U((axiAddrCmd.bits.addr.len.getWidth + 1).W)) // Tracks number of readData issued
/// Check if we can issue a read
val nextQueueCount = readDataQueue.io.count +& readIssued -&
readDataQueue.io.deq.fire
val maybeIssueRead = readActive &&
((readDataQueueSize.U - nextQueueCount) >= 1.U) &&
(readsIssued <= axiAddrCmd.bits.addr.len) // Don't issue if last transaction
val issueRead = maybeIssueRead && !io.periBusy
readIssued := issueRead
readsIssued := Mux(axiAddr.fire, 0.U, readsIssued + issueRead)
/// Forward read command to SRAM
/// Note: maybeIssueRead is used here instead of issue read so the downstream
/// arbiter can route the correct periBusy signal back. If periBusy is raised
/// the read operation is not conducted.
io.fabric.readDataAddr.valid := maybeIssueRead
io.fabric.readDataAddr.bits := cmdAddr
/// Response from SRAM, cycle later
readData.valid := readIssued
readData.bits.data := io.fabric.readData.bits
readData.bits.id := axiAddrCmd.bits.addr.id
readData.bits.resp := Mux(io.fabric.readData.valid,
AxiResponseType.OKAY.asUInt, AxiResponseType.SLVERR.asUInt)
readData.bits.last := (readsIssued === (axiAddrCmd.bits.addr.len +& 1.U))
/// Ensure read is enqueued
assert(!readIssued || readDataQueue.io.enq.ready)
// Update address between beats
val baseAddrMask = VecInit((0 until axiAddrCmd.bits.addr.addr.getWidth).map(
x => !(x.U < axiAddrCmd.bits.addr.size)))
val cmdAddrBase = axiAddrCmd.bits.addr.addr & baseAddrMask.asUInt
val (burst, burstValid) = AxiBurstType.safe(axiAddrCmd.bits.addr.burst)
val validBurst = axiAddrCmd.valid && burstValid
val addrNext = MuxCase(cmdAddr, Seq(
(validBurst && (burst === AxiBurstType.FIXED)) -> cmdAddr,
(validBurst && (burst === AxiBurstType.INCR)) ->
(cmdAddr + (1.U << axiAddrCmd.bits.addr.size)),
(validBurst && (burst === AxiBurstType.WRAP)) -> {
val newAddr = cmdAddr + (1.U << axiAddrCmd.bits.addr.size)
val newAddrWrapped = Mux(
newAddr >= cmdAddrBase + (p.axi2DataBits / 8).U,
cmdAddrBase, newAddr)
newAddrWrapped(31,0)
}
))
cmdAddr := MuxCase(cmdAddr, Seq(
// New command
axiAddr.fire -> axiAddr.bits.addr.addr,
// Updates
(writeActive && io.fabric.writeDataAddr.valid && !io.periBusy) -> addrNext,
(readActive && io.fabric.readDataAddr.valid && !io.periBusy) -> addrNext,
))
// Move to the next command when done
axiAddrCmd.ready := MuxCase(false.B, Seq(
writeActive -> writeResponse.fire,
readActive -> (readData.fire && readData.bits.last),
))
io.txnInProgress := axiAddrCmd.valid
}