Files
TheRock/rocm-systems/projects/rocprof-trace-decoder/test/unit/mi400_test.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

694 lines
20 KiB
C++

/* Copyright (c) 2025 Advanced Micro Devices, Inc. */
#include <gtest/gtest.h>
#include <array>
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
#include "gfx10/gfx10wave.h"
#include "mi400/mi400parser.h"
#include "mi400/mi400token.h"
#include "mi400/mi400wave.h"
//=============================================================================
// MI400 Token Type Tests
//=============================================================================
TEST(MI400TokenTypeTest, WendTypeMi400GetMethod)
{
mi400::wend_type wend{};
wend.tm = 3;
wend.sa = 1;
wend.simd = 2;
wend.wgp = 5;
wend.wid = 20;
auto common = wend.get();
EXPECT_EQ(common.tm, wend.tm);
EXPECT_EQ(common.sa, wend.sa);
EXPECT_EQ(common.simd, wend.simd);
EXPECT_EQ(common.wgp, wend.wgp);
EXPECT_EQ(common.wid, wend.wid);
}
TEST(MI400TokenTypeTest, DiagnosticPrintsReportDecodedFields)
{
mi400::immed_one_type immed{};
immed.header = 0b1101;
immed.wid = 17;
mi400::misc_type misc{};
misc.header = 0b1010001;
misc.CLF = 1;
misc.CLID = 9;
mi400::header_type header{};
header.header = 0b0010001;
header.version = 5;
header.DWGP = 3;
header.DSIMD = 2;
header.DSA = 1;
header.UCF = 1;
header.DPRate = 4;
header.WSM = 2;
EXPECT_STREQ(immed.typestr(), "IMMEDONE");
EXPECT_NE(immed.print().str().find("wid:17"), std::string::npos);
EXPECT_STREQ(misc.typestr(), "MISC");
EXPECT_NE(misc.print().str().find("raw:0x"), std::string::npos);
EXPECT_STREQ(header.typestr(), "HEADER");
const std::string header_text = header.print().str();
EXPECT_NE(header_text.find("TT Version:5"), std::string::npos);
EXPECT_NE(header_text.find("DWGP:3"), std::string::npos);
EXPECT_NE(header_text.find("DSIMD:2"), std::string::npos);
EXPECT_NE(header_text.find("DSA:1"), std::string::npos);
EXPECT_NE(header_text.find("UCF:1"), std::string::npos);
EXPECT_NE(header_text.find("DPRate:4"), std::string::npos);
EXPECT_NE(header_text.find("WSM:2"), std::string::npos);
}
//=============================================================================
// MI400 Token Lookup Table Tests
//=============================================================================
TEST(MI400LookupTableTest, BasicEncodingLookups)
{
mi400::TokenLookupTable lookup;
EXPECT_EQ(lookup.lookup(0b0010).type, RdnaType::INST);
EXPECT_EQ(lookup.lookup(0b011).type, RdnaType::VALU_INST);
EXPECT_EQ(lookup.lookup(0b1000001).type, RdnaType::WAVE_END);
}
TEST(MI400LookupTableTest, GetTimeForTimestamp)
{
mi400::TokenLookupTable lookup;
gfx12::timestamp_type ts{};
ts.pl = 0;
ts.rt = 0;
ts.time = 1000;
bool packetlost = false;
int64_t realtime = 0;
int64_t cur_time = 500;
auto result = lookup.getTime(lookup.lookup(0b0000001), ts.raw, cur_time, packetlost, realtime);
EXPECT_EQ(result, ts.time + cur_time);
}
TEST(MI400LookupTableTest, GetTimeForRealtimeTimestamp)
{
mi400::TokenLookupTable lookup;
gfx12::timestamp_type ts{};
ts.pl = 0;
ts.rt = 1;
ts.time = 12345;
bool packetlost = false;
int64_t realtime = 0;
int64_t cur_time = 500;
auto result = lookup.getTime(lookup.lookup(0b0000001), ts.raw, cur_time, packetlost, realtime);
EXPECT_EQ(result, cur_time);
EXPECT_EQ(realtime, ts.time);
}
TEST(MI400LookupTableTest, GetTimeWithPacketLoss)
{
mi400::TokenLookupTable lookup;
gfx12::timestamp_type ts{};
ts.pl = 1;
ts.rt = 0;
ts.time = 1000;
bool packetlost = false;
int64_t realtime = 0;
int64_t cur_time = 500;
lookup.getTime(lookup.lookup(0b0000001), ts.raw, cur_time, packetlost, realtime);
EXPECT_TRUE(packetlost);
}
TEST(MI400LookupTableTest, GetTimeForTimeToken)
{
mi400::TokenLookupTable lookup;
bool packetlost = false;
int64_t realtime = 0;
int64_t cur_time = 500;
auto result = lookup.getTime(lookup.lookup(0b1110), 0, cur_time, packetlost, realtime);
EXPECT_GT(result, cur_time);
}
//=============================================================================
// MI400 Wave Tests
//=============================================================================
TEST(MI400WaveTest, MapToCommonTypeSalu)
{
auto mapped = mi400::map_to_common_type(0, 0); // salu
EXPECT_EQ(mapped.category, WaveInstCategory::SALU);
EXPECT_EQ(mapped.cycles, 1);
}
TEST(MI400WaveTest, MapToCommonTypeTrap)
{
auto mapped = mi400::map_to_common_type(6, 0); // trap
EXPECT_EQ(mapped.category, WaveInstCategory::TRAP);
EXPECT_EQ(mapped.cycles, 1);
}
TEST(MI400WaveTest, MapToCommonTypeValutWithTrans2)
{
// valut (11) returns 4 normally, but 2 with trans2=1
auto mapped1 = mi400::map_to_common_type(11, 0);
EXPECT_EQ(mapped1.category, WaveInstCategory::VALU);
EXPECT_EQ(mapped1.cycles, 4);
auto mapped2 = mi400::map_to_common_type(11, 1);
EXPECT_EQ(mapped2.category, WaveInstCategory::VALU);
EXPECT_EQ(mapped2.cycles, 2);
}
TEST(MI400WaveTest, MapToCommonTypeOtherSimdLds)
{
// einst 80-84 → LDS_OTHER_SIMD
for (int einst = 80; einst <= 84; einst++)
{
auto mapped = mi400::map_to_common_type(einst, 0);
EXPECT_EQ(mapped.category, WaveInstCategory::LDS_OTHER_SIMD) << "Failed for einst=" << einst;
EXPECT_GT(mapped.cycles, 0) << "Failed for einst=" << einst;
}
}
TEST(MI400WaveTest, MapToCommonTypeOtherSimdFlat)
{
// einst 85-89 → FLAT_OTHER_SIMD
for (int einst = 85; einst <= 89; einst++)
{
auto mapped = mi400::map_to_common_type(einst, 0);
EXPECT_EQ(mapped.category, WaveInstCategory::FLAT_OTHER_SIMD) << "Failed for einst=" << einst;
EXPECT_GT(mapped.cycles, 0) << "Failed for einst=" << einst;
}
}
TEST(MI400WaveTest, MapToCommonTypeOtherSimdRemainder)
{
// einst 90-102 → NONE (no range matches)
for (int einst = 90; einst <= 102; einst++)
{
auto mapped = mi400::map_to_common_type(einst, 0);
EXPECT_EQ(mapped.category, WaveInstCategory::NONE) << "Failed for einst=" << einst;
EXPECT_EQ(mapped.cycles, 0) << "Failed for einst=" << einst;
}
}
TEST(MI400WaveTest, MapToCommonTypeVmemOtherSimd)
{
// vmem_other_simd range (188 to 221) returns VMEM_OTHER_SIMD
for (int einst = 188; einst < 222; einst++)
{
auto mapped = mi400::map_to_common_type(einst, 0);
EXPECT_EQ(mapped.category, WaveInstCategory::VMEM_OTHER_SIMD) << "Failed for einst=" << einst;
EXPECT_GT(mapped.cycles, 0) << "Failed for einst=" << einst;
}
}
TEST(MI400WaveTest, MapToCommonTypeBlockStore)
{
auto mapped = mi400::map_to_common_type(222, 0);
EXPECT_EQ(mapped.category, WaveInstCategory::VMEM);
EXPECT_EQ(mapped.cycles, 1);
auto mapped2 = mi400::map_to_common_type(225, 0);
EXPECT_EQ(mapped2.category, WaveInstCategory::VMEM);
EXPECT_EQ(mapped2.cycles, 4);
}
TEST(MI400WaveTest, MapToCommonTypeWmma)
{
auto xdl4 = mi400::map_to_common_type(184, 0);
EXPECT_EQ(xdl4.category, WaveInstCategory::VALU);
EXPECT_EQ(xdl4.cycles, 4);
auto dp32 = mi400::map_to_common_type(187, 0);
EXPECT_EQ(dp32.category, WaveInstCategory::VALU);
EXPECT_EQ(dp32.cycles, 32);
}
//=============================================================================
// MI400 Token Generator Tests
//=============================================================================
class TestMI400TokenGenerator : public mi400::TokenGenerator
{
public:
using mi400::TokenGenerator::current;
using mi400::TokenGenerator::FIFO;
using mi400::TokenGenerator::TokenGenerator;
};
static std::array<uint8_t, 16> make_token_buffer(uint64_t raw, size_t num_bytes)
{
std::array<uint8_t, 16> buffer{};
for (size_t i = 0; i < num_bytes && i < buffer.size(); i++)
buffer[i] = static_cast<uint8_t>((raw >> (8 * i)) & 0xFF);
return buffer;
}
TEST(MI400TokenGeneratorTest, ConstructorThrowsOnNullBuffer)
{
EXPECT_THROW(mi400::TokenGenerator(nullptr, 10, 0, 0), std::exception);
}
TEST(MI400TokenGeneratorTest, ConstructorThrowsOnZeroSize)
{
uint8_t dummy[10] = {0};
EXPECT_THROW(mi400::TokenGenerator(dummy, 0, 0, 0), std::exception);
}
TEST(MI400TokenGeneratorTest, UpdateFifoAndGetValuInst)
{
uint8_t buffer[16] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
TestMI400TokenGenerator gen(buffer, sizeof(buffer), 0, 0);
gen.update_fifo(5);
EXPECT_EQ(gen.FIFO[0], 5);
gen.update_fifo(10);
EXPECT_EQ(gen.FIFO[0], 10);
EXPECT_EQ(gen.FIFO[1], 5);
gen.update_fifo(15);
EXPECT_EQ(gen.FIFO[0], 15);
EXPECT_EQ(gen.FIFO[1], 10);
EXPECT_EQ(gen.FIFO[2], 5);
// Set current to a valu_inst with wavetm=0 (maps to FIFO[0]=15)
mi400::valu_inst_type valu{};
valu.wavetm = 0;
gen.current = valu.raw;
EXPECT_EQ(gen.get_valu_inst_mi400(), 15);
// wavetm=4 maps to FIFO[1]=10
valu.wavetm = 4;
gen.current = valu.raw;
EXPECT_EQ(gen.get_valu_inst_mi400(), 10);
// wavetm=8 maps to FIFO[2]=5
valu.wavetm = 8;
gen.current = valu.raw;
EXPECT_EQ(gen.get_valu_inst_mi400(), 5);
}
TEST(MI400TokenGeneratorTest, FifoDuplicateMovesFront)
{
uint8_t buffer[16] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
TestMI400TokenGenerator gen(buffer, sizeof(buffer), 0, 0);
gen.update_fifo(5);
gen.update_fifo(10);
gen.update_fifo(15);
gen.update_fifo(5);
EXPECT_EQ(gen.FIFO[0], 5);
EXPECT_EQ(gen.FIFO[1], 15);
EXPECT_EQ(gen.FIFO[2], 10);
mi400::valu_inst_type valu{};
valu.wavetm = 0;
gen.current = valu.raw;
EXPECT_EQ(gen.get_valu_inst_mi400(), 5);
}
TEST(MI400TokenGeneratorTest, GetValuInstUsesHighWaveTmBuckets)
{
uint8_t buffer[16] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
TestMI400TokenGenerator gen(buffer, sizeof(buffer), 0, 0);
gen.FIFO = {9, 8, 7, 6, 5, -1};
mi400::valu_inst_type valu{};
valu.wavetm = 12; // maps to FIFO[3]
gen.current = valu.raw;
EXPECT_EQ(gen.get_valu_inst_mi400(), 6);
EXPECT_EQ(gen.FIFO[0], 6);
gen.FIFO = {9, 8, 7, 6, 5, -1};
valu.wavetm = 14; // maps to FIFO[4]
gen.current = valu.raw;
EXPECT_EQ(gen.get_valu_inst_mi400(), 5);
EXPECT_EQ(gen.FIFO[0], 5);
}
TEST(MI400TokenGeneratorTest, NextInstPathsUpdateFifo)
{
struct inst_case_t
{
bool has_prefix;
uint8_t prefix;
size_t buffer_size;
int wid;
int inst;
int64_t min_time;
};
const std::array<inst_case_t, 4> cases{
inst_case_t{false, 0, 16, 13, 10, 0}, // unsafe path
inst_case_t{false, 0, 3, 5, 11, 0}, // safe path
inst_case_t{true, 0x00, 16, 7, 12, 0}, // NOP skip then INST
inst_case_t{true, 0x0E, 16, 6, 13, 1}, // TIME skip then INST
};
for (const auto& c : cases)
{
gfx12::inst_type inst{};
inst.header = 2; // low 4 bits become 0b0010 -> INST token type
inst.tm = 0;
inst.w64h = 0;
inst.wid = c.wid;
inst.inst = c.inst;
std::array<uint8_t, 16> buffer{};
size_t offset = 0;
if (c.has_prefix)
{
buffer[0] = c.prefix;
offset = 1;
}
buffer[offset + 0] = static_cast<uint8_t>(inst.raw & 0xFF);
buffer[offset + 1] = static_cast<uint8_t>((inst.raw >> 8) & 0xFF);
buffer[offset + 2] = static_cast<uint8_t>((inst.raw >> 16) & 0xFF);
TestMI400TokenGenerator gen(buffer.data(), c.buffer_size, 0, 0);
auto token = gen.next();
EXPECT_EQ(token.type, RdnaType::INST);
EXPECT_EQ(gen.FIFO[0], c.wid);
EXPECT_GE(token.time, c.min_time);
}
}
TEST(MI400TokenGeneratorTest, NextValuAndLongTokenPaths)
{
// VALU_INST remaps wave id from FIFO
{
mi400::valu_inst_type valu{};
valu.header = 3; // low 3 bits become 0b011 -> VALU_INST token type
valu.tp = 0;
valu.wavetm = 0;
auto buffer = make_token_buffer(valu.raw, 1);
TestMI400TokenGenerator gen(buffer.data(), buffer.size(), 0, 0);
gen.FIFO[0] = 9;
auto token = gen.next();
EXPECT_EQ(token.type, RdnaType::VALU_INST);
::valu_inst_type mapped{};
mapped.raw = token.contents;
EXPECT_EQ(mapped.wid, 9);
}
// NEW_PC_GFX12 path reads the extra byte when token length is > 64 bits
{
std::array<uint8_t, 16> buffer{};
buffer[0] = 0x21; // NEW_PC_GFX12 encoding: {1,0,0,0,0,1,0}
buffer[8] = 0xAB; // consumed by bits_toread > 64 path
TestMI400TokenGenerator gen(buffer.data(), buffer.size(), 0, 0);
auto token = gen.next();
EXPECT_EQ(token.type, RdnaType::NEW_PC_GFX12);
EXPECT_EQ(token.contents, 0xAB00000000000000ull);
}
}
TEST(MI400TokenGeneratorTest, SafeReaderHandlesRealtimeValuAndExtendedPcTokens)
{
{
gfx12::timestamp_type ts{};
ts.header = 0b0000001;
ts.rt = 1;
ts.pl = 0;
ts.time = 0x123456789ull;
std::array<uint8_t, sizeof(uint64_t)> buffer{};
std::memcpy(buffer.data(), &ts.raw, sizeof(ts.raw));
mi400::TokenGenerator gen(buffer.data(), buffer.size(), 0, 0);
auto token = gen.next();
EXPECT_EQ(token.type, RdnaType::TIMESTAMP);
ASSERT_EQ(gen.realtime.size(), 1u);
EXPECT_EQ(gen.realtime.front().shader_clock, 0);
EXPECT_EQ(gen.realtime.front().realtime_clock, ts.time);
}
{
mi400::valu_inst_type valu{};
valu.header = 0b011;
valu.wavetm = 0;
std::array<uint8_t, 1> buffer{static_cast<uint8_t>(valu.raw)};
TestMI400TokenGenerator gen(buffer.data(), buffer.size(), 0, 0);
gen.FIFO[0] = 9;
auto token = gen.next();
EXPECT_EQ(token.type, RdnaType::VALU_INST);
EXPECT_EQ(token.time, 1);
::valu_inst_type mapped{};
mapped.raw = token.contents;
EXPECT_EQ(mapped.wid, 9);
}
{
std::array<uint8_t, 9> buffer{};
buffer[0] = 0x21; // NEW_PC_GFX12 encoding: {1,0,0,0,0,1,0}
buffer[8] = 0xAB;
TestMI400TokenGenerator gen(buffer.data(), buffer.size(), 0, 0);
auto token = gen.next();
EXPECT_EQ(token.type, RdnaType::NEW_PC_GFX12);
EXPECT_EQ(token.contents, 0xAB00000000000000ull);
}
}
TEST(MI400TokenGeneratorTest, EmptyBufferTerminates)
{
uint8_t buffer[1] = {0};
mi400::TokenGenerator gen(buffer, 1, 0, 0);
int count = 0;
while (gen.nextValid() && count < 100)
{
gen.next();
count++;
}
EXPECT_LT(count, 100);
}
//=============================================================================
// MI400 XNACK tests
//=============================================================================
// Helper: simulate what rdna_sqtt.cpp does for an MI400 INST token
static void apply_mi400_inst(gfx10::wave_t& wave, int einst, int64_t time, int derate)
{
mapped_inst_t mapped{WaveInstCategory::NONE, 0};
try
{
mapped = mi400::map_to_common_type(einst, derate);
}
catch (std::exception&)
{
mi400::handle_xnack_rewind(wave);
return;
}
if (mapped.category == WaveInstCategory::LD_SCALE)
{
wave.next_ld_scale = true;
return;
}
int64_t inst_time = time;
if (wave.next_ld_scale)
{
wave.next_ld_scale = false;
inst_time -= 1;
mapped.cycles++;
}
wave.apply_inst(inst_time, einst, mapped, 5);
}
TEST(MI400xnack, InstReplayThrowsForXnack) { EXPECT_THROW(mi400::map_to_common_type(79, 0), std::exception); }
TEST(MI400xnack, XnackRewindRemovesImmedInstructions)
{
gfx10::Token token{};
token.time = 100;
token.type = RdnaType::WAVE_START;
pcinfo_t addr{1, 0x1000};
gfx10::wave_t wave(0, 0, 0, addr, token, false);
wave.trap_status = WaveTrapStatus::TRAP_RESTORED;
// SALU
apply_mi400_inst(wave, 0, 150, 0);
// VMEM (buf_rd_1 = 47)
apply_mi400_inst(wave, 47, 200, 0);
// IMMED (barrier_wait = 19)
apply_mi400_inst(wave, 19, 300, 0);
apply_mi400_inst(wave, 19, 400, 0);
size_t instCountBefore = wave.instructions.size();
EXPECT_GE(instCountBefore, 1);
int immedCountBefore = 0;
for (const auto& instr : wave.instructions)
if (instr.category == (uint32_t) WaveInstCategory::IMMED) immedCountBefore++;
// Trigger XNACK rewind with replay instruction (79)
apply_mi400_inst(wave, 79, 500, 0);
int immedCountAfter = 0;
for (const auto& instr : wave.instructions)
if (instr.category == (uint32_t) WaveInstCategory::IMMED) immedCountAfter++;
EXPECT_EQ(immedCountAfter, 0) << "XNACK rewind should remove all trailing IMMED instructions";
EXPECT_EQ(wave.cur_state, WaveslotState::WS_STALL) << "Wave should be stalled after XNACK rewind";
// Now add a VMEM instruction after the stall
apply_mi400_inst(wave, 47, 700, 0);
ASSERT_FALSE(wave.instructions.empty());
const auto& lastInst = wave.instructions.back();
EXPECT_EQ(lastInst.category, (uint32_t) WaveInstCategory::VMEM);
int64_t expectedStall = 700 - 500;
EXPECT_GE((int64_t) lastInst.stall, expectedStall - 1) << "Stall should reflect time since XNACK rewind";
}
TEST(MI400xnack, XnackRecoversFromWaveReady)
{
gfx10::Token token{};
token.time = 10;
token.type = RdnaType::WAVE_START;
pcinfo_t addr{1, 0x1000};
gfx10::wave_t wave(0, 0, 0, addr, token, false);
wave.trap_status = WaveTrapStatus::TRAP_RESTORED;
wave.apply_immediate(20);
// salu
apply_mi400_inst(wave, 0, 30, 1);
// buf_rd_1
wave.apply_wave_rdy(40);
apply_mi400_inst(wave, 47, 40, 1);
wave.apply_wave_rdy(40);
wave.apply_immediate(50);
wave.apply_immediate(60);
// replay -> XNACK rewind
apply_mi400_inst(wave, 79, 70, 0);
int immedCountAfter = 0;
for (const auto& instr : wave.instructions)
if (instr.category == (uint32_t) WaveInstCategory::IMMED) immedCountAfter++;
EXPECT_EQ(immedCountAfter, 1) << "XNACK rewind should remove all trailing IMMED instructions";
EXPECT_EQ(wave.cur_state, WaveslotState::WS_STALL);
EXPECT_EQ(wave.instructions.size(), 2) << "We only expect SALU and IMMED";
EXPECT_EQ(wave.instructions.back().category, (uint32_t) WaveInstCategory::SALU) << "Last=SALU";
EXPECT_EQ(wave.timeline.size(), 3) << "We only expect Idle -> Wait -> Exec";
}
TEST(MI400xnack, MultipleXnack)
{
gfx10::Token token{};
token.time = 10;
token.type = RdnaType::WAVE_START;
pcinfo_t addr{1, 0x1000};
gfx10::wave_t wave(0, 0, 0, addr, token, false);
wave.trap_status = WaveTrapStatus::TRAP_RESTORED;
// salu
apply_mi400_inst(wave, 0, 10, 1);
for (int i = 0; i < 19; i++)
{
int64_t base_time = i * 20 + 20;
// buf_rd_1
if (i % 2) wave.apply_wave_rdy(base_time);
apply_mi400_inst(wave, 47, base_time, 1);
if (i % 3) wave.apply_wave_rdy(base_time);
if (i % 4) wave.apply_immediate(base_time + 5);
// replay -> XNACK rewind
apply_mi400_inst(wave, 79, base_time + 19, 0);
// buf_rd_1 after XNACK
apply_mi400_inst(wave, 47, base_time + 19, 1);
}
int immedCountAfter = 0;
for (const auto& instr : wave.instructions)
if (instr.category == (uint32_t) WaveInstCategory::IMMED) immedCountAfter++;
EXPECT_EQ(immedCountAfter, 0);
EXPECT_EQ(wave.cur_state, WaveslotState::WS_EXEC);
EXPECT_EQ(wave.instructions.size(), 20) << "We expect SALU and 19xVMEM";
EXPECT_EQ(wave.instructions.front().category, (uint32_t) WaveInstCategory::SALU);
EXPECT_EQ(wave.instructions.back().category, (uint32_t) WaveInstCategory::VMEM);
int exec_time = 0;
for (size_t i = 1; i < wave.timeline.size(); i++)
if (wave.timeline[i].type != WaveslotState::WS_STALL) exec_time += wave.timeline[i].duration;
EXPECT_LE(exec_time, 20) << "Execution time should be small!";
}
TEST(MI400xnack, WaveReadyAfterXnack)
{
gfx10::Token token{};
token.time = 10;
token.type = RdnaType::WAVE_START;
pcinfo_t addr{1, 0x1000};
gfx10::wave_t wave(0, 0, 0, addr, token, false);
wave.trap_status = WaveTrapStatus::TRAP_RESTORED;
// salu
apply_mi400_inst(wave, 0, 20, 0);
// replay -> XNACK rewind
apply_mi400_inst(wave, 79, 30, 0);
// buf_rd_1 with wave_rdy
wave.apply_wave_rdy(30);
apply_mi400_inst(wave, 47, 30, 1);
wave.apply_wave_rdy(30);
EXPECT_EQ(wave.cur_state, WaveslotState::WS_EXEC);
}