I cannot agree that the simplicity of the 6502 wins over the 68000.
The 68000 has more registers and wider data types: but the registers are all uniformly the same. It's really just two registers A and D, copied a bunch of times in to D0 to D7, and A0 to A7. Whatever you can do with D0 can be done with D3 and so on. Some A's are dedicated, like for a stack pointer.
Simplicity of structure has to be balanced with simplicity of programming.
It's not that easy to program the 6502, because in moderately complex programs, you're constantly running into its limitations.
The best way to learn how to hack around the limitations of a tiny machine is to completely ignore them and become a seasoned software engineer. A seasoned engineer will read the instruction set manual in one setting, and other pertinent documents, and the clever hacks will start brewing in their head.
You don't have to start with tiny systems to get this skill. When you're new, you don't have the maturity for that; get something that's a bit easier to program with more addressing modes and easier handling of larger arrays, more registers, wider integers.
I come from a similar but different angle. Have been at 6502, 68k, z80, and some x86/64 over past few decades (mostly demo). I also wonder if this article/post was written by AI - he argues for 6502 that it has 6 registers, and thus it's simple. ANYONE who ever touched 6502 for more than a week will know you're pretty much dealing with three, and that's where the nickname of a sempahore CPU comes from (three registers).
6502 is fun and neat, especially if you want to program those machines. If you want more modern approach, it's not really all that suitable. Do what we did in the 90's then and start with MIPS if you want to follow the path of glory days or just start out with Neon. I'd even argue Z80 with all its registers and complexities is more similar to what you'll find today (far from it, but let's entertain the connection between past and present as OP does).
There's nothing inherently complicated about modern asm. Take FASM, start. It's not complicated, but it gets complex rather quickly which was and will be always true for such low level approach. Then you start macroing and sooner or later you have your own poor man's C on top.
I think one of the values 6502 provides is that it has so many retro platforms. Apple ][, NES, Atari 2600, BBC, C64, they all use 6502. If you want to do something cool retro stuffs that a lot of people today still enjoy, that's the best bet.
But again Z80 looks like a very good option too because it has GB/GBC plus ZX Spectrum. The same goes to 68K -- Mac 68K, Sega Genesis and Neogeo are popular hits.
When I was writing my reply to another question (https://news.ycombinator.com/item?id=42891200), I was thinking about a new software engineering education system which starts from a retro platform and goes up from there. Maybe all three can fit the bill. I'm very much against the "elegant/pure" way of teaching.
BTW totally agree 6502 essentially has 3 registers. You don't get to touch the others.
If you want to do something cool retro stuffs that a lot of people today still enjoy, that's the best bet.
This shouldn't be discounted, since it has great pedagogical value in itself. On the other hand, personal opinion here, is that not many things are transferable to modern ISAs. Even back in the 90's, we were shown to work on MIPS instead. 68k is very nice to read and write, almost feels like higher language, but overall ROI for newer stuff is maybe just jump into Neon, x86_64, or as someone said RV directly. It's not _THAT_ hard. It gets complex as programs grow though.
Yeah maybe jumping directly into x86-64 is not a bad idea. e.g. for Windows just have to understand the calling conventions and syscalls and such, to push some windows.
I need to look into my SDL2/C++ game's assembly code and see how it is done.
I get what you're saying but I wouldn't personally call zeropage bytes _registers_, even though they are physically connected to internal ones. I treat them as a special zone of memory that has special properties. There are many of these things and just like you would start with any of the old machines you'd first take a look at the memory map to see where's what and how to use it. Registers in a traditional sense would be those few mentioned of which you'd only ever really touch A, X, and Y.
One reason to consider them equivalent to registers is that the cycle time for zero page and register-based instructions is the same (2-3 cycles). The full 16-bit instructions ("absolute") take at least 1 extra cycle and often 2 if crossing a page.
Of course there is the Atari 2600 where you only have 128 of those bytes with RAM (80-FF) and the stack is set to page 0 instead of 1 (not that you'll use the stack a lot in an Atari 2600 program).
you're right, I meant $0000 and $0001 which _are_ registers. The rest 254, yeah. Zeropage is a must on C64 at least. I don't know much about other platforms.
So 0 and 1 are connected to the 6510 I/O data direction and I/O data register, but there is still RAM there too. Seems the 6510 won't issue write signals to the bus for addresses 0 and 1, but the VIC can see them and it's readable through sprite collisions.
> The best way to learn how to hack around the limitations of a tiny machine is to completely ignore them and become a seasoned software engineer
Learning to hack around the limitations of a tiny machine is a good step toward becoming a seasoned software (and possibly hardware) engineer!
The advantage of tiny systems is that you can understand them completely, from silicon to OS to software.
The 6809 is simpler than the 68K, and more powerful and orthogonal than the 6502, but there isn't the same software base for it. I think Motorola was onto something with the 6809 and 68K instruction sets and programmer-facing architecture. IIRC PDP-11/VAX and NS32K are similarly orthogonal.
The 6809 is the Chrysler Cordoba of 8 bit microprocessors, the personal sized luxury processor with styling in timeless good taste, rich Corinthian leather, and an efficient sophisticated multiply instruction.
I learned assembly on a 6809 (TRS-80 CoCo) platform. It was only later that I really appreciated how cool of a CPU it really was.
It’s a shame that Tandy missed the boat on including coprocessors for game support in their computers, especially that one. If they’d just included decent audio and maybe something for sprite management it would’ve been highly competitive.
Yeah, once OS-9 came out we got some decent game ports too. That’s where I discovered Epyx Rogue! It was very late in the lifespan of the system though.
C64/128 was what I was thinking of more than anything re 8-bit competition, keeping in mind I’m talking mid-late 80s by this point. I do also remember Atari 800 (and later) doing considerably better than you imply. But you’re right, Apple captured the early-mid 80s gaming market nicely.
6502 is a better "toy" asm than the Z80, but that's not saying much. It's not even obviously better than the AVR 8-bit insn set. As far as more modern platforms, I think there is a strong case for teaching RISC-V over something like MC68k. RISC-V can be very simple and elegant (in the base integer insn set) while still being very similar to other modern architectures like ARM, Aarch-64 and MIPS. It's also available in both 32 and 64-bit varieties, and the official documentation is very accessible.
MC68k just has tons of quirks which aren't really going to be relevant in the modern day and age. (About the only thing it has going for it is the huge variety of hardware platforms that happened to make use of it, many of which still have thriving retro communities around them. But that's more of a curiosity as opposed to something genuinely relevant.)
Some people believe that RISC-V is simple and elegant, other people believe that RISC-V is one of the worst and ugliest instruction-set architectures that have ever been conceived. Usually the first class of people consists of people with little experience in assembly programming and the second consists of those who had experience in using multiple ISAs for assembly programming.
Using RISC-V for executing programs written in high-level programming languages is fine. It should be noted that in the research papers that have coined the name RISC, where there was a list of properties defining what RISC means, one of them was that RISC ISAs were intended to be programmed exclusively in high-level languages and never in assembly language. From this point of view, RISC-V has certainly followed the original definition of the term RISC, by making the programming in assembly language difficult and error prone.
On the other hand, learning assembly programming with RISC-V is a great handicap because it does not expose the programmer to some of the most fundamental features of programming in an assembly language, because RISC-V does not have even many features that existed in Zilog Z80 or in vacuum-tube computers from 70 years ago, like integer overflow detection and indexed addressing.
Someone who has learned only RISC-V has an extremely incomplete image about how a normal instruction set architecture looks like. When faced with almost anyone of the several thousands of different ISAs that have been used during the last three quarters of a century a RISC-V assembly programmer will be puzzled.
6502 is also too quirky and unusual. I agree that among the old ISAs DEC PDP-11 or Motorola MC68000 are better ISAs for someone learning to program in an assembly language from scratch. Writing assembly programs for any of these 2 is much simpler than writing good assembly programs for RISC-V, where things as simple as doing a correct addition are quite difficult (by correct operation I mean an operation where any errors are handled appropriately).
This is the first time I’ve heard of this, but RISC-V not providing access to carry and overflow status seems insane. E.g. for BigNum implementations and constant-time (branchless) cryptography.
RISC-V does not have carry and overflow flags in the traditional sense. Which is actually great because it needs you don't have to specify the complicated details of how any single instruction might affect the flags. (And yes, it uses compare-and-branch instructions instead.) It does provide suggested insn sequences to check for overflow, which might be executed as a single instruction in a more full-featured implementation.
Those sequences of instructions transform the cheapest arithmetic operations into very expensive operations and they make unachievable the concurrent execution of up to 8 arithmetic instructions, which is possible in other modern CPU cores.
Using instruction fusion of a pair of instructions to achieve the effect of indexed addressing, but in a many times more complex and more inefficient way, is believable even if ugly.
Using instruction fusion to fuse the long sequence needed for checking overflow into a single operation is unbelievable.
Even if that were done, the immense discrepancy in complexity between detecting overflow with one extra XOR gate in a 64-bit adder that may have hundreds of gates, depending on its speed, and an instruction decoder capable of fetching ahead and decoding the corresponding long instruction sequence into one micro-operation is ridiculous.
I'm surprised RISC-V doesn't expose carry. Thanks for pointing this out. For embedded programming with AVR and ARM I often prefer ASM over C because I have access to the carry flag and I don't have to worry about C/C++ overflow undefined behavior.
I also agree the 6502 is not a simple ISA. However after learning 6502 machine code, MIPS, AVR, and ARMv6-M were all easy to learn.
Lol. I've been programming assembly language since 1980, on at least (that I can remember) 6502, 6800, 6809, 680x0, z80, 8086, PDP-11, VAX, Z8000, MIPS, SPARC, PA-RISC, PowerPC, Arm32, Arm64, AVR, PIC, MSP430, SuperH, and some proprietary ISAs on custom chips.
RISC-V is simply the best ISA I've ever used. It's got everything you need, without unnecessary complexity.
> RISC ISAs were intended to be programmed exclusively in high-level languages and never in assembly language.
That's a misunderstanding. RISC was designed to include only the instructions that high level language compilers found useful. There was never any intention to make assembly language programming difficult.
Some early RISC ISAs did make some of the housekeeping difficult for assembly language programmers by for example having branch delay slots, or no hardware interlocks between things such as loads or long-running instructions such as multiple or divide and the instructions that used their result. So if you counted wrong and tried to access the result register too soon you probably silently got the previous value.
That all went completely out the window as soon as there was a second implementation of the same ISA with a different number of pipeline stages, or more of less latency to cache, or a faster or slower divide instruction. And it was just completely untenable as soon as you got CPUs executing 2 or 3 instructions in each clock cycle instead of 1. The compiler could not calculate when it was safe to use a result because it didn't know what CPU version the code would be running on.
Modern RISC -- anything deigned since 1990 -- is completely fine to program in assembly language.
> RISC ISAs were intended to be programmed exclusively in high-level languages and never in assembly language.
> That's a misunderstanding. RISC was designed to include only the instructions that high level language compilers found useful. There was never any intention to make assembly language programming difficult.
That is no misunderstanding, but almost a quotation of the 4th point in the definition of the term "RISC", in “RISC I: A Reduced Instruction Set VLSI Computer”, David A. Patterson & Carlo H. Sequin (University of California, Berkeley), presented at ISCA '81.
Of course they did not try to make assembly language programming difficult as a goal, but the theory was that no ISA feature should be provided with the purpose of making the assembly programming easy, because it was expected that any inconvenience shall be handled by a compiler for a high-level language.
In practice, only the academic RISC ISAs from Berkeley and Stanford were less convenient to program in assembly language, while those from the industry, like IBM 801 and ARM and their successors did not have any disadvantage for programming in assembly language, on the contrary they were simpler to program than many less orthogonal older processors.
RISC-V has everything needed only in the same way as an UNIVAC from 1950 has everything needed.
As an ISA for didactic hardware implementation, RISC-V is very good. As a target for writing programs it is annoying whenever you are writing non-toy programs.
RISC-V has only one good feature in comparison with x86 or ARM, the combined compare-and-branch instructions, which saves a lot of instruction words in comparison with separate instructions, because branches are very frequent, e.g. one branch to every 6 to 8 instructions.
Except for that, there are too many important features that are missing without absolutely any justification and without any benefit.
Detecting integer overflow in hardware, like in almost all CPUs ever made, with the notable exceptions of Intel 8080, RISC-V and a few other that have never pretended to be suitable for general-purpose computing, is exceedingly simple and cheap. Detecting integer overflow in software is complicate and very expensive.
Implementing indexed addressing in hardware is extremely simple in comparison with implementing instruction fusion or with increasing the number of functional units and increasing the width of all structures to be able to execute more instructions concurrently, in order to reach the same performance for an ISA without indexed addressing.
While the loops of RISC-V usually save an instruction at the loop test, they waste at least one instruction at each memory access, so except for very simple loops RISC-V needs much more instructions for executing an interation than most other ISAs.
The RISC-V proponents have bogus excuses, i.e. the availability of the compressed mode and the possibility of using instruction fusion. These 2 are just workarounds for a bad instruction encoding that requires an excessive number of instructions for encoding any program. They can never be as good as a well designed instruction encoding.
All competing ISAs that are better encoded can also implement a compressed encoding (like ARM, MIPS and POWER also have) and they can implement instruction fusion. They seldom do this only because they seldom need this, unlike RISC-V.
I have programmed in about the same list as ISAs as enumerated by you, over about the same time interval. I agree that for most controller tasks RISC-V is acceptable, even if sub-optimal. When it is cheap enough (due to no royalties) that can overcome any other defects. On the other hand I have also written various programs dominated by complex computations, where the deficiencies in arithmetic and array addressing of RISC-V would have made it clearly unacceptable.
"4. Support high-level languages (HLL). An explanation of the degree of support follows. Our intention is always to use high-level languages with RISC I."
That doesn't say anything about making it difficult to use assembly language. It speaks only to making it UNNECESSARY, to the largest extent practical.
Maybe you weren't around at the time, but I remember very clearly the literature on the software crisis and the VAX being designed to make assembly language programming as productive as possible, EVEN at the expense of raw machine speed (the VAX 11/780 was slower than the PDP 11/70), because no one trusted the performance of high level languages.
The above aim of RISC was to make a machine fast enough and easy enough for compilers to use effectively that there would seldom be a reason to move out of the more productive high level language.
Great flamebait ;) what makes riscv suitable for teaching is that the ISA is trivial to map to hardware. This is because it is so regular. Students with zero experience can design simple riscv cpus in their first asm course. More experienced students can design cpus with pipelining, branch prediction, ooo execution, etc. Old ISAs from the 1980s are way more difficult.
RISC-V has been designed with the exact goal for being an ISA easy to implement in hardware, so that students will be able to do this.
For this purpose, RISC-V is excellent.
The RISC-V ISA has not been designed as an efficient method for encoding computer programs and even less for being easy to program in assembly language.
When programming in assembly language, a complex ISA is bad, because one cannot hold in mind all its peculiarities, but a too simple ISA is even worse, because every simple operation must be implemented by a complex sequence of instructions that is easy to forget and to get wrong.
I wonder if any of those who claim that the RISC-V ISA is simple can remember without searching the documentation how to implement the checks for integer overflow after each arithmetic operation.
The didactic examples of using RISC-V usually do not check for any errors, which is not acceptable in any critical application. I find funny that sometimes the same people who claim that the unsafe RISC-V ISA is good also claim that C and C++ are bad, because with default compilation options they are unsafe in comparison with e.g. Rust.
> I wonder if any of those who claim that the RISC-V ISA is simple can remember without searching the documentation how to implement the checks for integer overflow after each arithmetic operation.
I mean, I can't give you the Hacker's Delight magic numbers for expressing integer division by a constant via multiplication on AArch64 or x86-64 off the top of my head either, but that's what we have compilers for. The fact that you sometimes have to look up how to do simple things is just part of programming.
The 68K is still a tiny bit awkward, with its 24-bit bus and alignment restrictions and middling support for indexing into arrays (no scale factor). The 68020 is about as close to C as an ISA can get - it’s extraordinarily pleasant.
While I agree that 68020 felt like a great improvement over 68000 and 68010, scaled indexed addressing is an unnecessary feature in any ISA that has good support for post-incremented and pre-decremented addressing.
Scaled indexed addressing is useful in 80386 and successors only because they lack general support for addressing modes with register update, and also because they have INC/DEC instructions that are one-byte shorter than ADD/SUB, so it is preferable to add/subtract 1 to an index register, instead of adding/subtracting the operand size.
Scaled indexed addressing allows the writing with a minimum number of instructions of some loops where multiple arrays are accessed, even when those arrays have elements with different sizes. When all array elements have the same size, non-scaled indexed addressing is sufficient (because you increment the index register with the common operand size, not with 1).
However there are many loops where scaled index addressing is not enough for executing them with a minimum number of instructions, while using post-incremented/pre-decremented addressing still allows the minimum number of instructions (e.g. for arrays of structures or for multi-dimensional arrays).
Unfortunately not even MC68020 has complete support for auto-incremented addressing modes, because besides auto-incrementing with the operand size there are cases when one needs auto-incrementing with the increment in a register, like provided by CDC 6600, IBM 801, ARM, HP PA-RISC, IBM POWER and their successors (i.e. when the increment is an array stride that is unknown at compile-time).
On x86-64, using scaled indexed addressing is a necessity for efficient programs. On the other hand on ISAs like ARM, which have both scaled indexed addressing and auto-indexed addressing, it is possible to never use scaled indexed addressing without losing anything, so in such ISAs scaled indexed addressing is superfluous.
The 24-bit bus means that you can use the top bits of a pointer as a tag. In a small system we don't need that much memory, this can actually be a great advantage. We are rediscovering the value of tag bits in 64-bit systems.
While I learned programming with 6510, I agree that 68000 instruction set was much nicer and easier to read and learn. I would also chose 68000.
This said, 65XX on an early Commodore computers was extremely rewarding to use because there was no memory protection and you could write code altering video memory, mess with sprites, fonts, borders, interrupts, write self modifying code etc etc. 68000 assembly on Amiga was more safe and controlled.
When I was in college, they taught the Computer Architecture course using the 68000. Coded GUI stuff on the Mac 128k with assembler, and it was surprisingly easy, especially compared to doing anything with the 8086.
For anyone interested, learn the PDP-11 instruction set from the first model, the PDP-11/20, before DEC added protection modes, memory management hardware, etc. A summary of the instruction set is in Appendix A, and it's worth taking a look at chapters 3 and 4 of the PDP-11(/20) Handbook to get more detail on each instruction and the addressing modes.
My school started with a 6502 lab and then followed it with a 68k lab. That still seems like the right order to me all these years later. Starting with the smaller, more cramped chip and learning its limitations makes it all the more interesting when you can "spread your wings" into the larger, more capable one.
You scale up the complexity of the programs you need to build with the complexity of the chips. In the 6502 lab it was a lot about learning the basics of how things like an MMU works and building basic ones out of TTL logic gates. In the 68k lab you take an MMU for granted and do more ambitious things with that. There's useful skills both from knowing what the low level hardware is like as intimately as a 6502 can get versus the skills from where it is easier to program because you have more modern advantages and fewer limitations.
The other thing about that order was that breadboarding the 6502 hardware was a lot more complex, but it made up for it in that writing and debugging 6502 assembly was a lot easier. There are a ton of useful software emulators for the 6502 and you can debug your code easily before ever testing it on lab hardware. At one point in that lab I even just wrote my own mini-6502 emulator specific to our (intended) breadboard hardware design. On the other side, there a lot fewer software emulators for the 68k and the debug cycle was a lot rougher. The 68k breadboard hardware was a bit more "off the shelf" so it was easier to find an existing emulator that matched our (intended) hardware design, but emulator itself was buggier and more painful to use and ultimately testing on the real hardware was the only trustworthy way to deal with it. I also wasn't going to try to write my own 68k emulator. (Though some of the complexity there was the realities of hardware labs in that the hardware itself starts to pick up its own localized quirks from being run only on breadboards for years.)
Yeah. If you want to teach someone to implement a CPU, then the sort of simplicity the 6502 brings to the table is useful, but it's definitely not the most straightforward CPU to write code for.
The 68000 instruction set is very C-like (probably because it draws a lot of inspiration from the PDP-11 which was C's first target). Implementing it sucks (I know from experience) because the ISA is on the large side and all the little pragmatic exceptions add up. But because these exceptions are fairly pragmatic, they're not really a big issue when writing code.
One of the downsides having learned to code close to the metal on old CPUs is that you understood how that system worked and it‘s easy to assume assembler and the resulting machine code are the truth, but on modern CPUs that‘s a lie. A modern beefy out of order CPU will do insane optimization dynamically at runtime and the instructions you carefully scheduled to avoid register spills are nothing but a suggestion to the actual hardware. sigh<
That was never about the instruction set, it was more about the operating system -- or lack of one.
As for modern CPUs and OoO etc, that's only about performance. The CPU, no matter sophisticated, must produce exactly the same results as the simplest in-order CPU.
Hardware is never going to spill a register to RAM when you didn't write that. The maximum that is going to happen -- and this is pretty recent -- is that memory at small offsets from the SP might be treated as if it was extra registers, and this renaming can be tracked over smallish adjustments to the SP.
"You can't understand what modern CPUs do" is much overblown. Just write good clean code and it will work well on everything.
The performance is the point. 8-bit CPUs are so slow assembler could be - often had to be - hand-optimised for speed.
You can't do that on modern CPUs, because the nominal ISA has a very distant relationship to what happens inside the hardware.
The code you write gets optimised for you dynamically, and the machine is better at it than you are.
You may as well write in a high-level language, because the combination of optimising compilers with optimising hardware is probably going to be faster than any hand-written assembler. It's certainly going to be quicker to write.
The 68000 is a very nice, clean machine to code for, but the main point of doing it today is historical curiosity. It's quite distant from modern computing.
The original 68K instruction set is distant from modern computing only in these points:
- 32 bits
- lack of vectorization and such.
It's still perfect for most embedded stuff, by my estimation.
Well .... there are certain points like: wasn't there some issue with branch displacements on MC68K being confined to 16 bit ranges? If you have large functions, it can be a problem.
I dimly remember a project I was on circa 2001 to port a protocol stack (that we were developing on x86) to a system with some Freescale processor with a 68K instruction set.
I remember having to chop the source file into several translation units, because when it was all in one file, the inlining or static function calls or whatever, were generating PC relative branches that were too large for the opcode.
With today's hugeware, you'd be running into that left and right.
> I remember having to chop the source file into several translation units, because when it was all in one file, the inlining or static function calls or whatever, were generating PC relative branches that were too large for the opcode.
That's just inadequate tools.
With GNU as for RISC-V if I write `beq a1,a2,target` and target is more than 4k away then the assembler just silently emits `bne a1,a2,.+4; j target` instead.
The MC68K has a BRA instruction with an opcode 0110 0000 (0x60). The low byte of the 16 bit opcode word is a displacement. If it is 0, then the next 16 bit word has a 16 bit displacement. If that low byte is 0xFF, then the next two 16 bit words have a 32 bit displacement.
The displacement is PC relative.
This unconditional 0x60 opcode is just a special case of Bcc which ids 0x6N, where N is a condition type to check for a conditional branch. Bcc also has 8, 16 or 32 bit displacement.
So, yeah; that's not a problem. Not sure what the issue was with that GCC target; it somehow just didn't generate the bigger displacements.
Not sure why you were downvoted. What you say is true. You could count the cycles of the instructions to figure out exactly how long a loop would take at a given clock frequency. The entities manipulated by the program literally existed. If the ISA said there are 8 registers, you could find 8 areas of the silicon die corresponding to those. The ISA features on a modern processor are a fiction. Especially if it something riddled with 50 years of backwards compatibility, like Intel.
The 68000 has more registers and wider data types: but the registers are all uniformly the same. It's really just two registers A and D, copied a bunch of times in to D0 to D7, and A0 to A7. Whatever you can do with D0 can be done with D3 and so on. Some A's are dedicated, like for a stack pointer.
Simplicity of structure has to be balanced with simplicity of programming.
It's not that easy to program the 6502, because in moderately complex programs, you're constantly running into its limitations.
The best way to learn how to hack around the limitations of a tiny machine is to completely ignore them and become a seasoned software engineer. A seasoned engineer will read the instruction set manual in one setting, and other pertinent documents, and the clever hacks will start brewing in their head.
You don't have to start with tiny systems to get this skill. When you're new, you don't have the maturity for that; get something that's a bit easier to program with more addressing modes and easier handling of larger arrays, more registers, wider integers.