1
0
mirror of https://github.com/catseye/SixtyPical.git synced 2024-06-06 15:29:30 +00:00

First cut at support for targetting the Atari 2600.

This commit is contained in:
Chris Pressey 2018-03-28 14:20:53 +01:00
parent bb0e7aa992
commit 0093c7b7d9
7 changed files with 551 additions and 6 deletions

View File

@ -8,6 +8,8 @@ History of SixtyPical
be used in most places where literal values can be used.
* Specifying multiple SixtyPical source files will produce a single
compiled result from their combination.
* Rudimentary support for Atari 2600 prelude in a 4K cartridge image,
and start of an example program in `eg/atari2600` directory.
0.14
----

View File

@ -81,6 +81,12 @@ def process_input_files(filenames, options):
start_addr = 0x1001
prelude = [0x0b, 0x10, 0xc9, 0x07, 0x9e, 0x34,
0x31, 0x30, 0x39, 0x00, 0x00, 0x00]
elif options.prelude == 'atari2600':
output_format = 'crtbb'
start_addr = 0xf000
prelude = [0x78, 0xd8, 0xa2, 0xff, 0x9a, 0xa9,
0x00,0x95, 0x00, 0xca, 0xd0, 0xfb]
elif options.prelude:
raise NotImplementedError("Unknown prelude: {}".format(options.prelude))
@ -94,6 +100,14 @@ def process_input_files(filenames, options):
emitter.emit(Byte(byte))
compiler = Compiler(emitter)
compiler.compile_program(program)
# If we are outputting a cartridge with boot and BRK address
# at the end, pad to ROM size minus 4 bytes, and emit addresses.
if output_format == 'crtbb':
emitter.pad_to_size(4096 - 4)
emitter.emit(Word(start_addr))
emitter.emit(Word(start_addr))
if options.debug:
pprint(emitter.accum)
else:
@ -119,15 +133,15 @@ if __name__ == '__main__':
)
argparser.add_argument(
"--output-format", type=str, default='prg',
help="Executable format to produce. Options are: prg (.PRG file "
"for Commodore 8-bit). Default: prg."
help="Executable format to produce. Options are: prg, crtbb. "
"Default: prg."
)
argparser.add_argument(
"--prelude", type=str,
help="Insert a snippet before the compiled program "
"so that it can be LOADed and RUN on a certain platforms. "
help="Insert a snippet of code before the compiled program so that "
"it can be booted automatically on a particular platform. "
"Also sets the origin and format. "
"Options are: c64 or vic20."
"Options are: c64, vic20, atari2600."
)
argparser.add_argument(
"--debug",

View File

@ -38,4 +38,11 @@ In the [vic20](vic20/) directory are programs that run on the
Commodore VIC-20. The directory itself contains some simple demos,
for example [hearts.60p](vic20/hearts.60p).
### atari2600
In the [vic20](vic20/) directory are programs that run on the
Atari 2600 (4K cartridge). The directory itself contains a simple
demo, [atari-2600-example.60p](atari2600/atari-2600-example.60p).
(Doesn't work yet.)
[Ophis]: http://michaelcmartin.github.io/Ophis/

View File

@ -0,0 +1,168 @@
// atari-2600-example.60p - SixtyPical translation of atari-2600-example.oph
byte VSYNC @ $00
byte VBLANK @ $01
byte WSYNC @ $02
byte NUSIZ0 @ $04
byte NUSIZ1 @ $05
byte COLUPF @ $08
byte COLUBK @ $09
byte PF0 @ $0D
byte PF1 @ $0E
byte PF2 @ $0F
byte SWCHA @ $280
byte INTIM @ $284
byte TIM64T @ $296
byte CTRLPF @ $0A
byte COLUP0 @ $06
byte COLUP1 @ $07
byte GP0 @ $1B
byte GP1 @ $1C
byte HMOVE @ $2a
byte RESP0 @ $10
byte RESP1 @ $11
byte colour @ $80
byte luminosity @ $81
byte joystick_delay @ $82
byte table[8] image_data : "ZZZZUUUU"
// %01111110
// %10000001
// %10011001
// %10100101
// %10000001
// %10100101
// %10000001
// %01111110
define vertical_blank routine
outputs VSYNC, WSYNC, TIM64T
trashes a, x, z, n
{
ld x, $00
ld a, $02
st a, WSYNC
st a, WSYNC
st a, WSYNC
st a, VSYNC
st a, WSYNC
st a, WSYNC
ld a, $2C
st a, TIM64T
ld a, $00
st a, WSYNC
st a, VSYNC
}
define display_frame routine
inputs INTIM, image_data
outputs WSYNC, HMOVE, VBLANK, RESP0, GP0, PF0, PF1, PF2, COLUPF, COLUBK
trashes a, x, y, z, n
{
repeat {
ld a, INTIM
} until z
//; (After that loop finishes, we know the accumulator must contain 0.)
st a, WSYNC
st a, HMOVE
st a, VBLANK
//;
//; Wait for $3f (plus one?) scan lines to pass, by waiting for
//; WSYNC that many times.
//;
ld x, $3F
repeat {
st a, WSYNC
dec x
} until z // FIXME orig loop used "bpl _wsync_loop"
st a, WSYNC
//;
//; Delay while the raster scans across the screen. The more
//; we delay here, the more to the right the player will be when
//; we draw it.
//;
//// nop
//// nop
//// nop
//// nop
//// nop
//// nop
//// nop
//// nop
//// nop
//// nop
//// nop
//// nop
//// nop
//// nop
//// nop
//;
//; OK, *now* display the player.
//;
st a, RESP0
//;
//; Loop over the rows of the sprite data, drawing each to the screen
//; over four scan lines.
//;
//; TODO understand this better and describe it!
//;
ld y, $07
for y down to 0 {
ld a, image_data + y
st a, GP0
st a, WSYNC
st a, WSYNC
st a, WSYNC
st a, WSYNC
} // FIXME original was "dec y; bpl _image_loop"
ld a, $00
st a, GP0
//;
//; Turn off screen display and clear display registers.
//;
ld a, $02
st a, WSYNC
st a, VBLANK
ld a, $00
st a, PF0
st a, PF1
st a, PF2
st a, COLUPF
st a, COLUBK
}
define main routine
inputs image_data, INTIM
outputs CTRLPF, colour, luminosity, NUSIZ0, VSYNC, WSYNC, TIM64T, HMOVE, VBLANK, RESP0, GP0, PF0, PF1, PF2, COLUPF, COLUBK
trashes a, x, y, z, n
{
ld a, $00
st a, CTRLPF
ld a, $0c
st a, colour
ld a, $0a
st a, luminosity
ld a, $00
st a, NUSIZ0
repeat {
call vertical_blank
call display_frame
// call read_joystick
} forever
}

View File

@ -0,0 +1,340 @@
;
; atari-2600-example.oph
; Skeleton code for an Atari 2600 ROM,
; plus an example of reading the joystick.
; By Chris Pressey, November 2, 2012.
;
; This work is in the public domain. See the file UNLICENSE for more info.
;
; Based on Chris Cracknell's Atari 2600 clock (also in the public domain):
; http://everything2.com/title/An+example+of+Atari+2600+source+code
;
; to build and run in Stella:
; ophis atari-2600-example.oph -o example.bin
; stella example.bin
;
; More useful information can be found in the Stella Programmer's Guide:
; http://alienbill.com/2600/101/docs/stella.html
;
;
; Useful system addresses (TODO: briefly describe each of these.)
;
.alias VSYNC $00
.alias VBLANK $01
.alias WSYNC $02
.alias NUSIZ0 $04
.alias NUSIZ1 $05
.alias COLUPF $08
.alias COLUBK $09
.alias PF0 $0D
.alias PF1 $0E
.alias PF2 $0F
.alias SWCHA $280
.alias INTIM $284
.alias TIM64T $296
.alias CTRLPF $0A
.alias COLUP0 $06
.alias COLUP1 $07
.alias GP0 $1B
.alias GP1 $1C
.alias HMOVE $2a
.alias RESP0 $10
.alias RESP1 $11
;
; Cartridge ROM occupies the top 4K of memory ($F000-$FFFF).
; Thus, typically, the program will occupy all that space too.
;
; Zero-page RAM we can use with impunity starts at $80 and goes
; upward (at least until $99, but probably further.)
;
.alias colour $80
.alias luminosity $81
.alias joystick_delay $82
.org $F000
;
; Standard prelude for Atari 2600 cartridge code.
;
; Get various parts of the machine into a known state:
;
; - Disable interrupts
; - Clear the Decimal flag
; - Initialize the Stack Pointer
; - Zero all bytes in Zero Page memory
;
start:
sei
cld
ldx #$FF
txs
lda #$00
zero_loop:
sta $00, x
dex
bne zero_loop
; and fall through to...
;
; Initialization.
;
; - Clear the Playfield Control register.
; - Set the player (sprite) colour to light green (write to COLUP0.)
; - Set the player (sprite) size/repetion to normal (write to NUSIZ0.)
;
lda #$00
sta CTRLPF
lda #$0c
sta colour
lda #$0a
sta luminosity
lda #$00
sta NUSIZ0
; and fall through to...
;
; Main loop.
;
; A typical main loop consists of:
; - Waiting for the frame to start (vertical blank period)
; - Displaying stuff on the screen (the _display kernel_)
; - Doing any processing you like (reading joysticks, updating program state,
; etc.), as long as you get it all done before the next frame starts!
;
main:
jsr vertical_blank
jsr display_frame
jsr read_joystick
jmp main
;
; Vertical blank routine.
;
; In brief: wait until it is time for the next frame of video.
; TODO: describe this in more detail.
;
vertical_blank:
ldx #$00
lda #$02
sta WSYNC
sta WSYNC
sta WSYNC
sta VSYNC
sta WSYNC
sta WSYNC
lda #$2C
sta TIM64T
lda #$00
sta WSYNC
sta VSYNC
rts
;
; Display kernal.
;
; First, wait until it's time to display the frame.
;
.scope
display_frame:
lda INTIM
bne display_frame
;
; (After that loop finishes, we know the accumulator must contain 0.)
; Wait for the next scanline, zero HMOVE (for some reason; TODO discover
; this), then turn on the screen.
;
sta WSYNC
sta HMOVE
sta VBLANK
;
; Actual work in the display kernal is done here.
;
; This is a pathological approach to writing a display kernal.
; This wouldn't be how you'd do things in a game. So be it.
; One day I may improve it. For now, be happy that it displays
; anything at all!
;
;
; Wait for $3f (plus one?) scan lines to pass, by waiting for
; WSYNC that many times.
;
ldx #$3F
_wsync_loop:
sta WSYNC
dex
bpl _wsync_loop
sta WSYNC
;
; Delay while the raster scans across the screen. The more
; we delay here, the more to the right the player will be when
; we draw it.
;
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
;
; OK, *now* display the player.
;
sta RESP0
;
; Loop over the rows of the sprite data, drawing each to the screen
; over four scan lines.
;
; TODO understand this better and describe it!
;
ldy #$07
_image_loop:
lda image_data, y
sta GP0
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
dey
bpl _image_loop
lda #$00
sta GP0
;
; Turn off screen display and clear display registers.
;
lda #$02
sta WSYNC
sta VBLANK
lda #$00
sta PF0
sta PF1
sta PF2
sta COLUPF
sta COLUBK
rts
.scend
;
; Read the joystick and use it to modify the colour and luminosity
; of the player.
;
.scope
read_joystick:
lda joystick_delay
beq _continue
dec joystick_delay
rts
_continue:
lda SWCHA
and #$f0
cmp #$e0
beq _up
cmp #$d0
beq _down
cmp #$b0
beq _left
cmp #$70
beq _right
jmp _tail
_up:
inc luminosity
jmp _tail
_down:
dec luminosity
jmp _tail
_left:
dec colour
jmp _tail
_right:
inc colour
;jmp _tail
_tail:
lda colour
and #$0f
sta colour
lda luminosity
and #$0f
sta luminosity
lda colour
clc
rol
rol
rol
rol
ora luminosity
sta COLUP0
lda #$06
sta joystick_delay
rts
.scend
;
; Player (sprite) data.
;
; Because we loop over these bytes with the Y register counting *down*,
; this image is stored "upside-down".
;
image_data:
.byte %01111110
.byte %10000001
.byte %10011001
.byte %10100101
.byte %10000001
.byte %10100101
.byte %10000001
.byte %01111110
;
; Standard postlude for Atari 2600 cartridge code.
; Give BRK and boot vectors that point to the start of the code.
;
.advance $FFFC
.word start
.word start

View File

@ -1,6 +1,6 @@
#!/bin/sh
usage="Usage: loadngo.sh (c64|vic20) [--dry-run] <source.60p>"
usage="Usage: loadngo.sh (c64|vic20|atari2600) [--dry-run] <source.60p>"
arch="$1"
shift 1
@ -18,6 +18,9 @@ elif [ "X$arch" = "Xvic20" ]; then
else
emu="xvic"
fi
elif [ "X$arch" = "Xatari2600" ]; then
prelude='atari2600'
emu='stella'
else
echo $usage && exit 1
fi

View File

@ -186,3 +186,14 @@ class Emitter(object):
advance the address for the next label, but don't emit anything."""
self.resolve_label(label)
self.addr += label.length
def size(self):
return sum(emittable.size() for emittable in self.accum)
def pad_to_size(self, size):
self_size = self.size()
if self_size > size:
raise IndexError("Emitter size {} exceeds pad size {}".format(self_size, size))
num_bytes = size - self_size
if num_bytes > 0:
self.accum.extend([Byte(0)] * num_bytes)