Discuss Scratch
- MartinBraendli2
- Scratcher
100+ posts
S65 Documentation (6502 Emulator)
This is the official documentation to the S65.
The S65 is a 8-Bit computer that never existed in the real world, but could have. It is similar to computers from the late 70s to early 80s, like the Apple I or the VIC-20. With its specs it probably would have been quite expensive, so appreciate the fact that you can try it for free
Technical Specs
MOS 6502 CPU @~1Mhz (Phosphorus)
28kB of RAM
3kB of Video RAM
32kB ROM, exchangable
Monochrome Monitor, 256x256, @30Hz
US ASCII Keyboard
Sound Chip with 1 Voice, polyphonic
Memory Map
In the next post(s) I will show, how to address the hardware.
The S65 is a 8-Bit computer that never existed in the real world, but could have. It is similar to computers from the late 70s to early 80s, like the Apple I or the VIC-20. With its specs it probably would have been quite expensive, so appreciate the fact that you can try it for free
Technical Specs
MOS 6502 CPU @~1Mhz (Phosphorus)
28kB of RAM
3kB of Video RAM
32kB ROM, exchangable
Monochrome Monitor, 256x256, @30Hz
US ASCII Keyboard
Sound Chip with 1 Voice, polyphonic
Memory Map
0000 Memory
0000-00FF rw Zero Page
0100-01FF rw Stack
0200-6FFF rw Memory
7000 GPU
7000-77FF rw Character Memory
7800-7BFF rw Screen Memory
7C00 rw Cursor write
7C01 rw Cursor put char
7C02 rw Cursor x
7C03 rw Cursor y
7D00 Keyboard Interface Adapter
7D00 r Key Buffer
7D01 r Joypad
7D02 w Clear Key Buffer
7E00 Misc
7E00 r RND
7E01 w Debug Halt
7F00 Sound Chip
7F00 w Pitch
7F01 w Volume
7F02 w Delay
7F03 w Length/Play
In the next post(s) I will show, how to address the hardware.
Last edited by MartinBraendli2 (Nov. 19, 2017 19:52:58)
- MartinBraendli2
- Scratcher
100+ posts
S65 Documentation (6502 Emulator)
In this section I will show how to program the S65. I will try to comment enough, so that you don't need to know 6502 Assembly to follow. Nevertheless, I recommend you to have a look at it. http://nesdev.com/6502.txt is a good start.
There are many different dialects of 6502 Assembly. I will here use the one I wrote myself. You can get the Assembler (Python) and more instructions on github. If you don't have Python 3, you can also do this in your Browser (see post #13)
Skeleton
Every 6502 Assembler source has a skeleton like this:
Hello World
To print text to the screen, we have to write each character (a number) to the right place on the memory. Screen memory of the S65 starts at 0x7800. So if we write the number 65 to that address, the letter ‘A’ (ASCII 65) will be put on the top left corner of the screen. Each row is 32 characters long, so 0x781F will be the address last character on the first row, 0x7820 the first on the second row and so on.
Output (ready for import):
Easier printing
Instead of writing directly to the screen memory (0x7800-0x7BFF), you can also make use of the hardware cursor:¨
0x7C00: Print character to Screen. If the value is bigger that 31, it will be put on screen. If its 13 (Return), the (invisible) cursor will go to the start of the next line. If its 8 (Backspace), it will remove the character at the cursor and go one to the left. 9 (Tab) will move the cursor to the next multiple of 4. Cursor will move automatically to new line. If its at the end of the screen, it will move everything on screen one row up (removing the first row) and go to the now empty last row.
0x7C01: Raw print. Writing a number to this address will print it at the cursor position also (and increment the cursor position). The difference to 0x7C00 is, that this will also print characters 0-31. Also, it doesn't create new lines, but just goes back to the top left, once the end of the screen is reached.
0x7C02: Read/Write the x-position of the cursor. Only the lowest 5 bits are used (0-31)
0x7C03: Read/Write the y-position of the cursor. Only the lowest 5 bits are used (0-31)
Redefining Characters
0x7000-0x77FF in memory defines the characters. You can change the look/font of every character by modifying the right memory location. The first 8 bytes (0x7000-0x7007) define character 0, 0x7008-0x7010 define character 2 etc. So if you want to redefine character n, you'll have to change bytes 0x7000+8*n up to 0x7000+8*n+7.
The first byte defines the top row of a character, then it goes down. Bit 7 (the one with value 128) is the leftmost pixel, bit 0 (Value 1) the rightmost. You can experiment with this Scratch project to get a better feeling for what i fail to explain with words.
Output (ready for import):
There are many different dialects of 6502 Assembly. I will here use the one I wrote myself. You can get the Assembler (Python) and more instructions on github. If you don't have Python 3, you can also do this in your Browser (see post #13)
Skeleton
Every 6502 Assembler source has a skeleton like this:
ORIGIN = 0x8000 ;Tells the Assembler, where in the Memory this ROM will be placed
==Init== ;Label. Needed, so that the "Reset Vector" Can point here
;Code starts here
==Irq== ;Interrupt handler (more on that later)
RTI ;Return from Interrupt. So basically "do nothing on interrupt"
@0xFFFC ;When the 6502 is started up or reset, it reads the 2 bytes at
;0xFFFC and 0xFFFD and Jumps to that address.
DATA Init ;Init is a Word (= 2 bytes) we defined before with the Label.
;So the CPU looks here, finds this and Jumps to Init
DATA Irq ;On Adresses 0xFFFE and 0xFFFF is another Vector, telling the
;CPU where to go when an interrupt happens.
Hello World
To print text to the screen, we have to write each character (a number) to the right place on the memory. Screen memory of the S65 starts at 0x7800. So if we write the number 65 to that address, the letter ‘A’ (ASCII 65) will be put on the top left corner of the screen. Each row is 32 characters long, so 0x781F will be the address last character on the first row, 0x7820 the first on the second row and so on.
@0x00
Screen = 0x7800 ;Screen is the location of the first character on screen.
ORIGIN = 0xFFD0 ;The code will be short, so we dont have to start at 0x8000
==Start==
JSR Write_hw ;Jump to the subroutine Write_hw
{
JMP RE ;This is an infinite loop to trap the program counter. It Jumps back
;to the start of the bracket
}
==Write_hw==
LDX #0 ;Set the Register X to 0
{
LDA My_Text,x ;Loads the character X of My_Text (see further down) into Accumulator
BEQ BR ;If its Zero, break/leave the bracket
STA Screen,x ;Else, Store the character in Acc. on Screen + X
INX ;X++
JMP RE ;Jump to the start of the bracket/loop
}
RTS ;When done, jump back to where we came from
==My_Text== ;Label, so we can access the next data easily
DATA "Hello World!\00" ;Puts 13 bytes here (12 ASCII characters + 0x00)
==Irq_handler==
RTI
@0xFFFC
DATA Start
DATA Irq_handler
20D6FF4CD3FFA200BDE5FFF0079D0078E84CD8FF6048656C6C6F20576F726C64210040000000000000000000D0FFF2FF
Easier printing
Instead of writing directly to the screen memory (0x7800-0x7BFF), you can also make use of the hardware cursor:¨
0x7C00: Print character to Screen. If the value is bigger that 31, it will be put on screen. If its 13 (Return), the (invisible) cursor will go to the start of the next line. If its 8 (Backspace), it will remove the character at the cursor and go one to the left. 9 (Tab) will move the cursor to the next multiple of 4. Cursor will move automatically to new line. If its at the end of the screen, it will move everything on screen one row up (removing the first row) and go to the now empty last row.
0x7C01: Raw print. Writing a number to this address will print it at the cursor position also (and increment the cursor position). The difference to 0x7C00 is, that this will also print characters 0-31. Also, it doesn't create new lines, but just goes back to the top left, once the end of the screen is reached.
0x7C02: Read/Write the x-position of the cursor. Only the lowest 5 bits are used (0-31)
0x7C03: Read/Write the y-position of the cursor. Only the lowest 5 bits are used (0-31)
Redefining Characters
0x7000-0x77FF in memory defines the characters. You can change the look/font of every character by modifying the right memory location. The first 8 bytes (0x7000-0x7007) define character 0, 0x7008-0x7010 define character 2 etc. So if you want to redefine character n, you'll have to change bytes 0x7000+8*n up to 0x7000+8*n+7.
The first byte defines the top row of a character, then it goes down. Bit 7 (the one with value 128) is the leftmost pixel, bit 0 (Value 1) the rightmost. You can experiment with this Scratch project to get a better feeling for what i fail to explain with words.
@0x00
ascii_A = 0x41 ;ASCII value of A (0x41 = 65)
Screen = 0x7800 ;Screen is the location of the first character on screen.
Letter_A = 0x7208 ;Memory location of the definition of character 'A'
;ASCII 'A' is 65, so the first byte is 0x7000 + 8*65 = 0x7208
Put_char = 0x7C01 ;Memory address of put char to screen
Cursor_x = 0x7C02 ;Memory address of Cursor.x
Cursor_y = 0x7C03 ;Memory address of Cursor.y
ORIGIN = 0xFFC0
==Start==
LDX #0 ;Set register X to 0
{
CPX #8 ;Compare register X to 8
BEQ BR ;Break the loop if equal (x == 8)
LDA Bubble,x ;Load Bubble+X to accumulator
STA Letter_A,x ;Set byte X of character 'A' to what's in the accumulator
INX ;Increment register X
JMP RE ;Go back to the start of the loop
}
LDA #1 ;Load 1 to accumulator
STA Cursor_x ;Set Cursor.x to 1 (acc)
LDA #8
STA Cursor_y ;Set Cursor,y to 8 (the 8th line)
LDA #ascii_A ;Load character 'A', the one we redefined
STA Put_char ;Put it three times on Screen
STA Put_char
STA Put_char
{
JMP RE ;Trap the program counter, so it doesn't start executing the data from "Bubble"
}
==Bubble==
DATA 0x3C4299858181423C ;This is a 8 byte big image, created with https://scratch.mit.edu/projects/180802849/
==Irq_handler==
RTI
@0xFFFC
DATA Start
DATA Irq_handler
A200E008F00ABDE8FF9D0872E84CC2FFA9018D027CA9088D037CA9418D017C8D017C8D017C4CE5FF3C4299858181423C400000000000000000000000C0FFF0FF
Last edited by MartinBraendli2 (Nov. 12, 2017 23:33:12)
- MartinBraendli2
- Scratcher
100+ posts
S65 Documentation (6502 Emulator)
Interrupts/ Timing
Imagine you have a scanner, that sends a scanned document to your (real) computer when you press the “scan” button on it. The processor in your computer would have to ask the scanner every few miliseconds “do you have data for me”. This is called polling, and its pretty inefficient, since it takes up processing time. Thats what interrupts are for. Instead, your hardware (the scanner) can create an interrupt. By that, the scanner is telling your CPU “stop everything you're doing right now and handle the data I'm gonna send you now”.
On the S65 there is (currently) only one kind of interrupt: Frame timing. If the interrupt-disable flag of the CPU is low/ off / zero, the clock will generate an interupt every frame (30 times per second). This can be used for animations, timing, games and such. When an interrupt is handled, the program just continues where it was before the interupt.
The following program will change the character at the top-left of the screen every 8 frames:
Machine code:
It important to keep in mind, that the registers of the processor have to be the same as they were before the interrupt. Imagine, your program is in the middle of a calculation when an interrupt happens. Now the interrupt handler does a calculation itself, which affects the carry flag. When it returns to the program, the program uses the carry from the interrupt, not from the previous calculations. This may lead to bugs, that are pretty hard to debut. For that reason, you allways have to know, what processor registers are affected by your interrupt-handling-routine. In the exaple above, the “DEC wait_frames” will affect the zero-flag and the negative-flag (which are both part of the “processor-status” register). So to make sure that those flags are the same before and after the interrupt, we can push the processor-status on the stack at the start of the interrupt-handler and pull (restore) them at the end:
If you use (change) the accumulator, the register X or the register Y in your interrupt handler, you have to push them on the stack at the beginning as well, and at the end pull them (in reverse order).
Imagine you have a scanner, that sends a scanned document to your (real) computer when you press the “scan” button on it. The processor in your computer would have to ask the scanner every few miliseconds “do you have data for me”. This is called polling, and its pretty inefficient, since it takes up processing time. Thats what interrupts are for. Instead, your hardware (the scanner) can create an interrupt. By that, the scanner is telling your CPU “stop everything you're doing right now and handle the data I'm gonna send you now”.
On the S65 there is (currently) only one kind of interrupt: Frame timing. If the interrupt-disable flag of the CPU is low/ off / zero, the clock will generate an interupt every frame (30 times per second). This can be used for animations, timing, games and such. When an interrupt is handled, the program just continues where it was before the interupt.
The following program will change the character at the top-left of the screen every 8 frames:
@0x00
VAR wait_frames ;Reserve an address for wait_frames (number of frames to do nothing)
VAR char ;Reserve an address for the current character to be printed
Screen = 0x7800 ;Screen is the location of the first character on screen.
ORIGIN = 0xFFD0
==Start==
LDA #2
STA wait_frames ;Set the wait_frames to 2 (frames).
LDA #0
STA char ;Set the character to 0
CLI ;Clear the interrupt-disable flag (allow intterupts)
{
LDA wait_frames ;Load wait_frames into acc
BNE RE ;If its not 0, go back to the start of the loop
SEI ;Else, set the interrupt-disable flag (we dont want interrupts now)
LDA #8
STA wait_frames ;Set the wait_frames back to 8 (frames)
INC char ;Increment the currnt char
LDA char
STA Screen ;Put the churrent char to the screen
CLI ;Enable Intrrupts again by clearing the interrupt-disable flag
JMP RE ;jump back to the start of the loop
}
==Irq_handler==
DEC wait_frames ;Every frame, decrement wait_frames
RTI ;Return fom interrupt (go back to the regular code)
@0xFFFC
DATA Start
DATA Irq_handler
A9028500A900850158A500D0FC78A9088500E601A5018D0078584CD9FFC60040000000000000000000000000D0FFEDFF
It important to keep in mind, that the registers of the processor have to be the same as they were before the interrupt. Imagine, your program is in the middle of a calculation when an interrupt happens. Now the interrupt handler does a calculation itself, which affects the carry flag. When it returns to the program, the program uses the carry from the interrupt, not from the previous calculations. This may lead to bugs, that are pretty hard to debut. For that reason, you allways have to know, what processor registers are affected by your interrupt-handling-routine. In the exaple above, the “DEC wait_frames” will affect the zero-flag and the negative-flag (which are both part of the “processor-status” register). So to make sure that those flags are the same before and after the interrupt, we can push the processor-status on the stack at the start of the interrupt-handler and pull (restore) them at the end:
==Irq_handler==
PHP ;Push the processor-status to the stack
DEC wait_frames
PLP ;Pull the processor-status from stack
RTI
Last edited by MartinBraendli2 (Nov. 13, 2017 12:36:28)
- MartinBraendli2
- Scratcher
100+ posts
S65 Documentation (6502 Emulator)
Keyboard
When you press a key, the ASCII value of that key gets stored in the Key Buffer.
If you read from address 0x7D00, then you get the oldest value from this buffer (the buffer is a que, first-in-first-out, so its NOT a stack).
So when you type “Bar”, then the buffer will contain {66,97,114}. LDA 0x7D00 would then load 66 (ASCII ‘B’) into accumulator, the buffer would then be {97,114}. If the buffer is empty (no key was pressed), then reading from address 0x7D00 will return 0.
'Enter/Return' will put 13 to the buffer, ‘Tab’ 9, ‘Backspace’ 8 and ‘Space’ 32. Other non-printable-keys cann't be detected.
You can clear the Keyboard Buffer by writing (any value) to 0x7D02.
Joypad
If you read from address 0x7D01, you get the status of the “joypad”. Each bit of this status is high (=1), when a key on the “joypad” is pressed and else low/0.
Bit0: Up
Bit1: Right
Bit2: Down
Bit3: Left
Bit4: w
Bit5: d
Bit6: s
Bit7: a
So if you hold ‘up’, ‘left’ and ‘s’, then LDA 0x7D01 will set the accumulator to 73 (1 for ‘up’ + 8 for ‘left’ + 64 for ‘s’) or 0b01001001.
If you want to test whether a specific key on the “joypad” is pressed, you have to mask it with AND:
Random
Reading from address 0x7E00 will return a random number from 0-255
Debug Halt
Writing any value to address 0x7E01 will pause the emulation and enter debug mode. In debug mode you see the status of the processor and can do single steps. When you leave the debug mode (by clicking on the ladybug), the cpu will continue running normally.
Sound
Write to address 0x7F00 to sets the pitch (equal to Scratchs note blocks, 60 = Middle C)
Write to address 0x7F01 to sets the volume, 255 is 100%
Write to address 0x7F02 to sets the delay.
When you write a value to address 0x7F03, a sound will be played with the lenght of the value you wrote (in frames, so for a note to be 1 second long, you have to write 60). If delay is set to 0, the sound will play immediately, else it will be delayed by <delay> frames.
You don't have to set pitch, volume and delay again for each note you want to play. Initial values for pitch, volume and delay are 60, 255 resp. 0.
Morse.asm
The following program will morse whatever you type in your keyboard.
Morse_code is a table of the morse code in binary. Its grouped in 4x2 bits, where the first bit means, that a sound has to be played and the second, whether its long (a dah, bit = 1) or a dit (short, bit = 0). ‘A’ is 0b0b10110000, which is
10 dit
11 dah
00 nothing
00 nothin
So ‘A’ = dit-dah
Machine Code:
When you press a key, the ASCII value of that key gets stored in the Key Buffer.
If you read from address 0x7D00, then you get the oldest value from this buffer (the buffer is a que, first-in-first-out, so its NOT a stack).
So when you type “Bar”, then the buffer will contain {66,97,114}. LDA 0x7D00 would then load 66 (ASCII ‘B’) into accumulator, the buffer would then be {97,114}. If the buffer is empty (no key was pressed), then reading from address 0x7D00 will return 0.
'Enter/Return' will put 13 to the buffer, ‘Tab’ 9, ‘Backspace’ 8 and ‘Space’ 32. Other non-printable-keys cann't be detected.
You can clear the Keyboard Buffer by writing (any value) to 0x7D02.
Joypad
If you read from address 0x7D01, you get the status of the “joypad”. Each bit of this status is high (=1), when a key on the “joypad” is pressed and else low/0.
Bit0: Up
Bit1: Right
Bit2: Down
Bit3: Left
Bit4: w
Bit5: d
Bit6: s
Bit7: a
So if you hold ‘up’, ‘left’ and ‘s’, then LDA 0x7D01 will set the accumulator to 73 (1 for ‘up’ + 8 for ‘left’ + 64 for ‘s’) or 0b01001001.
If you want to test whether a specific key on the “joypad” is pressed, you have to mask it with AND:
{
LDA 0x7D01
AND 0b00000001
BEQ SK
{
;Code for 'Up' pressed here
JMP BR,BR
}
LDA 0x7D01
AND 0b00000010
BEQ SK
{
;Code for 'Right' pressed here
JMP BR,BR
}
LDA 0x7D01
AND 0b00000100
BEQ SK
{
;Code for 'Down' pressed here
JMP BR,BR
}
LDA 0x7D01
AND 0b00001000
BEQ SK
{
;Code for 'Left' pressed here
JMP BR,BR
}
}
Random
Reading from address 0x7E00 will return a random number from 0-255
Debug Halt
Writing any value to address 0x7E01 will pause the emulation and enter debug mode. In debug mode you see the status of the processor and can do single steps. When you leave the debug mode (by clicking on the ladybug), the cpu will continue running normally.
Sound
Write to address 0x7F00 to sets the pitch (equal to Scratchs note blocks, 60 = Middle C)
Write to address 0x7F01 to sets the volume, 255 is 100%
Write to address 0x7F02 to sets the delay.
When you write a value to address 0x7F03, a sound will be played with the lenght of the value you wrote (in frames, so for a note to be 1 second long, you have to write 60). If delay is set to 0, the sound will play immediately, else it will be delayed by <delay> frames.
You don't have to set pitch, volume and delay again for each note you want to play. Initial values for pitch, volume and delay are 60, 255 resp. 0.
Morse.asm
The following program will morse whatever you type in your keyboard.
Morse_code is a table of the morse code in binary. Its grouped in 4x2 bits, where the first bit means, that a sound has to be played and the second, whether its long (a dah, bit = 1) or a dit (short, bit = 0). ‘A’ is 0b0b10110000, which is
10 dit
11 dah
00 nothing
00 nothin
So ‘A’ = dit-dah
@0x00
VAR wait_frames
Keyboard = 0x7D00
Pitch = 0x7F00
Volume = 0x7F01
Delay = 0x7F02
Play = 0x7F03
ORIGIN = 0xFF63
==Start==
LDA #100
STA Volume ;set volume to 100%
LDA #80
STA Pitch ;set the pitch to 80
{
LDY Keyboard ;Get a keypress from the key que
CPY #97
BCC SK ;If its >= 97
{
CPY #123
BCS SK ;AND < 123, then it must be a lower case letter
{
LDA Morse_code-97,y ;Load the morse code for that letter into acc
JSR Morse_letter ;Morse that letter
}
JMP BR,RE ;Back to loop start (next letter)
}
CPY #32
BNE SK ;If its a space
{
LDA #21
STA wait_frames
JSR Wait ;Wait 21 frames (7 dits)
}
JMP RE
}
==Morse_letter==
LDX #0
STX Delay ;set delay to 0
{
CMP #128 ;compar acc (the binary-morse-code) to 128
BCC BR ;if its < 128, then the bit7 is not set, there are no more dit/dah for this character
PHA ;else, push a copy of the code to the stack
AND #0b01000000
BNE SK
{ ;If bit 6 not set
JSR Dit ;Morse a dit
JMP BR,SK
}
{ ;If bit 6 is set
JSR Dah ;Morse a dah
}
PLA ;Pull the copy of the code from the stack
ASL A
ASL A ;Shift 2 bits out (so that the next dit/dah is in bit6
JMP RE ;Morse the next dit/dah
}
LDA Delay ;Read the delay (lenght of the morse sound of the whole character)
CLC
ADC #9 ;Add a 9 to it (silence for the length of 3 dits to mark end of letter)
STA wait_frames
JSR Wait ;Wait length of the morse-sound + end-of-letter-pause
RTS
==Dit==
LDA #3
STA Play ;Play a dit (3 frames)
CLC
LDA Delay
ADC #6 ;Increment the Delay (for the next sound) by 6 (1 dit, plus silence for 1 dit)
STA Delay
RTS
==Dah==
LDA #9
STA Play ;Play a dah (3 dit = 9 frames)
CLC
LDA Delay
ADC #12
STA Delay ;Increment the Delay (for the next sound) by 12 (3 dit, plus silence for 1 dit)
RTS
==Wait==
CLI ;Enable interrupts
{
LDA wait_frames ;Loop until wait_frames is 0 (the Irq_handler will decrement wait_frames every 1/30 second
BNE RE
}
SEI ;Disable interrups
RTS
==Irq_handler==
PHP
DEC wait_frames ;Decrement wait_frames (every 1/30s), but only if interrups are enabled
PLP
RTI
==Morse_code==
DATA 0b10110000 ;A
DATA 0b11101010 ;B
DATA 0b11101110 ;C
DATA 0b11101000 ;D
DATA 0b10000000 ;E
DATA 0b10101110 ;F
DATA 0b11111000 ;G
DATA 0b10101010 ;H
DATA 0b10100000 ;I
DATA 0b10111111 ;J
DATA 0b11101100 ;K
DATA 0b10111010 ;L
DATA 0b11110000 ;M
DATA 0b11100000 ;N
DATA 0b11111100 ;O
DATA 0b10111110 ;P
DATA 0b11111011 ;Q
DATA 0b10111000 ;R
DATA 0b10101000 ;S
DATA 0b11000000 ;T
DATA 0b10101100 ;U
DATA 0b10101011 ;V
DATA 0b10111100 ;W
DATA 0b11101011 ;X
DATA 0b11101111 ;Y
DATA 0b11111010 ;Z
@0xFFFC
DATA Start
DATA Irq_handler
Machine Code:
A9648D017FA9508D007FAC007DC061900DC07BB006B981FF208FFF4C6DFFC020D007A915850020D6FF4C6DFFA2008E027FC9809014482940D00620B8FF4CA6FF20C7FF680A0A4C94FFAD027F186909850020D6FF60A9038D037F18AD027F69068D027F60A9098D037F18AD027F690C8D027F6058A500D0FC786008C6002840B0EAEEE880AEF8AAA0BFECBAF0E0FCBEFBB8A8C0ACABBCEBEFFA63FFDDFF
Last edited by MartinBraendli2 (Nov. 19, 2017 21:17:27)
- TheUltimatum
- Scratcher
1000+ posts
S65 Documentation (6502 Emulator)
ooh! I was wondering when you'd release this! Bravo, man… Bravo!
- novice27b
- Scratcher
1000+ posts
S65 Documentation (6502 Emulator)
Awesome, I'm amazed it can run at 1MHz in phosphorus.
i use arch btw
- TheUltimatum
- Scratcher
1000+ posts
S65 Documentation (6502 Emulator)
Ok so when compiling the example hello world program I get the following error:
Missing label for the hello world string or something? I have no experience with assembly whatsoever.
Error on line 27: Unknown data: "Hello World!\00"
Press Enter to exit...
- Jonathan50
- Scratcher
1000+ posts
S65 Documentation (6502 Emulator)
A Scratch project can run at 1,000,000 cycles per second???? (I think it's because phosphorus compiles it to JavaScript and then the JavaScript engine compiles it to machine code on-the-fly. But phosphorus would have to eval the compiled Scratch code and the JIT doesn't work with eval'd code, right?) Awesome, I'm amazed it can run at 1MHz in phosphorus.
Last edited by Jonathan50 (Nov. 11, 2017 20:59:31)
Not yet a Knight of the Mu Calculus.
- TheUltimatum
- Scratcher
1000+ posts
S65 Documentation (6502 Emulator)
Actually anything I try to assemble with the assembler gives me an error.
- LP--Jakob
- Scratcher
5 posts
S65 Documentation (6502 Emulator)
http://nesdev.com/6502.txt is a good start.In this section I will show how to program the S65. I will try to comment enough, so that you don't need to know 6502 Assembly to follow. Nevertheless, I recommend you to have a look at it.
There are many different dialects of 6502 Assembly. I will here use the one I wrote myself. You can get the Assembler (Python) and more instructions on github.
Skeleton
Every 6502 Assembler source has a skeleton like this:ORIGIN = 0x8000 ;Tells the Assembler, where in the Memory this ROM will be placed
==Init== ;Label. Needed, so that the "Reset Vector" Can point here
;Code starts here
==Irq== ;Interrupt handler (more on that later)
RTI ;Return from Interrupt. So basically "do nothing on interrupt"
@0xFFFC ;When the 6502 is started up or reset, it reads the 2 bytes at
;0xFFFC and 0xFFFD and Jumps to that address.
DATA Init ;Init is a Word (= 2 bytes) we defined before with the Label.
;So the CPU looks here, finds this and Jumps to Init
DATA Irq ;On Adresses 0xFFFE and 0xFFFF is another Vector, telling the
;CPU where to go when an interrupt happens.
Hello World
To print text to the screen, we have to write each character (a number) to the right place on the memory. Screen memory of the S65 starts at 0x7800. So if we write the number 65 to that address, the letter ‘A’ (ASCII 65) will be put on the top left corner of the screen. Each row is 32 characters long, so 0x781F will be the address last character on the first row, 0x7820 the first on the second row and so on.@0x00
Screen = 0x7800 ;Screen is the location of the first character on screen.
ORIGIN = 0x8000
==Start==
JSR Write_hw ;Jump to the subroutine Write_hw
{
JMP RE ;This is an infinite loop to trap the program counter. It Jumps back
} ;to the start of the bracket
==Write_hw==
LDX #0 ;Set the Register X to 0
{
LDA My_Text,x ;Loads the character X of My_Text (see further down) into Accumulator
BEQ BR ;If its Zero, break/leave the bracket
STA Screen,x ;Else, Store the character in Acc. on Screen + X
INX ;X++
JMP RE ;Jump to the start of the bracket/loop
}
RTS ;When done, jump back to where we came from
==My_Text== ;Label, so we can access the next data easily
DATA "Hello World!\00" ;Puts 13 bytes here (12 ASCII characters + 0x00)
==Irq_handler==
RTI
@0xFFFC
DATA Start
DATA Irq_handler
Where shoud I write this?
- MartinBraendli2
- Scratcher
100+ posts
S65 Documentation (6502 Emulator)
I made a repl.it, so you can assemble this in your browser, without having to install Python 3:
https://repl.it/@Tegmen/6502-Assembler?lite=true. If you get a 404, then you have to replace the “%40” in the url with a ‘@’ (or copy/paste the link instead of clicking on it).
Paste the code you want to assemble into the tab called “in” (its just right from main.py)
Press “Run”.
If you dont have any errors, you should get the message “Success” (may take a few seconds). You now finde the machine code in the tab “out”. Copy the hex-string, then import it into S65 via the Import-casette.
You can also check out the tabs “listing”, “constants” and “first_pass”. They might help you to debug your code.
https://repl.it/@Tegmen/6502-Assembler?lite=true. If you get a 404, then you have to replace the “%40” in the url with a ‘@’ (or copy/paste the link instead of clicking on it).
Paste the code you want to assemble into the tab called “in” (its just right from main.py)
Press “Run”.
If you dont have any errors, you should get the message “Success” (may take a few seconds). You now finde the machine code in the tab “out”. Copy the hex-string, then import it into S65 via the Import-casette.
You can also check out the tabs “listing”, “constants” and “first_pass”. They might help you to debug your code.
Last edited by MartinBraendli2 (Nov. 12, 2017 22:34:01)
- TheUltimatum
- Scratcher
1000+ posts
S65 Documentation (6502 Emulator)
Writing a C compiler for this would be interesting.
- MartinBraendli2
- Scratcher
100+ posts
S65 Documentation (6502 Emulator)
Writing a C compiler for this would be interesting.CC65 is a C-Compiler for the 6502. Maybe I'll write the standart libraries for the S65 sometime.
Edit: This shouldn't discurage you from trying to write a compiler yourself. I think the 6502 is a good platform to start, because its easiert to understand than for example the x86, with its thousands of opcodes. Maybe have a look at CC65 and see how they are doing it, then implement your own compiler for your own language. CC65 is also more managable than big name compilers like gcc, which are very well optimized but quite hard to read into.
Also, I updated the Documentation (Easiers printing and Redefining Characters)
Last edited by MartinBraendli2 (Nov. 12, 2017 23:43:41)
- TheUltimatum
- Scratcher
1000+ posts
S65 Documentation (6502 Emulator)
I was thinking of implementing it myself. As a good project to learn some assembly.Writing a C compiler for this would be interesting.CC65 is a C-Compiler for the 6502. Maybe I'll write the standart libraries for the S65 sometime.
Edit: This shouldn't discurage you from trying to write a compiler yourself. I think the 6502 is a good platform to start, because its easiert to understand than for example the x86, with its thousands of opcodes. Maybe have a look at CC65 and see how they are doing it, then implement your own compiler for your own language. CC65 is also more managable than big name compilers like gcc, which are very well optimized but quite hard to read into.
Also, I updated the Documentation (Easiers printing and Redefining Characters)
- bobbybee
- Scratcher
1000+ posts
S65 Documentation (6502 Emulator)
….stop tempting me Martin, I have too my things to do as it is!
“Ooo, can I call you Señorita Bee?” ~Chibi-Matoran
- mobluse
- Scratcher
100+ posts
S65 Documentation (6502 Emulator)
I made a video (in Swedish) with the version from 10 nov. 2017 of S65: https://youtu.be/k5Kc6scUrrA
It is about polynomial division in ehBasic with the example (x^2-2x-4)/(x-3)=x^2+x+3 with remainder 5, i.e. the same example as in Wikipedia: https://en.wikipedia.org/wiki/Polynomial_long_division .
This can be pasted in BASIC with the Insert key:
2O=0§4I=1§6Z=2§8E=3§10DIMD(E)§12J=I§14GOSUB72§16J=Z§18GOSUB72§20DIML(E,D(I))§22J=I§24GOSUB62§26J=Z§28GOSUB62§30D(E)=D(I)-D(Z)+I§32FORA=ITOD(E)§34L(E,A)=L(I,A)/L(Z,I)§36FORB=ZTOD(Z)§38L(I,A+B-I)=L(I,A+B-I)-L(E,A)*L(Z,B)§40NEXTB§42NEXTA§44?“X^”;D(I)-D(Z)§46FORA=ITOD(E)§48?L(E,A);§50NEXTA§52?§54FORA=D(I)-D(Z)+ZTOD(I)§56?L(I,A);§58NEXTA§60STOP§62FORA=ITOD(J)§64?“COEFX^”;D(J)-A;§66INPUTL(J,A)§68NEXTA§70RETURN§72INPUTD(J)§74D(J)=D(J)+I§76RETURN§
This forum software changes the double quotes to typographical quotes which might not work with S65 now. You would have to change those back manually.
Edit: I got the tip I could use the code-tag and then there are no problems with typographical quotes, but it is perhaps more difficult to copy:
It is about polynomial division in ehBasic with the example (x^2-2x-4)/(x-3)=x^2+x+3 with remainder 5, i.e. the same example as in Wikipedia: https://en.wikipedia.org/wiki/Polynomial_long_division .
This can be pasted in BASIC with the Insert key:
2O=0§4I=1§6Z=2§8E=3§10DIMD(E)§12J=I§14GOSUB72§16J=Z§18GOSUB72§20DIML(E,D(I))§22J=I§24GOSUB62§26J=Z§28GOSUB62§30D(E)=D(I)-D(Z)+I§32FORA=ITOD(E)§34L(E,A)=L(I,A)/L(Z,I)§36FORB=ZTOD(Z)§38L(I,A+B-I)=L(I,A+B-I)-L(E,A)*L(Z,B)§40NEXTB§42NEXTA§44?“X^”;D(I)-D(Z)§46FORA=ITOD(E)§48?L(E,A);§50NEXTA§52?§54FORA=D(I)-D(Z)+ZTOD(I)§56?L(I,A);§58NEXTA§60STOP§62FORA=ITOD(J)§64?“COEFX^”;D(J)-A;§66INPUTL(J,A)§68NEXTA§70RETURN§72INPUTD(J)§74D(J)=D(J)+I§76RETURN§
This forum software changes the double quotes to typographical quotes which might not work with S65 now. You would have to change those back manually.
Edit: I got the tip I could use the code-tag and then there are no problems with typographical quotes, but it is perhaps more difficult to copy:
2O=0§4I=1§6Z=2§8E=3§10DIMD(E)§12J=I§14GOSUB72§16J=Z§18GOSUB72§20DIML(E,D(I))§22J=I§24GOSUB62§26J=Z§28GOSUB62§30D(E)=D(I)-D(Z)+I§32FORA=ITOD(E)§34L(E,A)=L(I,A)/L(Z,I)§36FORB=ZTOD(Z)§38L(I,A+B-I)=L(I,A+B-I)-L(E,A)*L(Z,B)§40NEXTB§42NEXTA§44?"X^";D(I)-D(Z)§46FORA=ITOD(E)§48?L(E,A);§50NEXTA§52?§54FORA=D(I)-D(Z)+ZTOD(I)§56?L(I,A);§58NEXTA§60STOP§62FORA=ITOD(J)§64?"COEFX^";D(J)-A;§66INPUTL(J,A)§68NEXTA§70RETURN§72INPUTD(J)§74D(J)=D(J)+I§76RETURN§
Last edited by mobluse (Nov. 21, 2017 10:28:43)
- TheUltimatum
- Scratcher
1000+ posts
S65 Documentation (6502 Emulator)
https://youtu.be/k5Kc6scUrrAUse code tags. I made a video (in Swedish) with the version from 10 nov. 2017:
It is about polynomial division in ehBasic with the example: (x^2-2x-4)/(x-3) i.e. the same as in Wikipedia: https://en.wikipedia.org/wiki/Polynomial_long_division .
This can be pasted in BASIC with the Insert key:2O=0§4I=1§6Z=2§8E=3§10DIMD(E)§12J=I§14GOSUB72§16J=Z§18GOSUB72§20DIML(E,D(I))§22J=I§24GOSUB62§26J=Z§28GOSUB62§30D(E)=D(I)-D(Z)+I§32FORA=ITOD(E)§34L(E,A)=L(I,A)/L(Z,I)§36FORB=ZTOD(Z)§38L(I,A+B-I)=L(I,A+B-I)-L(E,A)*L(Z,B)§40NEXTB§42NEXTA§44?"X^";D(I)-D(Z)§46FORA=ITOD(E)§48?L(E,A);§50NEXTA§52?§54FORA=D(I)-D(Z)+ZTOD(I)§56?L(I,A);§58NEXTA§60STOP§62FORA=ITOD(J)§64?"COEFX^";D(J)-A;§66INPUTL(J,A)§68NEXTA§70RETURN§72INPUTD(J)§74D(J)=D(J)+I§76RETURN§
This forum software changes the double quotes to typographical quotes which might not work with S65 now. You would have to change those back manually.