First cut at support for targetting the Atari 2600.
Chris Pressey
4 years ago
7 | 7 | be used in most places where literal values can be used. |
8 | 8 | * Specifying multiple SixtyPical source files will produce a single |
9 | 9 | compiled result from their combination. |
10 | * Rudimentary support for Atari 2600 prelude in a 4K cartridge image, | |
11 | and start of an example program in `eg/atari2600` directory. | |
10 | 12 | |
11 | 13 | 0.14 |
12 | 14 | ---- |
80 | 80 | start_addr = 0x1001 |
81 | 81 | prelude = [0x0b, 0x10, 0xc9, 0x07, 0x9e, 0x34, |
82 | 82 | 0x31, 0x30, 0x39, 0x00, 0x00, 0x00] |
83 | elif options.prelude == 'atari2600': | |
84 | output_format = 'crtbb' | |
85 | start_addr = 0xf000 | |
86 | prelude = [0x78, 0xd8, 0xa2, 0xff, 0x9a, 0xa9, | |
87 | 0x00,0x95, 0x00, 0xca, 0xd0, 0xfb] | |
88 | ||
83 | 89 | elif options.prelude: |
84 | 90 | raise NotImplementedError("Unknown prelude: {}".format(options.prelude)) |
85 | 91 | |
93 | 99 | emitter.emit(Byte(byte)) |
94 | 100 | compiler = Compiler(emitter) |
95 | 101 | compiler.compile_program(program) |
102 | ||
103 | # If we are outputting a cartridge with boot and BRK address | |
104 | # at the end, pad to ROM size minus 4 bytes, and emit addresses. | |
105 | if output_format == 'crtbb': | |
106 | emitter.pad_to_size(4096 - 4) | |
107 | emitter.emit(Word(start_addr)) | |
108 | emitter.emit(Word(start_addr)) | |
109 | ||
96 | 110 | if options.debug: |
97 | 111 | pprint(emitter.accum) |
98 | 112 | else: |
118 | 132 | ) |
119 | 133 | argparser.add_argument( |
120 | 134 | "--output-format", type=str, default='prg', |
121 | help="Executable format to produce. Options are: prg (.PRG file " | |
122 | "for Commodore 8-bit). Default: prg." | |
135 | help="Executable format to produce. Options are: prg, crtbb. " | |
136 | "Default: prg." | |
123 | 137 | ) |
124 | 138 | argparser.add_argument( |
125 | 139 | "--prelude", type=str, |
126 | help="Insert a snippet before the compiled program " | |
127 | "so that it can be LOADed and RUN on a certain platforms. " | |
140 | help="Insert a snippet of code before the compiled program so that " | |
141 | "it can be booted automatically on a particular platform. " | |
128 | 142 | "Also sets the origin and format. " |
129 | "Options are: c64 or vic20." | |
143 | "Options are: c64, vic20, atari2600." | |
130 | 144 | ) |
131 | 145 | argparser.add_argument( |
132 | 146 | "--debug", |
37 | 37 | Commodore VIC-20. The directory itself contains some simple demos, |
38 | 38 | for example [hearts.60p](vic20/hearts.60p). |
39 | 39 | |
40 | ### atari2600 | |
41 | ||
42 | In the [vic20](vic20/) directory are programs that run on the | |
43 | Atari 2600 (4K cartridge). The directory itself contains a simple | |
44 | demo, [atari-2600-example.60p](atari2600/atari-2600-example.60p). | |
45 | (Doesn't work yet.) | |
46 | ||
40 | 47 | [Ophis]: http://michaelcmartin.github.io/Ophis/ |
0 | // atari-2600-example.60p - SixtyPical translation of atari-2600-example.oph | |
1 | ||
2 | byte VSYNC @ $00 | |
3 | byte VBLANK @ $01 | |
4 | byte WSYNC @ $02 | |
5 | byte NUSIZ0 @ $04 | |
6 | byte NUSIZ1 @ $05 | |
7 | byte COLUPF @ $08 | |
8 | byte COLUBK @ $09 | |
9 | byte PF0 @ $0D | |
10 | byte PF1 @ $0E | |
11 | byte PF2 @ $0F | |
12 | byte SWCHA @ $280 | |
13 | byte INTIM @ $284 | |
14 | byte TIM64T @ $296 | |
15 | byte CTRLPF @ $0A | |
16 | byte COLUP0 @ $06 | |
17 | byte COLUP1 @ $07 | |
18 | byte GP0 @ $1B | |
19 | byte GP1 @ $1C | |
20 | byte HMOVE @ $2a | |
21 | byte RESP0 @ $10 | |
22 | byte RESP1 @ $11 | |
23 | ||
24 | byte colour @ $80 | |
25 | byte luminosity @ $81 | |
26 | byte joystick_delay @ $82 | |
27 | ||
28 | byte table[8] image_data : "ZZZZUUUU" | |
29 | // %01111110 | |
30 | // %10000001 | |
31 | // %10011001 | |
32 | // %10100101 | |
33 | // %10000001 | |
34 | // %10100101 | |
35 | // %10000001 | |
36 | // %01111110 | |
37 | ||
38 | ||
39 | define vertical_blank routine | |
40 | outputs VSYNC, WSYNC, TIM64T | |
41 | trashes a, x, z, n | |
42 | { | |
43 | ld x, $00 | |
44 | ld a, $02 | |
45 | st a, WSYNC | |
46 | st a, WSYNC | |
47 | st a, WSYNC | |
48 | st a, VSYNC | |
49 | st a, WSYNC | |
50 | st a, WSYNC | |
51 | ld a, $2C | |
52 | st a, TIM64T | |
53 | ld a, $00 | |
54 | st a, WSYNC | |
55 | st a, VSYNC | |
56 | } | |
57 | ||
58 | define display_frame routine | |
59 | inputs INTIM, image_data | |
60 | outputs WSYNC, HMOVE, VBLANK, RESP0, GP0, PF0, PF1, PF2, COLUPF, COLUBK | |
61 | trashes a, x, y, z, n | |
62 | { | |
63 | repeat { | |
64 | ld a, INTIM | |
65 | } until z | |
66 | ||
67 | //; (After that loop finishes, we know the accumulator must contain 0.) | |
68 | ||
69 | st a, WSYNC | |
70 | st a, HMOVE | |
71 | st a, VBLANK | |
72 | ||
73 | //; | |
74 | //; Wait for $3f (plus one?) scan lines to pass, by waiting for | |
75 | //; WSYNC that many times. | |
76 | //; | |
77 | ||
78 | ld x, $3F | |
79 | repeat { | |
80 | st a, WSYNC | |
81 | dec x | |
82 | } until z // FIXME orig loop used "bpl _wsync_loop" | |
83 | st a, WSYNC | |
84 | ||
85 | //; | |
86 | //; Delay while the raster scans across the screen. The more | |
87 | //; we delay here, the more to the right the player will be when | |
88 | //; we draw it. | |
89 | //; | |
90 | ||
91 | //// nop | |
92 | //// nop | |
93 | //// nop | |
94 | //// nop | |
95 | //// nop | |
96 | //// nop | |
97 | //// nop | |
98 | //// nop | |
99 | //// nop | |
100 | //// nop | |
101 | //// nop | |
102 | //// nop | |
103 | //// nop | |
104 | //// nop | |
105 | //// nop | |
106 | ||
107 | //; | |
108 | //; OK, *now* display the player. | |
109 | //; | |
110 | ||
111 | st a, RESP0 | |
112 | ||
113 | //; | |
114 | //; Loop over the rows of the sprite data, drawing each to the screen | |
115 | //; over four scan lines. | |
116 | //; | |
117 | //; TODO understand this better and describe it! | |
118 | //; | |
119 | ||
120 | ld y, $07 | |
121 | for y down to 0 { | |
122 | ld a, image_data + y | |
123 | st a, GP0 | |
124 | ||
125 | st a, WSYNC | |
126 | st a, WSYNC | |
127 | st a, WSYNC | |
128 | st a, WSYNC | |
129 | } // FIXME original was "dec y; bpl _image_loop" | |
130 | ||
131 | ld a, $00 | |
132 | st a, GP0 | |
133 | ||
134 | //; | |
135 | //; Turn off screen display and clear display registers. | |
136 | //; | |
137 | ||
138 | ld a, $02 | |
139 | st a, WSYNC | |
140 | st a, VBLANK | |
141 | ld a, $00 | |
142 | st a, PF0 | |
143 | st a, PF1 | |
144 | st a, PF2 | |
145 | st a, COLUPF | |
146 | st a, COLUBK | |
147 | } | |
148 | ||
149 | define main routine | |
150 | inputs image_data, INTIM | |
151 | outputs CTRLPF, colour, luminosity, NUSIZ0, VSYNC, WSYNC, TIM64T, HMOVE, VBLANK, RESP0, GP0, PF0, PF1, PF2, COLUPF, COLUBK | |
152 | trashes a, x, y, z, n | |
153 | { | |
154 | ld a, $00 | |
155 | st a, CTRLPF | |
156 | ld a, $0c | |
157 | st a, colour | |
158 | ld a, $0a | |
159 | st a, luminosity | |
160 | ld a, $00 | |
161 | st a, NUSIZ0 | |
162 | repeat { | |
163 | call vertical_blank | |
164 | call display_frame | |
165 | // call read_joystick | |
166 | } forever | |
167 | } |
0 | ; | |
1 | ; atari-2600-example.oph | |
2 | ; Skeleton code for an Atari 2600 ROM, | |
3 | ; plus an example of reading the joystick. | |
4 | ; By Chris Pressey, November 2, 2012. | |
5 | ; | |
6 | ; This work is in the public domain. See the file UNLICENSE for more info. | |
7 | ; | |
8 | ; Based on Chris Cracknell's Atari 2600 clock (also in the public domain): | |
9 | ; http://everything2.com/title/An+example+of+Atari+2600+source+code | |
10 | ; | |
11 | ; to build and run in Stella: | |
12 | ; ophis atari-2600-example.oph -o example.bin | |
13 | ; stella example.bin | |
14 | ; | |
15 | ; More useful information can be found in the Stella Programmer's Guide: | |
16 | ; http://alienbill.com/2600/101/docs/stella.html | |
17 | ; | |
18 | ||
19 | ; | |
20 | ; Useful system addresses (TODO: briefly describe each of these.) | |
21 | ; | |
22 | ||
23 | .alias VSYNC $00 | |
24 | .alias VBLANK $01 | |
25 | .alias WSYNC $02 | |
26 | .alias NUSIZ0 $04 | |
27 | .alias NUSIZ1 $05 | |
28 | .alias COLUPF $08 | |
29 | .alias COLUBK $09 | |
30 | .alias PF0 $0D | |
31 | .alias PF1 $0E | |
32 | .alias PF2 $0F | |
33 | .alias SWCHA $280 | |
34 | .alias INTIM $284 | |
35 | .alias TIM64T $296 | |
36 | .alias CTRLPF $0A | |
37 | .alias COLUP0 $06 | |
38 | .alias COLUP1 $07 | |
39 | .alias GP0 $1B | |
40 | .alias GP1 $1C | |
41 | .alias HMOVE $2a | |
42 | .alias RESP0 $10 | |
43 | .alias RESP1 $11 | |
44 | ||
45 | ; | |
46 | ; Cartridge ROM occupies the top 4K of memory ($F000-$FFFF). | |
47 | ; Thus, typically, the program will occupy all that space too. | |
48 | ; | |
49 | ; Zero-page RAM we can use with impunity starts at $80 and goes | |
50 | ; upward (at least until $99, but probably further.) | |
51 | ; | |
52 | ||
53 | .alias colour $80 | |
54 | .alias luminosity $81 | |
55 | .alias joystick_delay $82 | |
56 | ||
57 | .org $F000 | |
58 | ||
59 | ; | |
60 | ; Standard prelude for Atari 2600 cartridge code. | |
61 | ; | |
62 | ; Get various parts of the machine into a known state: | |
63 | ; | |
64 | ; - Disable interrupts | |
65 | ; - Clear the Decimal flag | |
66 | ; - Initialize the Stack Pointer | |
67 | ; - Zero all bytes in Zero Page memory | |
68 | ; | |
69 | ||
70 | start: | |
71 | sei | |
72 | cld | |
73 | ldx #$FF | |
74 | txs | |
75 | lda #$00 | |
76 | ||
77 | zero_loop: | |
78 | sta $00, x | |
79 | dex | |
80 | bne zero_loop | |
81 | ||
82 | ; and fall through to... | |
83 | ||
84 | ; | |
85 | ; Initialization. | |
86 | ; | |
87 | ; - Clear the Playfield Control register. | |
88 | ; - Set the player (sprite) colour to light green (write to COLUP0.) | |
89 | ; - Set the player (sprite) size/repetion to normal (write to NUSIZ0.) | |
90 | ; | |
91 | ||
92 | lda #$00 | |
93 | sta CTRLPF | |
94 | lda #$0c | |
95 | sta colour | |
96 | lda #$0a | |
97 | sta luminosity | |
98 | lda #$00 | |
99 | sta NUSIZ0 | |
100 | ||
101 | ; and fall through to... | |
102 | ||
103 | ; | |
104 | ; Main loop. | |
105 | ; | |
106 | ; A typical main loop consists of: | |
107 | ; - Waiting for the frame to start (vertical blank period) | |
108 | ; - Displaying stuff on the screen (the _display kernel_) | |
109 | ; - Doing any processing you like (reading joysticks, updating program state, | |
110 | ; etc.), as long as you get it all done before the next frame starts! | |
111 | ; | |
112 | ||
113 | main: | |
114 | jsr vertical_blank | |
115 | jsr display_frame | |
116 | jsr read_joystick | |
117 | jmp main | |
118 | ||
119 | ; | |
120 | ; Vertical blank routine. | |
121 | ; | |
122 | ; In brief: wait until it is time for the next frame of video. | |
123 | ; TODO: describe this in more detail. | |
124 | ; | |
125 | ||
126 | vertical_blank: | |
127 | ldx #$00 | |
128 | lda #$02 | |
129 | sta WSYNC | |
130 | sta WSYNC | |
131 | sta WSYNC | |
132 | sta VSYNC | |
133 | sta WSYNC | |
134 | sta WSYNC | |
135 | lda #$2C | |
136 | sta TIM64T | |
137 | lda #$00 | |
138 | sta WSYNC | |
139 | sta VSYNC | |
140 | rts | |
141 | ||
142 | ; | |
143 | ; Display kernal. | |
144 | ; | |
145 | ; First, wait until it's time to display the frame. | |
146 | ; | |
147 | ||
148 | .scope | |
149 | display_frame: | |
150 | lda INTIM | |
151 | bne display_frame | |
152 | ||
153 | ; | |
154 | ; (After that loop finishes, we know the accumulator must contain 0.) | |
155 | ; Wait for the next scanline, zero HMOVE (for some reason; TODO discover | |
156 | ; this), then turn on the screen. | |
157 | ; | |
158 | ||
159 | sta WSYNC | |
160 | sta HMOVE | |
161 | sta VBLANK | |
162 | ||
163 | ; | |
164 | ; Actual work in the display kernal is done here. | |
165 | ; | |
166 | ; This is a pathological approach to writing a display kernal. | |
167 | ; This wouldn't be how you'd do things in a game. So be it. | |
168 | ; One day I may improve it. For now, be happy that it displays | |
169 | ; anything at all! | |
170 | ; | |
171 | ||
172 | ; | |
173 | ; Wait for $3f (plus one?) scan lines to pass, by waiting for | |
174 | ; WSYNC that many times. | |
175 | ; | |
176 | ||
177 | ldx #$3F | |
178 | _wsync_loop: | |
179 | sta WSYNC | |
180 | dex | |
181 | bpl _wsync_loop | |
182 | sta WSYNC | |
183 | ||
184 | ; | |
185 | ; Delay while the raster scans across the screen. The more | |
186 | ; we delay here, the more to the right the player will be when | |
187 | ; we draw it. | |
188 | ; | |
189 | ||
190 | nop | |
191 | nop | |
192 | nop | |
193 | nop | |
194 | nop | |
195 | nop | |
196 | nop | |
197 | nop | |
198 | nop | |
199 | nop | |
200 | nop | |
201 | nop | |
202 | nop | |
203 | nop | |
204 | nop | |
205 | ||
206 | ; | |
207 | ; OK, *now* display the player. | |
208 | ; | |
209 | ||
210 | sta RESP0 | |
211 | ||
212 | ; | |
213 | ; Loop over the rows of the sprite data, drawing each to the screen | |
214 | ; over four scan lines. | |
215 | ; | |
216 | ; TODO understand this better and describe it! | |
217 | ; | |
218 | ||
219 | ldy #$07 | |
220 | _image_loop: | |
221 | lda image_data, y | |
222 | sta GP0 | |
223 | ||
224 | sta WSYNC | |
225 | sta WSYNC | |
226 | sta WSYNC | |
227 | sta WSYNC | |
228 | dey | |
229 | bpl _image_loop | |
230 | ||
231 | lda #$00 | |
232 | sta GP0 | |
233 | ||
234 | ; | |
235 | ; Turn off screen display and clear display registers. | |
236 | ; | |
237 | ||
238 | lda #$02 | |
239 | sta WSYNC | |
240 | sta VBLANK | |
241 | lda #$00 | |
242 | sta PF0 | |
243 | sta PF1 | |
244 | sta PF2 | |
245 | sta COLUPF | |
246 | sta COLUBK | |
247 | ||
248 | rts | |
249 | .scend | |
250 | ||
251 | ||
252 | ; | |
253 | ; Read the joystick and use it to modify the colour and luminosity | |
254 | ; of the player. | |
255 | ; | |
256 | ||
257 | .scope | |
258 | read_joystick: | |
259 | lda joystick_delay | |
260 | beq _continue | |
261 | ||
262 | dec joystick_delay | |
263 | rts | |
264 | ||
265 | _continue: | |
266 | lda SWCHA | |
267 | and #$f0 | |
268 | cmp #$e0 | |
269 | beq _up | |
270 | cmp #$d0 | |
271 | beq _down | |
272 | cmp #$b0 | |
273 | beq _left | |
274 | cmp #$70 | |
275 | beq _right | |
276 | jmp _tail | |
277 | ||
278 | _up: | |
279 | inc luminosity | |
280 | jmp _tail | |
281 | _down: | |
282 | dec luminosity | |
283 | jmp _tail | |
284 | _left: | |
285 | dec colour | |
286 | jmp _tail | |
287 | _right: | |
288 | inc colour | |
289 | ;jmp _tail | |
290 | ||
291 | _tail: | |
292 | lda colour | |
293 | and #$0f | |
294 | sta colour | |
295 | ||
296 | lda luminosity | |
297 | and #$0f | |
298 | sta luminosity | |
299 | ||
300 | lda colour | |
301 | clc | |
302 | rol | |
303 | rol | |
304 | rol | |
305 | rol | |
306 | ora luminosity | |
307 | sta COLUP0 | |
308 | ||
309 | lda #$06 | |
310 | sta joystick_delay | |
311 | ||
312 | rts | |
313 | .scend | |
314 | ||
315 | ; | |
316 | ; Player (sprite) data. | |
317 | ; | |
318 | ; Because we loop over these bytes with the Y register counting *down*, | |
319 | ; this image is stored "upside-down". | |
320 | ; | |
321 | ||
322 | image_data: | |
323 | .byte %01111110 | |
324 | .byte %10000001 | |
325 | .byte %10011001 | |
326 | .byte %10100101 | |
327 | .byte %10000001 | |
328 | .byte %10100101 | |
329 | .byte %10000001 | |
330 | .byte %01111110 | |
331 | ||
332 | ; | |
333 | ; Standard postlude for Atari 2600 cartridge code. | |
334 | ; Give BRK and boot vectors that point to the start of the code. | |
335 | ; | |
336 | ||
337 | .advance $FFFC | |
338 | .word start | |
339 | .word start |
0 | 0 | #!/bin/sh |
1 | 1 | |
2 | usage="Usage: loadngo.sh (c64|vic20) [--dry-run] <source.60p>" | |
2 | usage="Usage: loadngo.sh (c64|vic20|atari2600) [--dry-run] <source.60p>" | |
3 | 3 | |
4 | 4 | arch="$1" |
5 | 5 | shift 1 |
17 | 17 | else |
18 | 18 | emu="xvic" |
19 | 19 | fi |
20 | elif [ "X$arch" = "Xatari2600" ]; then | |
21 | prelude='atari2600' | |
22 | emu='stella' | |
20 | 23 | else |
21 | 24 | echo $usage && exit 1 |
22 | 25 | fi |
185 | 185 | advance the address for the next label, but don't emit anything.""" |
186 | 186 | self.resolve_label(label) |
187 | 187 | self.addr += label.length |
188 | ||
189 | def size(self): | |
190 | return sum(emittable.size() for emittable in self.accum) | |
191 | ||
192 | def pad_to_size(self, size): | |
193 | self_size = self.size() | |
194 | if self_size > size: | |
195 | raise IndexError("Emitter size {} exceeds pad size {}".format(self_size, size)) | |
196 | num_bytes = size - self_size | |
197 | if num_bytes > 0: | |
198 | self.accum.extend([Byte(0)] * num_bytes) |