Skip to content

Commit

Permalink
[mega65] Add DMA hardware registers and DMA examples (#347)
Browse files Browse the repository at this point in the history
* Add DMA headers and example

* Add simple DMA example for fill/copy

* Avoid binary literals which is an extension

* Add asm volatile to end of trigger function

This prevents it from being optimized out, albeit
the underlying reason is still unknown.

* Add asm volatile hack to prevent optimization

* Use simpler volatile asm

* Remove comment

* Add DMA audio

* Use audio channel struct

* Added 24-bit register access on C23

* DMA audio bitflags

* Allow BitInt in C17 and C++

Remove C23 guard

* Add DMA audio example

* Documentation

* Fix CHSGN docs

* dma audio example works on real hardware

* Prettify doxygen strings

* Add include guard and comment to dma.hpp

* Fix typo
  • Loading branch information
mlund authored Jul 30, 2024
1 parent 255502c commit ae3a96f
Show file tree
Hide file tree
Showing 10 changed files with 485 additions and 0 deletions.
10 changes: 10 additions & 0 deletions examples/mega65/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,13 @@ install_example(viciv_test.prg)
add_executable(plasma.prg plasma.cc)
install_example(plasma.prg)

add_executable(vertical_raster.prg vertical_raster.cc)
install_example(vertical_raster.prg)

add_executable(simple_dma.prg simple_dma.cc)
install_example(simple_dma.prg)

add_executable(dma_audio.prg dma_audio.c)
install_example(dma_audio.prg)
install(FILES drums.s8 TYPE BIN)

34 changes: 34 additions & 0 deletions examples/mega65/dma_audio.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2024 LLVM-MOS Project
// Licensed under the Apache License, Version 2.0 with LLVM Exceptions.
// See https://github.com/llvm-mos/llvm-mos-sdk/blob/main/LICENSE for license
// information.

// MEGA65 DMA Audio example using C23

#include <mega65.h>

typedef unsigned _BitInt(24) uint24_t;

// Sample data prepared with:
// ~~~
// sox -v 2.0 -V -D drums.wav drums.s8 remix - lowpass 7000 rate -v -s -I 11822 dither -S
// ~~~
static const uint8_t sample[] = {
#embed "drums.s8"
};

int main(void) {
DMA.auden = DMA_AUDEN; // enable DMA audio
DMA.ch0rvol = 0; // mute right
DMA.ch0.enable = 0; // mute channel 0
DMA.ch0.baddr = (uint24_t)&sample; // base address
DMA.ch0.curaddr = (uint24_t)&sample; // current address
DMA.ch0.taddr = (uint16_t)&sample + sizeof(sample); // top address
DMA.ch0.freq = 0x001a88; // frequency
DMA.ch0.volume = 0x3f; // max volume
DMA.ch0.enable = DMA_CHENABLE ^ DMA_CHSBITS_8 ^ DMA_CHLOOP; // play!
while (1) {
// Leaves at least one cycle for DMA audio to steal
VICIV.bordercol += 1;
}
}
4 changes: 4 additions & 0 deletions examples/mega65/drums.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Source: https://commons.wikimedia.org/wiki/File:Patró_de_bateria.wav
Attribution: Escola Superior de Música de Catalunya (ESMUC)
License: CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0>, via Wikimedia Commons
Note: Modified from original.
Binary file added examples/mega65/drums.s8
Binary file not shown.
26 changes: 26 additions & 0 deletions examples/mega65/simple_dma.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2024 LLVM-MOS Project
// Licensed under the Apache License, Version 2.0 with LLVM Exceptions.
// See https://github.com/llvm-mos/llvm-mos-sdk/blob/main/LICENSE for license
// information.

#include <dma.hpp>
#include <mega65.h>

using namespace mega65::dma;

constexpr uint32_t SCREEN_ADDR = 0x0800; // Screen area
constexpr uint16_t COUNT = 4; // Bytes to fill
constexpr uint8_t CHAR = 41; // Char symbol to print

int main(void) {
{
// repeat some chars on first line
const auto dma = make_dma_fill(SCREEN_ADDR, CHAR, COUNT);
trigger_dma(dma);
}
{
// copy chars from above to second line
const auto dma = make_dma_copy(SCREEN_ADDR, SCREEN_ADDR + 80, COUNT);
trigger_dma(dma);
}
}
88 changes: 88 additions & 0 deletions examples/mega65/vertical_raster.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2024 LLVM-MOS Project
// Licensed under the Apache License, Version 2.0 with LLVM Exceptions.
// See https://github.com/llvm-mos/llvm-mos-sdk/blob/main/LICENSE for license
// information.

// Vertical Raster Bars using the DMAgic DMA controller.
//
// - Timing is crucial: specify NTSC or PAL below.
// - Compile with `mos-mega65-clang++ -Os -flto`
// - Originally from ACME assembler example in the MEGA65 book
// - Converted to llvm-mos / C++ by Wombat, 2024.
// - As of writing (Summer 2024) this runs only on real hardware

#include <dma.hpp>
#include <mega65.h>

using namespace mega65;

constexpr bool PAL = true; // Set to false on NTSC systems

constexpr uint8_t RASTER_COLORS[] = {
0, 1, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4,
5, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2,
3, 4, 5, 6, 7, 8, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2,
3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 1, 2, 3, 4,
5, 6, 7, 8, 9, 10, 11, 12, 13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 14,
13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 13, 12, 11, 10, 9,
8, 7, 6, 5, 4, 3, 2, 1, 0, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3,
2, 1, 0, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 10, 9, 8, 7,
6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 8, 7,
6, 5, 4, 3, 2, 1, 0, 7, 6, 5, 4, 3, 2, 1, 0, 6, 5, 4, 3,
2, 1, 0, 5, 4, 3, 2, 1, 0, 4, 3, 2, 1, 0, 3, 2, 1, 0, 2,
1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3,
4, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4,
5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1, 2, 3, 4, 5, 6,
7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 2, 3, 4,
5, 6, 7, 8, 9, 10, 11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 0, 1, 2, 3,
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 0, 0, 1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13, 14, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5,
4, 3, 2, 1, 0, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1,
0, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 12, 11, 10, 9,
8, 7, 6, 5, 4, 3, 2, 1, 0, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2,
1, 0, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4,
3, 2, 1, 0, 8, 7, 6, 5, 4, 3, 2, 1, 0, 7, 6, 5, 4, 3, 2,
1, 0, 6, 5, 4, 3, 2, 1, 0, 5, 4, 3, 2, 1, 0, 4, 3, 2, 1,
0, 3, 2, 1, 0, 2, 1, 0, 1, 0, 0, 0,
};

int main() {
asm volatile("sei"); // Disable interrupt to stabilise timing
VICIV.ctrl1 = 0; // Disable screen

// Set custom color palette
for (size_t i = 1; i < 16; i++) {
PALETTE.red[i] = 15 - i;
PALETTE.green[i] = 0;
PALETTE.blue[i] = i;
}

// Configure DMA job; see The MEGA65 Book, Appendix O.
dma::DMAJob<2, DMAList_F018A> dma;
dma.options[0] = DST_ADDR_BITS_OPT;
dma.options[1] = 0xff;
dma.dmalist.command = DMA_COPY_CMD;
dma.dmalist.count = PAL ? 628 : 624;
dma.dmalist.source_addr = (uint16_t)&RASTER_COLORS;
dma.dmalist.source_bank = 0x00;
dma.dmalist.dest_addr = 0x0020;
dma.dmalist.dest_bank = 0x0d ^ DMA_HOLD;
dma.dmalist.modulo = 0;

// Wait for new raster line
const auto line = VICIV.rasterline;
while (line == VICIV.rasterline)
;

#pragma clang loop unroll(disable)
while (true) {
dma::trigger_dma(dma);
if constexpr (PAL) {
asm volatile("nop");
}
}
}
2 changes: 2 additions & 0 deletions mos-platform/mega65/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ if(NOT CMAKE_CROSSCOMPILING)
endif()
install(FILES
_45E100.h
_dmagic.h
_vic3.h
_vic4.h
dma.hpp
mega65.h
TYPE INCLUDE)

Expand Down
213 changes: 213 additions & 0 deletions mos-platform/mega65/_dmagic.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
// Copyright 2023 LLVM-MOS Project
// Licensed under the Apache License, Version 2.0 with LLVM Exceptions.
// See https://github.com/llvm-mos/llvm-mos-sdk/blob/main/LICENSE for license
// information.

#ifndef _DMAGIC_H
#define _DMAGIC_H

#ifndef __cplusplus
#include <stddef.h>
#include <stdint.h>
#endif

/// DMA commands
enum
#ifdef __clang__
: uint8_t
#endif
{
DMA_COPY_CMD = 0x00, //!< DMA copy command
DMA_MIX_CMD = 0x01, //!< DMA mix command (unimplemented)
DMA_SWAP_CMD = 0x02, //!< DMA swap command (unimplemented)
DMA_FILL_CMD = 0x03, //!< DMA fill command
};

/// Addressing modes
enum
#ifdef __clang__
: uint8_t
#endif
{
DMA_LINEAR_ADDR = 0x00, //!< DMA linear (normal) addressing mode
DMA_MODULO_ADDR = 0x01, //!< DMA modulo (rectangular) addressing mode
DMA_HOLD_ADDR = 0x02, //!< DMA hold (constant address) addressing mode
DMA_XYMOD_ADDR =
0x03 //!< DMA XY MOD (bitmap rectangular) addressing mode (unimplemented)
};

/// BANK and FLAGS field has the following structure
enum
#ifdef __clang__
: uint8_t
#endif
{
DMA_HOLD = 16, //!< Do not change the address (bit 4)
DMA_MODULO = 32, //!< Apply the MODULO field to wrap around (bit 5)
DMA_DIRECTION = 64, //!< Apply the MODULO field to wrap around (bit 6)
DMA_IO = 128, //!< I/O registers visible during the DMA controller at $D000–$DFFF (bit 7).
};

/// DMA options (incomplete)
enum
#ifdef __clang__
: uint8_t
#endif
{
/// Use 11 byte F011A DMA list format [no value]
ENABLE_F018A_OPT = 0x0a,
/// Use 12 byte F011B DMA list format [no value]
ENABLE_F018B_OPT = 0x0b,
/// Source address bits 20 – 27 [value follows]
SRC_ADDR_BITS_OPT = 0x80,
/// Destination address bits 20 – 27 [value follows]
DST_ADDR_BITS_OPT = 0x81,
/// Destination skip rate (whole bytes) [value follows]
DST_SKIP_RATE_OPT = 0x85,
};

/// DMA audio channel structure
struct DMAAudioChannel {
uint8_t enable; //!< Enable Audio DMA channel X (offset 0x00)
union {
struct {
uint8_t baddr_lsb; //!< Base address LSB (offset 0x01)
uint8_t baddr_msb; //!< Base address MSB (offset 0x02)
uint8_t baddr_mb; //!< Base address middle byte (offset 0x03)
};
#ifdef __clang__
unsigned _BitInt(24) baddr; //!< 24-bit base address (offset 0x01)
#endif
};
union {
struct {
uint8_t freq_lsb; //!< Frequency LSB (offset 0x04)
uint8_t freq_mb; //!< Frequency middle byte (offset 0x05)
uint8_t freq_msb; //!< Frequency MSB (offset 0x06)
};
#ifdef __clang__
unsigned _BitInt(24) freq; //!< 24-bit frequency (offset 0x04)
#endif
};
union {
struct {
uint8_t taddr_lsb; //!< Top address LSB (offset 0x07)
uint8_t taddr_msb; //!< Top address MSB (offset 0x08)
};
#ifdef __clang__
unsigned _BitInt(16) taddr; //!< 16-bit top address (offset 0x07)
#endif
};
uint8_t volume; //!< playback volume (offset 0x09)
union {
struct {
uint8_t curaddr_lsb; //!< Current address LSB (offset 0x0a)
uint8_t curaddr_mb; //!< Current address middle byte (offset 0x0b)
uint8_t curaddr_msb; //!< Current address MSB (offset 0x0c)
};
#ifdef __clang__
unsigned _BitInt(24) curaddr; //!< 24-bit current address (offset 0x0a)
#endif
};
union {
struct {
uint8_t tmraddr_lsb; //!< Timing counter LSB (offset 0x0d)
uint8_t tmraddr_mb; //!< Timing counter middle byte (offset 0x0e)
uint8_t tmraddr_msb; //!< Timing counter MSB (offset 0x0f)
};
#ifdef __clang__
unsigned _BitInt(24) tmraddr; //!< 24-bit timing counter (offset 0x0d)
#endif
};
};

#ifdef __cplusplus
static_assert(sizeof(DMAAudioChannel) == 0x10);
#endif

/// Bitflags for controlling the DMA audio enable register ($d711)
enum
#ifdef __clang__
: uint8_t
#endif
{
DMA_AUDEN = 0b10000000, //!< Enable Audio DMA
DMA_BLKD = 0b01000000, //!< Block DMA
DMA_AUD_WRBLK = 0b00100000, //!< Audio write block
DMA_NOMIX = 0b00010000, //!< No mix; disables SID chips etc.
DMA_AUDBLKTO = 0b00000111, //!< Audio block timeout (DEBUG) Bits 0-2
};

/// Bitflags for controlling individual DMA audio channel enable registers
/// ($d720, etc)
enum
#ifdef __clang__
: uint8_t
#endif
{
DMA_CHENABLE = 0b10000000, //!< Enable channel
DMA_CHLOOP = 0b01000000, //!< Channel looping
DMA_CHSGN = 0b00100000, //!< Sample sign bit: 0=signed, 1=unsigned.
DMA_CHSINE = 0b00010000, //!< Play 32-sample sine wave instead of DMA data
DMA_CHSTP = 0b00001000, //!< Stop flag
DMA_CHSBITS_16 = 0b00000011, //!< 16-bit samples
DMA_CHSBITS_8 = 0b00000010, //!< 8-bit samples
};

/// The F018 "DMAgic" DMA controller at 0xd700
struct DMAgicController {
uint8_t addr_lsb_trigger; //!< DMAgic DMA list address LSB, and trigger DMA (when written) (offset 0x00)
uint8_t addr_msb; //!< DMA list address high byte (address bits 8 – 15) (offset 0x01)
uint8_t addr_bank; //!< DMA list address bank (address bits 16 – 22). Writing clears $D704 (offset 0x02)
uint8_t enable_f018b; //!< Offset 0x03, extensed fields
uint8_t addr_mb; //!< DMA list address mega-byte (offset 0x04)
uint8_t trigger_enhanced; //!< Set low-order byte of DMA list address, and trigger Enhanced DMA job (offset 0x05)
uint8_t etrigmapd; //!< Set low-order byte of DMAlistaddress and trigger EnhancedDMA job, with list in current CPU memory map (offset 0x06)
uint8_t unused1[7]; //!< Offset 0x07-0x0d
uint8_t addr_lsb; //!< DMA list address low byte (address bits 0 – 7) WITHOUT STARTING A DMA JOB (offset 0x0e)
uint8_t unused2[2]; //!< Offset 0x0f-0x10
uint8_t auden; //!< Enable Audio DMA (offset 0x11)
uint8_t unused3[10]; //!< Offset 0x12-0x1b
uint8_t ch0rvol; //!< Channel 0 right channel volume (offset 0x1c)
uint8_t ch1rvol; //!< Channel 1 right channel volume (offset 0x1d)
uint8_t ch2lvol; //!< Channel 2 left channel volume (offset 0x1e)
uint8_t ch3lvol; //!< Channel 3 left channel volume (offset 0x1f)
union {
struct {
struct DMAAudioChannel ch0; //!< Audio DMA channel 0 (offset 0x20)
struct DMAAudioChannel ch1; //!< Audio DMA channel 1 (offset 0x30)
struct DMAAudioChannel ch2; //!< Audio DMA channel 2 (offset 0x40)
struct DMAAudioChannel ch3; //!< Audio DMA channel 3 (offset 0x50)
};
struct DMAAudioChannel channel[4]; //!< Audio channels as an array (offset 0x20)
};
};

#ifdef __cplusplus
static_assert(sizeof(DMAgicController) == 0x60);
#endif

/// Older 11 byte DMA list structure; also known as just "F018"
struct DMAList_F018A {
uint8_t command; //!< Offset 0x00
uint16_t count; //!< Offset 0x01
uint16_t source_addr; //!< Offset 0x03
uint8_t source_bank; //!< Offset 0x05
uint16_t dest_addr; //!< Offset 0x06
uint8_t dest_bank; //!< Offset 0x08
uint16_t modulo; //!< Offset 0x09
};

/// Newer 12-byte "F018B" DMA list structure
struct DMAList_F018B {
uint8_t command; //!< Offset 0x00
uint16_t count; //!< Offset 0x01
uint16_t source_addr; //!< Offset 0x03
uint8_t source_bank; //!< Offset 0x05
uint16_t dest_addr; //!< Offset 0x06
uint8_t dest_bank; //!< Offset 0x08
uint8_t command_msb; //!< Offset 0x09
uint16_t modulo; //!< Offset 0x0a
};

#endif
Loading

0 comments on commit ae3a96f

Please sign in to comment.