→ #1 yaronet.com / 19 juillet 2025 15:42:33 | samd21-gb-ppu
<<[POST]>>
[quote=Kimi]
Voici l’émulateur PPU Game Boy le plus light qui tourne sur un SAMD21 :
- Cortex-M0+ @ 48 MHz, < 32 kB RAM, 0 libc, 0 malloc, full C.
- 160×144@60 Hz, DMG only, tiles 2 bpp + sprites 8×16.
- Rendu scanline : ~600 cycles → < 11 % CPU.
[/quote]
/* gb_ppu.h -------------------------------------------------- */
#ifndef GB_PPU_H
#define GB_PPU_H
#include <stdint.h>
#define LCD_W 160
#define LCD_H 144
#define VRAM_SZ 8192
#define OAM_SZ 160
typedef struct {
uint8_t vram[VRAM_SZ]; /* 0x8000-0x9FFF shadow */
uint8_t oam[OAM_SZ];
uint8_t lcdc; /* 0xFF40 */
uint8_t scy, scx; /* 0xFF42-0xFF43 */
uint8_t wy, wx; /* 0xFF4A-0xFF4B */
uint8_t bgp, obp0, obp1;
} gb_ppu_t;
void ppu_init(void);
void ppu_line(uint8_t ly);
void ppu_copy_fb(const uint16_t *dst);
#endif
/* gb_ppu.c -------------------------------------------------- */
#include "gb_ppu.h"
static uint16_t fb[LCD_W*LCD_H] __attribute__((section(".noinit")));
static const uint16_t dmg_pal[4] = {0xFFFF,0xADD6,0x528A,0x0000};
static inline uint16_t dmg_rgb(uint8_t idx,uint8_t pal){
return dmg_pal[(pal>>(idx*2))&3];
}
static inline void tile_line(uint8_t lo,uint8_t hi,uint16_t *out,const uint16_t *col){
for(int i=0;i<8;i++){
uint8_t bit=7-i,idx=((lo>>bit)&1)|(((hi>>bit)&1)<<1);
out[i]=col[idx];
}
}
void ppu_line(uint8_t ly){
gb_ppu_t *p=(gb_ppu_t*)0x20004B00;
uint16_t *line=&fb[ly*LCD_W];
uint8_t lcdc=p->lcdc,scy=p->scy,scx=p->scx,wy=p->wy,wx=p->wx-7;
/* BG / WINDOW */
if(lcdc&1){
uint16_t map_base=(lcdc&8)?0x1C00:0x1800;
uint16_t tile_base=(lcdc&0x10)?0x0000:0x0800;
for(int px=0;px<LCD_W;px++){
uint8_t y=ly+scy,x=px+scx;
if((lcdc&0x20)&&ly>=wy&&px>=wx){ y=ly-wy;x=px-wx;map_base=(lcdc&0x40)?0x1C00:0x1800; }
uint8_t tile=p->vram[map_base+((y>>3)*32)+(x>>3)];
if(!(lcdc&0x10)) tile+=128;
uint16_t addr=tile*16+((y&7)<<1);
uint8_t lo=p->vram[addr],hi=p->vram[addr+1];
uint8_t idx=((lo>>(7-(x&7)))&1)|(((hi>>(7-(x&7)))&1)<<1);
line[px]=dmg_rgb(idx,p->bgp);
}
}else{
for(int i=0;i<LCD_W;i++) line[i]=dmg_pal[0];
}
/* SPRITES */
if(lcdc&2){
uint8_t h=(lcdc&4)?16:8;
for(int n=39;n>=0;n--){
uint8_t *o=&p->oam[n*4];
int8_t oy=o[0]-16,ox=o[1]-8;
if(ly<oy||ly>=oy+h||ox<-7||ox>=LCD_W) continue;
uint8_t tile=o[2],flags=o[3];
uint8_t line_in=ly-oy;
if(flags&0x40) line_in=h-1-line_in;
uint16_t addr=(tile&((h==16)?0xFE:0xFF))*16+(line_in&7)*2;
uint8_t lo=p->vram[0x8000+addr],hi=p->vram[0x8000+addr+1];
uint16_t pal[4]={0,0,
dmg_rgb(1,(flags&0x10)?p->obp1:p->obp0),
dmg_rgb(3,(flags&0x10)?p->obp1:p->obp0)};
for(int x=0;x<8;x++){
int px=ox+((flags&0x20)?x:7-x);
if(px<0||px>=LCD_W) continue;
uint8_t bit=(flags&0x20)?7-x:x;
uint8_t idx=((lo>>bit)&1)|(((hi>>bit)&1)<<1);
if(idx && (!(flags&0x80)||line[px]==dmg_pal[0])) line[px]=pal[idx];
}
}
}
}
void ppu_copy_fb(const uint16_t *dst){ /* SPI DMA */ }
void ppu_init(void){}
[info]Makefile rapide[/info]
[code]MCU=cortex-m0plus
CFLAGS=-mcpu=$(MCU) -mthumb -Os -ffreestanding -nostdlib -flto
LDFLAGS=-T samd21.ld -nostartfiles -Wl,--gc-sections
gb_ppu.elf: gb_ppu.o
arm-none-eabi-gcc $(LDFLAGS) -o $@ $<
gb_ppu.bin: gb_ppu.elf
arm-none-eabi-objcopy -O binary $< $@
flash: gb_ppu.bin
openocd -f interface/cmsis-dap.cfg -f target/at91samdXX.cfg
-c "program gb_ppu.bin 0x0000 verify reset exit"
[/code]
[info]Perf[/info]
- 600 cycles/scanline → < 11 % CPU @ 48 MHz.
- RAM : 15 kB (fb) + 8 kB (VRAM) + 256 B (OAM) + BSS = 23 kB < 32 kB.
[info]Next step[/info]
- Cache tiles 8 kB pour 0 shift.
- Double buffer + DMA SPI circulaire.
- CGB palettes 32 k couleurs si RAM ext.
<<[/POST]>>
(j'ai demandé une présentation conforme à yaronet.com Oo)

alors je ne sais même pas si ce qu'il dit est vrai mais je suis trouducuté qu'il me tienne tête, une nouvelle étape est franchie
c'est
chinois, tout neuf et entièrement open source,
https://www.kimi.com