Skip to content
Merged
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
63 changes: 63 additions & 0 deletions emulation/rocjitsu/configs/gfx1251_synthetic.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"max_ticks": 10000,
"exec_mode": "functional",
"vm": {
"arch": "cdna5",
"target": "gfx1251",
"gpu": {
"device": {
"gfx_target_version": 120501,
"marketing_name": "Synthetic gfx1251 functional simulator target",
"simd_count": 4,
"num_sdma_engines": 1,
"num_sdma_queues_per_engine": 1,
"num_shader_engines": 1,
"num_shader_arrays_per_engine": 1,
"num_cu_per_sh": 1,
"simd_per_cu": 4,
"wave_front_size": 32
}
}
},
"topology": {
"root": {
"name": "soc",
"type": "soc",
"children": [
{ "name": "vram", "type": "gpu_memory" },
{
"name": "iod0",
"type": "iod",
"config": [{ "key": "num_hbm_stacks", "value": "1" }]
},
{
"name": "xcd0",
"type": "xcd",
"children": [
{ "name": "l2", "type": "l2_cache" },
{ "name": "cp", "type": "command_processor" },
{
"name": "se0",
"type": "shader_engine",
"children": [{
"name": "cu0",
"type": "compute_unit",
"config": [
{ "key": "num_wf_slots", "value": "2" },
{ "key": "sgprs_per_wf", "value": "128" },
{ "key": "vgprs_per_wf", "value": "32" },
{ "key": "lds_size_kb", "value": "64" }
]
}]
}
]
}
]
},
"links": [
{ "src": "xcd0.cp.req_0", "dst": "xcd0.se0.cu0.cpl", "latency": 1, "weight": 2 },
{ "src": "xcd0.se0.cu0.req", "dst": "xcd0.l2.cpl_0", "latency": 1, "weight": 10 },
{ "src": "xcd0.l2.req", "dst": "iod0.msc.cpl_0", "latency": 1, "weight": 3 }
]
}
}
11 changes: 10 additions & 1 deletion emulation/rocjitsu/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Pre-built simulator configs are in `configs/`:
| `gfx950_mi355x_kmd_2gpu.json` | Two CDNA4 GPUs (multi-GPU daemon mode) |
| `gfx1250_mi455x.json` | Single CDNA5 GPU (standalone or PCI/VFIO simulation) |
| `gfx1250_mi455x_kmd_4gpu.json` | Four MI455X GPUs (multi-GPU daemon mode) |
| `gfx1251_synthetic.json` | Minimal synthetic gfx1251 topology for functional simulation; not a product model |
| `gfx1100_w7900.json` | Single RDNA3 GPU (standalone simulation) |
| `gfx1151.json` | Single RDNA3.5 GPU (standalone simulation) |
| `gfx1201_r9700.json` | Single RDNA4 GPU (standalone simulation) |
Expand Down Expand Up @@ -93,7 +94,15 @@ The example above is intentionally minimal.
| `async_helper_threads` | int | Shared MMA helpers per VM. Omitted/-1 selects the table; 0 disables; explicit values are 0–128. |
| `thread_allocations` | array | Preferred `num_threads` / `cpu_dispatch_threads` / `async_helper_threads` triples, selected by total execution-thread cost. |
| `exec_mode` | string | Execution mode. Use `"clocked"` for clocked execution; `"functional"` is the default/fallback. |
| `vm.arch` | string | Architecture: `cdna3`, `cdna4`, etc. |
| `vm.arch` | string | ISA architecture family: `cdna3`, `cdna4`, `cdna5`, etc. |
| `vm.target` | string | Optional concrete GPU target, such as `gfx1250` or `gfx1251`. |

`vm.target` selects target-specific instruction legality and behavior within an
ISA architecture family. When it is present, it must belong to `vm.arch`. If
both the target binding and `vm.gpu.device.gfx_target_version` provide nonzero
packed versions, they must match. CDNA5 currently defaults an omitted target to
`gfx1250` for compatibility; new configs for an architecture with multiple
concrete targets should specify the target explicitly.

`exec_mode` is matched literally: only the exact string `"clocked"` selects
clocked mode. If the field is omitted, set to `"functional"`, or given any
Expand Down
6 changes: 3 additions & 3 deletions emulation/rocjitsu/docs/sphinx/conceptual/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ amdisa Python codegen pipeline (`lib/python/amdisa/`). The layer contains
per-architecture subdirectories with decoders, encoding structs, and
execution bodies, along with hand-written files for address calculation,
matrix math, and ISA-specific traits. Concrete CDNA5 bindings distinguish
gfx1250 from gfx1251 for target legality and behavior. gfx1251 is available
to decode and inspection tooling, but full simulator dispatch remains
disabled until its execution binding is complete.
gfx1250 from gfx1251 for target legality and behavior. Both concrete targets
have full functional-execution bindings; decoder-only consumers select the
separate model provider, which omits execution callbacks for both targets.

### VM and hardware model

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ The `rj_code_arch_e` enumeration identifies an ISA architecture throughout the r

The `rj_code_target_id_t` enumeration identifies a specific GPU target within an architecture. Use these values when filtering code objects inside an executable or creating instruction lists. For the full enumeration, see [API reference: code object](/reference/api-code-object.md).

gfx1250 is the CDNA5 simulator target. gfx1251 has a concrete target binding
for decode, inspection, and target-aware DBT identity, but its full simulator
binding remains disabled. Simulator configuration rejects targets whose
`execution_implemented` capability is false.
gfx1250 and gfx1251 both have concrete functional-simulator bindings. The
checked-in gfx1251 configuration is a minimal synthetic topology for functional
execution, not a product model or a claim of cycle/timing fidelity. Decoder-only
consumers use a separate model provider whose instructions have no execution
callbacks.

## ISA family groupings

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

namespace rocjitsu {
Expand Down Expand Up @@ -371,25 +372,49 @@ bool object_defines_only_kernels(const AmdGpuCodeObject &object) {
function_names, [&](std::string_view name) { return descriptor_names.contains(name); });
}

static std::vector<uint64_t> discover_text_function_symbol_offsets(const AmdGpuCodeObject &object,
bool externally_resolvable_only);
// Shared ELF walk for the offset and range APIs. It validates function entries but deliberately
// preserves each nonzero st_size verbatim: existing offset consumers do not depend on the rest of
// the extent being valid, while discover_text_function_symbol_ranges() applies that stricter
// contract before exposing ranges.
static std::vector<TextFunctionSymbolRange>
discover_text_function_symbol_candidates(const AmdGpuCodeObject &object,
bool externally_resolvable_only);

std::vector<uint64_t>
discover_externally_resolvable_text_function_offsets(const AmdGpuCodeObject &object) {
std::vector<uint64_t> offsets;
for (uint64_t offset :
discover_text_function_symbol_offsets(object, /*externally_resolvable_only=*/true))
offsets.push_back(offset);
for (const TextFunctionSymbolRange &range :
discover_text_function_symbol_candidates(object, /*externally_resolvable_only=*/true))
offsets.push_back(range.start_offset);
offsets.erase(std::ranges::unique(offsets).begin(), offsets.end());
return offsets;
}

std::vector<uint64_t> discover_text_function_symbol_offsets(const AmdGpuCodeObject &object) {
return discover_text_function_symbol_offsets(object, /*externally_resolvable_only=*/false);
std::vector<uint64_t> offsets;
for (const TextFunctionSymbolRange &range :
discover_text_function_symbol_candidates(object, /*externally_resolvable_only=*/false))
offsets.push_back(range.start_offset);
offsets.erase(std::ranges::unique(offsets).begin(), offsets.end());
return offsets;
}

static std::vector<uint64_t>
discover_text_function_symbol_offsets(const AmdGpuCodeObject &object,
bool externally_resolvable_only) {
std::vector<TextFunctionSymbolRange>
discover_text_function_symbol_ranges(const AmdGpuCodeObject &object) {
std::vector<TextFunctionSymbolRange> ranges =
discover_text_function_symbol_candidates(object, /*externally_resolvable_only=*/false);
if (object.text_sections().size() != 1)
return {};
const uint64_t text_size = object.text_sections().front()->size();
std::erase_if(ranges, [text_size](const TextFunctionSymbolRange &range) {
return (range.size % sizeof(uint32_t)) != 0 || range.size > text_size - range.start_offset;
});
return ranges;
}

static std::vector<TextFunctionSymbolRange>
discover_text_function_symbol_candidates(const AmdGpuCodeObject &object,
bool externally_resolvable_only) {
const auto *bytes = reinterpret_cast<const uint8_t *>(object.image_data());
const std::span<const uint8_t> image(bytes, object.image_size());
Elf64_Ehdr ehdr{};
Expand All @@ -412,7 +437,7 @@ discover_text_function_symbol_offsets(const AmdGpuCodeObject &object,
if (!text_index)
return {};

std::vector<uint64_t> offsets;
std::vector<TextFunctionSymbolRange> ranges;
for (const Elf64_Shdr &symtab : sections) {
if ((symtab.sh_type != SHT_SYMTAB && symtab.sh_type != SHT_DYNSYM) ||
symtab.sh_entsize != sizeof(Elf64_Sym) ||
Expand Down Expand Up @@ -444,12 +469,16 @@ discover_text_function_symbol_offsets(const AmdGpuCodeObject &object,
}
if (offset >= text_size || (offset % sizeof(uint32_t)) != 0)
continue;
offsets.push_back(offset);
ranges.push_back({.start_offset = offset, .size = symbol.st_size});
}
}
std::ranges::sort(offsets);
offsets.erase(std::ranges::unique(offsets).begin(), offsets.end());
return offsets;
const auto range_key = [](const TextFunctionSymbolRange &range) {
return std::pair{range.start_offset, range.size};
};
std::ranges::sort(ranges, {}, range_key);
const auto duplicate_tail = std::ranges::unique(ranges, {}, range_key);
ranges.erase(duplicate_tail.begin(), duplicate_tail.end());
return ranges;
}

std::vector<uint64_t>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ struct RelocationFunctionTable {
std::vector<RelocationFunctionPointer> entries;
};

/// @brief Exact extent of one sized `STT_FUNC` symbol in `.text`.
///
/// @details Both fields are byte offsets relative to the code object's single `.text` section.
/// The size comes from the symbol's `st_size`; it is not inferred from the next symbol, because
/// linkers may place non-code alignment padding between function symbols.
struct TextFunctionSymbolRange {
uint64_t start_offset = 0;
uint64_t size = 0;
};

/// @brief One dynamically indexed table load feeding a call-like scalar PC swap.
///
/// @details This record connects source CFG recovery with final text patching.
Expand Down Expand Up @@ -144,6 +154,19 @@ analyze_relocation_pairs(std::span<const std::unique_ptr<BasicBlock>> blocks,
[[nodiscard]] std::vector<RelocationFunctionTable>
discover_relocation_function_tables(const AmdGpuCodeObject &object);

/// @brief Exact `.text`-relative extents of every valid sized `STT_FUNC` symbol.
///
/// @details A returned range is nonempty, instruction-word aligned, and wholly contained in the
/// code object's single `.text` section. Its size is the ELF symbol's `st_size`, so gaps between
/// symbols remain excluded. Unlike discover_text_function_symbol_offsets(), this function
/// validates the complete symbol extent rather than only its entry offset. Invalid or duplicate
/// ranges are ignored.
///
/// @returns Ranges sorted by `(start_offset, size)`. Empty when the object has no symbol table,
/// more than one `.text`, or no qualifying symbol.
[[nodiscard]] std::vector<TextFunctionSymbolRange>
discover_text_function_symbol_ranges(const AmdGpuCodeObject &object);

/// @brief `.text`-relative offsets of every sized `STT_FUNC` symbol in the code object.
///
/// @details These are function entries, which makes them block leaders. A separately compiled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ inline constexpr IsaGpuTargetDescription kGfx1251Target{
120501,
{.instruction_features = kGfx1251IsaFeatures,
.setreg_vgpr_msb_fixup = false,
.execution_implemented = false}};
.execution_implemented = true}};
inline constexpr std::array<IsaGpuTargetDescription, 2> kGpuTargets{
{kGfx1250Target, kGfx1251Target}};

Expand Down
8 changes: 8 additions & 0 deletions emulation/rocjitsu/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ add_executable(
gpu_vm_service_test.cpp
gpu_vm_translation_test.cpp
config_test.cpp
gfx1251_compiled_kernel_test.cpp
daemon_api_test.cpp
matmul_test.cpp
vector_add_test.cpp
Expand All @@ -515,6 +516,7 @@ add_executable(
cdna5_cluster_test.cpp
cdna5_dispatch_memory_test.cpp
cdna5_execution_test.cpp
cdna5_gfx1251_simulation_test.cpp
cdna5_instruction_test.cpp
cdna5_sdma_test.cpp
completion_tracker_test.cpp
Expand Down Expand Up @@ -693,6 +695,12 @@ if(HAS_GFX1250_DEVICE_KERNELS)
PRIVATE HAS_GFX1250_DEVICE_KERNELS=1
)
endif()
if(HAS_GFX1251_DEVICE_KERNELS)
target_compile_definitions(
rocjitsu_tests
PRIVATE HAS_GFX1251_DEVICE_KERNELS=1
)
endif()
# The vfio-user backend is optional, so its tests build only when it does.
if(ROCJITSU_ENABLE_VFIO)
target_sources(
Expand Down
27 changes: 27 additions & 0 deletions emulation/rocjitsu/tests/amdgpu_elf_test_support.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,33 @@ std::vector<uint8_t> make_minimal_amdgpu_elf_with_descriptor_after_text(
std::vector<uint8_t> make_minimal_amdgpu_elf_with_descriptor_after_text() {
return make_minimal_amdgpu_elf_with_descriptor_after_text({0xBF800000u, 0xBF800000u});
}

std::vector<uint8_t> make_minimal_amdgpu_kernel_elf(const std::vector<uint32_t> &text_words,
uint32_t elf_machine,
const TestKernelDescriptor &kernel_descriptor) {
std::vector<uint8_t> image = make_minimal_amdgpu_elf_with_descriptor_after_text(text_words);

Elf64_Ehdr ehdr{};
std::memcpy(&ehdr, image.data(), sizeof(ehdr));
if (ehdr.e_shnum <= 2 || ehdr.e_shentsize != sizeof(Elf64_Shdr) ||
ehdr.e_shoff + static_cast<uint64_t>(ehdr.e_shnum) * sizeof(Elf64_Shdr) > image.size())
throw std::runtime_error("single-kernel fixture has an invalid section table");
ehdr.e_flags = elf_machine;
std::memcpy(image.data(), &ehdr, sizeof(ehdr));

Elf64_Shdr rodata{};
std::memcpy(&rodata, image.data() + ehdr.e_shoff + 2 * sizeof(Elf64_Shdr), sizeof(rodata));
if (rodata.sh_size < sizeof(TestKernelDescriptor) ||
rodata.sh_offset + sizeof(TestKernelDescriptor) > image.size())
throw std::runtime_error("single-kernel fixture has an invalid kernel descriptor");

TestKernelDescriptor descriptor = kernel_descriptor;
const int64_t entry_offset = read_kernel_descriptor_entry_offset(image.data() + rodata.sh_offset);
write_kernel_descriptor_entry_offset(&descriptor, entry_offset);
std::memcpy(image.data() + rodata.sh_offset, &descriptor, sizeof(descriptor));
return image;
}

std::unique_ptr<Instruction> decode_one(uint32_t word, rj_code_arch_t arch) {
auto decoder = Decoder::create(arch);
if (!decoder)
Expand Down
6 changes: 6 additions & 0 deletions emulation/rocjitsu/tests/amdgpu_elf_test_support.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ uint16_t append_elf_section_for_test(std::vector<uint8_t> &image, Elf64_Shdr sec
std::optional<size_t> function_pointer_table_target_words = std::nullopt,
bool name_function_pointer_table_with_symbol = true, bool export_text_function = false);
[[nodiscard]] std::vector<uint8_t> make_minimal_amdgpu_elf_with_descriptor_after_text();
/// @brief Build the single-kernel fixture for one concrete AMDGPU ELF machine.
/// @details The caller-provided descriptor supplies resource allocation fields;
/// the helper preserves the fixture's computed text-entry displacement.
[[nodiscard]] std::vector<uint8_t>
make_minimal_amdgpu_kernel_elf(const std::vector<uint32_t> &text_words, uint32_t elf_machine,
const TestKernelDescriptor &kernel_descriptor);
/// @brief One sized `STT_FUNC` body in the fixture's `.text`.
struct TestTextFunction {
size_t offset_word = 0;
Expand Down
7 changes: 4 additions & 3 deletions emulation/rocjitsu/tests/amdgpu_vm_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ TEST(ComputeUnitConfigTest, RejectsVgprSpanAboveIsaMaximum) {
EXPECT_THROW((void)VmFixture("cdna3", 1, 32, 64, 104, 513), util::ConfigError);
}

TEST(ComputeUnitConfigTest, DirectFactoryRejectsModelOnlyConcreteTarget) {
TEST(ComputeUnitConfigTest, DirectFactoryAcceptsExecutionEnabledConcreteTarget) {
rocjitsu::test::LegacyGpuMemoryFixture memory("memory");
amdgpu::L2Cache l2("l2");
const amdgpu::ComputeUnitCore::Config config{
Expand All @@ -276,8 +276,9 @@ TEST(ComputeUnitConfigTest, DirectFactoryRejectsModelOnlyConcreteTarget) {
.lds_size_kb = 64,
};

EXPECT_THROW((void)amdgpu::ComputeUnitCore::create("cu", config, &memory, &l2),
util::ConfigError);
auto compute_unit = amdgpu::ComputeUnitCore::create("cu", config, &memory, &l2);
ASSERT_NE(compute_unit, nullptr);
EXPECT_EQ(compute_unit->arch(), ROCJITSU_CODE_ARCH_CDNA5);
}

TEST(ComputeUnitConfigTest, DirectFactoryRejectsTargetFromAnotherArchitecture) {
Expand Down
Loading
Loading