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
128 changes: 128 additions & 0 deletions .github/workflows/acid-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
name: Acid test suite

# Runs the synthetic-ROM acid-test suite against every PR and push to
# main branches. Builds vasm from source (cached), assembles the
# tests, builds the core with TEST_EXPORTS=1 + BENCH_PROFILE=1, runs
# the suite, and gates on `test/acid/BASELINE.txt` -- a PR is blocked
# only if a previously-PASSing test now FAILs. New failing tests are
# allowed (they document bugs); the baseline is updated alongside.

on:
push:
branches: [develop, master, 'release/**']
pull_request:
branches: [develop, master]
paths:
# Only run when something the suite touches actually changes.
- 'src/**'
- 'libretro.c'
- 'test/acid/**'
- 'Makefile'
- 'Makefile.common'
- '.github/workflows/acid-test.yml'
workflow_dispatch: {}

concurrency:
group: acid-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
acid:
name: Acid suite (linux x86_64)
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Checkout
uses: actions/checkout@v4
with:
# Need the full history if we want to inspect the baseline diff
# against develop later, but shallow is fine for now.
fetch-depth: 1

- name: Cache vasm
id: vasm-cache
uses: actions/cache@v4
with:
path: /usr/local/bin/vasmm68k_mot
# Bust the cache if anyone bumps prb28/vasm SHA.
key: vasm-1.9-prb28-${{ runner.os }}-v1

- name: Build vasm from source
if: steps.vasm-cache.outputs.cache-hit != 'true'
run: |
set -euo pipefail
mkdir -p /tmp/vasm-build && cd /tmp/vasm-build
# prb28 mirror -- upstream sun.hasenbraten.de is intermittently
# unreachable. See test/acid/README.md for context.
git clone --depth 1 https://github.com/prb28/vasm.git src
cd src && make CPU=m68k SYNTAX=mot
sudo install vasmm68k_mot /usr/local/bin/vasmm68k_mot
vasmm68k_mot 2>&1 | head -3 || true

- name: Build the core (TEST_EXPORTS=1 BENCH_PROFILE=1)
run: |
set -euo pipefail
# The acid runner needs perf_counters_find exported (so it can
# report per-test counter deltas). TEST_EXPORTS=1 widens the
# symbol set; BENCH_PROFILE=1 emits the actual counter code.
make -j"$(getconf _NPROCESSORS_ONLN)" \
TEST_EXPORTS=1 BENCH_PROFILE=1
ls -la virtualjaguar_libretro.so

- name: Assemble acid-test ROMs (lint-clean check)
run: |
set -euo pipefail
# Builds include/jaguar_regs.s from C source, assembles every
# tests/**/*.s into a .jag, and runs the lint pass. The lint
# pass MUST pass -- any divergence between a local equate and
# the oracle, or any unknown bit in a B_COMMAND literal, is a
# blocking failure.
make -C test/acid all
make -C test/acid lint

- name: Run acid suite + capture output
id: run
run: |
set -euxo pipefail
# The suite's own exit code reports the FAIL count, which is
# not what we want to gate on (we *expect* known FAILs that
# document bugs). Capture the output and let
# check-baseline.py decide whether to fail the job.
set +e
make -C test/acid test \
CORE="$(pwd)/virtualjaguar_libretro.so" \
> test/acid/results.log 2>&1
set -e
tail -3 test/acid/results.log

- name: Compare against baseline (regression gate)
run: |
python3 test/acid/scripts/check-baseline.py \
test/acid/results.log \
test/acid/BASELINE.txt | tee acid-summary.txt
# check-baseline.py exits non-zero on regressions; the `set -e`
# at job level propagates it.

- name: Post summary to PR (job summary)
if: always()
run: |
{
echo "## Acid suite results"
echo
echo "\`\`\`"
cat acid-summary.txt 2>/dev/null || echo "(no summary)"
echo "\`\`\`"
echo
echo "Full output: artifacts \`acid-results.log\`"
} >> "$GITHUB_STEP_SUMMARY"

- name: Upload artefacts
if: always()
uses: actions/upload-artifact@v4
with:
name: acid-results
path: |
test/acid/results.log
acid-summary.txt
retention-days: 14
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,7 @@ test/lldb_*.py
/test/tools/test_blitter_compare
/test/tools/test_screenshot
test/tools/build/

# Acid-test build outputs
test/acid/acid_run
test/acid/tests/**/*.jag
19 changes: 19 additions & 0 deletions .yamllint
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# yamllint config for this repo.
#
# We mostly accept yamllint defaults, but:
# - GitHub Actions workflows legitimately use `on:` as a key, which
# yamllint's `truthy` rule flags under YAML 1.1 semantics. Quoting
# it (`"on":`) is the official escape hatch but is ugly and unusual,
# so we just allow `on` as a known truthy-looking key.
# - We don't require the leading `---` document-start marker.
# - Long lines (URLs, generated keys) are common; relax the limit.
extends: default

rules:
document-start: disable
truthy:
allowed-values: ['true', 'false', 'on', 'off']
check-keys: true
line-length:
max: 200
level: warning
14 changes: 13 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -857,7 +857,7 @@ test/tools/test_memory_map: test/tools/test_memory_map.c
-o $@ test/tools/test_memory_map.c -ldl
endif

.PHONY: clean test lint coverage benchmark
.PHONY: clean test lint coverage benchmark acid
endif

lint:
Expand Down Expand Up @@ -905,6 +905,18 @@ benchmark:
--warmup $(BENCH_WARMUP) --blitter $(BENCH_BLITTER) \
$(if $(BENCH_STATE),--load-state "$(BENCH_STATE)")

# `make acid` -- builds the core and runs the synthetic acid-test ROMs
# (see test/acid/README.md). Requires the vasm 68K assembler on $PATH;
# if absent, the assemble step is skipped and only the runner harness
# is built (so CI can still validate the harness compiles).
#
# Forces a BENCH_PROFILE=1 + TEST_EXPORTS=1 build of the core so the
# acid runner can dlsym `perf_counters_find` and report a per-test
# delta (halflines, vblank IRQs, blits, inner-loop iters, ...).
acid:
$(MAKE) BENCH_PROFILE=1 TEST_EXPORTS=1 -j$(shell getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4)
$(MAKE) -C test/acid test CORE=$(abspath $(TARGET))

print-%:
@echo '$*=$($*)'

32 changes: 32 additions & 0 deletions docs/emulation-bug-hunt-todos.md
Original file line number Diff line number Diff line change
Expand Up @@ -456,3 +456,35 @@ shipping v2.2.0; capture them so they don't get lost.
/ `const`-correctness audits as a CI step. `clang-tidy` and
`cppcheck` would be good starting points; the codebase already
has a C89 lint, so the infrastructure is there.

## Original `docs/TODO` items still relevant (Shamus / CJ)

The historical `docs/TODO` from the upstream Virtual Jaguar tree
lists several still-open accuracy / feature items. These map onto
the acid-test categories in `test/acid/README.md`; tracking here so
they don't get lost:

- **"Fix VC behavior to match what a real Jaguar does. Still not
sure just what the heck is going on there." [Shamus]** —
acid `timing/`. Active suspect for the Doom 1.5-2x speed
regression (issue #131).
- **"Cycle accuracy for GPU/DSP/OP/Blitter." [Shamus]** —
cross-cutting; informs every category in `test/acid/`, especially
`bus/` (which can't pass without it).
- **"Need to propagate blitter fixes in the A1 <- A2 direction
to the A1 -> A2 direction and the GPU fixes to various
instructions to the DSP." [Shamus]** — acid `blitter/` (A1↔A2
symmetry tests) and `gpu/` + `dsp/` (shared opcode coverage).
- **"Blitter needs fixing." [Shamus]** — acid `blitter/`.
PR #129 fixed a perf-relevant chunk (`ADDARRAY` etc); accuracy
axis still wide open.
- **"Need to emulate bus contention." [Shamus]** — acid `bus/`.
Almost certainly load-bearing for the Doom regression and the
AvP audio dropouts.
- **"Need to fix timing in the OP. As it is now, it gives a false
impression of how much it's capable of." [Shamus]** —
acid `op/`.

The original `docs/TODO` is intentionally left untouched — it's
the authors' historical record and we track our own work via
GitHub issues + this file + `test/acid/`.
11 changes: 10 additions & 1 deletion scripts/install-hooks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
# - scripts/c89-lint.sh on staged .c files (catches MSVC C89 violations)
# - scripts/check-info-version.sh if anything under dist/info/ or
# Makefile is staged (verifies display_version stays in sync)
# - yamllint on staged .yml/.yaml files (skipped if yamllint isn't
# installed -- CI runs it unconditionally)
#
# Skip with `git commit --no-verify` if you really need to (e.g., a WIP
# squash); CI will catch it later anyway.
Expand All @@ -29,7 +31,14 @@ fi
if echo "$STAGED" | grep -qE '^(dist/info/|Makefile$)'; then
scripts/check-info-version.sh
fi

# yamllint on staged YAML files (only if yamllint is on PATH; CI runs
# it unconditionally so it's fine to skip locally when missing).
STAGED_YAML=$(echo "$STAGED" | grep -E '\.ya?ml$' || true)
if [ -n "$STAGED_YAML" ] && command -v yamllint >/dev/null 2>&1; then
yamllint $STAGED_YAML
fi
HOOK

chmod +x "$HOOK_DIR/pre-commit"
echo "Installed pre-commit hook (C89 lint + .info version check)"
echo "Installed pre-commit hook (C89 lint + .info version check + yamllint)"
18 changes: 17 additions & 1 deletion src/core/jaguar.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "jaguar.h"

#include "cdrom.h"
#include "perf_counters.h"
#include "dac.h"
#include "dsp.h"
#include "eeprom.h"
Expand All @@ -33,6 +34,16 @@

static bool frameDone;

/* Frame-pacing instrumentation (no-op unless built with BENCH_PROFILE).
* Lets the acid runner / benchmark detect timing regressions like the
* Doom 2x speed bug -- e.g. expected 525 halflines/frame NTSC, 60 vblank
* IRQs/sec. See test/acid/README.md and src/core/perf_counters.h.
* Counters that fire from other TUs are declared at their use sites
* (PERF_COUNTER backs each name with a file-scope static). */
PERF_COUNTER(timing_halfline_callbacks);
PERF_COUNTER(timing_vblank_irqs);
PERF_COUNTER(timing_jaguar_execute_calls);

// Platform-independent xorshift32 PRNG for deterministic RAM initialization.
// libc rand() produces different sequences on different platforms (glibc vs
// macOS libsystem), which causes cross-platform baseline mismatches.
Expand Down Expand Up @@ -694,7 +705,8 @@ void JaguarInit(void)
// Half line times are, naturally, half of this. :-P
void HalflineCallback(void)
{
uint16_t vc = TOMReadWord(0xF00006, JAGUAR);
uint16_t vc = (PERF_INC(timing_halfline_callbacks),
TOMReadWord(0xF00006, JAGUAR));
uint16_t vp = TOMReadWord(0xF0003E, JAGUAR) + 1;
uint16_t vi = TOMReadWord(0xF0004E, JAGUAR);

Expand All @@ -712,7 +724,10 @@ void HalflineCallback(void)

// Time for Vertical Interrupt?
if ((vc & 0x7FF) == vi && (vc & 0x7FF) > 0)
{
PERF_INC(timing_vblank_irqs);
TOMSetPendingVideoInt();
}

TOMExecHalfline(vc, true);

Expand Down Expand Up @@ -934,6 +949,7 @@ uint8_t * GetRamPtr(void)
* so the DSP runs alongside the 68K and GPU, matching real hardware timing. */
void JaguarExecuteNew(void)
{
PERF_INC(timing_jaguar_execute_calls);
frameDone = false;

do
Expand Down
5 changes: 5 additions & 0 deletions src/jerry/jerry.c
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@
#include "eeprom.h"
#include "event.h"
#include "jaguar.h"
#include "perf_counters.h"

PERF_COUNTER(timing_jerry_irqs);
#include "joystick.h"
#include "m68000/m68kinterface.h"
#include "memtrack.h"
Expand Down Expand Up @@ -250,6 +253,7 @@ void JERRYPIT1Callback(void)
// Not sure, but I think we don't generate another IRQ if one's already going...
// But this seems to work... :-/
jerryPendingInterrupt |= IRQ2_TIMER1;
PERF_INC(timing_jerry_irqs);
m68k_set_irq(2); // Generate 68K IPL 2
}
}
Expand All @@ -266,6 +270,7 @@ void JERRYPIT2Callback(void)
if (jerryInterruptMask & IRQ2_TIMER2) // CPU Timer 2 IRQ
{
jerryPendingInterrupt |= IRQ2_TIMER2;
PERF_INC(timing_jerry_irqs);
m68k_set_irq(2); // Generate 68K IPL 2
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/tom/tom.c
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,11 @@
#include "jaguar.h"
#include "m68000/m68kinterface.h"
#include "op.h"
#include "perf_counters.h"
#include "settings.h"

PERF_COUNTER(timing_gpu_irqs_to_68k);

// Red Color Values for CrY<->RGB Color Conversion
uint8_t redcv[16][16] = {
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
Expand Down Expand Up @@ -1316,7 +1319,10 @@ void TOMExecPIT(uint32_t cycles)
GPUSetIRQLine(GPUIRQ_TIMER, ASSERT_LINE); // GPUSetIRQLine does the 'IRQ enabled' checking

if (TOMIRQEnabled(IRQ_TIMER))
{
PERF_INC(timing_gpu_irqs_to_68k);
m68k_set_irq(2); // Cause a 68000 IPL 2...
}

TOMResetPIT();
}
Expand All @@ -1329,7 +1335,10 @@ void TOMPITCallback(void)
GPUSetIRQLine(GPUIRQ_TIMER, ASSERT_LINE); // It does the 'IRQ enabled' checking

if (TOMIRQEnabled(IRQ_TIMER))
{
PERF_INC(timing_gpu_irqs_to_68k);
m68k_set_irq(2); // Generate a 68K IPL 2...
}

TOMResetPIT();
}
Expand Down
Loading
Loading