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
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

PRG32 is an educational runtime for RISC-V assembly and C games.

The resident firmware keeps games decoupled from the default app: cartridges
are stored in four 128 KiB flash slots, one selected cartridge is copied into
the configured executable RAM window, and local scoreboard records persist in a
dedicated flash partition until they can be synchronized with Cartridge Store or
ScoreServer APIs. Physical classroom builds default to a smaller RAM window to
preserve setup/Wi-Fi heap; QEMU keeps the 64 KiB extended profile.

## Academic Profile

- Project domain: Embedded Systems and Computer Architecture Education
Expand Down Expand Up @@ -525,6 +532,7 @@ C versions of:
- `platformer`
- `raycaster`
- `wing_commander`
- `frogger`

See [examples/games/README.md](examples/games/README.md) for step-by-step
instructions to run each game embedded in firmware or as an uploadable
Expand Down
5 changes: 3 additions & 2 deletions components/prg32/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,9 @@ config PRG32_CART_RAM_CLASSROOM
config PRG32_CART_RAM_EXTENDED
bool "Extended profile, 64 KiB"
help
Reserve the historical large cartridge window. Use this only for labs
or demos that intentionally need larger linked cartridge images.
Reserve a larger cartridge execution window. QEMU uses this profile by
default; physical builds can enable it for labs that intentionally need
larger linked cartridge images.

config PRG32_CART_RAM_CUSTOM
bool "Custom profile"
Expand Down
18 changes: 15 additions & 3 deletions components/prg32/include/prg32.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ extern "C" {
#define PRG32_LCD_H 240
#define PRG32_GAME_W 320
#define PRG32_GAME_H 200
#define PRG32_SCOREBOARD_TOP_MAX 5
#define PRG32_TEXT_COLS 40
#define PRG32_TEXT_ROWS 25
#define PRG32_TILE_W 8
Expand Down Expand Up @@ -84,7 +85,7 @@ extern "C" {

#define PRG32_CART_MAGIC "PRG2"
#define PRG32_CART_ABI_MAJOR 1
#define PRG32_CART_ABI_MINOR 0
#define PRG32_CART_ABI_MINOR 1
#define PRG32_CART_FLAG_AUDIO_BLOCK (1u << 0)
#define PRG32_CART_FLAG_MULTIPLAYER (1u << 1)
#define PRG32_CART_FLAG_ABI_TABLE (1u << 2)
Expand All @@ -103,13 +104,13 @@ extern "C" {
#define PRG32_CART_ARCH_ESP32C6 "esp32c6"
#define PRG32_CART_ARCH_QEMU "qemu"
#define PRG32_CART_LOAD_ADDR 0x40800000u
#define PRG32_CART_MAX_SIZE (32u * 1024u)
#define PRG32_CART_MAX_SIZE (128u * 1024u)
#ifndef CONFIG_PRG32_CART_RAM_KIB
#define CONFIG_PRG32_CART_RAM_KIB 32
#endif
#define PRG32_CART_RAM_SIZE ((uint32_t)CONFIG_PRG32_CART_RAM_KIB * 1024u)
#define PRG32_CART_NAME_LEN 32
#define PRG32_CART_SLOT_COUNT 2
#define PRG32_CART_SLOT_COUNT 4
#ifndef PRG32_FIRMWARE_VERSION
#define PRG32_FIRMWARE_VERSION "dev"
#endif
Expand Down Expand Up @@ -274,7 +275,16 @@ const char *prg32_wifi_current_ssid(void);
int prg32_wifi_setup_requested(void);
int prg32_wifi_setup_run(void);
void prg32_scores_api_start(void);
int prg32_score_player_get(char *out_player, size_t max_len);
int prg32_score_player_set(const char *player);
int prg32_score_player_prompt(void);
int prg32_score_submit(const char *game, const char *player, uint32_t score);
int prg32_score_submit_current_player(const char *game, uint32_t score);
int prg32_score_sync_remote(void);
int prg32_score_reset_local(const char *game);
int prg32_score_count(const char *game);
int prg32_score_get(const char *game, int index, prg32_score_t *out_score);
int prg32_scoreboard_show(const char *game, const char *title);
int prg32_score_submit_remote(const char *base_url,
const char *game,
const char *player,
Expand Down Expand Up @@ -302,6 +312,7 @@ int prg32_cart_install_slot(uint8_t slot,
size_t image_size,
int persist);
int prg32_cart_store_slot(uint8_t slot, const void *image, size_t image_size);
int prg32_cart_erase_slot(uint8_t slot);
size_t prg32_cart_slot_size(uint8_t slot);
int prg32_cart_stream_begin(uint8_t slot, size_t image_size);
int prg32_cart_stream_write(uint8_t slot, size_t offset, const void *data, size_t len);
Expand Down Expand Up @@ -428,6 +439,7 @@ void prg32_platform_camera_follow(const prg32_platform_actor_t *actor,
int prg32_sprite_hitbox(int ax, int ay, int aw, int ah, int bx, int by, int bw, int bh);
void prg32_sprite_draw_8x8(int x, int y, const uint8_t *bits, uint16_t fg, uint16_t bg);
void prg32_sprite_draw_16x16(int x, int y, const uint16_t *rgb565);
void prg32_sprite_draw_24x24(int x, int y, const uint16_t *rgb565);
uint32_t prg32_sprite_anim_frame(uint32_t now_ms,
uint32_t frame_count,
uint32_t frame_ms);
Expand Down
4 changes: 2 additions & 2 deletions components/prg32/include/prg32_abi_hash.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
/* Generated by tools/prg32_abi_gen.py; do not edit manually. */

#define PRG32_ABI_MAJOR 1u
#define PRG32_ABI_MINOR 0u
#define PRG32_ABI_HASH 0xb9cadd82u
#define PRG32_ABI_MINOR 2u
#define PRG32_ABI_HASH 0xec21efe2u
11 changes: 10 additions & 1 deletion components/prg32/include/prg32_abi_index.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,5 +116,14 @@ enum {
PRG32_ABI_FN_PRG32_SPRITE_ANIM_UPDATE = 110,
PRG32_ABI_FN_PRG32_SPRITE_ANIM_DRAW = 111,
PRG32_ABI_FN_PRG32_SCORE_SUBMIT = 112,
PRG32_ABI_FN_COUNT = 113
PRG32_ABI_FN_PRG32_SPRITE_DRAW_24X24 = 113,
PRG32_ABI_FN_PRG32_SCORE_PLAYER_GET = 114,
PRG32_ABI_FN_PRG32_SCORE_PLAYER_SET = 115,
PRG32_ABI_FN_PRG32_SCORE_PLAYER_PROMPT = 116,
PRG32_ABI_FN_PRG32_SCORE_SUBMIT_CURRENT_PLAYER = 117,
PRG32_ABI_FN_PRG32_SCORE_SYNC_REMOTE = 118,
PRG32_ABI_FN_PRG32_SCORE_COUNT = 119,
PRG32_ABI_FN_PRG32_SCORE_GET = 120,
PRG32_ABI_FN_PRG32_SCOREBOARD_SHOW = 121,
PRG32_ABI_FN_COUNT = 122
};
9 changes: 9 additions & 0 deletions components/prg32/prg32_abi_exports.c
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,15 @@ static const prg32_any_fn_t g_prg32_cart_abi_exports[] = {
(prg32_any_fn_t)prg32_sprite_anim_update,
(prg32_any_fn_t)prg32_sprite_anim_draw,
(prg32_any_fn_t)prg32_score_submit,
(prg32_any_fn_t)prg32_sprite_draw_24x24,
(prg32_any_fn_t)prg32_score_player_get,
(prg32_any_fn_t)prg32_score_player_set,
(prg32_any_fn_t)prg32_score_player_prompt,
(prg32_any_fn_t)prg32_score_submit_current_player,
(prg32_any_fn_t)prg32_score_sync_remote,
(prg32_any_fn_t)prg32_score_count,
(prg32_any_fn_t)prg32_score_get,
(prg32_any_fn_t)prg32_scoreboard_show,
};

void prg32_abi_exports_keep(void) {
Expand Down
9 changes: 9 additions & 0 deletions components/prg32/prg32_abi_table.c
Original file line number Diff line number Diff line change
Expand Up @@ -129,5 +129,14 @@ const prg32_abi_table_t prg32_abi_table = {
[PRG32_ABI_FN_PRG32_SPRITE_ANIM_UPDATE] = (const void *)prg32_sprite_anim_update,
[PRG32_ABI_FN_PRG32_SPRITE_ANIM_DRAW] = (const void *)prg32_sprite_anim_draw,
[PRG32_ABI_FN_PRG32_SCORE_SUBMIT] = (const void *)prg32_score_submit,
[PRG32_ABI_FN_PRG32_SPRITE_DRAW_24X24] = (const void *)prg32_sprite_draw_24x24,
[PRG32_ABI_FN_PRG32_SCORE_PLAYER_GET] = (const void *)prg32_score_player_get,
[PRG32_ABI_FN_PRG32_SCORE_PLAYER_SET] = (const void *)prg32_score_player_set,
[PRG32_ABI_FN_PRG32_SCORE_PLAYER_PROMPT] = (const void *)prg32_score_player_prompt,
[PRG32_ABI_FN_PRG32_SCORE_SUBMIT_CURRENT_PLAYER] = (const void *)prg32_score_submit_current_player,
[PRG32_ABI_FN_PRG32_SCORE_SYNC_REMOTE] = (const void *)prg32_score_sync_remote,
[PRG32_ABI_FN_PRG32_SCORE_COUNT] = (const void *)prg32_score_count,
[PRG32_ABI_FN_PRG32_SCORE_GET] = (const void *)prg32_score_get,
[PRG32_ABI_FN_PRG32_SCOREBOARD_SHOW] = (const void *)prg32_scoreboard_show,
},
};
35 changes: 35 additions & 0 deletions components/prg32/prg32_cart.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,14 @@ uint8_t prg32_cart_exec[PRG32_CART_RAM_SIZE] __attribute__((section(".iram1.data
static const char *const g_cart_labels[PRG32_CART_SLOT_COUNT] = {
"cart0",
"cart1",
"cart2",
"cart3",
};
static const uint8_t g_cart_subtypes[PRG32_CART_SLOT_COUNT] = {
0x40,
0x41,
0x42,
0x43,
};

static const esp_partition_t *g_cart_partitions[PRG32_CART_SLOT_COUNT];
Expand Down Expand Up @@ -613,6 +617,37 @@ int prg32_cart_store_slot(uint8_t slot, const void *image, size_t image_size) {
return 0;
}

int prg32_cart_erase_slot(uint8_t slot) {
if (slot >= PRG32_CART_SLOT_COUNT) {
set_error("invalid cartridge slot");
return -1;
}
const esp_partition_t *part = cart_partition_by_slot(slot);
if (!part) {
set_errorf("%s partition not found", slot_name(slot));
return -1;
}
if (lock_cart() != 0) {
set_error("failed to lock cartridge storage");
return -1;
}
esp_err_t err = esp_partition_erase_range(part, 0, part->size);
if (err == ESP_OK && g_current_slot == slot) {
g_stored = false;
}
unlock_cart();
if (err != ESP_OK) {
set_errorf("failed to erase cartridge slot: %s", esp_err_to_name(err));
return -1;
}
if (prg32_cart_default_slot() == (int)slot) {
prg32_cart_set_default_slot(-1);
}
set_error("ok");
ESP_LOGI(TAG, "cart erase: done %s", slot_name(slot));
return 0;
}

size_t prg32_cart_slot_size(uint8_t slot) {
const esp_partition_t *part = cart_partition_by_slot(slot);
return part ? part->size : 0;
Expand Down
Loading
Loading