diff --git a/.gitignore b/.gitignore index 623d56da0..f1c55be2f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .vscode/** build/** build* +*~ diff --git a/README.md b/README.md index c8272546d..17f37e6a9 100644 --- a/README.md +++ b/README.md @@ -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` | diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index f042a28e7..24c1825c1 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -44,3 +44,6 @@ endif() if(PLATFORM STREQUAL eater) add_subdirectory(eater) endif() +if(PLATFORM MATCHES ^atari2600-) + add_subdirectory(atari2600) +endif() diff --git a/examples/atari2600/CMakeLists.txt b/examples/atari2600/CMakeLists.txt new file mode 100644 index 000000000..fcd44ef8c --- /dev/null +++ b/examples/atari2600/CMakeLists.txt @@ -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) diff --git a/examples/atari2600/demo_vcslib.c b/examples/atari2600/demo_vcslib.c new file mode 100644 index 000000000..699bc5a34 --- /dev/null +++ b/examples/atari2600/demo_vcslib.c @@ -0,0 +1,120 @@ + +#include +#include +#include +#include + +#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; +} diff --git a/mos-platform/CMakeLists.txt b/mos-platform/CMakeLists.txt index c974e806a..fa591b80c 100644 --- a/mos-platform/CMakeLists.txt +++ b/mos-platform/CMakeLists.txt @@ -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) diff --git a/mos-platform/atari2600-3e/CMakeLists.txt b/mos-platform/atari2600-3e/CMakeLists.txt new file mode 100644 index 000000000..6bee97529 --- /dev/null +++ b/mos-platform/atari2600-3e/CMakeLists.txt @@ -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 .) diff --git a/mos-platform/atari2600-3e/clang.cfg b/mos-platform/atari2600-3e/clang.cfg new file mode 100644 index 000000000..ca0676b5a --- /dev/null +++ b/mos-platform/atari2600-3e/clang.cfg @@ -0,0 +1 @@ +-D__ATARI2600_MAPPER_3E__ diff --git a/mos-platform/atari2600-3e/init_mapper_3e.S b/mos-platform/atari2600-3e/init_mapper_3e.S new file mode 100644 index 000000000..591f12e68 --- /dev/null +++ b/mos-platform/atari2600-3e/init_mapper_3e.S @@ -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 + diff --git a/mos-platform/atari2600-3e/link.ld b/mos-platform/atari2600-3e/link.ld new file mode 100644 index 000000000..880588421 --- /dev/null +++ b/mos-platform/atari2600-3e/link.ld @@ -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) +} diff --git a/mos-platform/atari2600-3e/mapper.h b/mos-platform/atari2600-3e/mapper.h new file mode 100644 index 000000000..31700e669 --- /dev/null +++ b/mos-platform/atari2600-3e/mapper.h @@ -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 +#include + +typedef unsigned char bank_index_t; + +#endif diff --git a/mos-platform/atari2600-3e/mapper_3e.c b/mos-platform/atari2600-3e/mapper_3e.c new file mode 100644 index 000000000..07f5d156a --- /dev/null +++ b/mos-platform/atari2600-3e/mapper_3e.c @@ -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 +#include + +__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. diff --git a/mos-platform/atari2600-4k/CMakeLists.txt b/mos-platform/atari2600-4k/CMakeLists.txt new file mode 100644 index 000000000..eeb626bbf --- /dev/null +++ b/mos-platform/atari2600-4k/CMakeLists.txt @@ -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) + diff --git a/mos-platform/atari2600-4k/clang.cfg b/mos-platform/atari2600-4k/clang.cfg new file mode 100644 index 000000000..19665101d --- /dev/null +++ b/mos-platform/atari2600-4k/clang.cfg @@ -0,0 +1 @@ +-D__ATARI2600_MAPPER_4K__ diff --git a/mos-platform/atari2600-4k/link.ld b/mos-platform/atari2600-4k/link.ld new file mode 100644 index 000000000..66000a185 --- /dev/null +++ b/mos-platform/atari2600-4k/link.ld @@ -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) +} diff --git a/mos-platform/atari2600-4k/mapper.h b/mos-platform/atari2600-4k/mapper.h new file mode 100644 index 000000000..8c7b9a767 --- /dev/null +++ b/mos-platform/atari2600-4k/mapper.h @@ -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 diff --git a/mos-platform/atari2600-common/CMakeLists.txt b/mos-platform/atari2600-common/CMakeLists.txt new file mode 100644 index 000000000..ea4158c49 --- /dev/null +++ b/mos-platform/atari2600-common/CMakeLists.txt @@ -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 .) diff --git a/mos-platform/atari2600-common/_riot.h b/mos-platform/atari2600-common/_riot.h new file mode 100644 index 000000000..cde53da35 --- /dev/null +++ b/mos-platform/atari2600-common/_riot.h @@ -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; +}; diff --git a/mos-platform/atari2600-common/_tia.h b/mos-platform/atari2600-common/_tia.h new file mode 100644 index 000000000..6a1f70b2c --- /dev/null +++ b/mos-platform/atari2600-common/_tia.h @@ -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; +}; diff --git a/mos-platform/atari2600-common/atari2600.h b/mos-platform/atari2600-common/atari2600.h new file mode 100644 index 000000000..119454460 --- /dev/null +++ b/mos-platform/atari2600-common/atari2600.h @@ -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 + +#ifdef __cplusplus +} +#endif + +/* End of atari2600.h */ +#endif diff --git a/mos-platform/atari2600-common/atari2600.inc b/mos-platform/atari2600-common/atari2600.inc new file mode 100644 index 000000000..5bfacd7e1 --- /dev/null +++ b/mos-platform/atari2600-common/atari2600.inc @@ -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" diff --git a/mos-platform/atari2600-common/atari2600_constants.h b/mos-platform/atari2600-common/atari2600_constants.h new file mode 100644 index 000000000..c22233ec9 --- /dev/null +++ b/mos-platform/atari2600-common/atari2600_constants.h @@ -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 diff --git a/mos-platform/atari2600-common/atari2600_riot.inc b/mos-platform/atari2600-common/atari2600_riot.inc new file mode 100644 index 000000000..5eca2d045 --- /dev/null +++ b/mos-platform/atari2600-common/atari2600_riot.inc @@ -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 diff --git a/mos-platform/atari2600-common/atari2600_tia.inc b/mos-platform/atari2600-common/atari2600_tia.inc new file mode 100644 index 000000000..a6039ac48 --- /dev/null +++ b/mos-platform/atari2600-common/atari2600_tia.inc @@ -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 diff --git a/mos-platform/atari2600-common/clang.cfg b/mos-platform/atari2600-common/clang.cfg new file mode 100644 index 000000000..95c4ad1ab --- /dev/null +++ b/mos-platform/atari2600-common/clang.cfg @@ -0,0 +1,3 @@ +-mcpu=mos6502x +-mlto-zp=112 +-D__ATARI2600__ diff --git a/mos-platform/atari2600-common/crt0.S b/mos-platform/atari2600-common/crt0.S new file mode 100644 index 000000000..423f4ff64 --- /dev/null +++ b/mos-platform/atari2600-common/crt0.S @@ -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 diff --git a/mos-platform/atari2600-common/frameloop.c b/mos-platform/atari2600-common/frameloop.c new file mode 100644 index 000000000..f9fd4848a --- /dev/null +++ b/mos-platform/atari2600-common/frameloop.c @@ -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) {} +} diff --git a/mos-platform/atari2600-common/mapper_rom_multi.h b/mos-platform/atari2600-common/mapper_rom_multi.h new file mode 100644 index 000000000..0ed45ab59 --- /dev/null +++ b/mos-platform/atari2600-common/mapper_rom_multi.h @@ -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 diff --git a/mos-platform/atari2600-common/mapper_rom_single.h b/mos-platform/atari2600-common/mapper_rom_single.h new file mode 100644 index 000000000..33578d69f --- /dev/null +++ b/mos-platform/atari2600-common/mapper_rom_single.h @@ -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 diff --git a/mos-platform/atari2600-common/mapper_xram_single.h b/mos-platform/atari2600-common/mapper_xram_single.h new file mode 100644 index 000000000..fadae1aba --- /dev/null +++ b/mos-platform/atari2600-common/mapper_xram_single.h @@ -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_ diff --git a/mos-platform/atari2600-common/vcs.ld b/mos-platform/atari2600-common/vcs.ld new file mode 100644 index 000000000..d5f3b18bb --- /dev/null +++ b/mos-platform/atari2600-common/vcs.ld @@ -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) diff --git a/mos-platform/atari2600-common/vcslib.S b/mos-platform/atari2600-common/vcslib.S new file mode 100644 index 000000000..deafbcf80 --- /dev/null +++ b/mos-platform/atari2600-common/vcslib.S @@ -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 diff --git a/mos-platform/atari2600-common/vcslib.h b/mos-platform/atari2600-common/vcslib.h new file mode 100644 index 000000000..85ee7a6c6 --- /dev/null +++ b/mos-platform/atari2600-common/vcslib.h @@ -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 +#include + +#if defined(__ATARI2600_MAPPER__) +#include +#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