Skip to content

Commit

Permalink
Added initial Atari 2600 support (4K and 3E mappers) (#237)
Browse files Browse the repository at this point in the history
* Added initial Atari 2600 support (4K and 3E mappers)

* Atari 2600:

* Updated section origins to include bank index.
* Fixed mapper weak symbols.
* Fix for set_horiz_pos() register ordering.
* Pass -mcpu=mos6502x flag.
* Added license notices.

* Atari 2600:
- Refactored into atari2600-common as parent.
- Changed to MAPPER_* defines.
- Better XRAM support with DECLARE_XRAM_VARIABLE macro.

* [Atari 2600] Refactored headers for future mappers. Added 3E signature for Stella.

* [Atari 2600] Minor updates.
- Removed weak attributes and redundant declarations.
- 16 more bytes of zeropage, leaving 16 bytes of stack.
- 3E mapper selects ROM0 on startup.
- MAPPER_CART_ROM_KB macro.

* [Atari 2600] Removed (leaf) attribute from C functions.
sehugg authored Nov 11, 2023
1 parent d940ff8 commit 14c0525
Showing 33 changed files with 1,205 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -3,3 +3,4 @@
.vscode/**
build/**
build*
*~
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -6,6 +6,9 @@ The LLVM-MOS compiler toolchain and platform libraries.

## Supported platforms

- Atari 2600
- 4K cartridge
- TigerVision 3E (2-KiB fixed, 2-KiB banked ROM or RAM)
- Atari 8-bit
- XEX file
- 8-KiB or 16-KiB standard cartridge
@@ -139,6 +142,8 @@ executables and libraries for that target.

| Platform | Command |
| -------------------------------- | ------------------------- |
| Atari 2600 (4K) | `mos-atari2600-4k-clang` |
| Atari 2600 (TigerVision 3E) | `mos-atari2600-3e-clang` |
| Atari 8-bit (.XEX) | `mos-atari8-clang` |
| Atari 8-bit (Standard cartridge) | `mos-atari8-stdcart` |
| Ben Eater's 6502 Breadboard Kit | `mos-eater-clang` |
3 changes: 3 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -44,3 +44,6 @@ endif()
if(PLATFORM STREQUAL eater)
add_subdirectory(eater)
endif()
if(PLATFORM MATCHES ^atari2600-)
add_subdirectory(atari2600)
endif()
3 changes: 3 additions & 0 deletions examples/atari2600/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
add_executable(demo_vcslib.a26 demo_vcslib.c)
target_compile_options(demo_vcslib.a26 PRIVATE "-Os")
install_example(demo_vcslib.a26)
120 changes: 120 additions & 0 deletions examples/atari2600/demo_vcslib.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@

#include <atari2600.h>
#include <vcslib.h>
#include <peekpoke.h>
#include <mapper.h>

#ifdef __ATARI2600_MAPPER_3E__
MAPPER_CART_ROM_KB(8); // 8K ROM (3 * 2 KB + 2 KB)
#endif
#ifdef __ATARI2600_MAPPER_4K__
MAPPER_CART_ROM_KB(4); // 4K ROM
#endif

#if !defined(__ATARI2600__)
#error "This example is for Atari 2600 only"
#endif

unsigned char color; // a frame counter

#ifdef MAPPER_BANKED_ROM
#define ROM_BANK(index) __attribute__((noinline, section(".rom"#index)))
#else
#define ROM_BANK(index)
#endif

#define KERNEL_BANK 1

ROM_BANK(KERNEL_BANK) void my_preframe(void) {
// Doing frame computation during blank
// Update color
TIA.colubk = color++;
// Set player 1 horizontal position
set_horiz_pos(0, color >= 0x80 ? -color : color);
apply_hmove();
}

ROM_BANK(KERNEL_BANK) void my_doframe(void) {
int i;
char c = color;
// Set player sprite color
TIA.colup0 = COLOR_CONV(color);
// Draw each scanline
for (i=0; i<192; i++) {
TIA.wsync = 0; // sync to scanline
TIA.colubk = COLOR_CONV(c); // set background
TIA.pf1 = i; // set playfield
TIA.grp0 = i; // set sprite bitmap
c++;
}
TIA.grp0 = 0; // clear sprite
}

ROM_BANK(KERNEL_BANK) void my_postframe(void) {
// additional post-frame processing goes here
}

// Display kernel loop
ROM_BANK(KERNEL_BANK) void do_kernel_loop() {
// loop until reset released
while (SW_RESET()) { }
// loop forever
while (1) {
kernel_1();
my_preframe();
kernel_2();
my_doframe();
kernel_3();
my_postframe();
kernel_4();
}
}

#ifdef MAPPER_XRAM

// XRAM on the VCS has different areas for read vs. write

typedef struct {
char buf[256];
} XRAMStruct;

// XRAM on the VCS has different areas for read vs. write
// declare xram_data_read and xram_data_write as XRAM variables
DECLARE_XRAM_VARIABLE(0, XRAMStruct xram_data);

void test_ram(void) {
char x;
POKE(MAPPER_XRAM_WRITE | 0x7f0, 0xaa);
x = PEEK(MAPPER_XRAM_READ | 0x3f0);
if (x != 0xaa) asm("brk");
xram_write(0x3f1, 0x55);
x = xram_read(0x3f1);
if (x != 0x55) asm("brk");
xram_write(0x3f2, 0xaa);
x = xram_read(0x3f2);
if (x != 0xaa) asm("brk");
xram_data_write.buf[0] = 1;
x = xram_data_read.buf[0];
if (x != 1) asm("brk");
}

#endif

int main() {

// test extra RAM, if available
#ifdef MAPPER_XRAM
ram_select(0);
test_ram();
#endif

// test banked rom call, if available
#ifdef MAPPER_BANKED_ROM
bank_select(KERNEL_BANK ^ 1);
banked_call_rom(KERNEL_BANK, do_kernel_loop);
#else
do_kernel_loop();
#endif

return 0;
}
3 changes: 3 additions & 0 deletions mos-platform/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -46,6 +46,9 @@ else()
endif()

add_subdirectory(common)
add_subdirectory(atari2600-common)
add_subdirectory(atari2600-4k)
add_subdirectory(atari2600-3e)
add_subdirectory(atari8-common)
add_subdirectory(atari8)
add_subdirectory(atari8-stdcart)
25 changes: 25 additions & 0 deletions mos-platform/atari2600-3e/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
platform(atari2600-3e COMPLETE PARENT atari2600-common)

if(NOT CMAKE_CROSSCOMPILING)
return()
endif()

file(READ clang.cfg CONFIG)

install(FILES
mapper.h
TYPE INCLUDE)

install(FILES
link.ld
TYPE LIB)

add_platform_object_file(atari2600-3e-init-mapper-o
init_mapper_3e.o
init_mapper_3e.S
)

add_platform_library(atari2600-3e-c
mapper_3e.c
)
target_include_directories(atari2600-3e-c SYSTEM BEFORE PUBLIC .)
1 change: 1 addition & 0 deletions mos-platform/atari2600-3e/clang.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-D__ATARI2600_MAPPER_3E__
7 changes: 7 additions & 0 deletions mos-platform/atari2600-3e/init_mapper_3e.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
; ensure that ROM0 is selected on startup
; so that it matches the __current_bank variable
; which is zero-initialized

.section .init.055,"axR",@progbits
sta $3F ; A = 0 from init.050

132 changes: 132 additions & 0 deletions mos-platform/atari2600-3e/link.ld
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/* TigerVision 3E ROM mapper */

INCLUDE vcs.ld

INPUT(init_mapper_3e.o)

PROVIDE(__cart_rom_size = 32);
PROVIDE(__cart_ram_size = 32);

ASSERT(__cart_rom_size >= 6,
"ATARI 2600 3E: ROM must be at least 6 KiB")
ASSERT(__cart_rom_size <= 32,
"ATARI 2600 3E: ROM must be no larger than 32 KiB")
ASSERT(__cart_rom_size % 2 == 0,
"ATARI 2600 3E: ROM must be multiple of 2 KiB")
ASSERT(__cart_ram_size <= 255,
"ATARI 2600 3E: RAM must be no larger than 255 KiB")
ASSERT(__cart_ram_size % 2 == 0,
"ATARI 2600 3E: RAM must be multiple of 2 KiB")

/*
High word of origin address:
0x00 0x00 = fixed ROM
0x01 [bank#] = bank-switched ROM
0x02 [bank#] = bank-switched RAM
*/

MEMORY {
rom0 : ORIGIN = 0x01001000, LENGTH = 0x800
rom1 : ORIGIN = 0x01013000, LENGTH = 0x800
rom2 : ORIGIN = 0x01025000, LENGTH = __cart_rom_size >= 8 ? 0x800 : 0
rom3 : ORIGIN = 0x01037000, LENGTH = __cart_rom_size >= 10 ? 0x800 : 0
rom4 : ORIGIN = 0x01049000, LENGTH = __cart_rom_size >= 12 ? 0x800 : 0
rom5 : ORIGIN = 0x0105b000, LENGTH = __cart_rom_size >= 14 ? 0x800 : 0
rom6 : ORIGIN = 0x0106d000, LENGTH = __cart_rom_size >= 16 ? 0x800 : 0
rom7 : ORIGIN = 0x0107f000, LENGTH = __cart_rom_size >= 18 ? 0x800 : 0
rom8 : ORIGIN = 0x01081000, LENGTH = __cart_rom_size >= 20 ? 0x800 : 0
rom9 : ORIGIN = 0x01093000, LENGTH = __cart_rom_size >= 22 ? 0x800 : 0
rom10 : ORIGIN = 0x010a5000, LENGTH = __cart_rom_size >= 24 ? 0x800 : 0
rom11 : ORIGIN = 0x010b7000, LENGTH = __cart_rom_size >= 26 ? 0x800 : 0
rom12 : ORIGIN = 0x010c9000, LENGTH = __cart_rom_size >= 28 ? 0x800 : 0
rom13 : ORIGIN = 0x010db000, LENGTH = __cart_rom_size >= 30 ? 0x800 : 0
rom14 : ORIGIN = 0x010ed000, LENGTH = __cart_rom_size >= 32 ? 0x800 : 0

perm : ORIGIN = 0x0000f800, LENGTH = 0x800
}

REGION_ALIAS("c_readonly", perm)

MEMORY {
xram0_read : ORIGIN = 0x02001000, LENGTH = __cart_ram_size >= 4 ? 0x400 : 0
xram0_write : ORIGIN = 0x02001400, LENGTH = __cart_ram_size >= 4 ? 0x400 : 0
xram1_read : ORIGIN = 0x02013000, LENGTH = __cart_ram_size >= 8 ? 0x400 : 0
xram1_write : ORIGIN = 0x02013400, LENGTH = __cart_ram_size >= 8 ? 0x400 : 0
xram2_read : ORIGIN = 0x02025000, LENGTH = __cart_ram_size >= 12 ? 0x400 : 0
xram2_write : ORIGIN = 0x02025400, LENGTH = __cart_ram_size >= 12 ? 0x400 : 0
xram3_read : ORIGIN = 0x02037000, LENGTH = __cart_ram_size >= 16 ? 0x400 : 0
xram3_write : ORIGIN = 0x02037400, LENGTH = __cart_ram_size >= 16 ? 0x400 : 0
xram4_read : ORIGIN = 0x02049000, LENGTH = __cart_ram_size >= 20 ? 0x400 : 0
xram4_write : ORIGIN = 0x02049400, LENGTH = __cart_ram_size >= 20 ? 0x400 : 0
xram5_read : ORIGIN = 0x0205b000, LENGTH = __cart_ram_size >= 24 ? 0x400 : 0
xram5_write : ORIGIN = 0x0205b400, LENGTH = __cart_ram_size >= 24 ? 0x400 : 0
xram6_read : ORIGIN = 0x0206d000, LENGTH = __cart_ram_size >= 28 ? 0x400 : 0
xram6_write : ORIGIN = 0x0206d400, LENGTH = __cart_ram_size >= 28 ? 0x400 : 0
xram7_read : ORIGIN = 0x0207f000, LENGTH = __cart_ram_size >= 32 ? 0x400 : 0
xram7_write : ORIGIN = 0x0207f400, LENGTH = __cart_ram_size >= 32 ? 0x400 : 0
}


SECTIONS {
INCLUDE c.ld

.rom0 : { *(.rom0 .rom0.*) } >rom0
.rom1 : { *(.rom1 .rom1.*) } >rom1
.rom2 : { *(.rom2 .rom2.*) } >rom2
.rom3 : { *(.rom3 .rom3.*) } >rom3
.rom4 : { *(.rom4 .rom4.*) } >rom4
.rom5 : { *(.rom5 .rom5.*) } >rom5
.rom6 : { *(.rom6 .rom6.*) } >rom6
.rom7 : { *(.rom7 .rom7.*) } >rom7
.rom8 : { *(.rom8 .rom8.*) } >rom8
.rom9 : { *(.rom9 .rom9.*) } >rom9
.rom10 : { *(.rom10 .rom10.*) } >rom10
.rom11 : { *(.rom11 .rom11.*) } >rom11
.rom12 : { *(.rom12 .rom12.*) } >rom12
.rom13 : { *(.rom13 .rom13.*) } >rom13
.rom14 : { *(.rom14 .rom14.*) } >rom14

.perm : { *(.perm .perm.*) } >perm

.xram0_read (NOLOAD) : { *(.xram0_read .xram0_read.*) } >xram0_read
.xram0_write (NOLOAD) : { *(.xram0_write .xram0_write.*) } >xram0_write
.xram1_read (NOLOAD) : { *(.xram1_read .xram1_read.*) } >xram1_read
.xram1_write (NOLOAD) : { *(.xram1_write .xram1_write.*) } >xram1_write
.xram2_read (NOLOAD) : { *(.xram2_read .xram2_read.*) } >xram2_read
.xram2_write (NOLOAD) : { *(.xram2_write .xram2_write.*) } >xram2_write
.xram3_read (NOLOAD) : { *(.xram3_read .xram3_read.*) } >xram3_read
.xram3_write (NOLOAD) : { *(.xram3_write .xram3_write.*) } >xram3_write
.xram4_read (NOLOAD) : { *(.xram4_read .xram4_read.*) } >xram4_read
.xram4_write (NOLOAD) : { *(.xram4_write .xram4_write.*) } >xram4_write
.xram5_read (NOLOAD) : { *(.xram5_read .xram5_read.*) } >xram5_read
.xram5_write (NOLOAD) : { *(.xram5_write .xram5_write.*) } >xram5_write
.xram6_read (NOLOAD) : { *(.xram6_read .xram6_read.*) } >xram6_read
.xram6_write (NOLOAD) : { *(.xram6_write .xram6_write.*) } >xram6_write
.xram7_read (NOLOAD) : { *(.xram7_read .xram7_read.*) } >xram7_read
.xram7_write (NOLOAD) : { *(.xram7_write .xram7_write.*) } >xram7_write

.vector 0x0000fffc : {
/* we don't really need NMI on the 2600 */
SHORT(_start) /* START entrypoint */
SHORT(_start) /* START entrypoint */
} >perm
}

OUTPUT_FORMAT {
FULL(rom0)
FULL(rom1)
FULL(rom2)
FULL(rom3)
FULL(rom4)
FULL(rom5)
FULL(rom6)
FULL(rom7)
FULL(rom8)
FULL(rom9)
FULL(rom10)
FULL(rom11)
FULL(rom12)
FULL(rom13)
FULL(rom14)
FULL(perm)
}
24 changes: 24 additions & 0 deletions mos-platform/atari2600-3e/mapper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* TigerVision (3E) mapper include files */

#ifndef MAPPER_H
#define MAPPER_H

#define MAPPER_TYPE_3E

#define MAPPER_BANKED_ROM
#define MAPPER_BANKED_ROM_SIZE 0x800

#define MAPPER_XRAM
#define MAPPER_XRAM_SIZE 0x400
#define MAPPER_XRAM_READ 0x1000
#define MAPPER_XRAM_WRITE 0x1400

#define MAPPER_CART_ROM_KB(kb) \
asm(".globl __cart_rom_size\n__cart_rom_size = " #kb)

#include <mapper_rom_single.h>
#include <mapper_xram_single.h>

typedef unsigned char bank_index_t;

#endif
73 changes: 73 additions & 0 deletions mos-platform/atari2600-3e/mapper_3e.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// 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.

#include <peekpoke.h>
#include <mapper.h>

__attribute__((section(".zp.bss"))) bank_index_t __current_bank;

void set_current_bank(bank_index_t bank_id) {
if (bank_id & 0x80) {
ram_select(bank_id & 0x7f);
} else {
bank_select(bank_id);
}
}

bank_index_t get_current_bank(void) {
return __current_bank;
}

void bank_select(bank_index_t bank_id) {
POKE(0x3f, bank_id);
__current_bank = bank_id;
}

void ram_select(bank_index_t bank_id) {
POKE(0x3e, bank_id);
__current_bank = bank_id | 0x80;
}

void xram_write(int offset, unsigned char value) {
POKE(MAPPER_XRAM_WRITE | offset, value);
}

unsigned char xram_read(int offset) {
return PEEK(MAPPER_XRAM_READ | offset);
}

__attribute__((callback(2))) void banked_call_rom(bank_index_t bankId,
void (*method)(void)) {
bank_index_t previous_bank_id = get_current_bank();
bank_select(bankId);
method();
set_current_bank(previous_bank_id);
}

__attribute__((callback(2))) void banked_call_ram(bank_index_t bankId,
void (*method)(void)) {
bank_index_t previous_bank_id = get_current_bank();
ram_select(bankId);
method();
set_current_bank(previous_bank_id);
}

// From Stella (cartridge auto-detection):
// 3E cart RAM bankswitching is triggered by storing the bank number
// in address 3E using 'STA $3E', ROM bankswitching is triggered by
// storing the bank number in address 3F using 'STA $3F'.
// We expect the latter will be present at least 2 times, since there
// are at least two banks
// https://github.com/stella-emu/stella/blob/d6224a8a6e30b4b323cde86ade2f05f75dcdfbec/src/emucore/CartDetector.cxx#L4
// It also only calls the 3E detection routine on powers-of-two,
// and if it hasn't detected Superchip via repeating bytes.
__attribute__((used, retain, section(".rom0"))) static void _stella_signature_3e() {
asm("sta $3f");
asm("sta $3e");
asm("sta $3f");
}
// Javatari.js detects 3E as a fallback for weird ROM sizes...
// 6 KB, 14 KB, 18 KB, etc.
// or if you have [3E] in the filename.
14 changes: 14 additions & 0 deletions mos-platform/atari2600-4k/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
platform(atari2600-4k COMPLETE PARENT atari2600-common)

if(NOT CMAKE_CROSSCOMPILING)
return()
endif()

install(FILES
mapper.h
TYPE INCLUDE)

install(FILES
link.ld
TYPE LIB)

1 change: 1 addition & 0 deletions mos-platform/atari2600-4k/clang.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-D__ATARI2600_MAPPER_4K__
29 changes: 29 additions & 0 deletions mos-platform/atari2600-4k/link.ld
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* 4K ROM */

INCLUDE vcs.ld

PROVIDE(__cart_rom_size = 4);
ASSERT(__cart_rom_size == 4 || __cart_rom_size == 2,
"ROM size must be 4 KiB or 2 KiB");

MEMORY {
perm : ORIGIN = 0xF000, LENGTH = __cart_rom_size * 1024
}

REGION_ALIAS("c_readonly", perm)

SECTIONS {
INCLUDE c.ld

.perm : { *(.perm .perm.*) } >perm

.vector (0xf000 + __cart_rom_size * 1024 - 4) : {
/* we don't really need NMI on the 2600 */
SHORT(_start) /* START entrypoint */
SHORT(_start) /* START entrypoint */
} >perm
}

OUTPUT_FORMAT {
FULL(perm)
}
11 changes: 11 additions & 0 deletions mos-platform/atari2600-4k/mapper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* 4K ROM */

#ifndef MAPPER_H
#define MAPPER_H

#define MAPPER_TYPE_4K

#define MAPPER_CART_ROM_KB(kb) \
asm(".globl __cart_rom_size\n__cart_rom_size = " #kb)

#endif
46 changes: 46 additions & 0 deletions mos-platform/atari2600-common/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
platform(atari2600-common PARENT common)

if(NOT CMAKE_CROSSCOMPILING)
return()
endif()

install(FILES
vcs.ld
TYPE LIB)

install(FILES
_riot.h
_tia.h
atari2600.h
atari2600_constants.h
vcslib.h
mapper_rom_single.h
mapper_rom_multi.h
mapper_xram_single.h
TYPE INCLUDE)

install(FILES
atari2600.inc
atari2600_riot.inc
atari2600_tia.inc
DESTINATION ${ASMINCDIR})

add_platform_object_file(atari2600-common-crt0-o
crt0.o
crt0.S
)
target_link_libraries(atari2600-common-crt0-o PRIVATE common-asminc)

add_platform_library(atari2600-common-crt0)
merge_libraries(atari2600-common-crt0
common-copy-zp-data
common-copy-data
common-exit-loop
)
target_link_libraries(atari2600-common-crt0 PRIVATE common-asminc)

add_platform_library(atari2600-common-c
frameloop.c
vcslib.S
)
target_include_directories(atari2600-common-c BEFORE PUBLIC .)
33 changes: 33 additions & 0 deletions mos-platform/atari2600-common/_riot.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// 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.

// Originally from cc65.

/*****************************************************************************/
/* */
/* Atari VCS 2600 RIOT registers addresses */
/* */
/* Source: DASM - vcs.h */
/* */
/* Florent Flament (contact@florentflament.com), 2017 */
/* */
/*****************************************************************************/

/* RIOT registers */
struct __riot {
unsigned char swcha;
unsigned char swacnt;
unsigned char swchb;
unsigned char swbcnt;
unsigned char intim;
unsigned char timint;

unsigned char unused[14];

unsigned char tim1t;
unsigned char tim8t;
unsigned char tim64t;
unsigned char t1024t;
};
107 changes: 107 additions & 0 deletions mos-platform/atari2600-common/_tia.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.

// Originally from cc65.

/*****************************************************************************/
/* */
/* Atari VCS 2600 TIA registers addresses */
/* */
/* Source: DASM - vcs.h */
/* */
/* Florent Flament (contact@florentflament.com), 2017 */
/* */
/*****************************************************************************/

/* TIA write / read registers */
struct __tia {
union {
unsigned char vsync;
unsigned char cxm0p;
};
union {
unsigned char vblank;
unsigned char cxm1p;
};
union {
unsigned char wsync;
unsigned char cxp0fb;
};
union {
unsigned char rsync;
unsigned char cxp1fb;
};
union {
unsigned char nusiz0;
unsigned char cxm0fb;
};
union {
unsigned char nusiz1;
unsigned char cxm1fb;
};
union {
unsigned char colup0;
unsigned char cxblpf;
};
union {
unsigned char colup1;
unsigned char cxppmm;
};
union {
unsigned char colupf;
unsigned char inpt0;
};
union {
unsigned char colubk;
unsigned char inpt1;
};
union {
unsigned char ctrlpf;
unsigned char inpt2;
};
union {
unsigned char refp0;
unsigned char inpt3;
};
union {
unsigned char refp1;
unsigned char inpt4;
};
union {
unsigned char pf0;
unsigned char inpt5;
};
unsigned char pf1;
unsigned char pf2;
unsigned char resp0;
unsigned char resp1;
unsigned char resm0;
unsigned char resm1;
unsigned char resbl;
unsigned char audc0;
unsigned char audc1;
unsigned char audf0;
unsigned char audf1;
unsigned char audv0;
unsigned char audv1;
unsigned char grp0;
unsigned char grp1;
unsigned char enam0;
unsigned char enam1;
unsigned char enabl;
unsigned char hmp0;
unsigned char hmp1;
unsigned char hmm0;
unsigned char hmm1;
unsigned char hmbl;
unsigned char vdelp0;
unsigned char vdelp1;
unsigned char vdelbl;
unsigned char resmp0;
unsigned char resmp1;
unsigned char hmove;
unsigned char hmclr;
unsigned char cxclr;
};
39 changes: 39 additions & 0 deletions mos-platform/atari2600-common/atari2600.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// 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.

// Originally from cc65. Modified from original version (added volatile).

/*****************************************************************************/
/* */
/* Atari VCS 2600 TIA & RIOT registers addresses */
/* */
/* Source: DASM Version 1.05 - vcs.h */
/* */
/* Florent Flament (contact@florentflament.com), 2017 */
/* */
/*****************************************************************************/


#ifndef _ATARI2600_H
#define _ATARI2600_H

#ifdef __cplusplus
extern "C" {
#endif

#include <_tia.h>
#define TIA (*(volatile struct __tia*)0x0000)

#include <_riot.h>
#define RIOT (*(volatile struct __riot*)0x0280)

#include <atari2600_constants.h>

#ifdef __cplusplus
}
#endif

/* End of atari2600.h */
#endif
13 changes: 13 additions & 0 deletions mos-platform/atari2600-common/atari2600.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
; 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.
; Originally from cc65.

; Atari 2600 TIA & RIOT read / write registers
;
; Florent Flament (contact@florentflament.com), 2017

; TIA & RIOT registers mapping
.include "atari2600_tia.inc"
.include "atari2600_riot.inc"
95 changes: 95 additions & 0 deletions mos-platform/atari2600-common/atari2600_constants.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// 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.

// Originally from cc65.

#ifndef _ATARI2600_CONSTANTS_H
#define _ATARI2600_CONSTANTS_H

// TIA - CONSTANTS

#define HMOVE_L7 (0x70)
#define HMOVE_L6 (0x60)
#define HMOVE_L5 (0x50)
#define HMOVE_L4 (0x40)
#define HMOVE_L3 (0x30)
#define HMOVE_L2 (0x20)
#define HMOVE_L1 (0x10)
#define HMOVE_0 (0x00)
#define HMOVE_R1 (0xF0)
#define HMOVE_R2 (0xE0)
#define HMOVE_R3 (0xD0)
#define HMOVE_R4 (0xC0)
#define HMOVE_R5 (0xB0)
#define HMOVE_R6 (0xA0)
#define HMOVE_R7 (0x90)
#define HMOVE_R8 (0x80)

// Values for ENAMx and ENABL
#define DISABLE_BM (0b00)
#define ENABLE_BM (0b10)

// Values for RESMPx
#define LOCK_MISSILE (0b10)
#define UNLOCK_MISSILE (0b00)

// Values for REFPx
#define NO_REFLECT (0b0000)
#define REFLECT (0b1000)

// Values for NUSIZx
#define ONE_COPY (0b000)
#define TWO_COPIES (0b001)
#define TWO_MED_COPIES (0b010)
#define THREE_COPIES (0b011)
#define TWO_WIDE_COPIES (0b100)
#define DOUBLE_SIZE (0b101)
#define THREE_MED_COPIES (0b110)
#define QUAD_SIZE (0b111)
#define MSBL_SIZE1 (0b000000)
#define MSBL_SIZE2 (0b010000)
#define MSBL_SIZE4 (0b100000)
#define MSBL_SIZE8 (0b110000)

// Values for CTRLPF
#define PF_PRIORITY (0b100)
#define PF_SCORE (0b10)
#define PF_REFLECT (0b01)
#define PF_NO_REFLECT (0b00)

// Values for SWCHB
#define P1_DIFF_MASK (0b10000000)
#define P0_DIFF_MASK (0b01000000)
#define BW_MASK (0b00001000)
#define SELECT_MASK (0b00000010)
#define RESET_MASK (0b00000001)

#define VERTICAL_DELAY (1)

// SWCHA joystick bits
#define MOVE_RIGHT (0b01111111)
#define MOVE_LEFT (0b10111111)
#define MOVE_DOWN (0b11011111)
#define MOVE_UP (0b11101111)
#define P0_JOYSTICK_MASK (0b11110000)
#define P1_JOYSTICK_MASK (0b00001111)

// SWCHA paddle bits
#define P0_TRIGGER_PRESSED (0b01111111)
#define P1_TRIGGER_PRESSED (0b10111111)
#define P2_TRIGGER_PRESSED (0b11110111)
#define P3_TRIGGER_PRESSED (0b11111011)

// Values for VBLANK
#define DUMP_PORTS (0b10000000)
#define ENABLE_LATCHES (0b01000000)
#define DISABLE_TIA (0b00000010)
#define ENABLE_TIA (0b00000000)

// Values for VSYNC
#define START_VERT_SYNC (0b10)
#define STOP_VERT_SYNC (0b00)

#endif
26 changes: 26 additions & 0 deletions mos-platform/atari2600-common/atari2600_riot.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
; 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.
; Originally from cc65.

; Atari 2600 RIOT read / write registers
;
; Source: DASM - vcs.h
; Details available in: Stella Programmer's Guide by Steve Wright
;
; Florent Flament (contact@florentflament.com), 2017

; Read registers
SWCHA = $0280
SWACNT = $0281
SWCHB = $0282
SWBCNT = $0283
INTIM = $0284
TIMINT = $0285

; Write registers
TIM1T = $0294
TIM8T = $0295
TIM64T = $0296
T1024T = $0297
75 changes: 75 additions & 0 deletions mos-platform/atari2600-common/atari2600_tia.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
; 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.
; Originally from cc65.

; Atari 2600 TIA read / write registers
;
; Source: DASM - vcs.h
; Details available in: Stella Programmer's Guide by Steve Wright
;
; Florent Flament (contact@florentflament.com), 2017

; Read registers
VSYNC = $00
VBLANK = $01
WSYNC = $02
RSYNC = $03
NUSIZ0 = $04
NUSIZ1 = $05
COLUP0 = $06
COLUP1 = $07
COLUPF = $08
COLUBK = $09
CTRLPF = $0A
REFP0 = $0B
REFP1 = $0C
PF0 = $0D
PF1 = $0E
PF2 = $0F
RESP0 = $10
RESP1 = $11
RESM0 = $12
RESM1 = $13
RESBL = $14
AUDC0 = $15
AUDC1 = $16
AUDF0 = $17
AUDF1 = $18
AUDV0 = $19
AUDV1 = $1A
GRP0 = $1B
GRP1 = $1C
ENAM0 = $1D
ENAM1 = $1E
ENABL = $1F
HMP0 = $20
HMP1 = $21
HMM0 = $22
HMM1 = $23
HMBL = $24
VDELP0 = $25
VDELP1 = $26
VDELBL = $27
RESMP0 = $28
RESMP1 = $29
HMOVE = $2A
HMCLR = $2B
CXCLR = $2C

; Write registers
CXM0P = $00
CXM1P = $01
CXP0FB = $02
CXP1FB = $03
CXM0FB = $04
CXM1FB = $05
CXBLPF = $06
CXPPMM = $07
INPT0 = $08
INPT1 = $09
INPT2 = $0A
INPT3 = $0B
INPT4 = $0C
INPT5 = $0D
3 changes: 3 additions & 0 deletions mos-platform/atari2600-common/clang.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-mcpu=mos6502x
-mlto-zp=112
-D__ATARI2600__
27 changes: 27 additions & 0 deletions mos-platform/atari2600-common/crt0.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
; 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.
; Originally from cc65. Modified from original version.

.section .init.010,"axR",@progbits
; Clear decimal mode
cld

.section .init.050,"axR",@progbits
; Initialization Loop:
; * Clears Atari 2600 whole memory (128 bytes) including BSS segment
; * Clears TIA registers
; * Sets system stack pointer to $ff (i.e top of zero-page)
ldx #0
txa
clearLoop:
dex
txs
pha
bne clearLoop

; we jump to main directly here to save 2 bytes of stack
; TODO: how do we get rid of "jsr main" in crt0?
.section .init.400,"axR",@progbits
jmp main
52 changes: 52 additions & 0 deletions mos-platform/atari2600-common/frameloop.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 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.

#include "vcslib.h"

void kernel_1(void) {
// Vertical Sync signal
TIA.vsync = START_VERT_SYNC;
TIA.wsync = 0x00;
// Test reset switch
if (SW_RESET()) {
asm("brk");
}
TIA.wsync = 0x00;
TIA.wsync = 0x00;
TIA.vsync = STOP_VERT_SYNC;

// Vertical Blank (preframe)
RIOT.tim64t = VBLANK_TIM64;
}

void kernel_2(void) {
while (RIOT.intim != 0) {}

// Turn on beam
TIA.wsync = 0x00;
TIA.vblank = ENABLE_TIA;

// Display frame (doframe)
#ifdef PAL
RIOT.t1024t = KERNAL_T1024;
#else
RIOT.tim64t = KERNAL_TIM64;
#endif
}

void kernel_3(void) {
while (RIOT.intim != 0) {}

// Turn off beam
TIA.wsync = 0x00;
TIA.vblank = DISABLE_TIA;

// Overscan (postframe)
RIOT.tim64t = OVERSCAN_TIM64;
}

void kernel_4(void) {
while (RIOT.intim != 0) {}
}
27 changes: 27 additions & 0 deletions mos-platform/atari2600-common/mapper_rom_multi.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// 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.

// Mapper functions for multiple-area bank switching schemes.
// (E0, EC)

#ifndef _MAPPER_ROM_MULTI_H_
#define _MAPPER_ROM_MULTI_H_

#ifdef __cplusplus
extern "C" {
#endif

// ROM bank index type.
typedef unsigned char rom_bank_t;
typedef unsigned char banked_area_t;

// Switch in a ROM bank into a given area.
void bank_select(banked_area_t area, rom_bank_t bank_id);

#ifdef __cplusplus
}
#endif

#endif
39 changes: 39 additions & 0 deletions mos-platform/atari2600-common/mapper_rom_single.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// 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.

// Mapper functions for single-area bank switching schemes.
// (F8, F6, F4, 3E, FA, E7, F0, UA, 0840)

#ifndef _MAPPER_ROM_SINGLE_H_
#define _MAPPER_ROM_SINGLE_H_

#ifdef __cplusplus
extern "C" {
#endif

// ROM bank index type.
typedef unsigned char rom_bank_t;

// Switch in a ROM bank.
void bank_select(rom_bank_t bank_id);

// Switch in a ROM or RAM bank, depending on the high bit (if set, it's RAM)
void set_current_bank(rom_bank_t bank_id);

// Get the currently switched bank index.
// If the high bit is set, it's a RAM bank, otherwise it's a ROM bank.
rom_bank_t get_current_bank(void);

// Switch to another ROM bank and call this function. Note: Using banked_call to
// call a second function from within another banked_call is safe. This function
// works no matter which switchable bank the function is in.
__attribute__((callback(2))) void banked_call_rom(rom_bank_t bank_id,
void (*method)(void));

#ifdef __cplusplus
}
#endif

#endif
49 changes: 49 additions & 0 deletions mos-platform/atari2600-common/mapper_xram_single.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2022 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.

// Functions for single-area RAM bank switching schemes.
// (3E, E7)

#ifndef _MAPPER_XRAM_H_
#define _MAPPER_XRAM_H_

#ifdef __cplusplus
extern "C" {
#endif

// RAM bank index type.
typedef unsigned char ram_bank_t;

// Macro to declare a variable in XRAM.
// - index = XRAM bank index
// - declaration = variable declaration
// This will declare two variables, one read-only and one write-only.
// For example, DECLARE_XRAM_VARIABLE(0, int my_var)
// creates two variables: my_var_read and my_var_write.
// NOTE: These variables are not initialized by default.
#define DECLARE_XRAM_VARIABLE(index, declaration) \
__attribute__((section(".xram" #index "_read"))) volatile const declaration##_read; \
__attribute__((section(".xram" #index "_write"))) volatile declaration##_write;

// Switch in a RAM bank.
void ram_select(ram_bank_t bank_id);

// Switch to another RAM bank and call this function.
__attribute__((callback(2))) void banked_call_ram(ram_bank_t bank_id,
void (*method)(void));

// Write a byte to extended RAM at set offset
// RAM must be selected first, or use banked_call_ram
void xram_write(int offset, unsigned char value);

// Read a byte from extended RAM at set offset
// RAM must be selected first, or use banked_call_ram
unsigned char xram_read(int offset);

#ifdef __cplusplus
}
#endif

#endif // _MAPPER_H_
13 changes: 13 additions & 0 deletions mos-platform/atari2600-common/vcs.ld
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

MEMORY {
zp : ORIGIN = __rc31 + 1, LENGTH = 0x100 - (__rc31 + 1)

pia_ram (w) : ORIGIN = 0x80, LENGTH = 0x80
}

/* Provide imaginary (zero page) registers. */
__rc0 = 0x80;
INCLUDE imag-regs.ld
ASSERT(__rc31 == 0x9f, "Inconsistent zero page map.")

REGION_ALIAS("c_writeable", pia_ram)
23 changes: 23 additions & 0 deletions mos-platform/atari2600-common/vcslib.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
; 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.

.include "atari2600.inc"

.align $10
.global _set_horiz_pos
_set_horiz_pos:
sec ; set carry flag
sta WSYNC ; start a new line
div15:
sbc #15 ; subtract 15
bcs div15 ; branch until negative
eor #7 ; calculate fine offset
asl
asl
asl
asl
sta HMP0,x ; set fine offset
sta RESP0,x ; fix coarse position
rts
86 changes: 86 additions & 0 deletions mos-platform/atari2600-common/vcslib.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// 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 _VCSLIB_H
#define _VCSLIB_H

#include <atari2600.h>
#include <stdint.h>

#if defined(__ATARI2600_MAPPER__)
#include <mapper.h>
#endif

// Define data types for different data sizes and signedness

typedef unsigned char byte; // 8-bit unsigned data type
typedef signed char sbyte; // 8-bit signed data type
typedef unsigned short word; // 16-bit unsigned data type

// Atari 2600 kernel helpers, called in a sequence every frame

void kernel_1(void); // before preframe
void kernel_2(void); // before kernel
void kernel_3(void); // after kernel
void kernel_4(void); // after postframe

// Function to set horizontal position of a game object.

typedef enum { PLAYER_0=0, PLAYER_1, MISSILE_0, MISSILE_1, BALL } TIAObject;

// A = X coordinate
// X = object index
__attribute__((leaf)) void _set_horiz_pos(byte xpos, byte objindex);

// swap order of call
#define set_horiz_pos(objindex,xpos) _set_horiz_pos(xpos,objindex)

// Waits for next scanline start
#define do_wsync() \
__attribute__((leaf)) asm volatile("sta $42 /* WSYNC */");

// Applies horizontal motion to sprite(s) after set_horiz_pos()
#define apply_hmove() \
__attribute__((leaf)) asm volatile("sta $42 /* WSYNC */" "\n" "sta $6a /* HMOVE */");

// Macros

#define P0 0
#define P1 1
#define M0 2
#define M1 3
#define BALL 4

#define SW_RESET() ((RIOT.swchb & RESET_MASK) == 0)
#define SW_SELECT() ((RIOT.swchb & SELECT_MASK) == 0)
#define SW_COLOR() ((RIOT.swchb & BW_MASK) != 0)
#define SW_P0_PRO() ((RIOT.swchb & P0_DIFF_MASK) != 0)
#define SW_P1_PRO() ((RIOT.swchb & P1_DIFF_MASK) != 0)

#define JOY_UP(plyr) (!(RIOT.swcha & ((plyr) ? 0x1 : ~MOVE_UP)))
#define JOY_DOWN(plyr) (!(RIOT.swcha & ((plyr) ? 0x2 : ~MOVE_DOWN)))
#define JOY_LEFT(plyr) (!(RIOT.swcha & ((plyr) ? 0x4 : ~MOVE_LEFT)))
#define JOY_RIGHT(plyr) (!(RIOT.swcha & ((plyr) ? 0x8 : ~MOVE_RIGHT)))
#define JOY_FIRE(plyr) !(((plyr) ? TIA.inpt5 : TIA.inpt4) & 0x80)

// make color greyscale if black and white switch is set
#define COLOR_CONV(color) (SW_COLOR() ? color : color & 0x0f)

// macros for setting PIA timers
#define _CYCLES(lines) (((lines) * 76) - 13)
#define _TIM64(cycles) (((cycles) >> 6) - (((cycles) & 63) < 12))
#define _T1024(cycles) ((cycles) >> 10)

#ifdef PAL
#define VBLANK_TIM64 _TIM64(_CYCLES(45))
#define KERNAL_T1024 _T1024(_CYCLES(250))
#define OVERSCAN_TIM64 _TIM64(_CYCLES(36))
#else
#define VBLANK_TIM64 _TIM64(_CYCLES(37))
#define KERNAL_TIM64 _TIM64(_CYCLES(194))
#define OVERSCAN_TIM64 _TIM64(_CYCLES(32))
#endif

#endif

0 comments on commit 14c0525

Please sign in to comment.