feat(hdl): Add Chisel TL-UL <-> AXI bridges and CoreTlul Change-Id: I2ffc39a7d559eb64074c214c18e5f46e30f84aa1
diff --git a/hdl/chisel/src/bus/Axi2TLUL.scala b/hdl/chisel/src/bus/Axi2TLUL.scala new file mode 100644 index 0000000..f0a8e57 --- /dev/null +++ b/hdl/chisel/src/bus/Axi2TLUL.scala
@@ -0,0 +1,99 @@ +// 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")) + ) +}
diff --git a/hdl/chisel/src/bus/BUILD b/hdl/chisel/src/bus/BUILD index cbc3eff..9a6f1e3 100644 --- a/hdl/chisel/src/bus/BUILD +++ b/hdl/chisel/src/bus/BUILD
@@ -15,7 +15,10 @@ load( "@kelvin_hw//rules:chisel.bzl", "chisel_library", + "chisel_cc_library", ) +load("@kelvin_hw//rules:coco_tb.bzl", "cocotb_test_suite", "verilator_cocotb_model") +load("@kelvin_hw//third_party/python:requirements.bzl", "requirement") package(default_visibility = ["//visibility:public"]) @@ -23,11 +26,98 @@ name = "bus", srcs = [ "Axi.scala", + "TLUL2Axi.scala", + "Axi2TLUL.scala", "KelvinMemIO.scala", "KelvinToTlul.scala", "TileLinkUL.scala", ], deps = [ "//hdl/chisel/src/kelvin:kelvin_params", + "//hdl/chisel/src/common", ], ) + +chisel_cc_library( + name = "tlul2axi_cc_library", + chisel_lib = ":bus", + emit_class = "bus.EmitTLUL2Axi", + module_name = "TLUL2Axi", +) + +verilator_cocotb_model( + name = "tlul2axi_model", + hdl_toplevel = "TLUL2Axi", + verilog_source = "//hdl/chisel/src/bus:TLUL2Axi.sv", + cflags = [], + trace = True, +) + +# BEGIN_TESTCASES_FOR_tlul2axi_cocotb_test +TLUL2AXI_TESTCASES = [ + "test_put_request", + "test_get_request", + "test_backpressure", + "test_put_then_get", +] +# END_TESTCASES_FOR_tlul2axi_cocotb_test + +cocotb_test_suite( + name = "tlul2axi_cocotb_test", + simulators = ["verilator", "vcs"], + testcases = TLUL2AXI_TESTCASES, + testcases_vname = "TLUL2AXI_TESTCASES", + tests_kwargs = { + "hdl_toplevel": "TLUL2Axi", + "test_module": ["tlul2axi_cocotb_test.py"], + "size": "large", + "deps": [ + "@bazel_tools//tools/python/runfiles", + requirement("tqdm"), + ], + "waves": True, + }, + vcs_verilog_sources = ["//hdl/chisel/src/bus:tlul2axi_cc_library_verilog"], + verilator_model = ":tlul2axi_model", +) + +chisel_cc_library( + name = "axi2tlul_cc_library", + chisel_lib = ":bus", + emit_class = "bus.EmitAxi2TLUL", + module_name = "Axi2TLUL", +) + +verilator_cocotb_model( + name = "axi2tlul_model", + hdl_toplevel = "Axi2TLUL", + verilog_source = "//hdl/chisel/src/bus:Axi2TLUL.sv", + cflags = [], + trace = True, +) + +# BEGIN_TESTCASES_FOR_axi2tlul_cocotb_test +AXI2TLUL_TESTCASES = [ + "test_write_request", + "test_read_request", +] +# END_TESTCASES_FOR_axi2tlul_cocotb_test + +cocotb_test_suite( + name = "axi2tlul_cocotb_test", + simulators = ["verilator", "vcs"], + testcases = AXI2TLUL_TESTCASES, + testcases_vname = "AXI2TLUL_TESTCASES", + tests_kwargs = { + "hdl_toplevel": "Axi2TLUL", + "test_module": ["axi2tlul_cocotb_test.py"], + "size": "large", + "deps": [ + "@bazel_tools//tools/python/runfiles", + requirement("tqdm"), + ], + "waves": True, + }, + vcs_verilog_sources = ["//hdl/chisel/src/bus:axi2tlul_cc_library_verilog"], + verilator_model = ":axi2tlul_model", +) \ No newline at end of file
diff --git a/hdl/chisel/src/bus/TLUL2Axi.scala b/hdl/chisel/src/bus/TLUL2Axi.scala new file mode 100644 index 0000000..4bfb5b7 --- /dev/null +++ b/hdl/chisel/src/bus/TLUL2Axi.scala
@@ -0,0 +1,160 @@ +// 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 +import common.KelvinRRArbiter +import _root_.circt.stage.{ChiselStage,FirtoolOption} +import chisel3.stage.ChiselGeneratorAnnotation +import scala.annotation.nowarn + +/** + * TLUL2Axi: A Chisel module that serves as a bridge between a TileLink-UL master + * and an AXI4 slave. + * + * This module translates TileLink Get and Put operations into AXI read and write + * transactions, respectively. It uses a dataflow approach with queues and an + * arbiter to manage the protocol conversion. + * + * @param p The Kelvin parameters. + */ +class TLUL2Axi[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 tl_a = Flipped(Decoupled(new TileLink_A_ChannelBase(tlul_p, userAGen))) // TileLink Input + val tl_d = Decoupled(new TileLink_D_ChannelBase(tlul_p, userDGen)) // TileLink Output + val axi = new AxiMasterIO(p.axi2AddrBits, p.axi2DataBits, p.axi2IdBits) + }) + // --- Queue for incoming TileLink A-Channel requests --- + val tl_a_q = Queue(io.tl_a, 2) + + val is_get = tl_a_q.bits.opcode === TLULOpcodesA.Get.asUInt + val is_put = tl_a_q.bits.opcode === TLULOpcodesA.PutFullData.asUInt || + tl_a_q.bits.opcode === TLULOpcodesA.PutPartialData.asUInt + + // --- AXI Channel Generation --- + // TODO: Consider gating these signals (on get/put)? Especially address. + // Drive AXI write channels for Put requests + val aw_q = Module(new Queue(new AxiAddress(p.axi2AddrBits, p.axi2DataBits, p.axi2IdBits), 1)) + aw_q.io.enq.valid := tl_a_q.valid && is_put + aw_q.io.enq.bits.addr := tl_a_q.bits.address + aw_q.io.enq.bits.id := tl_a_q.bits.source + aw_q.io.enq.bits.len := 0.U + aw_q.io.enq.bits.size := tl_a_q.bits.size + aw_q.io.enq.bits.burst := AxiBurstType.INCR.asUInt + aw_q.io.enq.bits.prot := 0.U + aw_q.io.enq.bits.lock := 0.U + aw_q.io.enq.bits.cache := 0.U + aw_q.io.enq.bits.qos := 0.U + aw_q.io.enq.bits.region := 0.U + + val w_q = Module(new Queue(new AxiWriteData(p.axi2DataBits, p.axi2IdBits), 1)) + w_q.io.enq.valid := tl_a_q.valid && is_put + w_q.io.enq.bits.data := tl_a_q.bits.data + w_q.io.enq.bits.strb := tl_a_q.bits.mask + w_q.io.enq.bits.last := true.B + + io.axi.write.addr <> aw_q.io.deq + io.axi.write.data <> w_q.io.deq + + // Drive AXI read channel for Get requests + io.axi.read.addr.valid := tl_a_q.valid && is_get + io.axi.read.addr.bits.addr := tl_a_q.bits.address + io.axi.read.addr.bits.id := tl_a_q.bits.source + io.axi.read.addr.bits.len := 0.U // No bursting + io.axi.read.addr.bits.size := tl_a_q.bits.size + io.axi.read.addr.bits.burst := AxiBurstType.INCR.asUInt // Doesn't matter + io.axi.read.addr.bits.prot := 0.U // Default protection + + // Dequeue from TileLink queue when AXI transaction is accepted + tl_a_q.ready := (is_get && io.axi.read.addr.ready) || (is_put && aw_q.io.enq.ready && w_q.io.enq.ready) + + io.axi.write.addr.bits.lock := 0.U + io.axi.write.addr.bits.cache := 0.U + io.axi.write.addr.bits.qos := 0.U + io.axi.write.addr.bits.region := 0.U + io.axi.read.addr.bits.lock := 0.U + io.axi.read.addr.bits.cache := 0.U + io.axi.read.addr.bits.qos := 0.U + io.axi.read.addr.bits.region := 0.U + + // --- Response Path --- + class TxInfo extends Bundle { + val source = UInt(tlul_p.o.W) + val size = UInt(tlul_p.z.W) + } + + val read_tx_info_q = Module(new Queue(new TxInfo, entries = 2)) + val write_tx_info_q = Module(new Queue(new TxInfo, entries = 2)) + + read_tx_info_q.io.enq.valid := tl_a_q.valid && is_get && io.axi.read.addr.ready + read_tx_info_q.io.enq.bits.source := tl_a_q.bits.source + read_tx_info_q.io.enq.bits.size := tl_a_q.bits.size + + write_tx_info_q.io.enq.valid := tl_a_q.valid && is_put && aw_q.io.enq.ready && w_q.io.enq.ready + write_tx_info_q.io.enq.bits.source := tl_a_q.bits.source + write_tx_info_q.io.enq.bits.size := tl_a_q.bits.size + + // --- TileLink D-Channel (Response) Generation --- + val read_response = Wire(Decoupled(new TileLink_D_ChannelBase(tlul_p, userDGen))) + val write_response = Wire(Decoupled(new TileLink_D_ChannelBase(tlul_p, userDGen))) + + // AXI Read Response -> TileLink AccessAckData + read_response.valid := io.axi.read.data.valid && read_tx_info_q.io.deq.valid + read_response.bits.opcode := TLULOpcodesD.AccessAckData.asUInt + read_response.bits.param := 0.U + read_response.bits.size := read_tx_info_q.io.deq.bits.size + read_response.bits.source := read_tx_info_q.io.deq.bits.source + read_response.bits.sink := 0.U + read_response.bits.data := io.axi.read.data.bits.data + read_response.bits.error := io.axi.read.data.bits.resp =/= 0.U + read_response.bits.user := 0.U.asTypeOf(read_response.bits.user) + + // AXI Write Response -> TileLink AccessAck + write_response.valid := io.axi.write.resp.valid && write_tx_info_q.io.deq.valid + write_response.bits.opcode := TLULOpcodesD.AccessAck.asUInt + write_response.bits.param := 0.U + write_response.bits.size := write_tx_info_q.io.deq.bits.size + write_response.bits.source := write_tx_info_q.io.deq.bits.source + write_response.bits.sink := 0.U + write_response.bits.data := 0.U + write_response.bits.error := io.axi.write.resp.bits.resp =/= 0.U + write_response.bits.user := 0.U.asTypeOf(write_response.bits.user) + + // Arbitrate between read and write responses for the D-channel + val d_channel_arb = Module(new KelvinRRArbiter(new TileLink_D_ChannelBase(tlul_p, userDGen), 2)) + d_channel_arb.io.in(0) <> read_response + d_channel_arb.io.in(1) <> write_response + io.tl_d <> Queue(d_channel_arb.io.out, 2) + + // Drive ready signals + io.axi.read.data.ready := d_channel_arb.io.in(0).ready + read_tx_info_q.io.deq.ready := d_channel_arb.io.in(0).ready && io.axi.read.data.valid + + io.axi.write.resp.ready := d_channel_arb.io.in(1).ready + write_tx_info_q.io.deq.ready := d_channel_arb.io.in(1).ready && io.axi.write.resp.valid + +} + +@nowarn +object EmitTLUL2Axi extends App { + val p = Parameters() + (new ChiselStage).execute( + Array("--target", "systemverilog") ++ args, + Seq(ChiselGeneratorAnnotation(() => new TLUL2Axi(p, () => new OpenTitanTileLink_A_User, () => new OpenTitanTileLink_D_User))) ++ Seq(FirtoolOption("-enable-layers=Verification")) + ) +} \ No newline at end of file
diff --git a/hdl/chisel/src/bus/TileLinkUL.scala b/hdl/chisel/src/bus/TileLinkUL.scala index 8570ab1..aaaab69 100644 --- a/hdl/chisel/src/bus/TileLinkUL.scala +++ b/hdl/chisel/src/bus/TileLinkUL.scala
@@ -17,13 +17,14 @@ import chisel3._ import chisel3.util._ +import kelvin.Parameters import kelvin.MemoryRegion -case class TLULParameters() { - val w = 32 - val a = 32 - val z = 6 - val o = 10 +class TLULParameters(p: Parameters) { + val w = p.axi2DataBits / 8 + val a = p.axi2AddrBits + val z = log2Ceil(w) + val o = p.axi2IdBits val i = 1 } @@ -40,6 +41,64 @@ val End = Value(7.U(3.W)) } +class OpenTitanTileLink_A_User extends Bundle { + val rsvd = UInt(5.W) + val instr_type = UInt(4.W) // mubi4_t + val cmd_intg = UInt(7.W) + val data_intg = UInt(7.W) +} + +class OpenTitanTileLink_D_User extends Bundle { + val rsp_intg = UInt(7.W) + val data_intg = UInt(7.W) +} + +class NoUser extends Bundle {} + +class TileLink_A_ChannelBase[T <: Data](p: TLULParameters, val userGen: () => T) extends Bundle { + val opcode = UInt(3.W) + val param = UInt(3.W) + val size = UInt(p.z.W) + val source = UInt(p.o.W) + val address = UInt(p.a.W) + val mask = UInt(p.w.W) + val data = UInt((8 * p.w).W) + val user = userGen() +} + +class TileLink_D_ChannelBase[T <: Data](p: TLULParameters, val userGen: () => T) extends Bundle { + val opcode = UInt(3.W) + val param = UInt(3.W) + val size = UInt(p.z.W) + val source = UInt(p.o.W) + val sink = UInt(p.i.W) + val data = UInt((8 * p.w).W) + val user = userGen() + val error = Bool() +} + +class TileLink_A_Channel(p: TLULParameters) extends TileLink_A_ChannelBase(p, () => new NoUser) {} +class TileLink_D_Channel(p: TLULParameters) extends TileLink_D_ChannelBase(p, () => new NoUser) {} + +class TLULHost2Device[A_USER <: Data, D_USER <: Data](p: TLULParameters, userAGen: () => A_USER, userDGen: () => D_USER) extends Bundle { + val a = Decoupled(new TileLink_A_ChannelBase(p, userAGen)) + val d = Flipped(Decoupled(new TileLink_D_ChannelBase(p, userDGen))) +} + +class TLULDevice2Host[A_USER <: Data, D_USER <: Data](p: TLULParameters, userAGen: () => A_USER, userDGen: () => D_USER) extends Bundle { + val a = Flipped(Decoupled(new TileLink_A_ChannelBase(p, userAGen))) + val d = Decoupled(new TileLink_D_ChannelBase(p, userDGen)) +} + +object OpenTitanTileLink { + class A_Channel(p: TLULParameters) extends TileLink_A_ChannelBase(p, () => new OpenTitanTileLink_A_User) {} + class D_Channel(p: TLULParameters) extends TileLink_D_ChannelBase(p, () => new OpenTitanTileLink_D_User) {} + class Host2Device(p: TLULParameters) extends TLULHost2Device(p, () => new OpenTitanTileLink_A_User, () => new OpenTitanTileLink_D_User) {} + class Device2Host(p: TLULParameters) extends TLULDevice2Host(p, () => new OpenTitanTileLink_A_User, () => new OpenTitanTileLink_D_User) {} +} + +// NB: Stuff below here is for ChAI -- it's not likely that you want +// to use these for new development. class TileLinkULIO_H2D(p: TLULParameters) extends Bundle { val a_valid = (Bool()) val a_opcode = (UInt(3.W)) @@ -124,4 +183,4 @@ host_d :<>= device_d device_a :<>= host_a } -} +} \ No newline at end of file
diff --git a/hdl/chisel/src/bus/axi2tlul_cocotb_test.py b/hdl/chisel/src/bus/axi2tlul_cocotb_test.py new file mode 100644 index 0000000..e6b721c --- /dev/null +++ b/hdl/chisel/src/bus/axi2tlul_cocotb_test.py
@@ -0,0 +1,263 @@ +# 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 +# +# https://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. + +import cocotb +import enum +import random + +from cocotb.clock import Clock +from cocotb.triggers import ClockCycles, RisingEdge + +class TLUL_OpcodeA(enum.IntEnum): + PutFullData = 0 + PutPartialData = 1 + Get = 4 + +class TLUL_OpcodeD(enum.IntEnum): + AccessAck = 0 + AccessAckData = 1 + +async def reset_dut(dut): + """Applies reset to the DUT.""" + dut.reset.value = 1 + dut.io_axi_read_addr_valid.value = 0 + dut.io_axi_write_addr_valid.value = 0 + dut.io_axi_write_data_valid.value = 0 + dut.io_axi_write_resp_ready.value = 0 + dut.io_axi_read_data_ready.value = 0 + await ClockCycles(dut.clock, 2) + dut.reset.value = 0 + await ClockCycles(dut.clock, 2) + +async def axi_send_write(dut, address, source, size, data, strb, timeout_cycles=1000): + """Sends an AXI write transaction.""" + dut.io_axi_write_addr_valid.value = 1 + dut.io_axi_write_addr_bits_addr.value = address + dut.io_axi_write_addr_bits_id.value = source + dut.io_axi_write_addr_bits_size.value = size + + dut.io_axi_write_data_valid.value = 1 + dut.io_axi_write_data_bits_data.value = data + dut.io_axi_write_data_bits_strb.value = strb + + for _ in range(timeout_cycles): + await RisingEdge(dut.clock) + if dut.io_axi_write_addr_ready.value == 1 and dut.io_axi_write_data_ready.value == 1: + break + else: + raise RuntimeError(f"Timeout waiting for AXI write ready") + + dut.io_axi_write_addr_valid.value = 0 + dut.io_axi_write_data_valid.value = 0 + +async def axi_send_read(dut, address, source, size, timeout_cycles=1000): + """Sends an AXI read transaction.""" + dut.io_axi_read_addr_valid.value = 1 + dut.io_axi_read_addr_bits_addr.value = address + dut.io_axi_read_addr_bits_id.value = source + dut.io_axi_read_addr_bits_size.value = size + + for _ in range(timeout_cycles): + await RisingEdge(dut.clock) + if dut.io_axi_read_addr_ready.value == 1: + break + else: + raise RuntimeError(f"Timeout waiting for AXI read ready") + + dut.io_axi_read_addr_valid.value = 0 + +@cocotb.test() +async def test_write_request(dut): + """Tests a simple AXI write request.""" + clock = Clock(dut.clock, 10, unit="us") + cocotb.start_soon(clock.start()) + + await reset_dut(dut) + + dut.io_tl_a_ready.value = 0 + dut.io_tl_d_valid.value = 0 + + addr_width = 32 + source_width = 6 + data_width_bytes = 32 + timeout_cycles = 1000 + + size_power = random.randint(0, 5) + test_size = size_power + num_bytes = 2**size_power + + test_addr = random.randint(0, (2**addr_width) - 1) + test_source = random.randint(0, (2**source_width) - 1) + test_data = random.randint(0, (2**(data_width_bytes*8)) - 1) + test_strb = (1 << num_bytes) - 1 + + await axi_send_write(dut, address=test_addr, source=test_source, size=test_size, data=test_data, strb=test_strb, timeout_cycles=timeout_cycles) + + await RisingEdge(dut.clock) + assert dut.io_tl_a_valid.value, "TL A_VALID should be high" + assert dut.io_tl_a_bits_opcode.value == TLUL_OpcodeA.PutFullData, "TL A_OPCODE should be PutFullData" + assert dut.io_tl_a_bits_address.value == test_addr, "TL A_ADDRESS is incorrect" + assert dut.io_tl_a_bits_source.value == test_source, "TL A_SOURCE is incorrect" + assert dut.io_tl_a_bits_size.value == test_size, "TL A_SIZE is incorrect" + assert dut.io_tl_a_bits_data.value == test_data, "TL A_DATA is incorrect" + assert dut.io_tl_a_bits_mask.value == test_strb, "TL A_MASK is incorrect" + + dut.io_tl_a_ready.value = 1 + await RisingEdge(dut.clock) + dut.io_tl_a_ready.value = 0 + + dut.io_axi_write_resp_ready.value = 1 + dut.io_tl_d_valid.value = 1 + dut.io_tl_d_bits_opcode.value = TLUL_OpcodeD.AccessAck + dut.io_tl_d_bits_source.value = test_source + + for _ in range(timeout_cycles): + await RisingEdge(dut.clock) + if dut.io_tl_d_ready.value: + assert dut.io_axi_write_resp_valid.value, "AXI BVALID should be high" + assert dut.io_axi_write_resp_bits_id.value == test_source, "AXI BID is incorrect" + assert dut.io_axi_write_resp_bits_resp.value == 0, "AXI BRESP is incorrect" + dut.io_axi_write_resp_ready.value = 0 + break + else: + raise RuntimeError("Timeout waiting for io_tl_d_ready") + + await RisingEdge(dut.clock) + dut.io_tl_d_valid.value = 0 + + await ClockCycles(dut.clock, 5) + +@cocotb.test() +async def test_read_request(dut): + """Tests a simple AXI read request.""" + clock = Clock(dut.clock, 10, unit="us") + cocotb.start_soon(clock.start()) + + await reset_dut(dut) + + dut.io_tl_a_ready.value = 0 + dut.io_tl_d_valid.value = 0 + + addr_width = 32 + source_width = 6 + data_width_bytes = 32 + timeout_cycles = 1000 + + size_power = random.randint(0, 5) + test_size = size_power + + test_addr = random.randint(0, (2**addr_width) - 1) + test_source = random.randint(0, (2**source_width) - 1) + test_data = random.randint(0, (2**(data_width_bytes*8)) - 1) + + await axi_send_read(dut, address=test_addr, source=test_source, size=test_size, timeout_cycles=timeout_cycles) + + await RisingEdge(dut.clock) + assert dut.io_tl_a_valid.value, "TL A_VALID should be high" + assert dut.io_tl_a_bits_opcode.value == TLUL_OpcodeA.Get, "TL A_OPCODE should be Get" + assert dut.io_tl_a_bits_address.value == test_addr, "TL A_ADDRESS is incorrect" + assert dut.io_tl_a_bits_source.value == test_source, "TL A_SOURCE is incorrect" + assert dut.io_tl_a_bits_size.value == test_size, "TL A_SIZE is incorrect" + + dut.io_tl_a_ready.value = 1 + await RisingEdge(dut.clock) + dut.io_tl_a_ready.value = 0 + + await RisingEdge(dut.clock) + dut.io_tl_d_valid.value = 1 + dut.io_tl_d_bits_opcode.value = TLUL_OpcodeD.AccessAckData + dut.io_tl_d_bits_source.value = test_source + dut.io_tl_d_bits_data.value = test_data + + dut.io_axi_read_data_ready.value = 1 + + for _ in range(timeout_cycles): + await RisingEdge(dut.clock) + if dut.io_tl_d_ready.value: + assert dut.io_axi_read_data_valid.value, "AXI RVALID should be high" + assert dut.io_axi_read_data_bits_id.value == test_source, "AXI RID is incorrect" + assert dut.io_axi_read_data_bits_data.value == test_data, "AXI RDATA is incorrect" + assert dut.io_axi_read_data_bits_resp.value == 0, "AXI RRESP is incorrect" + dut.io_axi_read_data_ready.value = 0 + break + else: + raise RuntimeError("Timeout waiting for io_tl_d_ready") + + await RisingEdge(dut.clock) + dut.io_tl_d_valid.value = 0 + + await ClockCycles(dut.clock, 5) + + +@cocotb.test() +async def test_read_error(dut): + """Tests a simple AXI read request that results in a TL error.""" + clock = Clock(dut.clock, 10, unit="us") + cocotb.start_soon(clock.start()) + + await reset_dut(dut) + + dut.io_tl_a_ready.value = 0 + dut.io_tl_d_valid.value = 0 + + addr_width = 32 + source_width = 6 + data_width_bytes = 32 + timeout_cycles = 1000 + + size_power = random.randint(0, 5) + test_size = size_power + + test_addr = random.randint(0, (2**addr_width) - 1) + test_source = random.randint(0, (2**source_width) - 1) + test_data = random.randint(0, (2**(data_width_bytes*8)) - 1) + + await axi_send_read(dut, address=test_addr, source=test_source, size=test_size, timeout_cycles=timeout_cycles) + + await RisingEdge(dut.clock) + assert dut.io_tl_a_valid.value, "TL A_VALID should be high" + assert dut.io_tl_a_bits_opcode.value == TLUL_OpcodeA.Get, "TL A_OPCODE should be Get" + assert dut.io_tl_a_bits_address.value == test_addr, "TL A_ADDRESS is incorrect" + assert dut.io_tl_a_bits_source.value == test_source, "TL A_SOURCE is incorrect" + assert dut.io_tl_a_bits_size.value == test_size, "TL A_SIZE is incorrect" + + dut.io_tl_a_ready.value = 1 + await RisingEdge(dut.clock) + dut.io_tl_a_ready.value = 0 + + await RisingEdge(dut.clock) + dut.io_tl_d_valid.value = 1 + dut.io_tl_d_bits_opcode.value = TLUL_OpcodeD.AccessAckData + dut.io_tl_d_bits_source.value = test_source + dut.io_tl_d_bits_data.value = test_data + dut.io_tl_d_bits_error.value = 1 + + dut.io_axi_read_data_ready.value = 1 + + for _ in range(timeout_cycles): + await RisingEdge(dut.clock) + if dut.io_tl_d_ready.value: + assert dut.io_axi_read_data_valid.value, "AXI RVALID should be high" + assert dut.io_axi_read_data_bits_id.value == test_source, "AXI RID is incorrect" + assert dut.io_axi_read_data_bits_data.value == test_data, "AXI RDATA is incorrect" + assert dut.io_axi_read_data_bits_resp.value == 2, "AXI RRESP is incorrect" + dut.io_axi_read_data_ready.value = 0 + break + else: + raise RuntimeError("Timeout waiting for io_tl_d_ready") + + await RisingEdge(dut.clock) + dut.io_tl_d_valid.value = 0 + + await ClockCycles(dut.clock, 5)
diff --git a/hdl/chisel/src/bus/tlul2axi_cocotb_test.py b/hdl/chisel/src/bus/tlul2axi_cocotb_test.py new file mode 100644 index 0000000..e50b25b --- /dev/null +++ b/hdl/chisel/src/bus/tlul2axi_cocotb_test.py
@@ -0,0 +1,420 @@ +# 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 +# +# https://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. + +import cocotb +import enum +import random + +from cocotb.clock import Clock +from cocotb.triggers import ClockCycles, RisingEdge + +class TLUL_OpcodeA(enum.IntEnum): + PutFullData = 0 + PutPartialData = 1 + Get = 4 + +class TLUL_OpcodeD(enum.IntEnum): + AccessAck = 0 + AccessAckData = 1 + +async def reset_dut(dut): + """Applies reset to the DUT.""" + dut.reset.value = 1 + dut.io_tl_a_valid.value = 0 + dut.io_tl_d_ready.value = 0 + dut.io_tl_a_bits_opcode.value = 0 + dut.io_tl_a_bits_param.value = 0 + dut.io_tl_a_bits_size.value = 0 + dut.io_tl_a_bits_source.value = 0 + dut.io_tl_a_bits_address.value = 0 + dut.io_tl_a_bits_mask.value = 0 + dut.io_tl_a_bits_data.value = 0 + await ClockCycles(dut.clock, 2) + dut.reset.value = 0 + await ClockCycles(dut.clock, 2) + +async def wait_for_signal(clock, signal, timeout_cycles=1000, message=None): + """Waits for a signal to be asserted.""" + if message is None: + message = f"Timeout waiting for {signal._name}" + + for _ in range(timeout_cycles): + await RisingEdge(clock) + if signal.value: + return + else: + raise RuntimeError(message) + +async def tl_send_get(dut, address, source, size, timeout_cycles=1000): + """Sends a TileLink Get request.""" + dut.io_tl_a_valid.value = 1 + dut.io_tl_a_bits_opcode.value = TLUL_OpcodeA.Get + dut.io_tl_a_bits_address.value = address + dut.io_tl_a_bits_source.value = source + dut.io_tl_a_bits_size.value = size + dut.io_tl_a_bits_mask.value = 0 # Mask is ignored for Get + dut.io_tl_a_bits_data.value = 0 # Data is ignored for Get + + await wait_for_signal(dut.clock, dut.io_tl_a_ready, timeout_cycles) + + dut.io_tl_a_valid.value = 0 + + +async def tl_send_put(dut, address, source, size, data, mask, timeout_cycles=1000): + """Sends a TileLink PutFullData request.""" + dut.io_tl_a_valid.value = 1 + dut.io_tl_a_bits_opcode.value = TLUL_OpcodeA.PutFullData + dut.io_tl_a_bits_address.value = address + dut.io_tl_a_bits_source.value = source + dut.io_tl_a_bits_size.value = size + dut.io_tl_a_bits_data.value = data + dut.io_tl_a_bits_mask.value = mask + + await wait_for_signal(dut.clock, dut.io_tl_a_ready, timeout_cycles) + + dut.io_tl_a_valid.value = 0 + + +@cocotb.test() +async def test_put_request(dut): + """Tests a simple Put request.""" + clock = Clock(dut.clock, 10, unit="us") + cocotb.start_soon(clock.start()) + + # Reset + await reset_dut(dut) + + # AXI slave initial state + dut.io_axi_read_addr_ready.value = 0 + dut.io_axi_write_addr_ready.value = 0 + dut.io_axi_write_data_ready.value = 0 + dut.io_axi_write_resp_valid.value = 0 + dut.io_axi_read_data_valid.value = 0 + + # Test parameters + addr_width = 32 + source_width = 6 + data_width_bytes = 32 # Corresponds to 256 bits + timeout_cycles = 1000 + + size_power = random.randint(0, 5) # 2**5 = 32 bytes + test_size = size_power + num_bytes = 2**size_power + + test_addr = random.randint(0, (2**addr_width) - 1) + test_source = random.randint(0, (2**source_width) - 1) + test_data = random.randint(0, (2**(data_width_bytes*8)) - 1) + test_mask = (1 << num_bytes) - 1 + + # Drive TL Put request + await tl_send_put(dut, address=test_addr, source=test_source, size=test_size, data=test_data, mask=test_mask, timeout_cycles=timeout_cycles) + + # + # Check AXI Write Address and Data Channels + # + await wait_for_signal(dut.clock, dut.io_axi_write_addr_valid, timeout_cycles, "Timeout waiting for AXI AWVALID for Put") + + assert dut.io_axi_write_addr_valid.value, "AXI AWVALID should be high" + assert dut.io_axi_write_data_valid.value, "AXI WVALID should be high" + assert dut.io_axi_write_addr_bits_addr.value == test_addr, "AXI AWADDR is incorrect" + assert dut.io_axi_write_addr_bits_id.value == test_source, "AXI AWID is incorrect" + assert dut.io_axi_write_addr_bits_size.value == test_size, "AXI AWSIZE is incorrect" + assert dut.io_axi_write_data_bits_data.value == test_data, "AXI WDATA is incorrect" + assert dut.io_axi_write_data_bits_strb.value == test_mask, "AXI WSTRB is incorrect" + + # AXI slave accepts the request + dut.io_axi_write_addr_ready.value = 1 + dut.io_axi_write_data_ready.value = 1 + await RisingEdge(dut.clock) + dut.io_axi_write_addr_ready.value = 0 + dut.io_axi_write_data_ready.value = 0 + + # + # AXI slave provides write response + # + await RisingEdge(dut.clock) + dut.io_axi_write_resp_valid.value = 1 + dut.io_axi_write_resp_bits_id.value = test_source + dut.io_axi_write_resp_bits_resp.value = 0 # OKAY + + await wait_for_signal(dut.clock, dut.io_axi_write_resp_ready, timeout_cycles) + + await RisingEdge(dut.clock) + dut.io_axi_write_resp_valid.value = 0 + + # + # Check TileLink D Channel + # + dut.io_tl_d_ready.value = 1 + await wait_for_signal(dut.clock, dut.io_tl_d_valid, timeout_cycles, "Timeout waiting for TL D_VALID for Put") + + assert dut.io_tl_d_valid.value, "TL D_VALID should be high" + assert dut.io_tl_d_bits_opcode.value == TLUL_OpcodeD.AccessAck, "TL D_OPCODE should be AccessAck" + assert dut.io_tl_d_bits_source.value == test_source, "TL D_SOURCE is incorrect" + assert not dut.io_tl_d_bits_error.value, "TL D_ERROR should be low" + dut.io_tl_d_ready.value = 0 + + await ClockCycles(dut.clock, 5) + +@cocotb.test() +async def test_get_request(dut): + """Tests a simple Get request.""" + clock = Clock(dut.clock, 10, unit="us") + cocotb.start_soon(clock.start()) + + # Reset + await reset_dut(dut) + + # AXI slave initial state + dut.io_axi_read_addr_ready.value = 0 + dut.io_axi_write_addr_ready.value = 0 + dut.io_axi_write_data_ready.value = 0 + dut.io_axi_write_resp_valid.value = 0 + dut.io_axi_read_data_valid.value = 0 + + # Test parameters + addr_width = 32 + source_width = 6 + data_width_bytes = 32 + timeout_cycles = 1000 + + size_power = random.randint(0, 5) + test_size = size_power + + test_addr = random.randint(0, (2**addr_width) - 1) + test_source = random.randint(0, (2**source_width) - 1) + test_data = random.randint(0, (2**(data_width_bytes*8)) - 1) + + # Drive TL Get request + await tl_send_get(dut, address=test_addr, source=test_source, size=test_size, timeout_cycles=timeout_cycles) + + # + # Check AXI Read Address Channel + # + await RisingEdge(dut.clock) + assert dut.io_axi_read_addr_valid.value, "AXI ARVALID should be high" + assert dut.io_axi_read_addr_bits_addr.value == test_addr, "AXI ARADDR is incorrect" + assert dut.io_axi_read_addr_bits_id.value == test_source, "AXI ARID is incorrect" + assert dut.io_axi_read_addr_bits_size.value == test_size, "AXI ARSIZE is incorrect" + + # AXI slave accepts the request + dut.io_axi_read_addr_ready.value = 1 + await RisingEdge(dut.clock) + dut.io_axi_read_addr_ready.value = 0 + + # + # AXI slave provides read data + # + await RisingEdge(dut.clock) + dut.io_axi_read_data_valid.value = 1 + dut.io_axi_read_data_bits_data.value = test_data + dut.io_axi_read_data_bits_id.value = test_source + dut.io_axi_read_data_bits_resp.value = 0 # OKAY + + await wait_for_signal(dut.clock, dut.io_axi_read_data_ready, timeout_cycles) + + await RisingEdge(dut.clock) + dut.io_axi_read_data_valid.value = 0 + + # + # Check TileLink D Channel + # + dut.io_tl_d_ready.value = 1 + await RisingEdge(dut.clock) + + assert dut.io_tl_d_valid.value, "TL D_VALID should be high" + assert dut.io_tl_d_bits_opcode.value == TLUL_OpcodeD.AccessAckData, "TL D_OPCODE should be AccessAckData" + assert dut.io_tl_d_bits_source.value == test_source, "TL D_SOURCE is incorrect" + assert dut.io_tl_d_bits_data.value == test_data, "TL D_DATA is incorrect" + assert not dut.io_tl_d_bits_error.value, "TL D_ERROR should be low" + dut.io_tl_d_ready.value = 0 + + await ClockCycles(dut.clock, 5) + + +@cocotb.test() +async def test_backpressure(dut): + """Tests backpressure from the AXI slave.""" + clock = Clock(dut.clock, 10, unit="us") + cocotb.start_soon(clock.start()) + + # Reset + await reset_dut(dut) + + # AXI slave initial state + dut.io_axi_read_addr_ready.value = 0 + dut.io_axi_write_addr_ready.value = 0 + dut.io_axi_write_data_ready.value = 0 + dut.io_axi_write_resp_valid.value = 0 + dut.io_axi_read_data_valid.value = 0 + + # Test parameters + addr_width = 32 + source_width = 6 + data_width_bytes = 32 # Corresponds to 256 bits + timeout_cycles = 1000 + + size_power = random.randint(0, 5) # 2**5 = 32 bytes + test_size = size_power + num_bytes = 2**size_power + + test_addr = random.randint(0, (2**addr_width) - 1) + test_source = random.randint(0, (2**source_width) - 1) + test_data = random.randint(0, (2**(data_width_bytes*8)) - 1) + test_mask = (1 << num_bytes) - 1 + + # Drive TL Put request + await tl_send_put(dut, address=test_addr, source=test_source, size=test_size, data=test_data, mask=test_mask, timeout_cycles=timeout_cycles) + + # + # Check AXI Write Address and Data Channels + # + await wait_for_signal(dut.clock, dut.io_axi_write_addr_valid, timeout_cycles, "Timeout waiting for AXI AWVALID for Put") + + assert dut.io_axi_write_addr_valid.value, "AXI AWVALID should be high" + assert dut.io_axi_write_data_valid.value, "AXI WVALID should be high" + + # Apply backpressure to address channel + dut.io_axi_write_addr_ready.value = 0 + dut.io_axi_write_data_ready.value = 1 + + await ClockCycles(dut.clock, 10) + + # Address channel should be stalled, data channel should have cleared + assert dut.io_axi_write_addr_valid.value, "AXI AWVALID should remain high" + assert not dut.io_axi_write_data_valid.value, "AXI WVALID should be low" + + # Release backpressure + dut.io_axi_write_addr_ready.value = 1 + await RisingEdge(dut.clock) + dut.io_axi_write_addr_ready.value = 0 + dut.io_axi_write_data_ready.value = 0 + + # + # AXI slave provides write response + # + await RisingEdge(dut.clock) + dut.io_axi_write_resp_valid.value = 1 + dut.io_axi_write_resp_bits_id.value = test_source + dut.io_axi_write_resp_bits_resp.value = 0 # OKAY + + await wait_for_signal(dut.clock, dut.io_axi_write_resp_ready, timeout_cycles) + + await RisingEdge(dut.clock) + dut.io_axi_write_resp_valid.value = 0 + + # + # Check TileLink D Channel + # + dut.io_tl_d_ready.value = 1 + await wait_for_signal(dut.clock, dut.io_tl_d_valid, timeout_cycles, "Timeout waiting for TL D_VALID for Put") + + assert dut.io_tl_d_valid.value, "TL D_VALID should be high" + assert dut.io_tl_d_bits_opcode.value == TLUL_OpcodeD.AccessAck, "TL D_OPCODE should be AccessAck" + assert dut.io_tl_d_bits_source.value == test_source, "TL D_SOURCE is incorrect" + assert not dut.io_tl_d_bits_error.value, "TL D_ERROR should be low" + dut.io_tl_d_ready.value = 0 + + await ClockCycles(dut.clock, 5) + + +@cocotb.test() +async def test_put_then_get(dut): + """Tests a Put request followed by a Get request.""" + clock = Clock(dut.clock, 10, unit="us") + cocotb.start_soon(clock.start()) + + # Reset + await reset_dut(dut) + + # AXI slave initial state + dut.io_axi_read_addr_ready.value = 0 + dut.io_axi_write_addr_ready.value = 0 + dut.io_axi_write_data_ready.value = 0 + dut.io_axi_write_resp_valid.value = 0 + dut.io_axi_read_data_valid.value = 0 + + # Test parameters + addr_width = 32 + source_width = 6 + data_width_bytes = 32 + timeout_cycles = 1000 + + # Put parameters + put_size_power = random.randint(0, 5) + put_size = put_size_power + put_num_bytes = 2**put_size_power + put_addr = random.randint(0, (2**addr_width) - 1) + put_source = random.randint(0, (2**source_width) - 1) + put_data = random.randint(0, (2**(data_width_bytes*8)) - 1) + put_mask = (1 << put_num_bytes) - 1 + + # Get parameters + get_size_power = random.randint(0, 5) + get_size = get_size_power + get_addr = random.randint(0, (2**addr_width) - 1) + get_source = random.randint(0, (2**source_width) - 1) + get_data = random.randint(0, (2**(data_width_bytes*8)) - 1) + + # + # Complete Put Transaction + # + await tl_send_put(dut, address=put_addr, source=put_source, size=put_size, data=put_data, mask=put_mask, timeout_cycles=timeout_cycles) + + await wait_for_signal(dut.clock, dut.io_axi_write_addr_valid, timeout_cycles, "Timeout waiting for AXI AWVALID for Put") + assert dut.io_axi_write_addr_valid.value, "AXI AWVALID should be high for Put" + assert dut.io_axi_write_data_valid.value, "AXI WVALID should be high for Put" + + dut.io_axi_write_addr_ready.value = 1 + dut.io_axi_write_data_ready.value = 1 + await RisingEdge(dut.clock) + dut.io_axi_write_addr_ready.value = 0 + dut.io_axi_write_data_ready.value = 0 + + await RisingEdge(dut.clock) + dut.io_axi_write_resp_valid.value = 1 + await wait_for_signal(dut.clock, dut.io_axi_write_resp_ready, timeout_cycles) + dut.io_axi_write_resp_valid.value = 0 + + dut.io_tl_d_ready.value = 1 + await RisingEdge(dut.clock) + assert dut.io_tl_d_valid.value, "TL D_VALID should be high for Put" + assert dut.io_tl_d_bits_opcode.value == TLUL_OpcodeD.AccessAck, "TL D_OPCODE should be AccessAck for Put" + dut.io_tl_d_ready.value = 0 + + # + # Complete Get Transaction + # + await tl_send_get(dut, address=get_addr, source=get_source, size=get_size, timeout_cycles=timeout_cycles) + + await RisingEdge(dut.clock) + assert dut.io_axi_read_addr_valid.value, "AXI ARVALID should be high for Get" + + dut.io_axi_read_addr_ready.value = 1 + await RisingEdge(dut.clock) + dut.io_axi_read_addr_ready.value = 0 + + await RisingEdge(dut.clock) + dut.io_axi_read_data_valid.value = 1 + dut.io_axi_read_data_bits_data.value = get_data + await wait_for_signal(dut.clock, dut.io_axi_read_data_ready, timeout_cycles) + dut.io_axi_read_data_valid.value = 0 + + dut.io_tl_d_ready.value = 1 + await RisingEdge(dut.clock) + assert dut.io_tl_d_valid.value, "TL D_VALID should be high for Get" + assert dut.io_tl_d_bits_opcode.value == TLUL_OpcodeD.AccessAckData, "TL D_OPCODE should be AccessAckData for Get" + dut.io_tl_d_ready.value = 0 + + await ClockCycles(dut.clock, 5) \ No newline at end of file
diff --git a/hdl/chisel/src/chai/ChAI.scala b/hdl/chisel/src/chai/ChAI.scala index 27fec09..aed720a 100644 --- a/hdl/chisel/src/chai/ChAI.scala +++ b/hdl/chisel/src/chai/ChAI.scala
@@ -80,13 +80,13 @@ io.fault := u_kelvin.fault withClockAndReset(io.clk_i, rst_i) { - val tlul_p = new TLULParameters() + val tlul_p = new TLULParameters(kelvin_p) val kelvin_to_tlul = KelvinToTlul(tlul_p, kelvin_p) kelvin_to_tlul.io.kelvin <> u_kelvin.mem val tlul_sram = SRAM(p.sramDataEntries(), UInt(p.sramDataBits.W), p.sramReadPorts, p.sramWritePorts, p.sramReadWritePorts) - val tlul_adapter_sram = Module(new chai.TlulAdapterSram()) + val tlul_adapter_sram = Module(new chai.TlulAdapterSram(tlul_p)) tlul_adapter_sram.io.clk_i := io.clk_i tlul_adapter_sram.io.rst_ni := io.rst_ni tlul_adapter_sram.io.en_ifetch_i := 9.U // MuBi4False
diff --git a/hdl/chisel/src/chai/TlulAdapterSram.scala b/hdl/chisel/src/chai/TlulAdapterSram.scala index db2dcdf..ac204c4 100644 --- a/hdl/chisel/src/chai/TlulAdapterSram.scala +++ b/hdl/chisel/src/chai/TlulAdapterSram.scala
@@ -18,6 +18,8 @@ import bus._ + + package object sram_params { val SramAw = 17 val SramDw = 256 @@ -27,15 +29,14 @@ val EnableDataIntgPt = 0 } -class TlulAdapterSram extends BlackBox { - val tlul_p = new TLULParameters() +class TlulAdapterSram(p: TLULParameters) extends BlackBox { val io = IO(new Bundle { val clk_i = Input(Clock()) val rst_ni = Input(AsyncReset()) // TL-UL - val tl_i = Input(new TileLinkULIO_H2D(tlul_p)) - val tl_o = Output(new TileLinkULIO_D2H(tlul_p)) + val tl_i = Input(new TileLinkULIO_H2D(p)) + val tl_o = Output(new TileLinkULIO_D2H(p)) // control val en_ifetch_i = Input(UInt(4.W)) // mubi4_t
diff --git a/hdl/chisel/src/chai/Uart.scala b/hdl/chisel/src/chai/Uart.scala index 2e65da9..e81cd94 100644 --- a/hdl/chisel/src/chai/Uart.scala +++ b/hdl/chisel/src/chai/Uart.scala
@@ -18,13 +18,13 @@ import bus._ -class Uart(tlul_p: TLULParameters) extends BlackBox { +class Uart(p: TLULParameters) extends BlackBox { val io = IO(new Bundle { val clk_i = Input(Clock()) val rst_ni = Input(AsyncReset()) - val tl_i = Input(new TileLinkULIO_H2D(tlul_p)) - val tl_o = Output(new TileLinkULIO_D2H(tlul_p)) + val tl_i = Input(new TileLinkULIO_H2D(p)) + val tl_o = Output(new TileLinkULIO_D2H(p)) // These have some alert_{rx|tx}_t types. val alert_rx_i = Input(UInt(4.W))
diff --git a/hdl/chisel/src/kelvin/BUILD b/hdl/chisel/src/kelvin/BUILD index e8ad752..51ee1e7 100644 --- a/hdl/chisel/src/kelvin/BUILD +++ b/hdl/chisel/src/kelvin/BUILD
@@ -359,6 +359,7 @@ "Core.scala", "CoreAxi.scala", "CoreAxiCSR.scala", + "CoreTlul.scala", "Fabric.scala", "TCM.scala", "SRAM.scala", @@ -571,6 +572,24 @@ template_rule( chisel_cc_library, { + "rvv_core_mini_tlul_cc_library": { + "verilog_file_path": "RvvCoreMiniTlul.sv", + "extra_outs": [ + "VRvvCoreMiniTlul_parameters.h", + "RvvCoreMiniTlul.zip", + ], + "gen_flags": [ + "--moduleName=RvvCoreMini", + "--enableFetchL0=False", + "--fetchDataBits=128", + "--lsuDataBits=128", + "--enableVector=False", + "--enableRvv=True", + "--enableFloat=True", + "--useTlul=True", + ], + "module_name": "RvvCoreMiniTlul", + }, "rvv_core_mini_axi_cc_library": { "verilog_file_path": "RvvCoreMiniAxi.sv", "extra_outs": [ @@ -627,6 +646,11 @@ ], ) +filegroup( + name = "rvv_core_mini_tlul_verilog", + srcs = [":rvv_core_mini_tlul_cc_library_verilog"], +) + verilog_zip_bundle( name = "core_mini_axi_bundle", lib = ":core_mini_axi_cc_library_verilog",
diff --git a/hdl/chisel/src/kelvin/Core.scala b/hdl/chisel/src/kelvin/Core.scala index 45b5766..9410810 100644 --- a/hdl/chisel/src/kelvin/Core.scala +++ b/hdl/chisel/src/kelvin/Core.scala
@@ -110,6 +110,7 @@ var chiselArgs = List[String]() var targetDir: Option[String] = None var useAxi = false + var useTlul = false for (arg <- args) { if (arg.startsWith("--enableFetchL0")) { p.enableFetchL0 = arg.split("=")(1).toBoolean @@ -135,12 +136,15 @@ p.tcmHighmem = true } else if (arg.startsWith("--useAxi")) { useAxi = true + } else if (arg.startsWith("--useTlul")) { + useTlul = true } else if (arg.startsWith("--target-dir")) { targetDir = Some(arg.split("=")(1)) } else { chiselArgs = chiselArgs :+ arg } } + assert(!(useAxi && useTlul)) // The core module must be created in the ChiselStage context. Use lazy here // so it's created in ChiselStage, but referencable afterwards. @@ -161,7 +165,15 @@ ) p.m = memoryRegions } - new CoreAxi(p, moduleName) + new CoreAxi(p, moduleName) + } else if (useTlul) { + val memoryRegions = Seq( + new MemoryRegion(0x0000, 0x2000, MemoryRegionType.IMEM), // ITCM + new MemoryRegion(0x10000, 0x8000, MemoryRegionType.DMEM), // DTCM + new MemoryRegion(0x30000, 0x2000, MemoryRegionType.Peripheral), // CSR + ) + p.m = memoryRegions + new CoreTlul(p, moduleName) } else { // "Matcha" memory layout p.m = Seq(
diff --git a/hdl/chisel/src/kelvin/CoreTlul.scala b/hdl/chisel/src/kelvin/CoreTlul.scala new file mode 100644 index 0000000..7c41c55 --- /dev/null +++ b/hdl/chisel/src/kelvin/CoreTlul.scala
@@ -0,0 +1,59 @@ +// 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 kelvin + +import chisel3._ +import bus._ + +class CoreTlul(p: Parameters, coreModuleName: String) extends RawModule { + override val desiredName = coreModuleName + "Tlul" + val memoryRegions = p.m + val io = IO(new Bundle { + val clk = Input(Clock()) + val rst_ni = Input(AsyncReset()) + + val tl_host = new OpenTitanTileLink.Host2Device(new TLULParameters(p)) + val tl_device = new OpenTitanTileLink.Device2Host(new TLULParameters(p)) + + // Core status interrupts + val halted = Output(Bool()) + val fault = Output(Bool()) + val wfi = Output(Bool()) + val irq = Input(Bool()) + val te = Input(Bool()) + }) + dontTouch(io) + + val coreAxi = withClockAndReset(io.clk, io.rst_ni) { Module(new CoreAxi(p, coreModuleName)) } + val hostBridge = withClockAndReset(io.clk, (!io.rst_ni.asBool).asAsyncReset) { Module(new Axi2TLUL(p, () => new OpenTitanTileLink_A_User, () => new OpenTitanTileLink_D_User)) } + val deviceBridge = withClockAndReset(io.clk, (!io.rst_ni.asBool).asAsyncReset) { Module(new TLUL2Axi(p, () => new OpenTitanTileLink_A_User, () => new OpenTitanTileLink_D_User)) } + + coreAxi.io.aclk := io.clk + coreAxi.io.aresetn := io.rst_ni + coreAxi.io.te := io.te + coreAxi.io.irq := io.irq + io.wfi := coreAxi.io.wfi + io.fault := coreAxi.io.fault + io.halted := coreAxi.io.halted + + hostBridge.io.axi <> coreAxi.io.axi_master + deviceBridge.io.axi <> coreAxi.io.axi_slave + + io.tl_host.a <> hostBridge.io.tl_a + hostBridge.io.tl_d <> io.tl_host.d + + deviceBridge.io.tl_a <> io.tl_device.a + io.tl_device.d <> deviceBridge.io.tl_d +} \ No newline at end of file
diff --git a/hdl/verilog/TlulAdapterSram.sv b/hdl/verilog/TlulAdapterSram.sv index e1a295c..7d07623 100644 --- a/hdl/verilog/TlulAdapterSram.sv +++ b/hdl/verilog/TlulAdapterSram.sv
@@ -20,8 +20,8 @@ input tl_i_a_valid, input [2:0] tl_i_a_opcode, input [2:0] tl_i_a_param, - input [5:0] tl_i_a_size, - input [9:0] tl_i_a_source, + input [4:0] tl_i_a_size, + input [5:0] tl_i_a_source, input [31:0] tl_i_a_address, input [31:0] tl_i_a_mask, input [255:0] tl_i_a_data, @@ -38,8 +38,8 @@ output tl_o_d_valid, output [2:0] tl_o_d_opcode, output [2:0] tl_o_d_param, - output [5:0] tl_o_d_size, - output [9:0] tl_o_d_source, + output [4:0] tl_o_d_size, + output [5:0] tl_o_d_source, output tl_o_d_sink, output [255:0] tl_o_d_data, output [6:0] tl_o_d_user_rsp_intg, @@ -55,6 +55,16 @@ output intg_error_o ); +logic [5:0] tl_i_a_size_padded; +logic [9:0] tl_i_a_source_padded; +logic [5:0] tl_o_d_size_unpadded; +logic [9:0] tl_o_d_source_unpadded; + +assign tl_i_a_size_padded = {1'b0, tl_i_a_size}; +assign tl_i_a_source_padded = {4'b0, tl_i_a_source}; +assign tl_o_d_size = tl_o_d_size_unpadded[4:0]; +assign tl_o_d_source = tl_o_d_source_unpadded[5:0]; + mubi4_t en_ifetch_i_ = mubi4_t'(en_ifetch_i); tlul_adapter_sram #( @@ -72,8 +82,8 @@ tl_i_a_valid, tl_i_a_opcode, tl_i_a_param, - tl_i_a_size, - tl_i_a_source, + tl_i_a_size_padded, + tl_i_a_source_padded, tl_i_a_address, tl_i_a_mask, tl_i_a_data, @@ -87,8 +97,8 @@ tl_o_d_valid, tl_o_d_opcode, tl_o_d_param, - tl_o_d_size, - tl_o_d_source, + tl_o_d_size_unpadded, + tl_o_d_source_unpadded, tl_o_d_sink, tl_o_d_data, tl_o_d_user_rsp_intg, @@ -110,4 +120,4 @@ .rerror_i(rerror_i) ); -endmodule \ No newline at end of file +endmodule
diff --git a/hdl/verilog/Uart.sv b/hdl/verilog/Uart.sv index b00a4b4..56f1784 100644 --- a/hdl/verilog/Uart.sv +++ b/hdl/verilog/Uart.sv
@@ -18,8 +18,8 @@ input tl_i_a_valid, input [2:0] tl_i_a_opcode, input [2:0] tl_i_a_param, - input [5:0] tl_i_a_size, - input [9:0] tl_i_a_source, + input [4:0] tl_i_a_size, + input [5:0] tl_i_a_source, input [31:0] tl_i_a_address, input [31:0] tl_i_a_mask, input [255:0] tl_i_a_data, @@ -31,8 +31,8 @@ output tl_o_d_valid, output [2:0] tl_o_d_opcode, output [2:0] tl_o_d_param, - output [5:0] tl_o_d_size, - output [9:0] tl_o_d_source, + output [4:0] tl_o_d_size, + output [5:0] tl_o_d_source, output tl_o_d_sink, output [255:0] tl_o_d_data, output [6:0] tl_o_d_user_rsp_intg, @@ -57,6 +57,16 @@ output intr_rx_parity_err_o ); +logic [5:0] tl_i_a_size_padded; +logic [9:0] tl_i_a_source_padded; +logic [5:0] tl_o_d_size_unpadded; +logic [9:0] tl_o_d_source_unpadded; + +assign tl_i_a_size_padded = {1'b0, tl_i_a_size}; +assign tl_i_a_source_padded = {4'b0, tl_i_a_source}; +assign tl_o_d_size = tl_o_d_size_unpadded[4:0]; +assign tl_o_d_source = tl_o_d_source_unpadded[5:0]; + uart #() u_uart ( .clk_i(clk_i), .rst_ni(rst_ni), @@ -65,8 +75,8 @@ tl_i_a_valid, tl_i_a_opcode, tl_i_a_param, - tl_i_a_size, - tl_i_a_source, + tl_i_a_size_padded, + tl_i_a_source_padded, tl_i_a_address, tl_i_a_mask, tl_i_a_data, @@ -80,8 +90,8 @@ tl_o_d_valid, tl_o_d_opcode, tl_o_d_param, - tl_o_d_size, - tl_o_d_source, + tl_o_d_size_unpadded, + tl_o_d_source_unpadded, tl_o_d_sink, tl_o_d_data, tl_o_d_user_rsp_intg,