mirror of
https://github.com/libretro/parallel-dc.git
synced 2026-11-07 04:25:58 -10:00
tools/aica: the DSP, compiled against interpreted
The core has the sound chip's DSP twice - a compiler for x86-64 and ARM64 and an interpreter for the rest - and nothing ran one against the other. tools/aica/run.sh builds dsp_test.cpp around each, with nothing else of the core, and gives both the same 1000 made-up programs: 1 to 64 random steps, random coefficients and addresses, 64 samples of random input. The 16 outputs after every sample and the registers and delay memory at the end have to come out the same from both. Every program is run a second time with a step added at the end of the 128 that is not empty and does nothing, which makes the DSP run the empty steps it otherwise leaves out. Both runs have to leave the same behind, from the compiler and from the interpreter: that is "aica dsp: the empty steps after the program's last are not run" checked on both, where so far it had been checked on the compiler with one game's program. One difference between the two is known and is kept out of the programs; run.sh says what it is. On a machine that is not x86-64 only the interpreter's check of itself is run. Before "aica dsp: two faults in the compiled code" this fails.
This commit is contained in:
1 parent
c9e8884ede
commit
de4aaaab0a
2 files changed
+251
No files matched your search
@@ -0,0 +1,162 @@
|
||||
/* Runs the sound chip's DSP on made-up programs and prints what each leaves
|
||||
* behind. tools/aica/run.sh builds this twice, once around the DSP's
|
||||
* compiler (core/hw/aica/dsp_x64.cpp) and once around its interpreter
|
||||
* (core/hw/aica/dsp_interp.cpp), runs both, and compares what they print:
|
||||
* the two are separate implementations of the same machine and have to
|
||||
* agree on every program, to the last bit.
|
||||
*
|
||||
* A case is a program of 1 to 64 random steps, random coefficients and
|
||||
* memory addresses, and 64 samples of random input; what is printed for it
|
||||
* are checksums of the 16 outputs after every sample, and of the DSP's
|
||||
* registers and its delay memory at the end. One thing is kept out of the
|
||||
* random programs: a step that takes a value read from memory (IWT) only
|
||||
* appears two steps after one that reads it (MRD). See run.sh for why.
|
||||
* $DSP_TEST_PROGRAM names a file of 512 hex words to run in place of the
|
||||
* made-up program, for looking at one case.
|
||||
*
|
||||
* Every case is run twice: as it is, and with a step added at the very end
|
||||
* of the 128 that is not empty but does nothing (it only clears a register
|
||||
* nothing reads afterwards). The DSP leaves out the empty steps after a
|
||||
* program's last one; the added step makes it run all 128. The two runs
|
||||
* have to print the same, which is the check that leaving them out
|
||||
* changes nothing. */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "hw/aica/aica.h"
|
||||
#include "hw/aica/aica_if.h"
|
||||
#include "hw/aica/dsp.h"
|
||||
|
||||
unsigned ARAM_SIZE = 2 * 1024 * 1024;
|
||||
unsigned ARAM_MASK = 2 * 1024 * 1024 - 1;
|
||||
DSPData_struct *DSPData;
|
||||
VArray2 aica_ram;
|
||||
|
||||
static u8 regs[sizeof(DSPData_struct)] __attribute__((aligned(4096)));
|
||||
static u8 ram[2 * 1024 * 1024] __attribute__((aligned(4096)));
|
||||
|
||||
void os_DebugBreak()
|
||||
{
|
||||
fprintf(stderr, "the DSP stopped on a check of its own\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
bool vmem_platform_prepare_jit_block(void *code_area, unsigned size, void **code_area_rwx)
|
||||
{
|
||||
const uintptr_t page = (uintptr_t)sysconf(_SC_PAGESIZE);
|
||||
const uintptr_t from = (uintptr_t)code_area & ~(page - 1);
|
||||
const uintptr_t to = ((uintptr_t)code_area + size + page - 1) & ~(page - 1);
|
||||
|
||||
if (mprotect((void*)from, to - from, PROT_READ | PROT_WRITE | PROT_EXEC))
|
||||
return false;
|
||||
*code_area_rwx = code_area;
|
||||
return true;
|
||||
}
|
||||
|
||||
static u64 seed;
|
||||
static u32 rnd(void)
|
||||
{
|
||||
seed = seed * 6364136223846793005ull + 1442695040888963407ull;
|
||||
return (u32)(seed >> 33);
|
||||
}
|
||||
|
||||
static u64 fold(u64 h, const void *data, size_t size)
|
||||
{
|
||||
const u8 *p = (const u8*)data;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i + 8 <= size; i += 8)
|
||||
{
|
||||
u64 v;
|
||||
memcpy(&v, p + i, 8);
|
||||
h = (h ^ v) * 0x100000001b3ull;
|
||||
h ^= h >> 29;
|
||||
}
|
||||
for (; i < size; i++)
|
||||
h = (h ^ p[i]) * 0x100000001b3ull;
|
||||
return h;
|
||||
}
|
||||
|
||||
static void run_case(unsigned number, bool all_steps)
|
||||
{
|
||||
u64 out = 0xcbf29ce484222325ull, state = out, memory = out;
|
||||
unsigned steps, i, sample;
|
||||
|
||||
seed = 0x9E3779B97F4A7C15ull * (number + 1);
|
||||
memset(regs, 0, sizeof(regs));
|
||||
memset(ram, 0, sizeof(ram));
|
||||
dsp_init();
|
||||
|
||||
steps = 1 + rnd() % 64;
|
||||
for (i = 0; i < steps; i++)
|
||||
{
|
||||
u32 *w = &DSPData->MPRO[i * 4];
|
||||
|
||||
w[0] = rnd() & 0xFFFE;
|
||||
w[1] = rnd() & 0xFFFE;
|
||||
w[2] = rnd() & 0xFFFF;
|
||||
w[3] = rnd() & 0xFF80;
|
||||
/* the input select has 50 inputs to choose from */
|
||||
w[1] = (w[1] & ~(0x3Fu << 7)) | ((((w[1] >> 7) & 0x3F) % 0x32) << 7);
|
||||
/* a value read from memory is taken two steps after it is read */
|
||||
if (i < 2 || !(DSPData->MPRO[(i - 2) * 4 + 2] & 0x2000))
|
||||
w[1] &= ~0x40u;
|
||||
}
|
||||
if (getenv("DSP_TEST_PROGRAM"))
|
||||
{
|
||||
/* a program to run in place of the made-up one: 512 words in hex */
|
||||
FILE *f = fopen(getenv("DSP_TEST_PROGRAM"), "r");
|
||||
for (i = 0; i < 512; i++)
|
||||
{
|
||||
unsigned word = 0;
|
||||
if (fscanf(f, "%x", &word) != 1)
|
||||
word = 0;
|
||||
DSPData->MPRO[i] = word;
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
if (all_steps)
|
||||
DSPData->MPRO[127 * 4 + 2] = 2; /* ZERO: clears B, and that is all */
|
||||
for (i = 0; i < 128; i++)
|
||||
DSPData->COEF[i] = rnd() & 0xFFFF;
|
||||
for (i = 0; i < 64; i++)
|
||||
DSPData->MADRS[i] = rnd() & 0xFFFF;
|
||||
dsp.RBL = (8192u << (rnd() % 4)) - 1;
|
||||
dsp.RBP = ((rnd() % 1024) * 2048) & ARAM_MASK;
|
||||
dsp.dyndirty = true;
|
||||
dsp_writenmem(0x3400); /* the program was written */
|
||||
|
||||
for (sample = 0; sample < 64; sample++)
|
||||
{
|
||||
for (i = 0; i < 16; i++)
|
||||
dsp.MIXS[i] = (s32)(rnd() << 12) >> 12; /* 20 bits, signed */
|
||||
DSPData->EXTS[0] = (u32)(s32)(s16)rnd();
|
||||
DSPData->EXTS[1] = (u32)(s32)(s16)rnd();
|
||||
dsp_step();
|
||||
out = fold(out, DSPData->EFREG, sizeof(DSPData->EFREG));
|
||||
}
|
||||
state = fold(state, dsp.TEMP, sizeof(dsp.TEMP));
|
||||
state = fold(state, dsp.MEMS, sizeof(dsp.MEMS));
|
||||
state = fold(state, &dsp.regs.MDEC_CT, sizeof(dsp.regs.MDEC_CT));
|
||||
memory = fold(memory, ram, sizeof(ram));
|
||||
printf("%u %u %016llx %016llx %016llx\n", number, steps,
|
||||
(unsigned long long)out, (unsigned long long)state, (unsigned long long)memory);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const unsigned cases = argc > 1 ? (unsigned)atoi(argv[1]) : 200;
|
||||
const bool all_steps = argc > 2 && !strcmp(argv[2], "all");
|
||||
unsigned i;
|
||||
|
||||
DSPData = (DSPData_struct*)regs;
|
||||
aica_ram.data = ram;
|
||||
aica_ram.size = sizeof(ram);
|
||||
for (i = 0; i < cases; i++)
|
||||
run_case(i, all_steps);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
#!/bin/sh
|
||||
# The sound chip's DSP, compiled against interpreted.
|
||||
#
|
||||
# sh tools/aica/run.sh [cases]
|
||||
#
|
||||
# The DSP runs a 128-step program for every sample. The core has it twice:
|
||||
# a compiler that turns the program into machine code (dsp_x64.cpp here,
|
||||
# dsp_arm64.cpp on ARM64) and an interpreter (dsp_interp.cpp) for
|
||||
# everything else. Nothing ran the two against each other.
|
||||
#
|
||||
# dsp_test.cpp is built around each of them in turn, with nothing of the
|
||||
# rest of the core, and both are given the same made-up programs, 1000
|
||||
# unless a number is given: 1 to 64 random steps, random coefficients and
|
||||
# addresses, 64 samples of random input. What each leaves behind - its 16
|
||||
# outputs after every sample, its registers and its delay memory at the
|
||||
# end - has to be the same from both, for every program.
|
||||
#
|
||||
# Every program is also run a second time with a step added at the very
|
||||
# end of the 128 that is not empty and does nothing. The DSP leaves out
|
||||
# the empty steps after a program's last; the added step makes it run them
|
||||
# all. Both runs have to leave the same behind, from the compiler and from
|
||||
# the interpreter.
|
||||
#
|
||||
# One difference between the two is known and kept out of the programs. A
|
||||
# step can take a value that a step two before it read from memory. The
|
||||
# compiler keeps the four places those values wait in from one sample to
|
||||
# the next; the interpreter empties them at the start of every sample. So
|
||||
# a program that takes a value without having read one two steps before
|
||||
# gets an old value from one and zero from the other. Which the hardware
|
||||
# does is not known here, and no program that means anything does it, so
|
||||
# the made-up programs only take a value two steps after a read.
|
||||
#
|
||||
# Needs a C++ compiler and the core's makefile for its flags. Where the
|
||||
# core is not built for x86-64 there is no compiler to compare with and
|
||||
# only the interpreter's check of itself is run.
|
||||
set -e
|
||||
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
|
||||
cd "$ROOT"
|
||||
CASES=${1:-1000}
|
||||
WORK=${TMPDIR:-/tmp}/aica_dsp.$$
|
||||
mkdir -p "$WORK"
|
||||
trap 'rm -rf "$WORK"' EXIT
|
||||
|
||||
# the compiler and flags the core compiles this file with
|
||||
CMD=$(make -n -B core/hw/aica/dsp_x64.o 2>/dev/null | grep 'dsp_x64\.cpp' | tail -n 1)
|
||||
[ -n "$CMD" ] || { echo "cannot get the core's compile flags from make" >&2; exit 1; }
|
||||
CMD=${CMD% -o *}
|
||||
FLAGS=$(echo "$CMD" | sed 's| [^ ]*dsp_x64\.cpp||; s/ -MMD -MP//')
|
||||
CXX=${FLAGS%% *}
|
||||
|
||||
build() {
|
||||
# $1: name, $2: extra define
|
||||
for SRC in core/hw/aica/dsp.cpp core/hw/aica/dsp_x64.cpp core/hw/aica/dsp_interp.cpp tools/aica/dsp_test.cpp; do
|
||||
# the flags come out of make quoted for a shell
|
||||
eval "$FLAGS $2 \"$SRC\" -o \"$WORK/$1-$(basename "$SRC" .cpp).o\""
|
||||
done
|
||||
"$CXX" -o "$WORK/dsp_test_$1" "$WORK/$1"-*.o
|
||||
}
|
||||
|
||||
same() {
|
||||
# $1, $2: outputs to compare, $3: what they are; the case number and the
|
||||
# program's length lead each line and are left out of the comparison
|
||||
cut -d' ' -f3- "$1" > "$WORK/a.txt"
|
||||
cut -d' ' -f3- "$2" > "$WORK/b.txt"
|
||||
if ! cmp -s "$WORK/a.txt" "$WORK/b.txt"; then
|
||||
echo "FAIL: $3 differ, first in these cases:" >&2
|
||||
diff "$1" "$2" | grep '^<' | head -n 5 | cut -d' ' -f2-3 >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
build interp -DFEAT_DSPREC=DYNAREC_NONE
|
||||
"$WORK/dsp_test_interp" "$CASES" > "$WORK/interp.txt"
|
||||
"$WORK/dsp_test_interp" "$CASES" all > "$WORK/interp-all.txt"
|
||||
same "$WORK/interp.txt" "$WORK/interp-all.txt" "the interpreter's steps in use and all 128"
|
||||
|
||||
case $(uname -m) in
|
||||
x86_64|amd64)
|
||||
build jit ""
|
||||
"$WORK/dsp_test_jit" "$CASES" > "$WORK/jit.txt"
|
||||
"$WORK/dsp_test_jit" "$CASES" all > "$WORK/jit-all.txt"
|
||||
same "$WORK/jit.txt" "$WORK/jit-all.txt" "the compiler's steps in use and all 128"
|
||||
same "$WORK/jit.txt" "$WORK/interp.txt" "the compiler and the interpreter"
|
||||
echo "dsp: ok ($CASES programs: compiled and interpreted agree, with and without the empty steps)"
|
||||
;;
|
||||
*)
|
||||
echo "dsp: ok ($CASES programs through the interpreter, with and without the empty steps; no compiler to compare on this machine)"
|
||||
;;
|
||||
esac
|
||||
Reference in new issue
Block a user