refactor(bus): Clean up AXI/TL-UL bridges and tests

This commit refactors the AXI-to-TileLink and TileLink-to-AXI bridge
logic and cleans up the associated test infrastructure.

Key changes:
- Simplified the ready signal logic in `Axi2TLUL.scala` for better
  clarity and correctness.
- Moved the cocotb test files for the AXI/TL-UL bridges from
  `hdl/chisel/src/bus` to a dedicated `tests/cocotb/tlul` directory.
- Reorganized the Bazel BUILD files to reflect the new test locations
  and improve dependency management.
- Added minor delays at the end of the `tlul2axi` tests to facilitate
  easier waveform debugging.

Change-Id: I9a2a3c6510d34b010e0ecc2ba1da1db3e1462f2b
diff --git a/hdl/chisel/src/bus/Axi2TLUL.scala b/hdl/chisel/src/bus/Axi2TLUL.scala
index f0a8e57..c5ab3d3 100644
--- a/hdl/chisel/src/bus/Axi2TLUL.scala
+++ b/hdl/chisel/src/bus/Axi2TLUL.scala
@@ -16,6 +16,7 @@
 
 import chisel3._
 import chisel3.util._
+import common.KelvinRRArbiter
 
 import kelvin.Parameters
 
@@ -47,39 +48,45 @@
   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
+  private def axiToTl(addr: AxiAddress, data: Option[AxiWriteData]): TileLink_A_ChannelBase[A_USER] = {
+    val tl_a = Wire(new TileLink_A_ChannelBase(tlul_p, userAGen))
+    tl_a.opcode  := data.map(_ => TLULOpcodesA.PutFullData.asUInt).getOrElse(TLULOpcodesA.Get.asUInt)
+    tl_a.param   := 0.U
+    tl_a.address := addr.addr
+    tl_a.source  := addr.id
+    tl_a.size    := addr.size
+    tl_a.mask    := data.map(_.strb).getOrElse(0.U(tlul_p.w.W))
+    tl_a.data    := data.map(_.data).getOrElse(0.U((8 * p.axi2DataBits).W))
+    tl_a.user    := 0.U.asTypeOf(io.tl_a.bits.user)
+    tl_a
+  }
 
-  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
+  val read_stream = read_addr_q.map(axiToTl(_, None))
 
-  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
+  val write_stream = Wire(Decoupled(new TileLink_A_ChannelBase(tlul_p, userAGen)))
+  write_stream.valid := write_addr_q.valid && write_data_q.valid
+  write_stream.bits  := axiToTl(write_addr_q.bits, Some(write_data_q.bits))
 
-  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)
+  // Reads are given higher priority.
+  val arb = Module(new KelvinRRArbiter(new TileLink_A_ChannelBase(tlul_p, userAGen), 2))
+  arb.io.in(0) <> read_stream
+  arb.io.in(1) <> write_stream
+  io.tl_a <> arb.io.out
+
+  write_addr_q.ready := write_stream.fire
+  write_data_q.ready := write_stream.fire
 
   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.write.resp.bits.resp := Mux(io.tl_d.bits.error, AxiResponseType.SLVERR.asUInt, AxiResponseType.OKAY.asUInt)
 
   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.resp := Mux(io.tl_d.bits.error, AxiResponseType.SLVERR.asUInt, AxiResponseType.OKAY.asUInt)
   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)
diff --git a/hdl/chisel/src/bus/BUILD b/hdl/chisel/src/bus/BUILD
index 48737ef..3e2be71 100644
--- a/hdl/chisel/src/bus/BUILD
+++ b/hdl/chisel/src/bus/BUILD
@@ -47,6 +47,13 @@
 )
 
 chisel_cc_library(
+    name = "axi2tlul_cc_library",
+    chisel_lib = ":bus",
+    emit_class = "bus.EmitAxi2TLUL",
+    module_name = "Axi2TLUL",
+)
+
+chisel_cc_library(
     name = "tlul2axi_cc_library",
     chisel_lib = ":bus",
     emit_class = "bus.EmitTLUL2Axi",
@@ -54,81 +61,19 @@
 )
 
 verilator_cocotb_model(
-    name = "tlul2axi_model",
-    hdl_toplevel = "TLUL2Axi",
-    verilog_source = "//hdl/chisel/src/bus:TLUL2Axi.sv",
-    cflags = [],
+    name = "axi2tlul_model",
+    cflags = VERILATOR_BUILD_ARGS,
+    hdl_toplevel = "Axi2TLUL",
     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",
+    verilog_source = "//hdl/chisel/src/bus:Axi2TLUL.sv",
 )
 
 verilator_cocotb_model(
-    name = "axi2tlul_model",
-    hdl_toplevel = "Axi2TLUL",
-    verilog_source = "//hdl/chisel/src/bus:Axi2TLUL.sv",
-    cflags = [],
+    name = "tlul2axi_model",
+    cflags = VERILATOR_BUILD_ARGS,
+    hdl_toplevel = "TLUL2Axi",
     trace = True,
-)
-
-# BEGIN_TESTCASES_FOR_axi2tlul_cocotb_test
-AXI2TLUL_TESTCASES = [
-    "test_write_request",
-    "test_read_request",
-    "test_read_error",
-]
-# 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",
+    verilog_source = "//hdl/chisel/src/bus:TLUL2Axi.sv",
 )
 
 chisel_cc_library(
@@ -189,4 +134,4 @@
     hdl_toplevel = "SecdedEncoderTestbench57",
     trace = True,
     verilog_source = "//hdl/chisel/src/bus:SecdedEncoderTestbench57.sv",
-)
\ No newline at end of file
+)
diff --git a/tests/cocotb/tlul/BUILD b/tests/cocotb/tlul/BUILD
index 9b16b9c..4740777 100644
--- a/tests/cocotb/tlul/BUILD
+++ b/tests/cocotb/tlul/BUILD
@@ -24,6 +24,73 @@
     "VCS_TEST_ARGS",
 )
 
+# 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 = "//hdl/chisel/src/bus:tlul2axi_model",
+    vcs_build_args = VCS_BUILD_ARGS,
+    vcs_test_args = VCS_TEST_ARGS,
+    vcs_defines = VCS_DEFINES,
+)
+
+# BEGIN_TESTCASES_FOR_axi2tlul_cocotb_test
+AXI2TLUL_TESTCASES = [
+    "test_write_request",
+    "test_read_request",
+    "test_read_error",
+]
+# 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 = "//hdl/chisel/src/bus:axi2tlul_model",
+    vcs_build_args = VCS_BUILD_ARGS,
+    vcs_test_args = VCS_TEST_ARGS,
+    vcs_defines = VCS_DEFINES,
+)
+
 # BEGIN_TESTCASES_FOR_tlul_integrity_cocotb_test
 TLUL_INTEGRITY_TESTCASES = [
     "test_request_integrity_gen",
diff --git a/hdl/chisel/src/bus/axi2tlul_cocotb_test.py b/tests/cocotb/tlul/axi2tlul_cocotb_test.py
similarity index 99%
rename from hdl/chisel/src/bus/axi2tlul_cocotb_test.py
rename to tests/cocotb/tlul/axi2tlul_cocotb_test.py
index e6b721c..2abe7f4 100644
--- a/hdl/chisel/src/bus/axi2tlul_cocotb_test.py
+++ b/tests/cocotb/tlul/axi2tlul_cocotb_test.py
@@ -179,6 +179,7 @@
     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 = 0
 
     dut.io_axi_read_data_ready.value = 1
 
diff --git a/hdl/chisel/src/bus/tlul2axi_cocotb_test.py b/tests/cocotb/tlul/tlul2axi_cocotb_test.py
similarity index 98%
rename from hdl/chisel/src/bus/tlul2axi_cocotb_test.py
rename to tests/cocotb/tlul/tlul2axi_cocotb_test.py
index e50b25b..e62a48f 100644
--- a/hdl/chisel/src/bus/tlul2axi_cocotb_test.py
+++ b/tests/cocotb/tlul/tlul2axi_cocotb_test.py
@@ -165,6 +165,7 @@
     assert not dut.io_tl_d_bits_error.value, "TL D_ERROR should be low"
     dut.io_tl_d_ready.value = 0
 
+    # Allow a few extra cycles for waveform viewing.
     await ClockCycles(dut.clock, 5)
 
 @cocotb.test()
@@ -240,6 +241,7 @@
     assert not dut.io_tl_d_bits_error.value, "TL D_ERROR should be low"
     dut.io_tl_d_ready.value = 0
 
+    # Allow a few extra cycles for waveform viewing.
     await ClockCycles(dut.clock, 5)
 
 
@@ -326,6 +328,7 @@
     assert not dut.io_tl_d_bits_error.value, "TL D_ERROR should be low"
     dut.io_tl_d_ready.value = 0
 
+    # Allow a few extra cycles for waveform viewing.
     await ClockCycles(dut.clock, 5)
 
 
@@ -417,4 +420,5 @@
     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
 
+    # Allow a few extra cycles for waveform viewing.
     await ClockCycles(dut.clock, 5)
\ No newline at end of file