Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions sim/simx/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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 $@
Expand Down Expand Up @@ -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)
Expand Down
41 changes: 34 additions & 7 deletions sim/simx/dxa/dxa_core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ class DxaCore::Impl {

// gmem_req → rsp_buf → smem_wr inflight bookkeeping.
std::array<InflightSlot, VX_CFG_DXA_MAX_INFLIGHT> inflight;
std::deque<uint32_t> issued_order; // tag order, FIFO drain
std::deque<uint32_t> 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()
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -573,19 +576,40 @@ 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.
if (w.ag_idx == w.work_list.size()) finish_worker(w);
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;
Expand Down Expand Up @@ -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;
}
}
}
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion tests/unittest/Makefile
Original file line number Diff line number Diff line change
@@ -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
Expand Down
19 changes: 19 additions & 0 deletions tests/unittest/dxa_ooo/Makefile
Original file line number Diff line number Diff line change
@@ -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
144 changes: 144 additions & 0 deletions tests/unittest/dxa_ooo/main.cpp
Original file line number Diff line number Diff line change
@@ -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 <algorithm>
#include <cstdint>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <vector>

#include "core.h"
#include "dxa_core.h"
#include "mem_block_pool.h"
#include "processor.h"
#include "simobject.h"
#include <VX_types.h>

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<mem_block_t> 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<MemReq> 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;
}