blob: f0a8e57f957ecf1908affb50618c6a92f6a0e0c6 [file] [log] [blame]
// Copyright 2025 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 bus
import chisel3._
import chisel3.util._
import kelvin.Parameters
/**
* Axi2TLUL: A Chisel module that serves as a bridge between an AXI4 master
* and a TileLink-UL slave.
*
* This module translates AXI read and write transactions into TileLink Get and Put
* operations, respectively. It uses a dataflow approach with queues to manage
* the protocol conversion.
*
* Note: This implementation handles single-beat AXI transactions (len=0). AXI
* bursting would require more complex logic to be added.
*
* @param p The Kelvin parameters.
*/
class Axi2TLUL[A_USER <: Data, D_USER <: Data](p: Parameters, userAGen: () => A_USER, userDGen: () => D_USER) extends Module {
val tlul_p = new TLULParameters(p)
val io = IO(new Bundle {
val axi = Flipped(new AxiMasterIO(p.axi2AddrBits, p.axi2DataBits, p.axi2IdBits))
val tl_a = Decoupled(new TileLink_A_ChannelBase(tlul_p, userAGen)) // TileLink Output
val tl_d = Flipped(Decoupled(new TileLink_D_ChannelBase(tlul_p, userDGen))) // TileLink Input
})
assert(io.axi.read.addr.bits.len === 0.U || !io.axi.read.addr.valid, "Axi2TLUL: AXI read bursts not supported")
assert(io.axi.write.addr.bits.len === 0.U || !io.axi.write.addr.valid, "Axi2TLUL: AXI write bursts not supported")
val read_addr_q = Queue(io.axi.read.addr, entries = 2)
val write_addr_q = Queue(io.axi.write.addr, entries = 2)
val write_data_q = Queue(io.axi.write.data, entries = 2)
// Prioritize reads over writes.
val is_write = write_addr_q.valid && write_data_q.valid
val is_read = read_addr_q.valid
io.tl_a.valid := is_read || is_write
read_addr_q.ready := false.B
write_addr_q.ready := false.B
write_data_q.ready := false.B
read_addr_q.ready := Mux(is_read, io.tl_a.ready, false.B)
write_addr_q.ready := !is_read && io.tl_a.ready
write_data_q.ready := !is_read && io.tl_a.ready
io.tl_a.bits.opcode := Mux(is_read, TLULOpcodesA.Get.asUInt, TLULOpcodesA.PutFullData.asUInt)
io.tl_a.bits.param := 0.U
io.tl_a.bits.address := Mux(is_read, read_addr_q.bits.addr, write_addr_q.bits.addr)
io.tl_a.bits.source := Mux(is_read, read_addr_q.bits.id, write_addr_q.bits.id)
io.tl_a.bits.size := Mux(is_read, read_addr_q.bits.size, write_addr_q.bits.size)
io.tl_a.bits.mask := Mux(is_read, 0.U, write_data_q.bits.strb)
io.tl_a.bits.data := Mux(is_read, 0.U, write_data_q.bits.data)
io.tl_a.bits.user := 0.U.asTypeOf(io.tl_a.bits.user)
val d_is_write = io.tl_d.bits.opcode === TLULOpcodesD.AccessAck.asUInt
val d_is_read = io.tl_d.bits.opcode === TLULOpcodesD.AccessAckData.asUInt
io.axi.write.resp.valid := io.tl_d.valid && d_is_write
io.axi.write.resp.bits.id := io.tl_d.bits.source
io.axi.write.resp.bits.resp := 0.U
io.axi.read.data.valid := io.tl_d.valid && d_is_read
io.axi.read.data.bits.id := io.tl_d.bits.source
io.axi.read.data.bits.data := io.tl_d.bits.data
io.axi.read.data.bits.resp := Mux(io.tl_d.bits.error, "b10".U, "b00".U)
io.axi.read.data.bits.last := true.B
io.tl_d.ready := Mux(d_is_read, io.axi.read.data.ready, io.axi.write.resp.ready)
}
import _root_.circt.stage.{ChiselStage,FirtoolOption}
import chisel3.stage.ChiselGeneratorAnnotation
import scala.annotation.nowarn
@nowarn
object EmitAxi2TLUL extends App {
val p = Parameters()
(new ChiselStage).execute(
Array("--target", "systemverilog") ++ args,
Seq(ChiselGeneratorAnnotation(() => new Axi2TLUL(p, () => new NoUser, () => new NoUser))) ++ Seq(FirtoolOption("-enable-layers=Verification"))
)
}