Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Audio Subsystem

Motivation

Sound is an integral part of the DOOM experience: weapon noises, monster growls, door sounds, and pickup effects provide crucial gameplay feedback. While the base HaDes-V system had no audio output, the Nexys 4 DDR provides an onboard audio amplifier (connected to a 3.5 mm jack) driven by a single PWM-capable GPIO pin. This makes a simple PWM audio output feasible with minimal external hardware.

PWM Audio Principle

Pulse-width modulation (PWM) converts a digital sample value into a variable-duty-cycle square wave. When passed through a low-pass filter (the audio amplifier circuit on the Nexys 4 DDR has an integrated RC filter), the average voltage corresponds to the sample value. An 8-bit PWM operating at ~293 kHz (75 MHz / 256) provides adequate audio quality for 8-bit mono sound effects.

Wishbone Audio Interface (wishbone_audio.sv)

The audio peripheral is a single self-contained module combining a FIFO buffer, a sample-rate timer, and a PWM modulator.

Circular Buffer

An 8 KB BRAM buffer holds up to 8192 8-bit PCM samples in a circular configuration:

logic [7:0] fifo_ram [0:8191];
logic [13:0] wr_ptr;
logic [13:0] rd_ptr;

Write and read pointers use 14-bit values: the lower 13 bits address the BRAM, while the 14th bit distinguishes between a full and empty FIFO. The fill level is:

assign fifo_count = wr_ptr - rd_ptr;
assign fifo_free  = 14'd8192 - fifo_count;

Sample Rate Generation

DOOM’s sound engine operates at 11,025 Hz with 8-bit unsigned PCM samples (center value 128 = silence). The sample rate is derived from the 75 MHz system clock:

localparam int CLK_DIV_11KHZ = 6801; // 75,000,000 / 11,025 ≈ 6801

A 13-bit counter generates a tick every 6801 clock cycles, advancing the read pointer and loading the next sample into the PWM modulator:

if (tick_11k) begin
    if (fifo_count > 0) begin
        current_sample <= fifo_ram[rd_ptr[12:0]];
        rd_ptr <= rd_ptr + 1;
    end else begin
        current_sample <= 8'd128; // Silence when starved
    end
end

PWM Modulator

The modulator compares the current sample against a free-running 8-bit counter:

pwm_counter <= pwm_counter + 1;
aud_pwm     <= (current_sample > pwm_counter) ? 1'b1 : 1'b0;

This produces a PWM signal at ~293 kHz (75 MHz / 256). The duty cycle is proportional to the sample value (0 = always low, 255 = always high, 128 = 50% duty = center voltage).

The audio amplifier shutdown signal is permanently asserted high:

assign aud_sd = 1'b1;

Wishbone Register Map

OffsetAccessDescription
0x00WPush an 8-bit PCM sample into the FIFO
0x04RRead: free space in the FIFO (14-bit value)

The CPU polls the free space register and writes batches of samples. A write to offset 0 pushes one byte into the FIFO if there is space. The FIFO automatically drains at 11,025 Hz.

Integration

The audio peripheral is connected to the Wishbone bus alongside the other peripherals. The MCU instantiation in defines/constants.sv assigns it an address in the peripheral range. The PWM output (aud_pwm) and amplifier shutdown (aud_sd) are routed to the Nexys 4 DDR’s audio amplifier header pins (A11 and D12 respectively) via the constraints file.

The software-side audio driver, which feeds PCM samples from DOOM’s sound mixer into the Wishbone FIFO, is documented in the Software chapter.