diff --git a/sim/simx/Makefile b/sim/simx/Makefile index 2e935fd9bb..66eb0dda39 100644 --- a/sim/simx/Makefile +++ b/sim/simx/Makefile @@ -138,8 +138,10 @@ SIM_COMMON_OBJS := $(patsubst $(SIM_COMMON_DIR)/%.cpp,$(OBJ_DIR)/sim_common/%.o, SRC_OBJS := $(patsubst $(SRC_DIR)/%.cpp,$(OBJ_DIR)/%.o,$(SRC_SRCS)) OBJS := $(COMMON_OBJS) $(SIM_COMMON_OBJS) $(SRC_OBJS) MAIN_OBJ := $(OBJ_DIR)/main.o +DXA_OOO_TEST_OBJ := $(OBJ_DIR)/tests/dxa_ooo_test.o +DXA_OOO_TEST := $(DESTDIR)/dxa_ooo_test -DEPS := $(OBJS:.o=.d) $(MAIN_OBJ:.o=.d) +DEPS := $(OBJS:.o=.d) $(MAIN_OBJ:.o=.d) $(DXA_OOO_TEST_OBJ:.o=.d) # generate .d files alongside .o files CXXFLAGS += -MMD -MP -MF $(@:.o=.d) @@ -162,7 +164,7 @@ PROJECT := simx VORTEX_LIB := libvortex.so VORTEX_GEM5_LIB := libvortex-gem5.so -.PHONY: all force clean clean-lib clean-exe clean-obj libvortex clean-libvortex libvortex-gem5 clean-libvortex-gem5 +.PHONY: all force test-dxa-ooo clean clean-lib clean-exe clean-obj libvortex clean-libvortex libvortex-gem5 clean-libvortex-gem5 ifeq ($(USE_SST), 1) all: $(DESTDIR)/$(PROJECT) $(DESTDIR)/$(VORTEX_LIB) @@ -197,10 +199,20 @@ $(MAIN_OBJ): $(SRC_DIR)/main.cpp $(CONFIG_FILE) @mkdir -p $(@D) $(CXX) $(CXXFLAGS) -c $< -o $@ +$(DXA_OOO_TEST_OBJ): $(VORTEX_HOME)/tests/unittest/dxa_ooo/main.cpp $(CONFIG_FILE) + @mkdir -p $(@D) + $(CXX) $(CXXFLAGS) -c $< -o $@ + # Main executable $(DESTDIR)/$(PROJECT): $(OBJS) $(MAIN_OBJ) $(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@ +test-dxa-ooo: $(DXA_OOO_TEST) + $(DXA_OOO_TEST) + +$(DXA_OOO_TEST): $(OBJS) $(DXA_OOO_TEST_OBJ) + $(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@ + # Shared library $(DESTDIR)/lib$(PROJECT).so: $(OBJS) $(CXX) $(CXXFLAGS) $^ -shared $(LDFLAGS) -o $@ @@ -245,7 +257,7 @@ clean-libvortex-gem5: rm -f $(DESTDIR)/$(VORTEX_GEM5_LIB) clean-exe: - rm -f $(DESTDIR)/$(PROJECT) + rm -f $(DESTDIR)/$(PROJECT) $(DXA_OOO_TEST) clean-obj: rm -rf $(OBJ_DIR) diff --git a/sim/simx/dxa/dxa_core.cpp b/sim/simx/dxa/dxa_core.cpp index 3578cd6107..39901de5c6 100644 --- a/sim/simx/dxa/dxa_core.cpp +++ b/sim/simx/dxa/dxa_core.cpp @@ -116,7 +116,8 @@ class DxaCore::Impl { // gmem_req → rsp_buf → smem_wr inflight bookkeeping. std::array inflight; - std::deque issued_order; // tag order, FIFO drain + std::deque issued_order; // outstanding tags in issue order + uint32_t drain_slot = UINT32_MAX; // slot currently draining // smem_wr multicast replay state. uint32_t mc_cta_idx = 0; // 0..cta_indices.size() @@ -144,6 +145,7 @@ class DxaCore::Impl { w.work_list.clear(); w.cta_indices.clear(); w.issued_order.clear(); + w.drain_slot = UINT32_MAX; for (auto& s : w.inflight) { s.allocated = false; s.rsp_arrived = false; s.rsp_data.reset(); } w.ag_idx = 0; w.mc_cta_idx = 0; @@ -334,6 +336,7 @@ class DxaCore::Impl { w.work_list.clear(); w.ag_idx = 0; w.issued_order.clear(); + w.drain_slot = UINT32_MAX; for (auto& s : w.inflight) { s.allocated = false; s.rsp_arrived = false; s.rsp_data.reset(); } w.mc_cta_idx = 0; w.km_elem_idx = 0; @@ -573,9 +576,6 @@ class DxaCore::Impl { } // ── smem_wr: drain ready slots, build LMEM MemReqs ─────────────────── - // - // In-order: consume issued_order.front(); stall on the head until its - // GMEM response has arrived. void tick_worker_smem_wr(Worker& w) { if (w.issued_order.empty()) { // Nothing in flight — if all addr_gen done, transfer is finished. @@ -583,9 +583,33 @@ class DxaCore::Impl { return; } - uint32_t slot = w.issued_order.front(); + // Match the RTL's OoO direct-drain behavior: select any ready non-last + // slot without waiting for older requests. Once selected, keep draining + // the same slot because the multicast/scatter cursors are worker-local. + // The last work item carries notify_done and must remain deferred until + // it is the only outstanding slot. + uint32_t slot = w.drain_slot; + if (slot == UINT32_MAX) { + for (uint32_t i = 0; i < w.inflight.size(); ++i) { + const auto& candidate = w.inflight[i]; + if (candidate.allocated && candidate.rsp_arrived && !candidate.work.last) { + slot = i; + break; + } + } + + if (slot == UINT32_MAX && w.issued_order.size() == 1) { + uint32_t last_slot = w.issued_order.front(); + const auto& candidate = w.inflight[last_slot]; + if (candidate.allocated && candidate.rsp_arrived) + slot = last_slot; + } + + if (slot == UINT32_MAX) return; + w.drain_slot = slot; + } + auto& s = w.inflight[slot]; - if (!s.rsp_arrived) return; // wait // Determine destination core's LMEM port. uint32_t cluster_local_cid = w.req.core->id() % kCoresPerCluster; @@ -694,7 +718,9 @@ class DxaCore::Impl { s.allocated = false; s.rsp_arrived = false; s.rsp_data.reset(); - w.issued_order.pop_front(); + auto it = std::find(w.issued_order.begin(), w.issued_order.end(), slot); + if (it != w.issued_order.end()) w.issued_order.erase(it); + w.drain_slot = UINT32_MAX; } } } @@ -725,6 +751,7 @@ class DxaCore::Impl { w.work_list.clear(); w.cta_indices.clear(); w.issued_order.clear(); + w.drain_slot = UINT32_MAX; w.ag_idx = 0; w.mc_cta_idx = 0; w.km_elem_idx = 0; diff --git a/tests/unittest/Makefile b/tests/unittest/Makefile index 64a936e04c..936a2a4454 100644 --- a/tests/unittest/Makefile +++ b/tests/unittest/Makefile @@ -1,7 +1,7 @@ ROOT_DIR := $(realpath ../..) include $(ROOT_DIR)/config.mk -PROJECTS := vx_malloc gfx_binsort gfx_tex_sw gfx_rast_sw gfx_msaa gfx_om_mrt +PROJECTS := vx_malloc dxa_ooo gfx_binsort gfx_tex_sw gfx_rast_sw gfx_msaa gfx_om_mrt all: $(foreach p,$(PROJECTS),$(MAKE) -C $(p) &&) true diff --git a/tests/unittest/dxa_ooo/Makefile b/tests/unittest/dxa_ooo/Makefile new file mode 100644 index 0000000000..b6ee9fb5e6 --- /dev/null +++ b/tests/unittest/dxa_ooo/Makefile @@ -0,0 +1,19 @@ +ROOT_DIR := $(realpath ../../..) +include $(ROOT_DIR)/config.mk + +PROJECT := dxa_ooo + +SIMX_DIR := $(ROOT_DIR)/sim/simx + +CONFIGS := $(if $(findstring -DVX_CFG_EXT_DXA_ENABLE,$(CONFIGS)),$(CONFIGS),$(CONFIGS) -DVX_CFG_EXT_DXA_ENABLE) + +.PHONY: all run clean + +all: + $(MAKE) -C $(SIMX_DIR) $(SIMX_DIR)/dxa_ooo_test CONFIGS="$(CONFIGS)" + +run: + $(MAKE) -C $(SIMX_DIR) test-dxa-ooo CONFIGS="$(CONFIGS)" + +clean: + rm -f $(SIMX_DIR)/dxa_ooo_test $(SIMX_DIR)/obj/tests/dxa_ooo_test.o $(SIMX_DIR)/obj/tests/dxa_ooo_test.d diff --git a/tests/unittest/dxa_ooo/main.cpp b/tests/unittest/dxa_ooo/main.cpp new file mode 100644 index 0000000000..5f79dc5458 --- /dev/null +++ b/tests/unittest/dxa_ooo/main.cpp @@ -0,0 +1,144 @@ +// Copyright © 2019-2023 +// +// 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. + +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "dxa_core.h" +#include "mem_block_pool.h" +#include "processor.h" +#include "simobject.h" +#include + +using namespace vortex; + +namespace { + +[[noreturn]] void fail(const char *message) { + std::cerr << "FAILED: " << message << std::endl; + std::exit(1); +} + +void tick(uint32_t cycles = 1) { + for (uint32_t i = 0; i < cycles; ++i) + SimPlatform::instance().tick(); +} + +MemReq wait_for_lmem_write(DxaCore *dxa, uint32_t limit = 100) { + for (uint32_t i = 0; i < limit; ++i) { + tick(); + auto &output = dxa->lmem_req_out.at(0); + if (!output.empty()) { + MemReq req = output.peek(); + output.pop(); + return req; + } + } + fail("timed out waiting for LMEM write"); +} + +std::shared_ptr make_response_data(uint8_t value) { + auto data = make_mem_block(); + std::fill(data->begin(), data->end(), value); + return data; +} + +void send_response(DxaCore *dxa, const MemReq &request, uint8_t value) { + dxa->gmem_rsp_in.at(0).send( + MemRsp(request.tag, 0, 1, make_response_data(value))); +} + +void check_write(const MemReq &write, uint64_t expected_addr, + uint8_t expected_data, bool expected_notify) { + if (write.addr != expected_addr) + fail("response wrote an unexpected LMEM address"); + if (!write.data || write.data->at(0) != expected_data) + fail("response wrote unexpected data"); + if (write.flags.dxa_notify_done != expected_notify) + fail("response carried an unexpected completion flag"); +} + +} // namespace + +int main() { + Processor processor; + auto *core = processor.get_first_core(); + auto dxa = DxaCore::Create("dxa_ooo_test", nullptr); + + SimPlatform::instance().reset(); + + constexpr uint32_t slot = 0; + constexpr uint32_t line_size = VX_CFG_L1_LINE_SIZE; + constexpr uint32_t transfer_size = 3 * line_size; + constexpr uint32_t gmem_base = 16 * line_size; + constexpr uint32_t smem_base = 8 * line_size; + constexpr uint32_t dcr = VX_DCR_DXA_DESC_BASE + slot * VX_DCR_DXA_DESC_STRIDE; + dxa->dcr_write(dcr + VX_DCR_DXA_DESC_BASE_LO_OFF, gmem_base); + dxa->dcr_write(dcr + VX_DCR_DXA_DESC_BASE_HI_OFF, 0); + dxa->dcr_write(dcr + VX_DCR_DXA_DESC_SIZE0_OFF, transfer_size); + dxa->dcr_write(dcr + VX_DCR_DXA_DESC_META_OFF, 1); + dxa->dcr_write(dcr + VX_DCR_DXA_DESC_ESTRIDE0_OFF, 1); + dxa->dcr_write(dcr + VX_DCR_DXA_DESC_TILESIZE01_OFF, transfer_size); + + DxaReq req{}; + req.core = core; + req.uuid = 1; + req.desc_slot = slot; + req.cta_mask = 1; + req.smem_addr = smem_base; + dxa->dxa_req_in.at(0).send(req); + + std::vector reads; + for (uint32_t i = 0; i < 100 && reads.size() < 3; ++i) { + tick(); + auto &output = dxa->gmem_req_out.at(0); + if (!output.empty()) { + reads.push_back(output.peek()); + output.pop(); + } + } + + if (reads.size() != 3) + fail("expected exactly three GMEM reads"); + if (reads.at(0).addr != gmem_base || + reads.at(1).addr != gmem_base + line_size || + reads.at(2).addr != gmem_base + 2 * line_size) + fail("unexpected GMEM request addresses"); + + // C is the last work item. Even if it returns first, its completion write + // must remain deferred while A and B are outstanding. + send_response(dxa.get(), reads.at(2), 0xcc); + tick(8); + if (!dxa->lmem_req_out.at(0).empty()) + fail("last response drained before older outstanding requests"); + + // B is ready and is not the last work item, so it must bypass A. + send_response(dxa.get(), reads.at(1), 0xbb); + check_write(wait_for_lmem_write(dxa.get()), smem_base + line_size, 0xbb, + false); + + // Once A completes, the previously deferred C response must drain last. + send_response(dxa.get(), reads.at(0), 0xaa); + check_write(wait_for_lmem_write(dxa.get()), smem_base, 0xaa, false); + check_write(wait_for_lmem_write(dxa.get()), smem_base + 2 * line_size, 0xcc, + true); + + std::cout << "PASSED" << std::endl; + return 0; +}