Very, very well explained. Interestingly, this is exactly how things were done in the very early days of computing. Every CPU ever made always had (and will have) a mnemonic (human-readable text) version of every instruction/opcode (and variant) of the instruction set - purely because writing in machine code (numbers only) was never done (except as an academic exercise), for obvious reasons. The total number of mnemonic instructions then comprised that CPU's 'assembly language'. Programs would be written in assembly and the numeric version of each instruction/opcode (and operand if applicable) would be hand written against this. The operator would then enter in the numeric version to load the program - exactly what you see here. The BASIC loader program in this video then does the same thing as the early operators, poking each opcode/operand into memory.
I remember doing inline assembly on DOS, using Turbo Pascal. All the nice easy to use programming plus lightning fast pixel blitting and smooth VGA scrolling. My high school teacher was very impressed by the use of inline assembly in my projects (circa 1995)
Was doing the same thing, only nobody was impressed in my case. My teacher (in college, also around 1995) had no idea WHY I was writing stuff between this (for him) unknown asm keyword. He just thought I was showing off and making things too complex. In fact, that was the opposite of what I was actually doing. For sure the first steps towards me feeling completely alienated in school. I dropped out in my last year of college.
Nice. If you absolutely have to do it through BASIC, that's a very clear way to lay a simple program out. Assembling the code at a screen location was a neat trick. Makes you wonder if one could use PETSCII to enter machine code directly onto the screen, then poke (peek) statements to write it elsewhere...
Haha, I just wondered that too! But what about the unprintables? And what if you relocated this into a lineup that included 53280 ($D020)? We've had a Commodore 64 in my parents' family ever since I was 8, and here I am now, four decades later, having been the member of the family who got to keep this computer, finally understanding better what someone from way back then meant when he said, "Oh, that's the machine language" if I loaded something from disk wrong, like loading the directory with a 1 at the end of the load command (nonrelocating), or like loading some machine language programs without the putting the 1 at the end (relocation to the top of BASIC). I wish I would have understood this about memory locations a long time ago! So what does this mean in the context of modern computers such as current PCs and Macs, if we were to try writing a C program and then compiling it so that it ends up running in some memory address in the graphics card instead of the main RAM?
@@HelloKittyFanMan Compiling assembly to the graphics region of the 6502/VIC chip combination will give you garbage video settings and might do God knows what else. I'm very glad that the C64 OS was in ROM and completely immune from being permanently screwed over by anything you could do in software (the worst you can do is lose your program). Can you imagine having to load up the OS from a floppy which you subsequently lost or accidentally destroyed/overwrote?
Yes you can, or better yet, you can move the screen to a different location, say 12288, then print PETSCII to the screen and then move the screen back. This would essentially poke the code to a location other than the default screen location without using the slow poke and data process. I does take some time to figure out the correct PETSCII though and you have to keep turning reverse on and off depending on the character that is needed. for example: 10 poke 648,32:print"{home}{reverse on}{shift+S}{reverse off}{space}{reverse on}{shift + P}{reverse off}{shift +L}@0":poke 648,4:sys12288 This puts machine code at $3000 (12288) inc $d020 jmp $3000
It's really cool to see how it's done in a basic loader, but of course pretty much any decent monitor (Hesmon or SuperSnapshot) , or better yet an assembler like Merlin or Turbo Macro Pro makes this process a lot easier and more fun. heh
RUclipsr Iftkryo did a video on "Making 8-bit music from scratch" that uses this technique. A fun extension is that a negative table value like -192 for example moves the assembly program counter to page 192, allowing a simple way to separate subroutines and initialize hardware registers.
Just as a thought down the track - I would be inclined to go through the particular section of the Reference Guide, or photocopy it, and annotate all the opcodes with their decimal values. Another useful resource might be a chart of the numbers 0 to 255 with their hex and binary equivalents, and a second chart with branch-command values having 0 in the middle.
I'm so glad you asked this question. My original version of this code omitted the CMP instruction, and it worked perfectly fine. However, I noticed in the C64 Programmers Reference guide for the FFE4/GETIN function (Page 283 in my PDF copy of it), they were using the CMP instruction in their example, so to keep the video 'aligned to the programmer's reference guide' I included the CMP. In my opinion and very limited expierence, the CMP instruction is not needed.
@@MyDeveloperThoughts Had to look it up in the 6502 reference manual, too. LDA (which is used in the GETIN, of course) does influence the Z flag. So, yes, CMP #0 can be omitted.
This has me thinking about what it actually means to be documented as leaving a result in A, without specifically stating flags are set according to that result. In this specific case I also think “well, in this case what else would it even do?”, and on a 40 year old platform it’s not like there’s the potential of a new ROM release next week that would break dependence on current behavior. But if this were, say, 1984, I could see myself not wanting to depend on flag state coming out of calls like that. (Of course I may have missed something in my quick scan of the relevant page)
@@FirstLevelMagicthere are multiple official revisions of the c64 ROM and people have created lots of unofficial ones since 1982. Some recently. Even though LDA affects flags, there is no guarantee that the LDA is the last instruction.
This is absolutely wonderful! Thank you so so so much for making this video! Other than the reference guide, do you have any other good resources for this kind of thing? I don't really want to just "steal code" from people I'd rather make my own thing but I would still appreciate some other stepping stones to help me along
Get a copy of Turbo Assembler. Almost every demo ever made was written in it. There is even a REU version so you can use all memory without trashing the source code.
Haha, this is my first time of honestly trying to program the 128 through its monitor, ever since clear back in 1988 when we got it! But why doesn't this work? First, I typed F5 (245) to BEQ to but it questions me on that, so I skipped that part because we don't need keyboard input for the main feature. But then when I tell it to run at 0C000 (49152), it just breaks on me instead of changing border colors really fast! From direct BASIC, when I SYS409152, it just clears the screen without changing border color and then shows "ready" at the top. Why? And what even is at the subroutine that starts at $FFE4? (EDIT:) Oh, but then it kind of works in 64 mode if I Go64 or soft-reset into there! The auto-cycle doesn't work, but each time I SYS it steps the border to the next color! So: 1. how do you make the BEQ work in the monitor? 2. Why doesn't even the BEQless version work in 128 mode?
@@QuintarFarenor: Between the 64 and 128 I don't see how there would be any difference in routines to rapidly change the border or field colors. As you can see, it partially worked when I go64ed, so that means that 49152 is in an area of the RAM that both sides use; not just the second 64K, which only the 128 has access to. So RAM location isn't at issue; the BEQ instruction and other things are. But that's confusing because obviously the same processor is used on both sides for this. I don't even know how to access the Z-80 other than through CP/M, and I don't know how to program on it from there.
so if you goof and need to add more data statements later, you don't need to rewrite the active bit. mostly just good practice and useful for larger programs
@@chilleddriving1455: How did you do that? What was the brand name of that specific compiler? Did it come on a disk or a cartridge? And what did you like about it as compared to just using BASIC? And what sorts of things did you ever release with it, and to what pro level publicity once you started to become an adult, if any?
I stared at the book and the screen for hours trying to do assembly programming as an 8 year old. I wish I could have seen this 40 years ago.
Very, very well explained.
Interestingly, this is exactly how things were done in the very early days of computing. Every CPU ever made always had (and will have) a mnemonic (human-readable text) version of every instruction/opcode (and variant) of the instruction set - purely because writing in machine code (numbers only) was never done (except as an academic exercise), for obvious reasons. The total number of mnemonic instructions then comprised that CPU's 'assembly language'. Programs would be written in assembly and the numeric version of each instruction/opcode (and operand if applicable) would be hand written against this. The operator would then enter in the numeric version to load the program - exactly what you see here. The BASIC loader program in this video then does the same thing as the early operators, poking each opcode/operand into memory.
In the spirit of recording empirical results: I am in fact not surprised to see this - and a nice job of it, too!
I remember doing inline assembly on DOS, using Turbo Pascal. All the nice easy to use programming plus lightning fast pixel blitting and smooth VGA scrolling. My high school teacher was very impressed by the use of inline assembly in my projects (circa 1995)
Was doing the same thing, only nobody was impressed in my case. My teacher (in college, also around 1995) had no idea WHY I was writing stuff between this (for him) unknown asm keyword. He just thought I was showing off and making things too complex. In fact, that was the opposite of what I was actually doing. For sure the first steps towards me feeling completely alienated in school. I dropped out in my last year of college.
This is wonderful. I was wanting to learn assembly from the ground up, and this is perfect. Please make more!
Thanks for the video. I wish I‘d had something like that when I struggled (and failed) to learn machine code as a kid in the early eighties!🙂
Nice. If you absolutely have to do it through BASIC, that's a very clear way to lay a simple program out.
Assembling the code at a screen location was a neat trick. Makes you wonder if one could use PETSCII to enter machine code directly onto the screen, then poke (peek) statements to write it elsewhere...
Haha, I just wondered that too! But what about the unprintables? And what if you relocated this into a lineup that included 53280 ($D020)?
We've had a Commodore 64 in my parents' family ever since I was 8, and here I am now, four decades later, having been the member of the family who got to keep this computer, finally understanding better what someone from way back then meant when he said, "Oh, that's the machine language" if I loaded something from disk wrong, like loading the directory with a 1 at the end of the load command (nonrelocating), or like loading some machine language programs without the putting the 1 at the end (relocation to the top of BASIC). I wish I would have understood this about memory locations a long time ago!
So what does this mean in the context of modern computers such as current PCs and Macs, if we were to try writing a C program and then compiling it so that it ends up running in some memory address in the graphics card instead of the main RAM?
@@HelloKittyFanMan Compiling assembly to the graphics region of the 6502/VIC chip combination will give you garbage video settings and might do God knows what else. I'm very glad that the C64 OS was in ROM and completely immune from being permanently screwed over by anything you could do in software (the worst you can do is lose your program). Can you imagine having to load up the OS from a floppy which you subsequently lost or accidentally destroyed/overwrote?
Yes you can, or better yet, you can move the screen to a different location, say 12288, then print PETSCII to the screen and then move the screen back. This would essentially poke the code to a location other than the default screen location without using the slow poke and data process. I does take some time to figure out the correct PETSCII though and you have to keep turning reverse on and off depending on the character that is needed.
for example:
10 poke 648,32:print"{home}{reverse on}{shift+S}{reverse off}{space}{reverse on}{shift + P}{reverse off}{shift +L}@0":poke 648,4:sys12288
This puts machine code at $3000 (12288)
inc $d020
jmp $3000
Thanks for sharing, that's a great technique. Much better than endless DATA statements but of course takes up a lot of memory.
It's really cool to see how it's done in a basic loader, but of course pretty much any decent monitor (Hesmon or SuperSnapshot) , or better yet an assembler like Merlin or Turbo Macro Pro makes this process a lot easier and more fun. heh
RUclipsr Iftkryo did a video on "Making 8-bit music from scratch" that uses this technique. A fun extension is that a negative table value like -192 for example moves the assembly program counter to page 192, allowing a simple way to separate subroutines and initialize hardware registers.
Just as a thought down the track - I would be inclined to go through the particular section of the Reference Guide, or photocopy it, and annotate all the opcodes with their decimal values.
Another useful resource might be a chart of the numbers 0 to 255 with their hex and binary equivalents, and a second chart with branch-command values having 0 in the middle.
👍👍👍👍👍👍
Back in the day when memory was a scarce resource.
Plot twist at 9:10 is neat
So good, thank you. I’d love to see more.
This is machine code, assembly language is converted to machine code with an assembler.
Is the CMP really needed? If GETIN stores the result in register A, this will affect the zero flag, no?
I'm so glad you asked this question. My original version of this code omitted the CMP instruction, and it worked perfectly fine. However, I noticed in the C64 Programmers Reference guide for the FFE4/GETIN function (Page 283 in my PDF copy of it), they were using the CMP instruction in their example, so to keep the video 'aligned to the programmer's reference guide' I included the CMP. In my opinion and very limited expierence, the CMP instruction is not needed.
@@MyDeveloperThoughts Had to look it up in the 6502 reference manual, too. LDA (which is used in the GETIN, of course) does influence the Z flag. So, yes, CMP #0 can be omitted.
This has me thinking about what it actually means to be documented as leaving a result in A, without specifically stating flags are set according to that result. In this specific case I also think “well, in this case what else would it even do?”, and on a 40 year old platform it’s not like there’s the potential of a new ROM release next week that would break dependence on current behavior. But if this were, say, 1984, I could see myself not wanting to depend on flag state coming out of calls like that. (Of course I may have missed something in my quick scan of the relevant page)
I recomment ProfiAss64 😉
@@FirstLevelMagicthere are multiple official revisions of the c64 ROM and people have created lots of unofficial ones since 1982. Some recently. Even though LDA affects flags, there is no guarantee that the LDA is the last instruction.
This is absolutely wonderful! Thank you so so so much for making this video! Other than the reference guide, do you have any other good resources for this kind of thing? I don't really want to just "steal code" from people I'd rather make my own thing but I would still appreciate some other stepping stones to help me along
Get a copy of Turbo Assembler. Almost every demo ever made was written in it. There is even a REU version so you can use all memory without trashing the source code.
Where are you get the opcodes the C64?
C64 programmers reference manual (you can google it for a scanned pdf)
coooooool.
Haha, this is my first time of honestly trying to program the 128 through its monitor, ever since clear back in 1988 when we got it! But why doesn't this work? First, I typed F5 (245) to BEQ to but it questions me on that, so I skipped that part because we don't need keyboard input for the main feature. But then when I tell it to run at 0C000 (49152), it just breaks on me instead of changing border colors really fast! From direct BASIC, when I SYS409152, it just clears the screen without changing border color and then shows "ready" at the top. Why? And what even is at the subroutine that starts at $FFE4? (EDIT:) Oh, but then it kind of works in 64 mode if I Go64 or soft-reset into there! The auto-cycle doesn't work, but each time I SYS it steps the border to the next color! So: 1. how do you make the BEQ work in the monitor? 2. Why doesn't even the BEQless version work in 128 mode?
I'd try the C64 first before using this code on 128 not sure it's directly compareable
Everything else meant it crashed, apparently
@@QuintarFarenor: Between the 64 and 128 I don't see how there would be any difference in routines to rapidly change the border or field colors. As you can see, it partially worked when I go64ed, so that means that 49152 is in an area of the RAM that both sides use; not just the second 64K, which only the 128 has access to.
So RAM location isn't at issue; the BEQ instruction and other things are. But that's confusing because obviously the same processor is used on both sides for this.
I don't even know how to access the Z-80 other than through CP/M, and I don't know how to program on it from there.
What's the significance of putting the active part of the BASIC program down into starting with line 1000, instead of just 80?
No significance. Could have been any number. But it shows nicely that it’s completely separate from the DATA statements.
so if you goof and need to add more data statements later, you don't need to rewrite the active bit. mostly just good practice and useful for larger programs
@@OrangeDied: Isn't that what going by 10s was already for, though?
@@HelloKittyFanMan yea but making larger programs requires that much "whoops" room, i'd guess it's just force of habit for him
@@OrangeDied: It doesn't necessarily require it, but I guess I can see it being somewhat likely needed.
or inc $d020
a litle bit shorter
Why didn't you use COMAL?
You're sure that's even available for the Commodore 64?
@@HelloKittyFanMan Spent my entire childhood programming COMAL on my C64.
@@chilleddriving1455: How did you do that? What was the brand name of that specific compiler? Did it come on a disk or a cartridge? And what did you like about it as compared to just using BASIC? And what sorts of things did you ever release with it, and to what pro level publicity once you started to become an adult, if any?