1
0
mirror of https://github.com/hsoft/collapseos.git synced 2024-11-02 20:20:55 +11:00

Compare commits

..

No commits in common. "6acd22492c923e0ab3f5f30af3c1fc2fc484c196" and "fb2117dc2eed64a6e6b514c1d077a2940c4d5c5c" have entirely different histories.

7 changed files with 61 additions and 398 deletions

View File

@ -21,6 +21,7 @@ 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
@ -43,10 +44,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
@ -59,22 +60,24 @@ parseDecimalSkip: ; enter here to skip parsing the first digit
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
sbc a, a ; a=0 if no overflow, a=0xFF otherwise ; We do this to detect overflow at each step
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
rla rl b
add hl, hl ; x8 add hl, hl ; x8
rla rl b
add hl, de ; x10 add hl, de ; x10
rla rl b
ld d, a ; a is zero unless there's an overflow ld d, 0
ld e, b ld e, a
add hl, de add hl, de
adc a, a ; same as rla except affects Z rl b
; 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
@ -103,49 +106,33 @@ parseDecimalC:
; Sets Z on success. ; Sets Z on success.
parseHexadecimal: parseHexadecimal:
ld a, (hl) ld a, (hl)
call parseHex ; before "ret c" is "sub 0xfa" in parseHex call parseHex
; so carry implies not zero jp c, unsetZ ; we need at least one char
ret c ; we need at least one char
push bc push bc
ld de, 0 ld de, 0
ld b, d ld b, 0
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:
dec b ; we push to B to verify overflow
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 rl e \ rl d \ rl b
ld c, e or e
ld e, a ld e, a
; did we overflow?
ld a, b
or a
jr nz, .end ; overflow, NZ already set
; next char
inc hl inc hl
ld a, (hl) ld a, (hl)
call parseHex call parseHex
jr nc, .loop jr nc, .loop
ld a, b cp a ; ensure Z
add a, a \ add a, a \ add a, a \ add a, a
or d
ld d, a
ld a, c
add a, a \ add a, a \ add a, a \ add a, a
or e
ld e, a
xor a ; ensure z
.end: .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.
@ -157,10 +144,10 @@ parseBinaryLiteral:
add a, 0xff-'1' add a, 0xff-'1'
sub 0xff-1 sub 0xff-1
jr c, .end jr c, .end
rlc e ; sets carry if overflow, and affects Z rl e
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:
@ -180,13 +167,10 @@ parseLiteral:
ld a, (hl) ld a, (hl)
cp 0x27 ; apostrophe cp 0x27 ; apostrophe
jr z, .char jr z, .char
call isDigit
; inline parseDecimalDigit ret nz
add a, 0xc6 ; maps '0'-'9' onto 0xf6-0xff cp '0'
sub 0xf6 ; maps to 0-9 and carries if not a digit jp nz, parseDecimal
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)
@ -211,13 +195,14 @@ parseLiteral:
ld e, (hl) ; our result ld e, (hl) ; our result
inc hl inc hl
cp (hl) cp (hl)
; advance HL and return if good char jr nz, .charError ; not ending with an apostrophe
; good char, advance HL and return
inc hl inc hl
ret z ; Z already set
ret
; Z unset and there's an error .charError:
; In all error conditions, HL is advanced by 3. Rewind. ; In all error conditions, HL is advanced by 2. Rewind.
dec hl \ dec hl \ dec hl dec hl \ dec hl
; NZ already set ; NZ already set
ret ret
@ -230,9 +215,9 @@ isLiteralPrefix:
; Returns whether A is a digit ; Returns whether A is a digit
isDigit: isDigit:
cp '0' ; carry implies not zero for cp cp '0'
ret c jp c, unsetZ
cp '9' ; zero unset for a > '9', but set for a='9' cp '9'+1
ret nc jp nc, unsetZ
cp a ; ensure Z cp a ; ensure Z
ret ret

View File

@ -1 +0,0 @@
/sms

View File

@ -1,15 +0,0 @@
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)

View File

@ -1,194 +0,0 @@
#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;
}

View File

@ -1,77 +0,0 @@
#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);
}

View File

@ -1,26 +0,0 @@
#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);

View File

@ -38,7 +38,6 @@ 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
@ -52,17 +51,15 @@ 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
xor a ; palette 0: black ld hl, vdpPaletteData
out (VDP_DATAPORT), a ld b, vdpPaletteDataEnd-vdpPaletteData
ld a, 0x3f ; palette 1: white ld c, VDP_DATAPORT
out (VDP_DATAPORT), a otir
; Define tiles
xor a xor a
out (VDP_CTLPORT), a out (VDP_CTLPORT), a
ld a, 0x40 ld a, 0x40
@ -100,7 +97,6 @@ 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
@ -133,7 +129,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 ; 01 header + 0x3800 or 0x78
out (VDP_CTLPORT), a out (VDP_CTLPORT), a
pop bc pop bc
@ -283,16 +279,11 @@ vdpConv:
ld a, 0x5e ld a, 0x5e
ret ret
vdpPaletteData:
.db 0x00,0x3f
vdpPaletteDataEnd:
; VDP initialisation data ; VDP initialisation data
vdpInitData: vdpInitData:
; 0x8x == set register X .db 0x04,0x80,0x00,0x81,0xff,0x82,0xff,0x85,0xff,0x86,0xff,0x87,0x00,0x88,0x00,0x89,0xff,0x8a
.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: