APX Pascal Architecture, part one
Bill Lange has been blogging about Atari Pascal since early February at https://insideataripascal.blogspot.com/, so here's my own small contribution after spending an afternoon poking around in APX Pascal and looking for the core interpreter.
If we look at the PASCAL runtime on the APX Pascal disk, it's a simple enough image. It loads itself from disk to $3300-$59ff and then starts running from $3300. So what does that initial bootstrap code do? Here's the preamble:
3300: A2 00 LDX #0 3302: A9 0C LDA #$0C 3304: 9D 42 03 STA ICCMD,X 3307: 20 56 E4 JSR CIOV 330A: AD A7 33 LDA $33A7 330D: 85 F0 STA $F0 330F: AD A8 33 LDA $33A7+1 3312: 85 F1 STA $F0+1 3314: AD A9 33 LDA $33A9 3317: 85 F2 STA $F2 3319: AD AA 33 LDA $33A9+1 331C: 85 F3 STA $F2+1 331E: AD AB 33 LDA $33AB 3321: 85 F4 STA $F4 3323: AD AC 33 LDA $33AB+1 3326: 85 F5 STA $F4+1 3328: 20 73 33 JSR $3373
This code closes IOCB #0, then sets up $F0-F5 using values at $33A7-$33AC and then calls a subroutine. Those values are:
33A7: 00 3A .WORD $3A00 ; source 33A9: 00 A0 .WORD $A000 ; destination 33AB: 00 20 .WORD $2000 ; count 33AD: 00 1D .WORD $1D00
And the subroutine looks like:
3373: A0 00 LDY #0 3375: B1 F0 LDA ($F0),Y 3377: 91 F2 STA ($F2),Y 3379: A5 F0 LDA $F0 337B: 18 CLC 337C: 69 01 ADC #1 337E: 85 F0 STA $F0 3380: A5 F1 LDA $F0+1 3382: 69 00 ADC #0 3384: 85 F1 STA $F0+1 3386: A5 F2 LDA $F2 3388: 18 CLC 3389: 69 01 ADC #1 338B: 85 F2 STA $F2 338D: A5 F3 LDA $F2+1 338F: 69 00 ADC #0 3391: 85 F3 STA $F2+1 3393: A5 F4 LDA $F4 3395: 38 SEC 3396: E9 01 SBC #1 3398: 85 F4 STA $F4 339A: A5 F5 LDA $F4+1 339C: E9 00 SBC #0 339E: 85 F5 STA $F4+1 33A0: A5 F4 LDA $F4 33A2: 05 F5 ORA $F4+1 33A4: D0 CF BNE $3375 33A6: 60 RTS
This is just a block copy routine, which relocates all the code at $3A00-$59FF to $A000-$BFFF. This block of code (which is most of the PASCAL executable), is the actual runtime. A simpler way to do this would have been to use a multi-segment load file, but this works well enough. $A000-$BFFF is the cartridge address space for an 8k cart, so clearly this was intended at one point to be shipped as a cartridge.
What happens next:
332B: AD AD 33 LDA $33AD 332E: 85 80 STA $80 3330: AD AE 33 LDA $33AE 3333: 85 81 STA $81 3335: A9 00 LDA #0 3337: 85 82 STA $82 3339: 85 83 STA $83 333B: 20 00 A2 JSR $A200 ... A200: 4C 12 B8 JMP $B812
This copies the word in $33AD ($1D00) to $80,$81 and zeros $82,$83 before invoking a routine at $A200, which vectors to $B812.
That routine does several things, including:
B834: A5 80 LDA $80 B836: 85 D0 STA $D0 B838: A5 81 LDA $81 B83A: 85 D1 STA $D1 B83C: AD 78 A2 LDA $A278 B83F: 85 CE STA $CE B841: AD 79 A2 LDA $A278+1 B844: 85 CF STA $CE+1 B846: AD 7A A2 LDA $A27A B849: 85 D2 STA $D2 B84B: AD 7B A2 LDA $A27A+1 B84E: 85 D3 STA $D2+1 B850: 20 6B AE JSR $AE6B
where:
A278: 00 A0 .WORD $A000 A27A: 78 02 .WORD $0278
So we move the word at $80,$81 ($1D00) to $D0,D1, and set $CE,$CF to $A000 and $D2,D3 to $0278, before calling another block copier.
AE6B: A0 00 LDY #0 AE6D: A6 D3 LDX $D3 AE6F: F0 0E BEQ $AE7F AE71: B1 CE LDA ($CE),Y AE73: 91 D0 STA ($D0),Y AE75: C8 INY AE76: D0 F9 BNE $AE71 AE78: E6 CF INC $CF AE7A: E6 D1 INC $D1 AE7C: CA DEX AE7D: D0 F2 BNE $AE71 AE7F: A6 D2 LDX $D2 AE81: F0 08 BEQ $AE8B AE83: B1 CE LDA ($CE),Y AE85: 91 D0 STA ($D0),Y AE87: C8 INY AE88: CA DEX AE89: D0 F8 BNE $AE83 AE8B: 60 RTS
So we relocate the first $0278 bytes of the "cartridge" to address $1D00-$1F77. The first $200 bytes are just a series of addresses (more on those soon), the next $78 bytes are a set of JMP vectors, e.g.
1F00: 4C 12 B8 JMP $B812 1F03: 4C B1 AB JMP $ABB1 1F06: 4C B6 AB JMP $ABB6 ... 1F72: 4C 87 B8 JMP $B887 1F75: 4C 5F BC JMP $BC5F
After we return from this the code continues with:
B853: A5 80 LDA $80 B855: 85 82 STA $82 B857: A5 81 LDA $81 B859: 85 83 STA $83 B85B: E6 83 INC $83 B85D: E6 83 INC $83 B85F: 20 EB B9 JSR $B9EB
The word at $80,$81 gets moved to $82,$83 and incremented by $200, so the word at $82,$83 is now $1F00. The subroutine called looks like:
B9EB: A2 21 LDX #$21 B9ED: A0 00 LDY #0 B9EF: B9 CA B9 LDA $B9CA,Y B9F2: 99 92 00 STA $0092,Y B9F5: C8 INY B9F6: CA DEX B9F7: D0 F6 BNE $B9EF B9F9: A5 81 LDA $81 B9FB: 85 AD STA $AD B9FD: 85 B2 STA $B2 B9FF: E6 B2 INC $B2 BA01: 60 RTS
This copies the code at $B9CA into page zero, and patches the value at $81 ($1D) into $AD and the $B2 and then increments $B2, so we end up with the following:
0092: 18 CLC 0093: 65 A4 ADC $A4 0095: 85 A4 STA $A4 0097: 90 0A BCC $00A3 0099: E6 A5 INC $A5 009B: B0 06 BCS $00A3 009D: E6 A4 INC $A4 009F: D0 02 BNE $00A3 00A1: E6 A5 INC $A5 00A3: AD FF FF LDA $FFFF 00A6: 0A ASL A 00A7: B0 05 BCS $00AE 00A9: 85 AC STA $AC 00AB: 6C 00 1D JMP ($1D00) 00AE: 85 B1 STA $B1 00B0: 6C 00 1E JMP ($1E00)
This is the core of the Pascal interpreter, similar to the Forth NEXT routine I discussed in http://atariage.com/forums/blog/734/entry-15007-dealer-demo-part-4-some-forth-at-last/. It has three parts, and self-modifies its code as it runs. If you enter at $0092, it increments the current p-code pointer (located at $A4,$A5) by the accumulator. If you enter at $009D, it increments the current p-code pointer by 1. In both cases, it then proceeds to the third part (which can be called directly as well) which reads the p-code value, multiplies the value by two and then patches one of two jump vectors with that value depending on whether the multiply overflowed or not. This allows us to dispatch all 256 possible p-codes, and each code will then jump back into this routine, keeping the interpreter running forever.
Of course, we haven't actually gotten into the interpreter yet, only set it up. We'll discuss that in a future post, but we've made decent progress towards separating the runtime from the monitor. In particular, it's clear we could move the 8K runtime in $A000-$BFFF into a cartridge image and modify the PASCAL file to skip the initial relocation and rely on the cartridge. That focuses our attention on the remaining 1.8K of PASCAL to isolate the code that sets up the runtime and loads the MON program. Hopefully we can adapt that code to load another program directly, and thus produce binaries that can run without loading the monitor.
- 5
0 Comments
Recommended Comments
There are no comments to display.