-
Notifications
You must be signed in to change notification settings - Fork 14
Audio Pipeline
Every core you port was, at heart, a small musical instrument. A Commodore 64 sings through the SID — a three-voice analog synthesizer with a resonant filter that a generation of demo coders bent into basslines and lead solos. An Amiga plays four streams of digitized samples at once, the sound of a thousand tracker modules. A Game Boy chirps square waves out of a chip barely more complicated than a doorbell. Three machines, three utterly different ways of making noise — and your job, and the framework's, is to catch whatever each one produces and carry it, faithfully and at the same time, to two very different destinations: a 3.5 mm analog jack on the back of a MEGA65, and the HDMI cable, which — surprisingly — carries sound as well as picture.
There is a twist that makes this more than plumbing. The original machines sounded the way they did partly because their hardware was imperfect: a cheap converter and a simple resistor-and-capacitor filter softened every note before it reached the speaker, and that softness is baked into how we remember these machines sounding. A perfect digital re-creation, played through a clean modern converter, can actually sound too sharp — the warmth is missing. So the framework does not only carry the sound; it can optionally re-impose that lost analog character, the audio twin of the "CRT emulation" look on the video side.
This is the cover-to-cover guide to how all of that happens. It starts from the very
idea of digital sound — no prior knowledge of sampling, DACs, or filters assumed — walks
through what a MiSTer core actually hands you, and then follows one stereo stream all the
way to both outputs, stage by stage, naming every module it passes through. It uses the
three reference cores as running examples: the Commodore 64 (C64MEGA65), the
Amiga 500 (AExp), and the Game Boy Color (gbc4mega65). Between them they
exercise almost every feature the pipeline has. By the end you should be able to read —
and reproduce — every audio choice those three cores make.
It is a concepts article. It teaches you why the pipeline is shaped the way it is and
what each piece does, so that the exact porting steps make sense. Those steps — the
lines you edit in globals.vhd, mega65.vhd, config.vhd, and main.vhd — live in
The Ultimate MiSTer2MEGA65 Porting Guide, and this page points you there. Audio and
video share the HDMI wire and the clock-crossing machinery, so where they meet this page
leans on its sister articles, Video Pipeline and
Clocking and Clock Domain Crossing, rather than re-teaching them.
The MiSTer2MEGA65 audio pipeline takes one stream of signed 16-bit stereo samples from your core, optionally re-shapes it with a filter that recreates the gentle analog character of the original machine, and delivers it — at the same time — to a 3.5 mm analog jack (driven by a real audio DAC on newer boards, or synthesized one bit at a time on the R3) and to the HDMI connector (resampled to a standards-clean 48 kHz and woven into the video signal), and almost everything you do as an audio porter is choosing that filter and handing your samples over in the right format.
Before we can talk about a pipeline we have to agree on what "digital audio" even is. If you have never had to think about it, this part builds the vocabulary from zero, in the order the rest of the article needs it. If sampling and DACs are already familiar, skim to §1.5 — the retro-specific quirks there are where the framework earns its keep.
A loudspeaker makes sound by pushing air: a coil moves a paper cone back and forth, and the moving cone sends pressure waves down the room to your ear. What tells the cone how to move is a single electrical quantity — a voltage that rises and falls over time. Push the voltage up, the cone moves out; drop it, the cone pulls back. So at the very bottom, "an audio signal" is just a voltage that varies over time — a continuous, smoothly wiggling waveform. Everything a retro sound chip does, however cleverly, ends in producing such a wiggle.
The catch is that an FPGA cannot store or transmit a smooth wiggle. It deals in numbers. So the first thing that has to happen is to turn the wiggle into numbers — and, at the very end, turn numbers back into a wiggle.
The trick is to stop trying to capture the whole continuous curve and instead measure the voltage at regular instants — thousands of times a second — and write down each measurement as a number. Each measurement is a sample; the steady stream of samples is called PCM, for pulse-code modulation, which is a fancy name for "the waveform written down as a list of numbers." Nearly all digital audio, from a CD to the HDMI cable on your desk, is PCM under the hood.
Two numbers describe any PCM stream. The first is the sample rate: how many measurements you take per second, quoted in hertz (Hz) or kilohertz (kHz). A music CD uses 44,100 samples per second (44.1 kHz); the MiSTer world, the MEGA65, and HDMI all settle on 48,000 (48 kHz), and that number will follow us through the whole article.
Why 48,000 and not, say, 5,000? Because of a hard rule called the Nyquist limit: to
faithfully capture a sound whose highest frequency is F, you must sample at a rate
greater than 2 × F. Human hearing tops out around 20 kHz, so a sample rate a bit above
40 kHz captures everything we can hear — which is exactly why the common rates cluster
just above 40 kHz. (If sampling is new to you, this
gentle primer on PCM and sampling
is a ten-minute foundation.)
A sample is an integer, and like any integer in hardware it has a fixed width in bits —
its bit depth. M2M uses 16 bits per sample, the same as a CD. Sixteen bits give
2^16 = 65,536 distinct levels to place each measurement on; more bits would resolve
quieter details, fewer bits would add audible "gravel" (quantization noise). Sixteen is
the sweet spot the whole MiSTer ecosystem uses.
Now the subtle part, and the single most important fact about the format M2M expects. A
sound wave swings symmetrically around a resting point — the cone sits still at rest,
moves out, comes back through the middle, moves in, and so on. The natural way to write
that down is with signed numbers: zero means silence, positive numbers mean the
wave is above rest, negative numbers mean below. A 16-bit signed sample runs from -32768
(as far "down" as it goes) through 0 (silence) to +32767 (as far "up"). The negative
values are stored in two's complement, the standard hardware convention for signed
integers — you rarely have to think about the encoding, only that 0 is the middle.
Contrast this with an unsigned representation, where the numbers run 0 to 65535
and silence sits at the midpoint, 32768 (hex 0x8000). Both encodings carry the same
wave; they just disagree about where "silence" lives. Converting a chip that speaks
unsigned into the signed form M2M wants is a real porting task, and getting it wrong is
audible — hold that thought for the Game Boy in Part 2.
16-bit SIGNED (what M2M wants) 16-bit UNSIGNED (e.g. the Game Boy APU)
+32767 ..... loudest "up" 65535 ..... loudest "up"
0 ..... SILENCE 32768 ..... silence (the midpoint)
-32768 ..... loudest "down" 0 ..... loudest "down"
Finally, sound is stereo: two independent PCM streams, one for the left speaker and one for the right. So, reduced to its essence, "the audio signal" inside M2M is exactly this: two signed 16-bit numbers — left and right — produced fresh every sample period.
At the far end, to actually hear anything, the numbers must become a voltage again. The device that does this is a DAC — a digital-to-analog converter. Give it a stream of samples and it produces a stream of voltages.
But a DAC cannot magically draw a smooth curve through the samples. The simplest and most common thing it does is hold each sample's voltage steady until the next sample arrives — producing not a smooth wave but a staircase, a series of flat steps. That staircase is almost the original wave, but the sharp corners of the steps contain high-frequency energy that was never in the sound — spurious tones (called images or aliases) sitting above the audible band.
The cure is a reconstruction filter: a low-pass filter — a circuit (or computation) that lets low frequencies through and blocks high ones — placed after the DAC to round the staircase back into a smooth curve. Every DAC needs one. In cheap 1980s hardware that filter was often just a single resistor and capacitor (an "RC filter") soldered across the audio output — the crudest possible low-pass.
Here is where retro audio stops being generic. That crude RC filter on the original machine did not only remove the DAC staircase; it also gently rolled off the top of the audible band, softening the sound. And because every unit shipped with the same cheap filter, that softening became part of the machine's voice — the rounded warmth of a real SID, the particular grain of an Amiga sample. It is not a defect people want removed; it is how the instrument is remembered.
A modern FPGA core computes its samples mathematically, without that analog roll-off, and feeds them to a clean modern DAC. The result can sound noticeably brighter and harsher than the real machine — technically more accurate, emotionally wrong. So M2M offers an optional digital filter that re-imposes the original's gentle roll-off before the sound goes out, trading mathematical purity for the character people actually remember. This is the exact audio counterpart of the video pipeline's "CRT emulation": deliberately re-adding a beloved imperfection. We build that filter in Part 4.
Historically, sound left a home computer as an analog line: a voltage on a wire to an amplifier or a pair of headphones, through an RCA or headphone jack. The MEGA65 keeps that tradition with a 3.5 mm analog audio jack.
But the MEGA65 also has HDMI, and here is a fact that surprises many people: an HDMI cable carries audio as well as video. It does so by tucking little packets of audio samples into the brief gaps between video lines — the same blanking intervals the Video Pipeline article describes. So the identical sound must be delivered two ways at once: as a real analog voltage on the jack, and as digital sample-packets riding inside the HDMI signal. That fork — one stream, two outputs — is the spine of the whole pipeline, and we meet it properly in Part 3.
To make all of this concrete, here is the native sound each reference core generates, before the framework touches it — the three specimens dissected throughout the article.
-
Commodore 64 (
C64MEGA65). The MOS SID chip (6581 or 8580) is a genuine three-voice analog-style synthesizer with a resonant filter — the reason C64 music is so distinctive. In the core it is emulated to an 18-bit signed value per channel, and because the MEGA65 can host two SIDs, the output is genuinely stereo. -
Amiga 500 (
AExp). The Paula chip plays four independent channels of 8-bit PCM samples — each streamed straight from memory — and mixes them in hardware into a 15-bit signed left/right pair (channels 0 and 3 feed the left, 1 and 2 the right). This is the chip that made the Amiga the home of tracker music. -
Game Boy Color (
gbc4mega65). The APU (audio processing unit) has four simple voices — two pulse-wave channels, one programmable-wave channel, one noise channel — mixed to a 16-bit unsigned value that, awkwardly, is not centered on0x8000: its resting level drifts depending on how many voices are active. That quirk turns its format conversion into the trickiest of the three.
Three chips; three sample formats — 18-bit signed, 15-bit signed, and 16-bit unsigned-and-uncentered. Part 2 is the story of how each of those becomes M2M's one canonical format: signed 16-bit, centered on zero.
M2M exists to host a MiSTer core on MEGA65 hardware, so the practical starting point
is not "digital audio" in the abstract but "the specific signals a MiSTer core produces,"
and what you must do to them in CORE/vhdl/main.vhd before the framework will accept them.
If you have never seen a MiSTer core, the MiSTer Quick Reference Guide covers the
MiSTer side in full; this part covers only the audio handoff.
A MiSTer core emits its sound on a tiny, conventional set of wires:
-
AUDIO_L,AUDIO_R— the two PCM channels, each a signed 16-bit value. -
AUDIO_S— a one-bit flag:1if the samples are signed,0if unsigned. -
AUDIO_MIX— a two-bit hint asking the MiSTer framework to blend some left into right and vice-versa (mono-ing the sound to taste).
On real MiSTer hardware, the ARM-plus-Linux "framework" reads these, runs them through a filter and a mixer, and drives the board's audio DAC. M2M has no ARM and no Linux — it reimplements that whole chain in pure FPGA — so the first thing it needs from your core is those samples, in its own conventions.
The contract between your core and the framework is a pair of VHDL ports, both outputs of
your core, declared in CORE/vhdl/mega65.vhd:125-127:
-- Audio output (Signed PCM)
main_audio_left_o : out signed(15 downto 0);
main_audio_right_o : out signed(15 downto 0);Two facts are load-bearing. First, these live in your core's main clock domain — the
main_clk your core generates (about 54 MHz in the template). The framework will move them
into its own audio clock domain later (Part 3), so you do not have to; you just present the
samples on your own clock. Second, and this is the rule the whole rest of the pipeline
assumes: the samples must be signed (the framework hardwires MiSTer's AUDIO_S flag to
"signed" — we see exactly where in Part 4), and they must be 16-bit. Your sound chip
almost certainly is neither exactly 16 bits nor already centered on zero, so your one real
audio job in main.vhd is to convert your chip's native samples into signed 16-bit,
centered on zero.
That is the entire porter-side audio-in task. It is small — usually a line or two — but it is exactly where the three reference cores differ, and getting it wrong is audible. So the conversion is the worked example.
Commodore 64 — 18 bits down to 16, with an overflow guard. The dual-SID core produces
18-bit signed samples; it keeps the top 16 of those 18 bits (dropping the two least
significant, an effective divide-by-four into 16-bit headroom) and then saturates if the
result would overflow. From C64MEGA65/CORE/vhdl/main.vhd:1672-1705:
-- MiSTer audio signal processing: Convert the core's 18-bit signal to a signed 16-bit signal
audio_processing_proc : process (all)
variable alm, arm : std_logic_vector(16 downto 0);
begin
alm(16) := c64_sid_l(17);
alm(15 downto 0) := c64_sid_l(17 downto 2);
...
if alm(16) /= alm(15) then -- sign disagrees with top magnitude bit = overflow
alo(15) <= alm(16); -- clamp to the rail
alo(14 downto 0) <= (others => alm(15));
else
alo <= alm(15 downto 0);
end if;
...
audio_left_o <= signed(alo);The saturating clamp is not strictly needed today — a single SID never overflows the downshift — but the C64 authors left it as a deliberate scaffold: the comment notes that MiSTer also lets you add OPL, a DAC channel, and even tape-drive noise into this same sum, and those could overflow, so the guard is ready in advance.
Amiga — 15 bits up to 16, by a single shift. Paula has already mixed its four channels
into a 15-bit signed pair, so AExp only has to widen it to 16 bits.
From AExp/CORE/vhdl/main.vhd:861-866:
-- Audio: Paula 15-bit signed -> 16-bit signed PCM (as MiSTer: {data, 1'b0})
audio_left_o <= signed(aud_ldata & '0');
audio_right_o <= signed(aud_rdata & '0');Appending a '0' as a new least-significant bit is a one-bit left shift — a multiply by
two — which fills the extra bit of range while preserving the sign. It matches MiSTer's own
{data, 1'b0} idiom exactly. Clean, because Paula did the hard part (the four-channel mix)
in silicon.
Game Boy — the cautionary tale. The APU speaks unsigned audio that is not centered
on 0x8000, and this is where a careless conversion bites. From
gbc4mega65/CORE/vhdl/main.vhd:406-413:
-- Convert the Game Boy's unsigned audio to M2M's signed PCM format.
-- gbc_snd's output is unsigned and NOT centered around 0x8000 (the center drifts with
-- the number of active voices), so we use a logical shift right by one - silence stays
-- at level 0 and the maximum stays in the positive range. Do not use an arithmetic
-- shift or a plain "minus 0x8000" conversion here: both lead to loud crackling
-- (verified in the original gbc4mega65 with Dig Dug and Super Mario Land 1).
audio_left_o <= signed("0" & main_audio_l(15 downto 1));
audio_right_o <= signed("0" & main_audio_r(15 downto 1));The fix is a logical shift right by one with a forced 0 sign bit ("0" & x(15 downto 1)), which folds the APU's uncentered unsigned range into the non-negative half of
signed 16-bit — silence stays at zero, the peak stays positive. The comment is emphatic
about what not to do: an arithmetic shift, or the textbook "subtract 0x8000" trick,
both produce loud crackling, verified in real games (Dig Dug, Super Mario Land 1). It
is the perfect object lesson: "hand M2M signed 16-bit" is a contract, and honoring it —
matching your chip's sign convention and center point — is the whole of the porter's
audio-in work.
The demo core has no sound chip to convert, so it fabricates a tone: a small DSP
multiplier (M2M/vhdl/democore/democore_audio.vhd) generates an oscillator whose pitch
tracks the Breakout ball's height and whose left/right balance tracks its horizontal
position, then truncates the product into signed(15 downto 0). It exists purely so the
framework makes sound when it runs stand-alone — a hello-world beep you delete the moment a
real core arrives. Everything downstream of these two ports, however, is identical whether
the samples came from a demo oscillator or a real SID — which is exactly what the rest of
this article is about.
Your core produces one stereo stream. The MEGA65 has two audio outputs — the jack and
HDMI. The framework's job is to carry that one stream to both, and it does so inside the
same VHDL entity that handles video, av_pipeline.vhd. This part is the map: what happens
where, on which clock, and — just as important — which files you edit to steer it.
Here is the whole audio pipeline on one page. Read it top to bottom.
CORE — your MiSTer core, in CORE/vhdl/main.vhd
main_audio_left_o / main_audio_right_o : signed(15 downto 0), on YOUR main_clk
│
▼
cdc_stable ── cross into the framework's 12.288 MHz audio clock domain
│
▼
audio_out ─── the MiSTer "audio-improvement" IIR filter (Part 4)
│
▼
select/mute ─ pick raw / filtered / silence, from the on-screen menu (Part 4)
│
┌────────────┴─────────────────────┐
▼ ▼
ANALOG JACK (3.5 mm) HDMI (audio rides inside the video)
│ │
your BOARD picks the DAC: resample to 48 kHz, add ACR (N/CTS),
• R4/R5/R6: AK4432VT I2S DAC pack into HDMI data islands, and feed
• R3: 1-bit PDM into an RC the SAME TMDS serializers as the picture
filter on the jack (see the Video Pipeline article, §5.4)
▼ ▼
headphones / amplifier the TV or monitor speakers
One structural fact is worth pausing on, because it is the biggest difference from the video pipeline. The video pipeline forks first and then reprocesses each branch heavily — the analog path scandoubles, the digital path rescales, and they share almost nothing. The audio pipeline does the opposite: it processes once, then forks. The single filter-and-mute stage runs upstream of the split, so the jack and HDMI receive the identical processed samples; the only thing that differs afterward is the physical packaging (an analog DAC on one side, HDMI data-islands on the other). That is why this article is shorter than its video sibling — there is one processing stage to understand, not two divergent ones.
Everything from the core boundary onward runs in a single audio clock domain clocked
at 12.288 MHz. That oddly specific number is not arbitrary; it is
256 × 48,000. Here is why the 256 is there.
An audio DAC chip needs more than the samples — it needs a fast, steady master clock
(traditionally written MCLK) from which it derives its own internal timing, and the
long-standing convention is that MCLK is a power-of-two multiple of the sample rate, most
commonly 256 × Fs (256 times the sample frequency). With Fs = 48 kHz, that is
256 × 48000 = 12.288 MHz. Rather than run the audio logic at some convenient rate and
then generate a separate MCLK for the DAC, the framework simply runs its entire audio
domain at the DAC's master-clock rate. So the whole subsystem's heartbeat is
12.288 MHz, and every downstream rate — the 48 kHz sample tick, the I2S bit clock, the
HDMI resampler — is a clean division of it.
The clock comes from a dedicated mixed-mode clock manager in M2M/vhdl/clk_m2m.vhd:104-125:
i_clk_audio : MMCME2_BASE
generic map (
CLKFBOUT_MULT_F => 48.000, -- 960 MHz
CLKIN1_PERIOD => 10.0, -- INPUT @ 100 MHz
CLKOUT0_DIVIDE_F => 78.125, -- AUDIO @ 12.288 MHz
DIVCLK_DIVIDE => 5,
...The arithmetic: the 100 MHz board clock is multiplied to a 100 × 48 / 5 = 960 MHz
internal frequency, then divided by 78.125 to land on exactly
960 / 78.125 = 12.288 MHz.
A gotcha for anyone reading the source. Two comments in the framework still call this clock "30 MHz" (
clk_m2m.vhd:8) and "60 MHz" (framework.vhd:265). Both are stale and wrong; the real rate is 12.288 MHz, confirmed by the MMCM generics above, by the port comment atclk_m2m.vhd:36, and by the genericG_AUDIO_CLOCK_RATE => 12_288_000passed into the pipeline atframework.vhd:868. Trust the generics, not the two stray comments.
The clock-generation details themselves — how an MMCME2 works, what "locking" means —
belong to Clocking and Clock Domain Crossing; here it is enough to know the audio
domain ticks at 256 × 48 kHz for the DAC's sake.
Your core emits its samples on its own main clock; the audio domain runs on the
framework's 12.288 MHz clock. These are two unrelated clocks, so the left and right
samples must cross a clock-domain boundary — and crossing a multi-bit value between
unrelated clocks naïvely will, sooner or later, latch a value with some bits from the old
sample and some from the new (a "torn" read). The framework prevents that with a small
crossing block, cdc_stable, in framework.vhd:761-774:
-- Clock domain crossing: CORE to AUDIO
i_main2audio: entity work.cdc_stable
generic map (
G_REGISTER_SRC => true,
G_DATA_SIZE => 32
)
port map (
src_clk_i => main_clk_i,
src_data_i(15 downto 0) => std_logic_vector(main_audio_l_i),
src_data_i(31 downto 16) => std_logic_vector(main_audio_r_i),
dst_clk_i => audio_clk,
dst_data_o(15 downto 0) => audio_left,
dst_data_o(31 downto 16) => audio_right
); -- i_main2audioIt packs both 16-bit channels into one 32-bit word and, as its name says, only forwards a new value once it has sampled the same value on two consecutive audio clocks — i.e. once the crossing has settled — so a half-updated word is never passed on. Why is such a simple, cheap crossing safe here, when the video and HyperRAM paths need full asynchronous FIFOs? Because audio changes slowly. A fresh sample appears only 48,000 times a second, but the audio clock ticks 12.288 million times a second — over 250 clocks per sample — so the value sits still far longer than it takes the crossing to settle. A stability-gated crossing is exactly the right tool for slowly-varying data, and it costs a few flip-flops rather than a FIFO. (The full menu of crossing techniques, and when each applies, is the subject of Clocking and Clock Domain Crossing.)
Three questions a porter eventually asks — does audio survive a core reset? does it keep
playing while the menu is open? why is there a soft pause at power-on? — all turn on one
signal, the audio reset. It is generated right beside the audio clock, in clk_m2m.vhd,
and it asserts in exactly two situations: at power-on until the audio clock manager locks, and
on a full M2M reset (the long reset-button press). It deliberately does not assert on
an ordinary core reset. The happy consequence: your sound keeps playing when the user
resets the core — a game reset mid-tune does not click or drop out.
That reset has two visible effects, each landing on a stage we meet later:
- No power-on pop. On the R4-R6 boards the audio DAC is physically held in power-down while the reset is asserted (Part 5), so the speakers stay silent until everything is stable rather than thumping as the logic settles.
- A brief start-up mute on the filtered path. The improvement filter (Part 4) mutes itself for roughly the first 8000 samples — about 170 ms — after a reset, while its internal DC-blocker settles, then fades in. When the filter is on you may notice the very first note arrive a heartbeat late; that is this settling window, not a fault.
The framework carries this reset (and the audio clock itself) out to the board alongside the
samples: av_pipeline exports audio_clk_o and audio_reset_o next to
audio_left_o/audio_right_o (av_pipeline.vhd:694-697), and the board top feeds them to the
DAC as its master clock and its power-down control.
One last "is there sound?" question is settled by the menu, not the reset. A config switch,
OPTM_PAUSE (config.vhd), decides whether opening the on-screen menu pauses the core. It
defaults to false, so by default the game keeps running — and keeps playing — underneath the
menu. Set it true (some cores prefer a clean freeze) and opening the menu halts the core, so
the audio stops too until you close the menu. It is a one-line choice with an audible
consequence, which is why it earns a mention here.
Almost none of the pipeline is something you build; it is something you configure, and — exactly as on the video side — four files carry all of your audio decisions. Knowing which file owns which kind of decision saves real time.
| File | What it owns for audio |
|---|---|
CORE/vhdl/globals.vhd |
The fixed facts about your machine's sound: the improvement-filter coefficients (audio_flt_rate, audio_cx, audio_cx0..2, audio_cy0..2, audio_att, audio_mix). |
CORE/vhdl/mega65.vhd |
The live policy: the qnice_audio_filter_o and qnice_audio_mute_o outputs, driven from menu bits. |
CORE/vhdl/config.vhd |
The menu the user sees: the "Audio improvements" item (and, if you add one, a "Mute" item). |
CORE/vhdl/main.vhd |
The samples: the signed-16 conversion from Part 2. |
globals and config and mega65 decide how the pipeline treats the sound; main.vhd
decides what the sound is. The rest of the article walks these in order: the filter and
its controls (Part 4), then the two physical outputs (Parts 5 and 6).
This is the one genuine processing stage in the whole audio pipeline, and it is the piece a porter most needs to understand, because its behavior is data you supply. Its purpose, from §1.5: the original machine's crude analog output filter softened its sound, a mathematically exact core omits that softening, and this stage optionally puts it back.
A digital filter computes each output sample as a weighted combination of recent input and output samples. The kind MiSTer uses is an IIR filter — infinite impulse response, so named because it feeds its own past outputs back into the calculation, so a single input sample can keep influencing the output long after it has passed. That feedback is the family's defining trick: it is what lets a small, cheap filter have a long, smooth effect. MiSTer's filter remembers the last three inputs and the last three outputs — it is a third-order IIR — and combines them like this (using the coefficient names the code uses):
out[n] = cx · ( in[n] + cx0·in[n-1] + cx1·in[n-2] + cx2·in[n-3] ) ← 4 feedforward (INPUT) taps
− ( cy0·out[n-1] + cy1·out[n-2] + cy2·out[n-3] ) ← 3 feedback (OUTPUT) taps
Do not worry about the exact algebra — the shape is the point. The feedforward side
weights the current input and its three predecessors; the feedback side weights the three
previous outputs; and cx is one overall input gain — seven tunable numbers in all.
Together they define the filter's frequency response — here a gentle low-pass that rolls
off the harsh top end, mimicking the original RC filter. The crucial idea for a porter is
this: the coefficients are the filter's personality. Change the numbers and you change
the sound; and since every machine's analog output filter was different, every machine gets
its own set.
M2M does not write its own filter — it imports MiSTer's, verbatim, so that the sound
matches the MiSTer core you are porting. The Verilog module is audio_out.v
(M2M/vhdl/av_pipeline/audio_out.v); it wraps the actual IIR filter (IIR_filter, in
M2M/vhdl/controllers/MiSTer/iir_filter.v), a DC-blocker, and a stereo mixer. The
framework instantiates it in av_pipeline.vhd:318-349:
i_audio_out : audio_out
generic map ( CLK_RATE => G_AUDIO_CLOCK_RATE ) -- 12.288 MHz
port map (
reset => audio_rst_i, -- audio-domain reset (see section 3.4)
clk => audio_clk_i,
sample_rate => '0', -- 0 = 48 kHz (M2M never uses 96 kHz)
flt_rate => audio_flt_rate, -- filter processing-rate coefficient (from MiSTer)
cx => audio_cx, -- overall input gain
cx0 => audio_cx0, -- ┐
cx1 => audio_cx1, -- │ feedforward taps (weight recent INPUTS)
cx2 => audio_cx2, -- ┘
cy0 => audio_cy0, -- ┐
cy1 => audio_cy1, -- │ feedback taps (weight recent OUTPUTS)
cy2 => audio_cy2, -- ┘
att => audio_att, -- attenuation / volume (0 = untouched)
mix => audio_mix, -- stereo/mono crossfeed (0 = full stereo)
is_signed => '1', -- M2M ALWAYS feeds signed samples
core_l => audio_left_i,
core_r => audio_right_i,
alsa_l => (others => '0'),-- no second audio source (see below)
alsa_r => (others => '0'),
al => audio_filt_left,
ar => audio_filt_right
);Three of these inputs repay a closer look, because each teaches something about M2M:
-
is_signed => '1', always. Insideaudio_out.vthere is a one-line trick that flips a sample's sign bit whenis_signedis0, converting unsigned to signed on the way in. M2M never uses it — it hardwires'1'and relies on your core to have already produced signed samples (Part 2). This is precisely why the contract in §2.2 demands signed, and precisely why the Game Boy had to convert its unsigned APU output by hand. -
alsa_l/alsa_r => 0. On real MiSTer these carry a second audio source — the ARM/Linux side's "ALSA" sound (menu blips, CD audio, and so on) — to be mixed with the core's. M2M has no ARM and no Linux, so there is no second source; the ports are tied to zero and the built-in mixer simply passes the core's audio through. It is a small, telling reminder that M2M is MiSTer with the Linux computer removed. -
flt_rate(here 7,056,000). This is not a clock — it is another number you copy verbatim from MiSTer, setting the filter's internal processing cadence; you never compute it. Like the seven weights above, it lives in yourglobals.vhd.
Two more stages sit inside audio_out.v, after the filter itself. A DC-blocker strips any
constant offset — any slow drift away from the zero that means silence — from the stream; it is
a second guard on the very centering Part 2 laboured over, but note it runs only in the
filtered path, so it cannot rescue a mis-centered core when the filter is switched off. After
it, a small mixer applies the att and mix controls described next.
The filter's personality lives in CORE/vhdl/globals.vhd, as a block of constants. The
template ships the C64's values as a placeholder (globals.vhd:187-203):
-- Audio filters
-- If you use audio filters, then you need to copy the correct values from the MiSTer core
-- that you are porting: sys/sys_top.v
constant audio_flt_rate : std_logic_vector(31 downto 0) := std_logic_vector(to_signed(7056000, 32));
constant audio_cx : std_logic_vector(39 downto 0) := std_logic_vector(to_signed(4258969, 40));
constant audio_cx0 : std_logic_vector( 7 downto 0) := std_logic_vector(to_signed(3, 8));
constant audio_cx1 : std_logic_vector( 7 downto 0) := std_logic_vector(to_signed(2, 8));
constant audio_cx2 : std_logic_vector( 7 downto 0) := std_logic_vector(to_signed(1, 8));
constant audio_cy0 : std_logic_vector(23 downto 0) := std_logic_vector(to_signed(-6216759, 24));
constant audio_cy1 : std_logic_vector(23 downto 0) := std_logic_vector(to_signed( 6143386, 24));
constant audio_cy2 : std_logic_vector(23 downto 0) := std_logic_vector(to_signed(-2023767, 24));
constant audio_att : std_logic_vector( 4 downto 0) := "00000"; -- no attenuation
constant audio_mix : std_logic_vector( 1 downto 0) := "00"; -- full stereoEvery MiSTer core defines a tuned filter in its sys/sys_top.v; the porter's job is simply
to copy those numbers into this block. Two of the constants deserve a note:
-
audio_attis a 5-bit attenuator — its top bit forces silence and its low four bits shift the volume down — andaudio_mixblends the channels toward mono (00= full stereo;01,10,11= 25%, 50%, 100% blended). All three reference cores leave both at their neutral values; they exist for cores that need a quieter or narrower mix out of the box. -
audio_cx1is a well-known trap. Recall the four feedforward weights from §4.1:1, cx0, cx1, cx2. MiSTer intends these to be the binomial1, 3, 3, 1— that is,cx0 = 3,cx1 = 3,cx2 = 1. But the M2M template — and, copying from it, the C64 and Game Boy cores — carrycx1 = 2, a long-standing typo; only the Amiga core uses the correct3. The lesson is the one the file's own comment states: copy the numbers from your core'ssys/sys_top.v, not from the template, and you get the right value regardless.
The filter always runs, but whether the listener hears its output — or the raw samples,
or silence — is decided by two one-bit controls that come from the on-screen menu. Those
controls originate as core outputs in mega65.vhd:39-40:
qnice_audio_mute_o : out std_logic;
qnice_audio_filter_o : out std_logic;They are set from menu bits — in the template, mega65.vhd:413-414:
qnice_audio_mute_o <= '0'; -- never muted
qnice_audio_filter_o <= qnice_osm_control_i(C_MENU_IMPROVE_AUDIO); -- 0 = raw, 1 = filteredwhere C_MENU_IMPROVE_AUDIO is the zero-based line number of the " Audio improvements\n"
item in the menu (config.vhd:378). These bits are produced in the QNICE clock domain (the
menu runs on the QNICE co-processor), so they too are crossed into the audio domain — by a
small xpm_cdc_array_single in av_pipeline.vhd:303-315 — before they take effect. The
crossed bits then drive a plain selector, av_pipeline.vhd:351-365:
select_or_mute_audio : process(all)
begin
if audio_mute = '1' then
audio_left <= (others => '0'); -- silence
audio_right <= (others => '0');
else
if audio_filter = '0' then
audio_left <= audio_left_i; -- raw core audio (filter bypassed)
audio_right <= audio_right_i;
else
audio_left <= audio_filt_left; -- the filtered output
audio_right <= audio_filt_right;
end if;
end if;
end process select_or_mute_audio;So the improvement filter switches in and out glitch-free and with no core reset —
flipping the menu item just re-points this multiplexer. Note also that M2M does its own
muting here (forcing zeros), independent of the filter's internal att mute; the mute path
is fully supported by the framework even though the template never exercises it (it wires
qnice_audio_mute_o to '0').
The three reference cores make three different default choices, which is the whole point of having the switch:
- The C64 enables the filter by default — the SID's remembered warmth comes largely from that analog roll-off, so most users want it on.
- The Game Boy ships it off by default, leaving the crisp chiptune untouched unless the user asks for softening.
- The Amiga removes the menu item and runs the raw Paula output — Paula's sampled
sound already carries its own character, so
AExpbypasses the filter entirely (while keeping the correctedaudio_cx1 = 3in itsglobals.vhd, ready for the day it is switched on).
None of the three expose a "Mute" item today, though adding one is a trivial win: declare a
menu group and point qnice_audio_mute_o at its bit. The whole zero-to-silence path already
exists.
After the select/mute stage, the finished samples leave av_pipeline on a pair of signed
outputs and travel out to the board's top level (av_pipeline.vhd:694-697):
audio_left_o <= signed(audio_left);
audio_right_o <= signed(audio_right);From here to the physical jack, what happens depends on which MEGA65 you have — and that board-by-board split is the analog audio path's defining feature, the sound-side echo of the 15 kHz-versus-31 kHz divide on the video side.
A quick gotcha to clear up first. The analog video pipeline (
analog_pipeline.vhd) is also handed these audio samples, but it never uses them — the ports are vestigial. The jack's audio is routed around the video pipeline, straight from the select/mute stage to the board top. So do not go looking for audio processing inside the VGA path; there is none.
The R4 and later boards carry a dedicated stereo DAC chip, the AK4432VT, and the framework talks to it in I2S — the standard three-wire serial format for shipping PCM to a DAC. I2S carries a stereo sample stream on three signals, all derived from that 12.288 MHz master clock:
MCLK 12.288 MHz — the master clock (= 256 × 48 kHz), the DAC's heartbeat
BICK 3.072 MHz — the bit clock: one tick shifts one bit of sample data
LRCLK 48.000 kHz — the word-select clock: one full cycle = one stereo sample
SDTI (serial) — the sample bits themselves, most-significant bit first
The driver is M2M/vhdl/controllers/M65/audio.vhd. It counts the 12.288 MHz clock in a
0..255 cycle (256 clocks = one 48 kHz sample period), loads the left sample into the top
of a 64-bit shift register and the right sample into the middle, and clocks the bits out on
SDTI while generating BICK and the 48 kHz LRCLK from the same counter:
audio_mclk_o <= audio_clk_i; -- MCLK is simply the 12.288 MHz clock
...
i2s_data(63 downto 48) <= std_logic_vector(audio_left_i);
i2s_data(31 downto 16) <= std_logic_vector(audio_right_i);The DAC needs almost no setup. A hardwired pin (audio_i2cfil_o <= '0') selects its I2C interface
mode, and it otherwise comes up in a sane default — I2S input, 48 kHz — straight from power-on,
so the stock framework sends it no configuration at all. It does wire the DAC's control
lines into its shared I2C bus (audio_scl/audio_sda) — a separate, general-purpose
control bus, not to be confused with the I2S audio link above: I2S carries the sound; I2C would
only poke the chip's registers. Those lines exist so a core could retune the DAC (volume,
mode), but none of the reference cores do, and the I2C bus itself belongs to Devices. The
board top wires the driver up in one instance (top_mega65-r4.vhd:429-440, identical on R5 and
R6). If I2S is new to you,
the I²S serial format is a short read; the key idea
is just "MCLK, a bit clock, a left/right clock, and serial data."
The very first MEGA65 board revision, the R3, does not use a multi-bit DAC for the jack. Instead the FPGA drives the jack pin with a single-bit stream and lets a simple resistor-capacitor filter on the board turn it into an analog voltage. The technique is pulse-density modulation (PDM), also called first-order sigma-delta.
The idea is elegant. Instead of outputting a number, output a fast stream of 1s and 0s
whose density of 1s encodes the amplitude: a stream that is 1 three-quarters of the
time represents three-quarters full-scale, a stream that is 1 half the time represents
half, and so on. An RC low-pass filter averages the bitstream — literally computing its
running average as a voltage — and out comes the analog wave. All the FPGA has to do is
decide, every clock, whether to emit a 1 or a 0 so that the long-run density tracks the
sample. It does that with an accumulator (a "digital integrator"), in
M2M/vhdl/controllers/M65/pcm_to_pdm.vhdl:
if pdm_left_accumulator < 65536 then
pdm_left_accumulator <= pdm_left_accumulator + pcm_value_left;
ampPWM_pdm_l <= '0';
else
pdm_left_accumulator <= pdm_left_accumulator + pcm_value_left - 65536;
ampPWM_pdm_l <= '1';
end if;Every clock it adds the sample value to a running accumulator; whenever the accumulator
reaches full-scale it emits a 1 and subtracts full-scale back off, otherwise it emits a
0. Watch it settle on a half-scale sample (full-scale here is 65536, half is 32768):
| clock tick | accumulator (before) | reached full-scale? | output bit |
|---|---|---|---|
| 1 | 0 | no | 0 |
| 2 | 32768 | no | 0 |
| 3 | 65536 | yes | 1 |
| 4 | 32768 | no | 0 |
| 5 | 65536 | yes | 1 |
From tick 3 on it locks into 1, 0, 1, 0, … — high exactly half the time, encoding the
half-scale level. The long-run density of 1s always equals the sample's level, and the RC
filter turns that density back into the voltage. (The same module can also produce ordinary
PWM — a pulse whose width rather than density carries the level — selectable by a
mode input; M2M chooses PDM, which sounds cleaner on the MEGA65.) The R3 top wires it up at
top_mega65-r3.vhd:403-413, feeding the jack's pwm_l_o/pwm_r_o pins.
On the R3, the I2S DAC chip that is on the board (an SSM2518) is deliberately switched off
— held in power-down and its clock lines forced low (top_mega65-r3.vhd:453-457), because
the PDM path drives the jack instead. If PDM or sigma-delta is unfamiliar,
pulse-density modulation is a
gentle primer.
Whichever board you are on, the jack carries the same post-filter samples; only the last step — a real DAC or a one-bit bitstream — differs, and it is entirely the framework's problem. A porter never touches any of this. You hand over signed 16-bit samples once, and they come out of the headphone jack of every board revision, correctly, without another line of your code.
The other branch of the fork carries the same samples to HDMI. This is where audio borrows the video pipeline's machinery, so this part leans on Video Pipeline for the parts the two share and focuses on what is specific to sound.
An HDMI signal is, at bottom, the video pipeline's TMDS stream — HDMI's serial line code (see Video Pipeline §5.4). But HDMI adds something plain-video DVI never had: data islands, small packets transmitted during the blanking gaps between video lines. Audio rides in those islands. So the sound does not get its own cable or its own wires; it is a passenger tucked into the idle moments of the picture, encoded and serialized by the very same hardware. Two consequences follow immediately, and both matter later: the audio has to be squeezed into the blanking budget alongside the picture, and turning the islands off (DVI mode, §6.5) silences HDMI entirely.
HDMI audio in M2M is fixed at 48 kHz — chosen in digital_pipeline.vhd:164-167 as the
most universally compatible rate. The samples arrive in the 12.288 MHz audio domain, and
since 12.288 MHz = 256 × 48 kHz, a 48 kHz sample tick is simply "one clock in every 256."
The framework generates that tick generically, with a small accumulator
(clk_synthetic_enable) fed the two frequencies, in digital_pipeline.vhd:297-304:
i_clk_synthetic_enable : entity work.clk_synthetic_enable
port map (
clk_i => audio_clk_i,
src_speed_i => G_AUDIO_CLOCK_RATE, -- 12_288_000
dst_speed_i => HDMI_PCM_SAMPLING, -- 48_000
enable_o => audio_pcm_clken -- one pulse every 256 clocks
);For this particular ratio the accumulator lands on a perfect 256:1 division, so the 48 kHz strobe is exactly periodic and jitter-free.
Now a genuinely elegant problem. The HDMI sink — your TV — has to reconstruct the 48 kHz audio clock in order to play the samples. But the cable carries no separate audio clock; the only clock crossing it is the pixel clock that paces the video. So how does the TV recover 48 kHz from a 74.25 MHz pixel clock?
The answer is audio clock regeneration (ACR): the source periodically tells the sink two integers, N and CTS, from which the sink derives the audio rate off the pixel clock, by the HDMI-defined relation
f_audio = f_pixel × N / (128 × CTS)
M2M's values are set in digital_pipeline.vhd:306-309: N = 6144 (the HDMI-standard value
for 48 kHz, since 48000 × 128 / 1000 = 6144) and CTS = the pixel clock in kHz
(hdmi_video_mode.CLK_KHZ — e.g. 74250 at 720p). Because CTS is wired to the exact pixel
clock of the current video mode, the ratio N / CTS self-adjusts per mode and the sink always
recovers exactly 48 kHz, whatever HDMI mode is selected — no measurement, no drift. The
source resends this N/CTS pair in an ACR packet 1000 times a second (a 1 kHz strobe,
128 × Fs / N = 1 kHz, generated in digital_pipeline.vhd:311-326) so the sink stays locked.
The encoder that assembles the islands is vga_to_hdmi.vhd (from the Tyto2 project; the same
module the video pipeline uses to turn VGA-style timing into HDMI). Its banner cheerfully notes
that it "also injects PCM stereo audio." For sound it does three things:
filtered samples (48 kHz, in the 12.288 MHz audio domain)
│ widen each 16-bit sample to a 24-bit IEC 60958 subframe (the sample, low byte zero)
│ cross from the audio clock to the HDMI pixel clock
▼
HDMI data islands ── packed into the blanking gaps, alongside the ACR (N/CTS) packet
│ and two InfoFrames (audio + video) that describe the streams
▼
the SAME TMDS serializers that carry the picture ──▶ HDMI connector
(the video pipeline's Part 5.4 does this half)
IEC 60958 is the consumer-digital-audio frame format (the thing inside every optical S/PDIF cable); each 16-bit sample is left-justified into a 24-bit subframe. The samples then cross from the audio clock to the pixel clock inside the encoder, and are packed into audio-sample data islands. Two InfoFrames — small descriptor packets — tell the sink what it is receiving: the audio InfoFrame and channel-status bits declare 2-channel PCM, 16-bit, 48 kHz, front-left/front-right, no copyright, no pre-emphasis. The bit-level TMDS encoding and serialization are the video pipeline's job (Video Pipeline §5.4); here it is enough to know the sound is framed, described, and handed to the shared serializers.
One menu bit, qnice_dvi_o, puts the encoder into plain DVI mode. DVI is HDMI without
data islands — video only — so in DVI mode there are no audio packets, no ACR, and no
InfoFrames: the sound over HDMI simply disappears. This is deliberate, and occasionally a
lifesaver: some DVI-only monitors and cheap HDMI-to-DVI adapters choke on the audio islands
and show no picture at all until you switch to DVI. But it also means that "I get a picture
but no HDMI sound" often has a one-word cause — DVI — worth checking before anything
harder. (The same bit is discussed from the video side in Video Pipeline §5.4.)
And that is the whole digital path: resample to 48 kHz, tell the sink how to regenerate that rate from the pixel clock, frame the samples as IEC 60958, and let the picture's own serializers carry the sound out the connector. HDMI audio in M2M is stereo, 16-bit, 48 kHz, full stop — there is no surround, no higher bit depth, and (though the encoder technically supports it) no 44.1 kHz path; it is wired permanently to 48 kHz for maximum compatibility.
Everything in this article converges on the handful of decisions a porter actually makes. The three reference cores make them three ways; laid side by side they turn the abstract pipeline into a menu of concrete choices you can copy.
Commodore 64 (C64MEGA65) |
Amiga 500 (AExp) |
Game Boy Color (gbc4mega65) |
|
|---|---|---|---|
| Sound chip | SID (6581/8580): 3 analog voices + filter | Paula: 4 DMA sample channels | APU: 2 pulse + wave + noise |
| Native sample format | 18-bit signed (dual-SID stereo) | 15-bit signed (HW-mixed) | 16-bit unsigned, uncentered |
Conversion in main.vhd
|
keep top 16 bits (÷4) + saturate | append a 0 LSB (×2) |
logical shift right 1, forced 0 sign |
| Improvement filter | on by default | bypassed (raw Paula) | off by default |
audio_cx1 coefficient |
2 (template typo, inherited) | 3 (corrected binomial) | 2 (template typo, inherited) |
| Mute menu item | none (mute wired to '0') |
none | none |
| Analog jack DAC | framework — I2S on R4-R6, PDM on R3 | same | same |
| HDMI audio | framework — stereo 16-bit 48 kHz | same | same |
Read down a column and you have that core's entire audio design; read across a row and you have the menu of how one decision can go. The bottom two rows are grey for a reason: the jack DAC and the HDMI packaging are identical for every core, because they are the framework's job, not the porter's. Three short syntheses:
The Commodore 64 is the textbook case. Its chip is a beloved analog synthesizer whose warmth people expect, so the improvement filter is on by default and the only real work is the 18-to-16-bit downshift — with a saturating guard left in place as a scaffold for the extra audio sources (OPL, DAC, tape) a fuller port might add.
The Amiga shows that the filter is optional. Paula already sounds like Paula — sampled,
characterful — so AExp bypasses the filter and passes the raw stream, its conversion a single
one-bit shift. Tellingly, it still carries the corrected audio_cx1 = 3 in globals.vhd,
ready the moment anyone turns the filter on: the right numbers, waiting.
The Game Boy is the cautionary tale. Its APU speaks unsigned, uncentered audio, and the
wrong conversion — an arithmetic shift, or a naïve subtract of 0x8000 — produces loud
crackling, verified in real games. It is the concrete proof that "hand M2M signed 16-bit
centered on zero" is a contract you must actually honor, not a formality.
Distilling the whole article into the order you actually do things:
-
Convert your samples in
main.vhd. Map your sound chip's native output onto signed 16-bit centered on zero (§2.3). Test with a game that has near-silent passages: crackle or hum at silence means a sign or centering bug, not a volume one. -
Copy the filter coefficients into
globals.vhd. Take them from your MiSTer core'ssys/sys_top.v, not from the template (mindaudio_cx1— §4.3). Decide whether the filter should default on (like the C64) or off (like the Game Boy). -
Add the menu item(s) in
config.vhd. An "Audio improvements" toggle at minimum, and — a cheap win the reference cores skip — an optional "Mute." -
Wire the policy in
mega65.vhd. Driveqnice_audio_filter_o(and, if you added one,qnice_audio_mute_o) from those menu bits, and keep theC_MENU_*indices in sync with the menu order. - Stop there. The 12.288 MHz clock, the clock-domain crossing, the per-board jack DAC, and the HDMI 48 kHz packaging are all handled for you. Hand over good samples and the framework does the rest.
Steps 1–2 get sound out of both outputs; steps 3–4 give the user control over the filter; step 5 is the reminder that most of the pipeline is not yours to build.
- The signal that carries HDMI audio — Video Pipeline is this article's sister; its Part 5.4 covers the TMDS serializers and data islands that ferry the sound, and its Part 5 covers the DVI bit from the video side.
-
The audio clock and the clock-domain crossing — Clocking and Clock Domain Crossing
explains the 12.288 MHz
MMCME2, whatcdc_stableguarantees, and when to reach for a FIFO instead. - The coefficient constants in context — globals.vhd is the hardware-facts file where the filter coefficients (and much else) live.
- The menu items and general settings — config.vhd Switches and Settings is the exhaustive reference for the OSM lines you add.
- The exact edits, file by file — The Ultimate MiSTer2MEGA65 Porting Guide turns the checklist above into concrete steps with the MiSTer-to-M2M pattern catalog.
-
The MiSTer side you start from — MiSTer Quick Reference Guide describes the
AUDIO_L/AUDIO_R/AUDIO_S/AUDIO_MIXsignals a MiSTer core hands you. - Background primers — the PCM, I²S, and pulse-density-modulation links in Parts 1 and 5 are a gentle foundation if any of the digital-audio vocabulary still feels new.