Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[mega65] Add DMA hardware registers and DMA examples #347

Merged
merged 19 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions examples/mega65/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,10 @@ 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)


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;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here an initializer list would have been nice, but it doesn't seem to be supported by the compiler. That is dma.options = {DST_ADDR_BITS_OPT, 0xff};

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
107 changes: 107 additions & 0 deletions mos-platform/mega65/_dmagic.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// 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
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,
};

/// The F018 "DMAgic" DMA controller at 0xd700
struct DMAgicController {
uint8_t addr_lsb_trigger; //!< offset 0x00
uint8_t addr_msb; //!< offset 0x01
uint8_t addr_bank; //!< offset 0x02, writing clears $d704
uint8_t enable_f018b; //!< offset 0x03, extensed fields
uint8_t addr_mb; //!< offset 0x04
uint8_t trigger_enhanced; //!< offset 0x05
uint8_t etrigmapd; //!< offset 0x06
uint8_t unused1[7]; //!< offset 0x07-0x0d
uint8_t addr_lsb; //!< offset 0x0e
uint8_t unused2[2]; //!< offset 0x0f-0x10
};

/// 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
106 changes: 106 additions & 0 deletions mos-platform/mega65/dma.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// 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.
//
// C++ support for MEGA65 DMA operations.
//

#pragma once
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer include guards for nit-picky compiler-person reasons.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.


#include <array>
#include <cstdint>
#include <mega65.h>
#include <type_traits>

namespace mega65::dma {

/**
* Data structure for running enhanced DMA jobs
*
* @tparam T Either a F018B (default) or F018A DMA list object.
* @tparam N Byte count of the options and their potential
* arguments excluding the end option (0) which is automatically
* added.
*/
template <size_t N, typename T = DMAList_F018B> struct DMAJob {
static_assert(N > 0);
static_assert(std::is_same<T, DMAList_F018A>::value ||
std::is_same<T, DMAList_F018B>::value);
std::array<uint8_t, N> options;
const uint8_t end_option = 0;
T dmalist;
};

/// Common structure for DMA fill and copy jobs
typedef DMAJob<7, DMAList_F018B> CommonDMAJob;

/**
* Create DMA structure for filling memory with a value and optional skip
*
* To perform the actual fill, call `trigger_dma()` on the returned structure.
*
* @param dst 28-bit destination address
* @param value Fill value
* @param count Number of values to fill
* @param skip Optional skip (default: 1)
*/
CommonDMAJob make_dma_fill(const uint32_t dst, const uint8_t value,
const uint16_t count, const uint8_t skip = 1) {
CommonDMAJob dma;
dma.options[0] = ENABLE_F018B_OPT;
dma.options[1] = SRC_ADDR_BITS_OPT;
dma.options[2] = 0;
dma.options[3] = DST_ADDR_BITS_OPT;
dma.options[4] = (uint8_t)(dst >> 20);
dma.options[5] = DST_SKIP_RATE_OPT;
dma.options[6] = skip;
dma.dmalist.command = DMA_FILL_CMD;
dma.dmalist.count = count;
dma.dmalist.source_addr = value;
dma.dmalist.source_bank = 0;
dma.dmalist.dest_addr = dst & 0xffff;
dma.dmalist.dest_bank = (dst >> 16) & 0x0f;
dma.dmalist.command_msb = 0;
dma.dmalist.modulo = 0;
return dma;
}

/**
* Create DMA structure for copying memory
*
* To perform the actual copy, call `trigger_dma()` on the returned structure.
*
* @param src 28-bit source address
* @param dst 28-bit destination address
* @param count Number of values to copy
*/
CommonDMAJob make_dma_copy(const uint32_t src, const uint32_t dst,
const uint16_t count) {
auto dma = make_dma_fill(dst, 0, count);
dma.options[2] = (uint8_t)(src >> 20);
dma.dmalist.command = DMA_COPY_CMD;
dma.dmalist.source_addr = src & 0xffff;
dma.dmalist.source_bank = (src >> 16) & 0x0f;
return dma;
}

/**
* Perform enhanced DMA action defined in DMAJob structure.
*/
template <size_t N, typename T>
inline void trigger_dma(const DMAJob<N, T> &dma_job) {
DMA.enable_f018b = std::is_same<T, DMAList_F018B>::value;
DMA.addr_bank = 0;
DMA.addr_msb = ((uint16_t)&dma_job) >> 8;
DMA.trigger_enhanced = ((uint16_t)&dma_job) & 0xff;
// The following is to prevent `dma_job` from being optimized out
mysterymath marked this conversation as resolved.
Show resolved Hide resolved
// by making sure it's used in fictive asm code. A simple
// `asm volatile("")` also seems to work for unknown reasons(?).
asm volatile(""
: /* no output */
: "R"(dma_job.options[0])
: /* no clobbers */);
}

} // namespace mega65::dma
3 changes: 3 additions & 0 deletions mos-platform/mega65/mega65.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ extern "C" {

#include <_45E100.h>
#include <_6526.h>
#include <_dmagic.h>
#include <_sid.h>
#include <_vic2.h>
#include <_vic3.h>
Expand Down Expand Up @@ -162,6 +163,8 @@ struct __color_palette {
#define HYPERVISOR (*(volatile struct __hypervisor *)0xd640)
/// Ethernet controller
#define ETHERNET (*(volatile struct __45E100 *)0xd6e0)
/// DMAgic DMA controller
#define DMA (*(volatile struct DMAgicController *)0xd700)
/// Math busy flag
#define MATHBUSY (*(volatile uint8_t *)0xd70f)
/// Math accelerator
Expand Down