/* * epyc_oisc_cacheline_bench.c * * Cache-line-granular OISC benchmark for EPYC 9645 Turin. * * Three models, each building on the last: * * 1. WORD-SUBLEQ — traditional subleq on int16 words (baseline) * 2. CL-SUBLEQ — subleq on 64-byte cache lines via AVX-512 aligned load/store * 3. PCIE-TLP — each OISC instr = 64-byte PCIe TLP from a virtio-style * ring buffer; measures raw TLP throughput * * The model: a cache-native OISC pipeline hooks PCIe signal hooks (TLP * transactions) as its instruction fetch / memory access path. Every * instruction is one or more 64-byte cache-line reads/writes — exactly * what PCIe Gen5 x16 delivers as a single transaction. * * Build: * gcc -march=znver5 -O3 -flto -mavx512f -mavx512bw \ * epyc_oisc_cacheline_bench.c -o oisc_cl_bench * * Run: * perf stat -e cycles,instructions,cache-references,cache-misses,\ * L1-dcache-load-misses,LLC-load-misses,branch-misses \ * ./oisc_cl_bench */ #define _GNU_SOURCE #include #include #include #include #include #include #include /* ================================================================ * Common constants * ================================================================ */ #define CL_SIZE 64 /* x86 cache line */ #define CL_WORDS (CL_SIZE / sizeof(int16_t)) /* 32 int16 per cache line */ #define N_LINES 4096 /* 4096 cache lines = 256 KB workspace */ #define MEM_SIZE (N_LINES * CL_SIZE) #define HALT 0x8000 /* ================================================================ * 1. WORD-SUBLEQ (baseline) * mem[dst] -= mem[src]; if mem[dst] <= 0 → pc = next; else pc += 3 * Word-aligned, no padding. * ================================================================ */ static int64_t bench_word_subleq(int16_t *mem, int pc, int N) { int16_t src, dst, next; int64_t iters = 0; int cnt = 0; while (cnt < N) { src = mem[pc]; dst = mem[pc + 1]; next = mem[pc + 2]; if (next == HALT) break; mem[dst] -= mem[src]; pc = (mem[dst] <= 0) ? next : pc + 3; iters++; cnt++; } return iters; } /* Build a word-SUBLEQ program that executes ~N iterations. * Returns pc of first instruction. */ static int build_word_prog(int16_t *mem, int N, int *out_pc) { memset(mem, 0, MEM_SIZE); /* Layout: * line 0: constants (zero=0, neg1=-1, one=1, limit=N/64) * line 1: program (src,dst,next triples) * line 2+: workspace */ mem[0] = 0; /* zero addr */ mem[1] = -1; /* neg1 */ mem[2] = 1; /* one */ mem[3] = N; /* limit */ int cnt_addr = 64; /* counter at word 64 */ int tmp_addr = 65; /* temp at word 65 */ mem[cnt_addr] = 0; mem[tmp_addr] = 0; /* * Loop: * I0: cnt -= neg1 (cnt++) * I1: tmp -= cnt (tmp = N - cnt) * I2: branch if tmp <= 0 */ int pc = 100; /* program at word 100 */ *out_pc = pc; /* I0: cnt += 1 (cnt -= neg1, where neg1 = -1) */ mem[pc] = 1; /* src = addr 1 = neg1 */ mem[pc + 1] = cnt_addr; mem[pc + 2] = -1; /* fall through */ pc += 3; /* I1: tmp = N - cnt (tmp -= cnt, tmp starts = N at line 0) */ mem[pc] = cnt_addr; mem[pc + 1] = tmp_addr; mem[pc + 2] = pc + 3; /* fall through to I2 */ pc += 3; /* I2: if tmp <= 0 → halt; else → loop */ /* tmp <= 0 means we ran N iterations */ mem[pc] = 0; /* src = zero (no-op for tmp) */ mem[pc + 1] = tmp_addr; mem[pc + 2] = *out_pc; /* loop back */ pc += 3; /* HALT */ mem[pc] = 0; mem[pc + 1] = 0; mem[pc + 2] = HALT; return pc + 3; /* total words used */ } /* ================================================================ * 2. CL-SUBLEQ (cache-line granular) * * Each "word" in this variant is a 64-byte cache line. The SUBLEQ * instruction becomes: * * line[dst] := line[dst] − line[src] (elementwise, via AVX-512) * if all(line[dst]) <= 0 → pc = next; else pc += 3 * * "All zeros" is checked as: the first int16 of a cache line ≤ 0. * For a real pipeline, this would be a SIMD compare + mask test. * ================================================================ */ typedef int64_t cacheline_t[CL_WORDS] __attribute__((aligned(64))); static inline void cl_sub(cacheline_t *a, cacheline_t *b) { /* b[] -= a[] using AVX-512 */ __m512i va = _mm512_load_si512(a); __m512i vb = _mm512_load_si512(b); __m512i vr = _mm512_sub_epi16(vb, va); _mm512_store_si512(b, vr); } static inline int cl_is_nonpositive(cacheline_t *b) { /* Return 1 if first element <= 0 (proxy for "all zero") */ return (*b)[0] <= 0; } static int64_t bench_cl_subleq(cacheline_t *mem, int pc, int N) { int16_t src, dst, next; int64_t iters = 0; int cnt = 0; while (cnt < N) { /* The "program" is still stored as int16 triples in line 0 */ src = ((int16_t *)mem)[pc]; dst = ((int16_t *)mem)[pc + 1]; next = ((int16_t *)mem)[pc + 2]; if (next == HALT) break; cl_sub(&mem[src], &mem[dst]); pc = cl_is_nonpositive(&mem[dst]) ? next : pc + 3; iters++; cnt++; } return iters; } /* ================================================================ * 3. PCIE-TLP model * * Simulates PCIe Transaction Layer Packets as the instruction transport. * Each TLP = 64 bytes (one cache line): * [src_line:16] [dst_line:16] [next_line:16] [flags:16] [payload: 56 B pad] * * The OISC engine reads TLPs from a "RX ring" (pre-allocated buffer), * executes the SUBLEQ on cache lines, and writes result TLPs to a * "TX ring." * * Metric: TLPs processed per second = cache lines / sec. * At PCIe Gen5 x16 (64 GT/s, 128B/130B encoding) = ~63 GB/s raw. * Each TLP read+write = 2 × 64B = 128B per instruction. * Theoretical max: ~492 M TLPs/sec per PCIe Gen5 x16 lane pair. * * This benchmark measures the software-side bottleneck. * ================================================================ */ typedef struct __attribute__((packed, aligned(64))) { uint16_t src_line; uint16_t dst_line; uint16_t next_line; uint16_t flags; uint8_t pad[56]; /* fill to 64 B */ } PcieTlp; /* Generate a batch of TLPs in a ring buffer */ static int generate_tlps(PcieTlp *ring, int count, int src_line, int dst_line, int next_line) { for (int i = 0; i < count; i++) { ring[i].src_line = src_line; ring[i].dst_line = dst_line; ring[i].next_line = next_line; ring[i].flags = 0; memset(ring[i].pad, 0, 56); } return count; } /* Execute a TLP stream against a cache-line memory, as PCIe RX→process→TX */ static int64_t bench_pcie_tlp(PcieTlp *rx_ring, int n_tlps, cacheline_t *mem) { int64_t processed = 0; for (int i = 0; i < n_tlps; i++) { PcieTlp *tlp = &rx_ring[i]; /* SUBLEQ on cache lines */ cl_sub(&mem[tlp->src_line], &mem[tlp->dst_line]); /* "Write-back" — mark TLP as processed (simulates TX completion) */ tlp->flags = 1; processed++; } return processed; } /* ================================================================ * Timing helper * ================================================================ */ static double now_sec(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec + ts.tv_nsec * 1e-9; } /* ================================================================ * Main * ================================================================ */ int main(void) { printf("=== EPYC 9645 Turin — Cache-Line OISC Benchmark ===\n\n"); /* ---------- 1. WORD SUBLEQ ---------- */ printf("--- 1. WORD-SUBLEQ (baseline) ---\n"); int16_t *word_mem = aligned_alloc(64, MEM_SIZE); int word_pc; build_word_prog(word_mem, 10000000, &word_pc); double t0 = now_sec(); int64_t wi = bench_word_subleq(word_mem, word_pc, 10000000); double t1 = now_sec(); double ws = wi / (t1 - t0) / 1e6; printf(" %ld iter in %.4f sec = %.2f M instr/sec\n", (long)wi, t1 - t0, ws); printf(" Words touched/sec: %.2f M\n", ws * 6); /* 6 word accesses per instr */ free(word_mem); /* ---------- 2. CL SUBLEQ ---------- */ printf("\n--- 2. CL-SUBLEQ (cache-line granular, AVX-512) ---\n"); cacheline_t *cl_mem = aligned_alloc(64, MEM_SIZE); /* Build program in cl_mem[0] as int16 triples */ int cl_pc; build_word_prog((int16_t *)cl_mem, 10000000, &cl_pc); /* Warmup */ bench_cl_subleq(cl_mem, cl_pc, 1000); build_word_prog((int16_t *)cl_mem, 10000000, &cl_pc); t0 = now_sec(); int64_t ci = bench_cl_subleq(cl_mem, cl_pc, 1000000); t1 = now_sec(); double cs = ci / (t1 - t0) / 1e6; printf(" %ld iter in %.4f sec = %.2f M instr/sec\n", (long)ci, t1 - t0, cs); printf(" Cache lines touched/sec: %.2f M\n", cs * 2); free(cl_mem); /* ---------- 3. PCIE TLP ---------- */ printf("\n--- 3. PCIE-TLP MODEL (cache-line TLP stream) ---\n"); int n_tlps = 100000; PcieTlp *rx_ring = aligned_alloc(64, n_tlps * sizeof(PcieTlp)); cacheline_t *tlp_mem = aligned_alloc(64, MEM_SIZE); /* Set up: zero memory, generate TLP stream */ memset(tlp_mem, 0, MEM_SIZE); generate_tlps(rx_ring, n_tlps, 2, 3, HALT); /* line 2 = zero, line 3 = zero. cl_sub(zero, zero) = no-op. */ /* Warmup */ bench_pcie_tlp(rx_ring, 1000, tlp_mem); t0 = now_sec(); int64_t pi = bench_pcie_tlp(rx_ring, n_tlps, tlp_mem); t1 = now_sec(); double ps = pi / (t1 - t0) / 1e6; printf(" %ld TLPs in %.4f sec = %.2f M TLPs/sec\n", (long)pi, t1 - t0, ps); printf(" PCIe BW equivalent: %.2f GB/s (2 × 64B per TLP)\n", ps * 128.0 / 1000.0); /* Each TLP = 64B read + 64B write = 128B */ free(rx_ring); free(tlp_mem); /* ---------- SUMMARY ---------- */ printf("\n=== SUMMARY ===\n"); printf(" %-30s %12.2f M ops/sec\n", "WORD-SUBLEQ", ws); printf(" %-30s %12.2f M ops/sec\n", "CL-SUBLEQ (AVX-512)", cs); printf(" %-30s %12.2f M ops/sec\n", "PCIE-TLP (cache line)", ps); printf("\n TLP → PCIe Gen5 x16 raw: ~63 GB/s\n"); printf(" TLP → PCIe Gen5 x8 raw: ~31 GB/s\n"); printf(" TLP → PCIe Gen4 x16 raw: ~31 GB/s\n"); return 0; }