-
Notifications
You must be signed in to change notification settings - Fork 13
config.vhd Switches and Settings
This guide explains the parts of a MiSTer2MEGA65 (M2M) core's
config.vhd that are not the On-Screen Menu structure and not the
Welcome & Help screen text.
Scope of this guide. We cover the remaining switches and settings in
config.vhd: version strings, file-browser paths, the SD-card settings file, startup reset behavior, pause and input routing, HDMI scaler ownership, virtual-drive write-back tuning, and the serial-console core name. We deliberately do not re-teachOPTM_ITEMS,OPTM_GROUPS, submenus,%s,OPTM_DEP, orWHS_DATA/WHS. Those are covered by the two companion guides: "How to build an On-Screen Menu (OSM) inconfig.vhd" and "How to build the Welcome & Help screens inconfig.vhd". Everything here is based on the shipped C64 reference core and on the framework/Shell source that consumes these settings.
The useful way to think about config.vhd is this:
You author constants in VHDL. At synthesis time they become a little read-only ROM. At runtime the QNICE Shell reads that ROM and turns the values into live behavior.
The Shell does not parse VHDL. It sees config.vhd through a small addressable
interface:
QNICE Shell firmware qnice_wrapper.vhd CORE/vhdl/config.vhd
┌────────────────────┐ ┌────────────────────┐ ┌─────────────────────┐
│ select device │ │ exposes framework │ │ address decoder │
│ M2M$CONFIG │──────►│ device 0x0002 │───────►│ returns constants │
│ select a 4 KB │ │ (C_DEV_OSM_CONFIG) │ │ as 16-bit words │
│ config "window" │ └────────────────────┘ └─────────────────────┘
│ read from 0x7000 │
└────────────────────┘
On the VHDL side the address is split like this:
| Address bits | Meaning |
|---|---|
27..12 |
the selector: which config block to read |
11..0 |
the index inside that 4 KB config block |
For example:
- selector
0x0100returns the default file-browser directory, - selector
0x0101returns the SD-card config filename, - selector
0x0110returns the general settings words, - selector
0x0200returns the core name for the serial terminal.
String constants are served one character per 16-bit word and are
zero-terminated. Boolean constants are served as 0 or 1. Unknown selectors
return 0xEEEE, which is why a wrong selector usually shows up as a very
obvious sentinel value in debugging.
config.vhd has two kinds of lines:
-
configuration lines, where you set constants such as
DIR_START,RESET_COUNTER,SAVE_SETTINGS, andASCAL_USAGE, and -
framework lines marked
!!! DO NOT TOUCH !!!, such as selector constants, helper types, and the address decoder at the bottom.
You edit the configuration constants. You do not edit the selector values or the address decoder. The firmware and VHDL framework agree on those numbers.
Here is the complete shape of the C64 reference config.vhd, grouped by topic.
The rows marked "covered elsewhere" are intentionally only mentioned here.
Section in config.vhd
|
You edit? | Main constants | Covered here? |
|---|---|---|---|
| Welcome & Help screens | yes |
CORE_VERSION, WHS_*, WHS_DATA, WHS
|
only CORE_VERSION; screen authoring is in Welcome and Help Screens
|
| File browser / config file | yes |
DIR_START, CFG_FILE
|
yes |
| General configuration | yes |
RESET_COUNTER, OPTM_PAUSE, welcome/input/ascal/vdrive/save flags |
yes, except the full Welcome-screen behavior in Welcome and Help Screens |
| Core name | yes | CORENAME |
yes |
| OSM menu | yes |
OPTM_ITEMS, OPTM_GROUPS, OPTM_SIZE, OPTM_DX, OPTM_DY, OPTM_S_*
|
mostly covered by On‐Screen‐Menu (OSM); persistence details are here |
| Address decoder | no |
addr_decode, selectors, getGenConf
|
reference only |
The C64 reference is useful because it exercises almost all of the framework: SD-card persistence, file browsing, virtual drives, HDMI filters, simulated cartridges, help screens, and a large nested OSM. But the settings described in this page are framework-level settings; another M2M core will use the same mechanisms even when its own menu and core logic are completely different.
The C64 reference keeps the release version in one place:
constant CORE_VERSION : string := "WIP-V6-A17";That single string is then reused in several places:
constant CFG_FILE : string := "/c64/c64mega65-" & CORE_VERSION;
constant CORENAME : string :=
"Commodore 64 for MEGA65 Version " & CORE_VERSION;The Welcome and Help screen strings also embed CORE_VERSION, so the version
shown to the user and the version used for the settings filename stay in sync.
The C64 release script checks this constant and, for release builds, generates a
matching c64mega65-<version> settings file next to the .cor files.
The saved OSM settings file is one byte per menu line. That means the file layout
depends on the exact OPTM_ITEMS / OPTM_GROUPS layout of the core version that
created it.
If you ship version-specific filenames:
constant CFG_FILE : string := "/c64/c64mega65-" & CORE_VERSION;then several core versions can live on one SD card at the same time:
/c64/c64mega65-V6
/c64/c64mega65-V6.1
/c64/c64mega65-WIP-V6-A17
Each version reads only its own settings file. This avoids a subtle class of bugs where an old settings file has the right length but the bits now mean different menu lines.
For the C64 reference workflow, update CORE_VERSION first. Everything derived
from it follows:
- welcome/help page version text,
- the serial-terminal
CORENAME, - the on-SD-card
CFG_FILE, - the release-time generated config file name.
If your core does not use a release script like the C64 reference, still copy the same habit: make one version constant and derive user-facing text and config filenames from it.
The file browser's first directory comes from:
constant DIR_START : string := "/c64";When the Shell opens the file browser for the first time, it reads selector
0x0100 (M2M$CFG_DIR_START) and passes that string to the directory browser.
In the C64 reference this means the user starts in /c64, because that is where
the C64 user files and config file are expected to live.
The Shell tries DIR_START. If that path is not found, the file selector
falls back to the root directory (/) instead of aborting. This is helpful for
users with an empty or freshly formatted SD card.
That fallback is only for the "path not found" case. Other directory-read
errors are handled as errors. So this is not a substitute for good release
instructions: if your docs say "put files in /mycore", then DIR_START
should match that path.
Use an absolute path with a leading slash:
constant DIR_START : string := "/c64";
constant DIR_START : string := "/amiga";
constant DIR_START : string := "/games";Keep it short and boring. The path is stored as a zero-terminated string in the config ROM and handed directly to the FAT32 directory browser.
Two constants work together:
constant CFG_FILE : string := "/c64/c64mega65-" & CORE_VERSION;
constant SAVE_SETTINGS : boolean := true;CFG_FILE is the path of the SD-card file. SAVE_SETTINGS says whether the
Shell should try to load and save OSM state through that file.
The important part is this:
The M2M FAT32 code can write existing file contents, but it does not create or grow files. So
SAVE_SETTINGS = trueis not enough. The file must already exist and must already have exactly the right size.
The file is exactly OPTM_SIZE bytes long:
| File byte | Meaning |
|---|---|
byte 0
|
OSM line 0 selected? 0 or 1
|
byte 1
|
OSM line 1 selected? 0 or 1
|
byte 2
|
OSM line 2 selected? 0 or 1
|
| ... | ... |
It mirrors the runtime 256-bit OSM state vector: one bit per flat menu line, not one bit per group id. The OSM guide explains that flat index model in detail.
There is one special marker:
- if the first byte is
0xFF, the Shell treats the file as freshly generated and uses theOPTM_G_STDSELdefaults fromconfig.vhd.
The helper script writes exactly that kind of fresh file:
cd M2M/tools
./make_config.sh c64mega65-WIP-V6-A17 autoWith auto, the script reads OPTM_SIZE from ../../CORE/vhdl/config.vhd and
writes that many bytes of 0xFF.
At Shell startup, the menu state is initialized in one of two ways.
Case A: saved settings are loaded. This happens only when all of these are true:
-
SAVE_SETTINGS = true, - the SD card can be mounted,
-
CFG_FILEexists, - its file size is exactly
OPTM_SIZEbytes, - the first byte is not
0xFF, - every byte read is either
0or1.
Case B: defaults are used. This happens when SAVE_SETTINGS = false, the
file is missing, the size is wrong, the first byte is 0xFF, or the SD card
cannot be mounted. Defaults come from OPTM_G_STDSEL.
This fallback is deliberate. A missing config file does not break the core; it only means settings do not persist.
There is one harsher case: if the file passes the size and first-byte checks but
later contains a byte other than 0 or 1, the Shell treats it as a corrupt
settings file and stops with a fatal error. A generated all-0xFF file avoids
that because the first byte is tested before the rest of the file is read.
When the user closes the OSM, the Shell saves only when it has a valid config file handle and the remembered settings actually changed.
There are three important guard rails:
-
Mount-drive lines are not saved. The Shell writes
0for lines taggedOPTM_G_MOUNT_DRV, because a mounted disk image is runtime state, not a persistent menu preference. -
Load-ROM lines are not saved. The Shell writes
0for lines taggedOPTM_G_LOAD_ROM, for the same reason. - Saving is disabled after an SD-card switch. If the active SD card changes, the Shell clears the config-file handle so it cannot accidentally write settings to a different card than the one it loaded from.
Saving is also skipped while any virtual-drive write cache is dirty, because the same FAT32 machinery and hardware buffer are involved in disk-image write-back. This protects disk-image data.
Regenerate and redistribute the settings file whenever the persistent OSM layout changes.
The obvious case is an OPTM_SIZE change. There is also a subtler case: a
same-size reorder or semantic change of OPTM_ITEMS / OPTM_GROUPS can still
make old saved bytes mean the wrong thing. The Shell only checks the file size,
then maps saved bytes one-to-one onto the flat menu-line state.
Changing help text or welcome text does not change that settings format. Adding, removing, reordering, or reusing persistent menu lines does.
For the C64 reference release flow, make_release.py generates the matching
c64mega65-<version> file automatically. During development, use
M2M/tools/make_config.sh.
The framework starts with the core held in reset. The Shell then runs its initialization, loads menu defaults or saved settings, and finally releases the core.
This constant controls an extra delay while reset is still active:
constant RESET_COUNTER : natural := 100;The value is not milliseconds. The source comment calls it a number of
"QNICE loops". The firmware simply counts from zero to RESET_COUNTER inside
RP_SYSTEM_START before clearing the reset bit in the QNICE control/status
register.
Use it as a coarse startup stabilizer:
| Value | Effect |
|---|---|
0 |
no extra reset-hold loop |
100 |
C64 reference default |
| larger | keep the core in reset longer before first run |
The Shell writes the QNICE CSR reset bit. In VHDL that bit becomes:
reg_csr(0)
-> csr_reset_o
-> qnice_csr_reset
-> main_qnice_reset_o
-> your core's reset input path
In the C64 reference, that reset reaches main_reset_core_i and ultimately the
soft-reset input of main.vhd. Other cores may route it differently, but the
framework signal is the same.
Most cores leave the core in reset until the Shell has loaded the menu state. This matters because OSM defaults can feed clock, video, memory, or ROM-selection logic before the emulated machine starts.
The Options menu is drawn by QNICE as an overlay. From that menu the user can also open Help screens and the file browser. Some cores should keep running behind those menu-driven overlays; some should freeze.
That choice is:
constant OPTM_PAUSE : boolean := false;When the user opens the Options menu with Help, the Shell calls
RP_OPTM_START. That routine first clears pause, keyboard, and joystick CSR
bits, then turns selected ones back on according to the config.vhd settings.
If OPTM_PAUSE = true, it sets the CSR pause bit while that menu session is
active. When the menu closes, the Shell clears pause again.
The startup Welcome screen is separate. It is also drawn by QNICE, but it is not
entered through RP_OPTM_START; its behavior is controlled by
WELCOME_ACTIVE / WELCOME_AT_RESET.
In VHDL the pause bit follows this path:
reg_csr(1)
-> csr_pause_o
-> qnice_csr_pause
-> main_qnice_pause_o
-> your core's pause input
The C64 core passes this to the MiSTer C64 core's pause input and to the
simulated IEC drive. Another core can interpret pause however it needs to, but
the framework signal is just "pause requested by Shell".
Use false when:
- the user should be able to mount media while the machine keeps running,
- the original system had background activity you do not want to freeze,
- pausing the core would disturb audio/video or timing-sensitive behavior.
Use true when:
- menu interaction should freeze gameplay,
- accidental input during menus would be harmful,
- the core has a clean pause implementation.
The C64 reference uses false.
The MEGA65 keyboard and joystick ports are shared between the framework Shell and the running core. The Shell always needs to read keys for menus. The question is whether the core should also receive those inputs during reset or while an OSD is open.
There are six booleans:
constant KEYBOARD_AT_RESET : boolean := false;
constant JOY_1_AT_RESET : boolean := false;
constant JOY_2_AT_RESET : boolean := false;
constant KEYBOARD_AT_OSD : boolean := false;
constant JOY_1_AT_OSD : boolean := false;
constant JOY_2_AT_OSD : boolean := false;The OSD group is applied when the Options menu opens: RP_OPTM_START first
clears pause, keyboard, and joystick CSR bits, then turns selected ones back on
according to these *_AT_OSD flags.
The reset group is more limited in the current firmware. RP_SYSTEM_START turns
keyboard or joystick bits on when a *_AT_RESET flag is true, but it does not
clear those bits when a flag is false. Also note that the QNICE CSR default
already has keyboard and joystick bits enabled. Treat the reset flags as "request
input on during the startup reset window", not as a guaranteed input-disable
switch.
The booleans set or clear CSR bits:
| Setting | CSR bit | VHDL destination |
|---|---|---|
KEYBOARD_* |
bit 3 | m2m_keyb.enable_core_i |
JOY_1_* |
bit 4 | joystick port 1 enable in the debouncer |
JOY_2_* |
bit 5 | joystick port 2 enable in the debouncer |
The Shell's own keyboard snapshot is independent. Even with
KEYBOARD_AT_OSD = false, the Shell can still read Help,
Cursor, Return, Space, Run/Stop,
F1, and F3 for the menu and file browser. The setting only
decides whether the core also sees those key presses.
The C64 reference sets all six to false. The most important effect is in the
OSM path: while the menu is open, menu key presses do not leak into the C64. After
the Shell closes the menu, it waits briefly and reconnects keyboard and
joysticks.
This is usually the beginner-safe default. Turn the OSD flags on only when the core must keep receiving input while a menu or full-screen overlay is active.
Two Welcome-screen booleans live in the same general settings block as reset, pause, input routing, Ascal, and virtual-drive tuning:
constant WELCOME_ACTIVE : boolean := false;
constant WELCOME_AT_RESET : boolean := false;They are indices 3 and 4 in selector 0x0110 (M2M$CFG_GENERAL). Their full
behavior belongs to the Welcome & Help screen guide; they are mentioned here only
so the general settings table is complete. These flags are interpreted by the
Shell and do not feed a core VHDL signal directly.
ascal.vhd is the MiSTer scaler used for HDMI output. The framework exposes a
5-bit Ascal mode word to it:
| Bits | Meaning |
|---|---|
2..0 |
scaler mode: nearest, bilinear, sharp bilinear, bicubic, polyphase |
3 |
triple buffering |
4 |
reserved in the current framework interface |
config.vhd decides who owns that mode word:
constant ASCAL_USAGE : natural := 1;
constant ASCAL_MODE : natural := 0;There are three modes.
ASCAL_USAGE |
Name in firmware | Meaning |
|---|---|---|
0 |
M2M$CFG_AUSE_CFG |
fixed: Shell writes ASCAL_MODE from config.vhd into M2M$ASCAL_MODE at startup |
1 |
M2M$CFG_AUSE_CUSTOM |
custom: generic Shell does nothing; your core-specific QNICE assembly may write M2M$ASCAL_MODE; the C64 core offers various HDMI filters by implementing custom code in m2m-rom.asm
|
2 |
M2M$CFG_AUSE_AUTO |
auto: QNICE sets CSR bit 11, making sure that ascal_mode_o that you configure in mega65.vhd defines ASCAL_MODE |
Use this for a simple core with one scaler mode:
constant ASCAL_USAGE : natural := 0;
constant ASCAL_MODE : natural := 0; -- nearest neighborAt startup, ASCAL_INIT writes ASCAL_MODE to the QNICE register
M2M$ASCAL_MODE. After that, the mode stays there unless some custom firmware
changes it.
This is what the C64 reference uses:
constant ASCAL_USAGE : natural := 1;
constant ASCAL_MODE : natural := 0; -- ignored in this modeThe generic Shell leaves the mode register alone. The C64-specific
CORE/m2m-rom/m2m-rom.asm code reads the selected HDMI filter from the OSM
state vector, writes M2M$ASCAL_MODE, and for polyphase filters also loads the
matching coefficient tables into Ascal's polyphase RAM.
This is how the C64 menu can offer:
- No Filter,
- Sharp Bilinear,
- Bicubic,
- Smooth,
- Lanczos,
- Scanlines,
- CRT (S-Video),
- CRT (Composite).
The C64-specific code explicitly assumes ASCAL_USAGE = 1. If you change the
C64 reference back to ASCAL_USAGE = 2, those firmware writes to
M2M$ASCAL_MODE become ineffective because the register is auto-synced from the
VHDL input instead.
Use this when the scaler mode should be a live VHDL signal from your core. In
this mode the Shell sets CSR bit 11, and M2M/vhdl/QNICE/qnice.vhd takes the
5-bit Ascal mode from the VHDL input path instead of from the writable
M2M$ASCAL_MODE register.
As an M2M core author, you configure this in your core top,
CORE/vhdl/mega65.vhd, by driving the MEGA65_Core output ports:
qnice_ascal_mode_o <= "...";
qnice_ascal_polyphase_o <= '0' or '1';
qnice_ascal_triplebuf_o <= '0' or '1';Pick exactly one owner:
- simple constant mode:
ASCAL_USAGE = 0, - custom Shell logic or filter dispatcher:
ASCAL_USAGE = 1, - live VHDL-controlled scaler mode:
ASCAL_USAGE = 2.
Do not mix custom M2M$ASCAL_MODE writes with auto mode and expect both to win.
Auto mode deliberately makes the register follow the VHDL input.
Virtual drives are deliberately buffered. The emulated core does not write directly to the FAT32 file on the SD card.
Instead, the framework exposes the familiar MiSTer-style sd_lba, sd_rd,
sd_wr, sd_ack, and sd_buff_* signals to the core. The QNICE Shell services
those requests. For writes, it copies the bytes from the core's small transfer
buffer into the mounted disk image that lives in FPGA-accessible RAM, acknowledges
the write, and only later writes the RAM image back to the SD-card file.
This is why saves can be fast enough for timing-sensitive cores such as the C64: the core sees a RAM-backed disk image first. Physical SD-card write-back happens afterward in the background.
Two config.vhd constants tune that background write-back:
constant VD_ANTI_THRASHING_DELAY : natural := 2000;
constant VD_ITERATION_SIZE : natural := 100;The source-level flow is:
- The core raises a drive write request (
sd_wr_i). -
HANDLE_IOinM2M/rom/shell.asmnotices the request and callsHANDLE_DRV_WR. -
HANDLE_DRV_WRcopies the requested byte range from the core-side transfer buffer into the full disk-image buffer in RAM. - The Shell acknowledges the write by pulsing
VD_ACK. -
M2M/vhdl/vdrives.vhdsees that a latched write was acknowledged and marks the drive cache dirty. - While the cache is dirty, hardware counts down a quiet-time delay. Every new write clears the "flush started" state and restarts that delay.
- When the delay reaches zero, hardware raises
VD_CACHE_FLUSH_ST. - The next
FLUSH_CACHEpass starts or continues the SD-card write-back.
Two details matter for tuning.
First, the delay is handled in hardware. The firmware does not guess how long ago
the last write happened; it reads the VD_CACHE_FLUSH_ST flag from
vdrives.vhd.
Second, the current firmware does not keep a changed-sector bitmap. Once a flush starts, it seeks to byte 0 of the mounted image file and writes the disk-image buffer back to the file until the file size is exhausted. It spreads that full write-back across many small iterations so the Shell can keep polling input, menu state, and new drive requests.
This is the quiet time, in milliseconds, between the last acknowledged core write and the moment when hardware allows flushing to start.
constant VD_ANTI_THRASHING_DELAY : natural := 2000;The default is two seconds. It is called "anti thrashing" because disk saves usually arrive as bursts. If the Shell started rewriting the SD-card file after the first sector, the next sector would make the cache dirty again and force the flush process to restart. Waiting until writes have been quiet for a short time avoids that repeated start-over behavior.
Lowering this value makes write-back begin sooner after the final write. That can make the visible dirty state shorter for small, simple saves. Lower it too far, though, and bursty save routines can repeatedly interrupt the flush before it has made useful progress.
Raising this value makes the framework more patient. That is useful if the core
or emulated drive tends to produce several separated write bursts for one logical
save. The tradeoff is that the cache remains dirty longer. During that dirty
window the C64 reference keeps the drive LED on/yellow, protects soft reset
through prevent_reset, and the Shell skips OSM settings persistence.
The VHDL virtual-drive device has one delay register per drive. config.vhd
currently exposes one global value, and VD_INIT writes that same value to every
drive.
This is how many bytes FLUSH_CACHE writes to the SD-card file in one background
flush iteration after flushing has started.
constant VD_ITERATION_SIZE : natural := 100;This is not a sector count, not a block count, and not a VHDL burst length. In
the current firmware it is a byte count for one Shell pass through the flush
continuation loop. The loop reads one byte from the disk-image RAM buffer, calls
f32_fwrite, updates counters, and repeats until it has written
VD_ITERATION_SIZE bytes or until the whole mounted image file has been written.
After each partial iteration, the Shell remembers:
- the current 4 KB RAM window,
- the offset inside that window,
- the high and low words of the remaining byte count.
The next FLUSH_CACHE call resumes from that saved position. When the remaining
count reaches zero, the Shell calls f32_fflush and writes 0 to
VD_CACHE_DIRTY, which also clears the flushing and start flags in vdrives.vhd.
Larger values clear the dirty state in fewer Shell passes. The cost is that each pass spends longer inside the FAT32 write loop before returning to the main loop. Very large values can make the OSM, file browser, and drive-request polling feel less responsive during active write-back.
Smaller values return to the main loop more often. That improves responsiveness while flushing, but it takes more passes to rewrite the mounted image file, so the dirty state lasts longer.
The framework currently reads one value from config.vhd and copies it into the
Shell's per-drive VDRIVES_ITERSIZ array for every drive.
For mount lines that use %s, the OSM can temporarily show the saving string:
constant OPTM_S_SAVING : string := "<Saving>";Those %s replacement strings are explained in the OSM guide. They are relevant
here because the dirty-cache state also affects settings persistence: the Shell
does not save OSM settings while a virtual-drive cache is dirty.
CORENAME is not the splash screen and not the OSM title. It is the name printed
to the QNICE serial terminal when the Shell starts:
constant CORENAME : string :=
"Commodore 64 for MEGA65 Version " & CORE_VERSION;The Shell startup code calls LOG_CORENAME, which selects config window
0x0200 and prints this zero-terminated string.
For end users, use the Welcome screen, Help screens, and OSM text. CORENAME is
for the debug console and logs.
You normally do not need this table to author a core. It is useful when reading the Shell source or debugging with the QNICE monitor.
All selectors are 4 KB windows. QNICE selects the config device (M2M$CONFIG,
device 0x0002), writes one of these selectors to M2M$RAMROM_4KWIN, then reads
from M2M$RAMROM_DATA (0x7000 + index).
| Selector | Firmware name | Data served by config.vhd
|
|---|---|---|
0x0100 |
M2M$CFG_DIR_START |
DIR_START string |
0x0101 |
M2M$CFG_CFG_FILE |
CFG_FILE string |
0x0110 |
M2M$CFG_GENERAL |
general settings words, table below |
0x0200 |
M2M$CFG_CORENAME |
CORENAME string |
0x0300..0x030A |
M2M$CFG_OPTM_* |
main OSM data, covered by the OSM guide |
0x0310..0x0313 |
M2M$CFG_OPTM_* |
OSM dependency data, covered by the OSM guide |
0x1000.. |
M2M$CFG_WHS |
Welcome & Help screen data, covered by the WHS guide |
The general window is a small word table. Index 0 is unused and returns 0.
Index in config.vhd
|
QNICE address | Firmware name | Constant |
|---|---|---|---|
1 |
0x7001 |
M2M$CFG_RP_COUNTER |
RESET_COUNTER |
2 |
0x7002 |
M2M$CFG_RP_PAUSE |
OPTM_PAUSE |
3 |
0x7003 |
M2M$CFG_RP_WELCOME |
WELCOME_ACTIVE |
4 |
0x7004 |
M2M$CFG_RP_WLCM_RST |
WELCOME_AT_RESET |
5 |
0x7005 |
M2M$CFG_RP_KB_RST |
KEYBOARD_AT_RESET |
6 |
0x7006 |
M2M$CFG_RP_J1_RST |
JOY_1_AT_RESET |
7 |
0x7007 |
M2M$CFG_RP_J2_RST |
JOY_2_AT_RESET |
8 |
0x7008 |
M2M$CFG_RP_KB_OSD |
KEYBOARD_AT_OSD |
9 |
0x7009 |
M2M$CFG_RP_J1_OSD |
JOY_1_AT_OSD |
10 |
0x700A |
M2M$CFG_RP_J2_OSD |
JOY_2_AT_OSD |
11 |
0x700B |
M2M$CFG_ASCAL_USAGE |
ASCAL_USAGE |
12 |
0x700C |
M2M$CFG_ASCAL_MODE |
ASCAL_MODE |
13 |
0x700D |
M2M$CFG_VD_AT_DELAY |
VD_ANTI_THRASHING_DELAY |
14 |
0x700E |
M2M$CFG_VD_ITERSIZE |
VD_ITERATION_SIZE |
15 |
0x700F |
M2M$CFG_SAVEOSDCFG |
SAVE_SETTINGS |
For source reading, the path is:
CORE/vhdl/config.vhd
-> M2M/vhdl/qnice_wrapper.vhd exposes it as C_DEV_OSM_CONFIG / M2M$CONFIG
-> M2M/rom/sysdef.asm names the selector and index constants
-> M2M/rom/gencfg.asm, options.asm, selectfile.asm, vdrives.asm, coreinfo.asm
interpret the values
-> M2M/vhdl/QNICE/qnice.vhd and M2M/vhdl/framework.vhd turn CSR/register writes
into reset, pause, input-enable, OSM, and Ascal signals
This is why many config.vhd settings have no direct "consumer" in your core
RTL. The Shell consumes them first, then writes live framework registers.
Edit:
constant DIR_START : string := "/mycore";Then make sure your user docs and release SD-card layout use the same directory.
If the folder is not found, the browser falls back to /.
Edit:
constant SAVE_SETTINGS : boolean := false;The Shell will always use OPTM_G_STDSEL defaults at startup and will not try to
open CFG_FILE. You can leave CFG_FILE defined; it is simply ignored.
Edit:
constant SAVE_SETTINGS : boolean := true;
constant CFG_FILE : string := "/mycore/mycore-" & CORE_VERSION;Then ship a file at that path whose size is exactly OPTM_SIZE bytes. During
development:
cd M2M/tools
./make_config.sh mycore-WIP-A1 autoPut the resulting file in the SD-card directory named by CFG_FILE.
If you add, delete, or reorder OSM lines:
- Update
OPTM_SIZE. - Re-check
OPTM_ITEMSandOPTM_GROUPS. - Re-check any core-side flat line indices (
C_MENU_*in the C64 reference). - Run the menu checker if your core has one.
- Bump
CORE_VERSIONif you use versioned config filenames. - Regenerate and ship the settings file.
The dangerous case is not only "wrong file size". A same-size file from an older layout can silently apply old bits to new menu lines. Versioned filenames are the safest defense.
Use the C64 reference defaults:
constant KEYBOARD_AT_OSD : boolean := false;
constant JOY_1_AT_OSD : boolean := false;
constant JOY_2_AT_OSD : boolean := false;The Shell still reads the keyboard. The core does not.
Set:
constant OPTM_PAUSE : boolean := true;Then confirm your core actually honors its pause input. The framework transports the pause request; the core decides what "paused" means.
Set:
constant ASCAL_USAGE : natural := 0;
constant ASCAL_MODE : natural := 0;Then no menu or custom QNICE code is needed for Ascal mode selection.
Set:
constant ASCAL_USAGE : natural := 2;Then drive these framework ports from your core:
qnice_ascal_mode_o <= "...";
qnice_ascal_polyphase_o <= '0' or '1';
qnice_ascal_triplebuf_o <= '0' or '1';Do not also rely on firmware writes to M2M$ASCAL_MODE; auto mode makes the
register follow the VHDL input.
Start from the C64 defaults unless you have measured a problem:
constant VD_ANTI_THRASHING_DELAY : natural := 2000;
constant VD_ITERATION_SIZE : natural := 100;Think of a save as three phases:
core write -> RAM image is dirty -> quiet-time delay -> SD-card flush iterations
VD_ANTI_THRASHING_DELAY controls the middle part. It is measured in
milliseconds and starts after an acknowledged core write. Every later write
restarts the countdown. Lower it if SD-card write-back remains pending too long
after the final write. Keep it at 2000, or raise it, if the emulated drive does
several separated bursts for one save operation.
VD_ITERATION_SIZE controls the last part. Once flushing has started, the Shell
rewrites the mounted image file from the RAM buffer to SD in chunks of this many
bytes per FLUSH_CACHE continuation. The firmware writes one byte at a time and
returns to the main loop after each chunk.
Raise VD_ITERATION_SIZE only if flushing itself takes too long after it has
started. Larger chunks clear the dirty state in fewer passes, but each pass keeps
QNICE inside the FAT32 write loop longer. Test while the core is still running,
while the Options menu is open, and while using the file browser during an active
write-back.
Do not tune only by watching whether the first save succeeds. Also test a second
write while <Saving> is shown. A new write is supposed to cancel the current
flush attempt, mark the RAM image dirty again, and wait for another quiet period
before restarting the SD-card write-back.
SAVE_SETTINGS = true does not create a file. The file must already exist.
Use M2M/tools/make_config.sh or your release script.
CFG_FILE must match the shipped filename exactly. If CFG_FILE says
/c64/c64mega65-WIP-V6-A17, putting c64mega65-V6 on the SD card will not
enable persistence.
OPTM_SIZE is both a menu size and a file format size. Changing the menu
layout means changing the settings-file format.
The settings file stores flat line indices, not group ids. Byte 40 means "line 40", not "group id 40".
Mount and load lines are runtime actions, not saved preferences. The Shell
writes 0 for those bytes when saving.
The Shell disables settings persistence after an SD-card switch. This is intentional data-corruption protection.
RESET_COUNTER is not a millisecond value. It is a simple QNICE loop count.
Use it empirically.
KEYBOARD_AT_OSD = false does not disable menu input. It only disconnects
keyboard events from the core. The Shell still sees them.
ASCAL_USAGE decides ownership. If it is 1, custom firmware may write
M2M$ASCAL_MODE. If it is 2, the VHDL input wins and firmware writes do not
control the scaler.
CORENAME is not user documentation. It is printed to the debug console.
Use WHS and OSM text for users.
Version and paths:
| Constant | Typical value | Purpose |
|---|---|---|
CORE_VERSION |
"WIP-V6-A17" |
single version string to reuse |
DIR_START |
"/c64" |
first file-browser directory |
CFG_FILE |
"/c64/c64mega65-" & CORE_VERSION |
SD settings file |
CORENAME |
"Commodore 64 for MEGA65 Version " & CORE_VERSION |
serial-terminal banner |
General settings:
| Constant | C64 default | Purpose |
|---|---|---|
RESET_COUNTER |
100 |
extra startup reset-hold loop count |
OPTM_PAUSE |
false |
pause core while the Options menu session is open |
WELCOME_ACTIVE |
false |
show WHS element 0 at startup |
WELCOME_AT_RESET |
false |
repeat welcome after reset |
KEYBOARD_AT_RESET |
false |
request keyboard enabled during startup reset |
JOY_1_AT_RESET |
false |
request joystick 1 enabled during startup reset |
JOY_2_AT_RESET |
false |
request joystick 2 enabled during startup reset |
KEYBOARD_AT_OSD |
false |
allow keyboard through to core while OSD is open |
JOY_1_AT_OSD |
false |
allow joystick 1 through while OSD is open |
JOY_2_AT_OSD |
false |
allow joystick 2 through while OSD is open |
ASCAL_USAGE |
1 |
owner of HDMI scaler mode |
ASCAL_MODE |
0 |
fixed scaler mode, only used when ASCAL_USAGE = 0
|
VD_ANTI_THRASHING_DELAY |
2000 |
ms before virtual-drive cache flush starts |
VD_ITERATION_SIZE |
100 |
bytes per background flush iteration |
SAVE_SETTINGS |
true |
enable SD-card OSM persistence if CFG_FILE is valid |
Ascal ownership:
ASCAL_USAGE |
Owner |
|---|---|
0 |
ASCAL_MODE in config.vhd
|
1 |
custom QNICE assembly writes M2M$ASCAL_MODE
|
2 |
core VHDL drives qnice_ascal_*_o ports |
Persistence rules:
-
SAVE_SETTINGSmust betrue. -
CFG_FILEmust exist on SD. - File size must be exactly
OPTM_SIZEbytes. - First byte
0xFFmeans "use defaults, save real settings later". - Bytes must be
0or1once real settings are saved. - Regenerate the file whenever the persistent OSM layout changes, including same-size reorders or semantic changes.
Source-reading path: config.vhd constants become selector windows; the
Shell reads them via M2M$CONFIG; the Shell writes CSR, CFM, OSM, and Ascal
registers; framework VHDL transports those live registers into reset, pause,
input, overlay, scaler, and core-control signals.