C Development
C Compilers
LLVM-MOS
This fork of llvm-mos was adapted for F256/Wildbits usage by user sduensing from Kangaroo Punch Studios and can be found here. The installation instructions are simple and work for both Windows and Linux. It mostly uses a one-stop-shop f256lib.h file containing macros and functions leveraging some devices (not all yet) and the MicroKernel.
The default linking configuration will have your code start at 0x300, a 8 kb stack/trampoline from 0xA000 to 0xBFFF that is used to bring in higher memory content visible into that slice of the first 64k of the CPU. The slice 0xC000 to 0xDFFF is used by IO as usual and 0xE000 to 0xFFFF is used by the MicroKernel as usual. To change any of this, you can bring a modified copy of link.ld into your /projectfolder/ and it will supersede this default mapping.
List of "Gotchas" for llvm-mos
1) When trying to use the EMBED directive in order to put data at specific memory locations, keep in mind that you CAN'T comment those lines out with
/*and*/, the compiler will try to follow them whether they're in them or not. The only way to comment them is to use//.2) When trying to use the EMBED directive, you may want to put your assets in a subfolder of your project. As in, assuming your source files are in ./src/ (where '.' is the root of your project) and your assets are in ./assets/, then you WILL need to refer to your asset files like so:
EMBED(myassetname, "../assets/myassetfile.bin", 0x10000);. Note the weird ../ folder backtracking that has to be used. No other combination worked for me (-Mu0n)3) In the f256dev/ folder, the f256build.bat file contains all you need to compile and link your project. In the "call mos-f256-clang.bat" line, one important switch is the optimization level you let the compiler use. Experiment with -O1 (no optimization), -O2 (some) and -O3 (most). Some operations and repetitions work with some and fail with others. One prominent example of where it failed was a repetitive fileRead operation that read and parsed little chunks of data from a file, byte by byte and while it compiled and linked, it failed during execution. By reducing -O3 to -O2, it all worked again.
4) One linking error kept giving me "
ld.lld: error: ld-temp.o <inline asm>:2:1: operand must be a 16-bit address sta (mos8(.LpickAudioFile_zp_stk+4)" despite not having written any inline assembly in my project. You can skirt around the issue for the problematic function by adding a compiler attribute:__attribute__((optnone))right before your function definition.5) When your code nears a size of 0x9400 and above, you may get in trouble in terms of code not being fully loaded into RAM while executing. Either deliberately put some code into far memory and devise a scheme with the MMU to swap that code in and out of reach for the CPU, or you can carve yourself a bit more space by editing f256dev/llvm-mos/mos-platform/f256/lib/link.ld and change:
/* fake C Stack */
PROVIDE(__stack = 0xA000);
to
/* fake C Stack */
PROVIDE(__stack = 0xC000);to get yourself an extra 8k of code space before it collides with the stack
6) The compiler will often complain about a missing closing curly bracket '}' at the end of your source files. Just add the one it requires at the very end even though it's an extra one, or, finish your source file by putting your last function with this notation:
void myLastFunction(uint8_t whatever) {
//bunchacode
}
jbaker8935's guide on creating trampolines for functions in higher memory:
what you can do is pick a function in one of your projects that isnt time critical and create a trampoline function for it using this pattern:
void FAR8_video_init(void);
#pragma clang optimize off __attribute__((noinline)) void video_init(void) { volatile unsigned char ___mmu = (unsigned char)*(volatile unsigned char *)0x000d; *(volatile unsigned char *)0x000d = 8; FAR8_video_init(); *(volatile unsigned char *)0x000d = ___mmu; } #pragma clang optimize on __attribute__((noinline, section(".block8"))) void FAR8_video_init(void)note: that FAR8_video_init is the renamed function that is being moved. It's just a convention. in your main you can call video_init as you do normally and the trampoline will take care of swapping the code into A000 in cpu memory. For linking you need to create file f256.ld that sits in the project directory. it is a copy of "${ROOT}/llvm-mos/mos-platform/f256/lib/link.ld" but has additional linker instructions to explain how to deal with block8. à Around line 100 or so you need to add instructions after the include output.ld statement. like so:
/* Overlay Segments and Binary Data */ INCLUDE output.ld /* Hand Coded output.ld records */ SHORT(8*0x2000) BYTE(8/8) SHORT(end_block8 - __block8_lma) BYTE(0x00) TRIM(block8)i'm using f256build.sh and the normal 'everything in src' structure. Also I didnt fiddle with moving the default stack or swap page, so this example trampoline is using A000 (writing to 0x000d). Note that the mmu is left in edit mode, so the trampolines can just write to 000d with no disabling of interrupts.
Oscar64
Oscar64 is a C/C++ cross compiler running on a modern system (such as a Windows PC, Mac or Linux machine) and targets the classic 6502 family of processors. It is mainly focused on Commodore systems such as the C64, PET or VIC20. The compiler supports C99 and many C++ features up to variadic templates and lambda functions.
The purpose of this compiler is to eliminate the need to write 6502 assembler code to achieve high code density and fast execution speed. It continues to improve with all the games, demos and tools written by it. It supports disk overlays and banked cartridges for larger projects.
Oscar64 was adapted for the F256/WildBits by swdfrost and here is a link its github page. You will also need the adaptation of the f256lib (ported over from llvm-mos) from this github page as well, which also includes many projects examples as well.
Documentation
The main documentation website is located here: https://github.com/drmortalwombat/oscar64/blob/main/oscar64.md
Getting set up on Windows 10/11 (-Mu0n)
Assumptions:
- Windows' WSL is installed (ie with Ubuntu)
- Python is installed inside windows
- make is installed inside WSL
- gcc is installed inside WSL (sudo apt install build-essential)
Step 1) in Windows, make a base directory (let's call it BASEPATH) that will hold both: * the oscar64 project, but with the 'f256k-support' branch rather than 'main' branch https://github.com/sdwfrost/oscar64 * the f256lib-oscar64 project, 'main branch' https://github.com/sdwfrost/f256lib-oscar64 (both from sdwfrost's GitHub account) Step 2) from windows, open a cmd window, go to your intended base folder Step 3) type WSL Step 4) clone oscar64 with git clone -b f256k-support https://github.com/sdwfrost/oscar64.git Step 5) clone f256lib-oscar64 side by side in the same base folder. git clone -b main https://github.com/sdwfrost/f256lib-oscar64 Both 'oscar64' and 'f256lib-oscar64' folders will be in the same location when this is done. Step 6) go into oscar64/ Step 7) type: make -f make/makefile (this assumes gcc being present in WSL, it will take a while!) Step 8) switch over to f256lib-oscar64/ and type: OSCAR64=../../oscar64/bin/oscar64 make all (warning 1: at the time of writing this guide, the 2nd project, BachHero, triggers an error that halts the whole list. Simply remove that entry from /f256lib-oscar64/doodles/MakeFile to get the rest going) (warning 2: most projects will compile, but not run properly when run in the Wildbits environment since they expect extra library files that have not been ported over) Step 9) to compile a single project in general, make sure you use a path that points to the compiled oscar64 of step 7 like so: (assuming your project myproject is in BASEPATH/f256lib-oscar64/doodles/) BASEPATH/oscar64/bin/oscar64 -tm=f256k -n -i=BASEPATH/f256lib-oscar64/f256lib BASEPATH/f256lib-oscar64/doodles/myproject/src/myproject.c -o=BASEPATH/f256lib-oscar64/doodles/myproject/myproject.pgz
Embedding assets
Here's an example of a few assets loaded in a specific spot in memory, starting from 0x10000. It needs to be part of a section, then a region and then some array identifiers that point to the data itself. When the project compiles, you can check out the layout of the memory being used inside your .map file.
/*
* The following assets will be located within these bounds
*
10000 - 10400 : grupal, DATA:assets
10400 - 10800 : sBoy1, DATA:assets
10800 - 10c00 : sThing, DATA:assets
10c00 - 11000 : sGirl1, DATA:assets
11000 - 11400 : sCath, DATA:assets
11400 - 11800 : sCat, DATA:assets
11800 - 12000 : sDash, DATA:assets
*/
#pragma section( assets, 0 )
#pragma region( assets, 0x10000, 0x20000, , , {assets} )
#pragma data(assets)
__export const char grupal[] = { //1kb palette from 0x10000 to 0x103FF
#embed "../assets/grudge.pal"
};
__export const char sBoy1[] = { //16x16 sprite of 1kb from 0x10400 to 0x107FF
#embed "../assets/boy1.bin"
};
__export const char sThing[] = {
#embed "../assets/thing.bin"
};
__export const char sGirl1[] = {
#embed "../assets/girl1.bin"
};
__export const char sCath[] = {
#embed "../assets/cath.bin"
};
__export const char sCat[] = {
#embed "../assets/cat.bin"
};
__export const char sDash[] = {
#embed "../assets/dash.bin"
};
//return to normalcy
#pragma data(data)
Trampoline scheme for functions in higher memory
Much like assets, you will have to use the #pragma directive to set up a section and region for each chunk of RAM. Since a trampoline scheme gets used here in conjunction with the machine's MMU, these regions should be 0x2000 big, or 8kb.
Keep in mind that these chunks of RAM will be swapped in and out of the 0xA000 to 0xBFFF 8kb chunk visible by the CPU.
Here is the list of stuff you must define for it to work:
- A specific number for your code's block = physical address / 0x2000 you have in mind to stash your code. For example, to use the 8kb of code space between 0x10000 and 0x11FFF, this will be "overlay block 8". For the space between 0x12000 and 0x13FFF, this is "overlay block 9"
#define OVL1_BLOCK 8 // Physical 0x10000
- #pragma directive such as:
// Overlay 1: stored at physical 0x10000, compiled to run at 0xA000
#pragma section( ovl1_code, 0 )
#pragma region( ovl1, 0x10000, 0x12000, , , { ovl1_code }, 0xA000 )
- Forward declaration of a trampoline function used to access the higher memory function, such as:
// ---------------------------------------------------------------------------
// Forward declarations of trampolines (in main code, always visible)
// ---------------------------------------------------------------------------
void firstSegment(int arg1, int arg2);
// Trampolines save the current bank 5 mapping, switch to the target
// overlay's physical block, call the FAR function, then restore the
// original mapping. This supports nested cross-overlay calls.
void firstSegment(int arg1, int arg2) {
volatile byte saved = PEEK(OVERLAY_MMU_REG);
POKE(OVERLAY_MMU_REG, OVL1_BLOCK);
FAR_firstSegment(arg1, arg2);
POKE(OVERLAY_MMU_REG, saved);
}
- Your far memory function in between #pragma code directives:
#pragma code( ovl1_code )
void FAR_firstSegment(int arg1, int arg2) {
textPrint("firstSegment = ");
textPrintInt((int32_t)(arg1 + arg2));
textPrint("\n");
// Call into overlay 2 via trampoline (in main memory)
}
//return to normalcy
#pragma code( code )
Note: it's perfectly ok to define multiples of these high memory regions. One far memory function can call another far memory function from another region without issue, but it has to call it through its lower memory trampoline.
Compiling a program, scripts from Mu0n
I use a pair of scripts that are located in BASEPATH/f256lib-oscar64/doodles/
Here's the shell script ("c.sh") I use to simply compile a program, this works regardless if your project uses one .c file or multiples. Adjust your absolute paths to what you have on your modern computer. "projectname" is the folder of your project inside the "doodles" folder.
run it using the command ./c.sh myprojectname
#!/bin/bash
# --- 1. CONFIGURATION ---
OSCAR_BIN="/mnt/d/F256/oscar64/bin/oscar64"
# Use the Windows python launcher (it handles COM8 perfectly)
PYTHON_EXE="python.exe"
# Use the Windows path for the script (escaped backslashes)
FOENIX_MGR="D:\\F256\\llvm-mos\\f256dev\\FoenixMgr\\FoenixMgr\\fnxmgr.py"
# --- 2. ARGUMENTS ---
PROJ_NAME="$1"
SRC_DIR="${PROJ_NAME}/src/"
SRC_FILE="${PROJ_NAME}/src/${PROJ_NAME}.c"
OUT_DIR="${PROJ_NAME}/"
BIN_FILE="${PROJ_NAME}.pgz"
FLAGS="-tm=f256k -n -v -i=../f256lib"
# --- 3. Gather all files in src/
mapfile -t CFILES < <(find "$SRC_DIR" -maxdepth 1 -type f -name "*.c")
# --- 4. COMPILE ---
echo "Compiling:"
printf '%s\n' "${CFILES[@]}"
echo
echo "Linking into ${OUT_DIR}/${PROJ_NAME}.pgz"
$OSCAR_BIN $FLAGS "${CFILES[@]}" -o="./$OUT_DIR/$PROJ_NAME.pgz"
Here's the shell script ("car.sh" = Compile And Run) I use to compile a program and send it over to my Wildbits through the foenixmgr script. This assumes that your host is win11 and python is run from that, from inside WSL ubuntu. Tweak to your setup's situation, of course. run it using the command ./car.sh myprojectname
#!/bin/bash
# --- 1. CONFIGURATION ---
OSCAR_BIN="/mnt/d/F256/oscar64/bin/oscar64"
# Use the Windows python launcher (it handles COM8 perfectly)
PYTHON_EXE="python.exe"
# Use the Windows path for the script (escaped backslashes)
FOENIX_MGR="D:\\F256\\llvm-mos\\f256dev\\FoenixMgr\\FoenixMgr\\fnxmgr.py"
# --- 2. ARGUMENTS ---
PROJ_NAME="$1"
SRC_DIR="${PROJ_NAME}/src/"
SRC_FILE="${PROJ_NAME}/src/${PROJ_NAME}.c"
OUT_DIR="${PROJ_NAME}/"
BIN_FILE="${PROJ_NAME}.pgz"
FLAGS="-tm=f256k -n -i=../f256lib"
# --- 3. Gather all files in src/
mapfile -t CFILES < <(find "$SRC_DIR" -maxdepth 1 -type f -name "*.c")
# --- 4. COMPILE ---
echo "Compiling:"
printf '%s\n' "${CFILES[@]}"
echo
echo "Linking into ${OUT_DIR}/${PROJ_NAME}.pgz"
$OSCAR_BIN $FLAGS "${CFILES[@]}" -o="./$OUT_DIR/$PROJ_NAME.pgz"
# --- 5. TRANSFER ---
if [ $? -eq 0 ]; then
echo "Transferring to F256K2 via Windows Python..."
# We use the Windows path for the file we just built
WIN_BIN_PATH="D:\\F256\\f256lib-oscar64\\doodles\\${PROJ_NAME}\\${BIN_FILE}"
$PYTHON_EXE "$FOENIX_MGR" --port COM8 --run-pgz "$WIN_BIN_PATH"
else
echo "Compilation failed."
exit 1
fi