Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
50bf5cc
feat: dual-mode menu core with switchable native FB reader
asturur May 9, 2026
ef38ec1
fix: drive both modes from the native NTSC timing for clean CRT sync
asturur May 9, 2026
3f84a8e
feat: add OSD H/V image centering offsets (+/-8 px/lines)
asturur May 10, 2026
95a5153
fix: widen native video centering range
wizzomafizzo Jun 10, 2026
3a19e6d
fix: use safer native video centering steps
wizzomafizzo Jun 10, 2026
5db804a
feat: broadcast-geometry native video with PAL and 480i modes
wizzomafizzo Jun 11, 2026
23bcb88
docs: add frontend implementation brief for native video v2
wizzomafizzo Jun 11, 2026
9f1c2d6
fix: generate 27.000 MHz from a dedicated video PLL
wizzomafizzo Jun 11, 2026
b0fe65a
docs: clarify CRT mode coordination in the frontend brief
wizzomafizzo Jun 11, 2026
5d0feb9
fix: reject stale and legacy native video writers
wizzomafizzo Sep 9, 2026
20ff775
feat: add vblank scanout and black Menu startup
wizzomafizzo Sep 9, 2026
72d5536
ci: qualify scanout modules and gate RTL timing
wizzomafizzo Sep 9, 2026
92a8f3e
docs: clean up Menu guidance and rename README
wizzomafizzo Sep 9, 2026
3fc6772
fix: restore upstream snow cadence behind stock OSD
wizzomafizzo Sep 9, 2026
8b629b4
merge: sync native video branch with current master
wizzomafizzo Sep 9, 2026
fa8e765
ci: pin actions and disable checkout credentials
wizzomafizzo Sep 9, 2026
85cdbe3
fix: check running kernel release before scanout mapping
wizzomafizzo Sep 9, 2026
f54f764
fix: keep pixel enable low during video reset
wizzomafizzo Sep 9, 2026
1e07a6e
test: cover vblank gaps during legacy takeover
wizzomafizzo Sep 9, 2026
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: 59 additions & 4 deletions .github/workflows/ci_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,64 @@ permissions:
contents: read

jobs:
rtl-tests:
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false

- name: Install RTL simulator
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends -y iverilog

- name: Test timing-report gate
run: python3 -B -m unittest discover -s tb -p 'test_*.py'

- name: Run RTL regressions
run: sh tb/run.sh

scanout-module:
runs-on: ubuntu-24.04
timeout-minutes: 120
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false

- name: Install kernel build tools
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends -y \
build-essential bc bison flex libssl-dev libelf-dev \
libgmp-dev libmpc-dev libmpfr-dev kmod xz-utils git curl ca-certificates
rustup toolchain install 1.95.0 --profile minimal --no-self-update

- name: Cache verified compiler archive
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: kernel/.build/ci/gcc-arm-10.2-2020.11-x86_64-arm-none-linux-gnueabihf.tar.xz
key: scanout-gcc-102825ae56c9e00142d06f35d2bdd3299edb6060e84a275a25b095e66fd3fc2a

- name: Build qualified kernel and module
run: bash kernel/build-scanout.sh

- name: Upload scanout module
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: zaparoo-scanout-6.18.38-MiSTer
path: kernel/scanout-slots/zaparoo_scanout.ko
if-no-files-found: error

build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false

- name: Build with Quartus Lite
run: |
Expand All @@ -25,11 +78,13 @@ jobs:
theypsilon/quartus-lite-c5:17.0.2.docker0 \
/opt/intelFPGA_lite/quartus/bin/quartus_sh --flow compile menu

- name: Check build output
run: test -f output_files/menu.rbf
- name: Check build output and timing
run: |
test -f output_files/menu.rbf
python3 -B tb/check_timing.py output_files/menu.sta.rpt

- name: Upload RBF
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: menu-rbf
path: output_files/menu.rbf
Expand Down
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,15 @@ c5_pin_model_dump.txt
*.xml
*_netlist
*.cdf
/kernel/.build/
/kernel/**/*.o
/kernel/**/*.ko
/kernel/**/*.mod
/kernel/**/*.mod.c
/kernel/**/.*.cmd
/kernel/**/Module.symvers
/kernel/**/modules.order
/kernel/**/.tmp_versions/
/test-output/
.pi/
.claude/
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Startup core for MiSTer

## Native CRT video (this fork)

This fork drives the analog output with a native 15 kHz signal generated by
the core itself: 352x240p60 (NTSC) by default, with 720x480i60 and 352x288p50
(PAL) selectable by the ARM-side launcher through a DDR control block (see
[DDR contract](rtl/native_video_reader.sv)). There are no video options in the OSD — the
mode and the H/V centering trims are owned by the launcher. Idle video stays
black during frontend handoff; upstream-style snow appears behind the stock
OSD. Native frontend frames take priority over both backgrounds.

Native writers must use the v2 magic and publish a live frame counter. The
legacy 320-pixel, no-magic DDR contract is no longer accepted.

Note: `forced_scandoubler` (the "Forced scandoubler" MiSTer.ini setting) is
ignored by this core — the analog output is always 15 kHz. If your VGA output
feeds a 31 kHz-only monitor, set `vga_scaler=1` in MiSTer.ini instead.

* **ESC** - Back/Options
* **Enter** - OK
* **F1** - Cycle Background/Wallpaper
* **F9** - Go to Linux terminal (F12 - back)
* **F11** - Bluetooth Pairing Script
* **F12** - Recent Cores

## RTL tests

Install Icarus Verilog 12 and GNU coreutils, then run `sh tb/run.sh` from the
repository root. CI runs the same native timing, DDR reader, scanout latch,
snow cadence and bootstrap-black suites on Ubuntu 24.04. Assertions, compilation failures,
and a 600-second timeout per simulation fail the run. Generated simulators
stay in ignored `test-output/rtl/`.

Quartus builds must also pass `python3 tb/check_timing.py output_files/menu.sta.rpt`.
CI rejects missing or negative setup, hold, recovery, removal and pulse-width
results, even when Quartus reports successful compilation.

## Notes:
* Core supports sub-folders started with _ character.
* Regardless the place of RBF file, boot rom/vhd should be placed into either root of SD card or core's dedicated folder (should be created in root of SD card).
* Joystick (including emulation by keyboard) buttons defined in this core is default map for all cores unless defined in particalar core.

## Wallpaper
* Place menu.png or menu.jpg to the root of SD card to have it as background on HDMI (you can use vga_scaler=1 if you want it on VGA).
* Create "wallpapers" folder and place multiple .jpg or .png to it. Use **F1** to cycle between standard MiSTer backgrounds and wallpapers in folder.
17 changes: 0 additions & 17 deletions Readme.md

This file was deleted.

12 changes: 11 additions & 1 deletion files.qip
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
set_global_assignment -name QIP_FILE rtl/pll_video.qip
set_global_assignment -name SDC_FILE menu.sdc
set_global_assignment -name SYSTEMVERILOG_FILE rtl/sdram.sv
set_global_assignment -name SYSTEMVERILOG_FILE rtl/ddram.sv
set_global_assignment -name VERILOG_FILE rtl/lfsr.v
set_global_assignment -name SYSTEMVERILOG_FILE rtl/cos.sv
set_global_assignment -name SYSTEMVERILOG_FILE rtl/native_video_reader.sv
set_global_assignment -name SYSTEMVERILOG_FILE rtl/native_video_timing.sv
set_global_assignment -name SYSTEMVERILOG_FILE rtl/native_video_top.sv
set_global_assignment -name SYSTEMVERILOG_FILE menu.sv
set_global_assignment -name SYSTEMVERILOG_FILE sys/mister_magik_vblank_latch.sv
set_global_assignment -name SYSTEMVERILOG_FILE sys/mister_magik_latch_sys_top_bridge.sv
set_global_assignment -name SYSTEMVERILOG_FILE sys/mister_magik_bootstrap_black.sv
set_global_assignment -name SYSTEMVERILOG_FILE rtl/zaparoo_bootstrap_video.sv
set_global_assignment -name SYSTEMVERILOG_FILE rtl/zaparoo_snow_phase.sv
set_global_assignment -name SYSTEMVERILOG_FILE rtl/zaparoo_pixel_enable.sv
60 changes: 60 additions & 0 deletions kernel/build-scanout.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
# Build the exact kernel/module pair accepted by scanout-slots/Makefile.
set -euo pipefail

root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
build_root=${BUILD_ROOT:-"$root/kernel/.build/ci"}
kernel_src=${KERNEL_SRC:-"$build_root/linux"}
kernel_build="$build_root/kernel"
revision=aec7dc3aa4846385736f1d54c9155e3b3c726708
package=gcc-arm-10.2-2020.11-x86_64-arm-none-linux-gnueabihf
mkdir -p "$build_root"

if [[ -z ${CROSS_COMPILE:-} ]]; then
archive="$build_root/$package.tar.xz"
if [[ ! -f $archive ]]; then
curl --fail --location --retry 3 --connect-timeout 30 --max-time 1200 \
"https://developer.arm.com/-/media/Files/downloads/gnu-a/10.2-2020.11/binrel/$package.tar.xz" \
--output "$archive.partial"
mv "$archive.partial" "$archive"
fi
# SHA-256: https://github.com/buildroot/buildroot/blob/2021.02/toolchain/toolchain-external/toolchain-external-arm-arm/toolchain-external-arm-arm.hash
printf '%s %s\n' 102825ae56c9e00142d06f35d2bdd3299edb6060e84a275a25b095e66fd3fc2a "$archive" | sha256sum -c -
tar -xJf "$archive" -C "$build_root"
export CROSS_COMPILE="$build_root/$package/bin/arm-none-linux-gnueabihf-"
fi

test "$("${CROSS_COMPILE}gcc" -dumpfullversion -dumpversion)" = 10.2.1
if [[ -z ${KERNEL_SRC:-} ]]; then
if [[ ! -d $kernel_src ]]; then
git init "$kernel_src"
git -C "$kernel_src" remote add origin https://github.com/MiSTer-devel/Linux-Kernel_MiSTer.git
fi
if ! git -C "$kernel_src" cat-file -e "$revision^{commit}" 2>/dev/null; then
git -C "$kernel_src" fetch --depth=1 origin "$revision"
fi
if git -C "$kernel_src" rev-parse --verify HEAD >/dev/null 2>&1; then
git -C "$kernel_src" diff --quiet HEAD -- .
fi
git -C "$kernel_src" checkout --detach "$revision"
fi
# Explicit source overrides are read-only inputs, never reset or checked out.
test "$(git -C "$kernel_src" rev-parse HEAD)" = "$revision"
git -C "$kernel_src" diff --quiet HEAD -- .

# Pin optional tool detection as well as the target compiler. Rust 1.95.0 is
# needed only to reproduce the qualified Kconfig fingerprint; no Rust is built.
export RUSTUP_TOOLCHAIN=1.95.0
rustc --version | grep -E '^rustc 1\.95\.0 '
export PAHOLE=false
export BINDGEN=false
kernel_args=(-C "$kernel_src" "O=$kernel_build" ARCH=arm
"CROSS_COMPILE=$CROSS_COMPILE" LOCALVERSION=-MiSTer)
make "${kernel_args[@]}" MiSTer_defconfig
printf '%s %s\n' 0d010a3d551cbffcd91af7850f3f745ce73f3bb911cfd56ead902fc9b6c69823 "$kernel_build/.config" | sha256sum -c -
# modules_prepare alone cannot supply a genuine Module.symvers.
make "${kernel_args[@]}" -j"${JOBS:-$(nproc)}" vmlinux modules
make -C "$root/kernel/scanout-slots" KERNEL_SRC="$kernel_src" KERNEL_BUILD="$kernel_build" CROSS_COMPILE="$CROSS_COMPILE"
vermagic=$(modinfo -F vermagic "$root/kernel/scanout-slots/zaparoo_scanout.ko")
test "${vermagic% }" = '6.18.38-MiSTer SMP mod_unload ARMv7 p2v8'
sha256sum "$root/kernel/scanout-slots/zaparoo_scanout.ko"
28 changes: 28 additions & 0 deletions kernel/scanout-slots/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2026 Nigel Breslaw
# Zaparoo fork: local builds against the qualified MiSTer kernel.

KERNEL_SRC ?= ../.build/linux-6.18
KERNEL_BUILD ?= ../.build/kernel-6.18
ARCH ?= arm
CROSS_COMPILE ?= arm-none-linux-gnueabihf-
LOCALVERSION ?= -MiSTer

obj-m += zaparoo_scanout.o

.PHONY: all qualify clean
all: qualify
$(MAKE) -C $(abspath $(KERNEL_SRC)) O=$(abspath $(KERNEL_BUILD)) M=$(CURDIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) LOCALVERSION=$(LOCALVERSION) modules

# Pin source/config as well as vermagic; never bless a different 6.18 build.
qualify:
test "$(ARCH)" = arm
test "$(LOCALVERSION)" = -MiSTer
test "$$(git -C $(abspath $(KERNEL_SRC)) rev-parse HEAD)" = aec7dc3aa4846385736f1d54c9155e3b3c726708
git -C $(abspath $(KERNEL_SRC)) diff --quiet HEAD -- .
printf '%s %s\n' 0d010a3d551cbffcd91af7850f3f745ce73f3bb911cfd56ead902fc9b6c69823 $(abspath $(KERNEL_BUILD))/.config | sha256sum -c -
test -s $(abspath $(KERNEL_BUILD))/Module.symvers
test "$$($(CROSS_COMPILE)gcc -dumpfullversion -dumpversion)" = 10.2.1

clean:
$(MAKE) -C $(abspath $(KERNEL_SRC)) O=$(abspath $(KERNEL_BUILD)) M=$(CURDIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) LOCALVERSION=$(LOCALVERSION) clean
103 changes: 103 additions & 0 deletions kernel/scanout-slots/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Zaparoo scanout slots

This GPL-3.0-or-later component derives from Nigel Breslaw's MagiK scanout-slot
module and the Zaparoo demo's 1080p extension. Keep its source and attribution
with this Menu fork. It is a separate kernel artifact, not linked into the
frontend. Source imports retain their original license; the Linux module
loader's license classification is separate and must not be changed to gain
access to GPL-only kernel exports.

## Compatibility policy

Initially support only the exact qualified MiSTer 6.18 kernel build. Do not
force-load a module, update the kernel, or reuse an unverified module. Unknown
and older kernels retain the ordinary fb0 frontend path. Future kernel changes
require rebuilding and requalifying the module and its memory-map contract.

Use a Zaparoo-specific module/device identity and ABI. Do not install over
`mem_wc.ko` or `mister_magik_scanout_slots.ko`, or unload someone else's module.

## Shared hardware

DreamSTer's `mem_wc` provides a generic write-combined physical-memory mapper.
MagiK's module provides bounded scanout slots. These are not interchangeable
interfaces. A loaded module is not necessarily an active renderer.

Only one Zaparoo client may own the slots at a time. Resource reservations must
last until the last mapping/file reference closes, including after process
termination. Idle module residency must not reserve another application's
memory indefinitely. Main must coordinate the frontend's FPGA bus access and
terminate its child when Main exits.

Resource reservations prevent conflicting cooperative drivers, but do not
stop arbitrary `/dev/mem` or `/dev/mem_wc` mappings or another FPGA bitstream.
Conflict detection and lifecycle handoff are required; this is not a security
boundary against another privileged process starting an unrestricted mapper.
Do not claim concurrent DreamSTer/MagiK/Zaparoo rendering is supported.

## Upstream references

- https://github.com/NigelBreslaw/MiSTer-MagiK/tree/main/mister/platform/kernel/scanout-slots
- https://github.com/skmp/minicast/tree/master/mem_wc
- https://github.com/MiSTer-devel/Linux-Kernel_MiSTer/tree/MiSTer-v6.18

## Qualified build

CI builds the module in a separate job from Quartus and uploads
`zaparoo-scanout-6.18.38-MiSTer` containing `zaparoo_scanout.ko`. It does not
install the module or change the device kernel. Only the checksum-verified
compiler archive is cached; kernel output and `Module.symvers` are built fresh.

Run the same build from the Menu repository root. Install the build packages
listed in `.github/workflows/ci_build.yml`, then:

```sh
rustup toolchain install 1.95.0 --profile minimal
bash kernel/build-scanout.sh
```

The script pins the kernel revision and verifies the GNU ARM 10.2.1 archive's
SHA-256 before extracting it. Rust 1.95.0 reproduces Kconfig's tool-detection
fields; the kernel/module build does not compile Rust. All downloaded inputs
and kernel output stay under ignored `kernel/.build/ci/`.

For local reuse, `CROSS_COMPILE` may point to Main's qualified GNU toolchain
(not the frontend's musl compiler), and `KERNEL_SRC` may point to an existing
clean checkout of the pinned revision. `BUILD_ROOT` accepts an absolute build
path, and `JOBS` controls parallelism. The full kernel build creates real
`Module.symvers`; do not suppress modpost errors or substitute
`modules_prepare` alone.

The module Makefile rejects a different source revision, tracked source edits,
config fingerprint, compiler version or missing symbol table. Do not loosen
these checks to make an unknown build pass. Expected vermagic:
`6.18.38-MiSTer SMP mod_unload ARMv7 p2v8`.

Qualification evidence:

| Input | SHA-256 |
|---|---|
| Generated `.config` | `0d010a3d551cbffcd91af7850f3f745ce73f3bb911cfd56ead902fc9b6c69823` |
| `drivers/video/fbdev/MiSTer_fb.c` | `f4044889e96a843a54bde091737825043b71b6bb8994fe3f92387cccd6ee3924` |
| `arch/arm/boot/dts/intel/socfpga/socfpga_cyclone5_de10_nano.dts` | `5c03d8ffb9e1477523d6434c5255db46433158f771fb6288320c42f8d3484938` |

ABI v1 uses `/dev/zaparoo-scanout`, ioctl `_IOR('Z', 1, layout)` and a 64-byte
layout. Slots start at `0x23000000` and `0x23400000`, outside the complete
`MiSTer_fb` DT aperture (`0x22000000`, 8 MiB). Each has 4,147,200 usable bytes
and a 4,149,248-byte mapping. Slot-one mmap selector is 8,294,400, **not its
physical address**. Only exact shared read/write mappings are accepted;
executable mappings and fork inheritance are disabled.

6.18 compatibility decisions:

- `registered_fb` is no longer exported. Validate the pinned root-level DT
aperture instead of linking to that private symbol.
- `no_llseek` is gone; use a NULL file-operation entry.
- Published-VMA setters require GPL-only locking helpers. The pinned kernel
invokes `.mmap` on a newly allocated VMA before insertion into the tree, so
initialize its flags with `vm_flags_init`. Recheck this ordering for a new
kernel; do not change the loader license marker to bypass modpost.

Successful compilation is **not hardware qualification**. Before installing
or distributing the artifact, verify the matched Main/frontend/Menu stack on
the target device, including ownership handoff, crash recovery and fb0 fallback.
Loading
Loading