mirror of
https://github.com/hsoft/collapseos.git
synced 2024-11-26 08:38:06 +11:00
Compare commits
3 Commits
fb2117dc2e
...
6acd22492c
Author | SHA1 | Date | |
---|---|---|---|
|
6acd22492c | ||
|
d70c6d3b5f | ||
|
927d5f2392 |
@ -21,7 +21,6 @@ parseHex:
|
|||||||
add a, 10 ; C is clear, map back to 0xA-0xF
|
add a, 10 ; C is clear, map back to 0xA-0xF
|
||||||
ret
|
ret
|
||||||
|
|
||||||
|
|
||||||
; Parse string at (HL) as a decimal value and return value in DE.
|
; Parse string at (HL) as a decimal value and return value in DE.
|
||||||
; Reads as many digits as it can and stop when:
|
; Reads as many digits as it can and stop when:
|
||||||
; 1 - A non-digit character is read
|
; 1 - A non-digit character is read
|
||||||
@ -44,10 +43,10 @@ parseDecimal:
|
|||||||
; During this routine, we switch between HL and its shadow. On one side,
|
; During this routine, we switch between HL and its shadow. On one side,
|
||||||
; we have HL the string pointer, and on the other side, we have HL the
|
; we have HL the string pointer, and on the other side, we have HL the
|
||||||
; numerical result. We also use EXX to preserve BC, saving us a push.
|
; numerical result. We also use EXX to preserve BC, saving us a push.
|
||||||
|
parseDecimalSkip: ; enter here to skip parsing the first digit
|
||||||
exx ; HL as a result
|
exx ; HL as a result
|
||||||
ld h, 0
|
ld h, 0
|
||||||
ld l, a ; load first digit in without multiplying
|
ld l, a ; load first digit in without multiplying
|
||||||
ld b, 0 ; We use B to detect overflow
|
|
||||||
|
|
||||||
.loop:
|
.loop:
|
||||||
exx ; HL as a string pointer
|
exx ; HL as a string pointer
|
||||||
@ -58,26 +57,24 @@ parseDecimal:
|
|||||||
; same as other above
|
; same as other above
|
||||||
add a, 0xff-'9'
|
add a, 0xff-'9'
|
||||||
sub 0xff-9
|
sub 0xff-9
|
||||||
jr c, .end
|
jr c, .end
|
||||||
|
|
||||||
|
ld b, a ; we can now use a for overflow checking
|
||||||
add hl, hl ; x2
|
add hl, hl ; x2
|
||||||
; We do this to detect overflow at each step
|
sbc a, a ; a=0 if no overflow, a=0xFF otherwise
|
||||||
rl b
|
|
||||||
ld d, h
|
ld d, h
|
||||||
ld e, l ; de is x2
|
ld e, l ; de is x2
|
||||||
add hl, hl ; x4
|
add hl, hl ; x4
|
||||||
rl b
|
rla
|
||||||
add hl, hl ; x8
|
add hl, hl ; x8
|
||||||
rl b
|
rla
|
||||||
add hl, de ; x10
|
add hl, de ; x10
|
||||||
rl b
|
rla
|
||||||
ld d, 0
|
ld d, a ; a is zero unless there's an overflow
|
||||||
ld e, a
|
ld e, b
|
||||||
add hl, de
|
add hl, de
|
||||||
rl b
|
adc a, a ; same as rla except affects Z
|
||||||
; Did we oveflow?
|
; Did we oveflow?
|
||||||
xor a
|
|
||||||
or b
|
|
||||||
jr z, .loop ; No? continue
|
jr z, .loop ; No? continue
|
||||||
; error, NZ already set
|
; error, NZ already set
|
||||||
exx ; HL is now string pointer, restore BC
|
exx ; HL is now string pointer, restore BC
|
||||||
@ -106,33 +103,49 @@ parseDecimalC:
|
|||||||
; Sets Z on success.
|
; Sets Z on success.
|
||||||
parseHexadecimal:
|
parseHexadecimal:
|
||||||
ld a, (hl)
|
ld a, (hl)
|
||||||
call parseHex
|
call parseHex ; before "ret c" is "sub 0xfa" in parseHex
|
||||||
jp c, unsetZ ; we need at least one char
|
; so carry implies not zero
|
||||||
|
ret c ; we need at least one char
|
||||||
push bc
|
push bc
|
||||||
ld de, 0
|
ld de, 0
|
||||||
ld b, 0
|
ld b, d
|
||||||
|
ld c, d
|
||||||
|
|
||||||
|
; The idea here is that the 4 hex digits of the result can be represented "bdce",
|
||||||
|
; where each register holds a single digit. Then the result is simply
|
||||||
|
; e = (c << 4) | e, d = (b << 4) | d
|
||||||
|
; However, the actual string may be of any length, so when loading in the most
|
||||||
|
; significant digit, we don't know which digit of the result it actually represents
|
||||||
|
; To solve this, after a digit is loaded into a (and is checked for validity),
|
||||||
|
; all digits are moved along, with e taking the latest digit.
|
||||||
.loop:
|
.loop:
|
||||||
; we push to B to verify overflow
|
dec b
|
||||||
rl e \ rl d \ rl b
|
inc b ; b should be 0, else we've overflowed
|
||||||
rl e \ rl d \ rl b
|
jr nz, .end ; Z already unset if overflow
|
||||||
rl e \ rl d \ rl b
|
ld b, d
|
||||||
rl e \ rl d \ rl b
|
ld d, c
|
||||||
or e
|
ld c, e
|
||||||
ld e, a
|
ld e, a
|
||||||
; did we overflow?
|
inc hl
|
||||||
ld a, b
|
ld a, (hl)
|
||||||
or a
|
call parseHex
|
||||||
jr nz, .end ; overflow, NZ already set
|
jr nc, .loop
|
||||||
; next char
|
ld a, b
|
||||||
inc hl
|
add a, a \ add a, a \ add a, a \ add a, a
|
||||||
ld a, (hl)
|
or d
|
||||||
call parseHex
|
ld d, a
|
||||||
jr nc, .loop
|
|
||||||
cp a ; ensure Z
|
ld a, c
|
||||||
.end:
|
add a, a \ add a, a \ add a, a \ add a, a
|
||||||
|
or e
|
||||||
|
ld e, a
|
||||||
|
xor a ; ensure z
|
||||||
|
|
||||||
|
.end:
|
||||||
pop bc
|
pop bc
|
||||||
ret
|
ret
|
||||||
|
|
||||||
|
|
||||||
; Parse string at (HL) as a binary value (010101) without the "0b" prefix and
|
; Parse string at (HL) as a binary value (010101) without the "0b" prefix and
|
||||||
; return value in E. D is always zero.
|
; return value in E. D is always zero.
|
||||||
; HL is advanced to the character following the last successfully read char.
|
; HL is advanced to the character following the last successfully read char.
|
||||||
@ -144,10 +157,10 @@ parseBinaryLiteral:
|
|||||||
add a, 0xff-'1'
|
add a, 0xff-'1'
|
||||||
sub 0xff-1
|
sub 0xff-1
|
||||||
jr c, .end
|
jr c, .end
|
||||||
rl e
|
rlc e ; sets carry if overflow, and affects Z
|
||||||
|
ret c ; Z unset if carry set, since bit 0 of e must be set
|
||||||
add a, e
|
add a, e
|
||||||
ld e, a
|
ld e, a
|
||||||
jp c, unsetZ ; overflow
|
|
||||||
inc hl
|
inc hl
|
||||||
jr .loop
|
jr .loop
|
||||||
.end:
|
.end:
|
||||||
@ -167,10 +180,13 @@ parseLiteral:
|
|||||||
ld a, (hl)
|
ld a, (hl)
|
||||||
cp 0x27 ; apostrophe
|
cp 0x27 ; apostrophe
|
||||||
jr z, .char
|
jr z, .char
|
||||||
call isDigit
|
|
||||||
ret nz
|
; inline parseDecimalDigit
|
||||||
cp '0'
|
add a, 0xc6 ; maps '0'-'9' onto 0xf6-0xff
|
||||||
jp nz, parseDecimal
|
sub 0xf6 ; maps to 0-9 and carries if not a digit
|
||||||
|
ret c
|
||||||
|
; a already parsed so skip first few instructions of parseDecimal
|
||||||
|
jp nz, parseDecimalSkip
|
||||||
; maybe hex, maybe binary
|
; maybe hex, maybe binary
|
||||||
inc hl
|
inc hl
|
||||||
ld a, (hl)
|
ld a, (hl)
|
||||||
@ -195,14 +211,13 @@ parseLiteral:
|
|||||||
ld e, (hl) ; our result
|
ld e, (hl) ; our result
|
||||||
inc hl
|
inc hl
|
||||||
cp (hl)
|
cp (hl)
|
||||||
jr nz, .charError ; not ending with an apostrophe
|
; advance HL and return if good char
|
||||||
; good char, advance HL and return
|
|
||||||
inc hl
|
inc hl
|
||||||
; Z already set
|
ret z
|
||||||
ret
|
|
||||||
.charError:
|
; Z unset and there's an error
|
||||||
; In all error conditions, HL is advanced by 2. Rewind.
|
; In all error conditions, HL is advanced by 3. Rewind.
|
||||||
dec hl \ dec hl
|
dec hl \ dec hl \ dec hl
|
||||||
; NZ already set
|
; NZ already set
|
||||||
ret
|
ret
|
||||||
|
|
||||||
@ -215,9 +230,9 @@ isLiteralPrefix:
|
|||||||
|
|
||||||
; Returns whether A is a digit
|
; Returns whether A is a digit
|
||||||
isDigit:
|
isDigit:
|
||||||
cp '0'
|
cp '0' ; carry implies not zero for cp
|
||||||
jp c, unsetZ
|
ret c
|
||||||
cp '9'+1
|
cp '9' ; zero unset for a > '9', but set for a='9'
|
||||||
jp nc, unsetZ
|
ret nc
|
||||||
cp a ; ensure Z
|
cp a ; ensure Z
|
||||||
ret
|
ret
|
||||||
|
1
emul/hw/sms/.gitignore
vendored
Normal file
1
emul/hw/sms/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/sms
|
15
emul/hw/sms/Makefile
Normal file
15
emul/hw/sms/Makefile
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
EXTOBJS = ../../emul.o ../../libz80/libz80.o
|
||||||
|
OBJS = sms.o vdp.o
|
||||||
|
TARGET = sms
|
||||||
|
CFLAGS += `pkg-config --cflags xcb`
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: $(TARGET)
|
||||||
|
|
||||||
|
$(TARGET): $(OBJS) $(EXTOBJS)
|
||||||
|
$(CC) `pkg-config --libs xcb` $(OBJS) $(EXTOBJS) -o $@
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -f $(TARGET) $(OBJS)
|
||||||
|
|
194
emul/hw/sms/sms.c
Normal file
194
emul/hw/sms/sms.c
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <xcb/xcb.h>
|
||||||
|
|
||||||
|
#include "../../emul.h"
|
||||||
|
#include "vdp.h"
|
||||||
|
|
||||||
|
#define RAMSTART 0xc000
|
||||||
|
#define VDP_CMD_PORT 0xbf
|
||||||
|
#define VDP_DATA_PORT 0xbe
|
||||||
|
#define MAX_ROMSIZE 0x8000
|
||||||
|
|
||||||
|
static xcb_connection_t *conn;
|
||||||
|
static xcb_screen_t *screen;
|
||||||
|
|
||||||
|
/* graphics contexts */
|
||||||
|
static xcb_gcontext_t fg;
|
||||||
|
/* win */
|
||||||
|
static xcb_drawable_t win;
|
||||||
|
|
||||||
|
// pixels to draw. We draw them in one shot.
|
||||||
|
static xcb_rectangle_t rectangles[VDP_SCREENW*VDP_SCREENH];
|
||||||
|
|
||||||
|
static Machine *m;
|
||||||
|
static VDP vdp;
|
||||||
|
static bool vdp_changed;
|
||||||
|
|
||||||
|
static uint8_t iord_vdp_cmd()
|
||||||
|
{
|
||||||
|
return vdp_cmd_rd(&vdp);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t iord_vdp_data()
|
||||||
|
{
|
||||||
|
return vdp_data_rd(&vdp);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void iowr_vdp_cmd(uint8_t val)
|
||||||
|
{
|
||||||
|
vdp_cmd_wr(&vdp, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void iowr_vdp_data(uint8_t val)
|
||||||
|
{
|
||||||
|
vdp_changed = true;
|
||||||
|
vdp_data_wr(&vdp, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
void create_window()
|
||||||
|
{
|
||||||
|
uint32_t mask;
|
||||||
|
uint32_t values[2];
|
||||||
|
|
||||||
|
/* Create the window */
|
||||||
|
win = xcb_generate_id(conn);
|
||||||
|
mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
|
||||||
|
values[0] = screen->white_pixel;
|
||||||
|
values[1] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_KEY_PRESS |
|
||||||
|
XCB_EVENT_MASK_KEY_RELEASE;
|
||||||
|
xcb_create_window(
|
||||||
|
conn,
|
||||||
|
screen->root_depth,
|
||||||
|
win,
|
||||||
|
screen->root,
|
||||||
|
0, 0,
|
||||||
|
150, 150,
|
||||||
|
10,
|
||||||
|
XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
||||||
|
screen->root_visual,
|
||||||
|
mask, values);
|
||||||
|
|
||||||
|
fg = xcb_generate_id(conn);
|
||||||
|
mask = XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES;
|
||||||
|
values[0] = screen->black_pixel;
|
||||||
|
values[1] = 0;
|
||||||
|
xcb_create_gc(conn, fg, screen->root, mask, values);
|
||||||
|
|
||||||
|
/* Map the window on the screen */
|
||||||
|
xcb_map_window(conn, win);
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw_pixels()
|
||||||
|
{
|
||||||
|
xcb_get_geometry_reply_t *geom;
|
||||||
|
|
||||||
|
geom = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, win), NULL);
|
||||||
|
|
||||||
|
xcb_clear_area(
|
||||||
|
conn, 0, win, 0, 0, geom->width, geom->height);
|
||||||
|
// Figure out inner size to maximize our screen's aspect ratio
|
||||||
|
int psize = geom->height / VDP_SCREENH;
|
||||||
|
if (geom->width / VDP_SCREENW < psize) {
|
||||||
|
// width is the constraint
|
||||||
|
psize = geom->width / VDP_SCREENW;
|
||||||
|
}
|
||||||
|
int innerw = psize * VDP_SCREENW;
|
||||||
|
int innerh = psize * VDP_SCREENH;
|
||||||
|
int innerx = (geom->width - innerw) / 2;
|
||||||
|
int innery = (geom->height - innerh) / 2;
|
||||||
|
int drawcnt = 0;
|
||||||
|
for (int i=0; i<VDP_SCREENW; i++) {
|
||||||
|
for (int j=0; j<VDP_SCREENH; j++) {
|
||||||
|
if (vdp_pixel(&vdp, i, j)) {
|
||||||
|
int x = innerx + (i*psize);
|
||||||
|
int y = innery + (j*psize);
|
||||||
|
rectangles[drawcnt].x = x;
|
||||||
|
rectangles[drawcnt].y = y;
|
||||||
|
rectangles[drawcnt].height = psize;
|
||||||
|
rectangles[drawcnt].width = psize;
|
||||||
|
drawcnt++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (drawcnt) {
|
||||||
|
xcb_poly_fill_rectangle(
|
||||||
|
conn, win, fg, drawcnt, rectangles);
|
||||||
|
}
|
||||||
|
vdp_changed = false;
|
||||||
|
xcb_flush(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void event_loop()
|
||||||
|
{
|
||||||
|
while (1) {
|
||||||
|
emul_step();
|
||||||
|
if (vdp_changed) {
|
||||||
|
// To avoid overdrawing, we'll let the CPU run a bit to finish its
|
||||||
|
// drawing operation.
|
||||||
|
emul_steps(100);
|
||||||
|
draw_pixels();
|
||||||
|
}
|
||||||
|
xcb_generic_event_t *e = xcb_poll_for_event(conn);
|
||||||
|
if (!e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
switch (e->response_type & ~0x80) {
|
||||||
|
/* ESC to exit */
|
||||||
|
case XCB_KEY_RELEASE:
|
||||||
|
case XCB_KEY_PRESS: {
|
||||||
|
xcb_key_press_event_t *ev = (xcb_key_press_event_t *)e;
|
||||||
|
if (ev->detail == 0x09) return;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case XCB_EXPOSE: {
|
||||||
|
draw_pixels();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
if (argc != 2) {
|
||||||
|
fprintf(stderr, "Usage: ./sms /path/to/rom\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
FILE *fp = fopen(argv[1], "r");
|
||||||
|
if (fp == NULL) {
|
||||||
|
fprintf(stderr, "Can't open %s\n", argv[1]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
m = emul_init();
|
||||||
|
m->ramstart = RAMSTART;
|
||||||
|
int i = 0;
|
||||||
|
int c;
|
||||||
|
while ((c = fgetc(fp)) != EOF && i < MAX_ROMSIZE) {
|
||||||
|
m->mem[i++] = c & 0xff;
|
||||||
|
}
|
||||||
|
pclose(fp);
|
||||||
|
if (c != EOF) {
|
||||||
|
fprintf(stderr, "ROM image too large.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
vdp_init(&vdp);
|
||||||
|
vdp_changed = false;
|
||||||
|
m->iord[VDP_CMD_PORT] = iord_vdp_cmd;
|
||||||
|
m->iord[VDP_DATA_PORT] = iord_vdp_data;
|
||||||
|
m->iowr[VDP_CMD_PORT] = iowr_vdp_cmd;
|
||||||
|
m->iowr[VDP_DATA_PORT] = iowr_vdp_data;
|
||||||
|
conn = xcb_connect(NULL, NULL);
|
||||||
|
screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
|
||||||
|
create_window();
|
||||||
|
draw_pixels();
|
||||||
|
event_loop();
|
||||||
|
emul_printdebug();
|
||||||
|
return 0;
|
||||||
|
}
|
77
emul/hw/sms/vdp.c
Normal file
77
emul/hw/sms/vdp.c
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include "vdp.h"
|
||||||
|
|
||||||
|
void vdp_init(VDP *vdp)
|
||||||
|
{
|
||||||
|
memset(vdp->vram, 0, VDP_VRAM_SIZE);
|
||||||
|
memset(vdp->regs, 0, 0x10);
|
||||||
|
vdp->has_cmdlsb = false;
|
||||||
|
vdp->curaddr = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t vdp_cmd_rd(VDP *vdp)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void vdp_cmd_wr(VDP *vdp, uint8_t val)
|
||||||
|
{
|
||||||
|
if (!vdp->has_cmdlsb) {
|
||||||
|
vdp->cmdlsb = val;
|
||||||
|
vdp->has_cmdlsb = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vdp->has_cmdlsb = false;
|
||||||
|
if ((val & 0xc0) == 0x80) {
|
||||||
|
// set register
|
||||||
|
vdp->regs[val&0xf] = vdp->cmdlsb;
|
||||||
|
} else if ((val & 0xc0) == 0xc0) {
|
||||||
|
// palette RAM
|
||||||
|
vdp->curaddr = 0x4000 + (vdp->cmdlsb&0x1f);
|
||||||
|
} else {
|
||||||
|
// VRAM
|
||||||
|
vdp->curaddr = ((val&0x3f) << 8) + vdp->cmdlsb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t vdp_data_rd(VDP *vdp)
|
||||||
|
{
|
||||||
|
uint8_t res = vdp->vram[vdp->curaddr];
|
||||||
|
if (vdp->curaddr < VDP_VRAM_SIZE) {
|
||||||
|
vdp->curaddr++;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void vdp_data_wr(VDP *vdp, uint8_t val)
|
||||||
|
{
|
||||||
|
vdp->vram[vdp->curaddr] = val;
|
||||||
|
if (vdp->curaddr < VDP_VRAM_SIZE) {
|
||||||
|
vdp->curaddr++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t vdp_pixel(VDP *vdp, uint16_t x, uint16_t y)
|
||||||
|
{
|
||||||
|
if (x >= VDP_SCREENW) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (y >= VDP_SCREENH) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// name table offset
|
||||||
|
uint16_t offset = 0x3800 + ((y/8) << 6) + ((x/8) << 1);
|
||||||
|
uint16_t tableval = vdp->vram[offset] + (vdp->vram[offset+1] << 8);
|
||||||
|
uint16_t tilenum = tableval & 0x1ff;
|
||||||
|
// tile offset this time. Each tile is 0x20 bytes long.
|
||||||
|
offset = tilenum * 0x20;
|
||||||
|
// Each 4 byte is a row. Find row first.
|
||||||
|
offset += ((y%8) * 4);
|
||||||
|
uint8_t bitnum = 7 - (x%8);
|
||||||
|
// Now, let's compose the result by pushing the right bit of our 4 bytes
|
||||||
|
// into our result.
|
||||||
|
return ((vdp->vram[offset] >> bitnum) & 1) + \
|
||||||
|
(((vdp->vram[offset+1] >> bitnum) & 1) << 1) + \
|
||||||
|
(((vdp->vram[offset+2] >> bitnum) & 1) << 2) + \
|
||||||
|
(((vdp->vram[offset+3] >> bitnum) & 1) << 3);
|
||||||
|
}
|
26
emul/hw/sms/vdp.h
Normal file
26
emul/hw/sms/vdp.h
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#define VDP_VRAM_SIZE 0x4020
|
||||||
|
#define VDP_SCREENW (32*8)
|
||||||
|
#define VDP_SCREENH (24*8)
|
||||||
|
// Offset of the name table
|
||||||
|
#define VDP_NTABLE_OFFSET 0x3800
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
// the last 0x20 is palette RAM
|
||||||
|
uint8_t vram[VDP_VRAM_SIZE];
|
||||||
|
uint8_t regs[0x10];
|
||||||
|
uint8_t cmdlsb;
|
||||||
|
bool has_cmdlsb;
|
||||||
|
uint16_t curaddr;
|
||||||
|
} VDP;
|
||||||
|
|
||||||
|
void vdp_init(VDP *vdp);
|
||||||
|
uint8_t vdp_cmd_rd(VDP *vdp);
|
||||||
|
void vdp_cmd_wr(VDP *vdp, uint8_t val);
|
||||||
|
uint8_t vdp_data_rd(VDP *vdp);
|
||||||
|
void vdp_data_wr(VDP *vdp, uint8_t val);
|
||||||
|
// result is a RGB value
|
||||||
|
uint8_t vdp_pixel(VDP *vdp, uint16_t x, uint16_t y);
|
@ -38,6 +38,7 @@ vdpInit:
|
|||||||
ld c, VDP_CTLPORT
|
ld c, VDP_CTLPORT
|
||||||
otir
|
otir
|
||||||
|
|
||||||
|
; Blank VRAM
|
||||||
xor a
|
xor a
|
||||||
out (VDP_CTLPORT), a
|
out (VDP_CTLPORT), a
|
||||||
ld a, 0x40
|
ld a, 0x40
|
||||||
@ -51,15 +52,17 @@ vdpInit:
|
|||||||
or c
|
or c
|
||||||
jr nz, .loop1
|
jr nz, .loop1
|
||||||
|
|
||||||
|
; Set palettes
|
||||||
xor a
|
xor a
|
||||||
out (VDP_CTLPORT), a
|
out (VDP_CTLPORT), a
|
||||||
ld a, 0xc0
|
ld a, 0xc0
|
||||||
out (VDP_CTLPORT), a
|
out (VDP_CTLPORT), a
|
||||||
ld hl, vdpPaletteData
|
xor a ; palette 0: black
|
||||||
ld b, vdpPaletteDataEnd-vdpPaletteData
|
out (VDP_DATAPORT), a
|
||||||
ld c, VDP_DATAPORT
|
ld a, 0x3f ; palette 1: white
|
||||||
otir
|
out (VDP_DATAPORT), a
|
||||||
|
|
||||||
|
; Define tiles
|
||||||
xor a
|
xor a
|
||||||
out (VDP_CTLPORT), a
|
out (VDP_CTLPORT), a
|
||||||
ld a, 0x40
|
ld a, 0x40
|
||||||
@ -97,6 +100,7 @@ vdpInit:
|
|||||||
dec c
|
dec c
|
||||||
jr nz, .loop2
|
jr nz, .loop2
|
||||||
|
|
||||||
|
; Bit 7 = ?, Bit 6 = display enabled
|
||||||
ld a, 0b11000000
|
ld a, 0b11000000
|
||||||
out (VDP_CTLPORT), a
|
out (VDP_CTLPORT), a
|
||||||
ld a, 0x81
|
ld a, 0x81
|
||||||
@ -129,7 +133,7 @@ vdpSpitC:
|
|||||||
; two bits
|
; two bits
|
||||||
out (VDP_CTLPORT), a
|
out (VDP_CTLPORT), a
|
||||||
ld a, b ; 3 low bits set
|
ld a, b ; 3 low bits set
|
||||||
or 0x78
|
or 0x78 ; 01 header + 0x3800
|
||||||
out (VDP_CTLPORT), a
|
out (VDP_CTLPORT), a
|
||||||
pop bc
|
pop bc
|
||||||
|
|
||||||
@ -279,11 +283,16 @@ vdpConv:
|
|||||||
ld a, 0x5e
|
ld a, 0x5e
|
||||||
ret
|
ret
|
||||||
|
|
||||||
vdpPaletteData:
|
|
||||||
.db 0x00,0x3f
|
|
||||||
vdpPaletteDataEnd:
|
|
||||||
|
|
||||||
; VDP initialisation data
|
; VDP initialisation data
|
||||||
vdpInitData:
|
vdpInitData:
|
||||||
.db 0x04,0x80,0x00,0x81,0xff,0x82,0xff,0x85,0xff,0x86,0xff,0x87,0x00,0x88,0x00,0x89,0xff,0x8a
|
; 0x8x == set register X
|
||||||
|
.db 0b00000100, 0x80 ; Bit 2: Select mode 4
|
||||||
|
.db 0b00000000, 0x81
|
||||||
|
.db 0b11111111, 0x82 ; Name table: 0x3800
|
||||||
|
.db 0b11111111, 0x85 ; Sprite table: 0x3f00
|
||||||
|
.db 0b11111111, 0x86 ; sprite use tiles from 0x2000
|
||||||
|
.db 0b11111111, 0x87 ; Border uses palette 0xf
|
||||||
|
.db 0b00000000, 0x88 ; BG X scroll
|
||||||
|
.db 0b00000000, 0x89 ; BG Y scroll
|
||||||
|
.db 0b11111111, 0x8a ; Line counter (why have this?)
|
||||||
vdpInitDataEnd:
|
vdpInitDataEnd:
|
||||||
|
Loading…
Reference in New Issue
Block a user