takes the 6502 ten to twenty bytes to do something which its closest rival, the Z80, could do in about five (and which an 8080 can do in two).
What operation takes more code on a Z80 than an 8080? It's been a while since I worked with them, but I believe the Z80 was an enhancement of the 8080/8085.
Overall this is a good explanation of how very limited the 6502 is compared to something like a Z80, especially from the perspective of a compiler writer; it's really best programmed in Asm, where a human will be more easily able to apply some "lateral thinking" to reformulate problems in a way more conducive to implementation. Nonetheless, C compilers for the 6502 exist (and they are still common as IP cores in embedded systems) --- but the dialect of C they accept is very much non-standard.
What the helper function does is to pop the return address of the stack, copy the next six bytes into a parameter area, perform the computation (remember that the parameters here all point at the destination variables), and then push the updated return address back onto the stack.
That reminds me of how compilers for the 8051 (another 8-bit MCU that is not "HLL friendly") deal with pointers: it has several separate address spaces, so pointers can be up to 3 bytes long, and thus the necessary dereferencing is accomplished by calling library functions:
I'm pretty sure he's talking about doing 16-bit arithmetic with the 8-bit carrying or simply multiplying numbers since that requires adding multiple times. I mean this code here is the "fast" way of multiplying numbers:
For fun I have been implementing a 6502 emulator in the last few days. It's a very strange platform in some regards but also the first assembler I learned (many years ago) so I'm confused at how I should feel. My goal has been to try to learn more z80 and x86 so I have something to compare it to.
6502 was from a different era. Not really designed to be a great compiler target.
Most other old 8-bitters have comparable issues.
One nitpick, decrement 16 bit value by one. From the article:
4 ae a0 aa ldx a+0
2 ca dex
4 8e a0 aa stx a+0
2 e0 ff cpx #ff
2 d0 03 bne label
6 ce a1 aa dec a+1
.label
(20 cycles maximum, 14 minimum, 14 bytes.)
It'd be better to do something like this, 11 bytes, (max 24/min 12 cycles, assuming article cycle counts are correct):
4 ae a0 aa ldx a+0
2 d0 03 bne label
6 ce a1 aa dec a+1
.label
6 ce a0 aa dec a+0
4 more cycles for the worst case, but that occurs only every 256th time, in which time the best case has already saved cumulative 510 cycles compared to the article version.
On 6502 one should really keep the variables on the zero page. Same operation would take just 8 bytes for that case.
> Sadly, I can't use this trick for decrements. In this case, I need to decrement the high byte when the low byte underflows from 0x00 to 0xff. Sadly, the CPU doesn't let us check for this for free, so I need to load it into a register and do an explicit comparison.
This got me thinking maybe it can be used for indexing from 0 - 128 for indexed addressing modes and that begs the question are the indexed addressing signed or unsigned? Apparently they are unsigned, so the trick might be possible:
It's in a very rough state, but I have been working on something similar. The idea is to build a superoptimizer by exhaustive enumeration of instruction sequences. In a first pass, each sequence is executed on some test machine states, and bucketed by the hash of the results. On the second pass, we use a theorem prover to check the equivalence of the sequences.
The memory-inefficiency of the 6502 in doing 16 bit arithmetic lead Steve Wozniak to write a tiny virtual machine he called "sweet 16". That is a compact 16 register 16 bit arithmetic bytecode language implemented in about 300 bytes of 6502 machine code. It trades off speed vs memory, at about 10 times slower but 2 times more dense than straight 6502 machine code for 16 bit operations. And it is designed to easily jump back and forth between the sweet 16 bytecode and 6502 machine code. It might not be a bad fit for what the author is trying to do!
[1] http://www.6502.org/source/interpreters/sweet16.htm
Wow! I had no idea he did this. I did something similar, but unlike Steve's interpreter, I used BRK instructions as prefixes to my pseudo-ops. This let me mix straight 6502 assembly with my pseudo-ops without switching between different modes. I wish I could find the code that I wrote for that ... probably in a printout in a box somewhere.
Yup, blew my mind when I discovered it 20+ years ago.
Note also that quite a few Apple ][ disk copy protections invented their own pcode like language to obfuscate their protection. They had something like 10-15 opcodes to do basic operations such as read/write tracks, registers or stacks, etc...
I wrote a FORTRAN IV (subset) compiler for the Apple ][ once. It used fixed-point arithmetic and the compiler output was subroutine threaded. E.g. a=b+c would translate to
jsr load
address of b
jsr load
address of c
jsr add
jsr store
address of a
Like in the method described by the author, subroutines would fetch their arguments (if any) from the code and adjust the return address. I have no idea how fast it was, but it was certainly faster than doing the same computations in Applesoft BASIC.
I don't want to say I invented a FORTRAN variant, but I also wrote a stack-based arithmetic engine for gbcpu (similar to z80/8080 but NOT THE SAME). It was used like:
This takes me back to the early 80s when "Personal Computer World" (UK) used to have a letters column called "Subset", where people would share short assembler routines for the 8 bit chips of the time. People would share optimisations, hidden opcodes, quirks about the chips etc. Often there'd be posts that shaved a byte or two or a few t-states off a previous months post. It's amazing how inventive people can be with limited resources.
It seems that as processors are so fast and memory so abundant now that squeezing the last drop of efficiency from a routine is a dying art.
A dying art? Yes and no... There have been quite a few recent articles posted here about squeezing the maximum performance out of compression algorithms, say, by clever use of SIMD operations.
It’s certainly less common now to be quite so concerned with shaving individual bytes off the code size. But I bet that still comes up when generating function prologues/epilogues/trampolines etc in compiler output -- stuff that will appear a lot in the output, so even one word saved will be significant.
And I imagine the energy savings of such a change over billions of devices can be phenomenal in some cases. I’m glad there are still people working on these things.
I think it’s true, though, that it’s now a much narrower subset of people — it’s not really desirable or practical for most hobbyists to dive deep into assembly, the way I remember seeing back in the ‘80s.
Ah 6502 arithmetic... Back in 1984 I wrote a 3D graphics toolkit for the VIC20 in 6502 assembler. Starting with fixed point multiply, then my own floating point format, then floating point arithmetic functions, including transcendentals. From there I added 3D to 2D perspective and orientation transforms. And finally Bressingham's line drawing algorithm, clipping, etc.
I was 14 at the time. Wrote the code in exercise books on a Christmas camping holiday and then typed it in when I got home. After a couple of weeks' debugging during the January summer school holidays, it all worked.
Kids nowadays don't know how easy they have it. Clone a Github repo... Pfffff !
Yeah but that was very rewarding in its own way. Every line of code you wrote was your own and you really learned more that way. I'm currently going through the theory on Scratchapixel to see how OpenGL actually works.
Yup. And once it was finished, I never used it, but moved on to my next challenge. That was robotics, using car windscreen wiper motors, which had incredible amounts of torque :)
How did it go? I had idea to use two wiper motors connected to arm through springs for each join to get variable stiffness joints. Wiper motors are especially good for that because they have worm gear so thry don't have to use energy to keep springs tensioned.
So obviously this is impressive for a 14 year old in 1984. What were your sources of information? What was your line of learning? It's very interesting not only that you were able to do this, but the path that led you to being able to do it. 14 year olds don't just re-discover years of work in their heads.
a) I won the VIC20 as a prize in a science competition age 13. I started with BASIC, discovered PEEK and POKE, and then saved up my pocketmoney for the Assembly Language cartridge. I was intrigued by the ROM and copied the entire disassembly into exercise books to figure out how it worked (printers were too expensive back then!). I reverse engineered the arithmetic routines, from which I discovered floating point representation's mantissas and exponents.
b) I expect the graphics transformations came from Mathematical Elements for Computer Graphics, by David Rogers and James Adams (McGraw-Hill, 1976), which I would have found in the library at my mum's work. Her work was opposite my school and I often killed an hour or two in the computer section reading back issues of Byte magazine waiting for her to drive me home. I think that was the book because prompted by your question, I just found Rogers' 1988 update Procedural Elements for Computer Graphics in my bookshelf. The price sticker inside the cover says '1/89 $41.99'. That shows I bought it with the money earned from my first university holiday job in Dec 1988/Jan 1989. That was at the research labs of BHP Billiton, the Australian mining company, where I designed and built the electronics for a 32-channel channel data logger for a steel rolling mill, and taught myself C and wrote the data acquisition and display software.
Well, the 6502 is simply not made to be programmed in a high level language.
Note that there are many instructions that simply don't map to high level language features, such as bit rotation (not shift!), BCD mode and indirectly indexed adressing.
One thing that it very popular in highly optimized 6502 assembler code is self-manipulating code or even run-time generated code. This allows to work around a lot of limitations with indexed adressing or parameter loading.
The 6502 was designed at a time when CPUs were not limited by memory speed. I imagine that has only been the case right after the emergence of the first MOS-Memory (Intel, late 60ies)
Edit: For those interested in state of the art optimized 6502 arithmetic, this is a very good source:
I imagine that 'programming the 6502' here refers to somehow creating code that the CPU executes directly, and so interpreted languages are automatically excluded.
And those BASIC interpreters were extremely slow. Clearly there are many interpreters and compilers for the 6502 and almost every other CPU architecture ever designed. Nobody said it's impossible to program the 6502 in high level languages, the architecture is just not designed to be a compiler/interpreter target and it is basically impossible to map many high level language constructs to efficient 6502 code.
This reminds me, why do most high-level languages lack a bit rotation operator?
It's an operation that is required in some cases, and it can easily be emulated by the compiler for targets which lack it. However deducing that you're trying to do bit rotation and emitting the CPU instruction when present is not easy for the compiler.
The original ARM C compiler ('Norcroft') did this a lot as I recall.
Relevant to the original post here of course, as the original spec was a processor that could interpret BBC BASIC as fast as hand optimised 6502 assembler.
I always wondered if the PDP-11 (which c was originally developed on) was missing rotate instructions.
Weirdly enough, it does have rotate instructions. Even weirder, there is no logical shift right instruction on the PDP-11. The original c compiler must have been emulating one by clearing the carry bit beteen rotates.
> However deducing that you're trying to do bit rotation and emitting the CPU instruction when present is not easy for the compiler.
Uh, no. a rotate_left b is equivalent to a << b | a >>> (sizeof_in_bits(a) - b). As far as common instructions not present in language standards go, regular rotates are the easiest for the compiler to recognize.
Still I wonder, why go through this guesswork in order to try to figure out what the user wants, instead of having an operator which clearly states what the user wants?
> I only have 64kB to play with, so it's important to be efficient.
If the author does not want to upgrade to the 1980s and the 65816 with its 16-bit accumulator, there is always the KimKlone for relieving the memory pressure somewhat.
The 65c816 (1984) adds a 16-bit ALU. Also MUL and DIV instructions. Even if you don't use its 24-bit address bus (which sucks to wire up), it is a big improvement
According to Bill Mensch and Chuck Peddle the 6502 (and I guess the 6800 work it was originally based on before the team left Motorola) was really designed for industrial applications, not for general computing. Hence its ultra-fast interrupt handling latency and minimalist low-cost design.
Absolutely the Z80 had a richer instruction set. As did the 6809. But the 6502 was more cycle efficient than the Z80, and also cheaper. It did well in the simple games and graphics oriented machines of the late 70s early 80s for this reason.
65c816 doesn't have multiplication or division. The SNES had multiplication/division support, as did at least one oddball 816 variant but stock 816s don't.
"The biggest problem is that multibyte arithmetic has to be processed in LSB to MSB order, with the carry propagating from one byte to the next; but on the 6502 it's so much easier to count down than it is to count up..."
Not that I've tried this (haven't programmed in 6502 assembly in 30+ years now) but doesn't that suggest he should order the bytes in the opposite direction? (ie, if I'm understanding correctly, he's using little-endian now but his comments make me wonder if big-endian might be more efficient on 6502.)
Naturally I get my fifteen minutes of internet fame while I'm on holiday on the pilgrimage paths in Japan, so this is going to be pretty short, but yeah, there are some mistakes in the article (`cpx #0xff`... bah); please post comments and I'll fix them. Eventually.
This was all in aid of writing the 6502 and Z80 backends for my self-hosted Ada-like programming language, Cowgol: http://cowlark.com/cowgol/ (Supports native compilation _on_ CP/M, Fuzix and BBC MOS, _for_ CP/M, Fuzix and BBC MOS!)
...and I put what I'd learned to work and have a program which will render Mandelbrots in under 13 seconds on a 2MHz 6502: http://cowlark.com/2018-05-26-bogomandel/ (Although I do need to point out that the key algorithm isn't mine!)
59 comments
[ 2.8 ms ] story [ 120 ms ] threadWhat operation takes more code on a Z80 than an 8080? It's been a while since I worked with them, but I believe the Z80 was an enhancement of the 8080/8085.
Overall this is a good explanation of how very limited the 6502 is compared to something like a Z80, especially from the perspective of a compiler writer; it's really best programmed in Asm, where a human will be more easily able to apply some "lateral thinking" to reformulate problems in a way more conducive to implementation. Nonetheless, C compilers for the 6502 exist (and they are still common as IP cores in embedded systems) --- but the dialect of C they accept is very much non-standard.
What the helper function does is to pop the return address of the stack, copy the next six bytes into a parameter area, perform the computation (remember that the parameters here all point at the destination variables), and then push the updated return address back onto the stack.
That reminds me of how compilers for the 8051 (another 8-bit MCU that is not "HLL friendly") deal with pointers: it has several separate address spaces, so pointers can be up to 3 bytes long, and thus the necessary dereferencing is accomplished by calling library functions:
http://www.keil.com/support/docs/1964.htm
http://www.6502.org/source/integers/fastmult.htm
Most other old 8-bitters have comparable issues.
One nitpick, decrement 16 bit value by one. From the article:
It'd be better to do something like this, 11 bytes, (max 24/min 12 cycles, assuming article cycle counts are correct): 4 more cycles for the worst case, but that occurs only every 256th time, in which time the best case has already saved cumulative 510 cycles compared to the article version.On 6502 one should really keep the variables on the zero page. Same operation would take just 8 bytes for that case.
Oops. Other numbers are correct, but of course the worst case takes only 18 cycles, not 24.
It still takes only 12 cycles if a+0 != 0. 24 cycles if also a+1 != 0 (=every 256th round), etc.
The worst case time is when whole 32-bit counter is zero, 12 clk * 3 + 6 clk = 42 clk.
(Disclaimer: clock counts assume original snippet counts were correct).
Can you check the ~overflow~ negative flag?
http://www.obelisk.me.uk/6502/reference.html#DEC
Your link states overflow flag is not affected by DEC.
http://forum.6502.org/viewtopic.php?t=1300
See: Automatic Generation of Peephole Superoptimizers, Sorav Bansal and Alex Aiken, ASPLOS 2006. https://theory.stanford.edu/~aiken/publications/papers/asplo...
My implementation: https://github.com/RussellSprouts/6502-enumerator
Note also that quite a few Apple ][ disk copy protections invented their own pcode like language to obfuscate their protection. They had something like 10-15 opcodes to do basic operations such as read/write tracks, registers or stacks, etc...
It seems that as processors are so fast and memory so abundant now that squeezing the last drop of efficiency from a routine is a dying art.
https://en.wikipedia.org/wiki/Personal_Computer_World
Many of the Z80 and 6502 routines have been collected together into a couple of books, mentioned here:
http://mycodehere.blogspot.com/2011/10/my-work-in-print-1985...
It’s certainly less common now to be quite so concerned with shaving individual bytes off the code size. But I bet that still comes up when generating function prologues/epilogues/trampolines etc in compiler output -- stuff that will appear a lot in the output, so even one word saved will be significant.
I think it’s true, though, that it’s now a much narrower subset of people — it’s not really desirable or practical for most hobbyists to dive deep into assembly, the way I remember seeing back in the ‘80s.
I was 14 at the time. Wrote the code in exercise books on a Christmas camping holiday and then typed it in when I got home. After a couple of weeks' debugging during the January summer school holidays, it all worked.
Kids nowadays don't know how easy they have it. Clone a Github repo... Pfffff !
Thank you!!
a) I won the VIC20 as a prize in a science competition age 13. I started with BASIC, discovered PEEK and POKE, and then saved up my pocketmoney for the Assembly Language cartridge. I was intrigued by the ROM and copied the entire disassembly into exercise books to figure out how it worked (printers were too expensive back then!). I reverse engineered the arithmetic routines, from which I discovered floating point representation's mantissas and exponents.
b) I expect the graphics transformations came from Mathematical Elements for Computer Graphics, by David Rogers and James Adams (McGraw-Hill, 1976), which I would have found in the library at my mum's work. Her work was opposite my school and I often killed an hour or two in the computer section reading back issues of Byte magazine waiting for her to drive me home. I think that was the book because prompted by your question, I just found Rogers' 1988 update Procedural Elements for Computer Graphics in my bookshelf. The price sticker inside the cover says '1/89 $41.99'. That shows I bought it with the money earned from my first university holiday job in Dec 1988/Jan 1989. That was at the research labs of BHP Billiton, the Australian mining company, where I designed and built the electronics for a 32-channel channel data logger for a steel rolling mill, and taught myself C and wrote the data acquisition and display software.
Note that there are many instructions that simply don't map to high level language features, such as bit rotation (not shift!), BCD mode and indirectly indexed adressing.
One thing that it very popular in highly optimized 6502 assembler code is self-manipulating code or even run-time generated code. This allows to work around a lot of limitations with indexed adressing or parameter loading.
The 6502 was designed at a time when CPUs were not limited by memory speed. I imagine that has only been the case right after the emergence of the first MOS-Memory (Intel, late 60ies)
Edit: For those interested in state of the art optimized 6502 arithmetic, this is a very good source:
http://codebase64.org/doku.php?id=base:6502_6510_maths
It's an operation that is required in some cases, and it can easily be emulated by the compiler for targets which lack it. However deducing that you're trying to do bit rotation and emitting the CPU instruction when present is not easy for the compiler.
Relevant to the original post here of course, as the original spec was a processor that could interpret BBC BASIC as fast as hand optimised 6502 assembler.
Weirdly enough, it does have rotate instructions. Even weirder, there is no logical shift right instruction on the PDP-11. The original c compiler must have been emulating one by clearing the carry bit beteen rotates.
https://www.bell-labs.com/usr/dmr/www/chist.html
Uh, no. a rotate_left b is equivalent to a << b | a >>> (sizeof_in_bits(a) - b). As far as common instructions not present in language standards go, regular rotates are the easiest for the compiler to recognize.
edit: For example, VS2017 x86 target does not output rol instruction for your code with /O1.
For the record, here was what I was testing:
If the author does not want to upgrade to the 1980s and the 65816 with its 16-bit accumulator, there is always the KimKlone for relieving the memory pressure somewhat.
* http://laughtonelectronics.com/Arcana/KimKlone/Kimklone_shor... (https://news.ycombinator.com/item?id=16564257)
* https://news.ycombinator.com/item?id=17278022
According to Bill Mensch and Chuck Peddle the 6502 (and I guess the 6800 work it was originally based on before the team left Motorola) was really designed for industrial applications, not for general computing. Hence its ultra-fast interrupt handling latency and minimalist low-cost design.
Absolutely the Z80 had a richer instruction set. As did the 6809. But the 6502 was more cycle efficient than the Z80, and also cheaper. It did well in the simple games and graphics oriented machines of the late 70s early 80s for this reason.
Like, oh, Woz's "Sweet 16".
https://en.wikipedia.org/wiki/SWEET16
"The SWEET16 interpreter itself is located from $F689 to $F7FC in the Integer BASIC ROM." I.e. 371 bytes of code.
Not that I've tried this (haven't programmed in 6502 assembly in 30+ years now) but doesn't that suggest he should order the bytes in the opposite direction? (ie, if I'm understanding correctly, he's using little-endian now but his comments make me wonder if big-endian might be more efficient on 6502.)
Naturally I get my fifteen minutes of internet fame while I'm on holiday on the pilgrimage paths in Japan, so this is going to be pretty short, but yeah, there are some mistakes in the article (`cpx #0xff`... bah); please post comments and I'll fix them. Eventually.
There's also a followup article on doing exactly the same thing on the Z80: http://cowlark.com/2018-03-18-z80-arithmetic/
This was all in aid of writing the 6502 and Z80 backends for my self-hosted Ada-like programming language, Cowgol: http://cowlark.com/cowgol/ (Supports native compilation _on_ CP/M, Fuzix and BBC MOS, _for_ CP/M, Fuzix and BBC MOS!)
...and I put what I'd learned to work and have a program which will render Mandelbrots in under 13 seconds on a 2MHz 6502: http://cowlark.com/2018-05-26-bogomandel/ (Although I do need to point out that the key algorithm isn't mine!)