Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
116 changes: 116 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

permissions:
contents: read

concurrency:
# A commit to main gets a group of its own. Sharing one group would let a
# third push cancel the second while it waits, and that commit would ship
# no release without anything failing. Pull requests still collapse.
group: ${{ github.workflow }}-${{ github.ref == 'refs/heads/main' && github.sha || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

jobs:
examples:
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- uses: actions/checkout@v7

# From source, the way the SDK tells everyone to install it. bin/cl65
# finds its own ../target, so there is nothing to install afterwards.
- name: Build cc65
run: |
set -euo pipefail
git clone --depth 1 https://github.com/cc65/cc65 "$RUNNER_TEMP/cc65"
make -C "$RUNNER_TEMP/cc65" -j"$(nproc)"
echo "$RUNNER_TEMP/cc65/bin" >> "$GITHUB_PATH"

# rp6502_xram() runs a program on the emulator while CMake configures.
# These two variables skip the tool download, so the examples build
# against the tools this repository holds rather than the newest ones.
- name: Fetch the emulator
run: |
set -euo pipefail
cmake -DRP6502_TOOLS_RELOADED=TRUE -DRP6502_TOOLS_FETCHED=TRUE \
-P tools/rp6502.cmake
# A download that fails is a notice rather than an error, and the
# build would go on to report a header that is not at fault.
test -x tools/rp6502-emu

- name: Build every example
run: |
set -euo pipefail
cmake --preset cc65/Release
cmake --build --preset cc65/Release

- name: Collect the ROMs
id: pack
run: |
set -euo pipefail
sha=$(git rev-parse --short HEAD)
# A release is named for the commit it ships. A pull request ships
# nothing, and its HEAD is a merge commit nobody can look up, so it
# is named for the run instead.
if [ "$GITHUB_REF" = "refs/heads/main" ]; then
name="rp6502-${sha}-examples"
else
name="rp6502-${GITHUB_RUN_ID}-examples"
fi
mkdir "$name"
cp src/README.txt "$name/"
# Only the target ROMs. CMakeFiles holds directories with the same
# suffix, and the program rp6502_xram() runs while CMake configures
# is a .rp6502 of its own.
find build/cc65/release/src -mindepth 2 -maxdepth 2 -type f \
-name '*.rp6502' -exec cp -t "$name" {} +
# One ROM per example, so a target that quietly stopped producing
# one fails the release rather than leaving a gap in it.
want=$(grep -c '^add_subdirectory(src/' CMakeLists.txt)
got=$(find "$name" -name '*.rp6502' | wc -l)
if [ "$got" -ne "$want" ]; then
echo "packed ${got} ROMs, expected ${want}" >&2
exit 1
fi
ls -l "$name"
# Zipped from inside, so the ROMs are at the root of the archive
# rather than one folder down.
( cd "$name" && cmake -E tar cf "../${name}.zip" --format=zip . )
echo "sha=${sha}" >> "$GITHUB_OUTPUT"
echo "name=${name}" >> "$GITHUB_OUTPUT"

# Only where nothing is released. The name carries .zip because that is
# the filename the download is served with, and the directory is what
# goes in it, so the one archive holds the ROMs and nothing else.
- uses: actions/upload-artifact@v7
if: github.ref != 'refs/heads/main'
with:
name: ${{ steps.pack.outputs.name }}.zip
path: ${{ steps.pack.outputs.name }}/

- name: Publish the release
if: github.ref == 'refs/heads/main'
uses: softprops/action-gh-release@v3
with:
tag_name: build-${{ steps.pack.outputs.sha }}
# Without this the tag is created on whatever the default branch
# points at when the release is published, not on what was built.
target_commitish: ${{ github.sha }}
name: Examples ${{ steps.pack.outputs.sha }}
files: ${{ steps.pack.outputs.name }}.zip
fail_on_unmatched_files: true
body: |
Example ROMs for the Picocomputer 6502, built with cc65.

Unzip and run a `.rp6502` in the emulator, or load one on a
Picocomputer.

https://picocomputer.github.io
7 changes: 7 additions & 0 deletions src/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
RP6502 example ROMs

Each .rp6502 file is one program for the Picocomputer 6502. Run it in the
emulator, or load it on a Picocomputer from the monitor.

Documentation https://picocomputer.github.io
Source https://github.com/picocomputer/examples
52 changes: 24 additions & 28 deletions src/paint/paint.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ static void setup_bitmap(unsigned config, int width, int height, unsigned data)
// The point of the arrow is one pixel in from the corner of its image.
static void move_pointer(int x, int y)
{
xram0_struct_set(XRAM_POINTER_CONFIG, mode3_config_t, x_pos_px, x - 1);
xram0_struct_set(XRAM_POINTER_CONFIG, mode3_config_t, y_pos_px, y - 1);
xram0_struct_set(XRAM_CONFIG_POINTER, mode3_config_t, x_pos_px, x - 1);
xram0_struct_set(XRAM_CONFIG_POINTER, mode3_config_t, y_pos_px, y - 1);
}

static void draw_pointer(void)
Expand All @@ -86,7 +86,7 @@ static void draw_pointer(void)
};
// clang-format on
unsigned i;
RIA.addr0 = XRAM_POINTER_DATA;
RIA.addr0 = XRAM_DATA_POINTER;
RIA.step0 = 1;
for (i = 0; i < sizeof(image); i++)
RIA.rw0 = image[i];
Expand All @@ -113,7 +113,7 @@ static void mouse_sample(void)

VIA.ifr = 0x40; // acknowledge timer 1

RIA.addr0 = XRAM_MOU_DATA + offsetof(mouse_t, x);
RIA.addr0 = XRAM_DATA_MOU + offsetof(mouse_t, x);
RIA.step0 = 1;
count = RIA.rw0;
raw_x += (int8_t)(count - mouse_last_x);
Expand Down Expand Up @@ -155,8 +155,8 @@ static void mouse_init(void)
// in 8 ms.
unsigned period = ria_attr_get(RIA_ATTR_PHI2_KHZ) * 8 - 2;

xreg_ria_mouse(XRAM_MOU_DATA);
RIA.addr0 = XRAM_MOU_DATA + offsetof(mouse_t, x);
xreg_ria_mouse(XRAM_DATA_MOU);
RIA.addr0 = XRAM_DATA_MOU + offsetof(mouse_t, x);
RIA.step0 = 1;
mouse_last_x = RIA.rw0;
mouse_last_y = RIA.rw0;
Expand All @@ -183,7 +183,7 @@ static uint8_t mouse_read(int *x, int *y)
*x = mouse_x;
*y = mouse_y;
CLI();
RIA.addr0 = XRAM_MOU_DATA + offsetof(mouse_t, buttons);
RIA.addr0 = XRAM_DATA_MOU + offsetof(mouse_t, buttons);
return RIA.rw0 & (MOUSE_BUTTON_LEFT | MOUSE_BUTTON_RIGHT);
}

Expand All @@ -195,16 +195,12 @@ static uint8_t mouse_read(int *x, int *y)
// the host can draw a cursor, as the emulator can for a mouse, the program
// hides its own pointer and asks for a crosshair.

#define TABLET_CONTROL (XRAM_TAB_DATA + offsetof(tablet_t, control))
#define TABLET_STATUS (XRAM_TAB_DATA + offsetof(tablet_t, status))
#define TABLET_CONTACT (XRAM_TAB_DATA + offsetof(tablet_t, contact))

static int tablet_x, tablet_y;
static bool host_cursor;

static void tablet_init(void)
{
xreg_ria_tablet(XRAM_TAB_DATA);
xreg_ria_tablet(XRAM_DATA_TAB);
}

static uint8_t tablet_read(int *x, int *y)
Expand All @@ -213,20 +209,20 @@ static uint8_t tablet_read(int *x, int *y)
bool offered;
int tries;

RIA.addr0 = TABLET_STATUS;
RIA.addr0 = XRAM_DATA_TAB + offsetof(tablet_t, status);
offered = RIA.rw0 & TABLET_STATUS_HOST_CURSOR;
if (offered != host_cursor)
{
host_cursor = offered;
RIA.addr0 = TABLET_CONTROL;
RIA.addr0 = XRAM_DATA_TAB + offsetof(tablet_t, control);
RIA.rw0 = host_cursor ? TABLET_CURSOR_CROSSHAIR : TABLET_CURSOR_OFF;
}

// A read that lands while a value crosses into the next window can find
// every window zero, so the contact is read a second time.
for (tries = 0; tries < 2; tries++)
{
RIA.addr0 = TABLET_CONTACT;
RIA.addr0 = XRAM_DATA_TAB + offsetof(tablet_t, contact);
RIA.step0 = 1;
flags = RIA.rw0;
x0 = RIA.rw0;
Expand Down Expand Up @@ -266,7 +262,7 @@ static uint8_t tablet_read(int *x, int *y)
static void erase_picture(void)
{
unsigned i;
RIA.addr0 = XRAM_PICTURE_DATA;
RIA.addr0 = XRAM_DATA_PICTURE;
RIA.step0 = 1;
for (i = 0; i < CANVAS_WIDTH / 2 * (unsigned)CANVAS_HEIGHT; i++)
RIA.rw0 = 0;
Expand All @@ -276,7 +272,7 @@ static void erase_picture(void)
// program starts, and this one, a file to put it back.
static void load_logo(void)
{
unsigned addr = XRAM_PICTURE_DATA;
unsigned addr = XRAM_DATA_PICTURE;
int fd = open("ROM:logo", O_RDONLY);
int count;
while ((count = read_xram(addr, 0x7FFF, fd)) > 0)
Expand All @@ -289,7 +285,7 @@ static void draw_pixel(int x, int y)
{
uint8_t pair;
RIA.step0 = 0;
RIA.addr0 = XRAM_PICTURE_DATA + (unsigned)y * (CANVAS_WIDTH / 2) + x / 2;
RIA.addr0 = XRAM_DATA_PICTURE + (unsigned)y * (CANVAS_WIDTH / 2) + x / 2;
pair = RIA.rw0;
if (x & 1)
RIA.rw0 = (pair & 0xF0) | draw_color;
Expand Down Expand Up @@ -337,7 +333,7 @@ static void draw_picker_box(uint8_t shade, int x1, int y1, int x2, int y2)
RIA.step0 = 1;
for (y = y1; y <= y2; y++)
{
RIA.addr0 = XRAM_PICKER_DATA + PICKER_WIDTH * y + x1;
RIA.addr0 = XRAM_DATA_PICKER + PICKER_WIDTH * y + x1;
for (x = x1; x <= x2; x++)
RIA.rw0 = shade;
}
Expand Down Expand Up @@ -382,8 +378,8 @@ static void move_picker(int x, int y)
{
picker_x = clamp(x, 0, CANVAS_WIDTH - PICKER_WIDTH);
picker_y = clamp(y, 0, CANVAS_HEIGHT - PICKER_HEIGHT);
xram0_struct_set(XRAM_PICKER_CONFIG, mode3_config_t, x_pos_px, picker_x);
xram0_struct_set(XRAM_PICKER_CONFIG, mode3_config_t, y_pos_px, picker_y);
xram0_struct_set(XRAM_CONFIG_PICKER, mode3_config_t, x_pos_px, picker_x);
xram0_struct_set(XRAM_CONFIG_PICKER, mode3_config_t, y_pos_px, picker_y);
}

static int picker_pick(int x, int y)
Expand Down Expand Up @@ -470,20 +466,20 @@ int main(int argc, char *argv[])

load_logo();

xreg_vga_canvas(CANVAS_320X240);
setup_bitmap(XRAM_PICTURE_CONFIG, CANVAS_WIDTH, CANVAS_HEIGHT, XRAM_PICTURE_DATA);
setup_bitmap(XRAM_PICKER_CONFIG, PICKER_WIDTH, PICKER_HEIGHT, XRAM_PICKER_DATA);
setup_bitmap(XRAM_POINTER_CONFIG, POINTER_SIZE, POINTER_SIZE, XRAM_POINTER_DATA);
xreg_vga_canvas(1); // 320x240
setup_bitmap(XRAM_CONFIG_PICTURE, CANVAS_WIDTH, CANVAS_HEIGHT, XRAM_DATA_PICTURE);
setup_bitmap(XRAM_CONFIG_PICKER, PICKER_WIDTH, PICKER_HEIGHT, XRAM_DATA_PICKER);
setup_bitmap(XRAM_CONFIG_POINTER, POINTER_SIZE, POINTER_SIZE, XRAM_DATA_POINTER);

draw_picker();
move_picker((CANVAS_WIDTH - PICKER_WIDTH) / 2, 0);
set_color(LEFT, 8);
set_color(RIGHT, 0);
draw_pointer();

xreg_vga_mode3(MODE3_4BPP, XRAM_PICTURE_CONFIG, 0); // plane 0
xreg_vga_mode3(MODE3_8BPP, XRAM_PICKER_CONFIG, 1); // plane 1
xreg_vga_mode3(MODE3_8BPP, XRAM_POINTER_CONFIG, 2); // plane 2
xreg_vga_mode3(2, XRAM_CONFIG_PICTURE, 0); // 4 bits per pixel, plane 0
xreg_vga_mode3(3, XRAM_CONFIG_PICKER, 1); // 8 bits per pixel, plane 1
xreg_vga_mode3(3, XRAM_CONFIG_POINTER, 2); // 8 bits per pixel, plane 2

if (use_mouse)
mouse_init();
Expand Down
17 changes: 9 additions & 8 deletions src/paint/xram.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,14 @@ typedef struct
uint8_t pointer[POINTER_SIZE * POINTER_SIZE];
} xram_layout_t;

#define XRAM_PICTURE_CONFIG offsetof(xram_layout_t, picture_config)
#define XRAM_PICKER_CONFIG offsetof(xram_layout_t, picker_config)
#define XRAM_POINTER_CONFIG offsetof(xram_layout_t, pointer_config)
#define XRAM_TAB_DATA offsetof(xram_layout_t, tab)
#define XRAM_MOU_DATA offsetof(xram_layout_t, mou)
#define XRAM_PICTURE_DATA offsetof(xram_layout_t, picture)
#define XRAM_PICKER_DATA offsetof(xram_layout_t, picker)
#define XRAM_POINTER_DATA offsetof(xram_layout_t, pointer)
#define XRAM_CONFIG_PICTURE offsetof(xram_layout_t, picture_config)
#define XRAM_CONFIG_PICKER offsetof(xram_layout_t, picker_config)
#define XRAM_CONFIG_POINTER offsetof(xram_layout_t, pointer_config)

#define XRAM_DATA_TAB offsetof(xram_layout_t, tab)
#define XRAM_DATA_MOU offsetof(xram_layout_t, mou)
#define XRAM_DATA_PICTURE offsetof(xram_layout_t, picture)
#define XRAM_DATA_PICKER offsetof(xram_layout_t, picker)
#define XRAM_DATA_POINTER offsetof(xram_layout_t, pointer)

#endif
Loading